/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2019 Joyent, Inc. */ /* * This file contains PCI HotPlug functionality that is compatible with the * PCI SHPC specification 1.x. * * NOTE: This file is compiled and delivered through misc/pcie module. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct pcishpc_prop { char *prop_name; char *prop_value; } pcishpc_prop_t; static pcishpc_prop_t pcishpc_props[] = { { PCIEHPC_PROP_LED_FAULT, PCIEHPC_PROP_VALUE_LED }, { PCIEHPC_PROP_LED_POWER, PCIEHPC_PROP_VALUE_LED }, { PCIEHPC_PROP_LED_ATTN, PCIEHPC_PROP_VALUE_LED }, { PCIEHPC_PROP_LED_ACTIVE, PCIEHPC_PROP_VALUE_LED }, { PCIEHPC_PROP_CARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, { PCIEHPC_PROP_BOARD_TYPE, PCIEHPC_PROP_VALUE_TYPE }, { PCIEHPC_PROP_SLOT_CONDITION, PCIEHPC_PROP_VALUE_TYPE } }; /* reset delay to 1 sec. */ static int pcishpc_reset_delay = 1000000; /* Local function prototype */ static pcie_hp_ctrl_t *pcishpc_create_controller(dev_info_t *dip); static int pcishpc_setup_controller(pcie_hp_ctrl_t *ctrl_p); static int pcishpc_destroy_controller(dev_info_t *dip); static pcie_hp_slot_t *pcishpc_create_slot(pcie_hp_ctrl_t *ctrl_p); static int pcishpc_register_slot(pcie_hp_ctrl_t *ctrl_p, int slot); static int pcishpc_destroy_slots(pcie_hp_ctrl_t *ctrl_p); static int pcishpc_slot_get_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, ddi_hp_property_t *rval); static int pcishpc_slot_set_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, ddi_hp_property_t *rval); static int pcishpc_issue_command(pcie_hp_ctrl_t *ctrl_p, uint32_t cmd_code); static int pcishpc_wait_busy(pcie_hp_ctrl_t *ctrl_p); static void pcishpc_attn_btn_handler(pcie_hp_slot_t *slot_p); static void pcishpc_get_slot_state(pcie_hp_slot_t *slot_p); static int pcishpc_set_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t new_slot_state); static void pcishpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p, int slot); static int pcishpc_set_bus_speed(pcie_hp_slot_t *slot_p); static int pcishpc_setled(pcie_hp_slot_t *slot_p, pcie_hp_led_t led, pcie_hp_led_state_t state); static int pcishpc_led_shpc_to_hpc(int state); static int pcishpc_led_hpc_to_shpc(int state); static int pcishpc_slot_shpc_to_hpc(int shpc_state); static int pcishpc_slot_hpc_to_shpc(int state); static char *pcishpc_slot_textslotstate(ddi_hp_cn_state_t state); static char *pcishpc_slot_textledstate(pcie_hp_led_state_t state); static uint32_t pcishpc_read_reg(pcie_hp_ctrl_t *ctrl_p, int reg); static void pcishpc_write_reg(pcie_hp_ctrl_t *ctrl_p, int reg, uint32_t data); static int pcishpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state); static int pcishpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state); static int pcishpc_change_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state); static int pcishpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state); static int pcishpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state); static int pcishpc_slot_probe(pcie_hp_slot_t *slot_p); static int pcishpc_slot_unprobe(pcie_hp_slot_t *slot_p); #ifdef DEBUG static void pcishpc_dump_regs(pcie_hp_ctrl_t *ctrl_p); #endif /* DEBUG */ /* * Global functions (called by other drivers/modules) */ /* * pcishpc_init() * * Install and configure an SHPC controller and register the HotPlug slots * with the Solaris HotPlug framework. This function is usually called by * a PCI bridge Nexus driver that has a built in SHPC controller. */ int pcishpc_init(dev_info_t *dip) { pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); pcie_hp_ctrl_t *ctrl_p; int i; PCIE_DBG("pcishpc_init() called from %s#%d\n", ddi_driver_name(dip), ddi_get_instance(dip)); if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) != NULL) { PCIE_DBG("pcishpc_init() shpc instance already " "initialized!\n"); return (DDI_SUCCESS); } /* Initialize soft state structure for the SHPC instance. */ ctrl_p = pcishpc_create_controller(dip); if (ctrl_p == NULL) { PCIE_DBG("pcishpc_init() failed to create shpc softstate\n"); return (DDI_FAILURE); } if (pcishpc_setup_controller(ctrl_p) != DDI_SUCCESS) { PCIE_DBG("pcishpc_init() failed to setup controller\n"); goto cleanup; } /* * Setup resource maps for this bus node. */ (void) pci_resource_setup(dip); #ifdef DEBUG PCIE_DBG("%s%d: P2P bridge register dump:\n", ddi_driver_name(dip), ddi_get_instance(dip)); for (i = 0; i < 0x100; i += 4) { PCIE_DBG("SHPC Cfg reg 0x%02x: %08x\n", i, pci_config_get32(bus_p->bus_cfg_hdl, i)); } #endif /* DEBUG */ /* Setup each HotPlug slot on this SHPC controller. */ for (i = 0; i < ctrl_p->hc_num_slots_impl; i++) { if (pcishpc_register_slot(ctrl_p, i) != DDI_SUCCESS) { PCIE_DBG("pcishpc_init() failed to register " "slot %d\n", i); goto cleanup1; } if (pcie_create_minor_node(ctrl_p, i) != DDI_SUCCESS) { PCIE_DBG("pcishpc_init() failed to create " "minor node for slot %d\n", i); goto cleanup1; } } #ifdef DEBUG /* Dump out the SHPC registers. */ pcishpc_dump_regs(ctrl_p); #endif /* DEBUG */ PCIE_DBG("pcishpc_init() success(dip=%p)\n", dip); return (DDI_SUCCESS); cleanup1: for (i = 0; i < ctrl_p->hc_num_slots_impl; i++) { if (ctrl_p->hc_slots[i] == NULL) continue; pcie_remove_minor_node(ctrl_p, i); } (void) pci_resource_destroy(dip); cleanup: (void) pcishpc_destroy_controller(dip); return (DDI_FAILURE); } /* * pcishpc_uninit() * Unload the HogPlug controller driver and deallocate all resources. */ int pcishpc_uninit(dev_info_t *dip) { pcie_hp_ctrl_t *ctrl_p; int i; PCIE_DBG("pcishpc_uninit() called(dip=%p)\n", dip); ctrl_p = PCIE_GET_HP_CTRL(dip); if (!ctrl_p) { PCIE_DBG("pcishpc_uninit() Unable to find softstate\n"); return (DDI_FAILURE); } for (i = 0; i < PCIE_HP_MAX_SLOTS; i++) { if (ctrl_p->hc_slots[i] == NULL) continue; pcie_remove_minor_node(ctrl_p, i); } ctrl_p->hc_flags = 0; /* * Destroy resource maps for this bus node. */ (void) pci_resource_destroy(dip); (void) pcishpc_destroy_controller(dip); PCIE_DBG("pcishpc_uninit() success(dip=%p)\n", dip); return (DDI_SUCCESS); } /* * pcishpc_intr() * * This is the SHPC controller interrupt handler. */ int pcishpc_intr(dev_info_t *dip) { pcie_hp_ctrl_t *ctrl_p; uint32_t irq_locator, irq_serr_locator, reg; int slot; PCIE_DBG("pcishpc_intr() called\n"); /* get the soft state structure for this dip */ if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) return (DDI_INTR_UNCLAIMED); mutex_enter(&ctrl_p->hc_mutex); if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) { PCIE_DBG("pcishpc_intr() unclaimed\n"); mutex_exit(&ctrl_p->hc_mutex); return (DDI_INTR_UNCLAIMED); } PCIE_DBG("pcishpc_intr() interrupt received\n"); reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); if (reg & PCI_HP_SERR_INT_CMD_COMPLETE_IRQ) { PCIE_DBG("pcishpc_intr() " "PCI_HP_SERR_INT_CMD_COMPLETE_IRQ detected\n"); ctrl_p->hc_cmd_pending = B_FALSE; cv_signal(&ctrl_p->hc_cmd_comp_cv); } if (reg & PCI_HP_SERR_INT_ARBITER_IRQ) { PCIE_DBG("pcishpc_intr() PCI_HP_SERR_INT_ARBITER_IRQ " "detected\n"); ctrl_p->hc_arbiter_timeout = B_TRUE; } /* Write back the SERR INT register to acknowledge the IRQs. */ pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); irq_locator = pcishpc_read_reg(ctrl_p, PCI_HP_IRQ_LOCATOR_REG); irq_serr_locator = pcishpc_read_reg(ctrl_p, PCI_HP_SERR_LOCATOR_REG); /* Check for slot events that might have occured. */ for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { if ((irq_locator & (PCI_HP_IRQ_SLOT_N_PENDING<hc_slots[slot]-> hs_attn_btn_pending == B_TRUE) ctrl_p->hc_slots[slot]-> hs_attn_btn_pending = B_FALSE; /* wake up the ATTN event handler */ cv_signal(&ctrl_p->hc_slots[slot]-> hs_attn_btn_cv); } if (reg & PCI_HP_SLOT_MRL_DETECTED) PCIE_DBG("slot %d: " "PCI_HP_SLOT_MRL_DETECTED\n", slot+1); if (reg & PCI_HP_SLOT_POWER_DETECTED) PCIE_DBG("slot %d: " "PCI_HP_SLOT_POWER_DETECTED\n", slot+1); /* Acknoledge any slot interrupts */ pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, reg); } } mutex_exit(&ctrl_p->hc_mutex); PCIE_DBG("pcishpc_intr() claimed\n"); return (DDI_INTR_CLAIMED); } int pcishpc_slot_get_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, ddi_hp_property_t *rval) { ddi_hp_property_t request, result; #ifdef _SYSCALL32_IMPL ddi_hp_property32_t request32, result32; #endif pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; nvlist_t *prop_list; nvlist_t *prop_rlist; /* nvlist for return values */ nvpair_t *prop_pair; char *name, *value; int ret = DDI_SUCCESS; int i, n; boolean_t get_all_prop = B_FALSE; if (get_udatamodel() == DATAMODEL_NATIVE) { if (copyin(arg, &request, sizeof (ddi_hp_property_t)) || copyin(rval, &result, sizeof (ddi_hp_property_t))) return (DDI_FAILURE); } #ifdef _SYSCALL32_IMPL else { bzero(&request, sizeof (request)); bzero(&result, sizeof (result)); if (copyin(arg, &request32, sizeof (ddi_hp_property32_t)) || copyin(rval, &result32, sizeof (ddi_hp_property32_t))) return (DDI_FAILURE); request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; request.buf_size = request32.buf_size; result.nvlist_buf = (char *)(uintptr_t)result32.nvlist_buf; result.buf_size = result32.buf_size; } #endif if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, &prop_list)) != DDI_SUCCESS) return (ret); if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { ret = DDI_ENOMEM; goto get_prop_cleanup; } /* check whether the requested property is "all" or "help" */ prop_pair = nvlist_next_nvpair(prop_list, NULL); if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair)) { name = nvpair_name(prop_pair); n = sizeof (pcishpc_props) / sizeof (pcishpc_prop_t); if (strcmp(name, PCIEHPC_PROP_ALL) == 0) { (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_ALL); /* * Add all properties into the request list, so that we * will get the values in the following for loop. */ for (i = 0; i < n; i++) { if (nvlist_add_string(prop_list, pcishpc_props[i].prop_name, "") != 0) { ret = DDI_FAILURE; goto get_prop_cleanup1; } } get_all_prop = B_TRUE; } else if (strcmp(name, PCIEHPC_PROP_HELP) == 0) { /* * Empty the request list, and add help strings into the * return list. We will pass the following for loop. */ (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_HELP); for (i = 0; i < n; i++) { if (nvlist_add_string(prop_rlist, pcishpc_props[i].prop_name, pcishpc_props[i].prop_value) != 0) { ret = DDI_FAILURE; goto get_prop_cleanup1; } } } } mutex_enter(&ctrl_p->hc_mutex); /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* for each requested property, get the value and add it to nvlist */ prop_pair = NULL; while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) { name = nvpair_name(prop_pair); value = NULL; if (strcmp(name, PCIEHPC_PROP_LED_FAULT) == 0) { value = pcie_led_state_text( slot_p->hs_fault_led_state); } else if (strcmp(name, PCIEHPC_PROP_LED_POWER) == 0) { value = pcie_led_state_text( slot_p->hs_power_led_state); } else if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { value = pcie_led_state_text( slot_p->hs_attn_led_state); } else if (strcmp(name, PCIEHPC_PROP_LED_ACTIVE) == 0) { value = pcie_led_state_text( slot_p->hs_active_led_state); } else if (strcmp(name, PCIEHPC_PROP_CARD_TYPE) == 0) { ddi_acc_handle_t handle; dev_info_t *cdip; uint8_t prog_class, base_class, sub_class; int i; mutex_exit(&ctrl_p->hc_mutex); cdip = pcie_hp_devi_find( ctrl_p->hc_dip, slot_p->hs_device_num, 0); mutex_enter(&ctrl_p->hc_mutex); if ((slot_p->hs_info.cn_state != DDI_HP_CN_STATE_ENABLED) || (cdip == NULL)) { /* * When getting all properties, just ignore the * one that's not available under certain state. */ if (get_all_prop) continue; ret = DDI_ENOTSUP; goto get_prop_cleanup2; } if (pci_config_setup(cdip, &handle) != DDI_SUCCESS) { ret = DDI_FAILURE; goto get_prop_cleanup2; } prog_class = pci_config_get8(handle, PCI_CONF_PROGCLASS); base_class = pci_config_get8(handle, PCI_CONF_BASCLASS); sub_class = pci_config_get8(handle, PCI_CONF_SUBCLASS); pci_config_teardown(&handle); for (i = 0; i < class_pci_items; i++) { if ((base_class == class_pci[i].base_class) && (sub_class == class_pci[i].sub_class) && (prog_class == class_pci[i].prog_class)) { value = class_pci[i].short_desc; break; } } if (i == class_pci_items) value = PCIEHPC_PROP_VALUE_UNKNOWN; } else if (strcmp(name, PCIEHPC_PROP_BOARD_TYPE) == 0) { if (slot_p->hs_info.cn_state <= DDI_HP_CN_STATE_EMPTY) value = PCIEHPC_PROP_VALUE_UNKNOWN; else value = PCIEHPC_PROP_VALUE_PCIHOTPLUG; } else if (strcmp(name, PCIEHPC_PROP_SLOT_CONDITION) == 0) { value = pcie_slot_condition_text(slot_p->hs_condition); } else { /* unsupported property */ PCIE_DBG("Unsupported property: %s\n", name); ret = DDI_ENOTSUP; goto get_prop_cleanup2; } if (nvlist_add_string(prop_rlist, name, value) != 0) { ret = DDI_FAILURE; goto get_prop_cleanup2; } } // pack nvlist and copyout if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, &result.buf_size)) != DDI_SUCCESS) { goto get_prop_cleanup2; } if (get_udatamodel() == DATAMODEL_NATIVE) { if (copyout(&result, rval, sizeof (ddi_hp_property_t))) { ret = DDI_FAILURE; goto get_prop_cleanup2; } } #ifdef _SYSCALL32_IMPL else { if (result.buf_size > UINT32_MAX) { ret = DDI_FAILURE; } else { result32.buf_size = (uint32_t)result.buf_size; if (copyout(&result32, rval, sizeof (ddi_hp_property32_t))) ret = DDI_FAILURE; } } #endif get_prop_cleanup2: mutex_exit(&ctrl_p->hc_mutex); get_prop_cleanup1: nvlist_free(prop_rlist); get_prop_cleanup: nvlist_free(prop_list); return (ret); } int pcishpc_slot_set_property(pcie_hp_slot_t *slot_p, ddi_hp_property_t *arg, ddi_hp_property_t *rval) { ddi_hp_property_t request, result; #ifdef _SYSCALL32_IMPL ddi_hp_property32_t request32, result32; #endif pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; nvlist_t *prop_list; nvlist_t *prop_rlist; nvpair_t *prop_pair; char *name, *value; pcie_hp_led_state_t led_state; int ret = DDI_SUCCESS; if (get_udatamodel() == DATAMODEL_NATIVE) { if (copyin(arg, &request, sizeof (ddi_hp_property_t))) return (DDI_FAILURE); if (rval && copyin(rval, &result, sizeof (ddi_hp_property_t))) return (DDI_FAILURE); } #ifdef _SYSCALL32_IMPL else { bzero(&request, sizeof (request)); bzero(&result, sizeof (result)); if (copyin(arg, &request32, sizeof (ddi_hp_property32_t))) return (DDI_FAILURE); if (rval && copyin(rval, &result32, sizeof (ddi_hp_property32_t))) return (DDI_FAILURE); request.nvlist_buf = (char *)(uintptr_t)request32.nvlist_buf; request.buf_size = request32.buf_size; if (rval) { result.nvlist_buf = (char *)(uintptr_t)result32.nvlist_buf; result.buf_size = result32.buf_size; } } #endif if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size, &prop_list)) != DDI_SUCCESS) return (ret); /* check whether the requested property is "help" */ prop_pair = nvlist_next_nvpair(prop_list, NULL); if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair) && (strcmp(nvpair_name(prop_pair), PCIEHPC_PROP_HELP) == 0)) { if (!rval) { ret = DDI_ENOTSUP; goto set_prop_cleanup; } if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) { ret = DDI_ENOMEM; goto set_prop_cleanup; } if (nvlist_add_string(prop_rlist, PCIEHPC_PROP_LED_ATTN, PCIEHPC_PROP_VALUE_LED) != 0) { ret = DDI_FAILURE; goto set_prop_cleanup1; } if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf, &result.buf_size)) != DDI_SUCCESS) { goto set_prop_cleanup1; } if (get_udatamodel() == DATAMODEL_NATIVE) { if (copyout(&result, rval, sizeof (ddi_hp_property_t))) { ret = DDI_FAILURE; goto set_prop_cleanup1; } } #ifdef _SYSCALL32_IMPL else { if (result.buf_size > UINT32_MAX) { ret = DDI_FAILURE; goto set_prop_cleanup1; } else { result32.buf_size = (uint32_t)result.buf_size; if (copyout(&result32, rval, sizeof (ddi_hp_property32_t))) { ret = DDI_FAILURE; goto set_prop_cleanup1; } } } #endif set_prop_cleanup1: nvlist_free(prop_rlist); nvlist_free(prop_list); return (ret); } /* Validate the request */ prop_pair = NULL; while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) { name = nvpair_name(prop_pair); if (nvpair_type(prop_pair) != DATA_TYPE_STRING) { PCIE_DBG("Unexpected data type of setting " "property %s.\n", name); ret = DDI_EINVAL; goto set_prop_cleanup; } if (nvpair_value_string(prop_pair, &value)) { PCIE_DBG("Get string value failed for property %s.\n", name); ret = DDI_FAILURE; goto set_prop_cleanup; } if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { if ((strcmp(value, PCIEHPC_PROP_VALUE_ON) != 0) && (strcmp(value, PCIEHPC_PROP_VALUE_OFF) != 0) && (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) != 0)) { PCIE_DBG("Unsupported value of setting " "property %s\n", name); ret = DDI_ENOTSUP; goto set_prop_cleanup; } } else { PCIE_DBG("Unsupported property: %s\n", name); ret = DDI_ENOTSUP; goto set_prop_cleanup; } } mutex_enter(&ctrl_p->hc_mutex); /* get the current slot state */ pcishpc_get_slot_state(slot_p); // set each property prop_pair = NULL; while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) { name = nvpair_name(prop_pair); if (strcmp(name, PCIEHPC_PROP_LED_ATTN) == 0) { if (strcmp(value, PCIEHPC_PROP_VALUE_ON) == 0) led_state = PCIE_HP_LED_ON; else if (strcmp(value, PCIEHPC_PROP_VALUE_OFF) == 0) led_state = PCIE_HP_LED_OFF; else if (strcmp(value, PCIEHPC_PROP_VALUE_BLINK) == 0) led_state = PCIE_HP_LED_BLINK; else continue; (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, led_state); } } if (rval) { if (get_udatamodel() == DATAMODEL_NATIVE) { result.buf_size = 0; if (copyout(&result, rval, sizeof (ddi_hp_property_t))) ret = DDI_FAILURE; } #ifdef _SYSCALL32_IMPL else { result32.buf_size = 0; if (copyout(&result32, rval, sizeof (ddi_hp_property32_t))) ret = DDI_FAILURE; } #endif } mutex_exit(&ctrl_p->hc_mutex); set_prop_cleanup: nvlist_free(prop_list); return (ret); } /* * pcishpc_hp_ops() * * Handle hotplug commands * * Note: This function is called by DDI HP framework at kernel context only */ /* ARGSUSED */ int pcishpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op, void *arg, void *result) { pcie_hp_slot_t *slot_p = NULL; pcie_hp_ctrl_t *ctrl_p; int ret = DDI_SUCCESS, i; PCIE_DBG("pcishpc_hp_ops: dip=%p cn_name=%s op=%x arg=%p\n", dip, cn_name, op, arg); if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) return (DDI_FAILURE); for (i = 0; i < PCIE_HP_MAX_SLOTS && ctrl_p->hc_slots[i]; i++) { if (strcmp(ctrl_p->hc_slots[i]->hs_info.cn_name, cn_name) == 0) { /* Match with a physical slot, found */ slot_p = ctrl_p->hc_slots[i]; break; } } if (!slot_p) { PCIE_DBG("pcishpc_hp_ops: Failed to find the slot under" "dip %p with name: %s; op=%x arg=%p\n", dip, cn_name, op, arg); return (DDI_EINVAL); } switch (op) { case DDI_HPOP_CN_GET_STATE: { mutex_enter(&ctrl_p->hc_mutex); /* get the current slot state */ pcishpc_get_slot_state(slot_p); *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state; mutex_exit(&ctrl_p->hc_mutex); break; } case DDI_HPOP_CN_CHANGE_STATE: { ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg; mutex_enter(&slot_p->hs_ctrl->hc_mutex); ret = pcishpc_change_slot_state(slot_p, target_state); *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state; mutex_exit(&slot_p->hs_ctrl->hc_mutex); break; } case DDI_HPOP_CN_PROBE: ret = pcishpc_slot_probe(slot_p); break; case DDI_HPOP_CN_UNPROBE: ret = pcishpc_slot_unprobe(slot_p); break; case DDI_HPOP_CN_GET_PROPERTY: ret = pcishpc_slot_get_property(slot_p, (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); break; case DDI_HPOP_CN_SET_PROPERTY: ret = pcishpc_slot_set_property(slot_p, (ddi_hp_property_t *)arg, (ddi_hp_property_t *)result); break; default: ret = DDI_ENOTSUP; break; } return (ret); } /* * Local functions (called within this file) */ /* * pcishpc_create_controller() * * This function allocates and creates an SHPC controller state structure * and adds it to the linked list of controllers. */ static pcie_hp_ctrl_t * pcishpc_create_controller(dev_info_t *dip) { pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); pcie_hp_ctrl_t *ctrl_p; PCIE_DBG("pcishpc: create controller for %s#%d\n", ddi_driver_name(dip), ddi_get_instance(dip)); ctrl_p = kmem_zalloc(sizeof (pcie_hp_ctrl_t), KM_SLEEP); ctrl_p->hc_dip = dip; cv_init(&ctrl_p->hc_cmd_comp_cv, NULL, CV_DRIVER, NULL); /* Init the shpc controller's mutex. */ mutex_init(&ctrl_p->hc_mutex, NULL, MUTEX_DRIVER, NULL); /* HPC initialization is complete now */ ctrl_p->hc_flags = PCIE_HP_INITIALIZED_FLAG; bus_p->bus_hp_curr_mode = PCIE_PCI_HP_MODE; PCIE_SET_HP_CTRL(dip, ctrl_p); PCIE_DBG("pcishpc_create_controller() success\n"); return (ctrl_p); } /* * pcishpc_setup_controller() * * Get the number of HotPlug Slots, and the PCI device information * for this HotPlug controller. */ static int pcishpc_setup_controller(pcie_hp_ctrl_t *ctrl_p) { uint32_t config; dev_info_t *ppdip; config = pcishpc_read_reg(ctrl_p, PCI_HP_SLOT_CONFIGURATION_REG); /* Get the number of HotPlug slots implemented */ ctrl_p->hc_num_slots_impl = ((config)&31); /* * Initilize the current bus speed and number of hotplug slots * currently connected. */ ctrl_p->hc_curr_bus_speed = -1; ctrl_p->hc_num_slots_connected = 0; /* * Get the first PCI device Number used. * * PCI-X I/O boat workaround. * The register doesn't set up the correct value. */ ppdip = ddi_get_parent(ddi_get_parent(ctrl_p->hc_dip)); if ((ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, "vendor-id", -1) == 0x108e) && (ddi_prop_get_int(DDI_DEV_T_ANY, ppdip, DDI_PROP_DONTPASS, "device-id", -1) == 0x9010)) ctrl_p->hc_device_start = 4; else ctrl_p->hc_device_start = ((config>>8)&31); /* Get the first Physical device number. */ ctrl_p->hc_phys_start = ((config>>16)&0x7ff); /* Check if the device numbers increase or decrease. */ ctrl_p->hc_device_increases = ((config>>29)&0x1); ctrl_p->hc_has_attn = (config & PCI_HP_SLOT_CONFIG_ATTN_BUTTON) ? B_TRUE : B_FALSE; ctrl_p->hc_has_mrl = (config & PCI_HP_SLOT_CONFIG_MRL_SENSOR) ? B_TRUE : B_FALSE; ctrl_p->hc_cmd_pending = B_FALSE; ctrl_p->hc_arbiter_timeout = B_FALSE; if (ctrl_p->hc_num_slots_impl > PCIE_HP_MAX_SLOTS) { PCIE_DBG("pcishpc_setup_controller() too many SHPC " "slots error\n"); return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * pcishpc_destroy_controller() * * This function deallocates all of the SHPC controller resources. */ static int pcishpc_destroy_controller(dev_info_t *dip) { pcie_hp_ctrl_t *ctrl_p; pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); PCIE_DBG("pcishpc_destroy_controller() called(dip=%p)\n", dip); /* get the soft state structure for this dip */ if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) { PCIE_DBG("pcishpc_destroy_controller() not found\n"); return (DDI_FAILURE); } /* * Deallocate the slot state structures for this controller. */ PCIE_SET_HP_CTRL(dip, NULL); bus_p->bus_hp_curr_mode = PCIE_NONE_HP_MODE; (void) pcishpc_destroy_slots(ctrl_p); cv_destroy(&ctrl_p->hc_cmd_comp_cv); mutex_destroy(&ctrl_p->hc_mutex); kmem_free(ctrl_p, sizeof (pcie_hp_ctrl_t)); PCIE_DBG("pcishpc_destroy_controller() success\n"); return (DDI_SUCCESS); } /* * pcishpc_create_slot() * * Allocate and add a new HotPlug slot state structure to the linked list. */ static pcie_hp_slot_t * pcishpc_create_slot(pcie_hp_ctrl_t *ctrl_p) { pcie_hp_slot_t *slot_p; PCIE_DBG("pcishpc_create_slot() called(ctrl_p=%x)\n", ctrl_p); /* Allocate a new slot structure. */ slot_p = kmem_zalloc(sizeof (pcie_hp_slot_t), KM_SLEEP); slot_p->hs_ctrl = ctrl_p; /* Assign an initial value */ slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY; PCIE_DBG("pcishpc_create_slot() success\n"); return (slot_p); } /* * pcishpc_register_slot() * * Create and register a slot with the Solaris HotPlug framework. */ static int pcishpc_register_slot(pcie_hp_ctrl_t *ctrl_p, int slot) { dev_info_t *dip = ctrl_p->hc_dip; pcie_hp_slot_t *slot_p; slot_p = pcishpc_create_slot(ctrl_p); ctrl_p->hc_slots[slot] = slot_p; slot_p->hs_num = slot; /* Setup the PCI device # for this SHPC slot. */ if (ctrl_p->hc_device_increases) slot_p->hs_device_num = ctrl_p->hc_device_start + slot_p->hs_num; else slot_p->hs_device_num = ctrl_p->hc_device_start - slot_p->hs_num; /* Setup the DDI HP framework slot information. */ slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCI; slot_p->hs_info.cn_type_str = PCIE_PCI_HP_TYPE; slot_p->hs_info.cn_child = NULL; slot_p->hs_minor = PCI_MINOR_NUM( ddi_get_instance(dip), slot_p->hs_device_num); slot_p->hs_condition = AP_COND_UNKNOWN; /* setup thread for handling ATTN button events */ if (ctrl_p->hc_has_attn) { PCIE_DBG("pcishpc_register_slot: " "setting up ATTN button event " "handler thread for slot %d\n", slot); cv_init(&slot_p->hs_attn_btn_cv, NULL, CV_DRIVER, NULL); slot_p->hs_attn_btn_pending = B_FALSE; slot_p->hs_attn_btn_threadp = thread_create(NULL, 0, pcishpc_attn_btn_handler, (void *)slot_p, 0, &p0, TS_RUN, minclsyspri); slot_p->hs_attn_btn_thread_exit = B_FALSE; } /* setup the slot name (used for ap-id) */ pcishpc_set_slot_name(ctrl_p, slot); pcishpc_get_slot_state(slot_p); if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED) slot_p->hs_condition = AP_COND_OK; /* register the slot with DDI HP framework */ if (ndi_hp_register(dip, &slot_p->hs_info) != NDI_SUCCESS) { PCIE_DBG("pciehpc_register_slot() failed to register slot %d\n", slot_p->hs_phy_slot_num); return (DDI_FAILURE); } pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip), slot_p->hs_minor), slot_p->hs_device_num); PCIE_DBG("pcishpc_register_slot() success for slot %d\n", slot); return (DDI_SUCCESS); } /* * pcishpc_destroy_slots() * * Free up all of the slot resources for this controller. */ static int pcishpc_destroy_slots(pcie_hp_ctrl_t *ctrl_p) { dev_info_t *dip = ctrl_p->hc_dip; pcie_hp_slot_t *slot_p; int i; PCIE_DBG("pcishpc_destroy_slots() called(ctrl_p=%p)\n", ctrl_p); for (i = 0; i < PCIE_HP_MAX_SLOTS; i++) { if ((slot_p = ctrl_p->hc_slots[i]) == NULL) continue; if (slot_p->hs_attn_btn_threadp != NULL) { mutex_enter(&ctrl_p->hc_mutex); slot_p->hs_attn_btn_thread_exit = B_TRUE; cv_signal(&slot_p->hs_attn_btn_cv); PCIE_DBG("pcishpc_destroy_slots: " "waiting for ATTN thread exit\n"); cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex); PCIE_DBG("pcishpc_destroy_slots: " "ATTN thread exit\n"); cv_destroy(&slot_p->hs_attn_btn_cv); slot_p->hs_attn_btn_threadp = NULL; mutex_exit(&ctrl_p->hc_mutex); } PCIE_DBG("pcishpc_destroy_slots() (shpc_p=%p)\n" "destroyed", slot_p); pcie_hp_delete_occupant_props(dip, makedevice(ddi_driver_major(dip), slot_p->hs_minor)); /* unregister the slot with DDI HP framework */ if (ndi_hp_unregister(dip, slot_p->hs_info.cn_name) != NDI_SUCCESS) { PCIE_DBG("pcishpc_destroy_slots() " "failed to unregister slot %d\n", slot_p->hs_phy_slot_num); return (DDI_FAILURE); } kmem_free(slot_p->hs_info.cn_name, strlen(slot_p->hs_info.cn_name) + 1); kmem_free(slot_p, sizeof (pcie_hp_slot_t)); } return (DDI_SUCCESS); } /* * pcishpc_enable_irqs() * * Enable/unmask the different IRQ's we support from the SHPC controller. */ int pcishpc_enable_irqs(pcie_hp_ctrl_t *ctrl_p) { uint32_t reg; int slot; reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); /* Enable all interrupts. */ reg &= ~PCI_HP_SERR_INT_MASK_ALL; pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); /* Unmask the interrupts for each slot. */ for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { reg = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot); if ((reg & PCI_HP_SLOT_STATE_MASK) == PCI_HP_SLOT_ENABLED) { reg &= ~(PCI_HP_SLOT_MASK_ALL | PCI_HP_SLOT_MRL_SERR_MASK); ctrl_p->hc_num_slots_connected++; if (ctrl_p->hc_curr_bus_speed == -1) ctrl_p->hc_curr_bus_speed = pcishpc_read_reg(ctrl_p, PCI_HP_PROF_IF_SBCR_REG) & PCI_HP_SBCR_SPEED_MASK; } else { reg &= ~(PCI_HP_SLOT_MASK_ALL); } /* Enable/Unmask all slot interrupts. */ pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, reg); } PCIE_DBG("pcishpc_enable_irqs: ctrl_p 0x%p, " "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, ctrl_p->hc_curr_bus_speed, ctrl_p->hc_num_slots_connected); return (DDI_SUCCESS); } /* * pcishpc_disable_irqs() * * Disable/Mask the different IRQ's we support from the SHPC controller. */ int pcishpc_disable_irqs(pcie_hp_ctrl_t *ctrl_p) { uint32_t reg; int slot; reg = pcishpc_read_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG); /* Mask all interrupts. */ reg |= PCI_HP_SERR_INT_MASK_ALL; pcishpc_write_reg(ctrl_p, PCI_HP_CTRL_SERR_INT_REG, reg); /* Unmask the interrupts for each slot. */ for (slot = 0; slot < ctrl_p->hc_num_slots_impl; slot++) { reg = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot); /* Disable/Mask all slot interrupts. */ reg |= PCI_HP_SLOT_MASK_ALL; pcishpc_write_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot, reg); } PCIE_DBG("pcishpc_disable_irqs: ctrl_p 0x%p, " "current bus speed 0x%x, slots connected 0x%x\n", ctrl_p, ctrl_p->hc_curr_bus_speed, ctrl_p->hc_num_slots_connected); return (DDI_SUCCESS); } /* * pcishpc_slot_poweron() * * Poweron/Enable the slot. * * Note: This function is called by DDI HP framework at kernel context only */ /*ARGSUSED*/ static int pcishpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state) { uint32_t status; PCIE_DBG("pcishpc_slot_poweron called()\n"); ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* check if the slot is already in the 'enabled' state */ if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) { /* slot is already in the 'enabled' state */ PCIE_DBG("pcishpc_slot_poweron() slot %d already enabled\n", slot_p->hs_phy_slot_num); *result_state = slot_p->hs_info.cn_state; return (DDI_SUCCESS); } if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_EMPTY) { PCIE_DBG("pcishpc_slot_poweron() slot in empty state\n"); goto cleanup; } /* make sure the MRL sensor is closed */ status = pcishpc_read_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); if (status & PCI_HP_SLOT_MRL_STATE_MASK) { PCIE_DBG("pcishpc_slot_poweron() failed: MRL open\n"); goto cleanup; } /* Set the Power LED to blink */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); /* Turn all other LEDS off */ (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); /* Set the bus speed only if the bus segment is not running */ if (pcishpc_set_bus_speed(slot_p) != DDI_SUCCESS) { PCIE_DBG("pcishpc_slot_poweron() setting speed failed\n"); goto cleanup; } slot_p->hs_ctrl->hc_num_slots_connected++; PCIE_DBG("pcishpc_slot_poweron(): slot_p 0x%p, slot state 0x%x, " "current bus speed 0x%x, slots connected 0x%x\n", slot_p, slot_p->hs_info.cn_state, slot_p->hs_ctrl->hc_curr_bus_speed, slot_p->hs_ctrl->hc_num_slots_connected); /* Mask or Unmask MRL Sensor SEER bit based on new slot state */ if (slot_p->hs_ctrl->hc_has_mrl == B_TRUE) { uint32_t reg; reg = pcishpc_read_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); pcishpc_write_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num, reg & ~PCI_HP_SLOT_MRL_SERR_MASK); } /* Update the hardware slot state. */ if (pcishpc_set_slot_state(slot_p, DDI_HP_CN_STATE_ENABLED) != DDI_SUCCESS) { PCIE_DBG("pcishpc_slot_poweron() failed\n"); pcishpc_get_slot_state(slot_p); goto cleanup; } /* Update the current state. It will be used in pcishpc_setled() */ slot_p->hs_info.cn_state = DDI_HP_CN_STATE_ENABLED; /* Turn the Power LED ON for a enabled slot. */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_ON); /* Turn all other LEDS off. */ (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); /* delay after powerON to let the device initialize itself */ delay(drv_usectohz(pcishpc_reset_delay)); PCIE_DBG("pcishpc_slot_poweron() success!\n"); /* * Want to show up as POWERED state for now. It will be updated to * ENABLED state when user explicitly enable the slot. */ slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* * It should be poweron'ed now. Have a check here in case any * hardware problems. */ if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { PCIE_DBG("pcishpc_slot_poweron() failed after hardware" " registers all programmed.\n"); goto cleanup; } *result_state = slot_p->hs_info.cn_state; return (DDI_SUCCESS); cleanup: (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); return (DDI_FAILURE); } /*ARGSUSED*/ static int pcishpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result_state) { PCIE_DBG("pcishpc_slot_poweroff called()\n"); ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* check if the slot is not in the "enabled" or "powered" state */ if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { /* slot is in the 'disabled' state */ PCIE_DBG("pcishpc_slot_poweroff(): " "slot %d already disabled\n", slot_p->hs_phy_slot_num); *result_state = slot_p->hs_info.cn_state; return (DDI_SUCCESS); } /* Set the Power LED to blink */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); /* Turn all other LEDS off */ (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); if (--slot_p->hs_ctrl->hc_num_slots_connected == 0) slot_p->hs_ctrl->hc_curr_bus_speed = -1; PCIE_DBG("pcishpc_slot_poweroff(): slot_p 0x%p, slot state 0x%x, " "current bus speed 0x%x, slots connected 0x%x\n", slot_p, slot_p->hs_info.cn_state, slot_p->hs_ctrl->hc_curr_bus_speed, slot_p->hs_ctrl->hc_num_slots_connected); /* Mask or Unmask MRL Sensor SEER bit based on new slot state */ if (slot_p->hs_ctrl->hc_has_mrl == B_TRUE) { uint32_t reg; reg = pcishpc_read_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); pcishpc_write_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num, reg | PCI_HP_SLOT_MRL_SERR_MASK); } /* Update the hardware slot state. */ if (pcishpc_set_slot_state(slot_p, DDI_HP_CN_STATE_PRESENT) != DDI_SUCCESS) { PCIE_DBG("pcishpc_slot_poweroff() failed\n"); pcishpc_get_slot_state(slot_p); goto cleanup; } /* Update the current state. It will be used in pcishpc_setled() */ slot_p->hs_info.cn_state = DDI_HP_CN_STATE_PRESENT; /* Turn the Power LED OFF for a disabled slot. */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); /* Turn all other LEDS off. */ (void) pcishpc_setled(slot_p, PCIE_HP_FAULT_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_OFF); (void) pcishpc_setled(slot_p, PCIE_HP_ACTIVE_LED, PCIE_HP_LED_OFF); /* delay after powerON to let the device initialize itself */ delay(drv_usectohz(pcishpc_reset_delay)); pcishpc_get_slot_state(slot_p); /* * It should be poweroff'ed now. Have a check here in case any * hardware problems. */ if (slot_p->hs_info.cn_state > DDI_HP_CN_STATE_PRESENT) { PCIE_DBG("pcishpc_slot_poweroff() failed after hardware" " registers all programmed.\n"); goto cleanup; } PCIE_DBG("pcishpc_slot_poweroff() success!\n"); *result_state = slot_p->hs_info.cn_state; return (DDI_SUCCESS); cleanup: (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_OFF); return (DDI_FAILURE); } /* * pcishpc_slot_probe() * * Probe the slot. * * Note: This function is called by DDI HP framework at kernel context only */ /*ARGSUSED*/ static int pcishpc_slot_probe(pcie_hp_slot_t *slot_p) { mutex_enter(&slot_p->hs_ctrl->hc_mutex); PCIE_DBG("pcishpc_slot_probe called()\n"); /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* * Probe a given PCI Hotplug Connection (CN). */ if (pcie_hp_probe(slot_p) != DDI_SUCCESS) { (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_BLINK); PCIE_DBG("pcishpc_slot_probe() failed\n"); mutex_exit(&slot_p->hs_ctrl->hc_mutex); return (DDI_FAILURE); } PCIE_DBG("pcishpc_slot_probe() success!\n"); /* get the current slot state */ pcishpc_get_slot_state(slot_p); mutex_exit(&slot_p->hs_ctrl->hc_mutex); return (DDI_SUCCESS); } /* * pcishpc_slot_unprobe() * * Unprobe the slot. * * Note: This function is called by DDI HP framework at kernel context only */ /*ARGSUSED*/ static int pcishpc_slot_unprobe(pcie_hp_slot_t *slot_p) { mutex_enter(&slot_p->hs_ctrl->hc_mutex); PCIE_DBG("pcishpc_slot_unprobe called()\n"); /* get the current slot state */ pcishpc_get_slot_state(slot_p); /* * Unprobe a given PCI Hotplug Connection (CN). */ if (pcie_hp_unprobe(slot_p) != DDI_SUCCESS) { (void) pcishpc_setled(slot_p, PCIE_HP_ATTN_LED, PCIE_HP_LED_BLINK); PCIE_DBG("pcishpc_slot_unprobe() failed\n"); mutex_exit(&slot_p->hs_ctrl->hc_mutex); return (DDI_FAILURE); } PCIE_DBG("pcishpc_slot_unprobe() success!\n"); /* get the current slot state */ pcishpc_get_slot_state(slot_p); mutex_exit(&slot_p->hs_ctrl->hc_mutex); return (DDI_SUCCESS); } static int pcishpc_upgrade_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state) { ddi_hp_cn_state_t curr_state; int rv = DDI_SUCCESS; if (target_state > DDI_HP_CN_STATE_ENABLED) { return (DDI_EINVAL); } curr_state = slot_p->hs_info.cn_state; while ((curr_state < target_state) && (rv == DDI_SUCCESS)) { switch (curr_state) { case DDI_HP_CN_STATE_EMPTY: /* * From EMPTY to PRESENT, just check the hardware * slot state. */ pcishpc_get_slot_state(slot_p); curr_state = slot_p->hs_info.cn_state; if (curr_state < DDI_HP_CN_STATE_PRESENT) rv = DDI_FAILURE; break; case DDI_HP_CN_STATE_PRESENT: rv = pcishpc_slot_poweron(slot_p, &curr_state); break; case DDI_HP_CN_STATE_POWERED: curr_state = slot_p->hs_info.cn_state = DDI_HP_CN_STATE_ENABLED; break; default: /* should never reach here */ ASSERT("unknown devinfo state"); } } return (rv); } static int pcishpc_downgrade_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state) { ddi_hp_cn_state_t curr_state; int rv = DDI_SUCCESS; curr_state = slot_p->hs_info.cn_state; while ((curr_state > target_state) && (rv == DDI_SUCCESS)) { switch (curr_state) { case DDI_HP_CN_STATE_PRESENT: /* * From PRESENT to EMPTY, just check hardware * slot state. */ pcishpc_get_slot_state(slot_p); curr_state = slot_p->hs_info.cn_state; if (curr_state >= DDI_HP_CN_STATE_PRESENT) rv = DDI_FAILURE; break; case DDI_HP_CN_STATE_POWERED: rv = pcishpc_slot_poweroff(slot_p, &curr_state); break; case DDI_HP_CN_STATE_ENABLED: curr_state = slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; break; default: /* should never reach here */ ASSERT("unknown devinfo state"); } } return (rv); } /* Change slot state to a target state */ static int pcishpc_change_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t target_state) { ddi_hp_cn_state_t curr_state; int rv; pcishpc_get_slot_state(slot_p); curr_state = slot_p->hs_info.cn_state; if (curr_state == target_state) { return (DDI_SUCCESS); } if (curr_state < target_state) { rv = pcishpc_upgrade_slot_state(slot_p, target_state); } else { rv = pcishpc_downgrade_slot_state(slot_p, target_state); } return (rv); } /* * pcishpc_issue_command() * * Sends a command to the SHPC controller. */ static int pcishpc_issue_command(pcie_hp_ctrl_t *ctrl_p, uint32_t cmd_code) { int retCode; ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); PCIE_DBG("pcishpc_issue_command() cmd_code=%02x\n", cmd_code); ctrl_p->hc_cmd_pending = B_TRUE; /* Write the command to the SHPC controller. */ pcishpc_write_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG, cmd_code); while (ctrl_p->hc_cmd_pending == B_TRUE) cv_wait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex); /* Wait until the SHPC controller processes the command. */ retCode = pcishpc_wait_busy(ctrl_p); /* Make sure the command completed. */ if (retCode == DDI_SUCCESS) { /* Did the command fail to generate the command complete IRQ? */ if (ctrl_p->hc_cmd_pending != B_FALSE) { PCIE_DBG("pcishpc_issue_command() Failed on " "generate cmd complete IRQ\n"); retCode = DDI_FAILURE; } } if (retCode == DDI_FAILURE) PCIE_DBG("pcishpc_issue_command() Failed on cmd_code=%02x\n", cmd_code); else PCIE_DBG("pcishpc_issue_command() Success on " "cmd_code=%02x\n", cmd_code); return (retCode); } /* * pcishpc_wait_busy() * * Wait until the SHPC controller is not busy. */ static int pcishpc_wait_busy(pcie_hp_ctrl_t *ctrl_p) { uint32_t status; /* Wait until SHPC controller is NOT busy */ for (;;) { status = pcishpc_read_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG); /* Is there an MRL Sensor error? */ if ((status & PCI_HP_COMM_STS_ERR_MASK) == PCI_HP_COMM_STS_ERR_MRL_OPEN) { PCIE_DBG("pcishpc_wait_busy() ERROR: " "MRL Sensor error\n"); break; } /* Is there an Invalid command error? */ if ((status & PCI_HP_COMM_STS_ERR_MASK) == PCI_HP_COMM_STS_ERR_INVALID_COMMAND) { PCIE_DBG("pcishpc_wait_busy() ERROR: Invalid " "command error\n"); break; } /* Is there an Invalid Speed/Mode error? */ if ((status & PCI_HP_COMM_STS_ERR_MASK) == PCI_HP_COMM_STS_ERR_INVALID_SPEED) { PCIE_DBG("pcishpc_wait_busy() ERROR: Invalid " "Speed/Mode error\n"); break; } /* Is the SHPC controller not BUSY? */ if (!(status & PCI_HP_COMM_STS_CTRL_BUSY)) { /* Return Success. */ return (DDI_SUCCESS); } PCIE_DBG("pcishpc_wait_busy() SHPC controller busy. Waiting\n"); /* Wait before polling the status register again. */ delay(drv_usectohz(PCIE_HP_CMD_WAIT_TIME)); } return (DDI_FAILURE); } static void pcishpc_attn_btn_handler(pcie_hp_slot_t *slot_p) { pcie_hp_led_state_t hs_power_led_state; callb_cpr_t cprinfo; PCIE_DBG("pcishpc_attn_btn_handler: thread started\n"); CALLB_CPR_INIT(&cprinfo, &slot_p->hs_ctrl->hc_mutex, callb_generic_cpr, "pcishpc_attn_btn_handler"); mutex_enter(&slot_p->hs_ctrl->hc_mutex); /* wait for ATTN button event */ cv_wait(&slot_p->hs_attn_btn_cv, &slot_p->hs_ctrl->hc_mutex); while (slot_p->hs_attn_btn_thread_exit == B_FALSE) { if (slot_p->hs_attn_btn_pending == B_TRUE) { /* get the current state of power LED */ hs_power_led_state = slot_p->hs_power_led_state; /* Blink the Power LED while we wait for 5 seconds */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, PCIE_HP_LED_BLINK); /* wait for 5 seconds before taking any action */ if (cv_reltimedwait(&slot_p->hs_attn_btn_cv, &slot_p->hs_ctrl->hc_mutex, SEC_TO_TICK(5), TR_CLOCK_TICK) == -1) { /* * It is a time out; * make sure the ATTN pending flag is * still ON before sending the event * to DDI HP framework. */ if (slot_p->hs_attn_btn_pending == B_TRUE) { int hint; /* restore the power LED state */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, hs_power_led_state); /* * send the ATTN button event * to DDI HP framework */ slot_p->hs_attn_btn_pending = B_FALSE; pcishpc_get_slot_state(slot_p); if (slot_p->hs_info.cn_state <= DDI_HP_CN_STATE_PRESENT) { /* * Insertion. */ hint = SE_INCOMING_RES; } else { /* * Want to remove; */ hint = SE_OUTGOING_RES; } pcie_hp_gen_sysevent_req( slot_p->hs_info.cn_name, hint, slot_p->hs_ctrl->hc_dip, KM_SLEEP); continue; } } /* restore the power LED state */ (void) pcishpc_setled(slot_p, PCIE_HP_POWER_LED, hs_power_led_state); continue; } /* wait for another ATTN button event */ cv_wait(&slot_p->hs_attn_btn_cv, &slot_p->hs_ctrl->hc_mutex); } PCIE_DBG("pcishpc_attn_btn_handler: thread exit\n"); cv_signal(&slot_p->hs_attn_btn_cv); CALLB_CPR_EXIT(&cprinfo); thread_exit(); } /* * pcishpc_get_slot_state() * * Get the state of the slot. * The slot state should have been initialized before this function gets called. */ static void pcishpc_get_slot_state(pcie_hp_slot_t *slot_p) { uint32_t reg; ddi_hp_cn_state_t curr_state = slot_p->hs_info.cn_state; /* Read the logical slot register for this Slot. */ reg = pcishpc_read_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); /* Convert from the SHPC slot state to the HPC slot state. */ slot_p->hs_info.cn_state = pcishpc_slot_shpc_to_hpc(reg); if (curr_state == DDI_HP_CN_STATE_POWERED && slot_p->hs_info.cn_state > DDI_HP_CN_STATE_POWERED) { /* * Keep POWERED state if it is currently POWERED state because * this driver does not really implement enable/disable * slot operations. That is, when poweron, it actually enables * the slot also. * So, from hardware view, POWERED == ENABLED. * But, when user explicitly change to POWERED state, it should * be kept until user explicitly change to other states later. */ slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; } /* Convert from the SHPC Power LED state to the HPC Power LED state. */ slot_p->hs_power_led_state = pcishpc_led_shpc_to_hpc((reg>>2)&3); /* Convert from the SHPC Attn LED state to the HPC Attn LED state. */ slot_p->hs_attn_led_state = pcishpc_led_shpc_to_hpc((reg>>4)&3); /* We don't have a fault LED so just default it to OFF. */ slot_p->hs_fault_led_state = PCIE_HP_LED_OFF; /* We don't have an active LED so just default it to OFF. */ slot_p->hs_active_led_state = PCIE_HP_LED_OFF; } /* * pcishpc_set_slot_state() * * Updates the slot's state and leds. */ static int pcishpc_set_slot_state(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t new_slot_state) { uint32_t reg, cmd_code; ddi_hp_cn_state_t curr_state; ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); reg = pcishpc_read_reg(slot_p->hs_ctrl, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); /* Default all states to unchanged. */ cmd_code = ((1 + slot_p->hs_num) << 8); /* Has the slot state changed? */ curr_state = pcishpc_slot_shpc_to_hpc(reg); if (curr_state != new_slot_state) { PCIE_DBG("pcishpc_set_slot_state() Slot State changed"); /* Set the new slot state in the Slot operation command. */ cmd_code |= pcishpc_slot_hpc_to_shpc(new_slot_state); } /* Has the Power LED state changed? */ if (slot_p->hs_power_led_state != pcishpc_led_shpc_to_hpc((reg>>2)&3)) { PCIE_DBG("pcishpc_set_slot_state() Power LED State changed\n"); /* Set the new power led state in the Slot operation command. */ cmd_code |= (pcishpc_led_hpc_to_shpc(slot_p->hs_power_led_state) << 2); } /* Has the Attn LED state changed? */ if (slot_p->hs_attn_led_state != pcishpc_led_shpc_to_hpc((reg>>4)&3)) { PCIE_DBG("pcishpc_set_slot_state() Attn LED State changed\n"); /* Set the new attn led state in the Slot operation command. */ cmd_code |= (pcishpc_led_hpc_to_shpc(slot_p->hs_attn_led_state) << 4); } return (pcishpc_issue_command(slot_p->hs_ctrl, cmd_code)); } /* * setup slot name/slot-number info. */ static void pcishpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p, int slot) { pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[slot]; pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); uchar_t *slotname_data; int *slotnum; uint_t count; int len; uchar_t *s; uint32_t bit_mask; int pci_id_cnt, pci_id_bit; int slots_before, found; int invalid_slotnum = 0; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) == DDI_PROP_SUCCESS) { slot_p->hs_phy_slot_num = slotnum[0]; ddi_prop_free(slotnum); } else { if (ctrl_p->hc_device_increases) slot_p->hs_phy_slot_num = ctrl_p->hc_phys_start + slot; else slot_p->hs_phy_slot_num = ctrl_p->hc_phys_start - slot; if ((ndi_prop_update_int(DDI_DEV_T_NONE, ctrl_p->hc_dip, "physical-slot#", slot_p->hs_phy_slot_num)) != DDI_SUCCESS) PCIE_DBG("pcishpc_set_slot_name(): failed to " "create phyical-slot#%d\n", slot_p->hs_phy_slot_num); } /* Platform may not have initialized it */ if (!slot_p->hs_phy_slot_num) { slot_p->hs_phy_slot_num = pci_config_get8(bus_p->bus_cfg_hdl, PCI_BCNF_SECBUS); invalid_slotnum = 1; } slot_p->hs_info.cn_num = slot_p->hs_phy_slot_num; slot_p->hs_info.cn_num_dpd_on = DDI_HP_CN_NUM_NONE; /* * construct the slot_name: * if "slot-names" property exists then use that name * else if valid slot number exists then it is "pci". * else it will be "pcidev" */ if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS, "slot-names", (caddr_t)&slotname_data, &len) == DDI_PROP_SUCCESS) { bit_mask = slotname_data[3] | (slotname_data[2] << 8) | (slotname_data[1] << 16) | (slotname_data[0] << 24); pci_id_bit = 1; pci_id_cnt = slots_before = found = 0; /* * Walk the bit mask until we find the bit that corresponds * to our slots device number. We count how many bits * we find before we find our slot's bit. */ while (!found && (pci_id_cnt < 32)) { while (slot_p->hs_device_num != pci_id_cnt) { /* * Find the next bit set. */ while (!(bit_mask & pci_id_bit) && (pci_id_cnt < 32)) { pci_id_bit = pci_id_bit << 1; pci_id_cnt++; } if (slot_p->hs_device_num != pci_id_cnt) slots_before++; else found = 1; } } if (pci_id_cnt < 32) { /* * Set ptr to first string. */ s = slotname_data + 4; /* * Increment past all the strings for the slots * before ours. */ while (slots_before) { while (*s != '\0') s++; s++; slots_before--; } slot_p->hs_info.cn_name = i_ddi_strdup((char *)s, KM_SLEEP); kmem_free(slotname_data, len); return; } /* slot-names entry not found */ PCIE_DBG("pcishpc_set_slot_name(): " "No slot-names entry found for slot #%d\n", slot_p->hs_phy_slot_num); kmem_free(slotname_data, len); } if (invalid_slotnum) { char tmp_name[256]; (void) snprintf(tmp_name, sizeof (tmp_name), "pci%d", slot_p->hs_device_num); slot_p->hs_info.cn_name = i_ddi_strdup(tmp_name, KM_SLEEP); } else { char tmp_name[256]; (void) snprintf(tmp_name, sizeof (tmp_name), "pci%d", slot_p->hs_phy_slot_num); slot_p->hs_info.cn_name = i_ddi_strdup(tmp_name, KM_SLEEP); } } /* * pcishpc_set_bus_speed() * * Set the bus speed and mode. */ static int pcishpc_set_bus_speed(pcie_hp_slot_t *slot_p) { pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; int curr_speed = ctrl_p->hc_curr_bus_speed; int speed = -1; int avail_slots; uint32_t status, slots_avail1_reg, slots_avail2_reg; ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); /* Make sure that the slot is in a correct state */ status = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); /* Return failure if the slot is empty */ if ((status & PCI_HP_SLOT_CARD_EMPTY_MASK) == PCI_HP_SLOT_CARD_EMPTY_MASK) { PCIE_DBG("pcishpc_set_bus_speed() failed: " "the slot is empty\n"); return (DDI_FAILURE); } /* Return failure if the slot is not in disabled state */ if ((status & PCI_HP_SLOT_STATE_MASK) != PCI_HP_SLOT_DISABLED) { PCIE_DBG("pcishpc_set_bus_speed() failed: " "incorrect slot state\n"); return (DDI_FAILURE); } /* Set the "power-only" mode for the slot */ if (pcishpc_issue_command(ctrl_p, ((1+slot_p->hs_num)<<8) | PCI_HP_SLOT_POWER_ONLY) != DDI_SUCCESS) { PCIE_DBG("pcishpc_set_bus_speed() failed to set " "the slot %d in the power-only mode\n", slot_p->hs_num); return (DDI_FAILURE); } /* Wait for power good */ delay(drv_usectohz(PCIE_HP_POWER_GOOD_WAIT_TIME)); /* Make sure that the slot is in "power-only" state */ status = pcishpc_read_reg(ctrl_p, PCI_HP_LOGICAL_SLOT_REGS+slot_p->hs_num); if ((status & PCI_HP_SLOT_STATE_MASK) != PCI_HP_SLOT_POWER_ONLY) { PCIE_DBG("pcishpc_set_bus_speed() " "power-only failed: incorrect slot state\n"); return (DDI_FAILURE); } slots_avail1_reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_I_REG); slots_avail2_reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_II_REG); /* * Check if SHPC has available slots and select the highest * available bus speed for the slot. * * The bus speed codes are: * 100 - 133Mhz; <--+ * 011 - 100Mhz; <--+ PCI-X * 010 - 66Mhz; <--+ * * 001 - 66Mhz; <--+ * 000 - 33Mhz <--+ Conv PCI */ switch (status & PCI_HP_SLOT_PCIX_CAPABLE_MASK) { case PCI_HP_SLOT_133MHZ_PCIX_CAPABLE: avail_slots = (slots_avail1_reg >> PCI_HP_AVAIL_133MHZ_PCIX_SPEED_SHIFT) & PCI_HP_AVAIL_SPEED_MASK; if (((curr_speed == -1) && avail_slots) || (curr_speed == PCI_HP_SBCR_133MHZ_PCIX_SPEED)) { speed = PCI_HP_SBCR_133MHZ_PCIX_SPEED; break; } /* FALLTHROUGH */ case PCI_HP_SLOT_100MHZ_PCIX_CAPABLE: avail_slots = (slots_avail1_reg >> PCI_HP_AVAIL_100MHZ_PCIX_SPEED_SHIFT) & PCI_HP_AVAIL_SPEED_MASK; if (((curr_speed == -1) && avail_slots) || (curr_speed == PCI_HP_SBCR_100MHZ_PCIX_SPEED)) { speed = PCI_HP_SBCR_100MHZ_PCIX_SPEED; break; } /* FALLTHROUGH */ case PCI_HP_SLOT_66MHZ_PCIX_CAPABLE: avail_slots = (slots_avail1_reg >> PCI_HP_AVAIL_66MHZ_PCIX_SPEED_SHIFT) & PCI_HP_AVAIL_SPEED_MASK; if (((curr_speed == -1) && avail_slots) || (curr_speed == PCI_HP_SBCR_66MHZ_PCIX_SPEED)) { speed = PCI_HP_SBCR_66MHZ_PCIX_SPEED; break; } /* FALLTHROUGH */ default: avail_slots = (slots_avail2_reg >> PCI_HP_AVAIL_66MHZ_CONV_SPEED_SHIFT) & PCI_HP_AVAIL_SPEED_MASK; if ((status & PCI_HP_SLOT_66MHZ_CONV_CAPABLE) && (((curr_speed == -1) && avail_slots) || (curr_speed == PCI_HP_SBCR_66MHZ_CONV_SPEED))) { speed = PCI_HP_SBCR_66MHZ_CONV_SPEED; } else { avail_slots = (slots_avail1_reg >> PCI_HP_AVAIL_33MHZ_CONV_SPEED_SHIFT) & PCI_HP_AVAIL_SPEED_MASK; if (((curr_speed == -1) && (avail_slots)) || (curr_speed == PCI_HP_SBCR_33MHZ_CONV_SPEED)) { speed = PCI_HP_SBCR_33MHZ_CONV_SPEED; } else { PCIE_DBG("pcishpc_set_bus_speed() " " failed to set the bus speed, slot# %d\n", slot_p->hs_num); return (DDI_FAILURE); } } break; } /* * If the bus segment is already running, check to see the card * in the slot can support the current bus speed. */ if (curr_speed == speed) { /* * Check to see there is any slot available for the current * bus speed. Otherwise, we need fail the current slot connect * request. */ return ((avail_slots <= ctrl_p->hc_num_slots_connected) ? DDI_FAILURE : DDI_SUCCESS); } /* Set the bus speed */ if (pcishpc_issue_command(ctrl_p, PCI_HP_COMM_STS_SET_SPEED | speed) == DDI_FAILURE) { PCIE_DBG("pcishpc_set_bus_speed() failed " "to set bus %d speed\n", slot_p->hs_num); return (DDI_FAILURE); } /* Check the current bus speed */ status = pcishpc_read_reg(ctrl_p, PCI_HP_PROF_IF_SBCR_REG) & PCI_HP_SBCR_SPEED_MASK; if ((status & PCI_HP_SBCR_SPEED_MASK) != speed) { PCIE_DBG("pcishpc_set_bus_speed() an incorrect " "bus speed, slot = 0x%x, speed = 0x%x\n", slot_p->hs_num, status & PCI_HP_SBCR_SPEED_MASK); return (DDI_FAILURE); } /* Save the current bus speed */ ctrl_p->hc_curr_bus_speed = speed; return (DDI_SUCCESS); } /* * pcishpc_setled() * * Change the state of a slot's LED. */ static int pcishpc_setled(pcie_hp_slot_t *slot_p, pcie_hp_led_t led, pcie_hp_led_state_t state) { ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex)); switch (led) { case PCIE_HP_FAULT_LED: PCIE_DBG("pcishpc_setled() - PCIE_HP_FAULT_LED " "(set %s)\n", pcishpc_slot_textledstate(state)); slot_p->hs_fault_led_state = state; break; case PCIE_HP_POWER_LED: PCIE_DBG("pcishpc_setled() - PCIE_HP_POWER_LED " "(set %s)\n", pcishpc_slot_textledstate(state)); slot_p->hs_power_led_state = state; break; case PCIE_HP_ATTN_LED: PCIE_DBG("pcishpc_setled() - PCIE_HP_ATTN_LED " "(set %s)\n", pcishpc_slot_textledstate(state)); slot_p->hs_attn_led_state = state; break; case PCIE_HP_ACTIVE_LED: PCIE_DBG("pcishpc_setled() - PCIE_HP_ACTIVE_LED " "(set %s)\n", pcishpc_slot_textledstate(state)); slot_p->hs_active_led_state = state; break; } return (pcishpc_set_slot_state(slot_p, slot_p->hs_info.cn_state)); } /* * pcishpc_led_shpc_to_hpc() * * Convert from SHPC indicator status to HPC indicator status. */ static int pcishpc_led_shpc_to_hpc(int state) { switch (state) { case 1: /* SHPC On bits b01 */ return (PCIE_HP_LED_ON); case 2: /* SHPC Blink bits b10 */ return (PCIE_HP_LED_BLINK); case 3: /* SHPC Off bits b11 */ return (PCIE_HP_LED_OFF); } return (PCIE_HP_LED_OFF); } /* * pcishpc_led_hpc_to_shpc() * * Convert from HPC indicator status to SHPC indicator status. */ static int pcishpc_led_hpc_to_shpc(int state) { switch (state) { case PCIE_HP_LED_ON: return (1); /* SHPC On bits b01 */ case PCIE_HP_LED_BLINK: return (2); /* SHPC Blink bits b10 */ case PCIE_HP_LED_OFF: return (3); /* SHPC Off bits b11 */ } return (3); /* SHPC Off bits b11 */ } /* * pcishpc_slot_shpc_to_hpc() * * Convert from SHPC slot state to HPC slot state. * The argument shpc_state is expected to be read from the slot register. */ static int pcishpc_slot_shpc_to_hpc(int shpc_state) { if ((shpc_state & PCI_HP_SLOT_CARD_EMPTY_MASK) == PCI_HP_SLOT_CARD_EMPTY_MASK) return (DDI_HP_CN_STATE_EMPTY); switch (shpc_state & PCI_HP_SLOT_STATE_MASK) { case PCI_HP_SLOT_POWER_ONLY: /* SHPC Powered Only */ return (DDI_HP_CN_STATE_POWERED); case PCI_HP_SLOT_ENABLED: /* SHPC Enabled */ return (DDI_HP_CN_STATE_ENABLED); case PCI_HP_SLOT_DISABLED: /* SHPC Disabled */ default : /* SHPC Reserved */ return (DDI_HP_CN_STATE_PRESENT); } } /* * pcishpc_slot_hpc_to_shpc() * * Convert from HPC slot state to SHPC slot state. */ static int pcishpc_slot_hpc_to_shpc(int state) { switch (state) { case DDI_HP_CN_STATE_EMPTY: return (0); case DDI_HP_CN_STATE_POWERED: return (PCI_HP_SLOT_POWER_ONLY); case DDI_HP_CN_STATE_ENABLED: return (PCI_HP_SLOT_ENABLED); default: return (PCI_HP_SLOT_DISABLED); } } /* * pcishpc_slot_textslotstate() * * Convert the request into a text message. */ static char * pcishpc_slot_textslotstate(ddi_hp_cn_state_t state) { /* Convert an HPC slot state into a textual string. */ if (state == DDI_HP_CN_STATE_EMPTY) return ("HPC_SLOT_EMPTY"); else if (state == DDI_HP_CN_STATE_ENABLED) return ("HPC_SLOT_ENABLED"); else if (state == DDI_HP_CN_STATE_POWERED) return ("HPC_SLOT_POWERED_ONLY"); else return ("HPC_SLOT_DISABLED"); } /* * pcishpc_slot_textledstate() * * Convert the led state into a text message. */ static char * pcishpc_slot_textledstate(pcie_hp_led_state_t state) { /* Convert an HPC led state into a textual string. */ switch (state) { case PCIE_HP_LED_OFF: return ("off"); case PCIE_HP_LED_ON: return ("on"); case PCIE_HP_LED_BLINK: return ("blink"); } return ("unknown"); } /* * pcishpc_read_reg() * * Read from a SHPC controller register. */ static uint32_t pcishpc_read_reg(pcie_hp_ctrl_t *ctrl_p, int reg) { pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); /* Setup the SHPC dword select register. */ pci_config_put8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF, (uint8_t)reg); /* Read back the SHPC dword select register and verify. */ if (pci_config_get8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF) != (uint8_t)reg) { PCIE_DBG("pcishpc_read_reg() - Failed writing DWORD " "select reg\n"); return (0xFFFFFFFF); } /* Read from the SHPC dword data register. */ return (pci_config_get32(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_DATA_OFF)); } /* * pcishpc_write_reg() * * Write to a SHPC controller register. */ static void pcishpc_write_reg(pcie_hp_ctrl_t *ctrl_p, int reg, uint32_t data) { pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); /* Setup the SHPC dword select register. */ pci_config_put8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF, (uint8_t)reg); /* Read back the SHPC dword select register and verify. */ if (pci_config_get8(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_SELECT_OFF) != (uint8_t)reg) { PCIE_DBG("pcishpc_write_reg() - Failed writing " "DWORD select reg\n"); return; } /* Write to the SHPC dword data register. */ pci_config_put32(bus_p->bus_cfg_hdl, bus_p->bus_pci_hp_off + PCI_HP_DWORD_DATA_OFF, data); /* * Issue a read of the VendorID/DeviceID just to force the previous * write to complete. This is probably not necessary, but it does * help enforce ordering if there is an issue. */ (void) pci_config_get16(bus_p->bus_cfg_hdl, PCI_CONF_VENID); } #ifdef DEBUG /* * pcishpc_dump_regs() * * Dumps all of the SHPC controller registers. */ static void pcishpc_dump_regs(pcie_hp_ctrl_t *ctrl_p) { int slot, numSlots; uint32_t reg; char *state; if (!pcie_debug_flags) return; PCIE_DBG("pcishpc_dump_regs() called:\n"); PCIE_DBG("=========================================================="); PCIE_DBG("SHPC Base Offset " ": 0x%08x\n", pcishpc_read_reg(ctrl_p, PCI_HP_BASE_OFFSET_REG)); reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_I_REG); PCIE_DBG("Number of PCIX slots avail (33 Mhz) : %d\n", (reg & 31)); PCIE_DBG("Number of PCIX slots avail (66 Mhz) : %d\n", ((reg>>8) & 31)); PCIE_DBG("Number of PCIX slots avail (100 Mhz) : %d\n", ((reg>>16) & 31)); PCIE_DBG("Number of PCIX slots avail (133 Mhz) : %d\n", ((reg>>24) & 31)); reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOTS_AVAIL_II_REG); PCIE_DBG("Number of conventional PCI slots (66 Mhz) : %d\n", (reg & 31)); reg = pcishpc_read_reg(ctrl_p, PCI_HP_SLOT_CONFIGURATION_REG); numSlots = (reg & 31); PCIE_DBG("Number of Slots connected to this port : %d\n", numSlots); PCIE_DBG("PCI Device # for First HotPlug Slot : %d\n", ((reg>>8) & 31)); PCIE_DBG("Physical Slot # for First PCI Device # : %d\n", ((reg>>16) & 0x7ff)); PCIE_DBG("Physical Slot Number Up/Down : %d\n", ((reg>>29) & 0x1)); PCIE_DBG("MRL Sensor Implemented : %s\n", (reg & PCI_HP_SLOT_CONFIG_MRL_SENSOR) ? "Yes" : "No"); PCIE_DBG("Attention Button Implemented : %s\n", (reg & PCI_HP_SLOT_CONFIG_ATTN_BUTTON) ? "Yes" : "No"); reg = pcishpc_read_reg(ctrl_p, PCI_HP_PROF_IF_SBCR_REG); switch (reg & 7) { case 0: state = "33Mhz Conventional PCI"; break; case 1: state = "66Mhz Conventional PCI"; break; case 2: state = "66Mhz PCI-X"; break; case 3: state = "100Mhz PCI-X"; break; case 4: state = "133Mhz PCI-X"; break; default: state = "Reserved (Error)"; break; } PCIE_DBG("Current Port Operation Mode : %s\n", state); PCIE_DBG("SHPC Interrupt Message Number : %d\n", ((reg>>16) &31)); PCIE_DBG("SHPC Programming Interface : %d\n", ((reg>>24) & 0xff)); reg = pcishpc_read_reg(ctrl_p, PCI_HP_COMMAND_STATUS_REG); PCIE_DBG("SHPC Command Code : %d\n", (reg & 0xff)); PCIE_DBG("SHPC Target Slot : %d\n", ((reg>>8) & 31)); PCIE_DBG("SHPC Controller Busy : %s\n", ((reg>>16) & 1) ? "Yes" : "No"); PCIE_DBG("SHPC Controller Err: MRL Sensor : %s\n", ((reg>>17) & 1) ? "Yes" : "No"); PCIE_DBG("SHPC Controller Err: Invalid Command : %s\n", ((reg>>18) & 1) ? "Yes" : "No"); PCIE_DBG("SHPC Controller Err: Invalid Speed/Mode : %s\n", ((reg>>19) & 1) ? "Yes" : "No"); reg = pcishpc_read_reg(ctrl_p, PCI_HP_IRQ_LOCATOR_REG); PCIE_DBG("Command Completion Interrupt Pending : %s\n", (reg & PCI_HP_IRQ_CMD_COMPLETE) ? "Yes" : "No"); for (slot = 0; slot < numSlots; slot++) { PCIE_DBG("Slot %d Interrupt Pending : %s\n", slot+1, (reg & (PCI_HP_IRQ_SLOT_N_PENDING<>2) &3))); PCIE_DBG("Slot %d Attention Indicator State : %s\n", slot+1, pcishpc_slot_textledstate(pcishpc_led_shpc_to_hpc( (reg>>4)&3))); PCIE_DBG("Slot %d Power Fault : %s\n", slot+1, ((reg>>6)&1) ? "Fault Detected" : "No Fault"); PCIE_DBG("Slot %d Attention Button : %s\n", slot+1, ((reg>>7)&1) ? "Depressed" : "Not Depressed"); PCIE_DBG("Slot %d MRL Sensor : %s\n", slot+1, ((reg>>8)&1) ? "Not Closed" : "Closed"); PCIE_DBG("Slot %d 66mhz Capable : %s\n", slot+1, ((reg>>9)&1) ? "66mhz" : "33mgz"); switch ((reg>>10)&3) { case 0: state = "Card Present 7.5W"; break; case 1: state = "Card Present 15W"; break; case 2: state = "Card Present 25W"; break; case 3: state = "Slot Empty"; break; } PCIE_DBG("Slot %d PRSNT1#/PRSNT2# : %s\n", slot+1, state); switch ((reg>>12)&3) { case 0: state = "Non PCI-X"; break; case 1: state = "66mhz PCI-X"; break; case 2: state = "Reserved"; break; case 3: state = "133mhz PCI-X"; break; } PCIE_DBG("Slot %d Card Presence Change Detected : %s\n", slot+1, (reg & PCI_HP_SLOT_PRESENCE_DETECTED) ? "Yes" : "No"); PCIE_DBG("Slot %d Isolated Power Fault Detected : %s\n", slot+1, (reg & PCI_HP_SLOT_ISO_PWR_DETECTED) ? "Yes" : "No"); PCIE_DBG("Slot %d Attention Button Press Detected : %s\n", slot+1, (reg & PCI_HP_SLOT_ATTN_DETECTED) ? "Yes" : "No"); PCIE_DBG("Slot %d MRL Sensor Change Detected : %s\n", slot+1, (reg & PCI_HP_SLOT_MRL_DETECTED) ? "Yes" : "No"); PCIE_DBG("Slot %d Connected Power Fault Detected : %s\n", slot+1, (reg & PCI_HP_SLOT_POWER_DETECTED) ? "Yes" : "No"); PCIE_DBG("Slot %d Card Presence IRQ Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_PRESENCE_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d Isolated Power Fault IRQ Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_ISO_PWR_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d Attention Button IRQ Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_ATTN_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d MRL Sensor IRQ Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_MRL_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d Connected Power Fault IRQ Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_POWER_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d MRL Sensor SERR Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_MRL_SERR_MASK) ? "Yes" : "No"); PCIE_DBG("Slot %d Connected Power Fault SERR Masked : %s\n", slot+1, (reg & PCI_HP_SLOT_POWER_SERR_MASK) ? "Yes" : "No"); } } #endif /* DEBUG */