diff options
Diffstat (limited to 'drivers/sbus/char/openprom.c')
-rw-r--r-- | drivers/sbus/char/openprom.c | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c new file mode 100644 index 0000000..58ed337 --- /dev/null +++ b/drivers/sbus/char/openprom.c @@ -0,0 +1,630 @@ +/* + * Linux/SPARC PROM Configuration Driver + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * + * This character device driver allows user programs to access the + * PROM device tree. It is compatible with the SunOS /dev/openprom + * driver and the NetBSD /dev/openprom driver. The SunOS eeprom + * utility works without any modifications. + * + * The driver uses a minor number under the misc device major. The + * file read/write mode determines the type of access to the PROM. + * Interrupts are disabled whenever the driver calls into the PROM for + * sanity's sake. + */ + +/* This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define PROMLIB_INTERNAL + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <asm/oplib.h> +#include <asm/system.h> +#include <asm/uaccess.h> +#include <asm/openpromio.h> +#ifdef CONFIG_PCI +#include <linux/pci.h> +#include <asm/pbm.h> +#endif + +/* Private data kept by the driver for each descriptor. */ +typedef struct openprom_private_data +{ + int current_node; /* Current node for SunOS ioctls. */ + int lastnode; /* Last valid node used by BSD ioctls. */ +} DATA; + +/* ID of the PROM node containing all of the EEPROM options. */ +static int options_node = 0; + +/* + * Copy an openpromio structure into kernel space from user space. + * This routine does error checking to make sure that all memory + * accesses are within bounds. A pointer to the allocated openpromio + * structure will be placed in "*opp_p". Return value is the length + * of the user supplied buffer. + */ +static int copyin(struct openpromio __user *info, struct openpromio **opp_p) +{ + unsigned int bufsize; + + if (!info || !opp_p) + return -EFAULT; + + if (get_user(bufsize, &info->oprom_size)) + return -EFAULT; + + if (bufsize == 0) + return -EINVAL; + + /* If the bufsize is too large, just limit it. + * Fix from Jason Rappleye. + */ + if (bufsize > OPROMMAXPARAM) + bufsize = OPROMMAXPARAM; + + if (!(*opp_p = kmalloc(sizeof(int) + bufsize + 1, GFP_KERNEL))) + return -ENOMEM; + memset(*opp_p, 0, sizeof(int) + bufsize + 1); + + if (copy_from_user(&(*opp_p)->oprom_array, + &info->oprom_array, bufsize)) { + kfree(*opp_p); + return -EFAULT; + } + return bufsize; +} + +static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) +{ + int n, bufsize; + char c; + + if (!info || !opp_p) + return -EFAULT; + + if (!(*opp_p = kmalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL))) + return -ENOMEM; + + memset(*opp_p, 0, sizeof(int) + OPROMMAXPARAM + 1); + (*opp_p)->oprom_size = 0; + + n = bufsize = 0; + while ((n < 2) && (bufsize < OPROMMAXPARAM)) { + if (get_user(c, &info->oprom_array[bufsize])) { + kfree(*opp_p); + return -EFAULT; + } + if (c == '\0') + n++; + (*opp_p)->oprom_array[bufsize++] = c; + } + if (!n) { + kfree(*opp_p); + return -EINVAL; + } + return bufsize; +} + +/* + * Copy an openpromio structure in kernel space back to user space. + */ +static int copyout(void __user *info, struct openpromio *opp, int len) +{ + if (copy_to_user(info, opp, len)) + return -EFAULT; + return 0; +} + +/* + * SunOS and Solaris /dev/openprom ioctl calls. + */ +static int openprom_sunos_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg, int node) +{ + DATA *data = (DATA *) file->private_data; + char buffer[OPROMMAXPARAM+1], *buf; + struct openpromio *opp; + int bufsize, len, error = 0; + static int cnt; + void __user *argp = (void __user *)arg; + + if (cmd == OPROMSETOPT) + bufsize = getstrings(argp, &opp); + else + bufsize = copyin(argp, &opp); + + if (bufsize < 0) + return bufsize; + + switch (cmd) { + case OPROMGETOPT: + case OPROMGETPROP: + len = prom_getproplen(node, opp->oprom_array); + + if (len <= 0 || len > bufsize) { + error = copyout(argp, opp, sizeof(int)); + break; + } + + len = prom_getproperty(node, opp->oprom_array, buffer, bufsize); + + memcpy(opp->oprom_array, buffer, len); + opp->oprom_array[len] = '\0'; + opp->oprom_size = len; + + error = copyout(argp, opp, sizeof(int) + bufsize); + break; + + case OPROMNXTOPT: + case OPROMNXTPROP: + buf = prom_nextprop(node, opp->oprom_array, buffer); + + len = strlen(buf); + if (len == 0 || len + 1 > bufsize) { + error = copyout(argp, opp, sizeof(int)); + break; + } + + memcpy(opp->oprom_array, buf, len); + opp->oprom_array[len] = '\0'; + opp->oprom_size = ++len; + + error = copyout(argp, opp, sizeof(int) + bufsize); + break; + + case OPROMSETOPT: + case OPROMSETOPT2: + buf = opp->oprom_array + strlen(opp->oprom_array) + 1; + len = opp->oprom_array + bufsize - buf; + + error = prom_setprop(options_node, opp->oprom_array, + buf, len); + + if (error < 0) + error = -EINVAL; + break; + + case OPROMNEXT: + case OPROMCHILD: + case OPROMSETCUR: + if (bufsize < sizeof(int)) { + error = -EINVAL; + break; + } + + node = *((int *) opp->oprom_array); + + switch (cmd) { + case OPROMNEXT: node = __prom_getsibling(node); break; + case OPROMCHILD: node = __prom_getchild(node); break; + case OPROMSETCUR: break; + } + + data->current_node = node; + *((int *)opp->oprom_array) = node; + opp->oprom_size = sizeof(int); + + error = copyout(argp, opp, bufsize + sizeof(int)); + break; + + case OPROMPCI2NODE: + error = -EINVAL; + + if (bufsize >= 2*sizeof(int)) { +#ifdef CONFIG_PCI + struct pci_dev *pdev; + struct pcidev_cookie *pcp; + pdev = pci_find_slot (((int *) opp->oprom_array)[0], + ((int *) opp->oprom_array)[1]); + + pcp = pdev->sysdata; + if (pcp != NULL && pcp->prom_node != -1 && pcp->prom_node) { + node = pcp->prom_node; + data->current_node = node; + *((int *)opp->oprom_array) = node; + opp->oprom_size = sizeof(int); + error = copyout(argp, opp, bufsize + sizeof(int)); + } +#endif + } + break; + + case OPROMPATH2NODE: + node = prom_finddevice(opp->oprom_array); + data->current_node = node; + *((int *)opp->oprom_array) = node; + opp->oprom_size = sizeof(int); + + error = copyout(argp, opp, bufsize + sizeof(int)); + break; + + case OPROMGETBOOTARGS: + buf = saved_command_line; + + len = strlen(buf); + + if (len > bufsize) { + error = -EINVAL; + break; + } + + strcpy(opp->oprom_array, buf); + opp->oprom_size = len; + + error = copyout(argp, opp, bufsize + sizeof(int)); + break; + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n"); + error = -EINVAL; + break; + default: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg); + error = -EINVAL; + break; + } + + kfree(opp); + return error; +} + + +/* Return nonzero if a specific node is in the PROM device tree. */ +static int intree(int root, int node) +{ + for (; root != 0; root = prom_getsibling(root)) + if (root == node || intree(prom_getchild(root),node)) + return 1; + return 0; +} + +/* Return nonzero if a specific node is "valid". */ +static int goodnode(int n, DATA *data) +{ + if (n == data->lastnode || n == prom_root_node || n == options_node) + return 1; + if (n == 0 || n == -1 || !intree(prom_root_node,n)) + return 0; + data->lastnode = n; + return 1; +} + +/* Copy in a whole string from userspace into kernelspace. */ +static int copyin_string(char __user *user, size_t len, char **ptr) +{ + char *tmp; + + if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0) + return -EINVAL; + + tmp = kmalloc(len + 1, GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + if(copy_from_user(tmp, user, len)) { + kfree(tmp); + return -EFAULT; + } + + tmp[len] = '\0'; + + *ptr = tmp; + + return 0; +} + +/* + * NetBSD /dev/openprom ioctl calls. + */ +static int openprom_bsd_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = (DATA *) file->private_data; + void __user *argp = (void __user *)arg; + struct opiocdesc op; + int error, node, len; + char *str, *tmp; + char buffer[64]; + static int cnt; + + switch (cmd) { + case OPIOCGET: + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + if (!goodnode(op.op_nodeid,data)) + return -EINVAL; + + error = copyin_string(op.op_name, op.op_namelen, &str); + if (error) + return error; + + len = prom_getproplen(op.op_nodeid,str); + + if (len > op.op_buflen) { + kfree(str); + return -ENOMEM; + } + + op.op_buflen = len; + + if (len <= 0) { + kfree(str); + /* Verified by the above copy_from_user */ + if (__copy_to_user(argp, &op, + sizeof(op))) + return -EFAULT; + return 0; + } + + tmp = kmalloc(len + 1, GFP_KERNEL); + if (!tmp) { + kfree(str); + return -ENOMEM; + } + + prom_getproperty(op.op_nodeid, str, tmp, len); + + tmp[len] = '\0'; + + if (__copy_to_user(argp, &op, sizeof(op)) != 0 + || copy_to_user(op.op_buf, tmp, len) != 0) + error = -EFAULT; + + kfree(tmp); + kfree(str); + + return error; + + case OPIOCNEXTPROP: + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + if (!goodnode(op.op_nodeid,data)) + return -EINVAL; + + error = copyin_string(op.op_name, op.op_namelen, &str); + if (error) + return error; + + tmp = prom_nextprop(op.op_nodeid,str,buffer); + + if (tmp) { + len = strlen(tmp); + if (len > op.op_buflen) + len = op.op_buflen; + else + op.op_buflen = len; + } else { + len = op.op_buflen = 0; + } + + if (!access_ok(VERIFY_WRITE, argp, sizeof(op))) { + kfree(str); + return -EFAULT; + } + + if (!access_ok(VERIFY_WRITE, op.op_buf, len)) { + kfree(str); + return -EFAULT; + } + + error = __copy_to_user(argp, &op, sizeof(op)); + if (!error) error = __copy_to_user(op.op_buf, tmp, len); + + kfree(str); + + return error; + + case OPIOCSET: + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + if (!goodnode(op.op_nodeid,data)) + return -EINVAL; + + error = copyin_string(op.op_name, op.op_namelen, &str); + if (error) + return error; + + error = copyin_string(op.op_buf, op.op_buflen, &tmp); + if (error) { + kfree(str); + return error; + } + + len = prom_setprop(op.op_nodeid,str,tmp,op.op_buflen+1); + + if (len != op.op_buflen) + return -EINVAL; + + kfree(str); + kfree(tmp); + + return 0; + + case OPIOCGETOPTNODE: + if (copy_to_user(argp, &options_node, sizeof(int))) + return -EFAULT; + return 0; + + case OPIOCGETNEXT: + case OPIOCGETCHILD: + if (copy_from_user(&node, argp, sizeof(int))) + return -EFAULT; + + if (cmd == OPIOCGETNEXT) + node = __prom_getsibling(node); + else + node = __prom_getchild(node); + + if (__copy_to_user(argp, &node, sizeof(int))) + return -EFAULT; + + return 0; + + default: + if (cnt++ < 10) + printk(KERN_INFO "openprom_bsd_ioctl: cmd 0x%X\n", cmd); + return -EINVAL; + + } +} + + +/* + * Handoff control to the correct ioctl handler. + */ +static int openprom_ioctl(struct inode * inode, struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = (DATA *) file->private_data; + static int cnt; + + switch (cmd) { + case OPROMGETOPT: + case OPROMNXTOPT: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(inode, file, cmd, arg, + options_node); + + case OPROMSETOPT: + case OPROMSETOPT2: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EPERM; + return openprom_sunos_ioctl(inode, file, cmd, arg, + options_node); + + case OPROMNEXT: + case OPROMCHILD: + case OPROMGETPROP: + case OPROMNXTPROP: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(inode, file, cmd, arg, + data->current_node); + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + case OPROMGETBOOTARGS: + case OPROMSETCUR: + case OPROMPCI2NODE: + case OPROMPATH2NODE: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(inode, file, cmd, arg, 0); + + case OPIOCGET: + case OPIOCNEXTPROP: + case OPIOCGETOPTNODE: + case OPIOCGETNEXT: + case OPIOCGETCHILD: + if ((file->f_mode & FMODE_READ) == 0) + return -EBADF; + return openprom_bsd_ioctl(inode,file,cmd,arg); + + case OPIOCSET: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EBADF; + return openprom_bsd_ioctl(inode,file,cmd,arg); + + default: + if (cnt++ < 10) + printk("openprom_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg); + return -EINVAL; + } +} + +static int openprom_open(struct inode * inode, struct file * file) +{ + DATA *data; + + data = (DATA *) kmalloc(sizeof(DATA), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->current_node = prom_root_node; + data->lastnode = prom_root_node; + file->private_data = (void *)data; + + return 0; +} + +static int openprom_release(struct inode * inode, struct file * file) +{ + kfree(file->private_data); + return 0; +} + +static struct file_operations openprom_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = openprom_ioctl, + .open = openprom_open, + .release = openprom_release, +}; + +static struct miscdevice openprom_dev = { + SUN_OPENPROM_MINOR, "openprom", &openprom_fops +}; + +static int __init openprom_init(void) +{ + int error; + + error = misc_register(&openprom_dev); + if (error) { + printk(KERN_ERR "openprom: unable to get misc minor\n"); + return error; + } + + options_node = prom_getchild(prom_root_node); + options_node = prom_searchsiblings(options_node,"options"); + + if (options_node == 0 || options_node == -1) { + printk(KERN_ERR "openprom: unable to find options node\n"); + misc_deregister(&openprom_dev); + return -EIO; + } + + return 0; +} + +static void __exit openprom_cleanup(void) +{ + misc_deregister(&openprom_dev); +} + +module_init(openprom_init); +module_exit(openprom_cleanup); +MODULE_LICENSE("GPL"); |