162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Linux/SPARC PROM Configuration Driver
462306a36Sopenharmony_ci * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
562306a36Sopenharmony_ci * Copyright (C) 1996 Eddie C. Dost  (ecd@skynet.be)
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This character device driver allows user programs to access the
862306a36Sopenharmony_ci * PROM device tree. It is compatible with the SunOS /dev/openprom
962306a36Sopenharmony_ci * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
1062306a36Sopenharmony_ci * utility works without any modifications.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * The driver uses a minor number under the misc device major. The
1362306a36Sopenharmony_ci * file read/write mode determines the type of access to the PROM.
1462306a36Sopenharmony_ci * Interrupts are disabled whenever the driver calls into the PROM for
1562306a36Sopenharmony_ci * sanity's sake.
1662306a36Sopenharmony_ci */
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/errno.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/mutex.h>
2462306a36Sopenharmony_ci#include <linux/string.h>
2562306a36Sopenharmony_ci#include <linux/miscdevice.h>
2662306a36Sopenharmony_ci#include <linux/init.h>
2762306a36Sopenharmony_ci#include <linux/fs.h>
2862306a36Sopenharmony_ci#include <asm/oplib.h>
2962306a36Sopenharmony_ci#include <asm/prom.h>
3062306a36Sopenharmony_ci#include <linux/uaccess.h>
3162306a36Sopenharmony_ci#include <asm/openpromio.h>
3262306a36Sopenharmony_ci#ifdef CONFIG_PCI
3362306a36Sopenharmony_ci#include <linux/pci.h>
3462306a36Sopenharmony_ci#endif
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ciMODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost  (ecd@skynet.be)");
3762306a36Sopenharmony_ciMODULE_DESCRIPTION("OPENPROM Configuration Driver");
3862306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3962306a36Sopenharmony_ciMODULE_VERSION("1.0");
4062306a36Sopenharmony_ciMODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci/* Private data kept by the driver for each descriptor. */
4362306a36Sopenharmony_citypedef struct openprom_private_data
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct device_node *current_node; /* Current node for SunOS ioctls. */
4662306a36Sopenharmony_ci	struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
4762306a36Sopenharmony_ci} DATA;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* ID of the PROM node containing all of the EEPROM options. */
5062306a36Sopenharmony_cistatic DEFINE_MUTEX(openprom_mutex);
5162306a36Sopenharmony_cistatic struct device_node *options_node;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci/*
5462306a36Sopenharmony_ci * Copy an openpromio structure into kernel space from user space.
5562306a36Sopenharmony_ci * This routine does error checking to make sure that all memory
5662306a36Sopenharmony_ci * accesses are within bounds. A pointer to the allocated openpromio
5762306a36Sopenharmony_ci * structure will be placed in "*opp_p". Return value is the length
5862306a36Sopenharmony_ci * of the user supplied buffer.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_cistatic int copyin(struct openpromio __user *info, struct openpromio **opp_p)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	unsigned int bufsize;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (!info || !opp_p)
6562306a36Sopenharmony_ci		return -EFAULT;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (get_user(bufsize, &info->oprom_size))
6862306a36Sopenharmony_ci		return -EFAULT;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (bufsize == 0)
7162306a36Sopenharmony_ci		return -EINVAL;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/* If the bufsize is too large, just limit it.
7462306a36Sopenharmony_ci	 * Fix from Jason Rappleye.
7562306a36Sopenharmony_ci	 */
7662306a36Sopenharmony_ci	if (bufsize > OPROMMAXPARAM)
7762306a36Sopenharmony_ci		bufsize = OPROMMAXPARAM;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
8062306a36Sopenharmony_ci		return -ENOMEM;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (copy_from_user(&(*opp_p)->oprom_array,
8362306a36Sopenharmony_ci			   &info->oprom_array, bufsize)) {
8462306a36Sopenharmony_ci		kfree(*opp_p);
8562306a36Sopenharmony_ci		return -EFAULT;
8662306a36Sopenharmony_ci	}
8762306a36Sopenharmony_ci	return bufsize;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int getstrings(struct openpromio __user *info, struct openpromio **opp_p)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	int n, bufsize;
9362306a36Sopenharmony_ci	char c;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!info || !opp_p)
9662306a36Sopenharmony_ci		return -EFAULT;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
9962306a36Sopenharmony_ci		return -ENOMEM;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	(*opp_p)->oprom_size = 0;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	n = bufsize = 0;
10462306a36Sopenharmony_ci	while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
10562306a36Sopenharmony_ci		if (get_user(c, &info->oprom_array[bufsize])) {
10662306a36Sopenharmony_ci			kfree(*opp_p);
10762306a36Sopenharmony_ci			return -EFAULT;
10862306a36Sopenharmony_ci		}
10962306a36Sopenharmony_ci		if (c == '\0')
11062306a36Sopenharmony_ci			n++;
11162306a36Sopenharmony_ci		(*opp_p)->oprom_array[bufsize++] = c;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	if (!n) {
11462306a36Sopenharmony_ci		kfree(*opp_p);
11562306a36Sopenharmony_ci		return -EINVAL;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci	return bufsize;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/*
12162306a36Sopenharmony_ci * Copy an openpromio structure in kernel space back to user space.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_cistatic int copyout(void __user *info, struct openpromio *opp, int len)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	if (copy_to_user(info, opp, len))
12662306a36Sopenharmony_ci		return -EFAULT;
12762306a36Sopenharmony_ci	return 0;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	const void *pval;
13362306a36Sopenharmony_ci	int len;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (!dp ||
13662306a36Sopenharmony_ci	    !(pval = of_get_property(dp, op->oprom_array, &len)) ||
13762306a36Sopenharmony_ci	    len <= 0 || len > bufsize)
13862306a36Sopenharmony_ci		return copyout(argp, op, sizeof(int));
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	memcpy(op->oprom_array, pval, len);
14162306a36Sopenharmony_ci	op->oprom_array[len] = '\0';
14262306a36Sopenharmony_ci	op->oprom_size = len;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	return copyout(argp, op, sizeof(int) + bufsize);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	struct property *prop;
15062306a36Sopenharmony_ci	int len;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!dp)
15362306a36Sopenharmony_ci		return copyout(argp, op, sizeof(int));
15462306a36Sopenharmony_ci	if (op->oprom_array[0] == '\0') {
15562306a36Sopenharmony_ci		prop = dp->properties;
15662306a36Sopenharmony_ci		if (!prop)
15762306a36Sopenharmony_ci			return copyout(argp, op, sizeof(int));
15862306a36Sopenharmony_ci		len = strlen(prop->name);
15962306a36Sopenharmony_ci	} else {
16062306a36Sopenharmony_ci		prop = of_find_property(dp, op->oprom_array, NULL);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci		if (!prop ||
16362306a36Sopenharmony_ci		    !prop->next ||
16462306a36Sopenharmony_ci		    (len = strlen(prop->next->name)) + 1 > bufsize)
16562306a36Sopenharmony_ci			return copyout(argp, op, sizeof(int));
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		prop = prop->next;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	memcpy(op->oprom_array, prop->name, len);
17162306a36Sopenharmony_ci	op->oprom_array[len] = '\0';
17262306a36Sopenharmony_ci	op->oprom_size = ++len;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return copyout(argp, op, sizeof(int) + bufsize);
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
18062306a36Sopenharmony_ci	int len = op->oprom_array + bufsize - buf;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return of_set_property(options_node, op->oprom_array, buf, len);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	phandle ph;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (bufsize < sizeof(phandle))
19262306a36Sopenharmony_ci		return -EINVAL;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	ph = *((int *) op->oprom_array);
19562306a36Sopenharmony_ci	if (ph) {
19662306a36Sopenharmony_ci		dp = of_find_node_by_phandle(ph);
19762306a36Sopenharmony_ci		if (!dp)
19862306a36Sopenharmony_ci			return -EINVAL;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		switch (cmd) {
20162306a36Sopenharmony_ci		case OPROMNEXT:
20262306a36Sopenharmony_ci			dp = dp->sibling;
20362306a36Sopenharmony_ci			break;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		case OPROMCHILD:
20662306a36Sopenharmony_ci			dp = dp->child;
20762306a36Sopenharmony_ci			break;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		case OPROMSETCUR:
21062306a36Sopenharmony_ci		default:
21162306a36Sopenharmony_ci			break;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci	} else {
21462306a36Sopenharmony_ci		/* Sibling of node zero is the root node.  */
21562306a36Sopenharmony_ci		if (cmd != OPROMNEXT)
21662306a36Sopenharmony_ci			return -EINVAL;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci		dp = of_find_node_by_path("/");
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	ph = 0;
22262306a36Sopenharmony_ci	if (dp)
22362306a36Sopenharmony_ci		ph = dp->phandle;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	data->current_node = dp;
22662306a36Sopenharmony_ci	*((int *) op->oprom_array) = ph;
22762306a36Sopenharmony_ci	op->oprom_size = sizeof(phandle);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return copyout(argp, op, bufsize + sizeof(int));
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_cistatic int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	int err = -EINVAL;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	if (bufsize >= 2*sizeof(int)) {
23762306a36Sopenharmony_ci#ifdef CONFIG_PCI
23862306a36Sopenharmony_ci		struct pci_dev *pdev;
23962306a36Sopenharmony_ci		struct device_node *dp;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		pdev = pci_get_domain_bus_and_slot(0,
24262306a36Sopenharmony_ci						((int *) op->oprom_array)[0],
24362306a36Sopenharmony_ci						((int *) op->oprom_array)[1]);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci		dp = pci_device_to_OF_node(pdev);
24662306a36Sopenharmony_ci		data->current_node = dp;
24762306a36Sopenharmony_ci		*((int *)op->oprom_array) = dp->phandle;
24862306a36Sopenharmony_ci		op->oprom_size = sizeof(int);
24962306a36Sopenharmony_ci		err = copyout(argp, op, bufsize + sizeof(int));
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci		pci_dev_put(pdev);
25262306a36Sopenharmony_ci#endif
25362306a36Sopenharmony_ci	}
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	return err;
25662306a36Sopenharmony_ci}
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	phandle ph = 0;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	dp = of_find_node_by_path(op->oprom_array);
26362306a36Sopenharmony_ci	if (dp)
26462306a36Sopenharmony_ci		ph = dp->phandle;
26562306a36Sopenharmony_ci	data->current_node = dp;
26662306a36Sopenharmony_ci	*((int *)op->oprom_array) = ph;
26762306a36Sopenharmony_ci	op->oprom_size = sizeof(int);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	return copyout(argp, op, bufsize + sizeof(int));
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	char *buf = saved_command_line;
27562306a36Sopenharmony_ci	int len = strlen(buf);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (len > bufsize)
27862306a36Sopenharmony_ci		return -EINVAL;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	strcpy(op->oprom_array, buf);
28162306a36Sopenharmony_ci	op->oprom_size = len;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	return copyout(argp, op, bufsize + sizeof(int));
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci/*
28762306a36Sopenharmony_ci *	SunOS and Solaris /dev/openprom ioctl calls.
28862306a36Sopenharmony_ci */
28962306a36Sopenharmony_cistatic long openprom_sunos_ioctl(struct file * file,
29062306a36Sopenharmony_ci				 unsigned int cmd, unsigned long arg,
29162306a36Sopenharmony_ci				 struct device_node *dp)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	DATA *data = file->private_data;
29462306a36Sopenharmony_ci	struct openpromio *opp = NULL;
29562306a36Sopenharmony_ci	int bufsize, error = 0;
29662306a36Sopenharmony_ci	static int cnt;
29762306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	if (cmd == OPROMSETOPT)
30062306a36Sopenharmony_ci		bufsize = getstrings(argp, &opp);
30162306a36Sopenharmony_ci	else
30262306a36Sopenharmony_ci		bufsize = copyin(argp, &opp);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	if (bufsize < 0)
30562306a36Sopenharmony_ci		return bufsize;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	mutex_lock(&openprom_mutex);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	switch (cmd) {
31062306a36Sopenharmony_ci	case OPROMGETOPT:
31162306a36Sopenharmony_ci	case OPROMGETPROP:
31262306a36Sopenharmony_ci		error = opromgetprop(argp, dp, opp, bufsize);
31362306a36Sopenharmony_ci		break;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	case OPROMNXTOPT:
31662306a36Sopenharmony_ci	case OPROMNXTPROP:
31762306a36Sopenharmony_ci		error = opromnxtprop(argp, dp, opp, bufsize);
31862306a36Sopenharmony_ci		break;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	case OPROMSETOPT:
32162306a36Sopenharmony_ci	case OPROMSETOPT2:
32262306a36Sopenharmony_ci		error = opromsetopt(dp, opp, bufsize);
32362306a36Sopenharmony_ci		break;
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci	case OPROMNEXT:
32662306a36Sopenharmony_ci	case OPROMCHILD:
32762306a36Sopenharmony_ci	case OPROMSETCUR:
32862306a36Sopenharmony_ci		error = opromnext(argp, cmd, dp, opp, bufsize, data);
32962306a36Sopenharmony_ci		break;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	case OPROMPCI2NODE:
33262306a36Sopenharmony_ci		error = oprompci2node(argp, dp, opp, bufsize, data);
33362306a36Sopenharmony_ci		break;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	case OPROMPATH2NODE:
33662306a36Sopenharmony_ci		error = oprompath2node(argp, dp, opp, bufsize, data);
33762306a36Sopenharmony_ci		break;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	case OPROMGETBOOTARGS:
34062306a36Sopenharmony_ci		error = opromgetbootargs(argp, opp, bufsize);
34162306a36Sopenharmony_ci		break;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	case OPROMU2P:
34462306a36Sopenharmony_ci	case OPROMGETCONS:
34562306a36Sopenharmony_ci	case OPROMGETFBNAME:
34662306a36Sopenharmony_ci		if (cnt++ < 10)
34762306a36Sopenharmony_ci			printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
34862306a36Sopenharmony_ci		error = -EINVAL;
34962306a36Sopenharmony_ci		break;
35062306a36Sopenharmony_ci	default:
35162306a36Sopenharmony_ci		if (cnt++ < 10)
35262306a36Sopenharmony_ci			printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
35362306a36Sopenharmony_ci		error = -EINVAL;
35462306a36Sopenharmony_ci		break;
35562306a36Sopenharmony_ci	}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	kfree(opp);
35862306a36Sopenharmony_ci	mutex_unlock(&openprom_mutex);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	return error;
36162306a36Sopenharmony_ci}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cistatic struct device_node *get_node(phandle n, DATA *data)
36462306a36Sopenharmony_ci{
36562306a36Sopenharmony_ci	struct device_node *dp = of_find_node_by_phandle(n);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	if (dp)
36862306a36Sopenharmony_ci		data->lastnode = dp;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	return dp;
37162306a36Sopenharmony_ci}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci/* Copy in a whole string from userspace into kernelspace. */
37462306a36Sopenharmony_cistatic char * copyin_string(char __user *user, size_t len)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
37762306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	return memdup_user_nul(user, len);
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci/*
38362306a36Sopenharmony_ci *	NetBSD /dev/openprom ioctl calls.
38462306a36Sopenharmony_ci */
38562306a36Sopenharmony_cistatic int opiocget(void __user *argp, DATA *data)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct opiocdesc op;
38862306a36Sopenharmony_ci	struct device_node *dp;
38962306a36Sopenharmony_ci	char *str;
39062306a36Sopenharmony_ci	const void *pval;
39162306a36Sopenharmony_ci	int err, len;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	if (copy_from_user(&op, argp, sizeof(op)))
39462306a36Sopenharmony_ci		return -EFAULT;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	dp = get_node(op.op_nodeid, data);
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	str = copyin_string(op.op_name, op.op_namelen);
39962306a36Sopenharmony_ci	if (IS_ERR(str))
40062306a36Sopenharmony_ci		return PTR_ERR(str);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	pval = of_get_property(dp, str, &len);
40362306a36Sopenharmony_ci	err = 0;
40462306a36Sopenharmony_ci	if (!pval || len > op.op_buflen) {
40562306a36Sopenharmony_ci		err = -EINVAL;
40662306a36Sopenharmony_ci	} else {
40762306a36Sopenharmony_ci		op.op_buflen = len;
40862306a36Sopenharmony_ci		if (copy_to_user(argp, &op, sizeof(op)) ||
40962306a36Sopenharmony_ci		    copy_to_user(op.op_buf, pval, len))
41062306a36Sopenharmony_ci			err = -EFAULT;
41162306a36Sopenharmony_ci	}
41262306a36Sopenharmony_ci	kfree(str);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	return err;
41562306a36Sopenharmony_ci}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic int opiocnextprop(void __user *argp, DATA *data)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	struct opiocdesc op;
42062306a36Sopenharmony_ci	struct device_node *dp;
42162306a36Sopenharmony_ci	struct property *prop;
42262306a36Sopenharmony_ci	char *str;
42362306a36Sopenharmony_ci	int len;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	if (copy_from_user(&op, argp, sizeof(op)))
42662306a36Sopenharmony_ci		return -EFAULT;
42762306a36Sopenharmony_ci
42862306a36Sopenharmony_ci	dp = get_node(op.op_nodeid, data);
42962306a36Sopenharmony_ci	if (!dp)
43062306a36Sopenharmony_ci		return -EINVAL;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	str = copyin_string(op.op_name, op.op_namelen);
43362306a36Sopenharmony_ci	if (IS_ERR(str))
43462306a36Sopenharmony_ci		return PTR_ERR(str);
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci	if (str[0] == '\0') {
43762306a36Sopenharmony_ci		prop = dp->properties;
43862306a36Sopenharmony_ci	} else {
43962306a36Sopenharmony_ci		prop = of_find_property(dp, str, NULL);
44062306a36Sopenharmony_ci		if (prop)
44162306a36Sopenharmony_ci			prop = prop->next;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci	kfree(str);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	if (!prop)
44662306a36Sopenharmony_ci		len = 0;
44762306a36Sopenharmony_ci	else
44862306a36Sopenharmony_ci		len = prop->length;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	if (len > op.op_buflen)
45162306a36Sopenharmony_ci		len = op.op_buflen;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	if (copy_to_user(argp, &op, sizeof(op)))
45462306a36Sopenharmony_ci		return -EFAULT;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	if (len &&
45762306a36Sopenharmony_ci	    copy_to_user(op.op_buf, prop->value, len))
45862306a36Sopenharmony_ci		return -EFAULT;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	return 0;
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic int opiocset(void __user *argp, DATA *data)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	struct opiocdesc op;
46662306a36Sopenharmony_ci	struct device_node *dp;
46762306a36Sopenharmony_ci	char *str, *tmp;
46862306a36Sopenharmony_ci	int err;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (copy_from_user(&op, argp, sizeof(op)))
47162306a36Sopenharmony_ci		return -EFAULT;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	dp = get_node(op.op_nodeid, data);
47462306a36Sopenharmony_ci	if (!dp)
47562306a36Sopenharmony_ci		return -EINVAL;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	str = copyin_string(op.op_name, op.op_namelen);
47862306a36Sopenharmony_ci	if (IS_ERR(str))
47962306a36Sopenharmony_ci		return PTR_ERR(str);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	tmp = copyin_string(op.op_buf, op.op_buflen);
48262306a36Sopenharmony_ci	if (IS_ERR(tmp)) {
48362306a36Sopenharmony_ci		kfree(str);
48462306a36Sopenharmony_ci		return PTR_ERR(tmp);
48562306a36Sopenharmony_ci	}
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci	err = of_set_property(dp, str, tmp, op.op_buflen);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	kfree(str);
49062306a36Sopenharmony_ci	kfree(tmp);
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	return err;
49362306a36Sopenharmony_ci}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_cistatic int opiocgetnext(unsigned int cmd, void __user *argp)
49662306a36Sopenharmony_ci{
49762306a36Sopenharmony_ci	struct device_node *dp;
49862306a36Sopenharmony_ci	phandle nd;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	if (copy_from_user(&nd, argp, sizeof(phandle)))
50362306a36Sopenharmony_ci		return -EFAULT;
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	if (nd == 0) {
50662306a36Sopenharmony_ci		if (cmd != OPIOCGETNEXT)
50762306a36Sopenharmony_ci			return -EINVAL;
50862306a36Sopenharmony_ci		dp = of_find_node_by_path("/");
50962306a36Sopenharmony_ci	} else {
51062306a36Sopenharmony_ci		dp = of_find_node_by_phandle(nd);
51162306a36Sopenharmony_ci		nd = 0;
51262306a36Sopenharmony_ci		if (dp) {
51362306a36Sopenharmony_ci			if (cmd == OPIOCGETNEXT)
51462306a36Sopenharmony_ci				dp = dp->sibling;
51562306a36Sopenharmony_ci			else
51662306a36Sopenharmony_ci				dp = dp->child;
51762306a36Sopenharmony_ci		}
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci	if (dp)
52062306a36Sopenharmony_ci		nd = dp->phandle;
52162306a36Sopenharmony_ci	if (copy_to_user(argp, &nd, sizeof(phandle)))
52262306a36Sopenharmony_ci		return -EFAULT;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	return 0;
52562306a36Sopenharmony_ci}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_cistatic int openprom_bsd_ioctl(struct file * file,
52862306a36Sopenharmony_ci			      unsigned int cmd, unsigned long arg)
52962306a36Sopenharmony_ci{
53062306a36Sopenharmony_ci	DATA *data = file->private_data;
53162306a36Sopenharmony_ci	void __user *argp = (void __user *)arg;
53262306a36Sopenharmony_ci	int err;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	mutex_lock(&openprom_mutex);
53562306a36Sopenharmony_ci	switch (cmd) {
53662306a36Sopenharmony_ci	case OPIOCGET:
53762306a36Sopenharmony_ci		err = opiocget(argp, data);
53862306a36Sopenharmony_ci		break;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	case OPIOCNEXTPROP:
54162306a36Sopenharmony_ci		err = opiocnextprop(argp, data);
54262306a36Sopenharmony_ci		break;
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci	case OPIOCSET:
54562306a36Sopenharmony_ci		err = opiocset(argp, data);
54662306a36Sopenharmony_ci		break;
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	case OPIOCGETOPTNODE:
54962306a36Sopenharmony_ci		BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_ci		err = 0;
55262306a36Sopenharmony_ci		if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
55362306a36Sopenharmony_ci			err = -EFAULT;
55462306a36Sopenharmony_ci		break;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	case OPIOCGETNEXT:
55762306a36Sopenharmony_ci	case OPIOCGETCHILD:
55862306a36Sopenharmony_ci		err = opiocgetnext(cmd, argp);
55962306a36Sopenharmony_ci		break;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	default:
56262306a36Sopenharmony_ci		err = -EINVAL;
56362306a36Sopenharmony_ci		break;
56462306a36Sopenharmony_ci	}
56562306a36Sopenharmony_ci	mutex_unlock(&openprom_mutex);
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	return err;
56862306a36Sopenharmony_ci}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci/*
57262306a36Sopenharmony_ci *	Handoff control to the correct ioctl handler.
57362306a36Sopenharmony_ci */
57462306a36Sopenharmony_cistatic long openprom_ioctl(struct file * file,
57562306a36Sopenharmony_ci			   unsigned int cmd, unsigned long arg)
57662306a36Sopenharmony_ci{
57762306a36Sopenharmony_ci	DATA *data = file->private_data;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	switch (cmd) {
58062306a36Sopenharmony_ci	case OPROMGETOPT:
58162306a36Sopenharmony_ci	case OPROMNXTOPT:
58262306a36Sopenharmony_ci		if ((file->f_mode & FMODE_READ) == 0)
58362306a36Sopenharmony_ci			return -EPERM;
58462306a36Sopenharmony_ci		return openprom_sunos_ioctl(file, cmd, arg,
58562306a36Sopenharmony_ci					    options_node);
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	case OPROMSETOPT:
58862306a36Sopenharmony_ci	case OPROMSETOPT2:
58962306a36Sopenharmony_ci		if ((file->f_mode & FMODE_WRITE) == 0)
59062306a36Sopenharmony_ci			return -EPERM;
59162306a36Sopenharmony_ci		return openprom_sunos_ioctl(file, cmd, arg,
59262306a36Sopenharmony_ci					    options_node);
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	case OPROMNEXT:
59562306a36Sopenharmony_ci	case OPROMCHILD:
59662306a36Sopenharmony_ci	case OPROMGETPROP:
59762306a36Sopenharmony_ci	case OPROMNXTPROP:
59862306a36Sopenharmony_ci		if ((file->f_mode & FMODE_READ) == 0)
59962306a36Sopenharmony_ci			return -EPERM;
60062306a36Sopenharmony_ci		return openprom_sunos_ioctl(file, cmd, arg,
60162306a36Sopenharmony_ci					    data->current_node);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	case OPROMU2P:
60462306a36Sopenharmony_ci	case OPROMGETCONS:
60562306a36Sopenharmony_ci	case OPROMGETFBNAME:
60662306a36Sopenharmony_ci	case OPROMGETBOOTARGS:
60762306a36Sopenharmony_ci	case OPROMSETCUR:
60862306a36Sopenharmony_ci	case OPROMPCI2NODE:
60962306a36Sopenharmony_ci	case OPROMPATH2NODE:
61062306a36Sopenharmony_ci		if ((file->f_mode & FMODE_READ) == 0)
61162306a36Sopenharmony_ci			return -EPERM;
61262306a36Sopenharmony_ci		return openprom_sunos_ioctl(file, cmd, arg, NULL);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	case OPIOCGET:
61562306a36Sopenharmony_ci	case OPIOCNEXTPROP:
61662306a36Sopenharmony_ci	case OPIOCGETOPTNODE:
61762306a36Sopenharmony_ci	case OPIOCGETNEXT:
61862306a36Sopenharmony_ci	case OPIOCGETCHILD:
61962306a36Sopenharmony_ci		if ((file->f_mode & FMODE_READ) == 0)
62062306a36Sopenharmony_ci			return -EBADF;
62162306a36Sopenharmony_ci		return openprom_bsd_ioctl(file,cmd,arg);
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	case OPIOCSET:
62462306a36Sopenharmony_ci		if ((file->f_mode & FMODE_WRITE) == 0)
62562306a36Sopenharmony_ci			return -EBADF;
62662306a36Sopenharmony_ci		return openprom_bsd_ioctl(file,cmd,arg);
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	default:
62962306a36Sopenharmony_ci		return -EINVAL;
63062306a36Sopenharmony_ci	};
63162306a36Sopenharmony_ci}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_cistatic long openprom_compat_ioctl(struct file *file, unsigned int cmd,
63462306a36Sopenharmony_ci		unsigned long arg)
63562306a36Sopenharmony_ci{
63662306a36Sopenharmony_ci	long rval = -ENOTTY;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	/*
63962306a36Sopenharmony_ci	 * SunOS/Solaris only, the NetBSD one's have embedded pointers in
64062306a36Sopenharmony_ci	 * the arg which we'd need to clean up...
64162306a36Sopenharmony_ci	 */
64262306a36Sopenharmony_ci	switch (cmd) {
64362306a36Sopenharmony_ci	case OPROMGETOPT:
64462306a36Sopenharmony_ci	case OPROMSETOPT:
64562306a36Sopenharmony_ci	case OPROMNXTOPT:
64662306a36Sopenharmony_ci	case OPROMSETOPT2:
64762306a36Sopenharmony_ci	case OPROMNEXT:
64862306a36Sopenharmony_ci	case OPROMCHILD:
64962306a36Sopenharmony_ci	case OPROMGETPROP:
65062306a36Sopenharmony_ci	case OPROMNXTPROP:
65162306a36Sopenharmony_ci	case OPROMU2P:
65262306a36Sopenharmony_ci	case OPROMGETCONS:
65362306a36Sopenharmony_ci	case OPROMGETFBNAME:
65462306a36Sopenharmony_ci	case OPROMGETBOOTARGS:
65562306a36Sopenharmony_ci	case OPROMSETCUR:
65662306a36Sopenharmony_ci	case OPROMPCI2NODE:
65762306a36Sopenharmony_ci	case OPROMPATH2NODE:
65862306a36Sopenharmony_ci		rval = openprom_ioctl(file, cmd, arg);
65962306a36Sopenharmony_ci		break;
66062306a36Sopenharmony_ci	}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	return rval;
66362306a36Sopenharmony_ci}
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cistatic int openprom_open(struct inode * inode, struct file * file)
66662306a36Sopenharmony_ci{
66762306a36Sopenharmony_ci	DATA *data;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	data = kmalloc(sizeof(DATA), GFP_KERNEL);
67062306a36Sopenharmony_ci	if (!data)
67162306a36Sopenharmony_ci		return -ENOMEM;
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	mutex_lock(&openprom_mutex);
67462306a36Sopenharmony_ci	data->current_node = of_find_node_by_path("/");
67562306a36Sopenharmony_ci	data->lastnode = data->current_node;
67662306a36Sopenharmony_ci	file->private_data = (void *) data;
67762306a36Sopenharmony_ci	mutex_unlock(&openprom_mutex);
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	return 0;
68062306a36Sopenharmony_ci}
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cistatic int openprom_release(struct inode * inode, struct file * file)
68362306a36Sopenharmony_ci{
68462306a36Sopenharmony_ci	kfree(file->private_data);
68562306a36Sopenharmony_ci	return 0;
68662306a36Sopenharmony_ci}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_cistatic const struct file_operations openprom_fops = {
68962306a36Sopenharmony_ci	.owner =	THIS_MODULE,
69062306a36Sopenharmony_ci	.llseek =	no_llseek,
69162306a36Sopenharmony_ci	.unlocked_ioctl = openprom_ioctl,
69262306a36Sopenharmony_ci	.compat_ioctl =	openprom_compat_ioctl,
69362306a36Sopenharmony_ci	.open =		openprom_open,
69462306a36Sopenharmony_ci	.release =	openprom_release,
69562306a36Sopenharmony_ci};
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic struct miscdevice openprom_dev = {
69862306a36Sopenharmony_ci	.minor		= SUN_OPENPROM_MINOR,
69962306a36Sopenharmony_ci	.name		= "openprom",
70062306a36Sopenharmony_ci	.fops		= &openprom_fops,
70162306a36Sopenharmony_ci};
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic int __init openprom_init(void)
70462306a36Sopenharmony_ci{
70562306a36Sopenharmony_ci	int err;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	err = misc_register(&openprom_dev);
70862306a36Sopenharmony_ci	if (err)
70962306a36Sopenharmony_ci		return err;
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	options_node = of_get_child_by_name(of_find_node_by_path("/"), "options");
71262306a36Sopenharmony_ci	if (!options_node) {
71362306a36Sopenharmony_ci		misc_deregister(&openprom_dev);
71462306a36Sopenharmony_ci		return -EIO;
71562306a36Sopenharmony_ci	}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	return 0;
71862306a36Sopenharmony_ci}
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_cistatic void __exit openprom_cleanup(void)
72162306a36Sopenharmony_ci{
72262306a36Sopenharmony_ci	misc_deregister(&openprom_dev);
72362306a36Sopenharmony_ci}
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_cimodule_init(openprom_init);
72662306a36Sopenharmony_cimodule_exit(openprom_cleanup);
727