1*74ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * Linux/SPARC PROM Configuration Driver
41da177e4SLinus Torvalds * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
51da177e4SLinus Torvalds * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
61da177e4SLinus Torvalds *
71da177e4SLinus Torvalds * This character device driver allows user programs to access the
81da177e4SLinus Torvalds * PROM device tree. It is compatible with the SunOS /dev/openprom
91da177e4SLinus Torvalds * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
101da177e4SLinus Torvalds * utility works without any modifications.
111da177e4SLinus Torvalds *
121da177e4SLinus Torvalds * The driver uses a minor number under the misc device major. The
131da177e4SLinus Torvalds * file read/write mode determines the type of access to the PROM.
141da177e4SLinus Torvalds * Interrupts are disabled whenever the driver calls into the PROM for
151da177e4SLinus Torvalds * sanity's sake.
161da177e4SLinus Torvalds */
171da177e4SLinus Torvalds
181da177e4SLinus Torvalds
191da177e4SLinus Torvalds #include <linux/module.h>
201da177e4SLinus Torvalds #include <linux/kernel.h>
211da177e4SLinus Torvalds #include <linux/errno.h>
221da177e4SLinus Torvalds #include <linux/slab.h>
23a3108ca2SArnd Bergmann #include <linux/mutex.h>
241da177e4SLinus Torvalds #include <linux/string.h>
251da177e4SLinus Torvalds #include <linux/miscdevice.h>
261da177e4SLinus Torvalds #include <linux/init.h>
271da177e4SLinus Torvalds #include <linux/fs.h>
281da177e4SLinus Torvalds #include <asm/oplib.h>
298e48aec7SDavid S. Miller #include <asm/prom.h>
307c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
311da177e4SLinus Torvalds #include <asm/openpromio.h>
321da177e4SLinus Torvalds #ifdef CONFIG_PCI
331da177e4SLinus Torvalds #include <linux/pci.h>
341da177e4SLinus Torvalds #endif
351da177e4SLinus Torvalds
368e48aec7SDavid S. Miller MODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost (ecd@skynet.be)");
378e48aec7SDavid S. Miller MODULE_DESCRIPTION("OPENPROM Configuration Driver");
388e48aec7SDavid S. Miller MODULE_LICENSE("GPL");
398e48aec7SDavid S. Miller MODULE_VERSION("1.0");
401c339eb1SScott James Remnant MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
418e48aec7SDavid S. Miller
421da177e4SLinus Torvalds /* Private data kept by the driver for each descriptor. */
431da177e4SLinus Torvalds typedef struct openprom_private_data
441da177e4SLinus Torvalds {
458e48aec7SDavid S. Miller struct device_node *current_node; /* Current node for SunOS ioctls. */
468e48aec7SDavid S. Miller struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
471da177e4SLinus Torvalds } DATA;
481da177e4SLinus Torvalds
491da177e4SLinus Torvalds /* ID of the PROM node containing all of the EEPROM options. */
50a3108ca2SArnd Bergmann static DEFINE_MUTEX(openprom_mutex);
518e48aec7SDavid S. Miller static struct device_node *options_node;
521da177e4SLinus Torvalds
531da177e4SLinus Torvalds /*
541da177e4SLinus Torvalds * Copy an openpromio structure into kernel space from user space.
551da177e4SLinus Torvalds * This routine does error checking to make sure that all memory
561da177e4SLinus Torvalds * accesses are within bounds. A pointer to the allocated openpromio
571da177e4SLinus Torvalds * structure will be placed in "*opp_p". Return value is the length
581da177e4SLinus Torvalds * of the user supplied buffer.
591da177e4SLinus Torvalds */
copyin(struct openpromio __user * info,struct openpromio ** opp_p)601da177e4SLinus Torvalds static int copyin(struct openpromio __user *info, struct openpromio **opp_p)
611da177e4SLinus Torvalds {
621da177e4SLinus Torvalds unsigned int bufsize;
631da177e4SLinus Torvalds
641da177e4SLinus Torvalds if (!info || !opp_p)
651da177e4SLinus Torvalds return -EFAULT;
661da177e4SLinus Torvalds
671da177e4SLinus Torvalds if (get_user(bufsize, &info->oprom_size))
681da177e4SLinus Torvalds return -EFAULT;
691da177e4SLinus Torvalds
701da177e4SLinus Torvalds if (bufsize == 0)
711da177e4SLinus Torvalds return -EINVAL;
721da177e4SLinus Torvalds
731da177e4SLinus Torvalds /* If the bufsize is too large, just limit it.
741da177e4SLinus Torvalds * Fix from Jason Rappleye.
751da177e4SLinus Torvalds */
761da177e4SLinus Torvalds if (bufsize > OPROMMAXPARAM)
771da177e4SLinus Torvalds bufsize = OPROMMAXPARAM;
781da177e4SLinus Torvalds
798e48aec7SDavid S. Miller if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
801da177e4SLinus Torvalds return -ENOMEM;
811da177e4SLinus Torvalds
821da177e4SLinus Torvalds if (copy_from_user(&(*opp_p)->oprom_array,
831da177e4SLinus Torvalds &info->oprom_array, bufsize)) {
841da177e4SLinus Torvalds kfree(*opp_p);
851da177e4SLinus Torvalds return -EFAULT;
861da177e4SLinus Torvalds }
871da177e4SLinus Torvalds return bufsize;
881da177e4SLinus Torvalds }
891da177e4SLinus Torvalds
getstrings(struct openpromio __user * info,struct openpromio ** opp_p)901da177e4SLinus Torvalds static int getstrings(struct openpromio __user *info, struct openpromio **opp_p)
911da177e4SLinus Torvalds {
921da177e4SLinus Torvalds int n, bufsize;
931da177e4SLinus Torvalds char c;
941da177e4SLinus Torvalds
951da177e4SLinus Torvalds if (!info || !opp_p)
961da177e4SLinus Torvalds return -EFAULT;
971da177e4SLinus Torvalds
988e48aec7SDavid S. Miller if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
991da177e4SLinus Torvalds return -ENOMEM;
1001da177e4SLinus Torvalds
1011da177e4SLinus Torvalds (*opp_p)->oprom_size = 0;
1021da177e4SLinus Torvalds
1031da177e4SLinus Torvalds n = bufsize = 0;
1041da177e4SLinus Torvalds while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
1051da177e4SLinus Torvalds if (get_user(c, &info->oprom_array[bufsize])) {
1061da177e4SLinus Torvalds kfree(*opp_p);
1071da177e4SLinus Torvalds return -EFAULT;
1081da177e4SLinus Torvalds }
1091da177e4SLinus Torvalds if (c == '\0')
1101da177e4SLinus Torvalds n++;
1111da177e4SLinus Torvalds (*opp_p)->oprom_array[bufsize++] = c;
1121da177e4SLinus Torvalds }
1131da177e4SLinus Torvalds if (!n) {
1141da177e4SLinus Torvalds kfree(*opp_p);
1151da177e4SLinus Torvalds return -EINVAL;
1161da177e4SLinus Torvalds }
1171da177e4SLinus Torvalds return bufsize;
1181da177e4SLinus Torvalds }
1191da177e4SLinus Torvalds
1201da177e4SLinus Torvalds /*
1211da177e4SLinus Torvalds * Copy an openpromio structure in kernel space back to user space.
1221da177e4SLinus Torvalds */
copyout(void __user * info,struct openpromio * opp,int len)1231da177e4SLinus Torvalds static int copyout(void __user *info, struct openpromio *opp, int len)
1241da177e4SLinus Torvalds {
1251da177e4SLinus Torvalds if (copy_to_user(info, opp, len))
1261da177e4SLinus Torvalds return -EFAULT;
1271da177e4SLinus Torvalds return 0;
1281da177e4SLinus Torvalds }
1291da177e4SLinus Torvalds
opromgetprop(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize)1308e48aec7SDavid S. Miller static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
1318e48aec7SDavid S. Miller {
132ccf0dec6SStephen Rothwell const void *pval;
1338e48aec7SDavid S. Miller int len;
1348e48aec7SDavid S. Miller
135b9b64e6eSDavid S. Miller if (!dp ||
136b9b64e6eSDavid S. Miller !(pval = of_get_property(dp, op->oprom_array, &len)) ||
137b9b64e6eSDavid S. Miller len <= 0 || len > bufsize)
1388e48aec7SDavid S. Miller return copyout(argp, op, sizeof(int));
1398e48aec7SDavid S. Miller
1408e48aec7SDavid S. Miller memcpy(op->oprom_array, pval, len);
1418e48aec7SDavid S. Miller op->oprom_array[len] = '\0';
1428e48aec7SDavid S. Miller op->oprom_size = len;
1438e48aec7SDavid S. Miller
1448e48aec7SDavid S. Miller return copyout(argp, op, sizeof(int) + bufsize);
1458e48aec7SDavid S. Miller }
1468e48aec7SDavid S. Miller
opromnxtprop(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize)1478e48aec7SDavid S. Miller static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
1488e48aec7SDavid S. Miller {
1498e48aec7SDavid S. Miller struct property *prop;
1508e48aec7SDavid S. Miller int len;
1518e48aec7SDavid S. Miller
152b9b64e6eSDavid S. Miller if (!dp)
153b9b64e6eSDavid S. Miller return copyout(argp, op, sizeof(int));
1548e48aec7SDavid S. Miller if (op->oprom_array[0] == '\0') {
1558e48aec7SDavid S. Miller prop = dp->properties;
1568e48aec7SDavid S. Miller if (!prop)
1578e48aec7SDavid S. Miller return copyout(argp, op, sizeof(int));
1588e48aec7SDavid S. Miller len = strlen(prop->name);
1598e48aec7SDavid S. Miller } else {
1608e48aec7SDavid S. Miller prop = of_find_property(dp, op->oprom_array, NULL);
1618e48aec7SDavid S. Miller
1628e48aec7SDavid S. Miller if (!prop ||
1638e48aec7SDavid S. Miller !prop->next ||
1648e48aec7SDavid S. Miller (len = strlen(prop->next->name)) + 1 > bufsize)
1658e48aec7SDavid S. Miller return copyout(argp, op, sizeof(int));
1668e48aec7SDavid S. Miller
1678e48aec7SDavid S. Miller prop = prop->next;
1688e48aec7SDavid S. Miller }
1698e48aec7SDavid S. Miller
1708e48aec7SDavid S. Miller memcpy(op->oprom_array, prop->name, len);
1718e48aec7SDavid S. Miller op->oprom_array[len] = '\0';
1728e48aec7SDavid S. Miller op->oprom_size = ++len;
1738e48aec7SDavid S. Miller
1748e48aec7SDavid S. Miller return copyout(argp, op, sizeof(int) + bufsize);
1758e48aec7SDavid S. Miller }
1768e48aec7SDavid S. Miller
opromsetopt(struct device_node * dp,struct openpromio * op,int bufsize)1778e48aec7SDavid S. Miller static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
1788e48aec7SDavid S. Miller {
1798e48aec7SDavid S. Miller char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
1808e48aec7SDavid S. Miller int len = op->oprom_array + bufsize - buf;
1818e48aec7SDavid S. Miller
1828e48aec7SDavid S. Miller return of_set_property(options_node, op->oprom_array, buf, len);
1838e48aec7SDavid S. Miller }
1848e48aec7SDavid S. Miller
opromnext(void __user * argp,unsigned int cmd,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)1858e48aec7SDavid S. Miller static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
1868e48aec7SDavid S. Miller {
1878e48aec7SDavid S. Miller phandle ph;
1888e48aec7SDavid S. Miller
1898e48aec7SDavid S. Miller BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
1908e48aec7SDavid S. Miller
1918e48aec7SDavid S. Miller if (bufsize < sizeof(phandle))
1928e48aec7SDavid S. Miller return -EINVAL;
1938e48aec7SDavid S. Miller
1948e48aec7SDavid S. Miller ph = *((int *) op->oprom_array);
1958e48aec7SDavid S. Miller if (ph) {
1968e48aec7SDavid S. Miller dp = of_find_node_by_phandle(ph);
1978e48aec7SDavid S. Miller if (!dp)
1988e48aec7SDavid S. Miller return -EINVAL;
1998e48aec7SDavid S. Miller
2008e48aec7SDavid S. Miller switch (cmd) {
2018e48aec7SDavid S. Miller case OPROMNEXT:
2028e48aec7SDavid S. Miller dp = dp->sibling;
2038e48aec7SDavid S. Miller break;
2048e48aec7SDavid S. Miller
2058e48aec7SDavid S. Miller case OPROMCHILD:
2068e48aec7SDavid S. Miller dp = dp->child;
2078e48aec7SDavid S. Miller break;
2088e48aec7SDavid S. Miller
2098e48aec7SDavid S. Miller case OPROMSETCUR:
2108e48aec7SDavid S. Miller default:
2118e48aec7SDavid S. Miller break;
212da201161SPeter Senna Tschudin }
2138e48aec7SDavid S. Miller } else {
2148e48aec7SDavid S. Miller /* Sibling of node zero is the root node. */
2158e48aec7SDavid S. Miller if (cmd != OPROMNEXT)
2168e48aec7SDavid S. Miller return -EINVAL;
2178e48aec7SDavid S. Miller
2188e48aec7SDavid S. Miller dp = of_find_node_by_path("/");
2198e48aec7SDavid S. Miller }
2208e48aec7SDavid S. Miller
2218e48aec7SDavid S. Miller ph = 0;
2228e48aec7SDavid S. Miller if (dp)
2236016a363SGrant Likely ph = dp->phandle;
2248e48aec7SDavid S. Miller
2258e48aec7SDavid S. Miller data->current_node = dp;
2268e48aec7SDavid S. Miller *((int *) op->oprom_array) = ph;
2278e48aec7SDavid S. Miller op->oprom_size = sizeof(phandle);
2288e48aec7SDavid S. Miller
2298e48aec7SDavid S. Miller return copyout(argp, op, bufsize + sizeof(int));
2308e48aec7SDavid S. Miller }
2318e48aec7SDavid S. Miller
oprompci2node(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)2328e48aec7SDavid S. Miller static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
2338e48aec7SDavid S. Miller {
2348e48aec7SDavid S. Miller int err = -EINVAL;
2358e48aec7SDavid S. Miller
2368e48aec7SDavid S. Miller if (bufsize >= 2*sizeof(int)) {
2378e48aec7SDavid S. Miller #ifdef CONFIG_PCI
2388e48aec7SDavid S. Miller struct pci_dev *pdev;
239fa449bd6SDavid S. Miller struct device_node *dp;
240fa449bd6SDavid S. Miller
241c7923732SSinan Kaya pdev = pci_get_domain_bus_and_slot(0,
242c7923732SSinan Kaya ((int *) op->oprom_array)[0],
2438e48aec7SDavid S. Miller ((int *) op->oprom_array)[1]);
2448e48aec7SDavid S. Miller
245fa449bd6SDavid S. Miller dp = pci_device_to_OF_node(pdev);
2468e48aec7SDavid S. Miller data->current_node = dp;
2476016a363SGrant Likely *((int *)op->oprom_array) = dp->phandle;
2488e48aec7SDavid S. Miller op->oprom_size = sizeof(int);
2498e48aec7SDavid S. Miller err = copyout(argp, op, bufsize + sizeof(int));
250fa449bd6SDavid S. Miller
2517e9f3346SAlan Cox pci_dev_put(pdev);
2528e48aec7SDavid S. Miller #endif
2538e48aec7SDavid S. Miller }
2548e48aec7SDavid S. Miller
2558e48aec7SDavid S. Miller return err;
2568e48aec7SDavid S. Miller }
2578e48aec7SDavid S. Miller
oprompath2node(void __user * argp,struct device_node * dp,struct openpromio * op,int bufsize,DATA * data)2588e48aec7SDavid S. Miller static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
2598e48aec7SDavid S. Miller {
260b9b64e6eSDavid S. Miller phandle ph = 0;
261b9b64e6eSDavid S. Miller
2628e48aec7SDavid S. Miller dp = of_find_node_by_path(op->oprom_array);
263b9b64e6eSDavid S. Miller if (dp)
2646016a363SGrant Likely ph = dp->phandle;
2658e48aec7SDavid S. Miller data->current_node = dp;
266b9b64e6eSDavid S. Miller *((int *)op->oprom_array) = ph;
2678e48aec7SDavid S. Miller op->oprom_size = sizeof(int);
2688e48aec7SDavid S. Miller
2698e48aec7SDavid S. Miller return copyout(argp, op, bufsize + sizeof(int));
2708e48aec7SDavid S. Miller }
2718e48aec7SDavid S. Miller
opromgetbootargs(void __user * argp,struct openpromio * op,int bufsize)2728e48aec7SDavid S. Miller static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
2738e48aec7SDavid S. Miller {
2748e48aec7SDavid S. Miller char *buf = saved_command_line;
2758e48aec7SDavid S. Miller int len = strlen(buf);
2768e48aec7SDavid S. Miller
2778e48aec7SDavid S. Miller if (len > bufsize)
2788e48aec7SDavid S. Miller return -EINVAL;
2798e48aec7SDavid S. Miller
2808e48aec7SDavid S. Miller strcpy(op->oprom_array, buf);
2818e48aec7SDavid S. Miller op->oprom_size = len;
2828e48aec7SDavid S. Miller
2838e48aec7SDavid S. Miller return copyout(argp, op, bufsize + sizeof(int));
2848e48aec7SDavid S. Miller }
2858e48aec7SDavid S. Miller
2861da177e4SLinus Torvalds /*
2871da177e4SLinus Torvalds * SunOS and Solaris /dev/openprom ioctl calls.
2881da177e4SLinus Torvalds */
openprom_sunos_ioctl(struct file * file,unsigned int cmd,unsigned long arg,struct device_node * dp)28955929332SArnd Bergmann static long openprom_sunos_ioctl(struct file * file,
2908e48aec7SDavid S. Miller unsigned int cmd, unsigned long arg,
2918e48aec7SDavid S. Miller struct device_node *dp)
2921da177e4SLinus Torvalds {
2938e48aec7SDavid S. Miller DATA *data = file->private_data;
29432e5897dSDavid S. Miller struct openpromio *opp = NULL;
2958e48aec7SDavid S. Miller int bufsize, error = 0;
2961da177e4SLinus Torvalds static int cnt;
2971da177e4SLinus Torvalds void __user *argp = (void __user *)arg;
2981da177e4SLinus Torvalds
2991da177e4SLinus Torvalds if (cmd == OPROMSETOPT)
3001da177e4SLinus Torvalds bufsize = getstrings(argp, &opp);
3011da177e4SLinus Torvalds else
3021da177e4SLinus Torvalds bufsize = copyin(argp, &opp);
3031da177e4SLinus Torvalds
3041da177e4SLinus Torvalds if (bufsize < 0)
3051da177e4SLinus Torvalds return bufsize;
3061da177e4SLinus Torvalds
307a3108ca2SArnd Bergmann mutex_lock(&openprom_mutex);
30855929332SArnd Bergmann
3091da177e4SLinus Torvalds switch (cmd) {
3101da177e4SLinus Torvalds case OPROMGETOPT:
3111da177e4SLinus Torvalds case OPROMGETPROP:
3128e48aec7SDavid S. Miller error = opromgetprop(argp, dp, opp, bufsize);
3131da177e4SLinus Torvalds break;
3141da177e4SLinus Torvalds
3151da177e4SLinus Torvalds case OPROMNXTOPT:
3161da177e4SLinus Torvalds case OPROMNXTPROP:
3178e48aec7SDavid S. Miller error = opromnxtprop(argp, dp, opp, bufsize);
3181da177e4SLinus Torvalds break;
3191da177e4SLinus Torvalds
3201da177e4SLinus Torvalds case OPROMSETOPT:
3211da177e4SLinus Torvalds case OPROMSETOPT2:
3228e48aec7SDavid S. Miller error = opromsetopt(dp, opp, bufsize);
3231da177e4SLinus Torvalds break;
3241da177e4SLinus Torvalds
3251da177e4SLinus Torvalds case OPROMNEXT:
3261da177e4SLinus Torvalds case OPROMCHILD:
3271da177e4SLinus Torvalds case OPROMSETCUR:
3288e48aec7SDavid S. Miller error = opromnext(argp, cmd, dp, opp, bufsize, data);
3291da177e4SLinus Torvalds break;
3301da177e4SLinus Torvalds
3311da177e4SLinus Torvalds case OPROMPCI2NODE:
3328e48aec7SDavid S. Miller error = oprompci2node(argp, dp, opp, bufsize, data);
3331da177e4SLinus Torvalds break;
3341da177e4SLinus Torvalds
3351da177e4SLinus Torvalds case OPROMPATH2NODE:
3368e48aec7SDavid S. Miller error = oprompath2node(argp, dp, opp, bufsize, data);
3371da177e4SLinus Torvalds break;
3381da177e4SLinus Torvalds
3391da177e4SLinus Torvalds case OPROMGETBOOTARGS:
3408e48aec7SDavid S. Miller error = opromgetbootargs(argp, opp, bufsize);
3411da177e4SLinus Torvalds break;
3421da177e4SLinus Torvalds
3431da177e4SLinus Torvalds case OPROMU2P:
3441da177e4SLinus Torvalds case OPROMGETCONS:
3451da177e4SLinus Torvalds case OPROMGETFBNAME:
3461da177e4SLinus Torvalds if (cnt++ < 10)
3471da177e4SLinus Torvalds printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
3481da177e4SLinus Torvalds error = -EINVAL;
3491da177e4SLinus Torvalds break;
3501da177e4SLinus Torvalds default:
3511da177e4SLinus Torvalds if (cnt++ < 10)
3521da177e4SLinus Torvalds printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
3531da177e4SLinus Torvalds error = -EINVAL;
3541da177e4SLinus Torvalds break;
3551da177e4SLinus Torvalds }
3561da177e4SLinus Torvalds
3571da177e4SLinus Torvalds kfree(opp);
358a3108ca2SArnd Bergmann mutex_unlock(&openprom_mutex);
35955929332SArnd Bergmann
3601da177e4SLinus Torvalds return error;
3611da177e4SLinus Torvalds }
3621da177e4SLinus Torvalds
get_node(phandle n,DATA * data)3638e48aec7SDavid S. Miller static struct device_node *get_node(phandle n, DATA *data)
3641da177e4SLinus Torvalds {
3658e48aec7SDavid S. Miller struct device_node *dp = of_find_node_by_phandle(n);
3661da177e4SLinus Torvalds
3678e48aec7SDavid S. Miller if (dp)
3688e48aec7SDavid S. Miller data->lastnode = dp;
3698e48aec7SDavid S. Miller
3708e48aec7SDavid S. Miller return dp;
3711da177e4SLinus Torvalds }
3721da177e4SLinus Torvalds
3731da177e4SLinus Torvalds /* Copy in a whole string from userspace into kernelspace. */
copyin_string(char __user * user,size_t len)37421916a4aSSam Ravnborg static char * copyin_string(char __user *user, size_t len)
3751da177e4SLinus Torvalds {
3761da177e4SLinus Torvalds if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
37721916a4aSSam Ravnborg return ERR_PTR(-EINVAL);
3781da177e4SLinus Torvalds
37921916a4aSSam Ravnborg return memdup_user_nul(user, len);
3801da177e4SLinus Torvalds }
3811da177e4SLinus Torvalds
3821da177e4SLinus Torvalds /*
3831da177e4SLinus Torvalds * NetBSD /dev/openprom ioctl calls.
3841da177e4SLinus Torvalds */
opiocget(void __user * argp,DATA * data)3858e48aec7SDavid S. Miller static int opiocget(void __user *argp, DATA *data)
3868e48aec7SDavid S. Miller {
3878e48aec7SDavid S. Miller struct opiocdesc op;
3888e48aec7SDavid S. Miller struct device_node *dp;
3898e48aec7SDavid S. Miller char *str;
390ccf0dec6SStephen Rothwell const void *pval;
3918e48aec7SDavid S. Miller int err, len;
3928e48aec7SDavid S. Miller
3938e48aec7SDavid S. Miller if (copy_from_user(&op, argp, sizeof(op)))
3948e48aec7SDavid S. Miller return -EFAULT;
3958e48aec7SDavid S. Miller
3968e48aec7SDavid S. Miller dp = get_node(op.op_nodeid, data);
3978e48aec7SDavid S. Miller
39821916a4aSSam Ravnborg str = copyin_string(op.op_name, op.op_namelen);
39921916a4aSSam Ravnborg if (IS_ERR(str))
40021916a4aSSam Ravnborg return PTR_ERR(str);
4018e48aec7SDavid S. Miller
4028e48aec7SDavid S. Miller pval = of_get_property(dp, str, &len);
4038e48aec7SDavid S. Miller err = 0;
4048e48aec7SDavid S. Miller if (!pval || len > op.op_buflen) {
4058e48aec7SDavid S. Miller err = -EINVAL;
4068e48aec7SDavid S. Miller } else {
4078e48aec7SDavid S. Miller op.op_buflen = len;
4088e48aec7SDavid S. Miller if (copy_to_user(argp, &op, sizeof(op)) ||
4098e48aec7SDavid S. Miller copy_to_user(op.op_buf, pval, len))
4108e48aec7SDavid S. Miller err = -EFAULT;
4118e48aec7SDavid S. Miller }
4128e48aec7SDavid S. Miller kfree(str);
4138e48aec7SDavid S. Miller
4148e48aec7SDavid S. Miller return err;
4158e48aec7SDavid S. Miller }
4168e48aec7SDavid S. Miller
opiocnextprop(void __user * argp,DATA * data)4178e48aec7SDavid S. Miller static int opiocnextprop(void __user *argp, DATA *data)
4188e48aec7SDavid S. Miller {
4198e48aec7SDavid S. Miller struct opiocdesc op;
4208e48aec7SDavid S. Miller struct device_node *dp;
4218e48aec7SDavid S. Miller struct property *prop;
4228e48aec7SDavid S. Miller char *str;
42321916a4aSSam Ravnborg int len;
4248e48aec7SDavid S. Miller
4258e48aec7SDavid S. Miller if (copy_from_user(&op, argp, sizeof(op)))
4268e48aec7SDavid S. Miller return -EFAULT;
4278e48aec7SDavid S. Miller
4288e48aec7SDavid S. Miller dp = get_node(op.op_nodeid, data);
4298e48aec7SDavid S. Miller if (!dp)
4308e48aec7SDavid S. Miller return -EINVAL;
4318e48aec7SDavid S. Miller
43221916a4aSSam Ravnborg str = copyin_string(op.op_name, op.op_namelen);
43321916a4aSSam Ravnborg if (IS_ERR(str))
43421916a4aSSam Ravnborg return PTR_ERR(str);
4358e48aec7SDavid S. Miller
4368e48aec7SDavid S. Miller if (str[0] == '\0') {
4378e48aec7SDavid S. Miller prop = dp->properties;
4388e48aec7SDavid S. Miller } else {
4398e48aec7SDavid S. Miller prop = of_find_property(dp, str, NULL);
4408e48aec7SDavid S. Miller if (prop)
4418e48aec7SDavid S. Miller prop = prop->next;
4428e48aec7SDavid S. Miller }
4438e48aec7SDavid S. Miller kfree(str);
4448e48aec7SDavid S. Miller
4458e48aec7SDavid S. Miller if (!prop)
4468e48aec7SDavid S. Miller len = 0;
4478e48aec7SDavid S. Miller else
4488e48aec7SDavid S. Miller len = prop->length;
4498e48aec7SDavid S. Miller
4508e48aec7SDavid S. Miller if (len > op.op_buflen)
4518e48aec7SDavid S. Miller len = op.op_buflen;
4528e48aec7SDavid S. Miller
4538e48aec7SDavid S. Miller if (copy_to_user(argp, &op, sizeof(op)))
4548e48aec7SDavid S. Miller return -EFAULT;
4558e48aec7SDavid S. Miller
4568e48aec7SDavid S. Miller if (len &&
4578e48aec7SDavid S. Miller copy_to_user(op.op_buf, prop->value, len))
4588e48aec7SDavid S. Miller return -EFAULT;
4598e48aec7SDavid S. Miller
4608e48aec7SDavid S. Miller return 0;
4618e48aec7SDavid S. Miller }
4628e48aec7SDavid S. Miller
opiocset(void __user * argp,DATA * data)4638e48aec7SDavid S. Miller static int opiocset(void __user *argp, DATA *data)
4648e48aec7SDavid S. Miller {
4658e48aec7SDavid S. Miller struct opiocdesc op;
4668e48aec7SDavid S. Miller struct device_node *dp;
4678e48aec7SDavid S. Miller char *str, *tmp;
4688e48aec7SDavid S. Miller int err;
4698e48aec7SDavid S. Miller
4708e48aec7SDavid S. Miller if (copy_from_user(&op, argp, sizeof(op)))
4718e48aec7SDavid S. Miller return -EFAULT;
4728e48aec7SDavid S. Miller
4738e48aec7SDavid S. Miller dp = get_node(op.op_nodeid, data);
4748e48aec7SDavid S. Miller if (!dp)
4758e48aec7SDavid S. Miller return -EINVAL;
4768e48aec7SDavid S. Miller
47721916a4aSSam Ravnborg str = copyin_string(op.op_name, op.op_namelen);
47821916a4aSSam Ravnborg if (IS_ERR(str))
47921916a4aSSam Ravnborg return PTR_ERR(str);
4808e48aec7SDavid S. Miller
48121916a4aSSam Ravnborg tmp = copyin_string(op.op_buf, op.op_buflen);
48221916a4aSSam Ravnborg if (IS_ERR(tmp)) {
4838e48aec7SDavid S. Miller kfree(str);
48421916a4aSSam Ravnborg return PTR_ERR(tmp);
4858e48aec7SDavid S. Miller }
4868e48aec7SDavid S. Miller
4878e48aec7SDavid S. Miller err = of_set_property(dp, str, tmp, op.op_buflen);
4888e48aec7SDavid S. Miller
4898e48aec7SDavid S. Miller kfree(str);
4908e48aec7SDavid S. Miller kfree(tmp);
4918e48aec7SDavid S. Miller
4928e48aec7SDavid S. Miller return err;
4938e48aec7SDavid S. Miller }
4948e48aec7SDavid S. Miller
opiocgetnext(unsigned int cmd,void __user * argp)4958e48aec7SDavid S. Miller static int opiocgetnext(unsigned int cmd, void __user *argp)
4968e48aec7SDavid S. Miller {
4978e48aec7SDavid S. Miller struct device_node *dp;
4988e48aec7SDavid S. Miller phandle nd;
4998e48aec7SDavid S. Miller
5008e48aec7SDavid S. Miller BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
5018e48aec7SDavid S. Miller
5028e48aec7SDavid S. Miller if (copy_from_user(&nd, argp, sizeof(phandle)))
5038e48aec7SDavid S. Miller return -EFAULT;
5048e48aec7SDavid S. Miller
5058e48aec7SDavid S. Miller if (nd == 0) {
5068e48aec7SDavid S. Miller if (cmd != OPIOCGETNEXT)
5078e48aec7SDavid S. Miller return -EINVAL;
5088e48aec7SDavid S. Miller dp = of_find_node_by_path("/");
5098e48aec7SDavid S. Miller } else {
5108e48aec7SDavid S. Miller dp = of_find_node_by_phandle(nd);
5118e48aec7SDavid S. Miller nd = 0;
5128e48aec7SDavid S. Miller if (dp) {
5138e48aec7SDavid S. Miller if (cmd == OPIOCGETNEXT)
5148e48aec7SDavid S. Miller dp = dp->sibling;
5158e48aec7SDavid S. Miller else
5168e48aec7SDavid S. Miller dp = dp->child;
5178e48aec7SDavid S. Miller }
5188e48aec7SDavid S. Miller }
5198e48aec7SDavid S. Miller if (dp)
5206016a363SGrant Likely nd = dp->phandle;
5218e48aec7SDavid S. Miller if (copy_to_user(argp, &nd, sizeof(phandle)))
5228e48aec7SDavid S. Miller return -EFAULT;
5238e48aec7SDavid S. Miller
5248e48aec7SDavid S. Miller return 0;
5258e48aec7SDavid S. Miller }
5268e48aec7SDavid S. Miller
openprom_bsd_ioctl(struct file * file,unsigned int cmd,unsigned long arg)52755929332SArnd Bergmann static int openprom_bsd_ioctl(struct file * file,
5281da177e4SLinus Torvalds unsigned int cmd, unsigned long arg)
5291da177e4SLinus Torvalds {
53033cfe65aSJoe Perches DATA *data = file->private_data;
5311da177e4SLinus Torvalds void __user *argp = (void __user *)arg;
5328e48aec7SDavid S. Miller int err;
5331da177e4SLinus Torvalds
534a3108ca2SArnd Bergmann mutex_lock(&openprom_mutex);
5351da177e4SLinus Torvalds switch (cmd) {
5361da177e4SLinus Torvalds case OPIOCGET:
5378e48aec7SDavid S. Miller err = opiocget(argp, data);
5388e48aec7SDavid S. Miller break;
5391da177e4SLinus Torvalds
5401da177e4SLinus Torvalds case OPIOCNEXTPROP:
5418e48aec7SDavid S. Miller err = opiocnextprop(argp, data);
5428e48aec7SDavid S. Miller break;
5431da177e4SLinus Torvalds
5441da177e4SLinus Torvalds case OPIOCSET:
5458e48aec7SDavid S. Miller err = opiocset(argp, data);
5468e48aec7SDavid S. Miller break;
5471da177e4SLinus Torvalds
5481da177e4SLinus Torvalds case OPIOCGETOPTNODE:
5498e48aec7SDavid S. Miller BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
5508e48aec7SDavid S. Miller
55155929332SArnd Bergmann err = 0;
5526016a363SGrant Likely if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
55355929332SArnd Bergmann err = -EFAULT;
55455929332SArnd Bergmann break;
5551da177e4SLinus Torvalds
5561da177e4SLinus Torvalds case OPIOCGETNEXT:
5571da177e4SLinus Torvalds case OPIOCGETCHILD:
5588e48aec7SDavid S. Miller err = opiocgetnext(cmd, argp);
5598e48aec7SDavid S. Miller break;
5601da177e4SLinus Torvalds
5611da177e4SLinus Torvalds default:
56255929332SArnd Bergmann err = -EINVAL;
56355929332SArnd Bergmann break;
564da201161SPeter Senna Tschudin }
565a3108ca2SArnd Bergmann mutex_unlock(&openprom_mutex);
5668e48aec7SDavid S. Miller
5678e48aec7SDavid S. Miller return err;
5681da177e4SLinus Torvalds }
5691da177e4SLinus Torvalds
5701da177e4SLinus Torvalds
5711da177e4SLinus Torvalds /*
5721da177e4SLinus Torvalds * Handoff control to the correct ioctl handler.
5731da177e4SLinus Torvalds */
openprom_ioctl(struct file * file,unsigned int cmd,unsigned long arg)57455929332SArnd Bergmann static long openprom_ioctl(struct file * file,
5751da177e4SLinus Torvalds unsigned int cmd, unsigned long arg)
5761da177e4SLinus Torvalds {
57733cfe65aSJoe Perches DATA *data = file->private_data;
5781da177e4SLinus Torvalds
5791da177e4SLinus Torvalds switch (cmd) {
5801da177e4SLinus Torvalds case OPROMGETOPT:
5811da177e4SLinus Torvalds case OPROMNXTOPT:
5821da177e4SLinus Torvalds if ((file->f_mode & FMODE_READ) == 0)
5831da177e4SLinus Torvalds return -EPERM;
58455929332SArnd Bergmann return openprom_sunos_ioctl(file, cmd, arg,
5851da177e4SLinus Torvalds options_node);
5861da177e4SLinus Torvalds
5871da177e4SLinus Torvalds case OPROMSETOPT:
5881da177e4SLinus Torvalds case OPROMSETOPT2:
5891da177e4SLinus Torvalds if ((file->f_mode & FMODE_WRITE) == 0)
5901da177e4SLinus Torvalds return -EPERM;
59155929332SArnd Bergmann return openprom_sunos_ioctl(file, cmd, arg,
5921da177e4SLinus Torvalds options_node);
5931da177e4SLinus Torvalds
5941da177e4SLinus Torvalds case OPROMNEXT:
5951da177e4SLinus Torvalds case OPROMCHILD:
5961da177e4SLinus Torvalds case OPROMGETPROP:
5971da177e4SLinus Torvalds case OPROMNXTPROP:
5981da177e4SLinus Torvalds if ((file->f_mode & FMODE_READ) == 0)
5991da177e4SLinus Torvalds return -EPERM;
60055929332SArnd Bergmann return openprom_sunos_ioctl(file, cmd, arg,
6011da177e4SLinus Torvalds data->current_node);
6021da177e4SLinus Torvalds
6031da177e4SLinus Torvalds case OPROMU2P:
6041da177e4SLinus Torvalds case OPROMGETCONS:
6051da177e4SLinus Torvalds case OPROMGETFBNAME:
6061da177e4SLinus Torvalds case OPROMGETBOOTARGS:
6071da177e4SLinus Torvalds case OPROMSETCUR:
6081da177e4SLinus Torvalds case OPROMPCI2NODE:
6091da177e4SLinus Torvalds case OPROMPATH2NODE:
6101da177e4SLinus Torvalds if ((file->f_mode & FMODE_READ) == 0)
6111da177e4SLinus Torvalds return -EPERM;
61255929332SArnd Bergmann return openprom_sunos_ioctl(file, cmd, arg, NULL);
6131da177e4SLinus Torvalds
6141da177e4SLinus Torvalds case OPIOCGET:
6151da177e4SLinus Torvalds case OPIOCNEXTPROP:
6161da177e4SLinus Torvalds case OPIOCGETOPTNODE:
6171da177e4SLinus Torvalds case OPIOCGETNEXT:
6181da177e4SLinus Torvalds case OPIOCGETCHILD:
6191da177e4SLinus Torvalds if ((file->f_mode & FMODE_READ) == 0)
6201da177e4SLinus Torvalds return -EBADF;
62155929332SArnd Bergmann return openprom_bsd_ioctl(file,cmd,arg);
6221da177e4SLinus Torvalds
6231da177e4SLinus Torvalds case OPIOCSET:
6241da177e4SLinus Torvalds if ((file->f_mode & FMODE_WRITE) == 0)
6251da177e4SLinus Torvalds return -EBADF;
62655929332SArnd Bergmann return openprom_bsd_ioctl(file,cmd,arg);
6271da177e4SLinus Torvalds
6281da177e4SLinus Torvalds default:
6291da177e4SLinus Torvalds return -EINVAL;
6308e48aec7SDavid S. Miller };
6311da177e4SLinus Torvalds }
6321da177e4SLinus Torvalds
openprom_compat_ioctl(struct file * file,unsigned int cmd,unsigned long arg)633b31023fcSChristoph Hellwig static long openprom_compat_ioctl(struct file *file, unsigned int cmd,
634b31023fcSChristoph Hellwig unsigned long arg)
635b31023fcSChristoph Hellwig {
636b31023fcSChristoph Hellwig long rval = -ENOTTY;
637b31023fcSChristoph Hellwig
638b31023fcSChristoph Hellwig /*
639b31023fcSChristoph Hellwig * SunOS/Solaris only, the NetBSD one's have embedded pointers in
640b31023fcSChristoph Hellwig * the arg which we'd need to clean up...
641b31023fcSChristoph Hellwig */
642b31023fcSChristoph Hellwig switch (cmd) {
643b31023fcSChristoph Hellwig case OPROMGETOPT:
644b31023fcSChristoph Hellwig case OPROMSETOPT:
645b31023fcSChristoph Hellwig case OPROMNXTOPT:
646b31023fcSChristoph Hellwig case OPROMSETOPT2:
647b31023fcSChristoph Hellwig case OPROMNEXT:
648b31023fcSChristoph Hellwig case OPROMCHILD:
649b31023fcSChristoph Hellwig case OPROMGETPROP:
650b31023fcSChristoph Hellwig case OPROMNXTPROP:
651b31023fcSChristoph Hellwig case OPROMU2P:
652b31023fcSChristoph Hellwig case OPROMGETCONS:
653b31023fcSChristoph Hellwig case OPROMGETFBNAME:
654b31023fcSChristoph Hellwig case OPROMGETBOOTARGS:
655b31023fcSChristoph Hellwig case OPROMSETCUR:
656b31023fcSChristoph Hellwig case OPROMPCI2NODE:
657b31023fcSChristoph Hellwig case OPROMPATH2NODE:
65855929332SArnd Bergmann rval = openprom_ioctl(file, cmd, arg);
659b31023fcSChristoph Hellwig break;
660b31023fcSChristoph Hellwig }
661d5a858bcSDavid S. Miller
662d5a858bcSDavid S. Miller return rval;
663b31023fcSChristoph Hellwig }
664b31023fcSChristoph Hellwig
openprom_open(struct inode * inode,struct file * file)6651da177e4SLinus Torvalds static int openprom_open(struct inode * inode, struct file * file)
6661da177e4SLinus Torvalds {
6671da177e4SLinus Torvalds DATA *data;
6681da177e4SLinus Torvalds
6698e48aec7SDavid S. Miller data = kmalloc(sizeof(DATA), GFP_KERNEL);
6701da177e4SLinus Torvalds if (!data)
6711da177e4SLinus Torvalds return -ENOMEM;
6721da177e4SLinus Torvalds
673a3108ca2SArnd Bergmann mutex_lock(&openprom_mutex);
6748e48aec7SDavid S. Miller data->current_node = of_find_node_by_path("/");
6758e48aec7SDavid S. Miller data->lastnode = data->current_node;
6761da177e4SLinus Torvalds file->private_data = (void *) data;
677a3108ca2SArnd Bergmann mutex_unlock(&openprom_mutex);
6781da177e4SLinus Torvalds
6791da177e4SLinus Torvalds return 0;
6801da177e4SLinus Torvalds }
6811da177e4SLinus Torvalds
openprom_release(struct inode * inode,struct file * file)6821da177e4SLinus Torvalds static int openprom_release(struct inode * inode, struct file * file)
6831da177e4SLinus Torvalds {
6841da177e4SLinus Torvalds kfree(file->private_data);
6851da177e4SLinus Torvalds return 0;
6861da177e4SLinus Torvalds }
6871da177e4SLinus Torvalds
68800977a59SArjan van de Ven static const struct file_operations openprom_fops = {
6891da177e4SLinus Torvalds .owner = THIS_MODULE,
6901da177e4SLinus Torvalds .llseek = no_llseek,
69155929332SArnd Bergmann .unlocked_ioctl = openprom_ioctl,
692d5a858bcSDavid S. Miller .compat_ioctl = openprom_compat_ioctl,
6931da177e4SLinus Torvalds .open = openprom_open,
6941da177e4SLinus Torvalds .release = openprom_release,
6951da177e4SLinus Torvalds };
6961da177e4SLinus Torvalds
6971da177e4SLinus Torvalds static struct miscdevice openprom_dev = {
6988e48aec7SDavid S. Miller .minor = SUN_OPENPROM_MINOR,
6998e48aec7SDavid S. Miller .name = "openprom",
7008e48aec7SDavid S. Miller .fops = &openprom_fops,
7011da177e4SLinus Torvalds };
7021da177e4SLinus Torvalds
openprom_init(void)7031da177e4SLinus Torvalds static int __init openprom_init(void)
7041da177e4SLinus Torvalds {
7058e48aec7SDavid S. Miller int err;
7061da177e4SLinus Torvalds
7078e48aec7SDavid S. Miller err = misc_register(&openprom_dev);
7088e48aec7SDavid S. Miller if (err)
7098e48aec7SDavid S. Miller return err;
7108e48aec7SDavid S. Miller
711df58f37bSRob Herring options_node = of_get_child_by_name(of_find_node_by_path("/"), "options");
7128e48aec7SDavid S. Miller if (!options_node) {
7131da177e4SLinus Torvalds misc_deregister(&openprom_dev);
7141da177e4SLinus Torvalds return -EIO;
7151da177e4SLinus Torvalds }
7161da177e4SLinus Torvalds
7171da177e4SLinus Torvalds return 0;
7181da177e4SLinus Torvalds }
7191da177e4SLinus Torvalds
openprom_cleanup(void)7201da177e4SLinus Torvalds static void __exit openprom_cleanup(void)
7211da177e4SLinus Torvalds {
7221da177e4SLinus Torvalds misc_deregister(&openprom_dev);
7231da177e4SLinus Torvalds }
7241da177e4SLinus Torvalds
7251da177e4SLinus Torvalds module_init(openprom_init);
7261da177e4SLinus Torvalds module_exit(openprom_cleanup);
727