OpenSolaris_b135/lib/libdevice/devctl.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/nvpair.h>
#include "libdevice.h"

static int _libdevice_debug = 0;
static const char *devctl_minorname = ":devctl";
static const char *nullptr = "<null>";
static const char *devctl_target_raw = "a,raw";

typedef enum { DEVCTL_BUS, DEVCTL_DEVICE, DEVCTL_AP, DEVCTL_CLONE,
    DEVCTL_PM_DEV, DEVCTL_PM_BUS } dc_type_t;

/*
 * devctl_hdl structures are allocated by the devctl_XX_acquire()
 * interfaces and passed to the remaining interfaces in this library.
 */
struct devctl_hdl {
	char		*opath;		/* copy of the original path */
	dc_type_t	hdltype;	/* handle type */
	int		fd;		/* nexus device node */
	char		*nodename;	/* DEVCTL_DEVICE handles only */
	char		*unitaddr;	/* DEVCTL_DEVICE handles only */
};
#define	DCP(x)	((struct devctl_hdl *)(x))

static int dc_cmd(uint_t, uint_t, struct devctl_hdl *, nvlist_t *, void *);
static devctl_hdl_t dc_mkhndl(dc_type_t, char *, uint_t, devctl_hdl_t);


#pragma init(_libdevice_init)
void
_libdevice_init()
{
	_libdevice_debug = getenv("LIBDEVICE_DEBUG") != NULL;
}

/*
 * release a devctl_hdl structure
 */
void
devctl_release(devctl_hdl_t hdl)
{
	if (_libdevice_debug)
		(void) printf("devctl_release: %p\n", (void *)hdl);

	if (hdl == NULL)
		return;

	if (DCP(hdl)->fd != -1)
		(void) close(DCP(hdl)->fd);

	if (DCP(hdl)->opath != NULL)
		free(DCP(hdl)->opath);

	if (DCP(hdl)->nodename != NULL)
		free(DCP(hdl)->nodename);

	if (DCP(hdl)->unitaddr != NULL)
		free(DCP(hdl)->unitaddr);

	free(hdl);
}

/*
 * construct a handle suitable for devctl_bus_*() operations
 */
devctl_hdl_t
devctl_bus_acquire(char *devfs_path, uint_t flags)
{
	uint_t oflags;

	if (_libdevice_debug)
		(void) printf("devctl_bus_acquire: %s (%d)\n",
			((devfs_path != NULL) ? devfs_path : nullptr), flags);

	if ((devfs_path == NULL) || ((flags != 0) && (flags != DC_EXCL))) {
		errno = EINVAL;
		return (NULL);
	}

	oflags = ((flags & DC_EXCL) != 0) ? O_EXCL|O_RDWR : O_RDWR;
	return (dc_mkhndl(DEVCTL_BUS, devfs_path, oflags, NULL));
}


/*
 * construct a handle suitable for devctl_bus_*() and
 * devctl_device_*() operations.
 */
devctl_hdl_t
devctl_device_acquire(char *devfs_path, uint_t flags)
{
	uint_t oflags;

	if (_libdevice_debug)
		(void) printf("devctl_device_acquire: %s (%d)\n",
		    ((devfs_path != NULL) ? devfs_path : nullptr), flags);

	if ((devfs_path == NULL) || ((flags != 0) && (flags != DC_EXCL))) {
		errno = EINVAL;
		return (NULL);
	}

	oflags = ((flags & DC_EXCL) != 0) ? O_EXCL|O_RDWR : O_RDWR;
	return (dc_mkhndl(DEVCTL_DEVICE, devfs_path, oflags, NULL));
}


/*
 * given a devfs (/devices) pathname to an attachment point device,
 * access the device and return a handle to be passed to the
 * devctl_ap_XXX() functions.
 */
