view usr/src/uts/common/io/pshot.c @ 10702:ca0edf2daf1c

PSARC/2004/678 EOF SCF_ Smartcard APIs PSARC/2005/106 iButton Smartcard EOF PSARC/2005/107 Cyberflex Smartcard EOF PSARC/2006/295 EOF pam_smartcard(5) PSARC/2006/296 EOF smartcard(1m) 6857067 Smartcard EOF Removal
author Darren J Moffat <Darren.Moffat@Sun.COM>
date Thu, 01 Oct 2009 07:56:28 -0700
parents 3564e28dd582
children 76b4d0b12a17
line wrap: on
line source

/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * pseudo bus nexus driver
 * hotplug framework test facility
 */

/*
 * The pshot driver can be used to exercise the i/o framework together
 * with devfs by configuring an arbitrarily complex device tree.
 *
 * The pshot driver is rooted at /devices/pshot.  The following commands
 * illustrate the operation of devfs together with pshot's bus_config.
 * The first command demonstrates that, like the magician showing there's
 * nothing up his sleeve, /devices/pshot is empty.  The second command
 * conjures up a branch of pshot nodes.  Note that pshot's bus_config is
 * called sequentially by devfs for each node, as part of the pathname
 * resolution, and that each pshot node is fully configured and
 * attached before that node's bus_config is called to configure the
 * next child down the tree.  The final result is a "disk" node configured
 * at the bottom of the named hierarchy of pshot nodes.
 *
 *	#
 *	# ls /devices/pshot
 *	#
 *	# ls -ld /devices/pshot/pshot@0/pshot@1/pshot@2/disk@3,0
 *	drwxr-xr-x   2 root     sys          512 Feb  6 15:10
 *		/devices/pshot/pshot@0/pshot@1/pshot@2/disk@3,0
 *
 * pshot supports some unique behaviors as aids for test error cases.
 *
 * Match these special address formats to behavior:
 *
 *	err.*		- induce bus_config error
 *	delay		- induce 1 second of bus_config delay time
 *	delay,n		- induce n seconds of bus_config delay time
 *	wait		- induce 1 second of bus_config wait time
 *	wait,n		- induce n seconds of bus_config wait time
 *	failinit.*	- induce error at INITCHILD
 *	failprobe.*	- induce error at probe
 *	failattach.*	- induce error at attach
 */

#if defined(lint) && !defined(DEBUG)
#define	DEBUG	1
#endif

#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/conf.h>
#include <sys/ddi_impldefs.h>
#include <sys/autoconf.h>
#include <sys/open.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/devctl.h>
#include <sys/disp.h>
#include <sys/utsname.h>
#include <sys/pshot.h>
#include <sys/debug.h>

static int pshot_log		= 0;
static int pshot_devctl_debug	= 0;
static int pshot_debug_busy	= 0;

static void *pshot_softstatep;

static int pshot_prop_autoattach;

#define	MAXPWR	3


/*
 * device configuration data
 */

/* should keep in sync with current release */
static struct {
	char *name;
	char *val;
} pshot_nodetypes[] = {
	{"DDI_NT_SERIAL", DDI_NT_SERIAL},
	{"DDI_NT_SERIAL_MB", DDI_NT_SERIAL_MB},
	{"DDI_NT_SERIAL_DO", DDI_NT_SERIAL_DO},
	{"DDI_NT_SERIAL_MB_DO", DDI_NT_SERIAL_MB_DO},
	{"DDI_NT_SERIAL_LOMCON", DDI_NT_SERIAL_LOMCON},
	{"DDI_NT_BLOCK", DDI_NT_BLOCK},
	{"DDI_NT_BLOCK_CHAN", DDI_NT_BLOCK_CHAN},
	{"DDI_NT_BLOCK_WWN", DDI_NT_BLOCK_WWN},
	{"DDI_NT_BLOCK_SAS", DDI_NT_BLOCK_SAS},
	{"DDI_NT_CD", DDI_NT_CD},
	{"DDI_NT_CD_CHAN", DDI_NT_CD_CHAN},
	{"DDI_NT_FD", DDI_NT_FD},
	{"DDI_NT_ENCLOSURE", DDI_NT_ENCLOSURE},
	{"DDI_NT_SCSI_ENCLOSURE", DDI_NT_SCSI_ENCLOSURE},
	{"DDI_NT_TAPE", DDI_NT_TAPE},
	{"DDI_NT_NET", DDI_NT_NET},
	{"DDI_NT_DISPLAY", DDI_NT_DISPLAY},
	{"DDI_PSEUDO", DDI_PSEUDO},
	{"DDI_NT_AUDIO", DDI_NT_AUDIO},
	{"DDI_NT_MOUSE", DDI_NT_MOUSE},
	{"DDI_NT_KEYBOARD", DDI_NT_KEYBOARD},
	{"DDI_NT_PARALLEL", DDI_NT_PARALLEL},
	{"DDI_NT_PRINTER", DDI_NT_PRINTER},
	{"DDI_NT_UGEN", DDI_NT_UGEN},
	{"DDI_NT_NEXUS", DDI_NT_NEXUS},
	{"DDI_NT_SCSI_NEXUS", DDI_NT_SCSI_NEXUS},
	{"DDI_NT_ATTACHMENT_POINT", DDI_NT_ATTACHMENT_POINT},
	{"DDI_NT_SCSI_ATTACHMENT_POINT", DDI_NT_SCSI_ATTACHMENT_POINT},
	{"DDI_NT_PCI_ATTACHMENT_POINT", DDI_NT_PCI_ATTACHMENT_POINT},
	{"DDI_NT_SBD_ATTACHMENT_POINT", DDI_NT_SBD_ATTACHMENT_POINT},
	{"DDI_NT_FC_ATTACHMENT_POINT", DDI_NT_FC_ATTACHMENT_POINT},
	{"DDI_NT_USB_ATTACHMENT_POINT", DDI_NT_USB_ATTACHMENT_POINT},
	{"DDI_NT_BLOCK_FABRIC", DDI_NT_BLOCK_FABRIC},
	{"DDI_NT_AV_ASYNC", DDI_NT_AV_ASYNC},
	{"DDI_NT_AV_ISOCH", DDI_NT_AV_ISOCH},
	{ NULL, NULL }
};

/* Node name */
static char *pshot_compat_diskname = "cdisk";

/* Compatible names... */
static char *pshot_compat_psramdisks[] = {
	"psramhead",
	"psramrom",
	"psramdisk",
	"psramd",
	"psramwhat"
};

/*
 * devices "natively" supported by pshot (i.e. included with SUNWiotu)
 * used to initialize pshot_devices with
 */
static pshot_device_t pshot_stock_devices[] = {
	{"disk",	DDI_NT_BLOCK,		"gen_drv"},
	{"disk_chan",	DDI_NT_BLOCK_CHAN,	"gen_drv"},
	{"disk_wwn",	DDI_NT_BLOCK_WWN,	"gen_drv"},
	{"disk_cdrom",	DDI_NT_CD,		"gen_drv"},
	{"disk_cdrom.chan", DDI_NT_CD_CHAN,	"gen_drv"},
/* Note: use bad_drv to force attach errors */
	{"disk_fd",	DDI_NT_FD,		"bad_drv"},
	{"tape",	DDI_NT_TAPE,		"gen_drv"},
	{"net",		DDI_NT_NET,		"gen_drv"},
	{"display",	DDI_NT_DISPLAY,		"gen_drv"},
	{"pseudo",	DDI_PSEUDO,		"gen_drv"},
	{"audio",	DDI_NT_AUDIO,		"gen_drv"},
	{"mouse",	DDI_NT_MOUSE,		"gen_drv"},
	{"keyboard",	DDI_NT_KEYBOARD,	"gen_drv"},
	{"nexus",	DDI_NT_NEXUS,		"pshot"}
};
#define	PSHOT_N_STOCK_DEVICES \
	(sizeof (pshot_stock_devices) / sizeof (pshot_device_t))

static pshot_device_t *pshot_devices = NULL;
static size_t pshot_devices_len = 0;

/* protects <pshot_devices>, <pshot_devices_len> */
static kmutex_t pshot_devices_lock;


/*
 * event testing
 */

static ndi_event_definition_t pshot_ndi_event_defs[] = {
{ PSHOT_EVENT_TAG_OFFLINE, PSHOT_EVENT_NAME_DEV_OFFLINE,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },

{ PSHOT_EVENT_TAG_DEV_RESET, PSHOT_EVENT_NAME_DEV_RESET,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_TGT },

{ PSHOT_EVENT_TAG_BUS_RESET, PSHOT_EVENT_NAME_BUS_RESET,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },

{ PSHOT_EVENT_TAG_BUS_QUIESCE, PSHOT_EVENT_NAME_BUS_QUIESCE,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },

{ PSHOT_EVENT_TAG_BUS_UNQUIESCE, PSHOT_EVENT_NAME_BUS_UNQUIESCE,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },

{ PSHOT_EVENT_TAG_TEST_POST, PSHOT_EVENT_NAME_BUS_TEST_POST,
	EPL_INTERRUPT, NDI_EVENT_POST_TO_TGT }
};


#define	PSHOT_N_NDI_EVENTS \
	(sizeof (pshot_ndi_event_defs) / sizeof (ndi_event_definition_t))

#ifdef DEBUG

static ndi_event_definition_t pshot_test_events[] = {
{ 10, "test event 0", EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },
{ 11, "test event 1", EPL_KERNEL, NDI_EVENT_POST_TO_TGT },
{ 12, "test event 2", EPL_INTERRUPT, NDI_EVENT_POST_TO_TGT },
{ 13, "test event 3", EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },
{ 14, "test event 4", EPL_KERNEL, NDI_EVENT_POST_TO_ALL},
{ 15, "test event 5", EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL },
{ 16, "test event 6", EPL_KERNEL, NDI_EVENT_POST_TO_ALL },
{ 17, "test event 7", EPL_INTERRUPT, NDI_EVENT_POST_TO_ALL }
};

static ndi_event_definition_t pshot_test_events_high[] = {
{ 20, "test event high 0", EPL_HIGHLEVEL, NDI_EVENT_POST_TO_ALL}
};

#define	PSHOT_N_TEST_EVENTS \
	(sizeof (pshot_test_events)/sizeof (ndi_event_definition_t))
#endif

struct register_events {
	char		*event_name;
	ddi_eventcookie_t event_cookie;
	void	(*event_callback)
			(dev_info_t *,
			ddi_eventcookie_t,
			void *arg,
			void *impldata);
	ddi_callback_id_t cb_id;
};

struct register_events pshot_register_events[] = {
{ PSHOT_EVENT_NAME_DEV_OFFLINE, 0, pshot_event_cb, 0 },
{ PSHOT_EVENT_NAME_DEV_RESET, 0, pshot_event_cb, 0 },
{ PSHOT_EVENT_NAME_BUS_RESET, 0, pshot_event_cb, 0 },
{ PSHOT_EVENT_NAME_BUS_QUIESCE, 0, pshot_event_cb, 0 },
{ PSHOT_EVENT_NAME_BUS_UNQUIESCE, 0, pshot_event_cb, 0 },
{ PSHOT_EVENT_NAME_BUS_TEST_POST, 0, pshot_event_cb, 0 }
};

#define	PSHOT_N_DDI_EVENTS \
	(sizeof (pshot_register_events) / sizeof (struct register_events))


#ifdef DEBUG

static struct register_events pshot_register_test[] = {
{ "test event 0", 0, pshot_event_cb_test, 0},
{ "test event 1", 0, pshot_event_cb_test, 0},
{ "test event 2", 0, pshot_event_cb_test, 0},
{ "test event 3", 0, pshot_event_cb_test, 0},
{ "test event 4", 0, pshot_event_cb_test, 0},
{ "test event 5", 0, pshot_event_cb_test, 0},
{ "test event 6", 0, pshot_event_cb_test, 0},
{ "test event 7", 0, pshot_event_cb_test, 0}
};


static struct register_events pshot_register_high_test[] = {
	{"test event high 0", 0, pshot_event_cb_test, 0}
};

#endif /* DEBUG */

static struct {
	int ioctl_int;
	char *ioctl_char;
} pshot_devctls[] = {
	{DEVCTL_DEVICE_GETSTATE, "DEVCTL_DEVICE_GETSTATE"},
	{DEVCTL_DEVICE_ONLINE, "DEVCTL_DEVICE_ONLINE"},
	{DEVCTL_DEVICE_OFFLINE, "DEVCTL_DEVICE_OFFLINE"},
	{DEVCTL_DEVICE_REMOVE, "DEVCTL_DEVICE_REMOVE"},
	{DEVCTL_BUS_GETSTATE, "DEVCTL_BUS_GETSTATE"},
	{DEVCTL_BUS_DEV_CREATE, "DEVCTL_BUS_DEV_CREATE"},
	{DEVCTL_BUS_RESET, "DEVCTL_BUS_RESET"},
	{DEVCTL_BUS_RESETALL, "DEVCTL_BUS_RESETALL"},
	{0, NULL}
};

static struct bus_ops pshot_bus_ops = {
	BUSO_REV,			/* busops_rev */
	nullbusmap,			/* bus_map */
	NULL,				/* bus_get_intrspec */
	NULL,				/* bus_add_interspec */
	NULL,				/* bus_remove_interspec */
	i_ddi_map_fault,		/* bus_map_fault */
	ddi_dma_map,			/* bus_dma_map */
	ddi_dma_allochdl,		/* bus_dma_allochdl */
	ddi_dma_freehdl,		/* bus_dma_freehdl */
	ddi_dma_bindhdl,		/* bus_dma_bindhdl */
	ddi_dma_unbindhdl,		/* bus_dma_unbindhdl */
	ddi_dma_flush,			/* bus_dma_flush */
	ddi_dma_win,			/* bus_dma_win */
	ddi_dma_mctl,			/* bus_dma_ctl */
	pshot_ctl,			/* bus_ctl */
	ddi_bus_prop_op,		/* bus_prop_op */
	pshot_get_eventcookie,		/* bus_get_eventcookie */
	pshot_add_eventcall,		/* bus_add_eventcall */
	pshot_remove_eventcall,		/* bus_remove_event */
	pshot_post_event,		/* bus_post_event */
	NULL,				/* bus_intr_ctl */
	pshot_bus_config,		/* bus_config */
	pshot_bus_unconfig,		/* bus_unconfig */
	NULL,				/* bus_fm_init */
	NULL,				/* bus_fm_fini */
	NULL,				/* bus_fm_access_enter */
	NULL,				/* bus_fm_access_exit */
	pshot_bus_power,		/* bus_power */
	pshot_bus_introp		/* bus_intr_op */
};

static struct cb_ops pshot_cb_ops = {
	pshot_open,			/* open */
	pshot_close,			/* close */
	nodev,				/* strategy */
	nodev,				/* print */
	nodev,				/* dump */
	nodev,				/* read */
	nodev,				/* write */
	pshot_ioctl,			/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	nochpoll,			/* poll */
	ddi_prop_op,			/* prop_op */
	NULL,				/* streamtab */
	D_NEW | D_MP | D_HOTPLUG,	/* flags */
	CB_REV,				/* cb_rev */
	nodev,				/* aread */
	nodev,				/* awrite */
};

