| /* |
| * 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. |
| */ |
| /* |
| * Copyright 2012 Garrett D'Amore <garrett@damore.org>. All rights reserved. |
| * Copyright (c) 2016 by Delphix. All rights reserved. |
| */ |
| |
| /* |
| * 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 their 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 */ |
| NULL, /* 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-pow
|