18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * /proc/bus/pnp interface for Plug and Play devices 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Written by David Hinds, dahinds@users.sourceforge.net 68c2ecf20Sopenharmony_ci * Modified by Thomas Hood 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * The .../devices and .../<node> and .../boot/<node> files are 98c2ecf20Sopenharmony_ci * utilized by the lspnp and setpnp utilities, supplied with the 108c2ecf20Sopenharmony_ci * pcmcia-cs package. 118c2ecf20Sopenharmony_ci * http://pcmcia-cs.sourceforge.net 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * The .../escd file is utilized by the lsescd utility written by 148c2ecf20Sopenharmony_ci * Gunther Mayer. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * The .../legacy_device_resources file is not used yet. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * The other files are human-readable. 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#include <linux/module.h> 228c2ecf20Sopenharmony_ci#include <linux/kernel.h> 238c2ecf20Sopenharmony_ci#include <linux/slab.h> 248c2ecf20Sopenharmony_ci#include <linux/types.h> 258c2ecf20Sopenharmony_ci#include <linux/proc_fs.h> 268c2ecf20Sopenharmony_ci#include <linux/pnp.h> 278c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 288c2ecf20Sopenharmony_ci#include <linux/init.h> 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#include "pnpbios.h" 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic struct proc_dir_entry *proc_pnp = NULL; 358c2ecf20Sopenharmony_cistatic struct proc_dir_entry *proc_pnp_boot = NULL; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_cistatic int pnpconfig_proc_show(struct seq_file *m, void *v) 388c2ecf20Sopenharmony_ci{ 398c2ecf20Sopenharmony_ci struct pnp_isa_config_struc pnps; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci if (pnp_bios_isapnp_config(&pnps)) 428c2ecf20Sopenharmony_ci return -EIO; 438c2ecf20Sopenharmony_ci seq_printf(m, "structure_revision %d\n" 448c2ecf20Sopenharmony_ci "number_of_CSNs %d\n" 458c2ecf20Sopenharmony_ci "ISA_read_data_port 0x%x\n", 468c2ecf20Sopenharmony_ci pnps.revision, pnps.no_csns, pnps.isa_rd_data_port); 478c2ecf20Sopenharmony_ci return 0; 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int escd_info_proc_show(struct seq_file *m, void *v) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct escd_info_struc escd; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci if (pnp_bios_escd_info(&escd)) 558c2ecf20Sopenharmony_ci return -EIO; 568c2ecf20Sopenharmony_ci seq_printf(m, "min_ESCD_write_size %d\n" 578c2ecf20Sopenharmony_ci "ESCD_size %d\n" 588c2ecf20Sopenharmony_ci "NVRAM_base 0x%x\n", 598c2ecf20Sopenharmony_ci escd.min_escd_write_size, 608c2ecf20Sopenharmony_ci escd.escd_size, escd.nv_storage_base); 618c2ecf20Sopenharmony_ci return 0; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci#define MAX_SANE_ESCD_SIZE (32*1024) 658c2ecf20Sopenharmony_cistatic int escd_proc_show(struct seq_file *m, void *v) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci struct escd_info_struc escd; 688c2ecf20Sopenharmony_ci char *tmpbuf; 698c2ecf20Sopenharmony_ci int escd_size; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci if (pnp_bios_escd_info(&escd)) 728c2ecf20Sopenharmony_ci return -EIO; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci /* sanity check */ 758c2ecf20Sopenharmony_ci if (escd.escd_size > MAX_SANE_ESCD_SIZE) { 768c2ecf20Sopenharmony_ci printk(KERN_ERR 778c2ecf20Sopenharmony_ci "PnPBIOS: %s: ESCD size reported by BIOS escd_info call is too great\n", __func__); 788c2ecf20Sopenharmony_ci return -EFBIG; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci tmpbuf = kzalloc(escd.escd_size, GFP_KERNEL); 828c2ecf20Sopenharmony_ci if (!tmpbuf) 838c2ecf20Sopenharmony_ci return -ENOMEM; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci if (pnp_bios_read_escd(tmpbuf, escd.nv_storage_base)) { 868c2ecf20Sopenharmony_ci kfree(tmpbuf); 878c2ecf20Sopenharmony_ci return -EIO; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci escd_size = 918c2ecf20Sopenharmony_ci (unsigned char)(tmpbuf[0]) + (unsigned char)(tmpbuf[1]) * 256; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* sanity check */ 948c2ecf20Sopenharmony_ci if (escd_size > MAX_SANE_ESCD_SIZE) { 958c2ecf20Sopenharmony_ci printk(KERN_ERR "PnPBIOS: %s: ESCD size reported by" 968c2ecf20Sopenharmony_ci " BIOS read_escd call is too great\n", __func__); 978c2ecf20Sopenharmony_ci kfree(tmpbuf); 988c2ecf20Sopenharmony_ci return -EFBIG; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci seq_write(m, tmpbuf, escd_size); 1028c2ecf20Sopenharmony_ci kfree(tmpbuf); 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic int pnp_legacyres_proc_show(struct seq_file *m, void *v) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci void *buf; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci buf = kmalloc(65536, GFP_KERNEL); 1118c2ecf20Sopenharmony_ci if (!buf) 1128c2ecf20Sopenharmony_ci return -ENOMEM; 1138c2ecf20Sopenharmony_ci if (pnp_bios_get_stat_res(buf)) { 1148c2ecf20Sopenharmony_ci kfree(buf); 1158c2ecf20Sopenharmony_ci return -EIO; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci seq_write(m, buf, 65536); 1198c2ecf20Sopenharmony_ci kfree(buf); 1208c2ecf20Sopenharmony_ci return 0; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int pnp_devices_proc_show(struct seq_file *m, void *v) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci struct pnp_bios_node *node; 1268c2ecf20Sopenharmony_ci u8 nodenum; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci node = kzalloc(node_info.max_node_size, GFP_KERNEL); 1298c2ecf20Sopenharmony_ci if (!node) 1308c2ecf20Sopenharmony_ci return -ENOMEM; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci for (nodenum = 0; nodenum < 0xff;) { 1338c2ecf20Sopenharmony_ci u8 thisnodenum = nodenum; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (pnp_bios_get_dev_node(&nodenum, PNPMODE_DYNAMIC, node)) 1368c2ecf20Sopenharmony_ci break; 1378c2ecf20Sopenharmony_ci seq_printf(m, "%02x\t%08x\t%3phC\t%04x\n", 1388c2ecf20Sopenharmony_ci node->handle, node->eisa_id, 1398c2ecf20Sopenharmony_ci node->type_code, node->flags); 1408c2ecf20Sopenharmony_ci if (nodenum <= thisnodenum) { 1418c2ecf20Sopenharmony_ci printk(KERN_ERR 1428c2ecf20Sopenharmony_ci "%s Node number 0x%x is out of sequence following node 0x%x. Aborting.\n", 1438c2ecf20Sopenharmony_ci "PnPBIOS: proc_read_devices:", 1448c2ecf20Sopenharmony_ci (unsigned int)nodenum, 1458c2ecf20Sopenharmony_ci (unsigned int)thisnodenum); 1468c2ecf20Sopenharmony_ci break; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci kfree(node); 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic int pnpbios_proc_show(struct seq_file *m, void *v) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci void *data = m->private; 1568c2ecf20Sopenharmony_ci struct pnp_bios_node *node; 1578c2ecf20Sopenharmony_ci int boot = (long)data >> 8; 1588c2ecf20Sopenharmony_ci u8 nodenum = (long)data; 1598c2ecf20Sopenharmony_ci int len; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci node = kzalloc(node_info.max_node_size, GFP_KERNEL); 1628c2ecf20Sopenharmony_ci if (!node) 1638c2ecf20Sopenharmony_ci return -ENOMEM; 1648c2ecf20Sopenharmony_ci if (pnp_bios_get_dev_node(&nodenum, boot, node)) { 1658c2ecf20Sopenharmony_ci kfree(node); 1668c2ecf20Sopenharmony_ci return -EIO; 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci len = node->size - sizeof(struct pnp_bios_node); 1698c2ecf20Sopenharmony_ci seq_write(m, node->data, len); 1708c2ecf20Sopenharmony_ci kfree(node); 1718c2ecf20Sopenharmony_ci return 0; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic int pnpbios_proc_open(struct inode *inode, struct file *file) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci return single_open(file, pnpbios_proc_show, PDE_DATA(inode)); 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic ssize_t pnpbios_proc_write(struct file *file, const char __user *buf, 1808c2ecf20Sopenharmony_ci size_t count, loff_t *pos) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci void *data = PDE_DATA(file_inode(file)); 1838c2ecf20Sopenharmony_ci struct pnp_bios_node *node; 1848c2ecf20Sopenharmony_ci int boot = (long)data >> 8; 1858c2ecf20Sopenharmony_ci u8 nodenum = (long)data; 1868c2ecf20Sopenharmony_ci int ret = count; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci node = kzalloc(node_info.max_node_size, GFP_KERNEL); 1898c2ecf20Sopenharmony_ci if (!node) 1908c2ecf20Sopenharmony_ci return -ENOMEM; 1918c2ecf20Sopenharmony_ci if (pnp_bios_get_dev_node(&nodenum, boot, node)) { 1928c2ecf20Sopenharmony_ci ret = -EIO; 1938c2ecf20Sopenharmony_ci goto out; 1948c2ecf20Sopenharmony_ci } 1958c2ecf20Sopenharmony_ci if (count != node->size - sizeof(struct pnp_bios_node)) { 1968c2ecf20Sopenharmony_ci ret = -EINVAL; 1978c2ecf20Sopenharmony_ci goto out; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci if (copy_from_user(node->data, buf, count)) { 2008c2ecf20Sopenharmony_ci ret = -EFAULT; 2018c2ecf20Sopenharmony_ci goto out; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci if (pnp_bios_set_dev_node(node->handle, boot, node) != 0) { 2048c2ecf20Sopenharmony_ci ret = -EINVAL; 2058c2ecf20Sopenharmony_ci goto out; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci ret = count; 2088c2ecf20Sopenharmony_ciout: 2098c2ecf20Sopenharmony_ci kfree(node); 2108c2ecf20Sopenharmony_ci return ret; 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic const struct proc_ops pnpbios_proc_ops = { 2148c2ecf20Sopenharmony_ci .proc_open = pnpbios_proc_open, 2158c2ecf20Sopenharmony_ci .proc_read = seq_read, 2168c2ecf20Sopenharmony_ci .proc_lseek = seq_lseek, 2178c2ecf20Sopenharmony_ci .proc_release = single_release, 2188c2ecf20Sopenharmony_ci .proc_write = pnpbios_proc_write, 2198c2ecf20Sopenharmony_ci}; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ciint pnpbios_interface_attach_device(struct pnp_bios_node *node) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci char name[3]; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci sprintf(name, "%02x", node->handle); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci if (!proc_pnp) 2288c2ecf20Sopenharmony_ci return -EIO; 2298c2ecf20Sopenharmony_ci if (!pnpbios_dont_use_current_config) { 2308c2ecf20Sopenharmony_ci proc_create_data(name, 0644, proc_pnp, &pnpbios_proc_ops, 2318c2ecf20Sopenharmony_ci (void *)(long)(node->handle)); 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (!proc_pnp_boot) 2358c2ecf20Sopenharmony_ci return -EIO; 2368c2ecf20Sopenharmony_ci if (proc_create_data(name, 0644, proc_pnp_boot, &pnpbios_proc_ops, 2378c2ecf20Sopenharmony_ci (void *)(long)(node->handle + 0x100))) 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci return -EIO; 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci/* 2438c2ecf20Sopenharmony_ci * When this is called, pnpbios functions are assumed to 2448c2ecf20Sopenharmony_ci * work and the pnpbios_dont_use_current_config flag 2458c2ecf20Sopenharmony_ci * should already have been set to the appropriate value 2468c2ecf20Sopenharmony_ci */ 2478c2ecf20Sopenharmony_ciint __init pnpbios_proc_init(void) 2488c2ecf20Sopenharmony_ci{ 2498c2ecf20Sopenharmony_ci proc_pnp = proc_mkdir("bus/pnp", NULL); 2508c2ecf20Sopenharmony_ci if (!proc_pnp) 2518c2ecf20Sopenharmony_ci return -EIO; 2528c2ecf20Sopenharmony_ci proc_pnp_boot = proc_mkdir("boot", proc_pnp); 2538c2ecf20Sopenharmony_ci if (!proc_pnp_boot) 2548c2ecf20Sopenharmony_ci return -EIO; 2558c2ecf20Sopenharmony_ci proc_create_single("devices", 0, proc_pnp, pnp_devices_proc_show); 2568c2ecf20Sopenharmony_ci proc_create_single("configuration_info", 0, proc_pnp, 2578c2ecf20Sopenharmony_ci pnpconfig_proc_show); 2588c2ecf20Sopenharmony_ci proc_create_single("escd_info", 0, proc_pnp, escd_info_proc_show); 2598c2ecf20Sopenharmony_ci proc_create_single("escd", S_IRUSR, proc_pnp, escd_proc_show); 2608c2ecf20Sopenharmony_ci proc_create_single("legacy_device_resources", 0, proc_pnp, 2618c2ecf20Sopenharmony_ci pnp_legacyres_proc_show); 2628c2ecf20Sopenharmony_ci return 0; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_civoid __exit pnpbios_proc_exit(void) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci int i; 2688c2ecf20Sopenharmony_ci char name[3]; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci if (!proc_pnp) 2718c2ecf20Sopenharmony_ci return; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci for (i = 0; i < 0xff; i++) { 2748c2ecf20Sopenharmony_ci sprintf(name, "%02x", i); 2758c2ecf20Sopenharmony_ci if (!pnpbios_dont_use_current_config) 2768c2ecf20Sopenharmony_ci remove_proc_entry(name, proc_pnp); 2778c2ecf20Sopenharmony_ci remove_proc_entry(name, proc_pnp_boot); 2788c2ecf20Sopenharmony_ci } 2798c2ecf20Sopenharmony_ci remove_proc_entry("legacy_device_resources", proc_pnp); 2808c2ecf20Sopenharmony_ci remove_proc_entry("escd", proc_pnp); 2818c2ecf20Sopenharmony_ci remove_proc_entry("escd_info", proc_pnp); 2828c2ecf20Sopenharmony_ci remove_proc_entry("configuration_info", proc_pnp); 2838c2ecf20Sopenharmony_ci remove_proc_entry("devices", proc_pnp); 2848c2ecf20Sopenharmony_ci remove_proc_entry("boot", proc_pnp); 2858c2ecf20Sopenharmony_ci remove_proc_entry("bus/pnp", NULL); 2868c2ecf20Sopenharmony_ci} 287