static struct dev_ops pshot_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	pshot_info,		/* getinfo */
	nulldev,		/* identify */
	pshot_probe,		/* probe */
	pshot_attach,		/* attach */
	pshot_detach,		/* detach */
	nodev,			/* reset */
	&pshot_cb_ops,		/* driver operations */
	&pshot_bus_ops,		/* bus operations */
	pshot_power,		/* power */
	ddi_quiesce_not_supported,	/* devo_quiesce */

};


/*
 * Module linkage information for the kernel.
 */
static struct modldrv modldrv = {
	&mod_driverops,
	"pshotnex",
	&pshot_ops,
};

static struct modlinkage modlinkage = {
	MODREV_1, &modldrv, NULL
};


/*
 * pshot_devices is set up on the first attach and destroyed on fini
 *
 * therefore PSHOT_PROP_DEV* properties may be set just for the root device,
 * instead of being set globably, in pshot.conf by specifying the properties
 * on a single line in the form:
 *	name="pshot" parent="/" <dev props ..>
 * to unclutter a device tree snapshot.
 * this of course produces a long single line that may wrap around several
 * times on screen
 */

int
_init(void)
{
	int rv;

	rv = ddi_soft_state_init(&pshot_softstatep, sizeof (pshot_t), 0);

	if (rv != DDI_SUCCESS)
		return (rv);

	mutex_init(&pshot_devices_lock, NULL, MUTEX_DRIVER, NULL);
	pshot_devices = NULL;
	pshot_devices_len = 0;

	if ((rv = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&pshot_softstatep);
		mutex_destroy(&pshot_devices_lock);
	}
	return (rv);
}

int
_fini(void)
{
	int rv;

	if ((rv = mod_remove(&modlinkage)) != 0)
		return (rv);

	ddi_soft_state_fini(&pshot_softstatep);
	mutex_destroy(&pshot_devices_lock);
	if (pshot_devices)
		pshot_devices_free(pshot_devices, pshot_devices_len);
	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


/*ARGSUSED*/
static int
pshot_probe(dev_info_t *devi)
{
	int	instance = ddi_get_instance(devi);
	char	*bus_addr;

	/*
	 * Hook for tests to force probe fail
	 */
	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, devi, 0, "bus-addr",
	    &bus_addr) == DDI_PROP_SUCCESS) {
		if (strncmp(bus_addr, "failprobe", 9) == 0) {
			if (pshot_debug)
				cmn_err(CE_CONT, "pshot%d: "
				    "%s forced probe failure\n",
				    instance, bus_addr);
			ddi_prop_free(bus_addr);
			return (DDI_PROBE_FAILURE);
		}
		ddi_prop_free(bus_addr);
	}

	return (DDI_PROBE_SUCCESS);
}


/*ARGSUSED*/
static int
pshot_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	int instance;
	minor_t minor;
	pshot_t *pshot;

	minor = getminor((dev_t)arg);
	instance = pshot_minor_decode_inst(minor);
	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		pshot = ddi_get_soft_state(pshot_softstatep, instance);
		if (pshot == NULL) {
			cmn_err(CE_WARN, "pshot_info: get soft state failed "
			    "on minor %u, instance %d", minor, instance);
			return (DDI_FAILURE);
		}
		*result = (void *)pshot->dip;
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(uintptr_t)instance;
		break;
	default:
		cmn_err(CE_WARN, "pshot_info: unrecognized cmd 0x%x on "
		    "minor %u, instance %d", infocmd, minor, instance);
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);
}


static int
pshot_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	int instance = ddi_get_instance(devi);
	pshot_t *pshot;
	int rval, i;
	int prop_flags = DDI_PROP_DONTPASS | DDI_PROP_NOTPROM;
	char *bus_addr;
	char *pm_comp[] = {
		"NAME=bus",
		"0=B3",
		"1=B2",
		"2=B1",
		"3=B0"};
	char *pm_hw_state = {"needs-suspend-resume"};

	pshot_prop_autoattach = ddi_prop_get_int(DDI_DEV_T_ANY, devi,
	    prop_flags, "autoattach", 0);

	switch (cmd) {

	case DDI_ATTACH:
		if (pshot_debug)
			cmn_err(CE_CONT, "attach: %s%d/pshot%d\n",
			    ddi_get_name(ddi_get_parent(devi)),
			    ddi_get_instance(ddi_get_parent(devi)),
			    instance);

		/*
		 * Hook for tests to force attach fail
		 */
		if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, devi, 0, "bus-addr",
		    &bus_addr) == DDI_PROP_SUCCESS) && bus_addr != NULL) {
			if (strncmp(bus_addr, "failattach", 10) == 0) {
				if (pshot_debug)
					cmn_err(CE_CONT, "pshot%d: "
					    "%s forced attach failure\n",
					    instance, bus_addr);
				ddi_prop_free(bus_addr);
				return (DDI_FAILURE);
			}
			ddi_prop_free(bus_addr);
		}

		/*
		 * minor nodes setup
		 */
		if (ddi_soft_state_zalloc(pshot_softstatep, instance) !=
		    DDI_SUCCESS) {
			return (DDI_FAILURE);
		}
		pshot = ddi_get_soft_state(pshot_softstatep, instance);
		pshot->dip = devi;
		pshot->instance = instance;
		mutex_init(&pshot->lock, NULL, MUTEX_DRIVER, NULL);

		/* set each minor, then create on dip all together */

		i = PSHOT_NODENUM_DEVCTL;
		pshot->nodes[i].pshot = pshot;
		pshot->nodes[i].minor = pshot_minor_encode(instance, i);
		(void) strncpy(pshot->nodes[i].name, PSHOT_NODENAME_DEVCTL,
		    PSHOT_MAX_MINOR_NAMELEN);

		i = PSHOT_NODENUM_TESTCTL;
		pshot->nodes[i].pshot = pshot;
		pshot->nodes[i].minor = pshot_minor_encode(instance, i);
		(void) strncpy(pshot->nodes[i].name, PSHOT_NODENAME_TESTCTL,
		    PSHOT_MAX_MINOR_NAMELEN);

		/* this assumes contiguous a filling */
		for (i = 0; i <= PSHOT_MAX_NODENUM; i++) {
			if (ddi_create_minor_node(devi, pshot->nodes[i].name,
			    S_IFCHR, pshot->nodes[i].minor, DDI_NT_NEXUS, 0) !=
			    DDI_SUCCESS) {
				cmn_err(CE_WARN, "attach: cannot create "
				    "minor %s", pshot->nodes[i].name);
				goto FAIL_ATTACH;
			}
		}

		/*
		 * pshot_devices setup
		 */
		if (pshot_devices_setup(devi)) {
			cmn_err(CE_WARN, "attach: pshot devices setup "
			    "failed");
			goto FAIL_ATTACH;
		}

		/*
		 * events setup
		 */
		for (i = 0; i < PSHOT_N_DDI_EVENTS; i++) {
			rval =	ddi_get_eventcookie(devi,
			    pshot_register_events[i].event_name,
			    &pshot_register_events[i].event_cookie);

			if (pshot_debug)
				cmn_err(CE_CONT, "pshot%d: event=%s:"
				    "ddi_get_eventcookie rval=%d\n",
				    instance,
				    pshot_register_events[i].event_name, rval);

			if (rval == DDI_SUCCESS) {
				rval = ddi_add_event_handler(devi,
				    pshot_register_events[i].event_cookie,
				    pshot_register_events[i].event_callback,
				    (void *)pshot,
				    &pshot->callback_cache[i]);

				if (pshot_debug)
					cmn_err(CE_CONT, "pshot%d: event=%s: "
					    "ddi_add_event_handler rval=%d\n",
					    instance,
					    pshot_register_events[i].event_name,
					    rval);
			}
		}

#ifdef DEBUG
		if (pshot_event_test_enable) {
			pshot_event_test((void *)pshot);
			(void) timeout(pshot_event_test_post_one, (void *)pshot,
			    instance * drv_usectohz(60000000));
		}
#endif

		/*
		 * allocate an ndi event handle
		 */
		if (ndi_event_alloc_hdl(devi, NULL, &pshot->ndi_event_hdl,
		    NDI_SLEEP) != NDI_SUCCESS) {
			goto FAIL_ATTACH;
		}

		pshot->ndi_events.ndi_events_version = NDI_EVENTS_REV1;
		pshot->ndi_events.ndi_n_events = PSHOT_N_NDI_EVENTS;
		pshot->ndi_events.ndi_event_defs = pshot_ndi_event_defs;

		if (ndi_event_bind_set(pshot->ndi_event_hdl, &pshot->ndi_events,
		    NDI_SLEEP) != NDI_SUCCESS) {
			cmn_err(CE_CONT, "pshot%d bind set failed\n",
			    instance);
		}

		/*
		 * setup a test for nexus auto-attach iff we are
		 * a second level pshot node (parent == /SUNW,pshot)
		 * enable by setting "autoattach=1" in pshot.conf
		 */
		if ((PARENT_IS_PSHOT(devi)) && (pshot_prop_autoattach != 0) &&
		    (ddi_get_instance(ddi_get_parent(devi))) == 0)
			pshot_setup_autoattach(devi);

		/*
		 * initialize internal state to idle: busy = 0,
		 * power level = -1
		 */
		mutex_enter(&pshot->lock);
		pshot->busy = 0;
		pshot->busy_ioctl = 0;
		pshot->level = -1;
		pshot->state &= ~STRICT_PARENT;
		pshot->state |= PM_SUPPORTED;
		mutex_exit(&pshot->lock);

		/*
		 * Create the "pm-want-child-notification?" property
		 * for the root node /devices/pshot
		 */
		if (instance == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:\n\t"
				    " create the"
				    " \"pm-want-child-notification?\" property"
				    " for the root node\n", instance);
			}
			if (ddi_prop_create(DDI_DEV_T_NONE, devi, 0,
			    "pm-want-child-notification?", NULL, 0)
			    != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "%s%d:\n\t"
				    " unable to create the"
				    " \"pm-want-child-notification?\""
				    " property", ddi_get_name(devi),
				    ddi_get_instance(devi));

				goto FAIL_ATTACH;
			}
		}

		/*
		 * Check if the pm-want-child-notification? property was
		 * created in pshot_bus_config_setup_nexus() by the parent.
		 * Set the STRICT_PARENT flag if not.
		 */
		if (ddi_prop_exists(DDI_DEV_T_ANY, devi,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "pm-want-child-notification?") != 1) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
				    " STRICT PARENT\n", instance);
			}
			mutex_enter(&pshot->lock);
			pshot->state |= STRICT_PARENT;
			mutex_exit(&pshot->lock);
		} else {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
				    " INVOLVED PARENT\n", instance);
			}
			mutex_enter(&pshot->lock);
			pshot->state &= ~STRICT_PARENT;
			mutex_exit(&pshot->lock);
		}

		/*
		 * create the pm-components property: one component
		 * with 4 power levels.
		 * - skip for pshot@XXX,nopm and pshot@XXX,nopm_strict:
		 * "no-pm-components" property
		 */
		if (ddi_prop_exists(DDI_DEV_T_ANY, devi,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "no-pm-components") == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
				    " create the \"pm_components\" property\n",
				    instance);
			}
			if (ddi_prop_update_string_array(DDI_DEV_T_NONE, devi,
			    "pm-components", pm_comp, 5) != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "%s%d: DDI_ATTACH:\n\t"
				    " unable to create the \"pm-components\""
				    " property", ddi_get_name(devi),
				    ddi_get_instance(devi));

				goto FAIL_ATTACH;
			}
		} else {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
				    " NO-PM_COMPONENTS PARENT\n", instance);
			}
			mutex_enter(&pshot->lock);
			pshot->state &= ~PM_SUPPORTED;
			mutex_exit(&pshot->lock);
		}

		/*
		 * create the property needed to get DDI_SUSPEND
		 * and DDI_RESUME calls
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
			    " create pm-hardware-state property\n",
			    instance);
		}
		if (ddi_prop_update_string(DDI_DEV_T_NONE, devi,
		    "pm-hardware-state", pm_hw_state) != DDI_PROP_SUCCESS) {
			cmn_err(CE_WARN, "%s%d: DDI_ATTACH:\n\t"
			    " unable to create the \"pm-hardware-state\""
			    " property", ddi_get_name(devi),
			    ddi_get_instance(devi));

			goto FAIL_ATTACH;
		}

		/*
		 * set power level to max via pm_raise_power(),
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if (pshot->state & PM_SUPPORTED) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_ATTACH:"
				    " raise power to MAXPWR\n", instance);
			}
			if (pm_raise_power(pshot->dip, 0, MAXPWR) !=
			    DDI_SUCCESS) {
				cmn_err(CE_WARN, "%s%d: DDI_ATTACH:"
				    " pm_raise_power failed",
				    ddi_get_name(devi),
				    ddi_get_instance(devi));

				goto FAIL_ATTACH;

			}
		}

		if (pshot_log)
			cmn_err(CE_CONT, "pshot%d attached\n", instance);
		ddi_report_dev(devi);

		return (DDI_SUCCESS);
		/*NOTREACHED*/
FAIL_ATTACH:
		ddi_remove_minor_node(devi, NULL);
		mutex_destroy(&pshot->lock);
		ddi_soft_state_free(pshot_softstatep, instance);
		return (DDI_FAILURE);

	case DDI_RESUME:
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: DDI_RESUME: resuming\n",
			    instance);
		}
		pshot = ddi_get_soft_state(pshot_softstatep, instance);

		/*
		 * set power level to max via pm_raise_power(),
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if (pshot->state & PM_SUPPORTED) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_RESUME:"
				    " raise power to MAXPWR\n", instance);
			}
			if (pm_raise_power(pshot->dip, 0, MAXPWR) !=
			    DDI_SUCCESS) {
				cmn_err(CE_WARN, "%s%d: DDI_RESUME:"
				    " pm_raise_power failed",
				    ddi_get_name(devi),
				    ddi_get_instance(devi));
			}
		}

		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: DDI_RESUME: resumed\n",
			    instance);
		}
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}
}

static int
pshot_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance = ddi_get_instance(devi);
	int i, rval;
	pshot_t *pshot = ddi_get_soft_state(pshot_softstatep, instance);
	int level_tmp;

	if (pshot == NULL)
		return (DDI_FAILURE);

	switch (cmd) {

	case DDI_DETACH:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d: DDI_DETACH\n", instance);
		/*
		 * power off component 0
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if (pshot->state & PM_SUPPORTED) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_DETACH:"
				    " power off\n", instance);
			}
			if (pm_lower_power(pshot->dip, 0, 0) != DDI_SUCCESS) {
				cmn_err(CE_WARN, "%s%d: DDI_DETACH:\n\t"
				    "pm_lower_power failed for comp 0 to"
				    " level 0", ddi_get_name(devi),
				    ddi_get_instance(devi));

				return (DDI_FAILURE);
			}

			/*
			 * Check if the power level is actually OFF.
			 * Issue pm_power_has_changed if not.
			 */
			mutex_enter(&pshot->lock);
			if (pshot->level != 0) {
				if (pshot_debug) {
					cmn_err(CE_NOTE, "pshot%d:"
					    " DDI_DETACH: power off via"
					    " pm_power_has_changed instead\n",
					    instance);
				}
				level_tmp = pshot->level;
				pshot->level = 0;
				if (pm_power_has_changed(pshot->dip, 0, 0) !=
				    DDI_SUCCESS) {
					if (pshot_debug) {
						cmn_err(CE_NOTE, "pshot%d:"
						    " DDI_DETACH:"
						    " pm_power_has_changed"
						    " failed\n", instance);
					}
					pshot->level = level_tmp;
					mutex_exit(&pshot->lock);

					return (DDI_FAILURE);
				}
			}
			mutex_exit(&pshot->lock);
		}

		for (i = 0; i < PSHOT_N_DDI_EVENTS; i++) {
			if (pshot->callback_cache[i] != NULL) {
				rval = ddi_remove_event_handler(
				    pshot->callback_cache[i]);
				ASSERT(rval == DDI_SUCCESS);
			}
		}