devctl_hdl_t
devctl_ap_acquire(char *devfs_path, uint_t flags)
{
	uint_t oflags;

	if (_libdevice_debug)
		(void) printf("devctl_ap_acquire: %s (%d)\n",
		    ((devfs_path != NULL) ? devfs_path : nullptr), flags);

	if ((devfs_path == NULL) ||
	    ((flags != 0) && ((flags & DC_EXCL) != 0) &&
	    ((flags & DC_RDONLY) != 0))) {
		errno = EINVAL;
		return (NULL);
	}

	oflags = ((flags & DC_EXCL) != 0) ? O_EXCL : 0;
	oflags |= ((flags & DC_RDONLY) != 0) ? O_RDONLY : O_RDWR;

	return (dc_mkhndl(DEVCTL_AP, devfs_path, oflags, NULL));
}


/*
 * given a devfs (/devices) pathname access the device and return
 * a handle to be passed to the devctl_pm_XXX() functions.
 * The minor name ":devctl" is appended.
 */
devctl_hdl_t
devctl_pm_bus_acquire(char *devfs_path, uint_t flags)
{
	uint_t oflags;

	if (_libdevice_debug)
		(void) printf("devctl_pm_bus_acquire: %s (%d)\n",
		    ((devfs_path != NULL) ? devfs_path : nullptr), flags);

	if ((devfs_path == NULL) || ((flags != 0) && (flags != DC_EXCL))) {
		errno = EINVAL;
		return (NULL);
	}

	oflags = ((flags & DC_EXCL) != 0) ? (O_EXCL | O_RDWR) : O_RDWR;
	return (dc_mkhndl(DEVCTL_PM_BUS, devfs_path, oflags, NULL));
}


/*
 * given a devfs (/devices) pathname access the device and return
 * a handle to be passed to the devctl_pm_XXX() functions.
 * The minor name is derived from the device name.
 */
devctl_hdl_t
devctl_pm_dev_acquire(char *devfs_path, uint_t flags)
{
	uint_t oflags;

	if (_libdevice_debug)
		(void) printf("devctl_pm_dev_acquire: %s (%d)\n",
		    ((devfs_path != NULL) ? devfs_path : nullptr), flags);

	if ((devfs_path == NULL) || ((flags != 0) && (flags != DC_EXCL))) {
		errno = EINVAL;
		return (NULL);
	}

	oflags = ((flags & DC_EXCL) != 0) ? (O_EXCL | O_RDWR) : O_RDWR;
	return (dc_mkhndl(DEVCTL_PM_DEV, devfs_path, oflags, NULL));
}


/*
 * allocate and initalize the devctl_hdl structure for the
 * particular handle type.
 */
