/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * ********************************************************************** * Extension module for PCI nexus drivers to support PCI Hot Plug feature. * * DESCRIPTION: * This module basically implements "devctl" and Attachment Point device * nodes for hot plug operations. The cb_ops functions needed for access * to these device nodes are also implemented. For hotplug operations * on Attachment Points it interacts with the hotplug services (HPS) * framework. A pci nexus driver would simply call pcihp_init() in its * attach() function and pcihp_uninit() call in its detach() function. * ********************************************************************** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * NOTE: * This module depends on PCI Configurator module (misc/pcicfg), * Hot Plug Services framework module (misc/hpcsvc) and Bus Resource * Allocator module (misc/busra). */ /* * ************************************************************************ * *** Implementation specific data structures/definitions. *** * ************************************************************************ */ /* soft state */ typedef enum { PCIHP_SOFT_STATE_CLOSED, PCIHP_SOFT_STATE_OPEN, PCIHP_SOFT_STATE_OPEN_EXCL } pcihp_soft_state_t; #define PCI_MAX_DEVS 32 /* max. number of devices on a pci bus */ /* the following correspond to sysevent defined subclasses */ #define PCIHP_DR_AP_STATE_CHANGE 0 #define PCIHP_DR_REQ 1 /* pcihp_get_soft_state() command argument */ #define PCIHP_DR_NOOP 0 #define PCIHP_DR_BUS_CONFIGURE 1 #define PCIHP_DR_BUS_UNCONFIGURE 2 #define PCIHP_DR_SLOT_ENTER 4 #define PCIHP_DR_SLOT_EXIT 8 /* hot plug bus state */ enum { PCIHP_BUS_INITIALIZING, PCIHP_BUS_UNCONFIGURED, PCIHP_BUS_CONFIGURED }; /* * Soft state structure associated with each hot plug pci bus instance. */ typedef struct pcihp { struct pcihp *nextp; /* devinfo pointer to the pci bus node */ dev_info_t *dip; /* soft state flags: PCIHP_SOFT_STATE_* */ pcihp_soft_state_t soft_state; /* global mutex to serialize exclusive access to the bus */ kmutex_t mutex; /* slot information structure */ struct pcihp_slotinfo { hpc_slot_t slot_hdl; /* HPS slot handle */ ap_rstate_t rstate; /* state of Receptacle */ ap_ostate_t ostate; /* state of the Occupant */ ap_condition_t condition; /* condition of the occupant */ time32_t last_change; /* XXX needed? */ uint32_t event_mask; /* last event mask registered */ char *name; /* slot logical name */ uint_t slot_flags; uint16_t slot_type; /* slot type: pci or cpci */ uint16_t slot_capabilities; /* 64bit, etc. */ int hs_csr_location; /* Location of HS_CSR */ kmutex_t slot_mutex; /* mutex to serialize hotplug */ /* operations on the slot */ } slotinfo[PCI_MAX_DEVS]; /* misc. bus attributes */ uint_t bus_flags; uint_t bus_state; uint_t slots_active; } pcihp_t; /* * Bit definitions for slot_flags field: * * PCIHP_SLOT_AUTO_CFG_EN This flags is set if nexus can do auto * configuration of hot plugged card on this slot * if the hardware reports the hot plug events. * * PCIHP_SLOT_DISABLED Slot is disabled for hotplug operations. * * PCIHP_SLOT_NOT_HEALTHY HEALTHY# signal is not OK on this slot. */ #define PCIHP_SLOT_AUTO_CFG_EN 0x1 #define PCIHP_SLOT_DISABLED 0x2 #define PCIHP_SLOT_NOT_HEALTHY 0x4 #define PCIHP_SLOT_DEV_NON_HOTPLUG 0x8 #define PCIHP_SLOT_ENUM_INS_PENDING 0x10 #define PCIHP_SLOT_ENUM_EXT_PENDING 0x20 /* * Bit definitions for bus_flags field: * * PCIHP_BUS_66MHZ Bus is running at 66Mhz. */ #define PCIHP_BUS_66MHZ 0x1 #define PCIHP_BUS_ENUM_RADIAL 0x2 #define PCIHP_DEVICES_STR "/devices" /* * control structure for tree walk during configure/unconfigure operation. */ struct pcihp_config_ctrl { int pci_dev; /* PCI device number for the slot */ uint_t flags; /* control flags (see below) */ int op; /* operation: PCIHP_ONLINE or PCIHP_OFFLINE */ int rv; /* return error code */ dev_info_t *dip; /* dip at which the (first) error occurred */ hpc_occupant_info_t *occupant; }; /* * control flags for configure/unconfigure operations on the tree. * * PCIHP_CFG_CONTINUE continue the operation ignoring errors */ #define PCIHP_CFG_CONTINUE 0x1 #define PCIHP_ONLINE 1 #define PCIHP_OFFLINE 0 /* Leaf ops (hotplug controls for target devices) */ static int pcihp_open(dev_t *, int, int, cred_t *); static int pcihp_close(dev_t, int, int, cred_t *); static int pcihp_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); #ifdef DEBUG static int pcihp_debug = 0; #define PCIHP_DEBUG(args) if (pcihp_debug >= 1) cmn_err args #define PCIHP_DEBUG2(args) if (pcihp_debug >= 2) cmn_err args #else #define PCIHP_DEBUG(args) #define PCIHP_DEBUG2(args) #endif /* * We process ENUM# event one device at a time ie. as soon as we detect * that a device has the right ENUM# conditions, we return. If the following * variable is set to non-zero, we scan all the devices on the bus * for ENUM# conditions. */ static int pcihp_enum_scan_all = 0; /* * If HSC driver cannot determine the board type (for example: it may not * be possible to differentiate between a Basic Hotswap, Non Hotswap or * Non-friendly Full hotswap board), the default board type is assigned * to be as defined by the following variable. */ static int pcihp_cpci_board_type = HPC_BOARD_CPCI_NON_HS; static int pcihp_cpci_led_blink = 30; /* * It was noted that the blue LED when written on/off would cause INS/EXT * bit to be set causing an extra interrupt. Although the cPCI specifications * does not imply this, this behavior is seen with some FHS silicons. * Also, handling the INS/EXT bit would control the LED being On/Off. * Until the behavior is confirmed, this flag could be used to enable or * disable handling the LED. * 0 means the silicons handles the LED behavior via the INS/EXT bit. * 1 means the software must explicitly do the LED behavior. */ static int pcihp_cpci_blue_led = 1; /* static functions */ static pcihp_t *pcihp_create_soft_state(dev_info_t *dip); static void pcihp_destroy_soft_state(dev_info_t *dip); static pcihp_t *pcihp_get_soft_state(dev_info_t *dip, int cmd, int *rv); static int pcihp_configure_ap(pcihp_t *pcihp_p, int pci_dev); static int pcihp_unconfigure_ap(pcihp_t *pcihp_p, int pci_dev); static int pcihp_new_slot_state(dev_info_t *, hpc_slot_t, hpc_slot_info_t *, int); static int pcihp_configure(dev_info_t *, void *); static int pcihp_event_handler(caddr_t, uint_t); static dev_info_t *pcihp_devi_find(dev_info_t *dip, uint_t dev, uint_t func); static int pcihp_match_dev(dev_info_t *dip, void *hdl); static int pcihp_get_hs_csr(struct pcihp_slotinfo *, ddi_acc_handle_t, uint8_t *); static void pcihp_set_hs_csr(struct pcihp_slotinfo *, ddi_acc_handle_t, uint8_t *); static int pcihp_get_hs_csr_location(ddi_acc_handle_t); static int pcihp_handle_enum(pcihp_t *, int, int, int); static void pcihp_hs_csr_op(pcihp_t *, int, int); static int pcihp_enum_slot(pcihp_t *, struct pcihp_slotinfo *, int, int, int); static int pcihp_handle_enum_extraction(pcihp_t *, int, int, int); static int pcihp_handle_enum_insertion(pcihp_t *, int, int, int); static int pcihp_add_dummy_reg_property(dev_info_t *, uint_t, uint_t, uint_t); static int pcihp_config_setup(dev_info_t **, ddi_acc_handle_t *, dev_info_t **, int, pcihp_t *); static void pcihp_config_teardown(ddi_acc_handle_t *, dev_info_t **, int, pcihp_t *); static int pcihp_get_board_type(struct pcihp_slotinfo *); /* sysevent function */ static void pcihp_gen_sysevent(char *, int, int, dev_info_t *, int); extern int pcicfg_configure(dev_info_t *, uint_t); extern int pcicfg_unconfigure(dev_info_t *, uint_t); static int pcihp_list_occupants(dev_info_t *, void *); static int pcihp_indirect_map(dev_info_t *dip); #if 0 static void pcihp_probe_slot_state(dev_info_t *, int, hpc_slot_state_t *); #endif int pcihp_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp); struct cb_ops pcihp_cb_ops = { pcihp_open, /* open */ pcihp_close, /* close */ nodev, /* strategy */ nodev, /* print */ nodev, /* dump */ nodev, /* read */ nodev, /* write */ pcihp_ioctl, /* ioctl */ nodev, /* devmap */ nodev, /* mmap */ nodev, /* segmap */ nochpoll, /* poll */ pcihp_prop_op, /* cb_prop_op */ NULL, /* streamtab */ D_NEW | D_MP | D_HOTPLUG, /* Driver compatibility flag */ CB_REV, /* rev */ nodev, /* int (*cb_aread)() */ nodev /* int (*cb_awrite)() */ }; /* * local data */ int pcihp_autocfg_enabled = 1; /* auto config is enabled by default */ static kmutex_t pcihp_mutex; /* mutex to protect the following data */ static pcihp_t *pcihp_head = NULL; static kmutex_t pcihp_open_mutex; /* mutex to protect open/close/uninit */ static int pci_devlink_flags = 0; /* * Module linkage information for the kernel. */ extern struct mod_ops mod_miscops; static struct modlmisc modlmisc = { &mod_miscops, "PCI nexus hotplug support v%I%", }; static struct modlinkage modlinkage = { MODREV_1, &modlmisc, NULL }; int _init(void) { int error; mutex_init(&pcihp_mutex, NULL, MUTEX_DRIVER, NULL); mutex_init(&pcihp_open_mutex, NULL, MUTEX_DRIVER, NULL); if ((error = mod_install(&modlinkage)) != 0) { mutex_destroy(&pcihp_open_mutex); mutex_destroy(&pcihp_mutex); } return (error); } int _fini(void) { return (EBUSY); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } static pcihp_t * pcihp_create_soft_state( dev_info_t *dip) { pcihp_t *pcihp_p; pcihp_p = kmem_zalloc(sizeof (struct pcihp), KM_SLEEP); pcihp_p->dip = dip; mutex_init(&pcihp_p->mutex, NULL, MUTEX_DRIVER, NULL); mutex_enter(&pcihp_mutex); pcihp_p->nextp = pcihp_head; pcihp_head = pcihp_p; pcihp_p->bus_state = PCIHP_BUS_INITIALIZING; pcihp_p->slots_active = 0; mutex_exit(&pcihp_mutex); return (pcihp_p); } static void pcihp_destroy_soft_state( dev_info_t *dip) { pcihp_t *p; pcihp_t **pp; mutex_enter(&pcihp_mutex); pp = &pcihp_head; while ((p = *pp) != NULL) { if (p->dip == dip) { *pp = p->nextp; kmem_free(p, sizeof (struct pcihp)); break; } pp = &(p->nextp); } mutex_exit(&pcihp_mutex); } /* * This function should be imported by client nexus drivers as their * devo_getinfo() entry point. */ /* ARGSUSED */ int pcihp_info( dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) { pcihp_t *pcihp_p; major_t major; minor_t minor; int instance; major = getmajor((dev_t)arg); minor = getminor((dev_t)arg); instance = PCIHP_AP_MINOR_NUM_TO_INSTANCE(minor); switch (cmd) { default: return (DDI_FAILURE); case DDI_INFO_DEVT2INSTANCE: *result = (void *)(intptr_t)instance; return (DDI_SUCCESS); case DDI_INFO_DEVT2DEVINFO: mutex_enter(&pcihp_mutex); pcihp_p = pcihp_head; while (pcihp_p != NULL) { if (ddi_name_to_major(ddi_get_name(pcihp_p->dip)) == major && ddi_get_instance(pcihp_p->dip) == instance) { *result = (void *)pcihp_p->dip; mutex_exit(&pcihp_mutex); return (DDI_SUCCESS); } pcihp_p = pcihp_p->nextp; } mutex_exit(&pcihp_mutex); return (DDI_FAILURE); } } /* * This function retrieves the hot plug soft state and performs the * following primitive commands while the soft state is locked: * mark the bus unconfigured, increment slot activity, decrement * slot activity and noop. */ /* ARGSUSED */ static pcihp_t * pcihp_get_soft_state( dev_info_t *dip, int cmd, int *rv) { pcihp_t *pcihp_p; *rv = PCIHP_SUCCESS; mutex_enter(&pcihp_mutex); pcihp_p = pcihp_head; while (pcihp_p != NULL) { if (pcihp_p->dip == dip) { switch (cmd) { case PCIHP_DR_BUS_UNCONFIGURE: if (pcihp_p->slots_active == 0) pcihp_p->bus_state = PCIHP_BUS_UNCONFIGURED; else *rv = PCIHP_FAILURE; break; case PCIHP_DR_SLOT_ENTER: if (pcihp_p->bus_state == PCIHP_BUS_UNCONFIGURED) *rv = PCIHP_FAILURE; else pcihp_p->slots_active++; break; case PCIHP_DR_SLOT_EXIT: ASSERT(pcihp_p->slots_active > 0); if (pcihp_p->slots_active == 0) cmn_err(CE_PANIC, "pcihp (%s%d): mismatched slot" " activity", ddi_driver_name(dip), ddi_get_instance(dip)); else pcihp_p->slots_active--; break; case PCIHP_DR_NOOP: break; default: *rv = PCIHP_FAILURE; break; } mutex_exit(&pcihp_mutex); return (pcihp_p); } pcihp_p = pcihp_p->nextp; } mutex_exit(&pcihp_mutex); return (NULL); } /* ARGSUSED3 */ static int pcihp_open(dev_t *devp, int flags, int otyp, cred_t *credp) { dev_info_t *self; pcihp_t *pcihp_p; minor_t minor; int pci_dev; int rv; /* * Make sure the open is for the right file type. */ if (otyp != OTYP_CHR) return (EINVAL); mutex_enter(&pcihp_open_mutex); /* * Get the soft state structure. */ if (pcihp_info(NULL, DDI_INFO_DEVT2DEVINFO, (void *)*devp, (void **)&self) != DDI_SUCCESS) { mutex_exit(&pcihp_open_mutex); return (ENXIO); } pcihp_p = pcihp_get_soft_state(self, PCIHP_DR_NOOP, &rv); ASSERT(pcihp_p != NULL); mutex_enter(&pcihp_p->mutex); /* * If the pci_dev is valid then the minor device is an * AP. Otherwise it is ":devctl" minor device. */ minor = getminor(*devp); pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(minor); if (pci_dev < PCI_MAX_DEVS) { struct pcihp_slotinfo *slotinfop; slotinfop = &pcihp_p->slotinfo[pci_dev]; if (slotinfop->slot_hdl == NULL) { mutex_exit(&pcihp_p->mutex); mutex_exit(&pcihp_open_mutex); return (ENXIO); } } /* * Handle the open by tracking the device state. * * Note: Needs review w.r.t exclusive access to AP or the bus. * Currently in the pci plug-in we don't use EXCL open at all * so the code below implements EXCL access on the bus. */ /* enforce exclusive access to the bus */ if ((pcihp_p->soft_state == PCIHP_SOFT_STATE_OPEN_EXCL) || ((flags & FEXCL) && (pcihp_p->soft_state != PCIHP_SOFT_STATE_CLOSED))) { mutex_exit(&pcihp_p->mutex); mutex_exit(&pcihp_open_mutex); return (EBUSY); } if (flags & FEXCL) pcihp_p->soft_state = PCIHP_SOFT_STATE_OPEN_EXCL; else pcihp_p->soft_state = PCIHP_SOFT_STATE_OPEN; mutex_exit(&pcihp_p->mutex); mutex_exit(&pcihp_open_mutex); return (0); } /* ARGSUSED */ static int pcihp_close(dev_t dev, int flags, int otyp, cred_t *credp) { dev_info_t *self; pcihp_t *pcihp_p; int rv; if (otyp != OTYP_CHR) return (EINVAL); mutex_enter(&pcihp_open_mutex); if (pcihp_info(NULL, DDI_INFO_DEVT2DEVINFO, (void *)dev, (void **)&self) != DDI_SUCCESS) { mutex_exit(&pcihp_open_mutex); return (ENXIO); } pcihp_p = pcihp_get_soft_state(self, PCIHP_DR_NOOP, &rv); ASSERT(pcihp_p != NULL); mutex_enter(&pcihp_p->mutex); pcihp_p->soft_state = PCIHP_SOFT_STATE_CLOSED; mutex_exit(&pcihp_p->mutex); mutex_exit(&pcihp_open_mutex); return (0); } static int pcihp_list_occupants(dev_info_t *dip, void *hdl) { int pci_dev; struct pcihp_config_ctrl *ctrl = (struct pcihp_config_ctrl *)hdl; pci_regspec_t *pci_rp; int length; major_t major; /* * Get the PCI device number information from the devinfo * node. Since the node may not have the address field * setup (this is done in the DDI_INITCHILD of the parent) * we look up the 'reg' property to decode that information. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { ctrl->rv = DDI_FAILURE; ctrl->dip = dip; return (DDI_WALK_TERMINATE); } /* get the pci device id information */ pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); /* * free the memory allocated by ddi_prop_lookup_int_array */ ddi_prop_free(pci_rp); /* * Match the node for the device number of the slot. */ if (pci_dev == ctrl->pci_dev) { /* node is a match */ major = ddi_driver_major(dip); /* * If the node is not yet attached, then don't list it * as an occupant. This is valid, since nothing can be * consuming it until it is attached, and cfgadm will * ask for the property explicitly which will cause it * to be re-freshed right before checking with rcm. */ if ((major == -1) || (i_ddi_node_state(dip) < DS_ATTACHED)) return (DDI_WALK_PRUNECHILD); /* * If we have used all our occupants then print mesage * and terminate walk. */ if (ctrl->occupant->i >= HPC_MAX_OCCUPANTS) { cmn_err(CE_WARN, "pcihp (%s%d): unable to list all occupants", ddi_driver_name(ddi_get_parent(dip)), ddi_get_instance(ddi_get_parent(dip))); return (DDI_WALK_TERMINATE); } /* * No need to hold the dip as ddi_walk_devs * has already arranged that for us. */ ctrl->occupant->id[ctrl->occupant->i] = kmem_alloc(sizeof (char[MAXPATHLEN]), KM_SLEEP); (void) ddi_pathname(dip, (char *)ctrl->occupant->id[ctrl->occupant->i]); ctrl->occupant->i++; } /* * continue the walk to the next sibling to look for a match * or to find other nodes if this card is a multi-function card. */ return (DDI_WALK_PRUNECHILD); } static void pcihp_create_occupant_props_nolock(dev_info_t *self, dev_t dev, int pci_dev) { struct pcihp_config_ctrl ctrl; hpc_occupant_info_t *occupant; int i; occupant = kmem_alloc(sizeof (hpc_occupant_info_t), KM_SLEEP); occupant->i = 0; ctrl.flags = 0; ctrl.dip = NULL; ctrl.rv = NDI_SUCCESS; ctrl.pci_dev = pci_dev; ctrl.op = 55; /* should define DRYRUN */ ctrl.occupant = occupant; ddi_walk_devs(ddi_get_child(self), pcihp_list_occupants, (void *)&ctrl); if (occupant->i == 0) { /* no occupants right now, need to create stub property */ char *c[] = { "" }; (void) ddi_prop_update_string_array(dev, self, "pci-occupant", c, 1); } else { (void) ddi_prop_update_string_array(dev, self, "pci-occupant", occupant->id, occupant->i); } for (i = 0; i < occupant->i; i++) { kmem_free(occupant->id[i], sizeof (char[MAXPATHLEN])); } kmem_free(occupant, sizeof (hpc_occupant_info_t)); } static void pcihp_create_occupant_props(dev_info_t *self, dev_t dev, int pci_dev) { int circular; ndi_devi_enter(self, &circular); pcihp_create_occupant_props_nolock(self, dev, pci_dev); ndi_devi_exit(self, circular); } static void pcihp_delete_occupant_props(dev_info_t *dip, dev_t dev) { if (ddi_prop_remove(dev, dip, "pci-occupant") != DDI_PROP_SUCCESS) return; /* add error handling */ } /* * pcihp_ioctl: devctl hotplug controls */ /* ARGSUSED */ static int pcihp_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { pcihp_t *pcihp_p; dev_info_t *self; struct devctl_iocdata *dcp; uint_t bus_state; int rv = 0; int pci_dev; struct pcihp_slotinfo *slotinfop; hpc_slot_state_t rstate; devctl_ap_state_t ap_state; struct hpc_control_data hpc_ctrldata; struct hpc_led_info led_info; time_t time; int state_locking; int state_unlocking; int rval; char *pathname = NULL; /* * read devctl ioctl data before soft state retrieval */ if ((cmd != DEVCTL_AP_CONTROL) && ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) return (EFAULT); if (pcihp_info(NULL, DDI_INFO_DEVT2DEVINFO, (void *)dev, (void **)&self) != DDI_SUCCESS) { if (cmd != DEVCTL_AP_CONTROL) ndi_dc_freehdl(dcp); return (ENXIO); } switch (cmd) { case DEVCTL_AP_INSERT: case DEVCTL_AP_REMOVE: case DEVCTL_AP_CONNECT: case DEVCTL_AP_DISCONNECT: case DEVCTL_AP_CONFIGURE: case DEVCTL_AP_UNCONFIGURE: case DEVCTL_AP_GETSTATE: case DEVCTL_AP_CONTROL: state_locking = PCIHP_DR_SLOT_ENTER; state_unlocking = PCIHP_DR_SLOT_EXIT; break; default: state_locking = PCIHP_DR_NOOP; state_unlocking = PCIHP_DR_NOOP; break; } pcihp_p = pcihp_get_soft_state(self, state_locking, &rval); ASSERT(pcihp_p != NULL); if (rval == PCIHP_FAILURE) { (void) ddi_pathname(pcihp_p->dip, pathname); PCIHP_DEBUG((CE_WARN, "Hot Plug bus %s instance is unconfigured" " while slot activity is requested\n", pathname)); if (cmd != DEVCTL_AP_CONTROL) ndi_dc_freehdl(dcp); return (EBUSY); } /* * For attachment points the lower 8 bits of the minor number is the * PCI device number. */ pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(getminor(dev)); /* * We can use the generic implementation for these ioctls */ switch (cmd) { case DEVCTL_DEVICE_GETSTATE: case DEVCTL_DEVICE_ONLINE: case DEVCTL_DEVICE_OFFLINE: case DEVCTL_BUS_GETSTATE: rv = ndi_devctl_ioctl(self, cmd, arg, mode, 0); ndi_dc_freehdl(dcp); return (rv); default: break; } switch (cmd) { case DEVCTL_DEVICE_RESET: rv = ENOTSUP; break; case DEVCTL_BUS_QUIESCE: if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS) if (bus_state == BUS_QUIESCED) break; (void) ndi_set_bus_state(self, BUS_QUIESCED); break; case DEVCTL_BUS_UNQUIESCE: if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS) if (bus_state == BUS_ACTIVE) break; (void) ndi_set_bus_state(self, BUS_ACTIVE); break; case DEVCTL_BUS_RESET: rv = ENOTSUP; break; case DEVCTL_BUS_RESETALL: rv = ENOTSUP; break; case DEVCTL_AP_CONNECT: case DEVCTL_AP_DISCONNECT: /* * CONNECT(DISCONNECT) the hot plug slot to(from) the bus. * * For cPCI slots this operation is a nop so the HPC * driver may return success if it is a valid operation. */ case DEVCTL_AP_INSERT: case DEVCTL_AP_REMOVE: /* * Prepare the slot for INSERT/REMOVE operation. */ /* * check for valid request: * 1. It is a hotplug slot. * 2. The slot has no occupant that is in * the 'configured' state. */ if (pci_dev >= PCI_MAX_DEVS) { rv = ENXIO; break; } slotinfop = &pcihp_p->slotinfo[pci_dev]; mutex_enter(&slotinfop->slot_mutex); if ((slotinfop->slot_hdl == NULL) || (slotinfop->slot_flags & PCIHP_SLOT_DISABLED)) { rv = ENXIO; mutex_exit(&slotinfop->slot_mutex); break; } /* the slot occupant must be in the UNCONFIGURED state */ if (slotinfop->ostate != AP_OSTATE_UNCONFIGURED) { rv = EINVAL; mutex_exit(&slotinfop->slot_mutex); break; } /* * Call the HPC driver to perform the operation on the slot. */ switch (cmd) { case DEVCTL_AP_INSERT: rv = hpc_nexus_insert(slotinfop->slot_hdl, NULL, 0); break; case DEVCTL_AP_REMOVE: rv = hpc_nexus_remove(slotinfop->slot_hdl, NULL, 0); break; case DEVCTL_AP_CONNECT: rv = hpc_nexus_connect(slotinfop->slot_hdl, NULL, 0); if (rv == HPC_SUCCESS) { slotinfop->rstate = AP_RSTATE_CONNECTED; if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) slotinfop->last_change = (time_t)-1; else slotinfop->last_change = (time32_t)time; slotinfop = &pcihp_p->slotinfo[pci_dev]; pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_NO_HINT, pcihp_p->dip, KM_SLEEP); } break; case DEVCTL_AP_DISCONNECT: rv = hpc_nexus_disconnect(slotinfop->slot_hdl, NULL, 0); if (rv == HPC_SUCCESS) { slotinfop->rstate = AP_RSTATE_DISCONNECTED; if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) slotinfop->last_change = (time_t)-1; else slotinfop->last_change = (time32_t)time; slotinfop = &pcihp_p->slotinfo[pci_dev]; pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_NO_HINT, pcihp_p->dip, KM_SLEEP); } break; } mutex_exit(&slotinfop->slot_mutex); switch (rv) { case HPC_ERR_INVALID: rv = ENXIO; break; case HPC_ERR_NOTSUPPORTED: rv = ENOTSUP; break; case HPC_ERR_FAILED: rv = EIO; break; } break; case DEVCTL_AP_CONFIGURE: /* * ************************************** * CONFIGURE the occupant in the slot. * ************************************** */ rv = pcihp_configure_ap(pcihp_p, pci_dev); if (rv == HPC_SUCCESS) { slotinfop = &pcihp_p->slotinfo[pci_dev]; pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_NO_HINT, pcihp_p->dip, KM_SLEEP); pcihp_create_occupant_props(self, dev, pci_dev); } break; case DEVCTL_AP_UNCONFIGURE: /* * ************************************** * UNCONFIGURE the occupant in the slot. * ************************************** */ rv = pcihp_unconfigure_ap(pcihp_p, pci_dev); if (rv == HPC_SUCCESS) { slotinfop = &pcihp_p->slotinfo[pci_dev]; pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_NO_HINT, pcihp_p->dip, KM_SLEEP); pcihp_delete_occupant_props(pcihp_p->dip, dev); } break; case DEVCTL_AP_GETSTATE: { int mutex_held; /* * return the state of Attachment Point. * * If the occupant is in UNCONFIGURED state then * we should get the receptacle state from the * HPC driver because the receptacle state * maintained in the nexus may not be accurate. */ /* * check for valid request: * 1. It is a hotplug slot. */ slotinfop = &pcihp_p->slotinfo[pci_dev]; /* try to acquire the slot mutex */ mutex_held = mutex_tryenter(&slotinfop->slot_mutex); if (pci_dev >= PCI_MAX_DEVS || slotinfop->slot_hdl == NULL) { rv = ENXIO; if (mutex_held) { mutex_exit(&slotinfop->slot_mutex); } break; } if (slotinfop->ostate == AP_OSTATE_UNCONFIGURED) { if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_GET_SLOT_STATE, (caddr_t)&rstate) != 0) { rv = EIO; if (mutex_held) mutex_exit(&slotinfop->slot_mutex); break; } slotinfop->rstate = (ap_rstate_t)rstate; } ap_state.ap_rstate = slotinfop->rstate; ap_state.ap_ostate = slotinfop->ostate; ap_state.ap_condition = slotinfop->condition; ap_state.ap_last_change = slotinfop->last_change; ap_state.ap_error_code = 0; /* XXX */ if (mutex_held) ap_state.ap_in_transition = 0; /* AP is not busy */ else ap_state.ap_in_transition = 1; /* AP is busy */ if (mutex_held) mutex_exit(&slotinfop->slot_mutex); /* copy the return-AP-state information to the user space */ if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) rv = EFAULT; break; } case DEVCTL_AP_CONTROL: /* * HPC control functions: * HPC_CTRL_ENABLE_SLOT/HPC_CTRL_DISABLE_SLOT * Changes the state of the slot and preserves * the state across the reboot. * HPC_CTRL_ENABLE_AUTOCFG/HPC_CTRL_DISABLE_AUTOCFG * Enables or disables the auto configuration * of hot plugged occupant if the hardware * supports notification of the hot plug * events. * HPC_CTRL_GET_LED_STATE/HPC_CTRL_SET_LED_STATE * Controls the state of an LED. * HPC_CTRL_GET_SLOT_INFO * Get slot information data structure * (hpc_slot_info_t). * HPC_CTRL_GET_BOARD_TYPE * Get board type information (hpc_board_type_t). * HPC_CTRL_GET_CARD_INFO * Get card information (hpc_card_info_t). * * These control functions are used by the cfgadm plug-in * to implement "-x" and "-v" options. */ /* copy user ioctl data first */ #ifdef _MULTI_DATAMODEL if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { struct hpc_control32_data hpc_ctrldata32; if (copyin((void *)arg, (void *)&hpc_ctrldata32, sizeof (struct hpc_control32_data)) != 0) { rv = EFAULT; break; } hpc_ctrldata.cmd = hpc_ctrldata32.cmd; hpc_ctrldata.data = (void *)(intptr_t)hpc_ctrldata32.data; } #else if (copyin((void *)arg, (void *)&hpc_ctrldata, sizeof (struct hpc_control_data)) != 0) { rv = EFAULT; break; } #endif /* * check for valid request: * 1. It is a hotplug slot. */ slotinfop = &pcihp_p->slotinfo[pci_dev]; mutex_enter(&slotinfop->slot_mutex); if (pci_dev >= PCI_MAX_DEVS || slotinfop->slot_hdl == NULL) { rv = ENXIO; mutex_exit(&slotinfop->slot_mutex); break; } switch (hpc_ctrldata.cmd) { case HPC_CTRL_GET_LED_STATE: /* copy the led info from the user space */ if (copyin(hpc_ctrldata.data, (void *)&led_info, sizeof (hpc_led_info_t)) != 0) { rv = EFAULT; break; } /* get the state of LED information */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_GET_LED_STATE, (caddr_t)&led_info) != 0) { if (rv != ENOTSUP) rv = EIO; break; } /* copy the led info to the user space */ if (copyout((void *)&led_info, hpc_ctrldata.data, sizeof (hpc_led_info_t)) != 0) { rv = EFAULT; break; } break; case HPC_CTRL_SET_LED_STATE: /* copy the led info from the user space */ if (copyin(hpc_ctrldata.data, (void *)&led_info, sizeof (hpc_led_info_t)) != 0) { rv = EFAULT; break; } /* set the state of an LED */ rv = hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_SET_LED_STATE, (caddr_t)&led_info); /* * If the Hotswap Controller does not support * LED management (as you would find typically * in the cPCI industry), then we handle the * blue LED on/off/blink operations, just in * case it helps slot identification. */ if ((rv == HPC_ERR_NOTSUPPORTED) && (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI)) { if (led_info.led != HPC_ATTN_LED) break; switch (led_info.state) { case HPC_LED_OFF: pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); rv = 0; break; case HPC_LED_ON: /* * Please note that leaving * LED ON could be dangerous * as it means it is Ok to * remove the board, which * is not what we want to * convey. So it is upto the * user to take care of this * situation and usage. * * Normally, a Blink command * is more appropriate for * identifying a board. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); rv = 0; break; case HPC_LED_BLINK: { int bl; for (bl = 0; bl < 2; bl++) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); delay(pcihp_cpci_led_blink); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); delay(pcihp_cpci_led_blink); } rv = 0; break; } default: break; } } if (rv == HPC_ERR_FAILED) rv = EIO; break; case HPC_CTRL_ENABLE_SLOT: /* * If slot already enabled, do not send a duplicate * control message to the HPC driver. */ if ((slotinfop->slot_flags & PCIHP_SLOT_DISABLED) == 0) break; /* tell the HPC driver also */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_ENABLE_SLOT, NULL) != HPC_SUCCESS) { rv = EIO; break; } /* * Enable the slot for hotplug operations. */ slotinfop->slot_flags &= ~PCIHP_SLOT_DISABLED; slotinfop->condition = AP_COND_UNKNOWN; /* XXX need to preserve this state across reboot? */ break; case HPC_CTRL_DISABLE_SLOT: /* Do not disable if occupant configured */ if (slotinfop->ostate == AP_OSTATE_CONFIGURED) { rv = EAGAIN; break; } /* tell the HPC driver also */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DISABLE_SLOT, NULL) != HPC_SUCCESS) { rv = EIO; break; } /* * Disable the slot for hotplug operations. */ slotinfop->slot_flags |= PCIHP_SLOT_DISABLED; slotinfop->condition = AP_COND_UNUSABLE; /* XXX need to preserve this state across reboot? */ break; case HPC_CTRL_ENABLE_AUTOCFG: /* * Enable auto configuration on this slot. */ slotinfop->slot_flags |= PCIHP_SLOT_AUTO_CFG_EN; /* tell the HPC driver also */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_ENABLE_AUTOCFG, NULL); if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_ENABLE_ENUM); break; case HPC_CTRL_DISABLE_AUTOCFG: /* * Disable auto configuration on this slot. */ slotinfop->slot_flags &= ~PCIHP_SLOT_AUTO_CFG_EN; /* tell the HPC driver also */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DISABLE_AUTOCFG, NULL); if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_DISABLE_ENUM); break; case HPC_CTRL_GET_BOARD_TYPE: { hpc_board_type_t board_type; /* * Get board type data structure, hpc_board_type_t. */ board_type = pcihp_get_board_type(slotinfop); if (board_type == -1) { rv = ENXIO; break; } /* copy the board type info to the user space */ if (copyout((void *)&board_type, hpc_ctrldata.data, sizeof (hpc_board_type_t)) != 0) { rv = ENXIO; break; } break; } case HPC_CTRL_GET_SLOT_INFO: { hpc_slot_info_t slot_info; /* * Get slot information structure, hpc_slot_info_t. */ slot_info.version = HPC_SLOT_INFO_VERSION; slot_info.slot_type = slotinfop->slot_type; slot_info.pci_slot_capabilities = slotinfop->slot_capabilities; slot_info.pci_dev_num = (uint16_t)pci_dev; (void) strcpy(slot_info.pci_slot_name, slotinfop->name); /* copy the slot info structure to the user space */ if (copyout((void *)&slot_info, hpc_ctrldata.data, sizeof (hpc_slot_info_t)) != 0) { rv = EFAULT; break; } break; } case HPC_CTRL_GET_CARD_INFO: { hpc_card_info_t card_info; ddi_acc_handle_t handle; dev_info_t *cdip; /* * Get card information structure, hpc_card_info_t. */ /* verify that the card is configured */ if ((slotinfop->ostate != AP_OSTATE_CONFIGURED) || ((cdip = pcihp_devi_find(self, pci_dev, 0)) == NULL)) { /* either the card is not present or */ /* it is not configured. */ rv = ENXIO; break; } /* * If declared failed, don't allow Config operations. * Otherwise, if good or failing, it is assumed Ok * to get config data. */ if (slotinfop->condition == AP_COND_FAILED) { rv = EIO; break; } /* get the information from the PCI config header */ /* for the function 0. */ if (pci_config_setup(cdip, &handle) != DDI_SUCCESS) { rv = EIO; break; } card_info.prog_class = pci_config_get8(handle, PCI_CONF_PROGCLASS); card_info.base_class = pci_config_get8(handle, PCI_CONF_BASCLASS); card_info.sub_class = pci_config_get8(handle, PCI_CONF_SUBCLASS); card_info.header_type = pci_config_get8(handle, PCI_CONF_HEADER); pci_config_teardown(&handle); /* copy the card info structure to the user space */ if (copyout((void *)&card_info, hpc_ctrldata.data, sizeof (hpc_card_info_t)) != 0) { rv = EFAULT; break; } break; } default: rv = EINVAL; break; } mutex_exit(&slotinfop->slot_mutex); break; default: rv = ENOTTY; } if (cmd != DEVCTL_AP_CONTROL) ndi_dc_freehdl(dcp); (void) pcihp_get_soft_state(self, state_unlocking, &rval); return (rv); } /* * ************************************** * CONFIGURE the occupant in the slot. * ************************************** */ static int pcihp_configure_ap(pcihp_t *pcihp_p, int pci_dev) { dev_info_t *self = pcihp_p->dip; int rv = HPC_SUCCESS; struct pcihp_slotinfo *slotinfop; hpc_slot_state_t rstate; struct pcihp_config_ctrl ctrl; int circular_count; time_t time; /* * check for valid request: * 1. It is a hotplug slot. * 2. The receptacle is in the CONNECTED state. */ slotinfop = &pcihp_p->slotinfo[pci_dev]; mutex_enter(&slotinfop->slot_mutex); if ((pci_dev >= PCI_MAX_DEVS) || (slotinfop->slot_hdl == NULL) || (slotinfop->slot_flags & PCIHP_SLOT_DISABLED)) { mutex_exit(&slotinfop->slot_mutex); return (ENXIO); } /* * If the occupant is already in (partially?) configured * state then call the ndi_devi_online() on the device * subtree(s) for this attachment point. */ if (slotinfop->ostate == AP_OSTATE_CONFIGURED) { ctrl.flags = PCIHP_CFG_CONTINUE; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; ndi_devi_enter(self, &circular_count); ddi_walk_devs(ddi_get_child(self), pcihp_configure, (void *)&ctrl); ndi_devi_exit(self, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * one or more of the devices are not * onlined. How is this to be reported? */ cmn_err(CE_WARN, "pcihp (%s%d): failed to attach one or" " more drivers for the card in the slot %s", ddi_driver_name(self), ddi_get_instance(self), slotinfop->name); /* rv = EFAULT; */ } /* tell HPC driver that the occupant is configured */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIGURED, NULL); if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) slotinfop->last_change = (time_t)-1; else slotinfop->last_change = (time32_t)time; mutex_exit(&slotinfop->slot_mutex); return (rv); } /* * Occupant is in the UNCONFIGURED state. */ /* Check if the receptacle is in the CONNECTED state. */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_GET_SLOT_STATE, (caddr_t)&rstate) != 0) { mutex_exit(&slotinfop->slot_mutex); return (ENXIO); } if (rstate == HPC_SLOT_EMPTY) { /* error. slot is empty */ mutex_exit(&slotinfop->slot_mutex); return (ENXIO); } if (rstate != HPC_SLOT_CONNECTED) { /* error. either the slot is empty or connect failed */ mutex_exit(&slotinfop->slot_mutex); return (ENXIO); } slotinfop->rstate = AP_RSTATE_CONNECTED; /* record rstate */ /* Turn INS and LED off, and start configuration. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_CONFIGURE); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); slotinfop->slot_flags &= ~PCIHP_SLOT_ENUM_INS_PENDING; } (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_START, NULL); /* * Call the configurator to configure the card. */ if (pcicfg_configure(self, pci_dev) != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); } /* tell HPC driver occupant configure Error */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_FAILURE, NULL); mutex_exit(&slotinfop->slot_mutex); return (EIO); } /* record the occupant state as CONFIGURED */ slotinfop->ostate = AP_OSTATE_CONFIGURED; slotinfop->condition = AP_COND_OK; /* now, online all the devices in the AP */ ctrl.flags = PCIHP_CFG_CONTINUE; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; ndi_devi_enter(self, &circular_count); ddi_walk_devs(ddi_get_child(self), pcihp_configure, (void *)&ctrl); ndi_devi_exit(self, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * one or more of the devices are not * ONLINE'd. How is this to be * reported? */ cmn_err(CE_WARN, "pcihp (%s%d): failed to attach one or" " more drivers for the card in" " the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /* rv = EFAULT; */ } /* store HS_CSR location. No events, jut a read operation. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) pcihp_hs_csr_op(pcihp_p, pci_dev, -1); /* tell HPC driver that the occupant is configured */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIGURED, NULL); mutex_exit(&slotinfop->slot_mutex); return (rv); } /* * ************************************** * UNCONFIGURE the occupant in the slot. * ************************************** */ static int pcihp_unconfigure_ap(pcihp_t *pcihp_p, int pci_dev) { dev_info_t *self = pcihp_p->dip; int rv = HPC_SUCCESS; struct pcihp_slotinfo *slotinfop; struct pcihp_config_ctrl ctrl; int circular_count; time_t time; /* * check for valid request: * 1. It is a hotplug slot. * 2. The occupant is in the CONFIGURED state. */ slotinfop = &pcihp_p->slotinfo[pci_dev]; mutex_enter(&slotinfop->slot_mutex); if ((pci_dev >= PCI_MAX_DEVS) || (slotinfop->slot_hdl == NULL) || (slotinfop->slot_flags & PCIHP_SLOT_DISABLED)) { mutex_exit(&slotinfop->slot_mutex); return (ENXIO); } /* * The following may not need to be there, as we should * support unconfiguring of boards and free resources * even when the board is not hotswappable. But this is * the only way, we may be able to tell the system * administrator that it is not a hotswap board since * disconnect operation is never called. * This way we help the system administrator from not * accidentally removing a non hotswap board and * possibly destroying it. May be this behavior can * be a default, and can be enabled or disabled via * a global flag. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (slotinfop->slot_flags & PCIHP_SLOT_DEV_NON_HOTPLUG) { /* Operation unsupported if no HS board/slot */ mutex_exit(&slotinfop->slot_mutex); return (ENOTSUP); } } /* * If the occupant is in the CONFIGURED state then * call the configurator to unconfigure the slot. */ if (slotinfop->ostate == AP_OSTATE_CONFIGURED) { /* * since potential state change is imminent mask * enum events to prevent the slot from being re-configured */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_DISABLE_ENUM); /* * Detach all the drivers for the devices in the * slot. Call pcihp_configure() to do this. */ ctrl.flags = 0; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_OFFLINE; (void) devfs_clean(self, NULL, DV_CLEAN_FORCE); ndi_devi_enter(self, &circular_count); ddi_walk_devs(ddi_get_child(self), pcihp_configure, (void *)&ctrl); ndi_devi_exit(self, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * Failed to detach one or more drivers * Restore the state of drivers which * are offlined during this operation. */ ctrl.flags = 0; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; ndi_devi_enter(self, &circular_count); ddi_walk_devs(ddi_get_child(self), pcihp_configure, (void *)&ctrl); ndi_devi_exit(self, circular_count); /* tell HPC driver that the occupant is Busy */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_FAILURE, NULL); rv = EBUSY; } else { (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_START, NULL); if (pcicfg_unconfigure(self, pci_dev) == PCICFG_SUCCESS) { /* * Now that resources are freed, * clear EXT and Turn LED ON. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); slotinfop->hs_csr_location = 0; slotinfop->slot_flags &= ~(PCIHP_SLOT_DEV_NON_HOTPLUG| PCIHP_SLOT_ENUM_EXT_PENDING); } slotinfop->ostate = AP_OSTATE_UNCONFIGURED; slotinfop->condition = AP_COND_UNKNOWN; /* * send the notification of state change * to the HPC driver. */ (void) hpc_nexus_control( slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIGURED, NULL); } else { /* tell HPC driver occupant unconfigure Error */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_FAILURE, NULL); rv = EIO; } } } if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) slotinfop->last_change = (time_t)-1; else slotinfop->last_change = (time32_t)time; mutex_exit(&slotinfop->slot_mutex); /* unmask enum events again */ if ((slotinfop->slot_flags & PCIHP_SLOT_AUTO_CFG_EN) == 0) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_ENABLE_ENUM); } return (rv); } /* * Accessor function to return pointer to the pci hotplug * cb_ops structure. */ struct cb_ops * pcihp_get_cb_ops() { return (&pcihp_cb_ops); } /* * Setup function to initialize hot plug feature. Returns DDI_SUCCESS * for successful initialization, otherwise it returns DDI_FAILURE. * * It is assumed that this this function is called from the attach() * entry point of the PCI nexus driver. */ int pcihp_init(dev_info_t *dip) { pcihp_t *pcihp_p; int i; caddr_t enum_data; int enum_size; int rv; mutex_enter(&pcihp_open_mutex); /* * Make sure that it is not already initialized. */ if (pcihp_get_soft_state(dip, PCIHP_DR_NOOP, &rv) != NULL) { cmn_err(CE_WARN, "%s%d: pcihp instance already initialized!", ddi_driver_name(dip), ddi_get_instance(dip)); goto cleanup; } /* * Initialize soft state structure for the bus instance. */ if ((pcihp_p = pcihp_create_soft_state(dip)) == NULL) { cmn_err(CE_WARN, "%s%d: can't allocate pcihp structure", ddi_driver_name(dip), ddi_get_instance(dip)); goto cleanup; } pcihp_p->soft_state = PCIHP_SOFT_STATE_CLOSED; /* XXX if bus is running at 66Mhz then set PCI_BUS_66MHZ bit */ pcihp_p->bus_flags = 0; /* XXX FIX IT */ /* * If a platform wishes to implement Radial ENUM# routing * a property "enum-impl" must be presented to us with a * string value "radial". * This helps us not go for polling operation (default) * during a ENUM# event. */ if (ddi_getlongprop(DDI_DEV_T_ANY, dip, 0, "enum-impl", (caddr_t)&enum_data, &enum_size) == DDI_PROP_SUCCESS) { if (strcmp(enum_data, "radial") == 0) { pcihp_p->bus_flags |= PCIHP_BUS_ENUM_RADIAL; } kmem_free(enum_data, enum_size); } for (i = 0; i < PCI_MAX_DEVS; i++) { /* initialize slot mutex */ mutex_init(&pcihp_p->slotinfo[i].slot_mutex, NULL, MUTEX_DRIVER, NULL); } /* * register the bus instance with the HPS framework. */ if (hpc_nexus_register_bus(dip, pcihp_new_slot_state, 0) != 0) { cmn_err(CE_WARN, "%s%d: failed to register the bus with HPS", ddi_driver_name(dip), ddi_get_instance(dip)); goto cleanup1; } /* * Create the "devctl" minor for hot plug support. The minor * number for "devctl" node is in the same format as the AP * minor nodes. */ if (ddi_create_minor_node(dip, "devctl", S_IFCHR, PCIHP_AP_MINOR_NUM(ddi_get_instance(dip), PCIHP_DEVCTL_MINOR), DDI_NT_NEXUS, 0) != DDI_SUCCESS) goto cleanup2; /* * Setup resource maps for this bus node. (Note: This can * be done from the attach(9E) of the nexus itself.) */ (void) pci_resource_setup(dip); pcihp_p->bus_state = PCIHP_BUS_CONFIGURED; mutex_exit(&pcihp_open_mutex); return (DDI_SUCCESS); cleanup2: (void) hpc_nexus_unregister_bus(dip); cleanup1: for (i = 0; i < PCI_MAX_DEVS; i++) mutex_destroy(&pcihp_p->slotinfo[i].slot_mutex); pcihp_destroy_soft_state(dip); cleanup: mutex_exit(&pcihp_open_mutex); return (DDI_FAILURE); } /* * pcihp_uninit() * * The bus instance is going away, cleanup any data associated with * the management of hot plug slots. It is assumed that this function * is called from detach() routine of the PCI nexus driver. Also, * it is assumed that no devices on the bus are in the configured state. */ int pcihp_uninit(dev_info_t *dip) { pcihp_t *pcihp_p; int i, j; int rv; mutex_enter(&pcihp_open_mutex); /* get a pointer to the soft state structure */ pcihp_p = pcihp_get_soft_state(dip, PCIHP_DR_BUS_UNCONFIGURE, &rv); ASSERT(pcihp_p != NULL); /* slot mutexes should prevent any configure/unconfigure access */ for (i = 0; i < PCI_MAX_DEVS; i++) { if (!mutex_tryenter(&pcihp_p->slotinfo[i].slot_mutex)) { for (j = 0; j < i; j++) { mutex_exit(&pcihp_p->slotinfo[j].slot_mutex); } mutex_exit(&pcihp_open_mutex); return (DDI_FAILURE); } } if ((pcihp_p->soft_state != PCIHP_SOFT_STATE_CLOSED) || (rv == PCIHP_FAILURE)) { cmn_err(CE_WARN, "%s%d: pcihp instance is busy", ddi_driver_name(dip), ddi_get_instance(dip)); for (i = 0; i < PCI_MAX_DEVS; i++) { mutex_exit(&pcihp_p->slotinfo[i].slot_mutex); } mutex_exit(&pcihp_open_mutex); return (DDI_FAILURE); } /* * Unregister the bus with the HPS. * * (Note: It is assumed that the HPS framework uninstalls * event handlers for all the hot plug slots on this bus.) */ (void) hpc_nexus_unregister_bus(dip); /* Free up any kmem_alloc'd memory for slot info table. */ for (i = 0; i < PCI_MAX_DEVS; i++) { /* free up slot name strings */ if (pcihp_p->slotinfo[i].name != NULL) kmem_free(pcihp_p->slotinfo[i].name, strlen(pcihp_p->slotinfo[i].name) + 1); } /* destroy slot mutexes */ for (i = 0; i < PCI_MAX_DEVS; i++) mutex_destroy(&pcihp_p->slotinfo[i].slot_mutex); ddi_remove_minor_node(dip, NULL); /* free up the soft state structure */ pcihp_destroy_soft_state(dip); /* * Destroy resource maps for this bus node. (Note: This can * be done from the detach(9E) of the nexus itself.) */ (void) pci_resource_destroy(dip); mutex_exit(&pcihp_open_mutex); return (DDI_SUCCESS); } /* * pcihp_new_slot_state() * * This function is called by the HPS when it finds a hot plug * slot is added or being removed from the hot plug framework. * It returns 0 for success and HPC_ERR_FAILED for errors. */ static int pcihp_new_slot_state(dev_info_t *dip, hpc_slot_t hdl, hpc_slot_info_t *slot_info, int slot_state) { pcihp_t *pcihp_p; struct pcihp_slotinfo *slotinfop; int pci_dev; minor_t ap_minor; major_t ap_major; int rv = 0; time_t time; int auto_enable = 1; int rval; /* get a pointer to the soft state structure */ pcihp_p = pcihp_get_soft_state(dip, PCIHP_DR_SLOT_ENTER, &rval); ASSERT(pcihp_p != NULL); if (rval == PCIHP_FAILURE) { PCIHP_DEBUG((CE_WARN, "pcihp instance is unconfigured" " while slot activity is requested\n")); return (HPC_ERR_FAILED); } pci_dev = slot_info->pci_dev_num; slotinfop = &pcihp_p->slotinfo[pci_dev]; mutex_enter(&slotinfop->slot_mutex); switch (slot_state) { case HPC_SLOT_ONLINE: /* * Make sure the slot is not already ONLINE (paranoia?). * (Note: Should this be simply an ASSERTION?) */ if (slotinfop->slot_hdl != NULL) { PCIHP_DEBUG((CE_WARN, "pcihp (%s%d): pci slot (dev %x) already ONLINE!!", ddi_driver_name(dip), ddi_get_instance(dip), pci_dev)); rv = HPC_ERR_FAILED; break; } /* * Add the hot plug slot to the bus. */ /* create the AP minor node */ ap_minor = PCIHP_AP_MINOR_NUM(ddi_get_instance(dip), pci_dev); if (ddi_create_minor_node(dip, slot_info->pci_slot_name, S_IFCHR, ap_minor, DDI_NT_PCI_ATTACHMENT_POINT, 0) == DDI_FAILURE) { cmn_err(CE_WARN, "pcihp (%s%d): ddi_create_minor_node failed" " for pci dev %x", ddi_driver_name(dip), ddi_get_instance(dip), pci_dev); rv = HPC_ERR_FAILED; break; } /* save the slot handle */ slotinfop->slot_hdl = hdl; /* setup event handler for all hardware events on the slot */ ap_major = ddi_name_to_major(ddi_get_name(dip)); if (hpc_install_event_handler(hdl, -1, pcihp_event_handler, (caddr_t)makedevice(ap_major, ap_minor)) != 0) { cmn_err(CE_WARN, "pcihp (%s%d): install event handler failed" " for pci dev %x", ddi_driver_name(dip), ddi_get_instance(dip), pci_dev); rv = HPC_ERR_FAILED; break; } slotinfop->event_mask = (uint32_t)0xFFFFFFFF; pcihp_create_occupant_props(dip, makedevice(ap_major, ap_minor), pci_dev); /* set default auto configuration enabled flag for this slot */ slotinfop->slot_flags = pcihp_autocfg_enabled; /* copy the slot information */ slotinfop->name = kmem_alloc(strlen(slot_info->pci_slot_name) + 1, KM_SLEEP); (void) strcpy(slotinfop->name, slot_info->pci_slot_name); slotinfop->slot_type = slot_info->slot_type; slotinfop->hs_csr_location = 0; slotinfop->slot_capabilities = slot_info->pci_slot_capabilities; if (slot_info->slot_flags & HPC_SLOT_NO_AUTO_ENABLE) auto_enable = 0; if (slot_info->slot_flags & HPC_SLOT_CREATE_DEVLINK) { pci_devlink_flags |= (1 << pci_dev); (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, "ap-names", pci_devlink_flags); } PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): pci slot (dev %x) ONLINE\n", ddi_driver_name(dip), ddi_get_instance(dip), pci_dev)); /* * The slot may have an occupant that was configured * at boot time. If we find a devinfo node in the tree * for this slot (i.e pci device number) then we * record the occupant state as CONFIGURED. */ if (pcihp_devi_find(dip, pci_dev, 0) != NULL) { /* we have a configured occupant */ slotinfop->ostate = AP_OSTATE_CONFIGURED; slotinfop->rstate = AP_RSTATE_CONNECTED; slotinfop->condition = AP_COND_OK; if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { /* this will set slot flags too. */ (void) pcihp_get_board_type(slotinfop); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_CONFIGURE); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); /* ENUM# enabled by default for cPCI devices */ slotinfop->slot_flags |= PCIHP_SLOT_AUTO_CFG_EN; slotinfop->slot_flags &= ~PCIHP_SLOT_ENUM_INS_PENDING; } /* tell HPC driver that the occupant is configured */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIGURED, NULL); } else { struct pcihp_config_ctrl ctrl; int circular_count; slotinfop->ostate = AP_OSTATE_UNCONFIGURED; slotinfop->rstate = AP_RSTATE_EMPTY; slotinfop->condition = AP_COND_UNKNOWN; if (!auto_enable) { /* no further action */ break; } /* * We enable power to the slot and try to * configure if there is any card present. * * Note: This case is possible if the BIOS or * firmware doesn't enable the slots during * soft reboot. */ if (hpc_nexus_connect(slotinfop->slot_hdl, NULL, 0) != HPC_SUCCESS) break; if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_CONFIGURE); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); slotinfop->slot_flags |= PCIHP_SLOT_AUTO_CFG_EN; slotinfop->slot_flags &= ~PCIHP_SLOT_ENUM_INS_PENDING; } (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_START, NULL); /* * Call the configurator to configure the card. */ if (pcicfg_configure(dip, pci_dev) != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); } /* tell HPC driver occupant configure Error */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_FAILURE, NULL); /* * call HPC driver to turn off the power for * the slot. */ (void) hpc_nexus_disconnect(slotinfop->slot_hdl, NULL, 0); } else { /* record the occupant state as CONFIGURED */ slotinfop->ostate = AP_OSTATE_CONFIGURED; slotinfop->rstate = AP_RSTATE_CONNECTED; slotinfop->condition = AP_COND_OK; /* now, online all the devices in the AP */ ctrl.flags = PCIHP_CFG_CONTINUE; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; /* * the following sets slot_flags and * hs_csr_location too. */ (void) pcihp_get_board_type(slotinfop); ndi_devi_enter(dip, &circular_count); ddi_walk_devs(ddi_get_child(dip), pcihp_configure, (void *)&ctrl); ndi_devi_exit(dip, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * one or more of the devices are not * ONLINE'd. How is this to be * reported? */ cmn_err(CE_WARN, "pcihp (%s%d): failed to attach one or" " more drivers for the card in" " the slot %s", ddi_driver_name(dip), ddi_get_instance(dip), slotinfop->name); } /* tell HPC driver about the configured occupant */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIGURED, NULL); } } break; case HPC_SLOT_OFFLINE: /* * A hot plug slot is being removed from the bus. * Make sure there is no occupant configured on the * slot before removing the AP minor node. */ if (slotinfop->ostate != AP_OSTATE_UNCONFIGURED) { cmn_err(CE_WARN, "pcihp (%s%d): Card is still in configured" " state for pci dev %x", ddi_driver_name(dip), ddi_get_instance(dip), pci_dev); rv = HPC_ERR_FAILED; break; } /* * If the AP device is in open state then return * error. */ if (pcihp_p->soft_state != PCIHP_SOFT_STATE_CLOSED) { rv = HPC_ERR_FAILED; break; } if (slot_info->slot_flags & HPC_SLOT_CREATE_DEVLINK) { pci_devlink_flags &= ~(1 << pci_dev); (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, "ap-names", pci_devlink_flags); } /* remove the minor node */ ddi_remove_minor_node(dip, slotinfop->name); /* free up the memory for the name string */ kmem_free(slotinfop->name, strlen(slotinfop->name) + 1); /* update the slot info data */ slotinfop->name = NULL; slotinfop->slot_hdl = NULL; PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): pci slot (dev %x) OFFLINE\n", ddi_driver_name(dip), ddi_get_instance(dip), slot_info->pci_dev_num)); break; default: cmn_err(CE_WARN, "pcihp_new_slot_state: unknown slot_state %d", slot_state); rv = HPC_ERR_FAILED; } if (rv == 0) { if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS) slotinfop->last_change = (time_t)-1; else slotinfop->last_change = (time32_t)time; } mutex_exit(&slotinfop->slot_mutex); (void) pcihp_get_soft_state(dip, PCIHP_DR_SLOT_EXIT, &rval); return (rv); } /* * Event handler. It is assumed that this function is called from * a kernel context only. * * Parameters: * slot_arg AP minor number. * event_mask Event that occurred. */ static int pcihp_event_handler(caddr_t slot_arg, uint_t event_mask) { dev_t ap_dev = (dev_t)slot_arg; dev_info_t *self; pcihp_t *pcihp_p; int pci_dev; int rv = HPC_EVENT_CLAIMED; struct pcihp_slotinfo *slotinfop; struct pcihp_config_ctrl ctrl; int circular_count; int rval; /* * Get the soft state structure. */ if (pcihp_info(NULL, DDI_INFO_DEVT2DEVINFO, (void *)ap_dev, (void **)&self) != DDI_SUCCESS) return (ENXIO); pcihp_p = pcihp_get_soft_state(self, PCIHP_DR_SLOT_ENTER, &rval); ASSERT(pcihp_p != NULL); if (rval == PCIHP_FAILURE) { PCIHP_DEBUG((CE_WARN, "pcihp instance is unconfigured" " while slot activity is requested\n")); return (-1); } /* get the PCI device number for the slot */ pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(getminor(ap_dev)); slotinfop = &pcihp_p->slotinfo[pci_dev]; /* * All the events that may be handled in interrupt context should be * free of any mutex usage. */ switch (event_mask) { case HPC_EVENT_CLEAR_ENUM: /* * Check and clear ENUM# interrupt status. This may be * called by the Hotswap controller driver when it is * operating in a full hotswap system where the * platform may not have control on globally disabling ENUM#. * In such cases, the intent is to clear interrupt and * process the interrupt in non-interrupt context. * This is the first part of the ENUM# event processing. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): ENUM# is generated" " on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); /* this is the only event coming through in interrupt context */ rv = pcihp_handle_enum(pcihp_p, pci_dev, PCIHP_CLEAR_ENUM, KM_NOSLEEP); (void) pcihp_get_soft_state(self, PCIHP_DR_SLOT_EXIT, &rval); return (rv); default: break; } mutex_enter(&slotinfop->slot_mutex); switch (event_mask) { case HPC_EVENT_SLOT_INSERTION: /* * A card is inserted in the slot. Just report this * event and return. */ cmn_err(CE_NOTE, "pcihp (%s%d): card is inserted" " in the slot %s (pci dev %x)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name, pci_dev); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_CONFIGURE: /* * Configure the occupant that is just inserted in the slot. * The receptacle may or may not be in the connected state. If * the receptacle is not connected and the auto configuration * is enabled on this slot then connect the slot. If auto * configuration is enabled then configure the card. */ if ((slotinfop->slot_flags & PCIHP_SLOT_AUTO_CFG_EN) == 0) { /* * auto configuration is disabled. Tell someone * like RCM about this hotplug event? */ cmn_err(CE_NOTE, "pcihp (%s%d): SLOT_CONFIGURE event" " occurred for pci dev %x (slot %s)," " Slot disabled for auto-configuration.", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev, slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; } if (slotinfop->ostate == AP_OSTATE_CONFIGURED) { cmn_err(CE_WARN, "pcihp (%s%d): SLOT_CONFIGURE event" " re-occurred for pci dev %x (slot %s),", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev, slotinfop->name); mutex_exit(&slotinfop->slot_mutex); (void) pcihp_get_soft_state(self, PCIHP_DR_SLOT_EXIT, &rval); return (EAGAIN); } /* * Auto configuration is enabled. First, make sure the * receptacle is in the CONNECTED state. */ if ((rv = hpc_nexus_connect(slotinfop->slot_hdl, NULL, 0)) == HPC_SUCCESS) { slotinfop->rstate = AP_RSTATE_CONNECTED; /* record rstate */ } /* Clear INS and Turn LED Off and start configuring. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_CONFIGURE); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); } (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_START, NULL); /* * Call the configurator to configure the card. */ if (pcicfg_configure(pcihp_p->dip, pci_dev) != PCICFG_SUCCESS) { if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); } /* failed to configure the card */ cmn_err(CE_WARN, "pcihp (%s%d): failed to configure" " the card in the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /* failed to configure; disconnect the slot */ if (hpc_nexus_disconnect(slotinfop->slot_hdl, NULL, 0) == HPC_SUCCESS) { slotinfop->rstate = AP_RSTATE_DISCONNECTED; } /* tell HPC driver occupant configure Error */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIG_FAILURE, NULL); } else { /* record the occupant state as CONFIGURED */ slotinfop->ostate = AP_OSTATE_CONFIGURED; slotinfop->condition = AP_COND_OK; /* now, online all the devices in the AP */ ctrl.flags = PCIHP_CFG_CONTINUE; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; (void) pcihp_get_board_type(slotinfop); ndi_devi_enter(pcihp_p->dip, &circular_count); ddi_walk_devs(ddi_get_child(pcihp_p->dip), pcihp_configure, (void *)&ctrl); ndi_devi_exit(pcihp_p->dip, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * one or more of the devices are not * ONLINE'd. How is this to be * reported? */ cmn_err(CE_WARN, "pcihp (%s%d): failed to attach one or" " more drivers for the card in" " the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); } /* tell HPC driver that the occupant is configured */ (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_CONFIGURED, NULL); cmn_err(CE_NOTE, "pcihp (%s%d): card is CONFIGURED" " in the slot %s (pci dev %x)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name, pci_dev); } break; case HPC_EVENT_SLOT_UNCONFIGURE: /* * Unconfigure the occupant in this slot. */ if ((slotinfop->slot_flags & PCIHP_SLOT_AUTO_CFG_EN) == 0) { /* * auto configuration is disabled. Tell someone * like RCM about this hotplug event? */ cmn_err(CE_NOTE, "pcihp (%s%d): SLOT_UNCONFIGURE event" " for pci dev %x (slot %s) ignored," " Slot disabled for auto-configuration.", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev, slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; } if (slotinfop->ostate == AP_OSTATE_UNCONFIGURED) { cmn_err(CE_WARN, "pcihp (%s%d): SLOT_UNCONFIGURE " "event re-occurred for pci dev %x (slot %s),", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev, slotinfop->name); mutex_exit(&slotinfop->slot_mutex); (void) pcihp_get_soft_state(self, PCIHP_DR_SLOT_EXIT, &rval); return (EAGAIN); } /* * If the occupant is in the CONFIGURED state then * call the configurator to unconfigure the slot. */ if (slotinfop->ostate == AP_OSTATE_CONFIGURED) { /* * Detach all the drivers for the devices in the * slot. Call pcihp_configure() to offline the * devices. */ ctrl.flags = 0; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_OFFLINE; (void) devfs_clean(pcihp_p->dip, NULL, DV_CLEAN_FORCE); ndi_devi_enter(pcihp_p->dip, &circular_count); ddi_walk_devs(ddi_get_child(pcihp_p->dip), pcihp_configure, (void *)&ctrl); ndi_devi_exit(pcihp_p->dip, circular_count); if (ctrl.rv != NDI_SUCCESS) { /* * Failed to detach one or more drivers. * Restore the status for the drivers * which are offlined during this step. */ ctrl.flags = PCIHP_CFG_CONTINUE; ctrl.rv = NDI_SUCCESS; ctrl.dip = NULL; ctrl.pci_dev = pci_dev; ctrl.op = PCIHP_ONLINE; ndi_devi_enter(pcihp_p->dip, &circular_count); ddi_walk_devs(ddi_get_child(pcihp_p->dip), pcihp_configure, (void *)&ctrl); ndi_devi_exit(pcihp_p->dip, circular_count); rv = HPC_ERR_FAILED; } else { (void) hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_START, NULL); if (pcicfg_unconfigure(pcihp_p->dip, pci_dev) == PCICFG_SUCCESS) { /* Resources freed. Turn LED on. Clear EXT. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); slotinfop->hs_csr_location = 0; slotinfop->slot_flags &= ~PCIHP_SLOT_DEV_NON_HOTPLUG; } slotinfop->ostate = AP_OSTATE_UNCONFIGURED; slotinfop->condition = AP_COND_UNKNOWN; /* * send the notification of state change * to the HPC driver. */ (void) hpc_nexus_control( slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIGURED, NULL); /* disconnect the slot */ if (hpc_nexus_disconnect( slotinfop->slot_hdl, NULL, 0) == HPC_SUCCESS) { slotinfop->rstate = AP_RSTATE_DISCONNECTED; } cmn_err(CE_NOTE, "pcihp (%s%d): card is UNCONFIGURED" " in the slot %s (pci dev %x)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name, pci_dev); } else { /* tell HPC driver occupant is Busy */ (void) hpc_nexus_control( slotinfop->slot_hdl, HPC_CTRL_DEV_UNCONFIG_FAILURE, NULL); rv = HPC_ERR_FAILED; } } } /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_REMOVAL: /* * Card is removed from the slot. The card must have been * unconfigured before this event. */ if (slotinfop->ostate != AP_OSTATE_UNCONFIGURED) { panic("pcihp (%s%d): card is removed from" " the slot %s before doing unconfigure!!", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /*NOTREACHED*/ } cmn_err(CE_NOTE, "pcihp (%s%d): card is removed" " from the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_POWER_ON: /* * Slot is connected to the bus. i.e the card is powered * on. Are there any error conditions to be checked? */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): card is powered" " on in the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); slotinfop->rstate = AP_RSTATE_CONNECTED; /* record rstate */ /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_POWER_OFF: /* * Slot is disconnected from the bus. i.e the card is powered * off. Are there any error conditions to be checked? */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): card is powered" " off in the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); slotinfop->rstate = AP_RSTATE_DISCONNECTED; /* record rstate */ /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_LATCH_SHUT: /* * Latch on the slot is closed. */ cmn_err(CE_NOTE, "pcihp (%s%d): latch is shut" " for the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_LATCH_OPEN: /* * Latch on the slot is open. */ cmn_err(CE_NOTE, "pcihp (%s%d): latch is open" " for the slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_PROCESS_ENUM: /* * HSC knows the device number of the slot where the * ENUM# was triggered. * Now finish the necessary actions to be taken on that * slot. Please note that the interrupt is already cleared. * This is the second(last) part of the ENUM# event processing. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): processing ENUM#" " for slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); mutex_exit(&slotinfop->slot_mutex); rv = pcihp_enum_slot(pcihp_p, slotinfop, pci_dev, PCIHP_HANDLE_ENUM, KM_SLEEP); mutex_enter(&slotinfop->slot_mutex); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_BUS_ENUM: /* * Same as HPC_EVENT_SLOT_ENUM as defined the PSARC doc. * This term is used for better clarity of its usage. * * ENUM signal occurred on the bus. It may be from this * slot or any other hotplug slot on the bus. * * It is NOT recommended that the hotswap controller uses * event without queuing as NDI and other DDI calls may not * necessarily be invokable in interrupt context. * Hence the hotswap controller driver should use the * CLEAR_ENUM event which returns the slot device number * and then call HPC_EVENT_PROCESS_ENUM event with queuing. * * This can be used when the hotswap controller is * implementing a polled event mechanism to do the * necessary actions in a single call. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): ENUM# is generated" " on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); mutex_exit(&slotinfop->slot_mutex); rv = pcihp_handle_enum(pcihp_p, pci_dev, PCIHP_HANDLE_ENUM, KM_SLEEP); mutex_enter(&slotinfop->slot_mutex); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_BLUE_LED_ON: /* * Request to turn Hot Swap Blue LED on. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): Request To Turn On Blue " "LED on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); break; case HPC_EVENT_DISABLE_ENUM: /* * Disable ENUM# which disables auto configuration on this slot */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_DISABLE_ENUM); slotinfop->slot_flags &= ~PCIHP_SLOT_AUTO_CFG_EN; } break; case HPC_EVENT_ENABLE_ENUM: /* * Enable ENUM# which enables auto configuration on this slot. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_ENABLE_ENUM); slotinfop->slot_flags |= PCIHP_SLOT_AUTO_CFG_EN; } break; case HPC_EVENT_SLOT_BLUE_LED_OFF: /* * Request to turn Hot Swap Blue LED off. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): Request To Turn Off Blue " "LED on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); break; case HPC_EVENT_SLOT_NOT_HEALTHY: /* * HEALTHY# signal on this slot is not OK. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): HEALTHY# signal is not OK" " for this slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); /* record the state in slot_flags field */ slotinfop->slot_flags |= PCIHP_SLOT_NOT_HEALTHY; slotinfop->condition = AP_COND_FAILED; /* +++ HOOK for RCM to report this hotplug event? +++ */ break; case HPC_EVENT_SLOT_HEALTHY_OK: /* * HEALTHY# signal on this slot is OK now. */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): HEALTHY# signal is OK now" " for this slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); /* update the state in slot_flags field */ slotinfop->slot_flags &= ~PCIHP_SLOT_NOT_HEALTHY; slotinfop->condition = AP_COND_OK; /* +++ HOOK for RCM to report this hotplug event? +++ */ break; default: cmn_err(CE_NOTE, "pcihp (%s%d): unknown event %x" " for this slot %s", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), event_mask, slotinfop->name); /* +++ HOOK for RCM to report this hotplug event? +++ */ break; } mutex_exit(&slotinfop->slot_mutex); (void) pcihp_get_soft_state(self, PCIHP_DR_SLOT_EXIT, &rval); return (rv); } /* * This function is called to online or offline the devices for an * attachment point. If the PCI device number of the node matches * with the device number of the specified hot plug slot then * the operation is performed. */ static int pcihp_configure(dev_info_t *dip, void *hdl) { int pci_dev; struct pcihp_config_ctrl *ctrl = (struct pcihp_config_ctrl *)hdl; int rv; pci_regspec_t *pci_rp; int length; /* * Get the PCI device number information from the devinfo * node. Since the node may not have the address field * setup (this is done in the DDI_INITCHILD of the parent) * we look up the 'reg' property to decode that information. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { ctrl->rv = DDI_FAILURE; ctrl->dip = dip; return (DDI_WALK_TERMINATE); } /* get the pci device id information */ pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); /* * free the memory allocated by ddi_prop_lookup_int_array */ ddi_prop_free(pci_rp); /* * Match the node for the device number of the slot. */ if (pci_dev == ctrl->pci_dev) { /* node is a match */ if (ctrl->op == PCIHP_ONLINE) { /* it is CONFIGURE operation */ rv = ndi_devi_online(dip, NDI_ONLINE_ATTACH|NDI_CONFIG); } else { /* * it is UNCONFIGURE operation. */ rv = ndi_devi_offline(dip, NDI_UNCONFIG); } if (rv != NDI_SUCCESS) { /* failed to attach/detach the driver(s) */ ctrl->rv = rv; ctrl->dip = dip; /* terminate the search if specified */ if (!(ctrl->flags & PCIHP_CFG_CONTINUE)) return (DDI_WALK_TERMINATE); } } /* * continue the walk to the next sibling to look for a match * or to find other nodes if this card is a multi-function card. */ return (DDI_WALK_PRUNECHILD); } /* control structure used to find a device in the devinfo tree */ struct pcihp_find_ctrl { uint_t device; uint_t function; dev_info_t *dip; }; static dev_info_t * pcihp_devi_find(dev_info_t *dip, uint_t device, uint_t function) { struct pcihp_find_ctrl ctrl; int circular_count; ctrl.device = device; ctrl.function = function; ctrl.dip = NULL; ndi_devi_enter(dip, &circular_count); ddi_walk_devs(ddi_get_child(dip), pcihp_match_dev, (void *)&ctrl); ndi_devi_exit(dip, circular_count); return (ctrl.dip); } static int pcihp_match_dev(dev_info_t *dip, void *hdl) { struct pcihp_find_ctrl *ctrl = (struct pcihp_find_ctrl *)hdl; pci_regspec_t *pci_rp; int length; int pci_dev; int pci_func; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) { ctrl->dip = NULL; return (DDI_WALK_TERMINATE); } /* get the PCI device address info */ pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi); pci_func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi); /* * free the memory allocated by ddi_prop_lookup_int_array */ ddi_prop_free(pci_rp); if ((pci_dev == ctrl->device) && (pci_func == ctrl->function)) { /* found the match for the specified device address */ ctrl->dip = dip; return (DDI_WALK_TERMINATE); } /* * continue the walk to the next sibling to look for a match. */ return (DDI_WALK_PRUNECHILD); } #if 0 /* * Probe the configuration space of the slot to determine the receptacle * state. There may not be any devinfo tree created for this slot. */ static void pcihp_probe_slot_state(dev_info_t *dip, int dev, hpc_slot_state_t *rstatep) { /* XXX FIX IT */ } #endif /* * This routine is called when a ENUM# assertion is detected for a bus. * Since ENUM# may be bussed, the slot that asserted ENUM# may not be known. * The HPC Driver passes the handle of a slot that is its best guess. * If the best guess slot is the one that asserted ENUM#, the proper handling * will be done. If its not, all possible slots will be locked at until * one that is asserting ENUM is found. * Also, indicate to the HSC to turn on ENUM# after it is serviced, * incase if it was disabled by the HSC due to the nature of asynchronous * delivery of interrupt by the framework. * * opcode has the following meanings. * PCIHP_CLEAR_ENUM = just clear interrupt and return the PCI device no. if * success, else return -1. * PCIHP_HANDLE_ENUM = clear interrupt and handle interrupt also. * */ static int pcihp_handle_enum(pcihp_t *pcihp_p, int favorite_pci_dev, int opcode, int kmflag) { struct pcihp_slotinfo *slotinfop; int pci_dev, rc, event_serviced = 0; /* * Handle ENUM# condition for the "favorite" slot first. */ slotinfop = &pcihp_p->slotinfo[favorite_pci_dev]; if (slotinfop) { /* * First try the "favorite" pci device. This is the device * associated with the handle passed by the HPC Driver. */ rc = pcihp_enum_slot(pcihp_p, slotinfop, favorite_pci_dev, opcode, kmflag); if (rc != HPC_EVENT_UNCLAIMED) { /* indicates success */ event_serviced = 1; /* This MUST be a non-DEBUG feature. */ if (! pcihp_enum_scan_all) { return (rc); } } } /* * If ENUM# is implemented as a radial signal, then there is no * need to further poll the slots. */ if (pcihp_p->bus_flags & PCIHP_BUS_ENUM_RADIAL) goto enum_service_check; /* * If the "favorite" pci device didn't assert ENUM#, then * try the rest. Once we find and handle a device that asserted * ENUM#, then we will terminate the walk by returning unless * scan-all flag is set. */ for (pci_dev = 0; pci_dev < PCI_MAX_DEVS; pci_dev++) { if (pci_dev != favorite_pci_dev) { slotinfop = &pcihp_p->slotinfo[pci_dev]; if (slotinfop == NULL) { continue; } /* Only CPCI devices support ENUM# generation. */ if (!(slotinfop->slot_type & HPC_SLOT_TYPE_CPCI)) continue; rc = pcihp_enum_slot(pcihp_p, slotinfop, pci_dev, opcode, kmflag); if (rc != HPC_EVENT_UNCLAIMED) { event_serviced = 1; /* This MUST be a non-DEBUG feature. */ if (! pcihp_enum_scan_all) break; } } } enum_service_check: if (event_serviced) { return (rc); } /* No ENUM# event found, Return */ return (HPC_EVENT_UNCLAIMED); } /* * This routine attempts to handle a possible ENUM# assertion case for a * specified slot. This only works for adapters that implement Hot Swap * Friendly Silicon. If the slot's HS_CSR is read and it specifies ENUM# * has been asserted, either the insertion or removal handlers will be * called. */ static int pcihp_enum_slot(pcihp_t *pcihp_p, struct pcihp_slotinfo *slotinfop, int pci_dev, int opcode, int kmflag) { ddi_acc_handle_t handle; dev_info_t *dip, *new_child = NULL; int result, rv = -1; uint8_t hs_csr; if (pcihp_config_setup(&dip, &handle, &new_child, pci_dev, pcihp_p) != DDI_SUCCESS) { return (HPC_EVENT_UNCLAIMED); } /* * Read the device's HS_CSR. */ result = pcihp_get_hs_csr(slotinfop, handle, (uint8_t *)&hs_csr); PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): hs_csr = %x, flags = %x", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), hs_csr, slotinfop->slot_flags)); /* * we teardown our device map here, because in case of an * extraction event, our nodes would be freed and a teardown * will cause problems. */ pcihp_config_teardown(&handle, &new_child, pci_dev, pcihp_p); if (result == PCIHP_SUCCESS) { /* If ENUM# is masked, then it is not us. Some other device */ if ((hs_csr & HS_CSR_EIM) && (opcode == PCIHP_CLEAR_ENUM)) return (HPC_EVENT_UNCLAIMED); /* * This device supports Full Hot Swap and implements * the Hot Swap Control and Status Register. */ if ((hs_csr & HS_CSR_INS) || (slotinfop->slot_flags & PCIHP_SLOT_ENUM_INS_PENDING)) { /* handle insertion ENUM */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): " "Handle Insertion ENUM (INS) " "on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); /* * generate sysevent */ if (opcode == PCIHP_CLEAR_ENUM) pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_REQ, SE_INCOMING_RES, pcihp_p->dip, kmflag); rv = pcihp_handle_enum_insertion(pcihp_p, pci_dev, opcode, kmflag); } else if ((hs_csr & HS_CSR_EXT) || (slotinfop->slot_flags & PCIHP_SLOT_ENUM_EXT_PENDING)) { /* handle extraction ENUM */ PCIHP_DEBUG((CE_NOTE, "pcihp (%s%d): " "Handle Extraction ENUM (EXT) " "on the bus (for slot %s ?)", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), slotinfop->name)); /* * generate sysevent */ if (opcode == PCIHP_CLEAR_ENUM) pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_REQ, SE_OUTGOING_RES, pcihp_p->dip, kmflag); rv = pcihp_handle_enum_extraction(pcihp_p, pci_dev, opcode, kmflag); } if (opcode == PCIHP_CLEAR_ENUM) { if (rv == PCIHP_SUCCESS) rv = pci_dev; else rv = HPC_EVENT_UNCLAIMED; } } return (rv); } /* * This routine is called when a ENUM# caused by lifting the lever * is detected. If the occupant is configured, it will be unconfigured. * If the occupant is already unconfigured or is successfully unconfigured, * the blue LED on the adapter is illuminated which means its OK to remove. * Please note that the lock must be released before invoking the * generic AP unconfigure function. */ static int pcihp_handle_enum_extraction(pcihp_t *pcihp_p, int pci_dev, int opcode, int kmflag) { struct pcihp_slotinfo *slotinfop; int rv = PCIHP_FAILURE; slotinfop = &pcihp_p->slotinfo[pci_dev]; /* * It was observed that, clearing the EXT bit turned the LED ON. * This is a BIG problem in case if the unconfigure operation * failed because the board was busy. * In order to avoid this confusing situation (LED ON but the board * is not unconfigured), we instead decided not to clear EXT but * disable further ENUM# from this slot. Disabling ENUM# clears * the interrupt. * Finally before returning we clear the interrupt and enable * ENUM# back again from this slot. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_DISABLE_ENUM); if (opcode == PCIHP_CLEAR_ENUM) { slotinfop->slot_flags |= PCIHP_SLOT_ENUM_EXT_PENDING; return (PCIHP_SUCCESS); } rv = pcihp_unconfigure_ap(pcihp_p, pci_dev); if (rv != HPC_SUCCESS && rv != EBUSY) { cmn_err(CE_NOTE, "%s%d: PCI device %x Failed on Unconfigure", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev); } if (rv == EBUSY) cmn_err(CE_NOTE, "%s%d: PCI device %x Busy", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev); if (rv) { if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_OFF); } /* * we must clear interrupt in case the unconfigure didn't do it * due to a duplicate interrupt. Extraction is success. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_UNCONFIGURE); if (!rv) { /* * Sys Event Notification. */ pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_HINT_REMOVE, pcihp_p->dip, kmflag); } /* * Enable interrupts back from this board. * This could potentially be problematic in case if the user is * quick enough to extract the board. * But we must do it just in case if the switch is closed again. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_ENABLE_ENUM); slotinfop->slot_flags &= ~PCIHP_SLOT_ENUM_EXT_PENDING; return (rv); } /* * This routine is called when a ENUM# caused by when an adapter insertion * is detected. If the occupant is successfully configured (i.e. PCI resources * successfully assigned, the blue LED is left off, otherwise if configuration * is not successful, the blue LED is illuminated. * Please note that the lock must be released before invoking the * generic AP configure function. */ static int pcihp_handle_enum_insertion(pcihp_t *pcihp_p, int pci_dev, int opcode, int kmflag) { struct pcihp_slotinfo *slotinfop; int rv = PCIHP_FAILURE; minor_t ap_minor; major_t ap_major; slotinfop = &pcihp_p->slotinfo[pci_dev]; slotinfop->hs_csr_location = 0; /* we clear the interrupt here. This is a must here. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_CONFIGURE); /* * disable further interrupt from this board till it is * configured. */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_DISABLE_ENUM); if (opcode == PCIHP_CLEAR_ENUM) { slotinfop->slot_flags |= PCIHP_SLOT_ENUM_INS_PENDING; return (PCIHP_SUCCESS); } if ((slotinfop->slot_flags & PCIHP_SLOT_AUTO_CFG_EN) == PCIHP_SLOT_AUTO_CFG_EN) { rv = pcihp_configure_ap(pcihp_p, pci_dev); if (rv != HPC_SUCCESS) { /* configure failed */ cmn_err(CE_NOTE, "%s%d: PCI device %x Failed on" " Configure", ddi_driver_name(pcihp_p->dip), ddi_get_instance(pcihp_p->dip), pci_dev); if (pcihp_cpci_blue_led) pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_SLOT_BLUE_LED_ON); } /* pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_CLEAR_ENUM); */ pcihp_hs_csr_op(pcihp_p, pci_dev, HPC_EVENT_ENABLE_ENUM); if (!rv) { ap_major = ddi_driver_major(pcihp_p->dip); ap_minor = PCIHP_AP_MINOR_NUM( ddi_get_instance(pcihp_p->dip), pci_dev); pcihp_create_occupant_props(pcihp_p->dip, makedevice(ap_major, ap_minor), pci_dev); /* * Sys Event Notification. */ pcihp_gen_sysevent(slotinfop->name, PCIHP_DR_AP_STATE_CHANGE, SE_HINT_INSERT, pcihp_p->dip, kmflag); } } else rv = PCIHP_SUCCESS; slotinfop->slot_flags &= ~PCIHP_SLOT_ENUM_INS_PENDING; return (rv); } /* * Read the Hot Swap Control and Status Register (HS_CSR) and * place the result in the location pointed to be hs_csr. */ static int pcihp_get_hs_csr(struct pcihp_slotinfo *slotinfop, ddi_acc_handle_t config_handle, uint8_t *hs_csr) { if (slotinfop->hs_csr_location == -1) return (PCIHP_FAILURE); if (slotinfop->hs_csr_location == 0) { slotinfop->hs_csr_location = pcihp_get_hs_csr_location(config_handle); if (slotinfop->hs_csr_location == -1) return (PCIHP_FAILURE); } *hs_csr = pci_config_get8(config_handle, slotinfop->hs_csr_location); return (PCIHP_SUCCESS); } /* * Write the Hot Swap Control and Status Register (HS_CSR) with * the value being pointed at by hs_csr. */ static void pcihp_set_hs_csr(struct pcihp_slotinfo *slotinfop, ddi_acc_handle_t config_handle, uint8_t *hs_csr) { if (slotinfop->hs_csr_location == -1) return; if (slotinfop->hs_csr_location == 0) { slotinfop->hs_csr_location = pcihp_get_hs_csr_location(config_handle); if (slotinfop->hs_csr_location == -1) return; } pci_config_put8(config_handle, slotinfop->hs_csr_location, *hs_csr); PCIHP_DEBUG((CE_NOTE, "hs_csr wrote %x, read %x", *hs_csr, pci_config_get8(config_handle, slotinfop->hs_csr_location))); } static int pcihp_get_hs_csr_location(ddi_acc_handle_t config_handle) { uint8_t cap_id; uint_t cap_id_loc; uint16_t status; int location = -1; #define PCI_STAT_ECP_SUPP 0x10 /* * Need to check the Status register for ECP support first. * Also please note that for type 1 devices, the * offset could change. Should support type 1 next. */ status = pci_config_get16(config_handle, PCI_CONF_STAT); if (!(status & PCI_STAT_ECP_SUPP)) { PCIHP_DEBUG((CE_NOTE, "No Ext Capabilities for device\n")); return (-1); } cap_id_loc = pci_config_get8(config_handle, PCI_CONF_EXTCAP); /* * Walk the list of capabilities, but don't walk past the end * of the Configuration Space Header. */ while ((cap_id_loc) && (cap_id_loc < PCI_CONF_HDR_SIZE)) { cap_id = pci_config_get8(config_handle, cap_id_loc); if (cap_id == CPCI_HOTSWAP_CAPID) { location = cap_id_loc + PCI_ECP_HS_CSR; break; } cap_id_loc = pci_config_get8(config_handle, cap_id_loc + 1); } return (location); } static int pcihp_add_dummy_reg_property(dev_info_t *dip, uint_t bus, uint_t device, uint_t func) { pci_regspec_t dummy_reg; bzero(&dummy_reg, sizeof (dummy_reg)); dummy_reg.pci_phys_hi = PCIHP_MAKE_REG_HIGH(bus, device, func, 0); return (ndi_prop_update_int_array(DDI_DEV_T_NONE, dip, "reg", (int *)&dummy_reg, sizeof (pci_regspec_t)/sizeof (int))); } static void pcihp_hs_csr_op(pcihp_t *pcihp_p, int pci_dev, int event) { struct pcihp_slotinfo *slotinfop; ddi_acc_handle_t config_handle; dev_info_t *dip, *new_child = NULL; uint8_t hs_csr; int result; slotinfop = &pcihp_p->slotinfo[pci_dev]; if (pcihp_config_setup(&dip, &config_handle, &new_child, pci_dev, pcihp_p) != DDI_SUCCESS) { return; } result = pcihp_get_hs_csr(slotinfop, config_handle, (uint8_t *)&hs_csr); if ((result != PCIHP_SUCCESS) || (event == -1)) { pcihp_config_teardown(&config_handle, &new_child, pci_dev, pcihp_p); return; } hs_csr &= 0xf; switch (event) { case HPC_EVENT_SLOT_BLUE_LED_ON: hs_csr |= HS_CSR_LOO; break; case HPC_EVENT_SLOT_BLUE_LED_OFF: hs_csr &= ~HS_CSR_LOO; break; case HPC_EVENT_SLOT_CONFIGURE: hs_csr |= HS_CSR_INS; /* clear INS */ break; case HPC_EVENT_CLEAR_ENUM: hs_csr |= (HS_CSR_INS | HS_CSR_EXT); break; case HPC_EVENT_SLOT_UNCONFIGURE: hs_csr |= HS_CSR_EXT; /* clear EXT */ break; case HPC_EVENT_ENABLE_ENUM: hs_csr &= ~HS_CSR_EIM; break; case HPC_EVENT_DISABLE_ENUM: hs_csr |= HS_CSR_EIM; break; case HPC_EVENT_SLOT_NOT_HEALTHY: case HPC_EVENT_SLOT_HEALTHY_OK: default: break; } pcihp_set_hs_csr(slotinfop, config_handle, (uint8_t *)&hs_csr); pcihp_config_teardown(&config_handle, &new_child, pci_dev, pcihp_p); } static int pcihp_config_setup(dev_info_t **dip, ddi_acc_handle_t *handle, dev_info_t **new_child, int pci_dev, pcihp_t *pcihp_p) { dev_info_t *pdip = pcihp_p->dip; int bus, len, rc = DDI_SUCCESS; struct pcihp_slotinfo *slotinfop; hpc_slot_state_t rstate; ddi_acc_hdl_t *hp; struct bus_range { uint32_t lo; uint32_t hi; } pci_bus_range; int flags = 0; slotinfop = &pcihp_p->slotinfo[pci_dev]; /* * If declared failed, don't allow Config operations. * Otherwise, if good or failing, it is assumed Ok * to get config data. */ if (slotinfop->condition == AP_COND_FAILED) { return (PCIHP_FAILURE); } /* * check to see if there is a hardware present first. * If no hardware present, no need to probe this slot. * We can do this first probably as a first step towards * safeguarding from accidental removal (we don't support it!). */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_GET_SLOT_STATE, (caddr_t)&rstate) != 0) { return (DDI_FAILURE); } if (rstate != HPC_SLOT_CONNECTED) { /* error. slot must be connected */ return (DDI_FAILURE); } *new_child = NULL; /* * If there is no dip then we need to see if an * adapter has just been hot plugged. */ len = sizeof (struct bus_range); if (ddi_getlongprop_buf(DDI_DEV_T_NONE, pdip, 0, "bus-range", (caddr_t)&pci_bus_range, &len) != DDI_SUCCESS) { return (PCIHP_FAILURE); } /* primary bus number of this bus node */ bus = pci_bus_range.lo; if (ndi_devi_alloc(pdip, DEVI_PSEUDO_NEXNAME, (dnode_t)DEVI_SID_NODEID, dip) != NDI_SUCCESS) { PCIHP_DEBUG((CE_NOTE, "Failed to alloc probe node\n")); return (PCIHP_FAILURE); } if (pcihp_add_dummy_reg_property(*dip, bus, pci_dev, 0) != DDI_SUCCESS) { (void) ndi_devi_free(*dip); return (PCIHP_FAILURE); } /* * Probe for a device. Possibly a non (c)PCI board could be sitting * here which would never respond to PCI config cycles - in which * case we return. Eventually a configure operation would fail. */ if (pci_config_setup(*dip, handle) != DDI_SUCCESS) { cmn_err(CE_WARN, "Cannot set config space map for" " pci device number %d", pci_dev); (void) ndi_devi_free(*dip); return (PCIHP_FAILURE); } if (pcihp_indirect_map(*dip) == DDI_SUCCESS) flags |= PCICFG_CONF_INDIRECT_MAP; /* * See if there is any PCI HW at this location * by reading the Vendor ID. If it returns with 0xffff * then there is no hardware at this location. */ if (flags & PCICFG_CONF_INDIRECT_MAP) { if (pci_config_get16(*handle, 0) == 0xffff) { pci_config_teardown(handle); (void) ndi_devi_free(*dip); return (PCIHP_FAILURE); } } else { /* Check if mapping is OK */ hp = impl_acc_hdl_get(*handle); if (ddi_peek16(*dip, (int16_t *)(hp->ah_addr), (int16_t *)0) != DDI_SUCCESS) { #ifdef DEBUG cmn_err(CE_WARN, "Cannot Map PCI config space for " "device number %d", pci_dev); #endif pci_config_teardown(handle); (void) ndi_devi_free(*dip); return (PCIHP_FAILURE); } } *new_child = *dip; return (rc); } static void pcihp_config_teardown(ddi_acc_handle_t *handle, dev_info_t **new_child, int pci_dev, pcihp_t *pcihp_p) { struct pcihp_slotinfo *slotinfop = &pcihp_p->slotinfo[pci_dev]; pci_config_teardown(handle); if (*new_child) { (void) ndi_devi_free(*new_child); /* * If occupant not configured, reset HS_CSR location * so that we reprobe. This covers cases where * the receptacle had a status change without a * notification to the framework. */ if (slotinfop->ostate != AP_OSTATE_CONFIGURED) slotinfop->hs_csr_location = 0; } } static int pcihp_get_board_type(struct pcihp_slotinfo *slotinfop) { hpc_board_type_t board_type; /* * Get board type data structure, hpc_board_type_t. */ if (hpc_nexus_control(slotinfop->slot_hdl, HPC_CTRL_GET_BOARD_TYPE, (caddr_t)&board_type) != 0) { cmn_err(CE_WARN, "Cannot Get Board Type.."); return (-1); } /* * We expect the Hotswap Controller to tell us if the board is * a hotswap board or not, as it probably cannot differentiate * between a basic hotswap board, a non hotswap board and a * hotswap nonfriendly board. * So here is the logic to differentiate between the various * types of cPCI boards. * In case if the HSC returns board type as unknown, we assign * the default board type as defined by a configurable variable * for a BHS, nonfriendly FHS and non HS board. */ if (slotinfop->slot_type & HPC_SLOT_TYPE_CPCI) { if (slotinfop->hs_csr_location > 0) board_type = HPC_BOARD_CPCI_FULL_HS; else { if (board_type == HPC_BOARD_CPCI_HS) { if (slotinfop->hs_csr_location == -1) board_type = HPC_BOARD_CPCI_BASIC_HS; } if (board_type == HPC_BOARD_UNKNOWN) { if (slotinfop->hs_csr_location == -1) { board_type = pcihp_cpci_board_type; } else if (slotinfop->hs_csr_location != 0) { board_type = HPC_BOARD_CPCI_FULL_HS; } } } /* * If board type is a non hotswap board, then we must * deny a unconfigure operation. So set this flag. * Strictly speaking, there is no reason not to disallow * a unconfigure operation on nonhotswap boards. But this * is the only way we can prevent a user from accidentally * removing the board and damaging it. */ if (board_type == HPC_BOARD_CPCI_NON_HS) slotinfop->slot_flags |= PCIHP_SLOT_DEV_NON_HOTPLUG; else slotinfop->slot_flags &= ~PCIHP_SLOT_DEV_NON_HOTPLUG; } return (board_type); } /* * Generate the System Event with a possible hint. */ static void pcihp_gen_sysevent(char *slot_name, int event_sub_class, int hint, dev_info_t *self, int kmflag) { int err; char *ev_subclass = NULL; sysevent_id_t eid; nvlist_t *ev_attr_list = NULL; char attach_pnt[MAXPATHLEN]; /* * Minor device name (AP) will be bus path * concatenated with slot name */ (void) strcpy(attach_pnt, PCIHP_DEVICES_STR); (void) ddi_pathname(self, attach_pnt + strlen(PCIHP_DEVICES_STR)); (void) strcat(attach_pnt, ":"); (void) strcat(attach_pnt, slot_name); err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag); if (err != 0) { cmn_err(CE_WARN, "%s%d: Failed to allocate memory " "for event attributes%s", ddi_driver_name(self), ddi_get_instance(self), ESC_DR_AP_STATE_CHANGE); return; } switch (event_sub_class) { /* event sub class: ESC_DR_AP_STATE_CHANGE */ case PCIHP_DR_AP_STATE_CHANGE: ev_subclass = ESC_DR_AP_STATE_CHANGE; switch (hint) { case SE_NO_HINT: /* fall through */ case SE_HINT_INSERT: /* fall through */ case SE_HINT_REMOVE: err = nvlist_add_string(ev_attr_list, DR_HINT, SE_HINT2STR(hint)); if (err != 0) { cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]" " for %s event", ddi_driver_name(self), ddi_get_instance(self), DR_HINT, ESC_DR_AP_STATE_CHANGE); nvlist_free(ev_attr_list); return; } break; default: cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent", ddi_driver_name(self), ddi_get_instance(self)); nvlist_free(ev_attr_list); return; } break; /* event sub class: ESC_DR_REQ */ case PCIHP_DR_REQ: ev_subclass = ESC_DR_REQ; switch (hint) { case SE_INVESTIGATE_RES: /* fall through */ case SE_INCOMING_RES: /* fall through */ case SE_OUTGOING_RES: /* fall through */ err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE, SE_REQ2STR(hint)); if (err != 0) { cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] " "for %s event", ddi_driver_name(self), ddi_get_instance(self), DR_REQ_TYPE, ESC_DR_REQ); nvlist_free(ev_attr_list); return; } break; default: cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent", ddi_driver_name(self), ddi_get_instance(self)); nvlist_free(ev_attr_list); return; } break; default: cmn_err(CE_WARN, "%s%d: Unknown Event subclass", ddi_driver_name(self), ddi_get_instance(self)); nvlist_free(ev_attr_list); return; } /* * Add attachment point as attribute (common attribute) */ err = nvlist_add_string(ev_attr_list, DR_AP_ID, attach_pnt); if (err != 0) { cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event", ddi_driver_name(self), ddi_get_instance(self), DR_AP_ID, EC_DR); nvlist_free(ev_attr_list); return; } /* * Log this event with sysevent framework. */ err = ddi_log_sysevent(self, DDI_VENDOR_SUNW, EC_DR, ev_subclass, ev_attr_list, &eid, ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP)); if (err != 0) { cmn_err(CE_WARN, "%s%d: Failed to log %s event", ddi_driver_name(self), ddi_get_instance(self), EC_DR); } nvlist_free(ev_attr_list); } int pcihp_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op, int flags, char *name, caddr_t valuep, int *lengthp) { int pci_dev; if (dev == DDI_DEV_T_ANY) goto skip; if (strcmp(name, "pci-occupant") == 0) { pci_dev = PCIHP_AP_MINOR_NUM_TO_PCI_DEVNUM(getminor(dev)); pcihp_create_occupant_props_nolock(dip, dev, pci_dev); } /* other cases... */ skip: return (ddi_prop_op(dev, dip, prop_op, flags, name, valuep, lengthp)); } /* * this function is called only for SPARC platforms, where we may have * a mix n' match of direct vs indirectly mapped configuration space. * On x86, this function should always return success since the configuration * space is always indirect mapped. */ /*ARGSUSED*/ static int pcihp_indirect_map(dev_info_t *dip) { #if defined(__sparc) int rc = DDI_FAILURE; if (ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), 0, PCICFG_DEV_CONF_MAP_PROP, DDI_FAILURE) != DDI_FAILURE) rc = DDI_SUCCESS; else if (ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(dip), 0, PCICFG_BUS_CONF_MAP_PROP, DDI_FAILURE) != DDI_FAILURE) rc = DDI_SUCCESS; return (rc); #else return (DDI_SUCCESS); #endif }