#ifdef DEBUG
		for (i = 0; i < PSHOT_N_TEST_EVENTS; i++) {
			if (pshot->test_callback_cache[i] != NULL) {
				rval = ddi_remove_event_handler(
				    pshot->test_callback_cache[i]);
				ASSERT(rval == DDI_SUCCESS);
			}
		}
#endif
		rval = ndi_event_free_hdl(pshot->ndi_event_hdl);
		ASSERT(rval == DDI_SUCCESS);

		if (pshot_log)
			cmn_err(CE_CONT, "pshot%d detached\n", instance);

		ddi_remove_minor_node(devi, NULL);
		mutex_destroy(&pshot->lock);
		ddi_soft_state_free(pshot_softstatep, instance);
		break;

	case DDI_SUSPEND:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d: DDI_SUSPEND\n", instance);
		/*
		 * fail the suspend if FAIL_SUSPEND_FLAG is set.
		 * clear the FAIL_SUSPEND_FLAG flag
		 */
		mutex_enter(&pshot->lock);
		if (pshot->state & FAIL_SUSPEND_FLAG) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " FAIL_SUSPEND_FLAG set, fail suspend\n",
				    ddi_get_instance(devi));
			}
			pshot->state &= ~FAIL_SUSPEND_FLAG;
			rval = DDI_FAILURE;
		} else {
			rval = DDI_SUCCESS;
		}
		mutex_exit(&pshot->lock);

		/*
		 * power OFF via pm_power_has_changed
		 */
		mutex_enter(&pshot->lock);
		if (pshot->state & PM_SUPPORTED) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d: DDI_SUSPEND:"
				    " power off via pm_power_has_changed\n",
				    instance);
			}
			level_tmp = pshot->level;
			pshot->level = 0;
			if (pm_power_has_changed(pshot->dip, 0, 0) !=
			    DDI_SUCCESS) {
				if (pshot_debug) {
					cmn_err(CE_NOTE, "pshot%d:"
					    " DDI_SUSPEND:"
					    " pm_power_has_changed failed\n",
					    instance);
				}
				pshot->level = level_tmp;
				rval = DDI_FAILURE;
			}
		}
		mutex_exit(&pshot->lock);
		return (rval);

	default:
		break;
	}

	return (DDI_SUCCESS);
}


/*
 * returns number of bits to represent <val>
 */
static size_t
pshot_numbits(size_t val)
{
	size_t bitcnt;

	if (val == 0)
		return (0);
	for (bitcnt = 1; 1 << bitcnt < val; bitcnt++)
		;
	return (bitcnt);
}

/*
 * returns a minor number encoded with instance <inst> and an index <nodenum>
 * that identifies the minor node for this instance
 */
static minor_t
pshot_minor_encode(int inst, minor_t nodenum)
{
	return (((minor_t)inst << PSHOT_NODENUM_BITS()) |
	    (((1 << PSHOT_NODENUM_BITS()) - 1) & nodenum));
}

/*
 * returns instance of <minor>
 */
static int
pshot_minor_decode_inst(minor_t minor)
{
	return (minor >> PSHOT_NODENUM_BITS());
}

/*
 * returns node number indexing a minor node for the instance in <minor>
 */
static minor_t
pshot_minor_decode_nodenum(minor_t minor)
{
	return (minor & ((1 << PSHOT_NODENUM_BITS()) - 1));
}


/*
 * pshot_bus_introp: pshot convert an interrupt number to an
 *			   interrupt. NO OP for pseudo drivers.
 */
/*ARGSUSED*/
static int
pshot_bus_introp(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op,
    ddi_intr_handle_impl_t *hdlp, void *result)
{
	return (DDI_FAILURE);
}
static int
pshot_ctl(dev_info_t *dip, dev_info_t *rdip,
    ddi_ctl_enum_t ctlop, void *arg, void *result)
{
	int instance;
	pshot_t *pshot;
	char *childname;
	int childinstance;
	char *name;
	int circ;
	struct attachspec *as;
	struct detachspec *ds;
	int rval = DDI_SUCCESS;
	int no_pm_components_child;

	name = ddi_get_name(dip);
	instance = ddi_get_instance(dip);
	pshot = ddi_get_soft_state(pshot_softstatep, instance);
	if (pshot == NULL) {
		return (ENXIO);
	}

	switch (ctlop) {
	case DDI_CTLOPS_REPORTDEV:
		if (rdip == (dev_info_t *)0)
			return (DDI_FAILURE);
		cmn_err(CE_CONT, "?pshot-device: %s%d\n",
		    ddi_get_name(rdip), ddi_get_instance(rdip));
		return (DDI_SUCCESS);

	case DDI_CTLOPS_INITCHILD:
	{
		dev_info_t *child = (dev_info_t *)arg;

		if (pshot_debug) {
			cmn_err(CE_CONT, "initchild %s%d/%s%d state 0x%x\n",
			    ddi_get_name(dip), ddi_get_instance(dip),
			    ddi_node_name(child), ddi_get_instance(child),
			    DEVI(child)->devi_state);
		}

		return (pshot_initchild(dip, child));
	}

	case DDI_CTLOPS_UNINITCHILD:
	{
		dev_info_t *child = (dev_info_t *)arg;

		if (pshot_debug) {
			cmn_err(CE_CONT, "uninitchild %s%d/%s%d state 0x%x\n",
			    ddi_get_name(dip), ddi_get_instance(dip),
			    ddi_node_name(child), ddi_get_instance(child),
			    DEVI(child)->devi_state);
		}

		return (pshot_uninitchild(dip, child));
	}

	case DDI_CTLOPS_DMAPMAPC:
	case DDI_CTLOPS_REPORTINT:
	case DDI_CTLOPS_REGSIZE:
	case DDI_CTLOPS_NREGS:
	case DDI_CTLOPS_SIDDEV:
	case DDI_CTLOPS_SLAVEONLY:
	case DDI_CTLOPS_AFFINITY:
	case DDI_CTLOPS_POKE:
	case DDI_CTLOPS_PEEK:
		/*
		 * These ops correspond to functions that "shouldn't" be called
		 * by a pseudo driver.  So we whine when we're called.
		 */
		cmn_err(CE_CONT, "%s%d: invalid op (%d) from %s%d\n",
		    ddi_get_name(dip), ddi_get_instance(dip),
		    ctlop, ddi_get_name(rdip), ddi_get_instance(rdip));
		return (DDI_FAILURE);

	case DDI_CTLOPS_ATTACH:
	{
		dev_info_t *child = (dev_info_t *)rdip;
		childname = ddi_node_name(child);
		childinstance = ddi_get_instance(child);
		as = (struct attachspec *)arg;

		no_pm_components_child = 0;
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "no-pm-components") == 1) {
			no_pm_components_child = 1;
		}
		if (pshot_debug) {
			cmn_err(CE_CONT, "%s%d: ctl_attach %s%d [%d]\n",
			    name, instance, childname, childinstance,
			    no_pm_components_child);
		}

		ndi_devi_enter(dip, &circ);

		switch (as->when) {
		case DDI_PRE:
			/*
			 * Mark nexus busy before a child attaches.
			 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm
			 * - pshot@XXX,nopm_strict)
			 */
			if (!(pshot->state & PM_SUPPORTED))
				break;
			mutex_enter(&pshot->lock);
			++(pshot->busy);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "%s%d:"
				    " ctl_attach_pre: busy for %s%d:"
				    " busy = %d\n", name, instance,
				    childname, childinstance,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			rval = pm_busy_component(dip, 0);
			ASSERT(rval == DDI_SUCCESS);
			break;
		case DDI_POST:
			/*
			 * Mark nexus idle after a child attaches.
			 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm).
			 * - also skip if this is not a stict parent and
			 * - the child is a tape device or a no-pm-components
			 * - nexus node.
			 */
			if (!(pshot->state & PM_SUPPORTED) ||
			    (strcmp(childname, "tape") == 0 &&
			    !(pshot->state & STRICT_PARENT)) ||
			    no_pm_components_child)
				break;
			mutex_enter(&pshot->lock);
			ASSERT(pshot->busy > 0);
			--pshot->busy;
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "%s%d:"
				    " ctl_attach_post: idle for %s%d:"
				    " busy = %d\n", name, instance,
				    childname, childinstance,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			rval = pm_idle_component(dip, 0);
			ASSERT(rval == DDI_SUCCESS);
			break;
		}

		ndi_devi_exit(dip, circ);

		return (rval);
	}
	case DDI_CTLOPS_DETACH:
		{
		dev_info_t *child = (dev_info_t *)rdip;
		childname = ddi_node_name(child);
		childinstance = ddi_get_instance(child);
		ds = (struct detachspec *)arg;

		no_pm_components_child = 0;
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "no-pm-components") == 1) {
			no_pm_components_child = 1;
		}
		if (pshot_debug) {
			cmn_err(CE_CONT,
			    "%s%d: ctl_detach %s%d [%d]\n",
			    name, instance, childname, childinstance,
			    no_pm_components_child);
		}

		ndi_devi_enter(dip, &circ);

		switch (ds->when) {
		case DDI_PRE:
			/*
			 * Mark nexus busy before a child detaches.
			 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm
			 * - pshot@XXX,nopm_strict), or if the child is a
			 * - no-pm-components nexus node.
			 */
			if (!(pshot->state & PM_SUPPORTED) ||
			    (strcmp(childname, "tape") == 0 &&
			    !(pshot->state & STRICT_PARENT)) ||
			    no_pm_components_child)
				break;
			mutex_enter(&pshot->lock);
			++(pshot->busy);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "%s%d:"
				    " ctl_detach_pre: busy for %s%d:"
				    " busy = %d\n", name, instance,
				    childname, childinstance,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			rval = pm_busy_component(dip, 0);
			ASSERT(rval == DDI_SUCCESS);

			break;
		case DDI_POST:
			/*
			 * Mark nexus idle after a child detaches.
			 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
			 */
			if (!(pshot->state & PM_SUPPORTED))
				break;
			mutex_enter(&pshot->lock);
			ASSERT(pshot->busy > 0);
			--pshot->busy;
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "%s%d:"
				    " ctl_detach_post: idle for %s%d:"
				    " busy = %d\n", name, instance,
				    childname, childinstance,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			rval = pm_idle_component(dip, 0);
			ASSERT(rval == DDI_SUCCESS);

			/*
			 * Mark the driver idle if the NO_INVOL_FLAG
			 * is set. This is needed to make sure the
			 * parent is idle after the child detaches
			 * without calling pm_lower_power().
			 * Clear the NO_INVOL_FLAG.
			 * - also mark idle if a tape device has detached
			 */
			if (!(pshot->state & NO_INVOL_FLAG))
				break;
			mutex_enter(&pshot->lock);
			ASSERT(pshot->busy > 0);
			--pshot->busy;
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "%s%d:"
				    " ctl_detach_post: NO_INVOL:"
				    " idle for %s%d: busy = %d\n",
				    name, instance, childname,
				    childinstance, pshot->busy);
			}
			pshot->state &= ~NO_INVOL_FLAG;
			mutex_exit(&pshot->lock);
			rval = pm_idle_component(dip, 0);
			ASSERT(rval == DDI_SUCCESS);

			break;
		}

		ndi_devi_exit(dip, circ);

		return (rval);
	}

	case DDI_CTLOPS_BTOP:
	case DDI_CTLOPS_BTOPR:
	case DDI_CTLOPS_DVMAPAGESIZE:
	case DDI_CTLOPS_IOMIN:
	case DDI_CTLOPS_PTOB:
	default:
		/*
		 * The ops that we pass up (default).  We pass up memory
		 * allocation oriented ops that we receive - these may be
		 * associated with pseudo HBA drivers below us with target
		 * drivers below them that use ddi memory allocation
		 * interfaces like scsi_alloc_consistent_buf.
		 */
		return (ddi_ctlops(dip, rdip, ctlop, arg, result));
	}
}

/*ARGSUSED0*/
static int
pshot_power(dev_info_t *dip, int cmpt, int level)
{
	pshot_t *pshot;
	int instance = ddi_get_instance(dip);
	char *name = ddi_node_name(dip);
	int circ;
	int rv;

	pshot = ddi_get_soft_state(pshot_softstatep, instance);
	if (pshot == NULL) {

		return (DDI_FAILURE);
	}

	ndi_devi_enter(dip, &circ);

	/*
	 * set POWER_FLAG when power() is called.
	 * ioctl(DEVCT_PM_POWER) is a clear on read call.
	 */
	mutex_enter(&pshot->lock);
	pshot->state |= POWER_FLAG;
	/*
	 * refuse to power OFF if the component is busy
	 */
	if (pshot->busy != 0 && pshot->level > level) {
		cmn_err(CE_WARN, "%s%d: power: REFUSING POWER LEVEL CHANGE"
		    " (%d->%d), DEVICE NOT IDLE: busy = %d",
		    name, instance, pshot->level, level, pshot->busy);
		rv = DDI_FAILURE;
	} else {
		if (pshot_debug) {
			cmn_err(CE_CONT, "%s%d: power: comp %d (%d->%d)\n",
			    name, instance, cmpt, pshot->level, level);
		}
		pshot->level = level;
		rv = DDI_SUCCESS;
	}
	mutex_exit(&pshot->lock);

	ndi_devi_exit(dip, circ);

	return (rv);
}

/*ARGSUSED0*/
static int
pshot_bus_power(dev_info_t *dip, void *impl_arg, pm_bus_power_op_t op,
    void *arg, void *result)

