/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Ported from 4.1.1_PSRA: "@(#)openprom.c 1.19 91/02/19 SMI";
*
* Porting notes:
*
* OPROMU2P unsupported after SunOS 4.x.
*
* Only one of these devices per system is allowed.
*/
/*
* Openprom eeprom options/devinfo driver.
*/
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/openpromio.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/modctl.h>
#include <sys/debug.h>
#include <sys/autoconf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/promif.h>
#include <sys/sysmacros.h> /* offsetof */
#include <sys/nvpair.h>
#include <sys/wanboot_impl.h>
#include <sys/zone.h>
#include <sys/consplat.h>
#include <sys/bootconf.h>
#include <sys/systm.h>
#include <sys/bootprops.h>
#define MAX_OPENS 32 /* Up to this many simultaneous opens */
#define IOC_IDLE 0 /* snapshot ioctl states */
#define IOC_SNAP 1 /* snapshot in progress */
#define IOC_DONE 2 /* snapshot done, but not copied out */
#define IOC_COPY 3 /* copyout in progress */
/*
* XXX Make this dynamic.. or (better still) make the interface stateless
*/
static struct oprom_state {
pnode_t current_id; /* node we're fetching props from */
int16_t already_open; /* if true, this instance is 'active' */
int16_t ioc_state; /* snapshot ioctl state */
char *snapshot; /* snapshot of all prom nodes */
size_t size; /* size of snapshot */
prom_generation_cookie_t tree_gen;
} oprom_state[MAX_OPENS];
static kmutex_t oprom_lock; /* serialize instance assignment */
static int opromopen(dev_t *, int, int, cred_t *);
static int opromioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int opromclose(dev_t, int, int, cred_t *);
static int opinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result);
static int opattach(dev_info_t *, ddi_attach_cmd_t cmd);
static int opdetach(dev_info_t *, ddi_detach_cmd_t cmd);
/* help functions */
static int oprom_checknodeid(pnode_t, pnode_t);
static int oprom_copyinstr(intptr_t, char *, size_t, size_t);
static int oprom_copynode(pnode_t, uint_t, char **, size_t *);
static int oprom_snapshot(struct oprom_state *, intptr_t);
static int oprom_copyout(struct oprom_state *, intptr_t);
static int oprom_setstate(struct oprom_state *, int16_t);
static struct cb_ops openeepr_cb_ops = {
opromopen, /* open */
opromclose, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
opromioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
NULL, /* streamtab */
D_NEW | D_MP /* Driver compatibility flag */
};
static struct dev_ops openeepr_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
opinfo, /* info */
nulldev, /* identify */
nulldev, /* probe */
opattach, /* attach */
opdetach, /* detach */
nodev, /* reset */
&openeepr_cb_ops, /* driver operations */
NULL, /* bus operations */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
/*
* Module linkage information for the kernel.
*/
static struct modldrv modldrv = {
&mod_driverops,
"OPENPROM/NVRAM Driver",
&openeepr_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
NULL
};
int
_init(void)
{
int error;
mutex_init(&oprom_lock, NULL, MUTEX_DRIVER, NULL);
error = mod_install(&modlinkage);
if (error != 0) {
mutex_destroy(&oprom_lock);
return (error);
}
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int error;
error = mod_remove(&modlinkage);
if (error != 0)
return (error);
mutex_destroy(&oprom_lock);
return (0);
}
static dev_info_t *opdip;
static pnode_t options_nodeid;
/*ARGSUSED*/
static int
opinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = (void *)opdip;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
/* All dev_t's map to the same, single instance */
*result = (void *)0;
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
static int
opattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
if (prom_is_openprom()) {
options_nodeid = prom_optionsnode();
} else {
options_nodeid = OBP_BADNODE;
}
opdip = dip;
if (ddi_create_minor_node(dip, "openprom", S_IFCHR,
0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
opdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
ddi_remove_minor_node(dip, NULL);
opdip = NULL;
return (DDI_SUCCESS);
}
/*
* Allow multiple opens by tweaking the dev_t such that it looks like each
* open is getting a different minor device. Each minor gets a separate
* entry in the oprom_state[] table.
*/
/*ARGSUSED*/
static int
opromopen(dev_t *devp, int flag, int otyp, cred_t *credp)
{
int m;
struct oprom_state *st = oprom_state;
if (getminor(*devp) != 0)
return (ENXIO);
mutex_enter(&oprom_lock);
for (m = 0; m < MAX_OPENS; m++)
if (st->already_open)
st++;
else {
st->already_open = 1;
/*
* It's ours.
*/
st->current_id = (pnode_t)0;
ASSERT(st->snapshot == NULL && st->size == 0);
ASSERT(st->ioc_state == IOC_IDLE);
break;
}
mutex_exit(&oprom_lock);
if (m == MAX_OPENS) {
/*
* "Thank you for calling, but all our lines are
* busy at the moment.."
*
* We could get sophisticated here, and go into a
* sleep-retry loop .. but hey, I just can't see
* that many processes sitting in this driver.
*
* (And if it does become possible, then we should
* change the interface so that the 'state' is held
* external to the driver)
*/
return (EAGAIN);
}
*devp = makedevice(getmajor(*devp), (minor_t)m);
return (0);
}
/*ARGSUSED*/
static int
opromclose(dev_t dev, int flag, int otype, cred_t *cred_p)
{
struct oprom_state *st;
st = &oprom_state[getminor(dev)];
ASSERT(getminor(dev) < MAX_OPENS && st->already_open != 0);
if (st->snapshot) {
kmem_free(st->snapshot, st->size);
st->snapshot = NULL;
st->size = 0;
st->ioc_state = IOC_IDLE;
}
mutex_enter(&oprom_lock);
st->already_open = 0;
mutex_exit(&oprom_lock);
return (0);
}
#ifdef __sparc
static int
get_bootpath_prop(char *bootpath)
{
if (root_is_ramdisk) {
if (BOP_GETPROP(bootops, "bootarchive", bootpath) == -1)
return (-1);
(void) strlcat(bootpath, ":a", BO_MAXOBJNAME);
} else {
if ((BOP_GETPROP(bootops, "bootpath", bootpath) == -1) ||
strlen(bootpath) == 0) {
if (BOP_GETPROP(bootops,
"boot-path", bootpath) == -1)
return (-1);
}
if (memcmp(bootpath, BP_ISCSI_DISK,
strlen(BP_ISCSI_DISK)) == 0) {
get_iscsi_bootpath_vhci(bootpath);
}
}
return (0);
}
#endif
struct opromioctl_args {
struct oprom_state *st;
int cmd;
intptr_t arg;
int mode;
};
/*ARGSUSED*/
static int
opromioctl_cb(void *avp, int has_changed)
{
struct opromioctl_args *argp = avp;
int cmd;
intptr_t arg;
int mode;
struct oprom_state *st;
struct openpromio *opp;
int valsize;
char *valbuf;
int error = 0;
uint_t userbufsize;
pnode_t node_id;
char propname[OBP_MAXPROPNAME];
st = argp->st;
cmd = argp->cmd;
arg = argp->arg;
mode = argp->mode;
if (has_changed) {
/*
* The prom tree has changed since we last used current_id,
* so we need to check it.
*/
if ((st->current_id != OBP_NONODE) &&
(st->current_id != OBP_BADNODE)) {
if (oprom_checknodeid(st->current_id, OBP_NONODE) == 0)
st->current_id = OBP_BADNODE;
}
}
/*
* Check permissions
* and weed out unsupported commands on x86 platform
*/
switch (cmd) {
#if !defined(__i386) && !defined(__amd64)
case OPROMLISTKEYSLEN:
valsize = prom_asr_list_keys_len();
opp = (struct openpromio *)kmem_zalloc(
sizeof (uint_t) + 1, KM_SLEEP);
opp->oprom_size = valsize;
if (copyout(opp, (void *)arg, (sizeof (uint_t))) != 0)
error = EFAULT;
kmem_free(opp, sizeof (uint_t) + 1);
break;
case OPROMLISTKEYS:
valsize = prom_asr_list_keys_len();
if (copyin((void *)arg, &userbufsize, sizeof (uint_t)) != 0)
return (EFAULT);
if (valsize > userbufsize)
return (EINVAL);
valbuf = (char *)kmem_zalloc(valsize + 1, KM_SLEEP);
if (prom_asr_list_keys((caddr_t)valbuf) == -1) {
kmem_free(valbuf, valsize + 1);
return (EFAULT);
}
opp = (struct openpromio *)kmem_zalloc(
valsize + sizeof (uint_t) + 1, KM_SLEEP);
opp->oprom_size = valsize;
bcopy(valbuf, opp->oprom_array, valsize);
if (copyout(opp, (void *)arg, (valsize + sizeof (uint_t))) != 0)
error = EFAULT;
kmem_free(valbuf, valsize + 1);
kmem_free(opp, valsize + sizeof (uint_t) + 1);
break;
case OPROMEXPORT:
valsize = prom_asr_export_len();
if (copyin((void *)arg, &userbufsize, sizeof (uint_t)) != 0)
return (EFAULT);
if (valsize > userbufsize)
return (EINVAL);
valbuf = (char *)kmem_zalloc(valsize + 1, KM_SLEEP);
if (prom_asr_export((caddr_t)valbuf) == -1) {
kmem_free(valbuf, valsize + 1);
return (EFAULT);
}
opp = (struct openpromio *)kmem_zalloc(
valsize + sizeof (uint_t) + 1, KM_SLEEP);
opp->oprom_size = valsize;
bcopy(valbuf, opp->oprom_array, valsize);
if (copyout(opp, (void *)arg, (valsize + sizeof (uint_t))) != 0)
error = EFAULT;
kmem_free(valbuf, valsize + 1);
kmem_free(opp, valsize + sizeof (uint_t) + 1);
break;
case OPROMEXPORTLEN:
valsize = prom_asr_export_len();
opp = (struct openpromio *)kmem_zalloc(
sizeof (uint_t) + 1, KM_SLEEP);
opp->oprom_size = valsize;
if (copyout(opp, (void *)arg, (sizeof (uint_t))) != 0)
error = EFAULT;
kmem_free(opp, sizeof (uint_t) + 1);
break;
#endif
case OPROMGETOPT:
case OPROMNXTOPT:
if ((mode & FREAD) == 0) {
return (EPERM);
}
node_id = options_nodeid;
break;
case OPROMSETOPT:
case OPROMSETOPT2:
#if !defined(__i386) && !defined(__amd64)
if (mode & FWRITE) {
node_id = options_nodeid;
break;
}
#endif /* !__i386 && !__amd64 */
return (EPERM);
case OPROMNEXT:
case OPROMCHILD:
case OPROMGETPROP:
case OPROMGETPROPLEN:
case OPROMNXTPROP:
case OPROMSETNODEID:
if ((mode & FREAD) == 0) {
return (EPERM);
}
node_id = st->current_id;
break;
case OPROMCOPYOUT:
if (st->snapshot == NULL)
return (EINVAL);
/*FALLTHROUGH*/
case OPROMSNAPSHOT:
case OPROMGETCONS:
case OPROMGETBOOTARGS:
case OPROMGETBOOTPATH:
case OPROMGETVERSION:
case OPROMPATH2DRV:
case OPROMPROM2DEVNAME:
#if !defined(__i386) && !defined(__amd64)
case OPROMGETFBNAME:
case OPROMDEV2PROMNAME:
case OPROMREADY64:
#endif /* !__i386 && !__amd64 */
if ((mode & FREAD) == 0) {
return (EPERM);
}
break;
#if !defined(__i386) && !defined(__amd64)
case WANBOOT_SETKEY:
if (!(mode & FWRITE))
return (EPERM);
break;
#endif /* !__i386 && !defined(__amd64) */
default:
return (EINVAL);
}
/*
* Deal with SNAPSHOT and COPYOUT ioctls first
*/
switch (cmd) {
case OPROMCOPYOUT:
return (oprom_copyout(st, arg));
case OPROMSNAPSHOT:
return (oprom_snapshot(st, arg));
}
/*
* Copy in user argument length and allocation memory
*
* NB do not copyin the entire buffer we may not need
* to. userbufsize can be as big as 32 K.
*/
if (copyin((void *)arg, &userbufsize, sizeof (uint_t)) != 0)
return (EFAULT);
if (userbufsize == 0 || userbufsize > OPROMMAXPARAM)
return (EINVAL);
opp = (struct openpromio *)kmem_zalloc(
userbufsize + sizeof (uint_t) + 1, KM_SLEEP);
/*
* Execute command
*/
switch (cmd) {
case OPROMGETOPT:
case OPROMGETPROP:
case OPROMGETPROPLEN:
if ((prom_is_openprom() == 0) ||
(node_id == OBP_NONODE) || (node_id == OBP_BADNODE)) {
error = EINVAL;
break;
}
/*
* The argument, a NULL terminated string, is a prop name.
*/
if ((error = oprom_copyinstr(arg, opp->oprom_array,
(size_t)userbufsize, OBP_MAXPROPNAME)) != 0) {
break;
}
(void) strcpy(propname, opp->oprom_array);
valsize = prom_getproplen(node_id, propname);
/*
* 4010173: 'name' is a property, but not an option.
*/
if ((cmd == OPROMGETOPT) && (strcmp("name", propname) == 0))
valsize = -1;
if (cmd == OPROMGETPROPLEN) {
int proplen = valsize;
if (userbufsize < sizeof (int)) {
error = EINVAL;
break;
}
opp->oprom_size = valsize = sizeof (int);
bcopy(&proplen, opp->oprom_array, valsize);
} else if (valsize > 0 && valsize <= userbufsize) {
bzero(opp->oprom_array, valsize + 1);
(void) prom_getprop(node_id, propname,
opp->oprom_array);
opp->oprom_size = valsize;
if (valsize < userbufsize)
++valsize; /* Forces NULL termination */
/* If space permits */
} else {
/*
* XXX: There is no error code if the buf is too small.
* which is consistent with the current behavior.
*
* NB: This clause also handles the non-error
* zero length (boolean) property value case.
*/
opp->oprom_size = 0;
(void) strcpy(opp->oprom_array, "");
valsize = 1;
}
if (copyout(opp, (void *)arg, (valsize + sizeof (uint_t))) != 0)
error = EFAULT;
break;
case OPROMNXTOPT:
case OPROMNXTPROP:
if ((prom_is_openprom() == 0) ||
(node_id == OBP_NONODE) || (node_id == OBP_BADNODE)) {
error = EINVAL;
break;
}
/*
* The argument, a NULL terminated string, is a prop name.
*/
if ((error = oprom_copyinstr(arg, opp->oprom_array,
(size_t)userbufsize, OBP_MAXPROPNAME)) != 0) {
break;
}
valbuf = (char *)prom_nextprop(node_id, opp->oprom_array,
propname);
valsize = strlen(valbuf);
/*
* 4010173: 'name' is a property, but it's not an option.
*/
if ((cmd == OPROMNXTOPT) && valsize &&
(strcmp(valbuf, "name") == 0)) {
valbuf = (char *)prom_nextprop(node_id, "name",
propname);
valsize = strlen(valbuf);
}
if (valsize == 0) {
opp->oprom_size = 0;
} else if (++valsize <= userbufsize) {
opp->oprom_size = valsize;
bzero((caddr_t)opp->oprom_array, (size_t)valsize);
bcopy((caddr_t)valbuf, (caddr_t)opp->oprom_array,
(size_t)valsize);
}
if (copyout(opp, (void *)arg, valsize + sizeof (uint_t)) != 0)
error = EFAULT;
break;
case OPROMNEXT:
case OPROMCHILD:
case OPROMSETNODEID:
if (prom_is_openprom() == 0 ||
userbufsize < sizeof (pnode_t)) {
error = EINVAL;
break;
}
/*
* The argument is a phandle. (aka pnode_t)
*/
if (copyin(((caddr_t)arg + sizeof (uint_t)),
opp->oprom_array, sizeof (pnode_t)) != 0) {
error = EFAULT;
break;
}
/*
* If pnode_t from userland is garbage, we
* could confuse the PROM.
*/
node_id = *(pnode_t *)opp->oprom_array;
if (oprom_checknodeid(node_id, st->current_id) == 0) {
cmn_err(CE_NOTE, "!nodeid 0x%x not found",
(int)node_id);
error = EINVAL;
break;
}
if (cmd == OPROMNEXT)
st->current_id = prom_nextnode(node_id);
else if (cmd == OPROMCHILD)
st->current_id = prom_childnode(node_id);
else {
/* OPROMSETNODEID */
st->current_id = node_id;
break;
}
opp->oprom_size = sizeof (pnode_t);
*(pnode_t *)opp->oprom_array = st->current_id;
if (copyout(opp, (void *)arg,
sizeof (pnode_t) + sizeof (uint_t)) != 0)
error = EFAULT;
break;
case OPROMGETCONS:
/*
* Is openboot supported on this machine?
* This ioctl used to return the console device,
* information; this is now done via modctl()
* in libdevinfo.
*/
opp->oprom_size = sizeof (char);
opp->oprom_array[0] |= prom_is_openprom() ?
OPROMCONS_OPENPROM : 0;
/*
* The rest of the info is needed by Install to
* decide if graphics should be started.
*/
if ((getzoneid() == GLOBAL_ZONEID) &&
plat_stdin_is_keyboard()) {
opp->oprom_array[0] |= OPROMCONS_STDIN_IS_KBD;
}
if ((getzoneid() == GLOBAL_ZONEID) &&
plat_stdout_is_framebuffer()) {
opp->oprom_array[0] |= OPROMCONS_STDOUT_IS_FB;
}
if (copyout(opp, (void *)arg,
sizeof (char) + sizeof (uint_t)) != 0)
error = EFAULT;
break;
case OPROMGETBOOTARGS: {
extern char kern_bootargs[];
valsize = strlen(kern_bootargs) + 1;
if (valsize > userbufsize) {
error = EINVAL;
break;
}
(void) strcpy(opp->oprom_array, kern_bootargs);
opp->oprom_size = valsize - 1;
if (copyout(opp, (void *)arg, valsize + sizeof (uint_t)) != 0)
error = EFAULT;
break;
}
case OPROMGETBOOTPATH: {
#if defined(__sparc) && defined(_OBP)
char bpath[OBP_MAXPATHLEN];
if (get_bootpath_prop(bpath) != 0) {
error = EINVAL;
break;
}
valsize = strlen(bpath) + 1;
if (valsize > userbufsize) {
error = EINVAL;
break;
}
(void) strcpy(opp->oprom_array, bpath);
#elif defined(__i386) || defined(__amd64)
extern char saved_cmdline[];
valsize = strlen(saved_cmdline) + 1;
if (valsize > userbufsize) {
error = EINVAL;
break;
}
(void) strcpy(opp->oprom_array, saved_cmdline);
#endif
opp->oprom_size = valsize - 1;
if (copyout(opp, (void *)arg, valsize + sizeof (uint_t)) != 0)
error = EFAULT;
break;
}
/*
* convert a prom device path to an equivalent devfs path
*/
case OPROMPROM2DEVNAME: {
char *dev_name;
/*
* The input argument, a pathname, is a NULL terminated string.
*/
if ((error = oprom_copyinstr(arg, opp->oprom_array,
(size_t)userbufsize, MAXPATHLEN)) != 0) {
break;
}
dev_name = kmem_alloc(MAXPATHLEN, KM_SLEEP);
error = i_promname_to_devname(opp->oprom_array, dev_name);
if (error != 0) {
kmem_free(dev_name, MAXPATHLEN);
break;
}
valsize = opp->oprom_size = strlen(dev_name);
if (++valsize > userbufsize) {
kmem_free(dev_name, MAXPATHLEN);
error = EINVAL;
break;
}
(void) strcpy(opp->oprom_array, dev_name);
if (copyout(opp, (void *)arg, sizeof (uint_t) + valsize) != 0)
error = EFAULT;
kmem_free(dev_name, MAXPATHLEN);
break;
}
/*
* Convert a prom device path name to a driver name
*/
case OPROMPATH2DRV: {
char *drv_name;
major_t maj;
/*
* The input argument, a pathname, is a NULL terminated string.
*/
if ((error = oprom_copyinstr(arg, opp->oprom_array,
(size_t)userbufsize, MAXPATHLEN)) != 0) {
break;
}
/*
* convert path to a driver binding name
*/
maj = path_to_major((char *)opp->oprom_array);
if (maj == DDI_MAJOR_T_NONE) {
error = EINVAL;
break;
}
/*
* resolve any aliases
*/
if ((drv_name = ddi_major_to_name(maj)) == NULL) {
error = EINVAL;
break;
}
(void) strcpy(opp->oprom_array, drv_name);
opp->oprom_size = strlen(drv_name);
if (copyout(opp, (void *)arg,
sizeof (uint_t) + opp->oprom_size + 1) != 0)
error = EFAULT;
break;
}
case OPROMGETVERSION:
/*
* Get a string representing the running version of the
* prom. How to create such a string is platform dependent,
* so we just defer to a promif function. If no such
* association exists, the promif implementation
* may copy the string "unknown" into the given buffer,
* and return its length (incl. NULL terminator).
*
* We expect prom_version_name to return the actual
* length of the string, but copy at most userbufsize
* bytes into the given buffer, including NULL termination.
*/
valsize = prom_version_name(opp->oprom_array, userbufsize);
if (valsize < 0) {
error = EINVAL;
break;
}
/*
* copyout only the part of the user buffer we need to.
*/
if (copyout(opp, (void *)arg,
(size_t)(min((uint_t)valsize, userbufsize) +
sizeof (uint_t))) != 0)
error = EFAULT;
break;
#if !defined(__i386) && !defined(__amd64)
case OPROMGETFBNAME:
/*
* Return stdoutpath, if it's a frame buffer.
* Yes, we are comparing a possibly longer string against
* the size we're really going to copy, but so what?
*/
if ((getzoneid() == GLOBAL_ZONEID) &&
(prom_stdout_is_framebuffer() != 0) &&
(userbufsize > strlen(prom_stdoutpath()))) {
prom_strip_options(prom_stdoutpath(),
opp->oprom_array); /* strip options and copy */
valsize = opp->oprom_size = strlen(opp->oprom_array);
if (copyout(opp, (void *)arg,
valsize + 1 + sizeof (uint_t)) != 0)
error = EFAULT;
} else
error = EINVAL;
break;
/*
* Convert a logical or physical device path to prom device path
*/
case OPROMDEV2PROMNAME: {
char *prom_name;
/*
* The input argument, a pathname, is a NULL terminated string.
*/
if ((error = oprom_copyinstr(arg, opp->oprom_array,
(size_t)userbufsize, MAXPATHLEN)) != 0) {
break;
}
prom_name = kmem_alloc(userbufsize, KM_SLEEP);
/*
* convert the devfs path to an equivalent prom path
*/
error = i_devname_to_promname(opp->oprom_array, prom_name,
userbufsize);
if (error != 0) {
kmem_free(prom_name, userbufsize);
break;
}
for (valsize = 0; valsize < userbufsize; valsize++) {
opp->oprom_array[valsize] = prom_name[valsize];
if ((valsize > 0) && (prom_name[valsize] == '\0') &&
(prom_name[valsize-1] == '\0')) {
break;
}
}
opp->oprom_size = valsize;
kmem_free(prom_name, userbufsize);
if (copyout(opp, (void *)arg, sizeof (uint_t) + valsize) != 0)
error = EFAULT;
break;
}
case OPROMSETOPT:
case OPROMSETOPT2: {
int namebuflen;
int valbuflen;
if ((prom_is_openprom() == 0) ||
(node_id == OBP_NONODE) || (node_id == OBP_BADNODE)) {
error = EINVAL;
break;
}
/*
* The arguments are a property name and a value.
* Copy in the entire user buffer.
*/
if (copyin(((caddr_t)arg + sizeof (uint_t)),
opp->oprom_array, userbufsize) != 0) {
error = EFAULT;
break;
}
/*
* The property name is the first string, value second
*/
namebuflen = strlen(opp->oprom_array);
valbuf = opp->oprom_array + namebuflen + 1;
valbuflen = strlen(valbuf);
if (cmd == OPROMSETOPT) {
valsize = valbuflen + 1; /* +1 for the '\0' */
} else {
if ((namebuflen + 1 + valbuflen + 1) > userbufsize) {
error = EINVAL;
break;
}
valsize = (opp->oprom_array + userbufsize) - valbuf;
}
/*
* 4010173: 'name' is not an option, but it is a property.
*/
if (strcmp(opp->oprom_array, "name") == 0)
error = EINVAL;
else if (prom_setprop(node_id, opp->oprom_array,
valbuf, valsize) < 0)
error = EINVAL;
break;
}
case OPROMREADY64: {
struct openprom_opr64 *opr =
(struct openprom_opr64 *)opp->oprom_array;
int i;
pnode_t id;
if (userbufsize < sizeof (*opr)) {
error = EINVAL;
break;
}
valsize = userbufsize -
offsetof(struct openprom_opr64, message);
i = prom_version_check(opr->message, valsize, &id);
opr->return_code = i;
opr->nodeid = (int)id;
valsize = offsetof(struct openprom_opr64, message);
valsize += strlen(opr->message) + 1;
/*
* copyout only the part of the user buffer we need to.
*/
if (copyout(opp, (void *)arg,
(size_t)(min((uint_t)valsize, userbufsize) +
sizeof (uint_t))) != 0)
error = EFAULT;
break;
} /* case OPROMREADY64 */
case WANBOOT_SETKEY: {
struct wankeyio *wp;
int reslen;
int status;
int rv;
int i;
/*
* The argument is a struct wankeyio. Validate it as best
* we can.
*/
if (userbufsize != (sizeof (struct wankeyio))) {
error = EINVAL;
break;
}
if (copyin(((caddr_t)arg + sizeof (uint_t)),
opp->oprom_array, sizeof (struct wankeyio)) != 0) {
error = EFAULT;
break;
}
wp = (struct wankeyio *)opp->oprom_array;
/* check for key name and key size overflow */
for (i = 0; i < WANBOOT_MAXKEYNAMELEN; i++)
if (wp->wk_keyname[i] == '\0')
break;
if ((i == WANBOOT_MAXKEYNAMELEN) ||
(wp->wk_keysize > WANBOOT_MAXKEYLEN)) {
error = EINVAL;
break;
}
rv = prom_set_security_key(wp->wk_keyname, wp->wk_u.key,
wp->wk_keysize, &reslen, &status);
if (rv)
error = EIO;
else
switch (status) {
case 0:
error = 0;
break;
case -2: /* out of key storage space */
error = ENOSPC;
break;
case -3: /* key name or value too long */
error = EINVAL;
break;
case -4: /* can't delete: no such key */
error = ENOENT;
break;
case -1: /* unspecified error */
default: /* this should not happen */
error = EIO;
break;
}
break;
} /* case WANBOOT_SETKEY */
#endif /* !__i386 && !__amd64 */
} /* switch (cmd) */
kmem_free(opp, userbufsize + sizeof (uint_t) + 1);
return (error);
}
/*ARGSUSED*/
static int
opromioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp)
{
struct oprom_state *st;
struct opromioctl_args arg_block;
if (getminor(dev) >= MAX_OPENS)
return (ENXIO);
st = &oprom_state[getminor(dev)];
ASSERT(st->already_open);
arg_block.st = st;
arg_block.cmd = cmd;
arg_block.arg = arg;
arg_block.mode = mode;
return (prom_tree_access(opromioctl_cb, &arg_block, &st->tree_gen));
}
/*
* Copyin string and verify the actual string length is less than maxsize
* specified by the caller.
*
* Currently, maxsize is either OBP_MAXPROPNAME for property names
* or MAXPATHLEN for device path names. userbufsize is specified
* by the userland caller.
*/
static int
oprom_copyinstr(intptr_t arg, char *buf, size_t bufsize, size_t maxsize)
{
int error;
size_t actual_len;
if ((error = copyinstr(((caddr_t)arg + sizeof (uint_t)),
buf, bufsize, &actual_len)) != 0) {
return (error);
}
if ((actual_len == 0) || (actual_len > maxsize)) {
return (EINVAL);
}
return (0);
}
/*
* Check pnode_t passed in from userland
*/
static int
oprom_checknodeid(pnode_t node_id, pnode_t current_id)
{
int depth;
pnode_t id[OBP_STACKDEPTH];
/*
* optimized path
*/
if (node_id == 0) {
return (1);
}
if (node_id == OBP_BADNODE) {
return (0);
}
if ((current_id != OBP_BADNODE) && ((node_id == current_id) ||
(node_id == prom_nextnode(current_id)) ||
(node_id == prom_childnode(current_id)))) {
return (1);
}
/*
* long path: walk from root till we find node_id
*/
depth = 1;
id[0] = prom_nextnode((pnode_t)0);
while (depth) {
if (id[depth - 1] == node_id)
return (1); /* node_id found */
if (id[depth] = prom_childnode(id[depth - 1])) {
depth++;
continue;
}
while (depth &&
((id[depth - 1] = prom_nextnode(id[depth - 1])) == 0))
depth--;
}
return (0); /* node_id not found */
}
static int
oprom_copytree(struct oprom_state *st, uint_t flag)
{
ASSERT(st->snapshot == NULL && st->size == 0);
return (oprom_copynode(
prom_nextnode(0), flag, &st->snapshot, &st->size));
}
static int
oprom_snapshot(struct oprom_state *st, intptr_t arg)
{
uint_t flag;
if (oprom_setstate(st, IOC_SNAP) == -1)
return (EBUSY);
/* copyin flag and create snapshot */
if ((copyin((void *)arg, &flag, sizeof (uint_t)) != 0) ||
(oprom_copytree(st, flag) != 0)) {
(void) oprom_setstate(st, IOC_IDLE);
return (EFAULT);
}
/* copyout the size of the snapshot */
flag = (uint_t)st->size;
if (copyout(&flag, (void *)arg, sizeof (uint_t)) != 0) {
kmem_free(st->snapshot, st->size);
st->snapshot = NULL;
st->size = 0;
(void) oprom_setstate(st, IOC_IDLE);
return (EFAULT);
}
(void) oprom_setstate(st, IOC_DONE);
return (0);
}
static int
oprom_copyout(struct oprom_state *st, intptr_t arg)
{
int error = 0;
uint_t size;
if (oprom_setstate(st, IOC_COPY) == -1)
return (EBUSY);
/* copyin size and copyout snapshot */
if (copyin((void *)arg, &size, sizeof (uint_t)) != 0)
error = EFAULT;
else if (size < st->size)
error = EINVAL;
else if (copyout(st->snapshot, (void *)arg, st->size) != 0)
error = EFAULT;
if (error) {
/*
* on error keep the snapshot until a successful
* copyout or when the driver is closed.
*/
(void) oprom_setstate(st, IOC_DONE);
return (error);
}
kmem_free(st->snapshot, st->size);
st->snapshot = NULL;
st->size = 0;
(void) oprom_setstate(st, IOC_IDLE);
return (0);
}
/*
* Copy all properties of nodeid into a single packed nvlist
*/
static int
oprom_copyprop(pnode_t nodeid, uint_t flag, nvlist_t *nvl)
{
int proplen;
char *propname, *propval, *buf1, *buf2;
ASSERT(nvl != NULL);
/*
* non verbose mode, get the "name" property only
*/
if (flag == 0) {
proplen = prom_getproplen(nodeid, "name");
if (proplen <= 0) {
cmn_err(CE_WARN,
"failed to get the name of openprom node 0x%x",
nodeid);
(void) nvlist_add_string(nvl, "name", "");
return (0);
}
propval = kmem_zalloc(proplen + 1, KM_SLEEP);
(void) prom_getprop(nodeid, "name", propval);
(void) nvlist_add_string(nvl, "name", propval);
kmem_free(propval, proplen + 1);
return (0);
}
/*
* Ask for first property by passing a NULL string
*/
buf1 = kmem_alloc(OBP_MAXPROPNAME, KM_SLEEP);
buf2 = kmem_zalloc(OBP_MAXPROPNAME, KM_SLEEP);
buf1[0] = '\0';
while (propname = (char *)prom_nextprop(nodeid, buf1, buf2)) {
if (strlen(propname) == 0)
break; /* end of prop list */
(void) strcpy(buf1, propname);
proplen = prom_getproplen(nodeid, propname);
if (proplen == 0) {
/* boolean property */
(void) nvlist_add_boolean(nvl, propname);
continue;
}
/* add 1 for null termination in case of a string */
propval = kmem_zalloc(proplen + 1, KM_SLEEP);
(void) prom_getprop(nodeid, propname, propval);
(void) nvlist_add_byte_array(nvl, propname,
(uchar_t *)propval, proplen + 1);
kmem_free(propval, proplen + 1);
bzero(buf2, OBP_MAXPROPNAME);
}
kmem_free(buf1, OBP_MAXPROPNAME);
kmem_free(buf2, OBP_MAXPROPNAME);
return (0);
}
/*
* Copy all children and descendents into a a packed nvlist
*/
static int
oprom_copychild(pnode_t nodeid, uint_t flag, char **buf, size_t *size)
{
nvlist_t *nvl;
pnode_t child = prom_childnode(nodeid);
if (child == 0)
return (0);
(void) nvlist_alloc(&nvl, 0, KM_SLEEP);
while (child != 0) {
char *nodebuf = NULL;
size_t nodesize = 0;
if (oprom_copynode(child, flag, &nodebuf, &nodesize)) {
nvlist_free(nvl);
cmn_err(CE_WARN, "failed to copy nodeid 0x%x", child);
return (-1);
}
(void) nvlist_add_byte_array(nvl, "node",
(uchar_t *)nodebuf, nodesize);
kmem_free(nodebuf, nodesize);
child = prom_nextnode(child);
}
(void) nvlist_pack(nvl, buf, size, NV_ENCODE_NATIVE, KM_SLEEP);
nvlist_free(nvl);
return (0);
}
/*
* Copy a node into a packed nvlist
*/
static int
oprom_copynode(pnode_t nodeid, uint_t flag, char **buf, size_t *size)
{
int error = 0;
nvlist_t *nvl;
char *childlist = NULL;
size_t childsize = 0;
(void) nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP);
ASSERT(nvl != NULL);
/* @nodeid -- @ is not a legal char in a 1275 property name */
(void) nvlist_add_int32(nvl, "@nodeid", (int32_t)nodeid);
/* properties */
if (error = oprom_copyprop(nodeid, flag, nvl))
goto fail;
/* children */
error = oprom_copychild(nodeid, flag, &childlist, &childsize);
if (error != 0)
goto fail;
if (childlist != NULL) {
(void) nvlist_add_byte_array(nvl, "@child",
(uchar_t *)childlist, (uint_t)childsize);
kmem_free(childlist, childsize);
}
/* pack into contiguous buffer */
error = nvlist_pack(nvl, buf, size, NV_ENCODE_NATIVE, KM_SLEEP);
fail:
nvlist_free(nvl);
return (error);
}
/*
* The driver is stateful across OPROMSNAPSHOT and OPROMCOPYOUT.
* This function encapsulates the state machine:
*
* -> IOC_IDLE -> IOC_SNAP -> IOC_DONE -> IOC_COPY ->
* | SNAPSHOT COPYOUT |
* --------------------------------------------------
*
* Returns 0 on success and -1 on failure
*/
static int
oprom_setstate(struct oprom_state *st, int16_t new_state)
{
int ret = 0;
mutex_enter(&oprom_lock);
switch (new_state) {
case IOC_IDLE:
case IOC_DONE:
break;
case IOC_SNAP:
if (st->ioc_state != IOC_IDLE)
ret = -1;
break;
case IOC_COPY:
if (st->ioc_state != IOC_DONE)
ret = -1;
break;
default:
ret = -1;
}
if (ret == 0)
st->ioc_state = new_state;
else
cmn_err(CE_NOTE, "incorrect state transition from %d to %d",
st->ioc_state, new_state);
mutex_exit(&oprom_lock);
return (ret);
}