static devctl_hdl_t
dc_mkhndl(dc_type_t type, char *path, uint_t oflags, devctl_hdl_t pc)
{
	struct devctl_hdl *dcp;
	struct stat sb;
	char iocpath[MAXPATHLEN];
	char *nodename, *unitsep, *minorsep, *chop;
	char *minorname;
	size_t strlcpy_size;
	char *iocpath_dup;
	char *tok;

	if ((path == NULL) || (strlen(path) > MAXPATHLEN - 1)) {
		errno = EINVAL;
		return (NULL);
	}

	/*
	 * allocate handle and make a copy of the original path
	 */
	if ((dcp = calloc(1, sizeof (*dcp))) == NULL) {
		errno = ENOMEM;
		return (NULL);
	}
	if ((dcp->opath = strdup(path)) == NULL) {
		devctl_release((devctl_hdl_t)dcp);
		errno = ENOMEM;
		return (NULL);
	}

	(void) strcpy(iocpath, path);
	dcp->hdltype = type;
	dcp->fd = -1;

	/*
	 * break apart the pathname according to the type handle
	 */
	switch (type) {
	case DEVCTL_PM_BUS:
		/*
		 * chop off any minor name and concatenate the
		 * ":devctl" minor node name string.
		 */
		if ((chop = strrchr(iocpath, ':')) != NULL)
			*chop = '\0';

		if (strlcat(iocpath, devctl_minorname, MAXPATHLEN) >=
		    MAXPATHLEN) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		} else if (_libdevice_debug) {
			(void) printf("DEVCTL_PM_BUS: iocpath %s\n", iocpath);
		}
		break;

	case DEVCTL_PM_DEV:
		/*
		 * Chop up the last device component in the pathname.
		 * Concatenate either the device name itself, or the
		 * "a,raw" string, as the minor node name, to the iocpath.
		 */
		if ((iocpath_dup = strdup(iocpath)) == NULL) {
			devctl_release((devctl_hdl_t)dcp);
			errno = ENOMEM;
			return (NULL);
		}
		if ((chop = strrchr(iocpath_dup, '/')) == NULL) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		}
		*chop = '\0';
		nodename = chop + 1;

		/*
		 * remove the "@0,0" string
		 */
		tok = strtok(nodename, "@");
		if ((minorname = malloc(strlen(tok) +1)) == NULL) {
			if (_libdevice_debug)
				(void) printf("DEVCTL_PM_DEV: failed malloc for"
				    " minorname\n");
			devctl_release((devctl_hdl_t)dcp);
			errno = ENOMEM;
			return (NULL);
		}
		(void) strcpy(minorname, tok);
		if (_libdevice_debug) {
			(void) printf("DEVCTL_PM_DEV: minorname %s\n",
			    minorname);
		}

		/*
		 * construct the name of the ioctl device
		 * by concatenating either ":a,raw" or ":"minorname
		 */
		(void) strlcat(iocpath, ":", MAXPATHLEN);
		if (strcmp(minorname, "disk_chan") == 0 ||
		    strcmp(minorname, "disk_wwn") == 0 ||
		    strcmp(minorname, "disk_cdrom") == 0) {
			strlcpy_size = strlcat(iocpath, devctl_target_raw,
			    MAXPATHLEN);
		} else {
			strlcpy_size = strlcat(iocpath, minorname, MAXPATHLEN);
		}
		if (strlcpy_size >= MAXPATHLEN) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		} else if (_libdevice_debug) {
			(void) printf("DEVCTL_PM_DEV: iocpath %s\n",
			    iocpath);
		}
		break;

	case DEVCTL_AP:
		/*
		 * take the pathname as provided.
		 */
		break;

	case DEVCTL_BUS:
		/*
		 * chop off any minor name and concatenate the
		 * ":devctl" minor node name string.
		 */
		if ((chop = strrchr(iocpath, ':')) != NULL)
			*chop = '\0';

		if (strlcat(iocpath, devctl_minorname, MAXPATHLEN) >=
		    MAXPATHLEN) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		}
		break;

	case DEVCTL_CLONE:
		/*
		 * create a device handle for a new device created
		 * from a call to devctl_bus_dev_create()
		 */
		dcp->hdltype = DEVCTL_DEVICE;

		/* FALLTHRU */

	case DEVCTL_DEVICE:

		/*
		 * Chop up the last device component in the pathname.
		 * The componets are passed as nodename and unitaddr
		 * in the IOCTL data for DEVCTL ops on devices.
		 */
		if ((chop = strrchr(iocpath, '/')) == NULL) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		}
		*chop = '\0';

		nodename = chop + 1;
		unitsep = strchr(nodename, '@');
		minorsep = strchr(nodename, ':');

		if (unitsep == NULL) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		}

		/*
		 * copy the nodename and unit address
		 */
		if (((dcp->nodename = malloc(MAXNAMELEN)) == NULL) ||
		    ((dcp->unitaddr = malloc(MAXNAMELEN)) == NULL)) {
			devctl_release((devctl_hdl_t)dcp);
			errno = ENOMEM;
			return (NULL);
		}
		*unitsep = '\0';
		if (minorsep != NULL)
			*minorsep = '\0';
		(void) snprintf(dcp->nodename, MAXNAMELEN, "%s", nodename);
		(void) snprintf(dcp->unitaddr, MAXNAMELEN, "%s", unitsep+1);

		/*
		 * construct the name of the ioctl device
		 */
		if (strlcat(iocpath, devctl_minorname, MAXPATHLEN) >=
		    MAXPATHLEN) {
			devctl_release((devctl_hdl_t)dcp);
			errno = EINVAL;
			return (NULL);
		}
		break;

	default:
		devctl_release((devctl_hdl_t)dcp);
		errno = EINVAL;
		return (NULL);
	}

	if (_libdevice_debug)
		(void) printf("dc_mkhndl: iocpath %s ", iocpath);

	/*
	 * verify the devctl or ap device exists and is a
	 * character device interface.
	 */
	if (stat(iocpath, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) != S_IFCHR) {
			if (_libdevice_debug)
				(void) printf(" - not character device\n");
			errno = ENODEV;
			devctl_release((devctl_hdl_t)dcp);
			return (NULL);
		}
	} else {
		/*
		 * return failure with errno value set by stat
		 */
		if (_libdevice_debug)
			(void) printf(" - stat failed\n");
		devctl_release((devctl_hdl_t)dcp);
		return (NULL);
	}

	/*
	 * if this was a new device, dup the parents handle, otherwise
	 * just open the device.
	 */
	if (type == DEVCTL_CLONE)
		dcp->fd = dup(DCP(pc)->fd);
	else
		dcp->fd = open(iocpath, oflags);

	if (dcp->fd == -1) {
		if (_libdevice_debug)
			(void) printf(" - open/dup failed %d\n", errno);
		/*
		 * leave errno as set by open/dup
		 */
		devctl_release((devctl_hdl_t)dcp);
		return (NULL);
	}

	if (_libdevice_debug)
		(void) printf(" - open success\n");

	return ((devctl_hdl_t)dcp);
}