{
	int 				ret;
	int 				instance = ddi_get_instance(dip);
	char				*name = ddi_node_name(dip);
	pshot_t 			*pshot;
	pm_bp_child_pwrchg_t		*bpc;
	pm_bp_nexus_pwrup_t		bpn;
	pm_bp_has_changed_t		*bphc;
	int				pwrup_res;
	int				ret_failed = 0;
	int				pwrup_res_failed = 0;

	pshot = ddi_get_soft_state(pshot_softstatep, instance);
	if (pshot == NULL) {

		return (DDI_FAILURE);
	}

	switch (op) {
	case BUS_POWER_PRE_NOTIFICATION:
		bpc = (pm_bp_child_pwrchg_t *)arg;
		if (pshot_debug) {
			cmn_err(CE_CONT, "%s%d: pre_bus_power:"
			    " %s%d comp %d (%d->%d)\n",
			    name, instance, ddi_node_name(bpc->bpc_dip),
			    ddi_get_instance(bpc->bpc_dip),
			    bpc->bpc_comp, bpc->bpc_olevel,
			    bpc->bpc_nlevel);
		}

		/*
		 * mark parent busy if old_level is either -1 or 0,
		 * and new level is == MAXPWR
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if ((bpc->bpc_comp == 0 && bpc->bpc_nlevel == MAXPWR &&
		    bpc->bpc_olevel <= 0) && (pshot->state & PM_SUPPORTED)) {
			mutex_enter(&pshot->lock);
			++(pshot->busy);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT,
				    "%s%d: pre_bus_power:"
				    " busy parent for %s%d (%d->%d): "
				    " busy = %d\n",
				    name, instance,
				    ddi_node_name(bpc->bpc_dip),
				    ddi_get_instance(bpc->bpc_dip),
				    bpc->bpc_olevel, bpc->bpc_nlevel,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			ret = pm_busy_component(dip, 0);
			ASSERT(ret == DDI_SUCCESS);
		}

		/*
		 * if new_level > 0, power up parent, if not already at
		 * MAXPWR, via pm_busop_bus_power
		 * - skip for the no-pm nexus (pshot@XXX,nopm)
		 */
		if (bpc->bpc_comp == 0 && bpc->bpc_nlevel > 0 &&
		    pshot->level < MAXPWR && (pshot->state & PM_SUPPORTED)) {
			/*
			 * stuff the bpn struct
			 */
			bpn.bpn_comp = 0;
			bpn.bpn_level = MAXPWR;
			bpn.bpn_private = bpc->bpc_private;
			bpn.bpn_dip = dip;

			/*
			 * ask pm to power parent up
			 */
			if (pshot_debug) {
				cmn_err(CE_CONT, "%s%d: pre_bus_power:"
				    " pm_busop_bus_power on parent for %s%d"
				    " (%d->%d): enter", name, instance,
				    ddi_node_name(bpc->bpc_dip),
				    ddi_get_instance(bpc->bpc_dip),
				    bpc->bpc_olevel, bpc->bpc_nlevel);
			}
			ret = pm_busop_bus_power(dip, impl_arg,
			    BUS_POWER_NEXUS_PWRUP, (void *)&bpn,
			    (void *)&pwrup_res);

			/*
			 * check the return status individually,
			 * idle parent and exit if either failed.
			 */
			if (ret != DDI_SUCCESS) {
				cmn_err(CE_WARN,
				    "%s%d: pre_bus_power:"
				    " pm_busop_bus_power FAILED (ret) FOR"
				    " %s%d (%d->%d)",
				    name, instance,
				    ddi_node_name(bpc->bpc_dip),
				    ddi_get_instance(bpc->bpc_dip),
				    bpc->bpc_olevel, bpc->bpc_nlevel);
				ret_failed = 1;
			}
			if (pwrup_res != DDI_SUCCESS) {
				cmn_err(CE_WARN,
				    "%s%d: pre_bus_power:"
				    " pm_busop_bus_power FAILED (pwrup_res)"
				    " FOR %s%d (%d->%d)",
				    name, instance,
				    ddi_node_name(bpc->bpc_dip),
				    ddi_get_instance(bpc->bpc_dip),
				    bpc->bpc_olevel, bpc->bpc_nlevel);
				pwrup_res_failed = 1;
			}
			if (ret_failed || pwrup_res_failed) {
				/*
				 * decrement the busy count if it
				 * had been incremented.
				 */
				if ((bpc->bpc_comp == 0 &&
				    bpc->bpc_nlevel == MAXPWR &&
				    bpc->bpc_olevel <= 0) &&
				    (pshot->state & PM_SUPPORTED)) {
					mutex_enter(&pshot->lock);
					ASSERT(pshot->busy > 0);
					--(pshot->busy);
					if (pshot_debug_busy) {
						cmn_err(CE_CONT, "%s%d:"
						    " pm_busop_bus_power"
						    " failed: idle parent for"
						    " %s%d (%d->%d):"
						    " busy = %d\n",
						    name, instance,
						    ddi_node_name(
						    bpc->bpc_dip),
						    ddi_get_instance(
						    bpc->bpc_dip),
						    bpc->bpc_olevel,
						    bpc->bpc_nlevel,
						    pshot->busy);
					}
					mutex_exit(&pshot->lock);
					ret = pm_idle_component(dip, 0);
					ASSERT(ret == DDI_SUCCESS);
				}
				return (DDI_FAILURE);

			} else {
				if (pshot_debug) {
					cmn_err(CE_CONT,
					    "%s%d: pre_bus_power:"
					    " pm_busop_bus_power on parent"
					    " for %s%d (%d->%d)\n",
					    name, instance,
					    ddi_node_name(bpc->bpc_dip),
					    ddi_get_instance(bpc->bpc_dip),
					    bpc->bpc_olevel, bpc->bpc_nlevel);
				}
			}
		}
		break;

	case BUS_POWER_POST_NOTIFICATION:
		bpc = (pm_bp_child_pwrchg_t *)arg;
		if (pshot_debug) {
			cmn_err(CE_CONT, "%s%d: post_bus_power:"
			    " %s%d comp %d (%d->%d) result %d\n",
			    name, instance, ddi_node_name(bpc->bpc_dip),
			    ddi_get_instance(bpc->bpc_dip),
			    bpc->bpc_comp, bpc->bpc_olevel,
			    bpc->bpc_nlevel, *(int *)result);
		}

		/*
		 * handle pm_busop_bus_power() failure case.
		 * mark parent idle if had been marked busy.
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if (*(int *)result != DDI_SUCCESS) {
			cmn_err(CE_WARN,
			    "pshot%d: post_bus_power_failed:"
			    " pm_busop_bus_power FAILED FOR %s%d (%d->%d)",
			    instance, ddi_node_name(bpc->bpc_dip),
			    ddi_get_instance(bpc->bpc_dip),
			    bpc->bpc_olevel, bpc->bpc_nlevel);

			if ((bpc->bpc_comp == 0 && bpc->bpc_nlevel == MAXPWR &&
			    bpc->bpc_olevel <= 0) &&
			    (pshot->state & PM_SUPPORTED)) {
				mutex_enter(&pshot->lock);
				ASSERT(pshot->busy > 0);
				--(pshot->busy);
				if (pshot_debug_busy) {
					cmn_err(CE_CONT, "%s%d:"
					    " post_bus_power_failed:"
					    " idle parent for %s%d"
					    " (%d->%d): busy = %d\n",
					    name, instance,
					    ddi_node_name(bpc->bpc_dip),
					    ddi_get_instance(bpc->bpc_dip),
					    bpc->bpc_olevel, bpc->bpc_nlevel,
					    pshot->busy);
				}
				mutex_exit(&pshot->lock);
				ret = pm_idle_component(dip, 0);
				ASSERT(ret == DDI_SUCCESS);
			}
		}

		/*
		 * Mark nexus idle when a child's comp 0
		 * is set to level 0 from level 1, 2, or 3 only.
		 * And only if result arg == DDI_SUCCESS.
		 * This will leave the parent busy when the child
		 * does not call pm_lower_power() on detach after
		 * unsetting the NO_LOWER_POWER flag.
		 * If so, need to notify the parent to mark itself
		 * idle anyway, else the no-involumtary-power-cycles
		 * test cases will report false passes!
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if ((bpc->bpc_comp == 0 && bpc->bpc_nlevel == 0 &&
		    !(bpc->bpc_olevel <= 0) &&
		    *(int *)result == DDI_SUCCESS) &&
		    (pshot->state & PM_SUPPORTED)) {
			mutex_enter(&pshot->lock);
			ASSERT(pshot->busy > 0);
			--(pshot->busy);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT,
				    "%s%d: post_bus_power:"
				    " idle parent for %s%d (%d->%d):"
				    " busy = %d\n", name, instance,
				    ddi_node_name(bpc->bpc_dip),
				    ddi_get_instance(bpc->bpc_dip),
				    bpc->bpc_olevel, bpc->bpc_nlevel,
				    pshot->busy);
			}
			mutex_exit(&pshot->lock);
			ret = pm_idle_component(dip, 0);
			ASSERT(ret == DDI_SUCCESS);
		}
		break;

	case BUS_POWER_HAS_CHANGED:
		bphc = (pm_bp_has_changed_t *)arg;
		if (pshot_debug) {
			cmn_err(CE_CONT, "%s%d: has_changed_bus_power:"
			    " %s%d comp %d (%d->%d) result %d\n",
			    name, instance, ddi_node_name(bphc->bphc_dip),
			    ddi_get_instance(bphc->bphc_dip),
			    bphc->bphc_comp, bphc->bphc_olevel,
			    bphc->bphc_nlevel, *(int *)result);
		}

		/*
		 * Mark nexus idle when a child's comp 0
		 * is set to level 0 from levels 1, 2, or 3 only.
		 *
		 * If powering up child leaf/nexus nodes via
		 * pm_power_has_changed() calls, first issue
		 * DEVCTL_PM_BUSY_COMP ioctl to mark parent busy
		 * before powering the parent up, then power up the
		 * child node.
		 * - skip if PM_SUPPORTED is not set (pshot@XXX,nopm)
		 */
		if ((bphc->bphc_comp == 0 && bphc->bphc_nlevel == 0 &&
		    !(bphc->bphc_olevel <= 0)) &&
		    pshot->state & PM_SUPPORTED) {
			mutex_enter(&pshot->lock);
			ASSERT(pshot->busy > 0);
			--(pshot->busy);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT,
				    "%s%d: has_changed_bus_power:"
				    " idle parent for %s%d (%d->%d):"
				    " busy = %d\n", name, instance,
				    ddi_node_name(bphc->bphc_dip),
				    ddi_get_instance(bphc->bphc_dip),
				    bphc->bphc_olevel,
				    bphc->bphc_nlevel, pshot->busy);
			}
			mutex_exit(&pshot->lock);
			ret = pm_idle_component(dip, 0);
			ASSERT(ret == DDI_SUCCESS);
		}
		break;

	default:
		return (pm_busop_bus_power(dip, impl_arg, op, arg, result));

	}

	return (DDI_SUCCESS);
}

static int
pshot_initchild(dev_info_t *dip, dev_info_t *child)
{
	char	name[64];
	char	*bus_addr;
	char	*c_nodename;
	int	bus_id;
	dev_info_t *enum_child;
	int	enum_base;
	int	enum_extent;


	/* check for bus_enum node */

#ifdef	NOT_USED
	if (impl_ddi_merge_child(child) != DDI_SUCCESS)
		return (DDI_FAILURE);
#endif

	enum_base = ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
	    "busid_ebase", 0);

	enum_extent = ddi_prop_get_int(DDI_DEV_T_ANY, child,
	    DDI_PROP_DONTPASS, "busid_range", 0);

	/*
	 * bus enumeration node
	 */
	if ((enum_base != 0) && (enum_extent != 0))	{
		c_nodename = ddi_node_name(child);
		bus_id = enum_base;
		for (; bus_id < enum_extent; bus_id++) {
			if (ndi_devi_alloc(dip, c_nodename, DEVI_PSEUDO_NODEID,
			    &enum_child) != NDI_SUCCESS)
				return (DDI_FAILURE);

			(void) sprintf(name, "%d", bus_id);
			if (ndi_prop_update_string(DDI_DEV_T_NONE, enum_child,
			    "bus-addr", name) != DDI_PROP_SUCCESS) {
				(void) ndi_devi_free(enum_child);
				return (DDI_FAILURE);
			}

			if (ndi_devi_online(enum_child, 0) !=
			    DDI_SUCCESS) {
				(void) ndi_devi_free(enum_child);
				return (DDI_FAILURE);
			}
		}
		/*
		 * fail the enumeration node itself
		 */
		return (DDI_FAILURE);
	}

	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child, 0, "bus-addr",
	    &bus_addr) != DDI_PROP_SUCCESS) {
		cmn_err(CE_WARN, "pshot_initchild: bus-addr not defined (%s)",
		    ddi_node_name(child));
		return (DDI_NOT_WELL_FORMED);
	}

	if (strlen(bus_addr) == 0) {
		cmn_err(CE_WARN, "pshot_initchild: NULL bus-addr (%s)",
		    ddi_node_name(child));
		ddi_prop_free(bus_addr);
		return (DDI_FAILURE);
	}

	if (strncmp(bus_addr, "failinit", 8) == 0) {
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s forced INITCHILD failure\n",
			    ddi_get_instance(dip), bus_addr);
		ddi_prop_free(bus_addr);
		return (DDI_FAILURE);
	}

	if (pshot_log) {
		cmn_err(CE_CONT, "initchild %s%d/%s@%s\n",
		    ddi_get_name(dip), ddi_get_instance(dip),
		    ddi_node_name(child), bus_addr);
	}

	ddi_set_name_addr(child, bus_addr);
	ddi_prop_free(bus_addr);
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
pshot_uninitchild(dev_info_t *dip, dev_info_t *child)
{
	ddi_set_name_addr(child, NULL);
	return (DDI_SUCCESS);
}


/*
 * devctl IOCTL support
 */
/* ARGSUSED */
static int
pshot_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
	int instance;
	pshot_t *pshot;

	if (otyp != OTYP_CHR)
		return (EINVAL);

	instance = pshot_minor_decode_inst(getminor(*devp));
	if ((pshot = ddi_get_soft_state(pshot_softstatep, instance)) == NULL)
		return (ENXIO);

	/*
	 * Access is currently determined on a per-instance basis.
	 * If we want per-node, then need to add state and lock members to
	 * pshot_minor_t
	 */
	mutex_enter(&pshot->lock);
	if (((flags & FEXCL) && (pshot->state & IS_OPEN)) ||
	    (!(flags & FEXCL) && (pshot->state & IS_OPEN_EXCL))) {
		mutex_exit(&pshot->lock);
		return (EBUSY);
	}
	pshot->state |= IS_OPEN;
	if (flags & FEXCL)
		pshot->state |= IS_OPEN_EXCL;

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d open\n", instance);

	mutex_exit(&pshot->lock);
	return (0);
}

/*
 * pshot_close
 */
