mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 09:50:04 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 441 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Michael Ellerman <mpe@ellerman.id.au> (powerpc) Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190520071858.739733335@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
726 lines
15 KiB
C
726 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/string.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include <asm/oplib.h>
|
|
#include <asm/prom.h>
|
|
#include <linux/uaccess.h>
|
|
#include <asm/openpromio.h>
|
|
#ifdef CONFIG_PCI
|
|
#include <linux/pci.h>
|
|
#endif
|
|
|
|
MODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost (ecd@skynet.be)");
|
|
MODULE_DESCRIPTION("OPENPROM Configuration Driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("1.0");
|
|
MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
|
|
|
|
/* Private data kept by the driver for each descriptor. */
|
|
typedef struct openprom_private_data
|
|
{
|
|
struct device_node *current_node; /* Current node for SunOS ioctls. */
|
|
struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
|
|
} DATA;
|
|
|
|
/* ID of the PROM node containing all of the EEPROM options. */
|
|
static DEFINE_MUTEX(openprom_mutex);
|
|
static struct device_node *options_node;
|
|
|
|
/*
|
|
* 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 = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
|
|
return -ENOMEM;
|
|
|
|
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 = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
|
|
return -ENOMEM;
|
|
|
|
(*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;
|
|
}
|
|
|
|
static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
|
|
{
|
|
const void *pval;
|
|
int len;
|
|
|
|
if (!dp ||
|
|
!(pval = of_get_property(dp, op->oprom_array, &len)) ||
|
|
len <= 0 || len > bufsize)
|
|
return copyout(argp, op, sizeof(int));
|
|
|
|
memcpy(op->oprom_array, pval, len);
|
|
op->oprom_array[len] = '\0';
|
|
op->oprom_size = len;
|
|
|
|
return copyout(argp, op, sizeof(int) + bufsize);
|
|
}
|
|
|
|
static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
|
|
{
|
|
struct property *prop;
|
|
int len;
|
|
|
|
if (!dp)
|
|
return copyout(argp, op, sizeof(int));
|
|
if (op->oprom_array[0] == '\0') {
|
|
prop = dp->properties;
|
|
if (!prop)
|
|
return copyout(argp, op, sizeof(int));
|
|
len = strlen(prop->name);
|
|
} else {
|
|
prop = of_find_property(dp, op->oprom_array, NULL);
|
|
|
|
if (!prop ||
|
|
!prop->next ||
|
|
(len = strlen(prop->next->name)) + 1 > bufsize)
|
|
return copyout(argp, op, sizeof(int));
|
|
|
|
prop = prop->next;
|
|
}
|
|
|
|
memcpy(op->oprom_array, prop->name, len);
|
|
op->oprom_array[len] = '\0';
|
|
op->oprom_size = ++len;
|
|
|
|
return copyout(argp, op, sizeof(int) + bufsize);
|
|
}
|
|
|
|
static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
|
|
{
|
|
char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
|
|
int len = op->oprom_array + bufsize - buf;
|
|
|
|
return of_set_property(options_node, op->oprom_array, buf, len);
|
|
}
|
|
|
|
static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
|
{
|
|
phandle ph;
|
|
|
|
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
|
|
|
if (bufsize < sizeof(phandle))
|
|
return -EINVAL;
|
|
|
|
ph = *((int *) op->oprom_array);
|
|
if (ph) {
|
|
dp = of_find_node_by_phandle(ph);
|
|
if (!dp)
|
|
return -EINVAL;
|
|
|
|
switch (cmd) {
|
|
case OPROMNEXT:
|
|
dp = dp->sibling;
|
|
break;
|
|
|
|
case OPROMCHILD:
|
|
dp = dp->child;
|
|
break;
|
|
|
|
case OPROMSETCUR:
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
/* Sibling of node zero is the root node. */
|
|
if (cmd != OPROMNEXT)
|
|
return -EINVAL;
|
|
|
|
dp = of_find_node_by_path("/");
|
|
}
|
|
|
|
ph = 0;
|
|
if (dp)
|
|
ph = dp->phandle;
|
|
|
|
data->current_node = dp;
|
|
*((int *) op->oprom_array) = ph;
|
|
op->oprom_size = sizeof(phandle);
|
|
|
|
return copyout(argp, op, bufsize + sizeof(int));
|
|
}
|
|
|
|
static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
|
{
|
|
int err = -EINVAL;
|
|
|
|
if (bufsize >= 2*sizeof(int)) {
|
|
#ifdef CONFIG_PCI
|
|
struct pci_dev *pdev;
|
|
struct device_node *dp;
|
|
|
|
pdev = pci_get_domain_bus_and_slot(0,
|
|
((int *) op->oprom_array)[0],
|
|
((int *) op->oprom_array)[1]);
|
|
|
|
dp = pci_device_to_OF_node(pdev);
|
|
data->current_node = dp;
|
|
*((int *)op->oprom_array) = dp->phandle;
|
|
op->oprom_size = sizeof(int);
|
|
err = copyout(argp, op, bufsize + sizeof(int));
|
|
|
|
pci_dev_put(pdev);
|
|
#endif
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
|
|
{
|
|
phandle ph = 0;
|
|
|
|
dp = of_find_node_by_path(op->oprom_array);
|
|
if (dp)
|
|
ph = dp->phandle;
|
|
data->current_node = dp;
|
|
*((int *)op->oprom_array) = ph;
|
|
op->oprom_size = sizeof(int);
|
|
|
|
return copyout(argp, op, bufsize + sizeof(int));
|
|
}
|
|
|
|
static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
|
|
{
|
|
char *buf = saved_command_line;
|
|
int len = strlen(buf);
|
|
|
|
if (len > bufsize)
|
|
return -EINVAL;
|
|
|
|
strcpy(op->oprom_array, buf);
|
|
op->oprom_size = len;
|
|
|
|
return copyout(argp, op, bufsize + sizeof(int));
|
|
}
|
|
|
|
/*
|
|
* SunOS and Solaris /dev/openprom ioctl calls.
|
|
*/
|
|
static long openprom_sunos_ioctl(struct file * file,
|
|
unsigned int cmd, unsigned long arg,
|
|
struct device_node *dp)
|
|
{
|
|
DATA *data = file->private_data;
|
|
struct openpromio *opp = NULL;
|
|
int bufsize, 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;
|
|
|
|
mutex_lock(&openprom_mutex);
|
|
|
|
switch (cmd) {
|
|
case OPROMGETOPT:
|
|
case OPROMGETPROP:
|
|
error = opromgetprop(argp, dp, opp, bufsize);
|
|
break;
|
|
|
|
case OPROMNXTOPT:
|
|
case OPROMNXTPROP:
|
|
error = opromnxtprop(argp, dp, opp, bufsize);
|
|
break;
|
|
|
|
case OPROMSETOPT:
|
|
case OPROMSETOPT2:
|
|
error = opromsetopt(dp, opp, bufsize);
|
|
break;
|
|
|
|
case OPROMNEXT:
|
|
case OPROMCHILD:
|
|
case OPROMSETCUR:
|
|
error = opromnext(argp, cmd, dp, opp, bufsize, data);
|
|
break;
|
|
|
|
case OPROMPCI2NODE:
|
|
error = oprompci2node(argp, dp, opp, bufsize, data);
|
|
break;
|
|
|
|
case OPROMPATH2NODE:
|
|
error = oprompath2node(argp, dp, opp, bufsize, data);
|
|
break;
|
|
|
|
case OPROMGETBOOTARGS:
|
|
error = opromgetbootargs(argp, opp, bufsize);
|
|
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);
|
|
mutex_unlock(&openprom_mutex);
|
|
|
|
return error;
|
|
}
|
|
|
|
static struct device_node *get_node(phandle n, DATA *data)
|
|
{
|
|
struct device_node *dp = of_find_node_by_phandle(n);
|
|
|
|
if (dp)
|
|
data->lastnode = dp;
|
|
|
|
return dp;
|
|
}
|
|
|
|
/* Copy in a whole string from userspace into kernelspace. */
|
|
static char * copyin_string(char __user *user, size_t len)
|
|
{
|
|
if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return memdup_user_nul(user, len);
|
|
}
|
|
|
|
/*
|
|
* NetBSD /dev/openprom ioctl calls.
|
|
*/
|
|
static int opiocget(void __user *argp, DATA *data)
|
|
{
|
|
struct opiocdesc op;
|
|
struct device_node *dp;
|
|
char *str;
|
|
const void *pval;
|
|
int err, len;
|
|
|
|
if (copy_from_user(&op, argp, sizeof(op)))
|
|
return -EFAULT;
|
|
|
|
dp = get_node(op.op_nodeid, data);
|
|
|
|
str = copyin_string(op.op_name, op.op_namelen);
|
|
if (IS_ERR(str))
|
|
return PTR_ERR(str);
|
|
|
|
pval = of_get_property(dp, str, &len);
|
|
err = 0;
|
|
if (!pval || len > op.op_buflen) {
|
|
err = -EINVAL;
|
|
} else {
|
|
op.op_buflen = len;
|
|
if (copy_to_user(argp, &op, sizeof(op)) ||
|
|
copy_to_user(op.op_buf, pval, len))
|
|
err = -EFAULT;
|
|
}
|
|
kfree(str);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int opiocnextprop(void __user *argp, DATA *data)
|
|
{
|
|
struct opiocdesc op;
|
|
struct device_node *dp;
|
|
struct property *prop;
|
|
char *str;
|
|
int len;
|
|
|
|
if (copy_from_user(&op, argp, sizeof(op)))
|
|
return -EFAULT;
|
|
|
|
dp = get_node(op.op_nodeid, data);
|
|
if (!dp)
|
|
return -EINVAL;
|
|
|
|
str = copyin_string(op.op_name, op.op_namelen);
|
|
if (IS_ERR(str))
|
|
return PTR_ERR(str);
|
|
|
|
if (str[0] == '\0') {
|
|
prop = dp->properties;
|
|
} else {
|
|
prop = of_find_property(dp, str, NULL);
|
|
if (prop)
|
|
prop = prop->next;
|
|
}
|
|
kfree(str);
|
|
|
|
if (!prop)
|
|
len = 0;
|
|
else
|
|
len = prop->length;
|
|
|
|
if (len > op.op_buflen)
|
|
len = op.op_buflen;
|
|
|
|
if (copy_to_user(argp, &op, sizeof(op)))
|
|
return -EFAULT;
|
|
|
|
if (len &&
|
|
copy_to_user(op.op_buf, prop->value, len))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int opiocset(void __user *argp, DATA *data)
|
|
{
|
|
struct opiocdesc op;
|
|
struct device_node *dp;
|
|
char *str, *tmp;
|
|
int err;
|
|
|
|
if (copy_from_user(&op, argp, sizeof(op)))
|
|
return -EFAULT;
|
|
|
|
dp = get_node(op.op_nodeid, data);
|
|
if (!dp)
|
|
return -EINVAL;
|
|
|
|
str = copyin_string(op.op_name, op.op_namelen);
|
|
if (IS_ERR(str))
|
|
return PTR_ERR(str);
|
|
|
|
tmp = copyin_string(op.op_buf, op.op_buflen);
|
|
if (IS_ERR(tmp)) {
|
|
kfree(str);
|
|
return PTR_ERR(tmp);
|
|
}
|
|
|
|
err = of_set_property(dp, str, tmp, op.op_buflen);
|
|
|
|
kfree(str);
|
|
kfree(tmp);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int opiocgetnext(unsigned int cmd, void __user *argp)
|
|
{
|
|
struct device_node *dp;
|
|
phandle nd;
|
|
|
|
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
|
|
|
if (copy_from_user(&nd, argp, sizeof(phandle)))
|
|
return -EFAULT;
|
|
|
|
if (nd == 0) {
|
|
if (cmd != OPIOCGETNEXT)
|
|
return -EINVAL;
|
|
dp = of_find_node_by_path("/");
|
|
} else {
|
|
dp = of_find_node_by_phandle(nd);
|
|
nd = 0;
|
|
if (dp) {
|
|
if (cmd == OPIOCGETNEXT)
|
|
dp = dp->sibling;
|
|
else
|
|
dp = dp->child;
|
|
}
|
|
}
|
|
if (dp)
|
|
nd = dp->phandle;
|
|
if (copy_to_user(argp, &nd, sizeof(phandle)))
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int openprom_bsd_ioctl(struct file * file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
DATA *data = file->private_data;
|
|
void __user *argp = (void __user *)arg;
|
|
int err;
|
|
|
|
mutex_lock(&openprom_mutex);
|
|
switch (cmd) {
|
|
case OPIOCGET:
|
|
err = opiocget(argp, data);
|
|
break;
|
|
|
|
case OPIOCNEXTPROP:
|
|
err = opiocnextprop(argp, data);
|
|
break;
|
|
|
|
case OPIOCSET:
|
|
err = opiocset(argp, data);
|
|
break;
|
|
|
|
case OPIOCGETOPTNODE:
|
|
BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
|
|
|
|
err = 0;
|
|
if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
|
|
err = -EFAULT;
|
|
break;
|
|
|
|
case OPIOCGETNEXT:
|
|
case OPIOCGETCHILD:
|
|
err = opiocgetnext(cmd, argp);
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
mutex_unlock(&openprom_mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Handoff control to the correct ioctl handler.
|
|
*/
|
|
static long openprom_ioctl(struct file * file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
DATA *data = file->private_data;
|
|
|
|
switch (cmd) {
|
|
case OPROMGETOPT:
|
|
case OPROMNXTOPT:
|
|
if ((file->f_mode & FMODE_READ) == 0)
|
|
return -EPERM;
|
|
return openprom_sunos_ioctl(file, cmd, arg,
|
|
options_node);
|
|
|
|
case OPROMSETOPT:
|
|
case OPROMSETOPT2:
|
|
if ((file->f_mode & FMODE_WRITE) == 0)
|
|
return -EPERM;
|
|
return openprom_sunos_ioctl(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(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(file, cmd, arg, NULL);
|
|
|
|
case OPIOCGET:
|
|
case OPIOCNEXTPROP:
|
|
case OPIOCGETOPTNODE:
|
|
case OPIOCGETNEXT:
|
|
case OPIOCGETCHILD:
|
|
if ((file->f_mode & FMODE_READ) == 0)
|
|
return -EBADF;
|
|
return openprom_bsd_ioctl(file,cmd,arg);
|
|
|
|
case OPIOCSET:
|
|
if ((file->f_mode & FMODE_WRITE) == 0)
|
|
return -EBADF;
|
|
return openprom_bsd_ioctl(file,cmd,arg);
|
|
|
|
default:
|
|
return -EINVAL;
|
|
};
|
|
}
|
|
|
|
static long openprom_compat_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
long rval = -ENOTTY;
|
|
|
|
/*
|
|
* SunOS/Solaris only, the NetBSD one's have embedded pointers in
|
|
* the arg which we'd need to clean up...
|
|
*/
|
|
switch (cmd) {
|
|
case OPROMGETOPT:
|
|
case OPROMSETOPT:
|
|
case OPROMNXTOPT:
|
|
case OPROMSETOPT2:
|
|
case OPROMNEXT:
|
|
case OPROMCHILD:
|
|
case OPROMGETPROP:
|
|
case OPROMNXTPROP:
|
|
case OPROMU2P:
|
|
case OPROMGETCONS:
|
|
case OPROMGETFBNAME:
|
|
case OPROMGETBOOTARGS:
|
|
case OPROMSETCUR:
|
|
case OPROMPCI2NODE:
|
|
case OPROMPATH2NODE:
|
|
rval = openprom_ioctl(file, cmd, arg);
|
|
break;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int openprom_open(struct inode * inode, struct file * file)
|
|
{
|
|
DATA *data;
|
|
|
|
data = kmalloc(sizeof(DATA), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&openprom_mutex);
|
|
data->current_node = of_find_node_by_path("/");
|
|
data->lastnode = data->current_node;
|
|
file->private_data = (void *) data;
|
|
mutex_unlock(&openprom_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int openprom_release(struct inode * inode, struct file * file)
|
|
{
|
|
kfree(file->private_data);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations openprom_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = openprom_ioctl,
|
|
.compat_ioctl = openprom_compat_ioctl,
|
|
.open = openprom_open,
|
|
.release = openprom_release,
|
|
};
|
|
|
|
static struct miscdevice openprom_dev = {
|
|
.minor = SUN_OPENPROM_MINOR,
|
|
.name = "openprom",
|
|
.fops = &openprom_fops,
|
|
};
|
|
|
|
static int __init openprom_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = misc_register(&openprom_dev);
|
|
if (err)
|
|
return err;
|
|
|
|
options_node = of_get_child_by_name(of_find_node_by_path("/"), "options");
|
|
if (!options_node) {
|
|
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);
|