/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * MonteCarlo HotSwap Controller functionality */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HOTSWAP_MODE_PROP "hotswap-mode" #define ALARM_CARD_ON_SLOT 1 #define SCSB_HSC_FORCE_REMOVE 1 /* force remove enum intr handler */ /* TUNABLE PARAMETERS. Some are Debug Only. Please take care when using. */ /* * Set this flag to 1, to enable full hotswap mode at boot time. * Since HPS is threaded, it is not recommended that we set this flag * to 1 because enabling full hotswap interrupt can invoke the ENUM * event handler accessing the slot data structure which may have not * been initialized in the hotplug framework since the HPS may not yet * have called the slot registration function with the bus nexus. */ static int scsb_hsc_enable_fhs = 0; /* * Every time a slot is registered with the hotswap framework, the * framework calls back. This variable keeps a count on how many * callbacks are done. */ static int scsb_hsc_numReg = 0; /* * When this flag is set, the board is taken offline (put in reset) after * a unconfigure operation, in Basic Hotswap mode. */ static int scsb_hsc_bhs_slot_reset = 1; /* * When this flag is set, we take the board to reset after unconfigure * operation when operating in full hotswap mode. */ static int scsb_hsc_fhs_slot_reset = 1; /* * Implementation of this counter will work only on Montecarlo since * the ENUM# Interrupt line is not shared with other interrupts. * When the hardware routing changes, then there may be need to remove * or change this functionality. * This functionality is provided so that a bad or non friendly full hotswap * board does not hang the system in full hotswap mode. Atleast the * intent is that! Eventually Solaris kernel will provide similar support * for recovering from a stuck interrupt line. Till then, lets do this. */ static int scsb_hsc_max_intr_count = 8; /* * Since the hardware does not support enabling/disabling ENUM#, the * following flag can be used for imitating that behaviour. * Currently we can set this flag and use the remove op to remove the * interrupt handler from the system. Care must be taken when using this * function since trying to remove the interrupt handler when the interrupts * are pending may hang the system permanently. * Since the hardware does not support this functionality, we adopt this * approach for debugs. */ static int scsb_hsc_enum_switch = 0; /* * When the board loses Healthy# at runtime (with the board being configured), * cPCI specs states that a Reset has to be asserted immediately. * We dont do this currently, until satellite processor support is given * and the implications of such a act is fully understood. * To adopt the cPCI specs recommendation, set this flag to 1. */ static int scsb_hsc_healthy_reset = 0; /* * According to PCI 2.2 specification, once a board comes out of PCI_RST#, * it may take upto 2^25 clock cycles to respond to config cycles. For * montecarlo using a 33MHz cPCI bus, it's around 1.024 s. The variable * will specify the time in ms to wait before attempting config access. */ static int scsb_connect_delay = 1025; /* * slot map property for MC should be * * hsc-slot-map="/pci@1f,0/pci@1/pci@1","15","2", * "/pci@1f,0/pci@1/pci@1","14","3", * "/pci@1f,0/pci@1/pci@1","13","4", * "/pci@1f,0/pci@1/pci@1","12","5" * "/pci@1f,0/pci@1/pci@1","11","6" * "/pci@1f,0/pci@1/pci@1","10","7" * "/pci@1f,0/pci@1/pci@1","8","8"; * * slot map property for Tonga should be * hsc-slot-map="/pci@1f,0/pci@1/pci@1","8","1" * "/pci@1f,0/pci@1/pci@1", "15", "2" * "/pci@1f,0/pci@1/pci@1", "14", "4" * "/pci@1f,0/pci@1/pci@1", "13", "5" * * Please note that the CPU slot number is 3 for Tonga. */ /* * Services we require from the SCSB */ extern int scsb_get_slot_state(void *, int, int *); extern int scsb_read_bhealthy(scsb_state_t *scsb); extern int scsb_read_slot_health(scsb_state_t *scsb, int pslotnum); extern int scsb_connect_slot(void *, int, int); extern int scsb_disconnect_slot(void *, int, int); static void *hsc_state; static uint_t hsc_enum_intr(char *); static hsc_slot_t *hsc_get_slot_info(hsc_state_t *, int); static int scsb_enable_enum(hsc_state_t *); static int scsb_disable_enum(hsc_state_t *, int); static int atoi(const char *); static int isdigit(int); static hsc_slot_t *hsc_find_slot(int); static void hsc_led_op(hsc_slot_t *, int, hpc_led_t, hpc_led_state_t); static int hsc_led_state(hsc_slot_t *, int, hpc_led_info_t *); static int scsb_hsc_disable_slot(hsc_slot_t *); static int scsb_hsc_enable_slot(hsc_slot_t *); #ifndef lint static int hsc_clear_all_enum(hsc_state_t *); #endif static int hsc_slot_register(hsc_state_t *, char *, uint16_t, uint_t, boolean_t); static int hsc_slot_unregister(int); static int scsb_hsc_init_slot_state(hsc_state_t *, hsc_slot_t *); static int hsc_slot_autoconnect(hsc_slot_t *); static hpc_slot_ops_t *hsc_slotops; static hsc_slot_t *hsc_slot_list; /* linked list of slots */ /* * This mutex protects the following variables: * hsc_slot_list */ static kmutex_t hsc_mutex; /* ARGSUSED */ static int hsc_connect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags) { hsc_slot_t *hsp = (hsc_slot_t *)ops_arg; int rc, rstate; hsc_state_t *hsc; DEBUG2("hsc_connect: slot %d, healthy %d", hsp->hs_slot_number, hsp->hs_board_healthy); if (!(hsp->hs_flags & (HSC_ENABLED|HSC_SLOT_ENABLED))) return (HPC_ERR_FAILED); /* if SCB hotswapped, do not allow connect operations */ if (hsp->hs_flags & HSC_SCB_HOTSWAPPED) return (HPC_ERR_FAILED); /* * if previous occupant stayed configured, do not allow another * occupant to be connected. * This behaviour is an indication that the slot state * is not clean. */ if (hsp->hs_flags & HSC_SLOT_BAD_STATE) { /* * In the current implementation, we turn both fault * and active LEDs to ON state in this situation. */ hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); return (HPC_ERR_FAILED); } /* * Get the actual status from the i2c bus */ rc = scsb_get_slot_state(hsp->hs_hpchandle, hsp->hs_slot_number, &rstate); if (rc != DDI_SUCCESS) return (HPC_ERR_FAILED); hsp->hs_slot_state = rstate; if (hsp->hs_slot_state == HPC_SLOT_EMPTY) { #ifdef DEBUG cmn_err(CE_CONT, "?hsc_connect: slot %d is empty\n", hsp->hs_slot_number); #endif return (HPC_ERR_FAILED); } if (hsp->hs_slot_state == HPC_SLOT_CONNECTED) return (HPC_SUCCESS); rc = HPC_SUCCESS; /* * call scsb to connect the slot. This also makes sure board is healthy */ if (scsb_connect_slot(hsp->hs_hpchandle, hsp->hs_slot_number, hsp->hs_board_healthy) != DDI_SUCCESS) { DEBUG1("hsc_connect: slot %d connection failed", hsp->hs_slot_number); rc = HPC_ERR_FAILED; } else { if (hsp->hs_slot_state != HPC_SLOT_CONNECTED) { if (hsp->hs_board_healthy == B_FALSE) { cmn_err(CE_NOTE, "HEALTHY# not asserted on " " slot %d", hsp->hs_slot_number); return (HPC_ERR_FAILED); } hsc = hsp->hsc; hsc->hsp_last = hsp; if (scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_UNRESET_SLOT) != 0) { return (HPC_ERR_FAILED); } /* * Unresetting a board may have caused an interrupt * burst in case of non friendly boards. So it is * important to make sure that the ISR has not * put this board back to disconnect state. */ delay(1); if (hsp->hs_flags & HSC_ENUM_FAILED) { hsp->hs_flags &= ~HSC_ENUM_FAILED; return (HPC_ERR_FAILED); } DEBUG1("hsc_connect: slot %d connected", hsp->hs_slot_number); rc = HPC_SUCCESS; hsp->hs_slot_state = HPC_SLOT_CONNECTED; (void) hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); } } /* * PCI 2.2 specs recommend that the probe software wait * for upto 2^25 PCI clock cycles after deassertion of * PCI_RST# before the board is able to respond to config * cycles. So, before we return, we wait for ~1 sec. */ delay(drv_usectohz(scsb_connect_delay * 1000)); return (rc); } /* ARGSUSED */ static int hsc_disconnect(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags) { hsc_slot_t *hsp = (hsc_slot_t *)ops_arg; hsc_state_t *hsc; #ifdef DEBUG static const char func[] = "hsc_disconnect"; #endif DEBUG1("hsc_disconnect: slot %d", hsp->hs_slot_number); if (hsp->hs_board_configured) { #ifdef DEBUG cmn_err(CE_NOTE, "%s: cannot disconnect configured board in slot %d", func, hsp->hs_slot_number); #endif return (HPC_ERR_FAILED); } if (hsp->hs_slot_state == HPC_SLOT_EMPTY) { #ifdef DEBUG cmn_err(CE_NOTE, "%s: slot %d is empty", func, hsp->hs_slot_number); #endif return (HPC_SUCCESS); } if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) { /* * if already disconnected, just return success * Duplicate disconnect messages should not be failed! */ return (HPC_SUCCESS); } /* if SCB hotswapped, do not allow disconnect operations */ if (hsp->hs_flags & HSC_SCB_HOTSWAPPED) return (HPC_ERR_FAILED); /* call scsb to disconnect the slot */ if (scsb_disconnect_slot(hsp->hs_hpchandle, B_TRUE, hsp->hs_slot_number) != DDI_SUCCESS) return (HPC_ERR_FAILED); hsc = hsp->hsc; if (hsc->hsp_last == hsp) hsc->hsp_last = NULL; return (HPC_SUCCESS); } /* * In the cPCI world, this operation is not applicable. * However, we use this function to enable full hotswap mode in debug mode. */ /* ARGSUSED */ static int hsc_insert(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags) { hsc_slot_t *hsp = (hsc_slot_t *)ops_arg; if (scsb_hsc_enum_switch && (scsb_enable_enum(hsp->hsc) == DDI_SUCCESS)) { return (HPC_SUCCESS); } return (HPC_ERR_NOTSUPPORTED); } /* * In the cPCI world, this operation is not applicable. * However, we use this function to disable full hotswap mode in debug mode. */ /* ARGSUSED */ static int hsc_remove(caddr_t ops_arg, hpc_slot_t slot_hdl, void *data, uint_t flags) { hsc_slot_t *hsp = (hsc_slot_t *)ops_arg; if (scsb_hsc_enum_switch && (scsb_disable_enum(hsp->hsc, SCSB_HSC_FORCE_REMOVE) == DDI_SUCCESS)) { hsp->hs_flags &= ~HSC_ENUM_FAILED; return (HPC_SUCCESS); } return (HPC_ERR_NOTSUPPORTED); } static void hsc_led_op(hsc_slot_t *hsp, int cmd, hpc_led_t led, hpc_led_state_t led_state) { hpc_led_info_t ledinfo; ledinfo.led = led; ledinfo.state = led_state; (void) hsc_led_state(hsp, cmd, &ledinfo); } static int hsc_led_state(hsc_slot_t *hsp, int cmd, hpc_led_info_t *hlip) { hpc_led_state_t *hlsp; scsb_uinfo_t sunit; int res; DEBUG3("hsc_led_state: slot %d, led %x, state %x", hsp->hs_slot_number, hlip->led, hlip->state); sunit.unit_type = SLOT; sunit.unit_number = hsp->hs_slot_number; /* * We ignore operations on LEDs that we don't support */ switch (hlip->led) { case HPC_FAULT_LED: sunit.led_type = NOK; hlsp = &hsp->hs_fault_led_state; break; case HPC_ACTIVE_LED: sunit.led_type = OK; hlsp = &hsp->hs_active_led_state; break; default: return (HPC_ERR_NOTSUPPORTED); } switch (hlip->state) { case HPC_LED_BLINK: sunit.unit_state = BLINK; if (hlip->led != HPC_ACTIVE_LED) return (HPC_ERR_NOTSUPPORTED); break; case HPC_LED_ON: sunit.unit_state = ON; break; case HPC_LED_OFF: sunit.unit_state = OFF; break; default: break; } switch (cmd) { case HPC_CTRL_SET_LED_STATE: res = scsb_led_set(hsp->hs_hpchandle, &sunit, sunit.led_type); if (res != 0) return (HPC_ERR_FAILED); *hlsp = (hpc_led_state_t)sunit.unit_state; break; case HPC_CTRL_GET_LED_STATE: res = scsb_led_get(hsp->hs_hpchandle, &sunit, sunit.led_type); if (res) return (HPC_ERR_FAILED); /* hlip->state = sunit.unit_state; */ break; default: return (HPC_ERR_INVALID); } return (HPC_SUCCESS); } static int hsc_get_slot_state(hsc_slot_t *hsp, hpc_slot_state_t *hssp) { int rstate = 0; int rc; #ifdef DEBUG int orstate; /* original rstate */ #endif DEBUG1("hsc_get_slot_state: slot %d", hsp->hs_slot_number); rc = scsb_get_slot_state(hsp->hs_hpchandle, hsp->hs_slot_number, &rstate); if (rc != DDI_SUCCESS) return (HPC_ERR_FAILED); #ifdef DEBUG orstate = hsp->hs_slot_state; #endif hsp->hs_slot_state = rstate; switch (hsp->hs_slot_state) { case HPC_SLOT_EMPTY: DEBUG0("empty"); break; case HPC_SLOT_CONNECTED: DEBUG0("connected"); break; case HPC_SLOT_DISCONNECTED: DEBUG0("disconnected"); break; } *hssp = hsp->hs_slot_state; /* doing get-state above may have caused a freeze operation */ if ((hsp->hs_flags & HSC_SCB_HOTSWAPPED) && (rstate == HPC_SLOT_DISCONNECTED)) { /* freeze puts disconnected boards to connected state */ *hssp = HPC_SLOT_CONNECTED; #if 0 /* in FHS, deassertion of reset may have configured the board */ if (hsp->hs_board_configured == B_TRUE) { hsp->hs_slot_state = *hssp; } #endif } #ifdef DEBUG /* a SCB hotswap may have forced a state change on the receptacle */ if (orstate != *hssp) { cmn_err(CE_NOTE, "hsc_get_state: slot%d state change due" " to SCB hotswap!", hsp->hs_slot_number); } #endif return (HPC_SUCCESS); } static int hsc_set_config_state(hsc_slot_t *hsp, int cmd) { hsc_state_t *hsc = hsp->hsc; DEBUG1("hsc_set_config_state: slot %d", hsp->hs_slot_number); switch (cmd) { case HPC_CTRL_DEV_CONFIGURED: /* * Closing of the Ejector switch in configured/busy state can * cause duplicate CONFIGURED messages to come down. * Make sure our LED states are fine. */ if (hsp->hs_board_configured == B_TRUE) { hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); break; } hsp->hs_board_configured = B_TRUE; hsp->hs_board_configuring = B_FALSE; if ((hsc->state & HSC_ATTACHED) == HSC_ATTACHED && hsp->hs_flags & HSC_ALARM_CARD_PRES) (void) scsb_hsc_ac_op(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_HSC_AC_CONFIGURED); /* LED must be OFF on the occupant. */ (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_SLOT_BLUE_LED_OFF, 0); if (hsp->hs_flags & HSC_AUTOCFG) (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_ENABLE_ENUM, 0); else (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_DISABLE_ENUM, 0); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); if (hsc->hsp_last == hsp) hsc->hsp_last = NULL; break; case HPC_CTRL_DEV_UNCONFIGURED: hsp->hs_board_configured = B_FALSE; hsp->hs_board_unconfiguring = B_FALSE; hsp->hs_flags &= ~HSC_SLOT_BAD_STATE; if (hsp->hs_flags & HSC_ALARM_CARD_PRES) (void) scsb_hsc_ac_op(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_HSC_AC_UNCONFIGURED); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); if (((hsc->state & HSC_ENUM_ENABLED) && scsb_hsc_fhs_slot_reset) || (((hsc->state & HSC_ENUM_ENABLED) != HSC_ENUM_ENABLED) && scsb_hsc_bhs_slot_reset) || ((hsp->hs_flags & HSC_AUTOCFG) != HSC_AUTOCFG)) { if (scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_RESET_SLOT) == 0) { hsp->hs_slot_state = HPC_SLOT_DISCONNECTED; hsp->hs_board_healthy = B_FALSE; hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); } } break; case HPC_CTRL_DEV_CONFIG_FAILURE: hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); break; case HPC_CTRL_DEV_UNCONFIG_FAILURE: hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); break; case HPC_CTRL_DEV_CONFIG_START: case HPC_CTRL_DEV_UNCONFIG_START: hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); break; default: return (HPC_ERR_INVALID); } if (cmd != HPC_CTRL_DEV_CONFIG_START && cmd != HPC_CTRL_DEV_UNCONFIG_START && hsc->regDone == B_FALSE && scsb_hsc_numReg < hsc->n_registered_occupants) { scsb_hsc_numReg++; /* * If the callback is invoked for all registered slots, * enable ENUM. */ if (((hsc->state & HSC_ATTACHED) == HSC_ATTACHED) && (scsb_hsc_numReg == hsc->n_registered_occupants)) { hsc->regDone = B_TRUE; if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL) { #ifdef DEBUG cmn_err(CE_CONT, "%s%d: Enabling full hotswap" ":%d non-empty slots\n", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsc->n_registered_occupants); #endif if (scsb_enable_enum(hsc) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s#%d: Cannot enable " "Full Hotswap", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); return (HPC_ERR_FAILED); } } } } return (HPC_SUCCESS); } /*ARGSUSED*/ static int hsc_get_board_type(hsc_slot_t *hsp, hpc_board_type_t *hbtp) { *hbtp = hsp->hs_board_type; return (HPC_SUCCESS); } /* ARGSUSED */ static int hsc_autoconfig(hsc_slot_t *hsp, int cmd) { int res = HPC_SUCCESS, enum_disable = B_TRUE, i; char slotautocfg_prop[18]; hsc_state_t *hsc; DEBUG1("hsc_autoconfig: slot %d", hsp->hs_slot_number); (void) sprintf(slotautocfg_prop, "slot%d-autoconfig", hsp->hs_slot_number); if (cmd == HPC_CTRL_ENABLE_AUTOCFG) { hsp->hs_flags |= HSC_AUTOCFG; (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip, slotautocfg_prop, "enabled"); if ((res = scsb_enable_enum(hsp->hsc)) == DDI_SUCCESS) { (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_ENABLE_ENUM, 0); } } else { (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip, slotautocfg_prop, "disabled"); hsp->hs_flags &= ~HSC_AUTOCFG; hsc = hsp->hsc; if (hsc->state & HSC_ATTACHED) { (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_DISABLE_ENUM, 0); for (i = 0; i < hsc->slot_table_size; i++) { hsc_slot_t *thsp; int slotnum; slotnum = hsc->slot_table_prop[i].pslotnum; thsp = hsc_find_slot(slotnum); if (thsp == NULL) { cmn_err(CE_WARN, "%s#%d: hsc_autocfg:" "No Slot Info for slot %d", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), slotnum); continue; } if (thsp->hs_flags & HSC_AUTOCFG) { enum_disable = B_FALSE; break; } } if (enum_disable == B_TRUE) (void) scsb_disable_enum(hsc, SCSB_HSC_FORCE_REMOVE); } } return (res); } /* * This function is invoked to enable/disable a slot */ /* ARGSUSED */ #ifndef lint static int hsc_slot_enable(hsc_slot_t *hsp, boolean_t enabled) { scsb_uinfo_t sunit; int res; DEBUG1("hsc_slot_enable: slot %d", hsp->hs_slot_number); sunit.unit_type = SLOT; sunit.unit_number = hsp->hs_slot_number; if (enabled) sunit.unit_state = ON; else sunit.unit_state = OFF; res = scsb_reset_unit(hsp->hs_hpchandle, &sunit); if (res == 0) return (HPC_SUCCESS); else if (res == EINVAL) return (HPC_ERR_INVALID); else return (HPC_ERR_FAILED); } #endif /*ARGSUSED*/ static int hsc_control(caddr_t ops_arg, hpc_slot_t slot_hdl, int request, caddr_t arg) { hsc_slot_t *hsp = (hsc_slot_t *)ops_arg; int rc = HPC_SUCCESS; DEBUG2("hsc_control: slot %d, op=%x\n", hsp->hs_slot_number, request); switch (request) { case HPC_CTRL_GET_LED_STATE: return (hsc_led_state(hsp, HPC_CTRL_GET_LED_STATE, (hpc_led_info_t *)arg)); case HPC_CTRL_SET_LED_STATE: return (hsc_led_state(hsp, HPC_CTRL_SET_LED_STATE, (hpc_led_info_t *)arg)); case HPC_CTRL_GET_SLOT_STATE: return (hsc_get_slot_state(hsp, (hpc_slot_state_t *)arg)); case HPC_CTRL_DEV_CONFIGURED: return (hsc_set_config_state(hsp, HPC_CTRL_DEV_CONFIGURED)); case HPC_CTRL_DEV_UNCONFIGURED: return (hsc_set_config_state(hsp, HPC_CTRL_DEV_UNCONFIGURED)); case HPC_CTRL_DEV_CONFIG_FAILURE: return (hsc_set_config_state(hsp, HPC_CTRL_DEV_CONFIG_FAILURE)); case HPC_CTRL_DEV_UNCONFIG_FAILURE: return (hsc_set_config_state(hsp, HPC_CTRL_DEV_UNCONFIG_FAILURE)); case HPC_CTRL_DEV_CONFIG_START: case HPC_CTRL_DEV_UNCONFIG_START: return (hsc_set_config_state(hsp, request)); case HPC_CTRL_GET_BOARD_TYPE: return (hsc_get_board_type(hsp, (hpc_board_type_t *)arg)); case HPC_CTRL_DISABLE_AUTOCFG: return (hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG)); case HPC_CTRL_ENABLE_AUTOCFG: return (hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG)); case HPC_CTRL_DISABLE_SLOT: /* * No hardware support for disabling the slot. * Just imitate a disable_autoconfig operation for now */ if (hsp->hs_board_configured == B_TRUE) return (HPC_ERR_FAILED); if (scsb_hsc_disable_slot(hsp) != DDI_SUCCESS) rc = HPC_ERR_FAILED; return (rc); case HPC_CTRL_ENABLE_SLOT: if (scsb_hsc_enable_slot(hsp) != DDI_SUCCESS) rc = HPC_ERR_FAILED; return (rc); case HPC_CTRL_ENABLE_ENUM: return (scsb_enable_enum(hsp->hsc)); case HPC_CTRL_DISABLE_ENUM: return (scsb_disable_enum(hsp->hsc, 0)); default: return (HPC_ERR_INVALID); } } static int scsb_hsc_disable_slot(hsc_slot_t *hsp) { int rc; char slot_disable_prop[18]; DEBUG1("hsc_disable_slot: slot %d", hsp->hs_slot_number); (void) sprintf(slot_disable_prop, "slot%d-status", hsp->hs_slot_number); rc = scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_RESET_SLOT); if (rc == DDI_SUCCESS) { (void) hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG); hsp->hs_flags &= ~HSC_SLOT_ENABLED; (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsp->hsc->dip, slot_disable_prop, "disabled"); } else rc = DDI_FAILURE; return (rc); } static int scsb_hsc_enable_slot(hsc_slot_t *hsp) { int rc; char slot_disable_prop[18]; DEBUG1("hsc_disable_slot: slot %d", hsp->hs_slot_number); (void) sprintf(slot_disable_prop, "slot%d-status", hsp->hs_slot_number); rc = scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_UNRESET_SLOT); if (rc == DDI_SUCCESS) { (void) hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG); hsp->hs_flags |= HSC_SLOT_ENABLED; (void) ddi_prop_remove(DDI_DEV_T_NONE, hsp->hsc->dip, slot_disable_prop); } else rc = HPC_ERR_FAILED; return (rc); } #define NEW(type) (type *) kmem_zalloc(sizeof (type), KM_SLEEP) static hsc_slot_t * hsc_alloc_slot( uint16_t device_number, int slot_number, boolean_t board_in_slot) { hpc_slot_info_t *hsip; hsc_slot_t *hsp = NEW(hsc_slot_t); DEBUG2("hsc_alloc_slot: slot %d %s", slot_number, board_in_slot ? "occupied" : "empty"); if (hsp == NULL) { cmn_err(CE_NOTE, "hsc_alloc_slot: allocation failed for slot %d", slot_number); return (NULL); } hsip = &hsp->hs_info; hsip->version = HPC_SLOT_INFO_VERSION; hsip->slot_type = HPC_SLOT_TYPE_CPCI; hsip->pci_dev_num = device_number; hsip->pci_slot_capabilities = 0; hsip->slot_flags = HPC_SLOT_CREATE_DEVLINK; /* * Note: the name *must* be 'pci' so that the correct cfgadm plug-in * library is selected */ (void) sprintf(hsip->pci_slot_name, "cpci_slot%d", slot_number); /* * We assume that the following LED settings reflect * the hardware state. * After we register the slot, we will be invoked by the nexus * if the slot is occupied, and we will turn on the LED then. */ hsp->hs_active_led_state = HPC_LED_OFF; hsp->hs_fault_led_state = HPC_LED_OFF; hsp->hs_board_configured = B_FALSE; hsp->hs_board_healthy = B_FALSE; hsp->hs_board_type = HPC_BOARD_UNKNOWN; hsp->hs_flags = HSC_ENABLED | HSC_SLOT_ENABLED; hsp->hs_slot_number = slot_number; /* * we should just set this to connected, * as MC slots are always connected. */ if (board_in_slot) hsp->hs_slot_state = HPC_SLOT_CONNECTED; else hsp->hs_slot_state = HPC_SLOT_EMPTY; return (hsp); } static void hsc_free_slot(hsc_slot_t *hsp) { DEBUG0("hsc_free_slot"); kmem_free(hsp, sizeof (*hsp)); } /* * This function is invoked to register a slot */ static int hsc_slot_register( hsc_state_t *hsc, char *bus_path, /* PCI nexus pathname */ uint16_t device_number, /* PCI device number */ uint_t slot_number, /* physical slot number */ boolean_t board_in_slot) /* receptacle status */ { int rc = HPC_SUCCESS; hsc_slot_t *hsp; DEBUG2("hsc_slot_register: slot number %d, device number %d", slot_number, device_number); hsp = hsc_alloc_slot(device_number, slot_number, board_in_slot); if (hsp == NULL) { #ifdef DEBUG cmn_err(CE_NOTE, "hsc_slot_register: hsc_alloc_slot failed"); #endif return (HPC_ERR_FAILED); } hsp->hs_hpchandle = hsc->scsb_handle; /* handle for call backs */ hsp->hsc = hsc; rc = scsb_hsc_init_slot_state(hsc, hsp); if (rc != DDI_SUCCESS) return (HPC_ERR_FAILED); /* slot autoconfiguration by default. */ if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL) (void) hsc_autoconfig(hsp, HPC_CTRL_ENABLE_AUTOCFG); else (void) hsc_autoconfig(hsp, HPC_CTRL_DISABLE_AUTOCFG); /* * Append to our list */ mutex_enter(&hsc_mutex); hsp->hs_next = hsc_slot_list; hsc_slot_list = hsp; mutex_exit(&hsc_mutex); rc = hpc_slot_register(hsc->dip, bus_path, &hsp->hs_info, &hsp->hs_slot_handle, /* return value */ hsc_slotops, (caddr_t)hsp, 0); if (rc != HPC_SUCCESS) { cmn_err(CE_WARN, "%s#%d: failed to register slot %s:%d", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), bus_path, device_number); hsc_free_slot(hsp); return (rc); } DEBUG0("hsc_slot_register: hpc_slot_register successful"); return (rc); } static int hsc_slot_unregister(int slot_number) { hsc_slot_t *hsp, *prev; DEBUG1("hsc_slot_unregister: slot number %d", slot_number); mutex_enter(&hsc_mutex); hsp = prev = NULL; for (hsp = hsc_slot_list; hsp != NULL; hsp = hsp->hs_next) { if (hsp->hs_slot_number == slot_number) { if (prev == NULL) /* first entry */ hsc_slot_list = hsc_slot_list->hs_next; else prev->hs_next = hsp->hs_next; hsp->hs_next = NULL; break; } prev = hsp; } mutex_exit(&hsc_mutex); if (hsp != NULL) { (void) hpc_slot_unregister(&hsp->hs_slot_handle); if ((hsp->hsc->state & HSC_ATTACHED) != HSC_ATTACHED && hsp->hs_slot_state != HPC_SLOT_EMPTY) { hsp->hsc->n_registered_occupants--; } hsc_free_slot(hsp); return (0); } return (1); } static int scsb_hsc_init_slot_state(hsc_state_t *hsc, hsc_slot_t *hsp) { int rc, rstate; int slot_number = hsp->hs_slot_number; scsb_state_t *scsb = (scsb_state_t *)hsc->scsb_handle; rc = scsb_get_slot_state(hsc->scsb_handle, slot_number, &rstate); if (rc != DDI_SUCCESS) return (DDI_FAILURE); /* * Set the healthy status for this slot */ hsp->hs_board_healthy = scsb_read_slot_health(scsb, slot_number); hsp->hs_slot_state = rstate; switch (rstate) { case HPC_SLOT_EMPTY: /* * this will clear any state differences between * SCB Freeze operations. */ hsp->hs_slot_state = HPC_SLOT_EMPTY; /* slot empty. */ (void) scsb_reset_slot(hsc->scsb_handle, slot_number, SCSB_RESET_SLOT); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_OFF); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); break; case HPC_SLOT_DISCONNECTED: /* * this will clear any state differences between * SCB Freeze operations. */ hsp->hs_slot_state = HPC_SLOT_DISCONNECTED; /* check recovery from SCB freeze */ if (hsp->hs_board_configured != B_TRUE) { /* * Force a disconnect just in case there are * differences between healthy and reset states. */ (void) scsb_reset_slot(hsc->scsb_handle, slot_number, SCSB_RESET_SLOT); /* * Slot in reset. OBP has not probed this * device. Hence it is ok to remove this board. */ hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); break; } /*FALLTHROUGH*/ case HPC_SLOT_CONNECTED: /* * this will clear any state differences between * SCB Freeze operations. */ hsp->hs_slot_state = HPC_SLOT_CONNECTED; /* * OBP should have probed this device, unless * it was plugged in during the boot operation * before the driver was loaded. In any case, * no assumption is made and hence we take * the conservative approach by keeping fault * led off so board removal is not allowed. */ if (hsp->hs_board_configured == B_TRUE) hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); else hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); /* * Netra ct alarm card hotswap support */ if (slot_number == scsb->ac_slotnum && scsb->scsb_hsc_state & SCSB_ALARM_CARD_PRES) { hsp->hs_flags |= HSC_ALARM_CARD_PRES; DEBUG0("Xscsb_hsc_init_slot_state: " "set HSC_ALARM_CARD_PRES"); } break; default: break; } return (rc); } static hsc_slot_t * hsc_get_slot_info(hsc_state_t *hsc, int pci_devno) { int i; for (i = 0; i < hsc->slot_table_size; i++) { if (hsc->slot_table_prop[i].pci_devno == pci_devno) return ((hsc_slot_t *)hsc_find_slot( hsc->slot_table_prop[i].pslotnum)); } return (NULL); } static hsc_slot_t * hsc_find_slot(int slot_number) { hsc_slot_t *hsp; mutex_enter(&hsc_mutex); for (hsp = hsc_slot_list; hsp != NULL; hsp = hsp->hs_next) { if (hsp->hs_slot_number == slot_number) break; } mutex_exit(&hsc_mutex); return (hsp); } /* * This function is invoked by the SCSB when an interrupt * happens to indicate that a board has been inserted-in/removed-from * the specified slot. */ int hsc_slot_occupancy(int slot_number, boolean_t occupied, int flags, int healthy) { static const char func[] = "hsc_slot_occupancy"; hsc_slot_t *hsp; int rc = DDI_SUCCESS; DEBUG4("hsc_slot_occupancy: slot %d %s, ac=%d, healthy=%d", slot_number, occupied ? "occupied" : "not occupied", (flags == ALARM_CARD_ON_SLOT) ? 1:0, healthy); hsp = hsc_find_slot(slot_number); if (hsp == NULL) { cmn_err(CE_NOTE, "%s: cannot map slot number %d to a hsc_slot_t", func, slot_number); return (DDI_FAILURE); } hsp->hs_board_healthy = healthy; if (occupied) { /* * A board was just inserted. We are disconnected at this point. */ if (hsp->hs_slot_state == HPC_SLOT_EMPTY) hsp->hs_board_type = HPC_BOARD_CPCI_HS; hsp->hs_slot_state = HPC_SLOT_DISCONNECTED; if (flags == ALARM_CARD_ON_SLOT) { hsp->hs_flags |= HSC_ALARM_CARD_PRES; DEBUG0("Xhsc_slot_occupancy: set HSC_ALARM_CARD_PRES"); } hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); /* * if previous occupant stayed configured, do not allow another * occupant to be connected. * So as soon as the board is plugged in, we turn both LEDs On. * This behaviour is an indication that the slot state * is not clean. */ if (hsp->hs_flags & HSC_SLOT_BAD_STATE) { hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); return (DDI_SUCCESS); } /* Do not allow connect if slot is disabled */ if ((hsp->hs_flags & HSC_SLOT_ENABLED) != HSC_SLOT_ENABLED) return (DDI_SUCCESS); /* if no healthy, we stay disconnected. */ if (healthy == B_FALSE) { return (DDI_SUCCESS); } rc = hsc_slot_autoconnect(hsp); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_BLINK); } else { /* * A board was just removed */ hsp->hs_slot_state = HPC_SLOT_EMPTY; hsp->hs_board_type = HPC_BOARD_UNKNOWN; hsp->hs_flags &= ~HSC_ENUM_FAILED; if (hsp->hs_flags & HSC_ALARM_CARD_PRES) { hsp->hs_flags &= ~HSC_ALARM_CARD_PRES; DEBUG0("Xhsc_slot_occupancy:clear HSC_ALARM_CARD_PRES"); } if (hsp->hs_board_configured == B_TRUE) { (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_SLOT_NOT_HEALTHY, 0); cmn_err(CE_WARN, "%s#%d: ALERT! Surprise Removal " " on Slot %d, Occupant Online!!", ddi_driver_name(hsp->hsc->dip), ddi_get_instance(hsp->hsc->dip), slot_number); cmn_err(CE_WARN, "%s#%d: ALERT! System now in " " Inconsistent State! Slot disabled. Halt!", ddi_driver_name(hsp->hsc->dip), ddi_get_instance(hsp->hsc->dip)); /* Slot in reset and disabled */ (void) scsb_hsc_disable_slot(hsp); hsp->hs_flags |= HSC_SLOT_BAD_STATE; /* the following works for P1.0 only. */ hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_ON); } else { hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_ACTIVE_LED, HPC_LED_OFF); } } return (rc); } /* * This function is invoked by the SCSB when the health status of * a board changes. */ /*ARGSUSED*/ int scsb_hsc_board_healthy(int slot_number, boolean_t healthy) { hsc_slot_t *hsp; hsc_state_t *hsc; DEBUG2("hsc_board_healthy: slot %d = %d\n", slot_number, healthy); hsp = hsc_find_slot(slot_number); if (hsp == NULL) { cmn_err(CE_NOTE, "hsc_board_healthy: No Slot Info."); return (DDI_FAILURE); } hsc = hsp->hsc; if (hsp->hs_slot_state == HPC_SLOT_EMPTY) { #ifdef DEBUG cmn_err(CE_NOTE, "%s#%d: Healthy# %s on " "empty slot %d", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), healthy == B_TRUE ? "On" : "Off", slot_number); #endif return (DDI_FAILURE); } if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) { DEBUG2("healthy %s on disconnected slot %d\n", healthy == B_TRUE ? "On":"Off", slot_number); /* * Connect the slot if board healthy and in autoconfig mode. */ hsp->hs_board_healthy = healthy; if (healthy == B_TRUE) return (hsc_slot_autoconnect(hsp)); } /* * the board is connected. The result could be seviour depending * on the occupant state. */ if (healthy == B_TRUE) { if (hsp->hs_board_healthy != B_TRUE) { hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); /* Regained HEALTHY# at Run Time...!!! */ cmn_err(CE_NOTE, "%s#%d: slot %d Occupant " "%s, Regained HEALTHY#!", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), slot_number, hsp->hs_board_configured == B_TRUE ? "configured" : "Unconfigured"); (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_SLOT_HEALTHY_OK, 0); } } else { if (hsp->hs_board_configured == B_TRUE) { /* Lost HEALTHY# at Run Time...Serious Condition. */ cmn_err(CE_WARN, "%s#%d: ALERT! Lost HEALTHY#" " on Slot %d, Occupant %s", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), slot_number, hsp->hs_board_configured == B_TRUE ? "Online!!!" : "Offline"); (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_SLOT_NOT_HEALTHY, 0); } if ((hsp->hs_board_configured != B_TRUE) || scsb_hsc_healthy_reset) { if (scsb_reset_slot(hsp->hs_hpchandle, slot_number, SCSB_RESET_SLOT) == 0) { /* signal Ok to remove board. */ hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); cmn_err(CE_WARN, "%s#%d: Slot %d " "successfully taken offline", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), slot_number); } } } hsp->hs_board_healthy = healthy; return (DDI_SUCCESS); } static int hsc_slot_autoconnect(hsc_slot_t *hsp) { hsc_state_t *hsc = hsp->hsc; int rc = DDI_SUCCESS; /* * Keep slot in reset unless autoconfiguration is enabled * Ie. for Basic Hotswap mode, we stay disconnected at * insertion. For full hotswap mode, we automatically * go into connected state at insertion, so that occupant * autoconfiguration is possible. */ if (((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) && (hsp->hs_flags & HSC_AUTOCFG)) { /* this statement must be here before unreset. */ hsc->hsp_last = hsp; if ((rc = scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_UNRESET_SLOT)) == 0) { hsp->hs_slot_state = HPC_SLOT_CONNECTED; hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_OFF); } else { hsc->hsp_last = NULL; rc = DDI_FAILURE; } } return (rc); } /* * The SCSB code should invoke this function from its _init() function. */ int hsc_init() { int rc; rc = ddi_soft_state_init(&hsc_state, sizeof (hsc_state_t), 1); if (rc != 0) return (rc); hsc_slotops = hpc_alloc_slot_ops(KM_SLEEP); hsc_slotops->hpc_version = HPC_SLOT_OPS_VERSION; hsc_slotops->hpc_op_connect = hsc_connect; hsc_slotops->hpc_op_disconnect = hsc_disconnect; hsc_slotops->hpc_op_insert = hsc_insert; hsc_slotops->hpc_op_remove = hsc_remove; hsc_slotops->hpc_op_control = hsc_control; return (DDI_SUCCESS); } /* * The SCSB code should invoke this function from its _fini() function. */ int hsc_fini() { if (hsc_slotops != NULL) { hpc_free_slot_ops(hsc_slotops); hsc_slotops = NULL; } ddi_soft_state_fini(&hsc_state); return (DDI_SUCCESS); } static int scsb_enable_enum(hsc_state_t *hsc) { DEBUG0("hsc: Enable ENUM#\n"); if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) return (DDI_SUCCESS); if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED) return (DDI_FAILURE); if (ddi_add_intr(hsc->dip, 1, NULL, NULL, hsc_enum_intr, (caddr_t)hsc) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s#%d: failed ENUM# interrupt registration", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); return (DDI_FAILURE); } cmn_err(CE_CONT, "?%s%d: Successfully Upgraded to " "Full Hotswap Mode\n", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); hsc->state |= HSC_ENUM_ENABLED; (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip, HOTSWAP_MODE_PROP, "full"); return (DDI_SUCCESS); } /*ARGSUSED*/ static int scsb_disable_enum(hsc_state_t *hsc, int op) { DEBUG0("hsc: Disable ENUM#\n"); if (op == SCSB_HSC_FORCE_REMOVE) { /* * Clear all pending interrupts before unregistering * the interrupt. Otherwise the system will hang. * * Due to the hang problem, we'll not turn off or disable * interrupts because if there's a non-friendly full hotswap * device out there, the ENUM# will be kept asserted and * hence hsc_clear_all_enum() can never deassert ENUM#. * So the system will hang. */ if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) { /* hsc_clear_all_enum(hsc); */ ddi_remove_intr(hsc->dip, 1, NULL); hsc->state &= ~HSC_ENUM_ENABLED; cmn_err(CE_CONT, "?%s%d: Successfully Downgraded to " "Basic Hotswap Mode\n", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); } (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip, HOTSWAP_MODE_PROP, "basic"); return (DDI_SUCCESS); } else /* No programming interface for disabling ENUM# on MC/Tonga */ return (HPC_ERR_NOTSUPPORTED); } #ifndef lint static int hsc_clear_all_enum(hsc_state_t *hsc) { int i, rc; hsc_slot_t *hsp; for (i = 0; i < hsc->slot_table_size; i++) { hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum); if (hsp == NULL) continue; rc = hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_CLEAR_ENUM, HPC_EVENT_SYNCHRONOUS); if (rc == HPC_EVENT_UNCLAIMED) break; /* no pending interrupts across the bus */ DEBUG1("Pending Intr on slot %d\n", hsc->slot_table_prop[i].pslotnum); } return (0); } #endif int scsb_hsc_attach(dev_info_t *dip, void *scsb_handle, int instance) { int i, n, prop_len; int prom_prop = 0; /* default: OS property gives slot-table */ int rc; char *hotswap_model; hsc_state_t *hsc; scsb_state_t *scsb = (scsb_state_t *)scsb_handle; caddr_t hpc_slot_table_data, s; int hpc_slot_table_size; hsc_prom_slot_table_t *hpstp; int rstate; DEBUG0("hsc_attach: enter\n"); /* * To get the slot information, * The OBP defines the 'slot-table' property. But the OS * can override it with 'hsc-slot-map' property * through the .conf file. * Since the formats are different, 2 different property names * are chosen. * The OBP property format is * ,,, * The OS property format is (ga-bits is not used however) * ,,, */ rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "hsc-slot-map", (caddr_t)&hpc_slot_table_data, &hpc_slot_table_size); if (rc != DDI_PROP_SUCCESS) { prom_prop = 1; rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "slot-table", (caddr_t)&hpc_slot_table_data, &hpc_slot_table_size); if (rc != DDI_PROP_SUCCESS) { cmn_err(CE_WARN, "%s#%d: 'slot-table' property " "missing!", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_FAILURE); } } rc = ddi_soft_state_zalloc(hsc_state, instance); if (rc != DDI_SUCCESS) return (DDI_FAILURE); hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); hsc->scsb_handle = scsb_handle; hsc->dip = dip; hsc->instance = instance; hsc->n_registered_occupants = 0; hsc->regDone = B_FALSE; /* hsc->slot_info = hsc_slot_list; */ /* * Check whether the system should be in basic or full * hotswap mode. The PROM property always says full, so * look at the .conf file property whether this is "full" */ if (scsb_hsc_enable_fhs) { hsc->hotswap_mode = HSC_HOTSWAP_MODE_FULL; } else { hsc->hotswap_mode = HSC_HOTSWAP_MODE_BASIC; } rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "default-hotswap-mode", (caddr_t)&hotswap_model, &prop_len); if (rc == DDI_PROP_SUCCESS) { if (strcmp(hotswap_model, "full") == 0) { hsc->hotswap_mode = HSC_HOTSWAP_MODE_FULL; } else if (strcmp(hotswap_model, "basic") == 0) { hsc->hotswap_mode = HSC_HOTSWAP_MODE_BASIC; } kmem_free(hotswap_model, prop_len); } /* * Determine the size of the slot table from the property and * allocate the slot table arrary..Decoding is different for * OS and PROM property. */ if (!prom_prop) { /* OS .conf property */ for (i = 0, n = 0; i < hpc_slot_table_size; i++) { if (hpc_slot_table_data[i] == 0) { n++; } } /* There should be four elements per entry */ if (n % 4) { cmn_err(CE_WARN, "%s#%d: bad format for " "slot-table(%d)", ddi_driver_name(dip), ddi_get_instance(dip), n); kmem_free(hpc_slot_table_data, hpc_slot_table_size); ddi_soft_state_free(hsc_state, instance); return (DDI_FAILURE); } hsc->slot_table_size = n / 4; } else { hsc->slot_table_size = hpc_slot_table_size / sizeof (hsc_prom_slot_table_t); n = hpc_slot_table_size % sizeof (hsc_prom_slot_table_t); if (n) { cmn_err(CE_WARN, "%s#%d: bad format for " "slot-table(%d)", ddi_driver_name(dip), ddi_get_instance(dip), hpc_slot_table_size); kmem_free(hpc_slot_table_data, hpc_slot_table_size); ddi_soft_state_free(hsc_state, instance); return (DDI_FAILURE); } } /* * Netract800 FTC (formerly known as CFTM) workaround. * Leave Slot 2 out of the HS table if FTC is present in Slot 2 */ if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES) { hsc->slot_table_size -= 1; } DEBUG1("hsc_attach: %d hotplug slots on bus\n", hsc->slot_table_size); /* * Create enough space for each slot table entry * based on how many entries in the property */ hsc->slot_table_prop = (hsc_slot_table_t *) kmem_zalloc(hsc->slot_table_size * sizeof (hsc_slot_table_t), KM_SLEEP); if (!prom_prop) { s = hpc_slot_table_data; for (i = 0; i < hsc->slot_table_size; i++) { char *nexus, *pcidev, *phys_slotname, *ga; /* Pick off pointer to nexus path or PROM handle */ nexus = s; while (*s != NULL) s++; s++; /* Pick off pointer to the pci device number */ pcidev = s; while (*s != NULL) s++; s++; /* Pick off physical slot no */ phys_slotname = s; while (*s != NULL) s++; s++; /* Pick off GA bits which we dont use for now. */ ga = s; while (*s != NULL) s++; s++; if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES && atoi(phys_slotname) == SC_MC_CTC_SLOT) { --i; continue; } hsc->slot_table_prop[i].pslotnum = atoi(phys_slotname); hsc->slot_table_prop[i].ga = atoi(ga); hsc->slot_table_prop[i].pci_devno = atoi(pcidev); (void) strcpy(hsc->slot_table_prop[i].nexus, nexus); } } else { hpstp = (hsc_prom_slot_table_t *)hpc_slot_table_data; for (i = 0; i < hsc->slot_table_size; i++, hpstp++) { if (scsb->scsb_hsc_state & SCSB_HSC_CTC_PRES && hpstp->pslotnum == SC_MC_CTC_SLOT) { --i; continue; } hsc->slot_table_prop[i].pslotnum = hpstp->pslotnum; hsc->slot_table_prop[i].ga = hpstp->ga; hsc->slot_table_prop[i].pci_devno = hpstp->pci_devno; if (prom_phandle_to_path((uint_t)hpstp->phandle, hsc->slot_table_prop[i].nexus, sizeof (hsc->slot_table_prop[i].nexus)) == -1) { cmn_err(CE_WARN, "%s#%d: Cannot get phandle " "to nexus path", ddi_driver_name(dip), ddi_get_instance(dip)); kmem_free(hsc->slot_table_prop, (hsc->slot_table_size * sizeof (hsc_slot_table_t))); kmem_free(hpc_slot_table_data, hpc_slot_table_size); ddi_soft_state_free(hsc_state, instance); return (DDI_FAILURE); } } } /* keep healthy register cache uptodate before reading slot state */ if (scsb_read_bhealthy(scsb_handle) != 0) { cmn_err(CE_WARN, "%s#%d: hsc_attach: Cannot read " "Healthy Registers", ddi_driver_name(dip), ddi_get_instance(dip)); kmem_free(hsc->slot_table_prop, (hsc->slot_table_size * sizeof (hsc_slot_table_t))); kmem_free(hpc_slot_table_data, hpc_slot_table_size); ddi_soft_state_free(hsc_state, instance); return (DDI_FAILURE); } /* * Before we start registering the slots, calculate how many * slots are occupied. */ for (i = 0; i < hsc->slot_table_size; i++) { if (scsb_get_slot_state(scsb_handle, hsc->slot_table_prop[i].pslotnum, &rstate) != DDI_SUCCESS) return (rc); if (rstate != HPC_SLOT_EMPTY) hsc->n_registered_occupants++; } mutex_init(&hsc->hsc_mutex, NULL, MUTEX_DRIVER, NULL); for (i = 0; i < hsc->slot_table_size; i++) { DEBUG2("Registering on nexus [%s] cPCI device [%d]\n", hsc->slot_table_prop[i].nexus, hsc->slot_table_prop[i].pci_devno); if (hsc_slot_register(hsc, hsc->slot_table_prop[i].nexus, hsc->slot_table_prop[i].pci_devno, hsc->slot_table_prop[i].pslotnum, B_FALSE) != HPC_SUCCESS) { cmn_err(CE_WARN, "%s#%d: Slot Registration Failure", ddi_driver_name(dip), ddi_get_instance(dip)); while (i) { i--; n = hsc->slot_table_prop[i].pslotnum; if (hsc_slot_unregister(n) != 0) { cmn_err(CE_WARN, "%s#%d: failed to unregister" " slot %d", ddi_driver_name(dip), ddi_get_instance(dip), n); } } mutex_destroy(&hsc->hsc_mutex); kmem_free(hsc->slot_table_prop, (hsc->slot_table_size * sizeof (hsc_slot_table_t))); kmem_free(hpc_slot_table_data, hpc_slot_table_size); ddi_soft_state_free(hsc_state, instance); return (DDI_FAILURE); } } hsc->hsp_last = NULL; hsc->hsc_intr_counter = 0; kmem_free(hpc_slot_table_data, hpc_slot_table_size); (void) ddi_prop_update_string(DDI_DEV_T_NONE, hsc->dip, HOTSWAP_MODE_PROP, "basic"); hsc->state |= (HSC_ATTACHED|HSC_SCB_CONNECTED); /* * We enable full hotswap right here if all the slots are empty. */ if ((hsc->regDone == B_FALSE && hsc->n_registered_occupants == 0) || scsb_hsc_numReg == hsc->n_registered_occupants) { hsc->regDone = B_TRUE; if (hsc->hotswap_mode == HSC_HOTSWAP_MODE_FULL) { if (scsb_enable_enum(hsc) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s#%d: Cannot enable " "Full Hotswap", ddi_driver_name(dip), ddi_get_instance(dip)); } } } return (DDI_SUCCESS); } /*ARGSUSED*/ int scsb_hsc_detach(dev_info_t *dip, void *scsb_handle, int instance) { int i = 0; hsc_state_t *hsc; char slotautocfg_prop[18]; DEBUG0("hsc_detach: enter\n"); hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); if (hsc == NULL) { DEBUG2("%s#%d: hsc_detach: Soft state NULL", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_FAILURE); } if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED) return (DDI_FAILURE); /* * let's unregister the hotpluggable slots with hotplug service. */ for (i = 0; i < hsc->slot_table_size; i++) { hsc_slot_t *hsp; hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum); if (hsp == NULL) { cmn_err(CE_WARN, "%s#%d: hsc_detach: No Slot Info", ddi_driver_name(dip), ddi_get_instance(dip)); } else { hpc_led_info_t aledinfo; /* active led info. */ hpc_led_info_t fledinfo; /* fault led info. */ aledinfo.led = HPC_ACTIVE_LED; aledinfo.state = HPC_LED_BLINK; fledinfo.led = HPC_FAULT_LED; fledinfo.state = HPC_LED_OFF; (void) hsc_led_state(hsp, HPC_CTRL_SET_LED_STATE, &aledinfo); (void) hsc_led_state(hsp, HPC_CTRL_SET_LED_STATE, &fledinfo); } (void) sprintf(slotautocfg_prop, "slot%d-autoconfig", hsp->hs_slot_number); (void) ddi_prop_remove(DDI_DEV_T_NONE, hsc->dip, slotautocfg_prop); if (hsc_slot_unregister(hsc->slot_table_prop[i].pslotnum) != 0) { cmn_err(CE_NOTE, "%s#%d: failed to unregister" " slot %d\n", ddi_driver_name(dip), ddi_get_instance(dip), hsc->slot_table_prop[i].pslotnum); return (DDI_FAILURE); } } kmem_free(hsc->slot_table_prop, (hsc->slot_table_size * sizeof (hsc_slot_table_t))); if ((hsc->state & HSC_ENUM_ENABLED) == HSC_ENUM_ENABLED) { ddi_remove_intr(hsc->dip, 1, hsc->enum_iblock); hsc->state &= ~HSC_ENUM_ENABLED; } mutex_destroy(&hsc->hsc_mutex); (void) ddi_prop_remove(DDI_DEV_T_NONE, hsc->dip, HOTSWAP_MODE_PROP); hsc->state &= ~(HSC_ATTACHED|HSC_SCB_CONNECTED); ddi_soft_state_free(hsc_state, instance); return (DDI_SUCCESS); } /* * The following function is called when the SCSB is hot extracted from * the system. */ int scsb_hsc_freeze(dev_info_t *dip) { hsc_state_t *hsc; int instance = ddi_get_instance(dip); int i; hsc_slot_t *hsp; hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); if (hsc == NULL) { DEBUG2("%s#%d: Soft state NULL", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_SUCCESS); } if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED) return (DDI_SUCCESS); hsc->state &= ~HSC_SCB_CONNECTED; for (i = 0; i < hsc->slot_table_size; i++) { hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum); if (hsp == NULL) { cmn_err(CE_NOTE, "hsc_freeze: " " Cannot map slot number %d to a hsc_slot_t", hsc->slot_table_prop[i].pslotnum); continue; } /* * Since reset lines are pulled low, lets mark these * slots and not allow a connect operation. * Note that we still keep the slot as slot disconnected, * although it is connected from the hardware standpoint. * As soon as the SCB is plugged back in, we check these * states and put the hardware state back to its original * state. */ if (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) { cmn_err(CE_WARN, "%s#%d: Slot %d Now out of Reset!", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsp->hs_slot_number); } hsp->hs_flags |= HSC_SCB_HOTSWAPPED; } return (DDI_SUCCESS); } /* * The following function is called when the SCSB is hot inserted from * the system. We must update the LED status and set the RST# registers * again. */ int scsb_hsc_restore(dev_info_t *dip) { int i; hsc_state_t *hsc; hsc_slot_t *hsp; int instance = ddi_get_instance(dip); hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); if (hsc == NULL) { DEBUG2("%s#%d: Soft state NULL", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_SUCCESS); } if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED) return (DDI_SUCCESS); hsc->state |= HSC_SCB_CONNECTED; for (i = 0; i < hsc->slot_table_size; i++) { hsp = hsc_find_slot(hsc->slot_table_prop[i].pslotnum); if (hsp == NULL) { cmn_err(CE_NOTE, "%s#%d: hsc_restore: " " Cannot map slot number %d to a hsc_slot_t", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsc->slot_table_prop[i].pslotnum); continue; } if ((hsp->hs_slot_state == HPC_SLOT_DISCONNECTED) && (hsp->hs_board_configured == B_FALSE)) { if (scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_RESET_SLOT) != 0) { cmn_err(CE_WARN, "%s#%d: hsc_restore: " " Cannot reset disconnected slot %d", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsp->hs_slot_number); } } if (scsb_hsc_init_slot_state(hsc, hsp) != DDI_SUCCESS) { cmn_err(CE_WARN, "%s#%d: hsc_freeze: Cannot init" " slot%d state", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsp->hs_slot_number); } hsp->hs_flags &= ~HSC_SCB_HOTSWAPPED; } return (DDI_SUCCESS); } #ifndef lint int scsb_hsc_freeze_check(dev_info_t *dip) { hsc_state_t *hsc; int instance = ddi_get_instance(dip); hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); if (hsc == NULL) { DEBUG2("%s#%d: Soft state NULL", ddi_driver_name(dip), ddi_get_instance(dip)); return (DDI_SUCCESS); } if ((hsc->state & HSC_ATTACHED) != HSC_ATTACHED) return (DDI_SUCCESS); return (DDI_SUCCESS); } #endif /* * update info about Alarm Card insert/remove mechanism. */ void hsc_ac_op(int instance, int pslotnum, int op, void *arg) { hsc_slot_t *hsp; hsc_state_t *hsc; hsc = (hsc_state_t *)ddi_get_soft_state(hsc_state, instance); if (hsc == NULL) { cmn_err(CE_WARN, "%s#%d: hsc_ac_op: No Soft State Info", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); return; } hsp = hsc_find_slot(pslotnum); if (hsp == NULL) { cmn_err(CE_WARN, "%s#%d: hsc_ac_op: No Slot Info", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); return; } switch (op) { case SCSB_HSC_AC_UNCONFIGURE : /* * If ENUM# is enabled, then action is pending on * this slot, just send a event. */ if (hsc->state & HSC_ENUM_ENABLED) (void) hpc_slot_event_notify( hsp->hs_slot_handle, HPC_EVENT_PROCESS_ENUM, 0); break; case SCSB_HSC_AC_GET_SLOT_INFO : *(hsc_slot_t **)arg = hsp; break; default : break; } } static uint_t hsc_enum_intr(caddr_t iarg) { int rc; hsc_state_t *hsc = (hsc_state_t *)iarg; hsc_slot_t *hsp; DEBUG0("!E!"); if ((hsc->state & HSC_ATTACHED) == 0) return (DDI_INTR_UNCLAIMED); hsp = hsc_find_slot(hsc->slot_table_prop[0].pslotnum); if (hsp == NULL) /* No slots registered */ return (DDI_INTR_UNCLAIMED); /* * The following must be done to clear interrupt (synchronous event). * To process the interrupt, we send an asynchronous event. */ rc = hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_CLEAR_ENUM, HPC_EVENT_SYNCHRONOUS); if (rc == HPC_EVENT_UNCLAIMED) { /* * possible support for handling insertion of non friendly * full hotswap boards, otherwise the system hangs due * to uncleared interrupt bursts. */ DEBUG2("!E>counter %d, last op@slot %lx\n", hsc->hsc_intr_counter, hsc->hsp_last); hsc->hsc_intr_counter ++; if (hsc->hsc_intr_counter == scsb_hsc_max_intr_count) { if (!hsc->hsp_last) { cmn_err(CE_WARN, "%s#%d: hsc_enum_intr: " " No Last Board Insertion Info.", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); hsc->hsc_intr_counter = 0; return (DDI_INTR_UNCLAIMED); } hsp = hsc->hsp_last; cmn_err(CE_WARN, "%s#%d: Bad (non friendly ?) Board " "in Slot %d ? Taking it Offline.", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), hsp->hs_slot_number); /* * this should put just inserted board back in * reset, thus deasserting the ENUM# and the * system hang. */ if (scsb_reset_slot(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_RESET_SLOT) == 0) { /* Enumeration failed on this board */ hsp->hs_flags |= HSC_ENUM_FAILED; if (hsp->hs_board_configured == B_TRUE) cmn_err(CE_WARN, "%s#%d: ALERT! System" " now in Inconsistent State." " Halt!", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip)); hsc_led_op(hsp, HPC_CTRL_SET_LED_STATE, HPC_FAULT_LED, HPC_LED_ON); } hsc->hsc_intr_counter = 0; } return (DDI_INTR_UNCLAIMED); } hsc->hsc_intr_counter = 0; /* * if interrupt success, rc denotes the PCI device number which * generated the ENUM# interrupt. */ hsp = hsc_get_slot_info(hsc, rc); if (hsp == NULL) { cmn_err(CE_WARN, "%s#%d: hsc_enum_intr: no slot info for " "dev %x", ddi_driver_name(hsc->dip), ddi_get_instance(hsc->dip), rc); return (DDI_INTR_CLAIMED); /* interrupt already cleared */ } /* if this is Alarm Card and if it is busy, dont process event */ if (hsp->hs_flags & HSC_ALARM_CARD_PRES) { if (scsb_hsc_ac_op(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_HSC_AC_BUSY) == B_TRUE) { /* * Busy means we need to inform (envmond)alarmcard.so * that it should save the AC configuration, stop the * heartbeat, and shutdown the RSC link. */ (void) scsb_hsc_ac_op(hsp->hs_hpchandle, hsp->hs_slot_number, SCSB_HSC_AC_REMOVAL_ALERT); return (DDI_INTR_CLAIMED); } } /* * If SCB was swapped out, dont process ENUM#. We put this slot * back in reset after SCB is inserted. */ if ((hsp->hs_flags & HSC_SCB_HOTSWAPPED) && (hsp->hs_slot_state == HPC_SLOT_DISCONNECTED)) return (DDI_INTR_CLAIMED); (void) hpc_slot_event_notify(hsp->hs_slot_handle, HPC_EVENT_PROCESS_ENUM, 0); return (DDI_INTR_CLAIMED); } /* * A routine to convert a number (represented as a string) to * the integer value it represents. */ static int isdigit(int ch) { return (ch >= '0' && ch <= '9'); } #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') #define bad(val) (val == NULL || !isdigit(*val)) static int atoi(const char *p) { int n; int c, neg = 0; if (!isdigit(c = *p)) { while (isspace(c)) c = *++p; switch (c) { case '-': neg++; /* FALLTHROUGH */ case '+': c = *++p; } if (!isdigit(c)) return (0); } for (n = '0' - c; isdigit(c = *++p); ) { n *= 10; /* two steps to avoid unnecessary overflow */ n += '0' - c; /* accum neg to avoid surprises at MAX */ } return (neg ? n : -n); }