/* ARGSUSED */
static int
pshot_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
	int instance;
	pshot_t *pshot;

	if (otyp != OTYP_CHR)
		return (EINVAL);

	instance = pshot_minor_decode_inst(getminor(dev));
	if ((pshot = ddi_get_soft_state(pshot_softstatep, instance)) == NULL)
		return (ENXIO);

	mutex_enter(&pshot->lock);
	pshot->state &= ~(IS_OPEN | IS_OPEN_EXCL);
	mutex_exit(&pshot->lock);
	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d closed\n", instance);
	return (0);
}


/*
 * pshot_ioctl: redirects to appropriate command handler based on various
 * 	criteria
 */
/* ARGSUSED */
static int
pshot_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
	pshot_t *pshot;
	int instance;
	minor_t nodenum;
	char *nodename;

	instance = pshot_minor_decode_inst(getminor(dev));
	if ((pshot = ddi_get_soft_state(pshot_softstatep, instance)) == NULL)
		return (ENXIO);

	nodenum = pshot_minor_decode_nodenum(getminor(dev));
	nodename = pshot->nodes[nodenum].name;

	if (pshot_debug)
		cmn_err(CE_CONT,
		    "pshot%d ioctl: dev=%p, cmd=%x, arg=%p, mode=%x\n",
		    instance, (void *)dev, cmd, (void *)arg, mode);

	if (strcmp(nodename, PSHOT_NODENAME_DEVCTL) == 0)
		return (pshot_devctl(pshot, nodenum, cmd, arg, mode, credp,
		    rvalp));

	if (strcmp(nodename, PSHOT_NODENAME_TESTCTL) == 0)
		return (pshot_testctl(pshot, nodenum, cmd, arg, mode, credp,
		    rvalp));

	cmn_err(CE_WARN, "pshot_ioctl: unmatched nodename on minor %u",
	    pshot->nodes[nodenum].minor);
	return (ENXIO);
}


/*
 * pshot_devctl: handle DEVCTL operations
 */
/* ARGSUSED */
static int
pshot_devctl(pshot_t *pshot, minor_t nodenum, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp)
{
	dev_info_t *self;
	dev_info_t *child = NULL;
	struct devctl_iocdata *dcp;
	uint_t state;
	int rv = 0;
	uint_t flags;
	int instance;
	int i;
	int ret;

	self = pshot->dip;

	flags = (pshot_devctl_debug) ? NDI_DEVI_DEBUG : 0;
	instance = pshot->instance;

	/*
	 * We can use the generic implementation for these ioctls
	 */
	for (i = 0; pshot_devctls[i].ioctl_int != 0; i++) {
		if (pshot_devctls[i].ioctl_int == cmd) {
			if (pshot_debug)
				cmn_err(CE_CONT, "pshot%d devctl: %s",
				    instance, pshot_devctls[i].ioctl_char);
		}
	}
	switch (cmd) {
	case DEVCTL_DEVICE_GETSTATE:
	case DEVCTL_DEVICE_ONLINE:
	case DEVCTL_DEVICE_OFFLINE:
	case DEVCTL_DEVICE_REMOVE:
	case DEVCTL_BUS_GETSTATE:
	case DEVCTL_BUS_DEV_CREATE:
		rv = ndi_devctl_ioctl(self, cmd, arg, mode, flags);
		if (pshot_debug && rv != 0) {
			cmn_err(CE_CONT, "pshot%d ndi_devctl_ioctl:"
			    " failed, rv = %d", instance, rv);
		}

		return (rv);
	}

	/*
	 * read devctl ioctl data
	 */
	if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
		return (EFAULT);

	switch (cmd) {

	case DEVCTL_DEVICE_RESET:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_DEVICE_RESET\n", instance);
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_DEV_RESET,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	case DEVCTL_BUS_QUIESCE:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_BUS_QUIESCE\n", instance);
		if (ndi_get_bus_state(self, &state) == NDI_SUCCESS) {
			if (state == BUS_QUIESCED) {
				break;
			}
			(void) ndi_set_bus_state(self, BUS_QUIESCED);
		}
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_QUIESCE,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);

		break;

	case DEVCTL_BUS_UNQUIESCE:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_BUS_UNQUIESCE\n", instance);
		if (ndi_get_bus_state(self, &state) == NDI_SUCCESS) {
			if (state == BUS_ACTIVE) {
				break;
			}
		}

		/*
		 * quiesce the bus through bus-specific means
		 */
		(void) ndi_set_bus_state(self, BUS_ACTIVE);
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_UNQUIESCE,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	case DEVCTL_BUS_RESET:
	case DEVCTL_BUS_RESETALL:
		/*
		 * no reset support for the pseudo bus
		 * but if there were....
		 */
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_RESET,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	/*
	 * PM related ioctls
	 */
	case DEVCTL_PM_BUSY_COMP:
		/*
		 * mark component 0 busy.
		 * Keep track of ioctl updates to the busy count
		 * via pshot->busy_ioctl.
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_BUSY_COMP\n", instance);
		}
		mutex_enter(&pshot->lock);
		++(pshot->busy);
		++(pshot->busy_ioctl);
		if (pshot_debug_busy) {
			cmn_err(CE_CONT, "pshot%d:"
			    " DEVCTL_PM_BUSY_COMP comp 0 busy"
			    " %d busy_ioctl %d\n", instance, pshot->busy,
			    pshot->busy_ioctl);
		}
		mutex_exit(&pshot->lock);
		ret = pm_busy_component(pshot->dip, 0);
		ASSERT(ret == DDI_SUCCESS);

		break;

	case DEVCTL_PM_BUSY_COMP_TEST:
		/*
		 * test bus's busy state
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_BUSY_COMP_TEST\n", instance);
		}
		mutex_enter(&pshot->lock);
		state = pshot->busy;
		if (copyout(&state, dcp->cpyout_buf,
		    sizeof (uint_t)) != 0) {
			cmn_err(CE_WARN, "pshot%d devctl:"
			    " DEVCTL_PM_BUSY_COMP_TEST: copyout failed",
			    instance);
			rv = EINVAL;
		}
		if (pshot_debug_busy) {
			cmn_err(CE_CONT, "pshot%d: DEVCTL_PM_BUSY_COMP_TEST:"
			    " comp 0 busy %d busy_ioctl %d\n", instance,
			    state, pshot->busy_ioctl);
		}
		mutex_exit(&pshot->lock);
		break;

	case DEVCTL_PM_IDLE_COMP:
		/*
		 * mark component 0 idle.
		 * NOP if pshot->busy_ioctl <= 0.
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_IDLE_COMP\n", instance);
		}
		mutex_enter(&pshot->lock);
		if (pshot->busy_ioctl > 0) {
			ASSERT(pshot->busy > 0);
			--(pshot->busy);
			--(pshot->busy_ioctl);
			if (pshot_debug_busy) {
				cmn_err(CE_CONT, "pshot%d:"
				    " DEVCTL_PM_IDLE_COM: comp 0"
				    " busy %d busy_ioctl %d\n", instance,
				    pshot->busy, pshot->busy_ioctl);
			}
			mutex_exit(&pshot->lock);
			ret = pm_idle_component(pshot->dip, 0);
			ASSERT(ret == DDI_SUCCESS);

		} else {
			mutex_exit(&pshot->lock);
		}
		break;

	case DEVCTL_PM_RAISE_PWR:
		/*
		 * raise component 0 to full power level MAXPWR via a
		 * pm_raise_power() call
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_RAISE_PWR\n", instance);
		}
		if (pm_raise_power(pshot->dip, 0, MAXPWR) != DDI_SUCCESS) {
			rv = EINVAL;
		} else {
			mutex_enter(&pshot->lock);
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " DEVCTL_PM_RAISE_POWER: comp 0"
				    " to level %d\n", instance, pshot->level);
			}
			mutex_exit(&pshot->lock);
		}
		break;

	case DEVCTL_PM_LOWER_PWR:
		/*
		 * pm_lower_power() call for negative testing
		 * expected to fail.
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_LOWER_PWR\n", instance);
		}
		if (pm_lower_power(pshot->dip, 0, 0) != DDI_SUCCESS) {
			rv = EINVAL;
		} else {
			mutex_enter(&pshot->lock);
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " DEVCTL_PM_LOWER_POWER comp 0"
				    " to level %d\n", instance, pshot->level);
			}
			mutex_exit(&pshot->lock);
		}
		break;

	case DEVCTL_PM_CHANGE_PWR_LOW:
		/*
		 * inform the PM framework that component 0 has changed
		 * power level to 0 via a pm_power_has_changed() call
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_CHANGE_PWR_LOW\n", instance);
		}
		mutex_enter(&pshot->lock);
		pshot->level = 0;
		if (pm_power_has_changed(pshot->dip, 0, 0) != DDI_SUCCESS) {
			rv = EINVAL;
		} else {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " DEVCTL_PM_CHANGE_PWR_LOW comp 0 to"
				    " level %d\n", instance, pshot->level);
			}
		}
		mutex_exit(&pshot->lock);
		break;

	case DEVCTL_PM_CHANGE_PWR_HIGH:
		/*
		 * inform the PM framework that component 0 has changed
		 * power level to MAXPWR via a pm_power_has_changed() call
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_CHANGE_PWR_HIGH\n", instance);
		}
		mutex_enter(&pshot->lock);
		pshot->level = MAXPWR;
		if (pm_power_has_changed(pshot->dip, 0, MAXPWR)
		    != DDI_SUCCESS) {
			rv = EINVAL;
		} else {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " DEVCTL_PM_CHANGE_PWR_HIGH comp 0 to"
				    " level %d\n", instance, pshot->level);
			}
		}
		mutex_exit(&pshot->lock);
		break;

	case DEVCTL_PM_POWER:
		/*
		 * test if the pshot_power() routine has been called,
		 * then clear
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_POWER\n", instance);
		}
		mutex_enter(&pshot->lock);
		state = (pshot->state & POWER_FLAG) ? 1 : 0;
		if (copyout(&state, dcp->cpyout_buf,
		    sizeof (uint_t)) != 0) {
			cmn_err(CE_WARN, "pshot%d devctl:"
			    " DEVCTL_PM_POWER: copyout failed",
			    instance);
			rv = EINVAL;
		}
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: DEVCTL_PM_POWER:"
			    " POWER_FLAG = %d\n", instance, state);
		}
		pshot->state &= ~POWER_FLAG;
		mutex_exit(&pshot->lock);
		break;

	case DEVCTL_PM_FAIL_SUSPEND:
		/*
		 * fail DDI_SUSPEND
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_FAIL_SUSPEND\n", instance);
		}
		mutex_enter(&pshot->lock);
		pshot->state |= FAIL_SUSPEND_FLAG;
		mutex_exit(&pshot->lock);
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: DEVCTL_PM_FAIL_SUSPEND\n",
			    instance);
		}
		break;

	case DEVCTL_PM_BUS_STRICT_TEST:
		/*
		 * test the STRICT_PARENT flag:
		 *	set => STRICT PARENT
		 *	not set => INVOLVED PARENT
		 */
		mutex_enter(&pshot->lock);
		state = (pshot->state & STRICT_PARENT) ? 1 : 0;
		if (copyout(&state, dcp->cpyout_buf,
		    sizeof (uint_t)) != 0) {
			cmn_err(CE_WARN, "pshot%d devctl:"
			    " DEVCTL_PM_BUS_STRICT_TEST: copyout failed",
			    instance);
			rv = EINVAL;
		}
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_BUS_STRICT_TEST: type = %s\n",
			    instance, ((state == 0) ? "INVOLVED" : "STRICT"));
		}
		mutex_exit(&pshot->lock);
		break;

	case DEVCTL_PM_BUS_NO_INVOL:
		/*
		 * Set the NO_INVOL_FLAG flag to
		 * notify the driver that the child will not
		 * call pm_lower_power() on detach.
		 * The driver needs to mark itself idle twice
		 * during DDI_CTLOPS_DETACH (post).
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d devctl:"
			    " DEVCTL_PM_BUS_NO_INVOL\n", instance);
		}
		mutex_enter(&pshot->lock);
		pshot->state |= NO_INVOL_FLAG;
		mutex_exit(&pshot->lock);
		break;

	default:
		rv = ENOTTY;
	}

	ndi_dc_freehdl(dcp);
	return (rv);
}


/*
 * pshot_testctl: handle other test operations
 *	- If <cmd> is a DEVCTL cmd, then <arg> is a dev_t indicating which
 *	  child to direct the DEVCTL to, if applicable;
 *	  furthermore, any cmd here can be sent by layered ioctls (unlike
 *	  those to pshot_devctl() which must come from userland)
 */
/* ARGSUSED */
static int
pshot_testctl(pshot_t *pshot, minor_t nodenum, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp)
{
	dev_info_t *self;
	dev_info_t *child = NULL;
	uint_t state;
	int rv = 0;
	int instance;
	int i;

	/* uint_t flags; */

	/* flags = (pshot_devctl_debug) ? NDI_DEVI_DEBUG : 0; */
	self = pshot->dip;
	instance = pshot->instance;

	if (cmd & DEVCTL_IOC) {
		child = e_ddi_hold_devi_by_dev((dev_t)arg, 0);
	}

	for (i = 0; pshot_devctls[i].ioctl_int != 0; i++) {
		if (pshot_devctls[i].ioctl_int == cmd) {
			if (pshot_debug)
				cmn_err(CE_CONT, "pshot%d devctl: %s",
				    instance, pshot_devctls[i].ioctl_char);
		}
	}
	switch (cmd) {
	case DEVCTL_DEVICE_RESET:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d testctl:"
			    " DEVCTL_PM_POWER\n", instance);
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_DEV_RESET,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	case DEVCTL_BUS_QUIESCE:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d testctl:"
			    " DEVCTL_PM_POWER\n", instance);
		if (ndi_get_bus_state(self, &state) == NDI_SUCCESS) {
			if (state == BUS_QUIESCED) {
				break;
			}
			(void) ndi_set_bus_state(self, BUS_QUIESCED);
		}
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_QUIESCE,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);

		break;

	case DEVCTL_BUS_UNQUIESCE:
		if (pshot_debug)
			cmn_err(CE_CONT, "pshot%d testctl:"
			    " DEVCTL_PM_POWER\n", instance);
		if (ndi_get_bus_state(self, &state) == NDI_SUCCESS) {
			if (state == BUS_ACTIVE) {
				break;
			}
		}

		/*
		 * quiesce the bus through bus-specific means
		 */
		(void) ndi_set_bus_state(self, BUS_ACTIVE);
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_UNQUIESCE,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	case DEVCTL_BUS_RESET:
	case DEVCTL_BUS_RESETALL:
		/*
		 * no reset support for the pseudo bus
		 * but if there were....
		 */
		rv = pshot_event(pshot, PSHOT_EVENT_TAG_BUS_RESET,
		    child, (void *)self);
		ASSERT(rv == NDI_SUCCESS);
		break;

	default:
		rv = ENOTTY;
	}

	if (child != NULL)
		ddi_release_devi(child);
	return (rv);
}


