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