/*
 * Power up component 0, to level MAXPWR, via a pm_raise_power() call
 */
int
devctl_pm_raisepower(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_RAISE_PWR, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_raisepower: %d\n", rv);

	return (rv);
}

/*
 * Power up component 0, to level MAXPWR, via a power_has_changed() call
 */
int
devctl_pm_changepowerhigh(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_CHANGE_PWR_HIGH, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_changepowerhigh: %d\n", rv);

	return (rv);
}

/*
 * Power down component 0, to level 0, via a pm_change_power() call
 */
int
devctl_pm_changepowerlow(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_CHANGE_PWR_LOW, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_changepowerlow: %d\n", rv);

	return (rv);
}

/*
 * mark component 0 idle
 */
int
devctl_pm_idlecomponent(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_IDLE_COMP, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_idlecomponent: %d\n", rv);

	return (rv);
}

/*
 * mark component 0 busy
 */
int
devctl_pm_busycomponent(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_BUSY_COMP, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_busycomponent: %d\n", rv);

	return (rv);
}

/*
 * test pm busy state
 */
int
devctl_pm_testbusy(devctl_hdl_t dcp, uint_t *busystate)
{
	int  rv;
	uint_t	busy_state = 0;

	if (busystate == NULL) {
		errno = EINVAL;
		return (-1);
	}

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_BUSY_COMP_TEST, 0, DCP(dcp), NULL,
	    (void *)&busy_state);

	if (rv == -1)
		*busystate = 0;
	else
		*busystate = busy_state;

	if (_libdevice_debug)
		(void) printf("devctl_pm_bus_testbusy: rv %d busystate %x\n",
		    rv, *busystate);

	return (rv);
}

/*
 * set flag to fail DDI_SUSPEND
 */
int
devctl_pm_failsuspend(devctl_hdl_t dcp)
{
	int rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_FAIL_SUSPEND, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_failsuspend: %d\n", rv);
	return (rv);
}