static int
pshot_get_eventcookie(dev_info_t *dip, dev_info_t *rdip,
	char *eventname, ddi_eventcookie_t *event_cookiep)
{
	int	instance = ddi_get_instance(dip);
	pshot_t *pshot = ddi_get_soft_state(pshot_softstatep, instance);

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d: "
		    "pshot_get_eventcookie:\n\t"
		    "dip = 0x%p rdip = 0x%p (%s/%d) eventname = %s\n",
		    instance, (void *)dip, (void *)rdip,
		    ddi_node_name(rdip), ddi_get_instance(rdip),
		    eventname);


	return (ndi_event_retrieve_cookie(pshot->ndi_event_hdl,
	    rdip, eventname, event_cookiep, NDI_EVENT_NOPASS));
}

static int
pshot_add_eventcall(dev_info_t *dip, dev_info_t *rdip,
	ddi_eventcookie_t cookie,
	void (*callback)(), void *arg, ddi_callback_id_t *cb_id)
{
	int	instance = ddi_get_instance(dip);
	pshot_t *pshot = ddi_get_soft_state(pshot_softstatep, instance);

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d: "
		    "pshot_add_eventcall:\n\t"
		    "dip = 0x%p rdip = 0x%p (%s%d)\n\tcookie = 0x%p (%s)\n\t"
		    "cb = 0x%p, arg = 0x%p\n",
		    instance, (void *)dip, (void *)rdip,
		    ddi_node_name(rdip), ddi_get_instance(rdip), (void *)cookie,
		    NDI_EVENT_NAME(cookie), (void *)callback, arg);

	/* add callback to our event handle */
	return (ndi_event_add_callback(pshot->ndi_event_hdl, rdip,
	    cookie, callback, arg, NDI_SLEEP, cb_id));
}

static int
pshot_remove_eventcall(dev_info_t *dip, ddi_callback_id_t cb_id)
{

	ndi_event_callbacks_t *cb = (ndi_event_callbacks_t *)cb_id;

	int instance = ddi_get_instance(dip);
	pshot_t *pshot = ddi_get_soft_state(pshot_softstatep, instance);

	ASSERT(cb);

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d: "
		    "pshot_remove_eventcall:\n\t"
		    "dip = 0x%p rdip = 0x%p (%s%d)\n\tcookie = 0x%p (%s)\n",
		    instance, (void *)dip, (void *)cb->ndi_evtcb_dip,
		    ddi_node_name(cb->ndi_evtcb_dip),
		    ddi_get_instance(cb->ndi_evtcb_dip),
		    (void *)cb->ndi_evtcb_cookie,
		    NDI_EVENT_NAME(cb->ndi_evtcb_cookie));

	return (ndi_event_remove_callback(pshot->ndi_event_hdl, cb_id));
}

static int
pshot_post_event(dev_info_t *dip, dev_info_t *rdip,
	ddi_eventcookie_t cookie, void *impl_data)
{
	int	instance = ddi_get_instance(dip);
	pshot_t *pshot = ddi_get_soft_state(pshot_softstatep, instance);

	if (pshot_debug) {
		if (rdip) {
			cmn_err(CE_CONT, "pshot%d: "
			    "pshot_post_event:\n\t"
			    "dip = 0x%p rdip = 0x%p (%s%d\n\t"
			    "cookie = 0x%p (%s)\n\tbus_impl = 0x%p\n",
			    instance, (void *)dip, (void *)rdip,
			    ddi_node_name(rdip), ddi_get_instance(rdip),
			    (void *)cookie,
			    NDI_EVENT_NAME(cookie), impl_data);
		} else {
			cmn_err(CE_CONT, "pshot%d: "
			    "pshot_post_event:\n\t"
			    "dip = 0x%p cookie = 0x%p (%s) bus_impl = 0x%p\n",
			    instance, (void *)dip, (void *)cookie,
			    NDI_EVENT_NAME(cookie), impl_data);
		}
	}

	/*  run callbacks for this event */
	return (ndi_event_run_callbacks(pshot->ndi_event_hdl, rdip,
	    cookie, impl_data));
}

/*
 * the nexus driver will generate events
 * that need to go to children
 */
static int
pshot_event(pshot_t *pshot, int event_tag, dev_info_t *child,
	void *bus_impldata)
{
	ddi_eventcookie_t cookie = ndi_event_tag_to_cookie(
	    pshot->ndi_event_hdl, event_tag);

	if (pshot_debug) {
		if (child) {
			cmn_err(CE_CONT, "pshot%d: "
			    "pshot_event: event_tag = 0x%x (%s)\n\t"
			    "child = 0x%p (%s%d) bus_impl = 0x%p (%s%d)\n",
			    pshot->instance, event_tag,
			    ndi_event_tag_to_name(pshot->ndi_event_hdl,
			    event_tag),
			    (void *)child, ddi_node_name(child),
			    ddi_get_instance(child), bus_impldata,
			    ddi_node_name((dev_info_t *)bus_impldata),
			    ddi_get_instance((dev_info_t *)bus_impldata));
		} else {
			cmn_err(CE_CONT, "pshot%d: "
			    "pshot_event: event_tag = 0x%x (%s)\n\t"
			    "child = NULL,  bus_impl = 0x%p (%s%d)\n",
			    pshot->instance, event_tag,
			    ndi_event_tag_to_name(pshot->ndi_event_hdl,
			    event_tag),
			    bus_impldata,
			    ddi_node_name((dev_info_t *)bus_impldata),
			    ddi_get_instance((dev_info_t *)bus_impldata));
		}
	}

	return (ndi_event_run_callbacks(pshot->ndi_event_hdl,
	    child, cookie, bus_impldata));
}


/*
 * the pshot driver event notification callback
 */
static void
pshot_event_cb(dev_info_t *dip, ddi_eventcookie_t cookie,
	void *arg, void *bus_impldata)
{
	pshot_t *pshot = (pshot_t *)arg;
	int event_tag;

	/* look up the event */
	event_tag = NDI_EVENT_TAG(cookie);

	if (pshot_debug) {
		cmn_err(CE_CONT, "pshot%d: "
		    "pshot_event_cb:\n\t"
		    "dip = 0x%p cookie = 0x%p (%s), tag = 0x%x\n\t"
		    "arg = 0x%p bus_impl = 0x%p (%s%d)\n",
		    pshot->instance, (void *)dip, (void *)cookie,
		    NDI_EVENT_NAME(cookie), event_tag, arg, bus_impldata,
		    ddi_node_name((dev_info_t *)bus_impldata),
		    ddi_get_instance((dev_info_t *)bus_impldata));
	}

	switch (event_tag) {
	case PSHOT_EVENT_TAG_OFFLINE:
	case PSHOT_EVENT_TAG_BUS_RESET:
	case PSHOT_EVENT_TAG_BUS_QUIESCE:
	case PSHOT_EVENT_TAG_BUS_UNQUIESCE:
		/* notify all subscribers of the this event */
		(void) ndi_event_run_callbacks(pshot->ndi_event_hdl,
		    NULL, cookie, bus_impldata);
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: event=%s\n\t"
			    "pshot_event_cb\n", pshot->instance,
			    NDI_EVENT_NAME(cookie));
		}
		/*FALLTHRU*/
	case PSHOT_EVENT_TAG_TEST_POST:
	case PSHOT_EVENT_TAG_DEV_RESET:
	default:
		return;
	}
}

static int
pshot_bus_config(dev_info_t *parent, uint_t flags,
    ddi_bus_config_op_t op, void *arg, dev_info_t **childp)
{
	int		rval;
	char		*devname;
	char		*devstr, *cname, *caddr;
	int		devstrlen;
	int		circ;
	pshot_t		*pshot;
	int		instance = ddi_get_instance(parent);

	if (pshot_debug) {
		flags |= NDI_DEVI_DEBUG;
		cmn_err(CE_CONT,
		    "pshot%d: bus_config %s flags=0x%x\n",
		    ddi_get_instance(parent),
		    (op == BUS_CONFIG_ONE) ? (char *)arg : "", flags);
	}

	pshot = ddi_get_soft_state(pshot_softstatep, instance);
	if (pshot == NULL) {

		return (NDI_FAILURE);
	}

	/*
	 * Hold the nexus across the bus_config
	 */
	ndi_devi_enter(parent, &circ);

	switch (op) {
	case BUS_CONFIG_ONE:

		/*
		 * lookup and hold child device, create if not found
		 */
		devname = (char *)arg;
		devstrlen = strlen(devname) + 1;
		devstr = i_ddi_strdup(devname, KM_SLEEP);
		i_ddi_parse_name(devstr, &cname, &caddr, NULL);

		/*
		 * The framework ensures that the node has
		 * a name but each nexus is responsible for
		 * the bus address name space.  This driver
		 * requires that a bus address be specified,
		 * as will most nexus drivers.
		 */
		ASSERT(cname && strlen(cname) > 0);
		if (caddr == NULL || strlen(caddr) == 0) {
			cmn_err(CE_WARN,
			    "pshot%d: malformed name %s (no bus address)",
			    ddi_get_instance(parent), devname);
			kmem_free(devstr, devstrlen);
			ndi_devi_exit(parent, circ);
			return (NDI_FAILURE);
		}

		/*
		 * Handle a few special cases for testing purposes
		 */
		rval = pshot_bus_config_test_specials(parent,
		    devname, cname, caddr);

		if (rval == NDI_SUCCESS) {
			/*
			 * Set up either a leaf or nexus device
			 */
			if (strcmp(cname, "pshot") == 0) {
				rval = pshot_bus_config_setup_nexus(parent,
				    cname, caddr);
			} else {
				rval = pshot_bus_config_setup_leaf(parent,
				    cname, caddr);
			}
		}

		kmem_free(devstr, devstrlen);
		break;

	case BUS_CONFIG_DRIVER:
	case BUS_CONFIG_ALL:
		rval = NDI_SUCCESS;
		break;

	default:
		rval = NDI_FAILURE;
		break;
	}

	if (rval == NDI_SUCCESS)
		rval = ndi_busop_bus_config(parent, flags, op, arg, childp, 0);

	ndi_devi_exit(parent, circ);

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d: bus_config %s\n",
		    ddi_get_instance(parent),
		    (rval == NDI_SUCCESS) ? "ok" : "failed");

	return (rval);
}

static int
pshot_bus_unconfig(dev_info_t *parent, uint_t flags,
    ddi_bus_config_op_t op, void *arg)
{
	major_t		major;
	int		rval = NDI_SUCCESS;
	int		circ;

	if (pshot_debug) {
		flags |= NDI_DEVI_DEBUG;
		cmn_err(CE_CONT,
		    "pshot%d: bus_unconfig %s flags=0x%x\n",
		    ddi_get_instance(parent),
		    (op == BUS_UNCONFIG_ONE) ? (char *)arg : "", flags);
	}

	/*
	 * Hold the nexus across the bus_unconfig
	 */
	ndi_devi_enter(parent, &circ);

	switch (op) {
	case BUS_UNCONFIG_ONE:
		/*
		 * Nothing special required here
		 */
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: bus_unconfig:"
			    " BUS_UNCONFIG_ONE\n", ddi_get_instance(parent));
		}
		break;

	case BUS_UNCONFIG_DRIVER:
		if (pshot_debug > 0) {
			major = (major_t)(uintptr_t)arg;
			cmn_err(CE_CONT,
			    "pshot%d: BUS_UNCONFIG_DRIVER: %s\n",
			    ddi_get_instance(parent),
			    ddi_major_to_name(major));
		}
		break;

	case BUS_UNCONFIG_ALL:
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: bus_unconfig:"
			    " BUS_UNCONFIG_ALL\n", ddi_get_instance(parent));
		}
		break;

	default:
		if (pshot_debug) {
			cmn_err(CE_CONT, "pshot%d: bus_unconfig: DEFAULT\n",
			    ddi_get_instance(parent));
		}
		rval = NDI_FAILURE;
	}

	if (rval == NDI_SUCCESS)
		rval = ndi_busop_bus_unconfig(parent, flags, op, arg);

	ndi_devi_exit(parent, circ);

	if (pshot_debug)
		cmn_err(CE_CONT, "pshot%d: bus_unconfig %s\n",
		    ddi_get_instance(parent),
		    (rval == NDI_SUCCESS) ? "ok" : "failed");

	return (rval);
}

static dev_info_t *
pshot_findchild(dev_info_t *pdip, char *cname, char *caddr)
{
	dev_info_t *dip;
	char *addr;

	ASSERT(cname != NULL && caddr != NULL);
	ASSERT(DEVI_BUSY_OWNED(pdip));

	for (dip = ddi_get_child(pdip); dip != NULL;
	    dip = ddi_get_next_sibling(dip)) {
		if (strcmp(cname, ddi_node_name(dip)) != 0)
			continue;

		if ((addr = ddi_get_name_addr(dip)) == NULL) {
			if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0,
			    "bus-addr", &addr) == DDI_PROP_SUCCESS) {
				if (strcmp(caddr, addr) == 0) {
					ddi_prop_free(addr);
					return (dip);
				}
				ddi_prop_free(addr);
			}
		} else {
			if (strcmp(caddr, addr) == 0)
				return (dip);
		}
	}

	return (NULL);
}

static void
pshot_nexus_properties(dev_info_t *parent, dev_info_t *child, char *cname,
    char *caddr)
{
	char *extension;

	/*
	 * extract the address extension
	 */
	extension = strstr(caddr, ",");
	if (extension != NULL) {
		++extension;
	} else {
		extension = "null";
	}

	/*
	 * Create the "pm-want-child-notification?" property for all
	 * nodes that do not have the "pm_strict" or "nopm_strict"
	 * extension
	 */
	if (strcmp(extension, "pm_strict") != 0 &&
	    strcmp(extension, "nopm_strict") != 0) {
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "pm-want-child-notification?") == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " nexus_properties:\n\tcreate the"
				    " \"pm-want-child-notification?\""
				    " property for %s@%s\n",
				    ddi_get_instance(parent), cname, caddr);
			}
			if (ddi_prop_create(DDI_DEV_T_NONE, child, 0,
			    "pm-want-child-notification?", NULL, 0)
			    != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "pshot%d:"
				    " nexus_properties:\n\tunable to create"
				    " the \"pm-want-child-notification?\""
				    " property for %s@%s",
				    ddi_get_instance(parent), cname, caddr);
			}
		}
	}

	/*
	 * Create the "no-pm-components" property for all nodes
	 * with extension "nopm" or "nopm_strict"
	 */
	if (strcmp(extension, "nopm") == 0 ||
	    strcmp(extension, "nopm_strict") == 0) {
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "no-pm-components") == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " nexus_properties:\n\tcreate the"
				    " \"no-pm-components\""
				    " property for %s@%s\n",
				    ddi_get_instance(parent), cname, caddr);
			}
			if (ddi_prop_create(DDI_DEV_T_NONE, child, 0,
			    "no-pm-components", NULL, 0)
			    != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "pshot%d:"
				    " nexus_properties:\n\tunable to create"
				    " the \"no-pm-components\""
				    " property for %s@%s",
				    ddi_get_instance(parent), cname, caddr);
			}
		}
	}
}

static void
pshot_leaf_properties(dev_info_t *parent, dev_info_t *child, char *cname,
    char *caddr)
{
	char *extension;

	/*
	 * extract the address extension
	 */
	extension = strstr(caddr, ",");
	if (extension != NULL) {
		++extension;
	} else {
		extension = "null";
	}

	/*
	 * Create the "no-involuntary-power-cycles" property for
	 * all leaf nodes with extension "no_invol"
	 */
	if (strcmp(extension, "no_invol") == 0) {
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "no-involuntary-power-cycles") == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " leaf_properties:\n\tcreate the"
				    " \"no-involuntary-power-cycles\""
				    " property for %s@%s\n",
				    ddi_get_instance(parent), cname, caddr);
			}
			if (ddi_prop_create(DDI_DEV_T_NONE, child,
			    DDI_PROP_CANSLEEP,
			    "no-involuntary-power-cycles", NULL, 0)
			    != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "pshot%d:"
				    " leaf_properties:\n\tunable to create the"
				    " \"no-involuntary-power-cycles\""
				    " property for %s@%s",
				    ddi_get_instance(parent), cname, caddr);
			}
		}
	}

	/*
	 * Create the "dependency-property" property for all leaf
	 * nodes with extension "dep_prop"
	 * to be used with the PM_ADD_DEPENDENT_PROPERTY ioctl
	 */
	if (strcmp(extension, "dep_prop") == 0) {
		if (ddi_prop_exists(DDI_DEV_T_ANY, child,
		    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
		    "dependency-property") == 0) {
			if (pshot_debug) {
				cmn_err(CE_CONT, "pshot%d:"
				    " leaf_properties:\n\tcreate the"
				    " \"dependency-property\""
				    " property for %s@%s\n",
				    ddi_get_instance(parent), cname, caddr);
			}
			if (ddi_prop_create(DDI_DEV_T_NONE, child,
			    DDI_PROP_CANSLEEP, "dependency-property", NULL, 0)
			    != DDI_PROP_SUCCESS) {
				cmn_err(CE_WARN, "pshot%d:"
				    " leaf_properties:\n\tunable to create the"
				    " \"dependency-property\" property for"
				    " %s@%s", ddi_get_instance(parent),
				    cname, caddr);
			}
		}
	}
}

/*
 * BUS_CONFIG_ONE: setup a child nexus instance.
 */
static int
pshot_bus_config_setup_nexus(dev_info_t *parent, char *cname, char *caddr)
{
	dev_info_t *child;
	int rval;

	ASSERT(parent != 0);
	ASSERT(cname != NULL);
	ASSERT(caddr != NULL);

	child = pshot_findchild(parent, cname, caddr);
	if (child) {
		if (pshot_debug) {
			cmn_err(CE_CONT,
			    "pshot%d: bus_config one %s@%s found\n",
			    ddi_get_instance(parent), cname, caddr);
		}

		/*
		 * create the "pm-want-child-notification?" property
		 * for this child, if it doesn't already exist
		 */
		(void) pshot_nexus_properties(parent, child, cname, caddr);

		return (NDI_SUCCESS);
	}

	ndi_devi_alloc_sleep(parent, cname, DEVI_SID_NODEID, &child);
	ASSERT(child != NULL);

	if (ndi_prop_update_string(DDI_DEV_T_NONE, child,
	    "bus-addr", caddr) != DDI_PROP_SUCCESS) {
		cmn_err(CE_WARN, "pshot%d: _prop_update %s@%s failed",
		    ddi_get_instance(parent), cname, caddr);
		(void) ndi_devi_free(child);
		return (NDI_FAILURE);
	}

	rval = ndi_devi_bind_driver(child, 0);
	if (rval != NDI_SUCCESS) {
		cmn_err(CE_WARN, "pshot%d: bind_driver %s failed",
		    ddi_get_instance(parent), cname);
		(void) ndi_devi_free(child);
		return (NDI_FAILURE);
	}

	/*
	 * create the "pm-want-child-notification?" property
	 */
	(void) pshot_nexus_properties(parent, child, cname, caddr);

	return (NDI_SUCCESS);
}

/*
 * BUS_CONFIG_ONE: setup a child leaf device instance.
 * for testing purposes, we will create nodes of a variety of types.
 */
static int
pshot_bus_config_setup_leaf(dev_info_t *parent, char *cname, char *caddr)
{
	dev_info_t *child;
	char *compat_name;
	char *nodetype;
	int rval;
	int i;

	ASSERT(parent != 0);
	ASSERT(cname != NULL);
	ASSERT(caddr != NULL);

	/*
	 * if we already have a node with this name, return it
	 */
	if ((child = pshot_findchild(parent, cname, caddr)) != NULL) {
		/*
		 * create the "no-involuntary-power-cycles" or
		 * the "dependency-property" property, if they
		 * don't already exit
		 */
		(void) pshot_leaf_properties(parent, child, cname, caddr);

		return (NDI_SUCCESS);
	}

	ndi_devi_alloc_sleep(parent, cname, DEVI_SID_NODEID, &child);
	ASSERT(child != NULL);

	if (ndi_prop_update_string(DDI_DEV_T_NONE, child, "bus-addr",
	    caddr) != DDI_PROP_SUCCESS) {
		(void) ndi_devi_free(child);
		return (NDI_FAILURE);
	}

	/*
	 * test compatible naming
	 * if the child nodename is "cdisk", attach the list of compatible
	 * named disks
	 */
	if (strcmp(cname, pshot_compat_diskname) == 0) {
		if ((ndi_prop_update_string_array(DDI_DEV_T_NONE,
		    child, "compatible", (char **)pshot_compat_psramdisks,
		    5)) != DDI_PROP_SUCCESS) {
			(void) ndi_devi_free(child);
			return (NDI_FAILURE);
		}
	} else {
		for (i = 0; i < pshot_devices_len && pshot_devices[i].name;
		    i++) {
			if (strcmp(cname, pshot_devices[i].name) == 0) {
				compat_name = pshot_devices[i].compat;
				nodetype = pshot_devices[i].nodetype;
				if (pshot_debug) {
					cmn_err(CE_CONT, "pshot%d: %s %s %s\n",
					    ddi_get_instance(parent), cname,
					    compat_name, nodetype);
				}
				if ((ndi_prop_update_string_array(
				    DDI_DEV_T_NONE, child, "compatible",
				    &compat_name, 1)) != DDI_PROP_SUCCESS) {
					(void) ndi_devi_free(child);
					return (NDI_FAILURE);
				}
				if ((ndi_prop_update_string(
				    DDI_DEV_T_NONE, child, "node-type",
				    nodetype)) != DDI_PROP_SUCCESS) {
					(void) ndi_devi_free(child);
					return (NDI_FAILURE);
				}
			}
		}
	}

	rval = ndi_devi_bind_driver(child, 0);
	if (rval != NDI_SUCCESS) {
		cmn_err(CE_WARN, "pshot%d: bind_driver %s failed",
		    ddi_get_instance(parent), cname);
		(void) ndi_devi_free(child);
		return (NDI_FAILURE);
	}

	/*
	 * create the "no-involuntary-power-cycles" or
	 * the "dependency-property" property
	 */
	(void) pshot_leaf_properties(parent, child, cname, caddr);

	return (NDI_SUCCESS);
}

/*
 * Handle some special cases for testing bus_config via pshot
 *
 * Match these special address formats to behavior:
 *
 *	err.*		- induce bus_config error
 *	delay		- induce 1 second of bus_config delay time
 *	delay,n		- induce n seconds of bus_config delay time
 *	wait		- induce 1 second of bus_config wait time
 *	wait,n		- induce n seconds of bus_config wait time
 *	failinit.*	- induce error at INITCHILD
 *	failprobe.*	- induce error at probe
 *	failattach.*	- induce error at attach
 */
/*ARGSUSED*/
static int
pshot_bus_config_test_specials(dev_info_t *parent, char *devname,
	char *cname, char *caddr)
{
	char	*p;
	int	n;

	if (strncmp(caddr, "err", 3) == 0) {
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s forced failure\n",
			    ddi_get_instance(parent), devname);
		return (NDI_FAILURE);
	}

	/*
	 * The delay and wait strings have the same effect.
	 * The "wait[,]" support should be removed once the
	 * devfs test suites are fixed.
	 * NOTE: delay should not be called from interrupt context
	 */
	ASSERT(!servicing_interrupt());

	if (strncmp(caddr, "delay,", 6) == 0) {
		p = caddr+6;
		n = stoi(&p);
		if (*p != 0)
			n = 1;
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s delay %d second\n",
			    ddi_get_instance(parent), devname, n);
		delay(n * drv_usectohz(1000000));
	} else if (strncmp(caddr, "delay", 5) == 0) {
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s delay 1 second\n",
			    ddi_get_instance(parent), devname);
		delay(drv_usectohz(1000000));
	} else if (strncmp(caddr, "wait,", 5) == 0) {
		p = caddr+5;
		n = stoi(&p);
		if (*p != 0)
			n = 1;
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s wait %d second\n",
			    ddi_get_instance(parent), devname, n);
		delay(n * drv_usectohz(1000000));
	} else if (strncmp(caddr, "wait", 4) == 0) {
		if (pshot_debug)
			cmn_err(CE_CONT,
			    "pshot%d: %s wait 1 second\n",
			    ddi_get_instance(parent), devname);
		delay(drv_usectohz(1000000));
	}

	return (NDI_SUCCESS);
}

/*
 * translate nodetype name to actual value
 */
static char *
pshot_str2nt(char *str)
{
	int i;

	for (i = 0; pshot_nodetypes[i].name; i++) {
		if (strcmp(pshot_nodetypes[i].name, str) == 0)
			return (pshot_nodetypes[i].val);
	}
	return (NULL);
}

/*
 * grows array pointed to by <dstp>, with <src> data
 * <dstlen> = # elements of the original <*dstp>
 * <srclen> = # elements of <src>
 *
 * on success, returns 0 and a pointer to the new array through <dstp> with
 * <srclen> + <dstlen> number of elements;
 * else returns non-zero
 *
 * a NULL <*dstp> is OK (a NULL <dstp> is not) and so is a zero <dstlen>
 */
static int
pshot_devices_grow(pshot_device_t **dstp, size_t dstlen,
    const pshot_device_t *src, size_t srclen)
{
	size_t i;
	pshot_device_t *newdst;

	newdst = kmem_alloc((srclen + dstlen) * sizeof (*src),
	    KM_SLEEP);

	/* keep old pointers and dup new ones */
	if (*dstp)
		bcopy(*dstp, newdst, dstlen * sizeof (*src));
	for (i = 0; i < srclen; i++) {
		newdst[i + dstlen].name =
		    i_ddi_strdup(src[i].name, KM_SLEEP);

		newdst[i + dstlen].nodetype =
		    i_ddi_strdup(src[i].nodetype, KM_SLEEP);

		newdst[i + dstlen].compat =
		    i_ddi_strdup(src[i].compat, KM_SLEEP);
	}

	/* do last */
	if (*dstp)
		kmem_free(*dstp, dstlen * sizeof (*src));
	*dstp = newdst;
	return (0);
}

/*
 * free a pshot_device_t array <dp> with <len> elements
 * null pointers within the elements are ok
 */
static void
pshot_devices_free(pshot_device_t *dp, size_t len)
{
	size_t i;

	for (i = 0; i < len; i++) {
		if (dp[i].name)
			kmem_free(dp[i].name, strlen(dp[i].name) + 1);
		if (dp[i].nodetype)
			kmem_free(dp[i].nodetype, strlen(dp[i].nodetype) + 1);
		if (dp[i].compat)
			kmem_free(dp[i].compat, strlen(dp[i].compat) + 1);
	}
	kmem_free(dp, len * sizeof (*dp));
}

/*
 * returns an array of pshot_device_t parsed from <dip>'s properties
 *
 * property structure (i.e. pshot.conf) for pshot:
 *
 * corresponding         |   pshot_device_t array elements
 * pshot_device_t        |
 * member by prop name   |   [0]            [1]           [2]
 * ----------------------|--------------|-------------|-----------------------
 * <PSHOT_PROP_DEVNAME>  ="disk",        "tape",       "testdev";
 * <PSHOT_PROP_DEVNT>    ="DDI_NT_BLOCK","DDI_NT_TAPE","ddi_testdev_nodetype";
 * <PSHOT_PROP_DEVCOMPAT>="testdrv",     "testdrv",    "testdrv";
 *
 *
 * if any of these properties are specified, then:
 * - all the members must be specified
 * - the number of elements for each string array property must be the same
 * - no empty strings allowed
 * - nodetypes (PSHOT_PROP_DEVNT) must be the nodetype name as specified in
 *   sys/sunddi.h
 *
 * NOTE: the pshot_nodetypes[] table should be kept in sync with the list
 * of ddi nodetypes.  It's not normally critical to always be in sync so
 * keeping this up-to-date can usually be done "on-demand".
 *
 * if <flags> & PSHOT_DEV_ANYNT, then custom nodetype strings are allowed.
 * these will be duplicated verbatim
 */
static pshot_device_t *
pshot_devices_from_props(dev_info_t *dip, size_t *lenp, int flags)
{
	pshot_device_t *devarr = NULL;
	char **name_arr = NULL, **nt_arr = NULL, **compat_arr = NULL;
	uint_t name_arr_len, nt_arr_len, compat_arr_len;
	uint_t i;
	char *str;

	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, 0,
	    PSHOT_PROP_DEVNAME, &name_arr, &name_arr_len) !=
	    DDI_PROP_SUCCESS)
		name_arr = NULL;

	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, 0,
	    PSHOT_PROP_DEVNT, &nt_arr, &nt_arr_len) !=
	    DDI_PROP_SUCCESS)
		nt_arr = NULL;

	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, 0,
	    PSHOT_PROP_DEVCOMPAT, &compat_arr, &compat_arr_len) !=
	    DDI_PROP_SUCCESS)
		compat_arr = NULL;

	/*
	 * warn about any incorrect usage, if specified
	 */
	if (!(name_arr || nt_arr || compat_arr))
		return (NULL);

	if (!(name_arr && nt_arr && compat_arr) ||
	    (name_arr_len != nt_arr_len) ||
	    (name_arr_len != compat_arr_len))
		goto FAIL;

	for (i = 0; i < name_arr_len; i++) {
		if (*name_arr[i] == '\0' ||
		    *nt_arr[i] == '\0' ||
		    *compat_arr[i] == '\0')
			goto FAIL;
	}

	devarr = kmem_zalloc(name_arr_len * sizeof (*devarr), KM_SLEEP);
	for (i = 0; i < name_arr_len; i++) {
		devarr[i].name = i_ddi_strdup(name_arr[i], KM_SLEEP);
		devarr[i].compat = i_ddi_strdup(compat_arr[i], KM_SLEEP);

		if ((str = pshot_str2nt(nt_arr[i])) == NULL)
			if (flags & PSHOT_DEV_ANYNT)
				str = nt_arr[i];
			else
				goto FAIL;
		devarr[i].nodetype = i_ddi_strdup(str, KM_SLEEP);
	}
	ddi_prop_free(name_arr);
	ddi_prop_free(nt_arr);
	ddi_prop_free(compat_arr);

	/* set <*lenp> ONLY on success */
	*lenp = name_arr_len;

	return (devarr);
	/*NOTREACHED*/