int
devctl_pm_bus_teststrict(devctl_hdl_t dcp, uint_t *strict)
{
	int  rv;
	uint_t	strict_state;

	if (strict == NULL) {
		errno = EINVAL;
		return (-1);
	}

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_BUS_STRICT_TEST, 0, DCP(dcp), NULL,
	    (void *)&strict_state);

	if (rv == -1)
		*strict = 0;
	else
		*strict = strict_state;

	if (_libdevice_debug)
		(void) printf("devctl_pm_bus_teststrict: rv %d strict %x\n",
		    rv, *strict);

	return (rv);
}

/*
 * issue prom_printf() call
 */
int
devctl_pm_device_promprintf(devctl_hdl_t dcp)
{
	int rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_PROM_PRINTF, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_device_promprintf: %d\n", rv);
	return (rv);
}

/*
 * set flag to power up the device via
 * pm_power_has_changed() calls vs.
 * pm_raise_power(), during DDI_RESUME
 */
int
devctl_pm_device_changeonresume(devctl_hdl_t dcp)
{
	int rv;

	if (dcp == NULL || (DCP(dcp)->hdltype != DEVCTL_PM_DEV &&
	    DCP(dcp)->hdltype != DEVCTL_PM_BUS)) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_PWR_HAS_CHANGED_ON_RESUME, 0,
	    DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_device_changeonresume: %d\n", rv);
	return (rv);
}

/*
 * issue DEVCTL_PM_NO_LOWER_POWER to clear the LOWER_POWER_FLAG
 * flag: pm_lower_power() will not be called on device detach
 */
int
devctl_pm_device_no_lower_power(devctl_hdl_t dcp)
{
	int rv;

	if (dcp == NULL || DCP(dcp)->hdltype != DEVCTL_PM_DEV) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_NO_LOWER_POWER, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_device_no_lower_power: %d\n", rv);
	return (rv);
}

/*
 * issue DEVCTL_PM_BUS_NO_INVOL ioctl to set the NO_INVOL_FLAG
 * flag: parent driver will mark itself idle twice in
 * DDI_CTLOPS_DETACH(POST)
 */
int
devctl_pm_bus_no_invol(devctl_hdl_t dcp)
{
	int rv;

	if (dcp == NULL || DCP(dcp)->hdltype != DEVCTL_PM_BUS) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_PM_BUS_NO_INVOL, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_pm_bus_no_invol: %d\n", rv);
	return (rv);
}

/*
 * Place the device ONLINE
 */
int
devctl_device_online(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || DCP(dcp)->hdltype != DEVCTL_DEVICE) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_DEVICE_ONLINE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_device_online: %d\n", rv);

	return (rv);
}

/*
 * take device OFFLINE
 */
int
devctl_device_offline(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || DCP(dcp)->hdltype != DEVCTL_DEVICE) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_DEVICE_OFFLINE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_device_offline: %d\n", rv);

	return (rv);
}

/*
 * take the device OFFLINE and remove its dev_info node
 */
int
devctl_device_remove(devctl_hdl_t dcp)
{
	int  rv;

	if (dcp == NULL || DCP(dcp)->hdltype != DEVCTL_DEVICE) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_DEVICE_REMOVE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_device_remove: %d\n", rv);

	return (rv);
}


/*
 * QUIESCE the bus
 */
int
devctl_bus_quiesce(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_QUIESCE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_quiesce: %d\n", rv);

	return (rv);
}

int
devctl_bus_unquiesce(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_UNQUIESCE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_unquiesce: %d\n", rv);

	return (rv);
}

int
devctl_bus_reset(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_RESET, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_reset: %d\n", rv);

	return (rv);
}

int
devctl_bus_resetall(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_RESETALL, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_resetall: %d\n", rv);

	return (rv);
}

int
devctl_device_reset(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_DEVICE_RESET, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_device_reset: %d\n", rv);

	return (rv);
}