FAIL:
	cmn_err(CE_WARN, "malformed device specification property");
	if (name_arr)
		ddi_prop_free(name_arr);
	if (nt_arr)
		ddi_prop_free(nt_arr);
	if (compat_arr)
		ddi_prop_free(compat_arr);
	if (devarr)
		pshot_devices_free(devarr, name_arr_len);
	return (NULL);
}

/*
 * if global <pshot_devices> was not set up already (i.e. is NULL):
 *	sets up global <pshot_devices> and <pshot_devices_len>,
 *	using device properties	from <dip> and global <pshot_stock_devices>.
 *	device properties, if any, overrides pshot_stock_devices.
 *
 * returns 0 on success (or if pshot_devices already set up)
 *
 * INTERNAL LOCKING: <pshot_devices_lock>
 */
static int
pshot_devices_setup(dev_info_t *dip)
{
	pshot_device_t *newdevs = NULL;
	size_t newdevs_len = 0;
	int rv = 0;

	mutex_enter(&pshot_devices_lock);
	if (pshot_devices != NULL)
		goto FAIL;

	ASSERT(pshot_devices_len == 0);

	newdevs = pshot_devices_from_props(dip, &newdevs_len, PSHOT_DEV_ANYNT);
	rv = pshot_devices_grow(&newdevs, newdevs_len, pshot_stock_devices,
	    PSHOT_N_STOCK_DEVICES);
	if (rv != 0) {
		cmn_err(CE_WARN, "pshot_devices_setup: pshot_devices_grow "
		    "failed");
		goto FAIL;
	}
	newdevs_len += PSHOT_N_STOCK_DEVICES;

	pshot_devices = newdevs;
	pshot_devices_len = newdevs_len;
	rv = 0;
FAIL:
	if (rv && newdevs)
		pshot_devices_free(newdevs, newdevs_len);
	mutex_exit(&pshot_devices_lock);
	return (rv);
}


#ifdef NOTNEEDED
/* ARGSUSED */
static int
pshot_probe_family(dev_info_t *self, ddi_probe_method_t probe_how,
    dev_info_t **return_dip)
{
	char name[64];
	uint_t bus_id;
	dev_info_t *child;

	for (bus_id = 10; bus_id < 20; bus_id++) {
		(void) sprintf(name, "%d", bus_id);
		if ((ndi_devi_alloc(self, "psramd", DEVI_SID_NODEID,
		    &child)) != NDI_SUCCESS) {
			return (DDI_FAILURE);
		}

		if (ndi_prop_update_string(DDI_DEV_T_NONE, child,
		    "bus-addr", name) != DDI_PROP_SUCCESS) {
			(void) ndi_devi_free(child);
			if (return_dip != NULL)
				*return_dip = (dev_info_t *)NULL;
			return (DDI_FAILURE);
		}

		if (ndi_devi_online(child, 0) != NDI_SUCCESS) {
			return (DDI_FAILURE);
		}
	}
	return (DDI_SUCCESS);
}

static int
strtoi(char *str)
{
	int c;
	int val;

	for (val = 0, c = *str++; c >= '0' && c <= '9'; c = *str++) {
		val *= 10;
		val += c - '0';
	}
	return (val);
}

#endif

static void
pshot_setup_autoattach(dev_info_t *devi)
{
	dev_info_t *l1child, *l2child;
	int rv;

	rv = ndi_devi_alloc(devi, "pshot", DEVI_SID_NODEID, &l1child);
	if (rv == NDI_SUCCESS) {
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, l1child,
		    "bus-addr", "0");
		rv =  ndi_devi_alloc(l1child, "port", DEVI_SID_NODEID,
		    &l2child);
		if (rv == NDI_SUCCESS)
			(void) ndi_prop_update_string(DDI_DEV_T_NONE,
			    l2child, "bus-addr", "99");
	}

	rv = ndi_devi_alloc(devi, "port", DEVI_SID_NODEID, &l1child);
	if (rv == NDI_SUCCESS)
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, l1child,
		    "bus-addr", "99");

	rv = ndi_devi_alloc(devi, "gen_drv", DEVI_SID_NODEID, &l1child);
	if (rv == NDI_SUCCESS)
		(void) ndi_prop_update_string(DDI_DEV_T_NONE, l1child,
		    "bus-addr", "99");

	rv = ndi_devi_alloc(devi, "no_driver", DEVI_SID_NODEID, &l1child);
	if (rv == NDI_SUCCESS)
		(void) ndi_devi_alloc(l1child, "no_driver", DEVI_SID_NODEID,
		    &l2child);
}

#ifdef PRUNE_SNUBS

#define	PRUNE_THIS_NODE(d) (((d)->devi_node_name != NULL) && \
	(DEVI_PROM_NODE((d)->devi_nodeid)) && \
	((d)->devi_addr == NULL))
/*
 * test code to remove OBP nodes that have not attached
 */
static void
prune_snubs(const char *name)
{
	struct dev_info *nex_dip, *cdip, *cndip;
	int maj;
	int rv;

	maj = ddi_name_to_major((char *)name);
	if (maj != -1) {
		nex_dip = (struct dev_info *)devnamesp[maj].dn_head;
		while (nex_dip != NULL) {
			cndip = ddi_get_child(nex_dip);
			while ((cdip = cndip) != NULL) {
				cndip = cdip->devi_sibling;
				if (PRUNE_THIS_NODE(cdip)) {
					cmn_err(CE_NOTE,
					    "parent %s@%s pruning node %s",
					    nex_dip->devi_node_name,
					    nex_dip->devi_addr,
					    cdip->devi_node_name);
					rv = ndi_devi_offline(cdip,
					    NDI_DEVI_REMOVE);
					if (rv != NDI_SUCCESS)
						cmn_err(CE_NOTE,
						    "failed to prune node, "
						    "err %d", rv);
				}
			}
		nex_dip = nex_dip->devi_next;
		}
	}
}

#endif /* PRUBE_SNUBS */

#ifdef KERNEL_DEVICE_TREE_WALKER
static kthread_id_t pwt;
static kmutex_t pwl;
static kcondvar_t pwcv;

static void
pshot_walk_tree()
{
	static int pshot_devnode(dev_info_t *dip, void * arg);

	dev_info_t *root = ddi_root_node();
	ddi_walk_devs(root, pshot_devnode, NULL);
}

static void
pshot_walk_thread()
{
	static void pshot_timeout(void *arg);
	static kthread_id_t pwt;

	pwt = curthread;
	mutex_init(&pwl, NULL, MUTEX_DRIVER, NULL);
	cv_init(&pwcv, NULL, CV_DRIVER, NULL);

	while (1) {
		pshot_walk_tree();
		mutex_enter(&pwl);
		(void) timeout(pshot_timeout, NULL, 5 * drv_usectohz(1000000));
		cv_wait(&pwcv, &pwl);
		mutex_exit(&pwl);
	}
}

static void
pshot_timeout(void *arg)
{
	mutex_enter(&pwl);
	cv_signal(&pwcv);
	mutex_exit(&pwl);
}

static int
pshot_devnode(dev_info_t *dip, void *arg)
{
	dev_info_t *f_dip;

	if (dip != ddi_root_node()) {
		f_dip = ndi_devi_find((dev_info_t *)DEVI(dip)->devi_parent,
		    DEVI(dip)->devi_node_name, DEVI(dip)->devi_addr);
		if (f_dip != dip) {
			cmn_err(CE_NOTE, "!pshot_devnode: failed lookup"
			    "node (%s/%s@%s)\n",
			    DEVI(DEVI(dip)->devi_parent)->devi_node_name,
			    (DEVI(dip)->devi_node_name ?
			    DEVI(dip)->devi_node_name : "NULL"),
			    (DEVI(dip)->devi_addr ? DEVI(dip)->devi_addr :
			    "NULL"));
		}
	}
	return (DDI_WALK_CONTINUE);
}
#endif /* KERNEL_DEVICE_TREE_WALKER */

#ifdef DEBUG
static void
pshot_event_cb_test(dev_info_t *dip, ddi_eventcookie_t cookie,
	void *arg, void *bus_impldata)
{
	pshot_t *softstate = (pshot_t *)arg;
	int event_tag;

	/* look up the event */
	event_tag = NDI_EVENT_TAG(cookie);
	cmn_err(CE_CONT, "pshot_event_cb_test:\n\t"
	    "dip = 0x%p cookie = 0x%p (%s), tag = %d\n\t"
	    "arg = 0x%p bus_impl = 0x%p\n",
	    (void *)dip, (void *)cookie, NDI_EVENT_NAME(cookie),
	    event_tag, (void *)softstate, (void *)bus_impldata);

}

static void
pshot_event_test(void *arg)
{
	pshot_t *pshot = (pshot_t *)arg;
	ndi_event_hdl_t hdl;
	ndi_event_set_t	events;
	int i, rval;

	(void) ndi_event_alloc_hdl(pshot->dip, NULL, &hdl, NDI_SLEEP);

	events.ndi_events_version = NDI_EVENTS_REV1;
	events.ndi_n_events = PSHOT_N_TEST_EVENTS;
	events.ndi_event_defs = pshot_test_events;

	cmn_err(CE_CONT, "pshot: binding set of 8 events\n");
	delay(drv_usectohz(1000000));
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: binding the same set of 8 events\n");
	delay(drv_usectohz(1000000));
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  all events\n");
	delay(drv_usectohz(1000000));
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);


	cmn_err(CE_CONT, "pshot: binding one highlevel event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = pshot_test_events_high;
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: binding the same set of 8 events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = PSHOT_N_TEST_EVENTS;
	events.ndi_event_defs = pshot_test_events;
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding one highlevel event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = pshot_test_events_high;
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: binding one highlevel event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = pshot_test_events_high;
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding one highlevel event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = pshot_test_events_high;
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: binding the same set of 8 events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = PSHOT_N_TEST_EVENTS;
	events.ndi_event_defs = pshot_test_events;
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding first 2 events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 2;
	events.ndi_event_defs = pshot_test_events;
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding first 2 events again\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 2;
	events.ndi_event_defs = pshot_test_events;
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  middle 2 events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 2;
	events.ndi_event_defs = &pshot_test_events[4];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: binding those 2 events back\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 2;
	events.ndi_event_defs = &pshot_test_events[4];
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  2 events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 2;
	events.ndi_event_defs = &pshot_test_events[4];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  all events\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = PSHOT_N_TEST_EVENTS;
	events.ndi_event_defs = pshot_test_events;
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  1 event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = &pshot_test_events[2];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  1 event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = &pshot_test_events[3];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  1 event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = &pshot_test_events[6];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: unbinding  1 event\n");
	delay(drv_usectohz(1000000));
	events.ndi_n_events = 1;
	events.ndi_event_defs = &pshot_test_events[7];
	rval = ndi_event_unbind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_unbind_set rval = %d\n", rval);

	events.ndi_n_events = PSHOT_N_TEST_EVENTS;
	events.ndi_event_defs = pshot_test_events;

	cmn_err(CE_CONT, "pshot: binding set of 8 events\n");
	delay(drv_usectohz(1000000));
	rval = ndi_event_bind_set(hdl, &events, NDI_SLEEP);
	cmn_err(CE_CONT, "pshot: ndi_event_bind_set rval = %d\n", rval);

	cmn_err(CE_CONT, "pshot: adding 8 callbacks\n");
	delay(drv_usectohz(1000000));
	for (i = 0; i < 8; i++) {
		rval = ndi_event_add_callback(hdl, pshot->dip,
		    ndi_event_tag_to_cookie(hdl,
		    pshot_test_events[i].ndi_event_tag),
		    pshot_event_cb_test,
		    (void *)(uintptr_t)pshot_test_events[i].ndi_event_tag,
		    NDI_SLEEP, &pshot->test_callback_cache[i]);
		ASSERT(rval == NDI_SUCCESS);
	}

	cmn_err(CE_CONT, "pshot: event callbacks\n");

	for (i = 10; i < 18; i++) {
		ddi_eventcookie_t cookie = ndi_event_tag_to_cookie(hdl, i);

		rval = ndi_event_run_callbacks(hdl, pshot->dip, cookie,
		    (void *)hdl);

		cmn_err(CE_CONT, "pshot: callback, tag=%d rval=%d\n",
		    i, rval);
		delay(drv_usectohz(1000000));
	}

	cmn_err(CE_CONT, "pshot: redo event callbacks\n");

	for (i = 10; i < 18; i++) {
		ddi_eventcookie_t cookie = ndi_event_tag_to_cookie(hdl, i);

		rval = ndi_event_run_callbacks(hdl,
		    pshot->dip, cookie, (void *)hdl);

		cmn_err(CE_CONT, "pshot: callback, tag=%d rval=%d\n",
		    i, rval);
		delay(drv_usectohz(1000000));
	}

	cmn_err(CE_CONT, "pshot: removing 8 callbacks\n");
	delay(drv_usectohz(1000000));

	for (i = 0; i < 8; i++) {
		(void) ndi_event_remove_callback(hdl,
		    pshot->test_callback_cache[i]);

		pshot->test_callback_cache[i] = 0;
	}

	cmn_err(CE_CONT, "pshot: freeing handle with bound set\n");
	delay(drv_usectohz(1000000));

	rval =	ndi_event_free_hdl(hdl);

	ASSERT(rval == NDI_SUCCESS);

}

void
pshot_event_test_post_one(void *arg)
{
	pshot_t	*pshot = (pshot_t *)arg;
	int rval;
	ddi_eventcookie_t cookie;

	cmn_err(CE_CONT, "pshot%d: pshot_event_post_one event\n",
	    pshot->instance);

	if (ddi_get_eventcookie(pshot->dip, PSHOT_EVENT_NAME_BUS_TEST_POST,
	    &cookie) != DDI_SUCCESS) {
		cmn_err(CE_NOTE, "pshot_bus_test_post cookie not found");
		return;
	}

	rval = ndi_post_event(pshot->dip, pshot->dip, cookie, NULL);

	cmn_err(CE_CONT, "pshot%d: pshot_event_post_one rval=%d\n",
	    pshot->instance, rval);

	(void) timeout(pshot_event_test_post_one, (void *)pshot,
	    pshot->instance * drv_usectohz(60000000));

}
#endif /* DEBUG */