int
devctl_device_getstate(devctl_hdl_t dcp, uint_t *devstate)
{
	int  rv;
	uint_t device_state;

	if (devstate == NULL) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_DEVICE_GETSTATE, 0, DCP(dcp), NULL,
	    (void *)&device_state);

	if (rv == -1)
		*devstate = 0;
	else
		*devstate = device_state;

	if (_libdevice_debug)
		(void) printf("devctl_device_getstate: rv %d state %x\n",
		    rv, *devstate);

	return (rv);
}

int
devctl_bus_getstate(devctl_hdl_t dcp, uint_t *devstate)
{
	int  rv;
	uint_t device_state;

	if (devstate == NULL) {
		errno = EINVAL;
		return (-1);
	}

	rv = dc_cmd(DEVCTL_BUS_GETSTATE, 0, DCP(dcp), NULL,
	    (void *)&device_state);

	if (rv == -1)
		*devstate = 0;
	else
		*devstate = device_state;

	if (_libdevice_debug)
		(void) printf("devctl_bus_getstate: rv %d, state %x\n",
		    rv, *devstate);

	return (rv);
}

int
devctl_bus_configure(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_CONFIGURE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_configure: %d\n", rv);

	return (rv);
}

int
devctl_bus_unconfigure(devctl_hdl_t dcp)
{
	int  rv;

	rv = dc_cmd(DEVCTL_BUS_UNCONFIGURE, 0, DCP(dcp), NULL, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_bus_unconfigure: %d\n", rv);

	return (rv);
}

/*
 * devctl_bus_dev_create() - create a new child device
 * Attempt to construct and attach a new child device below a
 * bus nexus (dcp).  The device is defined using the devctl_ddef_*()
 * routines to specify the set of bus-specific properties required
 * to initalize and attach the device.
 */
int
devctl_bus_dev_create(devctl_hdl_t dcp, devctl_ddef_t ddef_hdl,
    uint_t flags, devctl_hdl_t *new_dcp)
{
	char devname[MAXNAMELEN];
	char devpath[MAXPATHLEN];
	int  rv = 0;

	if (dcp == NULL || ddef_hdl == NULL) {
		errno = EINVAL;
		return (-1);
	}

	(void) memset(devname, 0, sizeof (devname));
	rv = dc_cmd(DEVCTL_BUS_DEV_CREATE, flags, DCP(dcp),
	    (nvlist_t *)ddef_hdl, devname);

	/*
	 * construct a device handle for the new device
	 */
	if ((rv == 0) && (new_dcp != NULL)) {
		char *minorname, *lastslash;

		(void) memset(devpath, 0, sizeof (devpath));
		(void) strcat(devpath, DCP(dcp)->opath);

		/*
		 * Take the pathname of the parent device, chop off
		 * any minor name info, and append the name@addr of
		 * the new child device.
		 * Call dc_mkhndl() with this constructed path and
		 * the CLONE handle type to create a new handle which
		 * references the new child device.
		 */
		lastslash = strrchr(devpath, '/');
		if (*(lastslash + 1) == '\0') {
			*lastslash = '\0';
		} else {
			if ((minorname = strchr(lastslash, ':')) != NULL)
				*minorname = '\0';
		}
		(void) strcat(devpath, "/");
		(void) strlcat(devpath, devname, MAXPATHLEN);
		*new_dcp = dc_mkhndl(DEVCTL_CLONE, devpath, 0, dcp);
		if (*new_dcp == NULL)
			rv = -1;
	}

	return (rv);
}

int
devctl_ap_connect(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_CONNECT, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_connect: %d\n", rv);

	return (rv);
}

int
devctl_ap_disconnect(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_DISCONNECT, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_disconnect: %d\n", rv);

	return (rv);
}

int
devctl_ap_insert(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_INSERT, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_insert: %d\n", rv);

	return (rv);
}

int
devctl_ap_remove(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_REMOVE, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_remove: %d\n", rv);

	return (rv);
}

int
devctl_ap_configure(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_CONFIGURE, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_configure: %d\n", rv);

	return (rv);
}

int
devctl_ap_unconfigure(devctl_hdl_t dcp, nvlist_t *ap_data)
{
	int  rv;

	rv = dc_cmd(DEVCTL_AP_UNCONFIGURE, 0, DCP(dcp), ap_data, NULL);

	if (_libdevice_debug)
		(void) printf("devctl_ap_unconfigure: %d\n", rv);

	return (rv);
}

int
devctl_ap_getstate(devctl_hdl_t dcp, nvlist_t *ap_data,
    devctl_ap_state_t *apstate)
{
	int  rv;
	devctl_ap_state_t ap_state;

	rv = dc_cmd(DEVCTL_AP_GETSTATE, 0, DCP(dcp), ap_data,
	    (void *)&ap_state);

	if (rv == -1)
		(void) memset(apstate, 0, sizeof (struct devctl_ap_state));
	else
		*apstate = ap_state;

	if (_libdevice_debug)
		(void) printf("devctl_ap_getstate: %d\n", rv);

	return (rv);
}

/*
 * Allocate a device 'definition' handle, in reality a list of
 * nvpair data.
 */
/* ARGSUSED */
devctl_ddef_t
devctl_ddef_alloc(char *nodename, int flags)
{

	nvlist_t *nvlp;

	if ((nodename == NULL) || *nodename == '\0') {
		errno = EINVAL;
		return (NULL);
	}

	/*
	 * allocate nvlist structure which is returned as an
	 * opaque handle to the caller.  If this fails, return
	 * NULL with errno left set to the value
	 */
	if (nvlist_alloc(&nvlp, NV_UNIQUE_NAME_TYPE, 0) != 0) {
		errno = ENOMEM;
		return (NULL);
	}

	/*
	 * add the nodename of the new device to the list
	 */
	if (nvlist_add_string(nvlp, DC_DEVI_NODENAME, nodename) != 0) {
		nvlist_free(nvlp);
		errno = ENOMEM;
		return (NULL);
	}

	if (_libdevice_debug)
		(void) printf("devctl_ddef_alloc: node %s nvp %p\n", nodename,
		    (void *)nvlp);

	return ((devctl_ddef_t)nvlp);
}

/*
 * free the definition handle
 */
void
devctl_ddef_free(devctl_ddef_t ddef_hdl)
{
	if (_libdevice_debug)
		(void) printf("devctl_ddef_free: nvp %p\n", (void *)ddef_hdl);

	if (ddef_hdl != NULL) {
		nvlist_free((nvlist_t *)ddef_hdl);
	}
}

/*
 * define an integer property
 */
int
devctl_ddef_int(devctl_ddef_t ddef_hdl, char *name, int32_t value)
{

	int rv;

	if (ddef_hdl == NULL || name == NULL || *name == '\0') {
		errno = EINVAL;
		return (-1);
	}

	rv = nvlist_add_int32((nvlist_t *)ddef_hdl, name, value);

	if (_libdevice_debug)
		(void) printf("devctl_ddef_int: rv %d nvp %p name %s val %d\n",
		    rv, (void *)ddef_hdl, name, value);

	return (rv);
}

/*
 * define an integer array property
 */
int
devctl_ddef_int_array(devctl_ddef_t ddef_hdl, char *name, int nelements,
    int32_t *value)
{
	int rv, i;

	if (ddef_hdl == NULL || name == NULL || *name == '\0') {
		errno = EINVAL;
		return (-1);
	}

	rv = nvlist_add_int32_array((nvlist_t *)ddef_hdl, name, value,
	    nelements);

	if (_libdevice_debug) {
		(void) printf("devctl_ddef_int_array: rv %d nvp %p name %s: ",
		    rv, (void *)ddef_hdl, name);
		for (i = 0; i < nelements; i++)
			(void) printf("0x%x ", value[i]);
		(void) printf("\n");
	}

	return (rv);
}

/*
 * define a string property
 */
int
devctl_ddef_string(devctl_ddef_t ddef_hdl, char *name, char *value)
{
	int rv;

	if (ddef_hdl == NULL || name == NULL || *name == '\0') {
		errno = EINVAL;
		return (-1);
	}

	rv = nvlist_add_string((nvlist_t *)ddef_hdl, name, value);

	if (_libdevice_debug)
		(void) printf("devctl_ddef_string: rv %d nvp %p %s=\"%s\"\n",
		    rv, (void *)ddef_hdl, name, value);

	return (rv);
}

/*
 * define a string array property
 */
int
devctl_ddef_string_array(devctl_ddef_t ddef_hdl, char *name, int nelements,
    char **value)
{
	int rv, i;

	if (ddef_hdl == NULL || name == NULL || *name == '\0') {
		errno = EINVAL;
		return (-1);
	}

	rv = nvlist_add_string_array((nvlist_t *)ddef_hdl, name,
	    value, nelements);

	if (_libdevice_debug) {
		(void) printf("devctl_ddef_string_array: rv %d nvp %p "
		    "name %s:\n", rv, (void *)ddef_hdl, name);
		for (i = 0; i < nelements; i++)
			(void) printf("\t%d: \"%s\"\n", i, value[i]);
	}
	return (rv);
}

/*
 * define a byte array property
 */
int
devctl_ddef_byte_array(devctl_ddef_t ddef_hdl, char *name, int nelements,
    uchar_t *value)
{
	int rv;

	if (ddef_hdl == NULL || name == NULL || *name == '\0') {
		errno = EINVAL;
		return (-1);
	}

	rv = nvlist_add_byte_array((nvlist_t *)ddef_hdl, name, value,
	    nelements);

	return (rv);
}

/*
 * return the pathname which was used to acquire the handle
 */
char *
devctl_get_pathname(devctl_hdl_t dcp, char *pathbuf, size_t bufsz)
{
	if (dcp == NULL || pathbuf == NULL || bufsz == 0) {
		errno = EINVAL;
		return (NULL);
	}

	(void) snprintf(pathbuf, bufsz, "%s", DCP(dcp)->opath);
	return (pathbuf);
}


/*
 * execute the IOCTL request
 */
static int
dc_cmd(uint_t cmd, uint_t flags, struct devctl_hdl *dcp, nvlist_t *ulp,
    void *retinfo)
{
	struct devctl_iocdata iocdata;
	int  rv = 0;

	if (_libdevice_debug)
		(void) printf("dc_cmd: %x dcp %p ulp %p flags %x rv %p\n", cmd,
		    (void *)dcp, (void *)ulp, flags, retinfo);

	if ((dcp == NULL) || (DCP(dcp)->fd == -1)) {
		errno = EINVAL;
		return (-1);
	}

	(void) memset(&iocdata, 0, sizeof (struct devctl_iocdata));

	/*
	 * if there was any user supplied data in the form of a nvlist,
	 * pack the list prior to copyin.
	 */
	if (ulp != NULL) {
		if (rv = nvlist_pack(ulp, (char **)&iocdata.nvl_user,
		    &iocdata.nvl_usersz, NV_ENCODE_NATIVE, 0)) {
			/*
			 * exit with errno set by nvlist_pack()
			 */
			goto exit;
		}
	} else {
		iocdata.nvl_user = NULL;
		iocdata.nvl_usersz = 0;
	}

	/*
	 * finish initalizing the request and execute the IOCTL
	 */
	iocdata.cmd = cmd;
	iocdata.flags = flags;
	iocdata.c_nodename = dcp->nodename;
	iocdata.c_unitaddr = dcp->unitaddr;
	iocdata.cpyout_buf = retinfo;
	rv = ioctl(dcp->fd, cmd, &iocdata);
	if (rv < 0 && _libdevice_debug) {
		(void) printf("dc_cmd: exited with rv %d, errno(%d):%s\n",
		    rv, errno, strerror(errno));
	}

exit:
	if (iocdata.nvl_user != NULL)
		free(iocdata.nvl_user);

	return (rv);
}