/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2018, Joyent, Inc. * Copyright 2024 Oxide Computer Co. */ /* * ACPI interface related functions used in PCIEHPC driver module. * * 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 /* local static functions */ static int pciehpc_acpi_hpc_init(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_hpc_uninit(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_slotinfo_init(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_enable_intr(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_disable_intr(pcie_hp_ctrl_t *ctrl_p); static int pciehpc_acpi_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result); static int pciehpc_acpi_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result); static void pciehpc_acpi_setup_ops(pcie_hp_ctrl_t *ctrl_p); static ACPI_STATUS pciehpc_acpi_install_event_handler(pcie_hp_ctrl_t *ctrl_p); static void pciehpc_acpi_uninstall_event_handler(pcie_hp_ctrl_t *ctrl_p); static ACPI_STATUS pciehpc_acpi_power_on_slot(pcie_hp_ctrl_t *ctrl_p); static ACPI_STATUS pciehpc_acpi_power_off_slot(pcie_hp_ctrl_t *ctrl_p); static void pciehpc_acpi_notify_handler(ACPI_HANDLE device, uint32_t val, void *context); static ACPI_STATUS pciehpc_acpi_get_dev_state(ACPI_HANDLE obj, int *statusp); /* * Update ops vector with platform specific (ACPI, CK8-04,...) functions. */ void pciehpc_update_ops(pcie_hp_ctrl_t *ctrl_p) { boolean_t hp_native_mode = B_FALSE; uint32_t osc_flags = OSC_CONTROL_PCIE_NAT_HP; /* * Call _OSC method to determine if hotplug mode is native or ACPI. * If _OSC method succeeds hp_native_mode below will be set according to * if native hotplug control was granted or not by BIOS. * * If _OSC method fails for any reason or if native hotplug control was * not granted assume it's ACPI mode and update platform specific * (ACPI, CK8-04,...) impl. ops */ if (pcie_acpi_osc(ctrl_p->hc_dip, &osc_flags) == DDI_SUCCESS) { hp_native_mode = (osc_flags & OSC_CONTROL_PCIE_NAT_HP) ? B_TRUE : B_FALSE; } if (!hp_native_mode) { pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); /* update ops vector for ACPI mode */ pciehpc_acpi_setup_ops(ctrl_p); bus_p->bus_hp_sup_modes |= PCIE_ACPI_HP_MODE; bus_p->bus_hp_curr_mode = PCIE_ACPI_HP_MODE; } } void pciehpc_acpi_setup_ops(pcie_hp_ctrl_t *ctrl_p) { ctrl_p->hc_ops.init_hpc_hw = pciehpc_acpi_hpc_init; ctrl_p->hc_ops.uninit_hpc_hw = pciehpc_acpi_hpc_uninit; ctrl_p->hc_ops.init_hpc_slotinfo = pciehpc_acpi_slotinfo_init; ctrl_p->hc_ops.uninit_hpc_slotinfo = pciehpc_acpi_slotinfo_uninit; ctrl_p->hc_ops.poweron_hpc_slot = pciehpc_acpi_slot_poweron; ctrl_p->hc_ops.poweroff_hpc_slot = pciehpc_acpi_slot_poweroff; ctrl_p->hc_ops.disable_hpc_intr = pciehpc_acpi_disable_intr; ctrl_p->hc_ops.enable_hpc_intr = pciehpc_acpi_enable_intr; } /* * Intialize hot plug control for ACPI mode. */ static int pciehpc_acpi_hpc_init(pcie_hp_ctrl_t *ctrl_p) { ACPI_HANDLE pcibus_obj; int status = AE_ERROR; ACPI_HANDLE slot_dev_obj; ACPI_HANDLE hdl; pciehpc_acpi_t *acpi_p; uint16_t bus_methods = 0; uint16_t slot_methods = 0; /* get the ACPI object for the bus node */ status = acpica_get_handle(ctrl_p->hc_dip, &pcibus_obj); if (status != AE_OK) return (DDI_FAILURE); /* get the ACPI object handle for the child node */ status = AcpiGetNextObject(ACPI_TYPE_DEVICE, pcibus_obj, NULL, &slot_dev_obj); if (status != AE_OK) { PCIE_DBG("pciehpc_acpi_hpc_init: Get ACPI object failed\n"); return (DDI_FAILURE); } /* * gather the info about the ACPI methods present on the bus node * and the child nodes. */ if (AcpiGetHandle(pcibus_obj, "_OSC", &hdl) == AE_OK) bus_methods |= PCIEHPC_ACPI_OSC_PRESENT; if (AcpiGetHandle(pcibus_obj, "_OSHP", &hdl) == AE_OK) bus_methods |= PCIEHPC_ACPI_OSHP_PRESENT; if (AcpiGetHandle(pcibus_obj, "_HPX", &hdl) == AE_OK) bus_methods |= PCIEHPC_ACPI_HPX_PRESENT; if (AcpiGetHandle(pcibus_obj, "_HPP", &hdl) == AE_OK) bus_methods |= PCIEHPC_ACPI_HPP_PRESENT; if (AcpiGetHandle(pcibus_obj, "_DSM", &hdl) == AE_OK) bus_methods |= PCIEHPC_ACPI_DSM_PRESENT; if (AcpiGetHandle(slot_dev_obj, "_SUN", &hdl) == AE_OK) slot_methods |= PCIEHPC_ACPI_SUN_PRESENT; if (AcpiGetHandle(slot_dev_obj, "_PS0", &hdl) == AE_OK) slot_methods |= PCIEHPC_ACPI_PS0_PRESENT; if (AcpiGetHandle(slot_dev_obj, "_EJ0", &hdl) == AE_OK) slot_methods |= PCIEHPC_ACPI_EJ0_PRESENT; if (AcpiGetHandle(slot_dev_obj, "_STA", &hdl) == AE_OK) slot_methods |= PCIEHPC_ACPI_STA_PRESENT; /* save ACPI object handles, etc. */ acpi_p = kmem_zalloc(sizeof (pciehpc_acpi_t), KM_SLEEP); acpi_p->bus_obj = pcibus_obj; acpi_p->slot_dev_obj = slot_dev_obj; acpi_p->bus_methods = bus_methods; acpi_p->slot_methods = slot_methods; ctrl_p->hc_misc_data = acpi_p; return (DDI_SUCCESS); } /* * Uninitialize HPC. */ static int pciehpc_acpi_hpc_uninit(pcie_hp_ctrl_t *ctrl_p) { /* free up buffer used for misc_data */ if (ctrl_p->hc_misc_data) { kmem_free(ctrl_p->hc_misc_data, sizeof (pciehpc_acpi_t)); ctrl_p->hc_misc_data = NULL; } return (DDI_SUCCESS); } /* * Enable interrupts. For ACPI hot plug this is a NOP. * Just return DDI_SUCCESS. */ /*ARGSUSED*/ static int pciehpc_acpi_enable_intr(pcie_hp_ctrl_t *ctrl_p) { return (DDI_SUCCESS); } /* * Disable interrupts. For ACPI hot plug this is a NOP. * Just return DDI_SUCCESS. */ /*ARGSUSED*/ static int pciehpc_acpi_disable_intr(pcie_hp_ctrl_t *ctrl_p) { return (DDI_SUCCESS); } /* * This function is similar to pciehpc_slotinfo_init() with some * changes: * - no need for kernel thread to handle ATTN button events * - function ops for connect/disconnect are different * * ASSUMPTION: No conflict in doing reads to HP registers directly. * Otherwise, there are no ACPI interfaces to do LED control or to get * the hot plug capabilities (ATTN button, MRL, etc.). */ static int pciehpc_acpi_slotinfo_init(pcie_hp_ctrl_t *ctrl_p) { uint32_t slot_capabilities; pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); mutex_enter(&ctrl_p->hc_mutex); /* * setup DDI HP framework slot information structure */ slot_p->hs_device_num = 0; slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCIE; slot_p->hs_info.cn_type_str = PCIE_ACPI_HP_TYPE; slot_p->hs_info.cn_child = NULL; slot_p->hs_minor = PCI_MINOR_NUM(ddi_get_instance(ctrl_p->hc_dip), slot_p->hs_device_num); /* read Slot Capabilities Register */ slot_capabilities = pciehpc_reg_get32(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCAP); /* setup slot number/name */ pciehpc_set_slot_name(ctrl_p); /* check if Attn Button present */ ctrl_p->hc_has_attn = (slot_capabilities & PCIE_SLOTCAP_ATTN_BUTTON) ? B_TRUE : B_FALSE; /* check if Manual Retention Latch sensor present */ ctrl_p->hc_has_mrl = (slot_capabilities & PCIE_SLOTCAP_MRL_SENSOR) ? B_TRUE : B_FALSE; /* * PCI-E (draft) version 1.1 defines EMI Lock Present bit * in Slot Capabilities register. Check for it. */ ctrl_p->hc_has_emi_lock = (slot_capabilities & PCIE_SLOTCAP_EMI_LOCK_PRESENT) ? B_TRUE : B_FALSE; pciehpc_led_init(slot_p); /* get current slot state from the hw */ pciehpc_get_slot_state(slot_p); if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED) slot_p->hs_condition = AP_COND_OK; mutex_exit(&ctrl_p->hc_mutex); if (!pciehpc_slot_kstat_init(slot_p)) { (void) pciehpc_acpi_slotinfo_uninit(ctrl_p); return (DDI_FAILURE); } /* setup Notify() handler for hot plug events from ACPI BIOS */ if (pciehpc_acpi_install_event_handler(ctrl_p) != AE_OK) { (void) pciehpc_acpi_slotinfo_uninit(ctrl_p); return (DDI_FAILURE); } PCIE_DBG("ACPI hot plug is enabled for slot #%d\n", slot_p->hs_phy_slot_num); return (DDI_SUCCESS); } /* * This function is similar to pciehcp_slotinfo_uninit() but has ACPI * specific cleanup. */ static int pciehpc_acpi_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p) { pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; /* uninstall Notify() event handler */ pciehpc_acpi_uninstall_event_handler(ctrl_p); if (slot_p->hs_info.cn_name) kmem_free(slot_p->hs_info.cn_name, strlen(slot_p->hs_info.cn_name) + 1); pciehpc_slot_kstat_fini(slot_p); return (DDI_SUCCESS); } /* * This function is same as pciehpc_slot_poweron() except that it * uses ACPI method PS0 to enable power to the slot. If no PS0 method * is present then it returns DDI_FAILURE. */ /*ARGSUSED*/ static int pciehpc_acpi_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result) { pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); uint16_t status, control; ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); /* get the current state of the slot */ pciehpc_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_ENABLED) { /* slot is already in the 'connected' state */ PCIE_DBG("slot %d already connected\n", slot_p->hs_phy_slot_num); *result = slot_p->hs_info.cn_state; return (DDI_SUCCESS); } /* read the Slot Status Register */ status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS); /* make sure the MRL switch is closed if present */ if ((ctrl_p->hc_has_mrl) && (status & PCIE_SLOTSTS_MRL_SENSOR_OPEN)) { /* MRL switch is open */ cmn_err(CE_WARN, "MRL switch is open on slot %d", slot_p->hs_phy_slot_num); goto cleanup; } /* make sure the slot has a device present */ if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { /* slot is empty */ PCIE_DBG("slot %d is empty\n", slot_p->hs_phy_slot_num); goto cleanup; } /* get the current state of Slot Control Register */ control = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL); /* check if the slot's power state is ON */ if (!(control & PCIE_SLOTCTL_PWR_CONTROL)) { /* slot is already powered up */ PCIE_DBG("slot %d already connected\n", slot_p->hs_phy_slot_num); *result = slot_p->hs_info.cn_state; return (DDI_SUCCESS); } /* turn on power to the slot using ACPI method (PS0) */ if (pciehpc_acpi_power_on_slot(ctrl_p) != AE_OK) goto cleanup; *result = slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; return (DDI_SUCCESS); cleanup: return (DDI_FAILURE); } /* * This function is same as pciehpc_slot_poweroff() except that it * uses ACPI method EJ0 to disable power to the slot. If no EJ0 method * is present then it returns DDI_FAILURE. */ /*ARGSUSED*/ static int pciehpc_acpi_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result) { pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl; pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip); uint16_t status; ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex)); /* get the current state of the slot */ pciehpc_get_slot_state(slot_p); /* check if the slot is already in the state less than 'powered' */ if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { /* slot is in the 'disconnected' state */ PCIE_DBG("slot %d already disconnected\n", slot_p->hs_phy_slot_num); *result = slot_p->hs_info.cn_state; return (DDI_SUCCESS); } /* read the Slot Status Register */ status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS); /* make sure the slot has a device present */ if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) { /* slot is empty */ PCIE_DBG("slot %d is empty", slot_p->hs_phy_slot_num); goto cleanup; } /* turn off power to the slot using ACPI method (EJ0) */ if (pciehpc_acpi_power_off_slot(ctrl_p) != AE_OK) goto cleanup; /* get the current state of the slot */ pciehpc_get_slot_state(slot_p); *result = slot_p->hs_info.cn_state; return (DDI_SUCCESS); cleanup: return (DDI_FAILURE); } /* * Install event handler for the hot plug events on the bus node as well * as device function (dev=0,func=0). */ static ACPI_STATUS pciehpc_acpi_install_event_handler(pcie_hp_ctrl_t *ctrl_p) { pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; int status = AE_OK; pciehpc_acpi_t *acpi_p; PCIE_DBG("install event handler for slot %d\n", slot_p->hs_phy_slot_num); acpi_p = ctrl_p->hc_misc_data; if (acpi_p->slot_dev_obj == NULL) return (AE_NOT_FOUND); /* * Install event hanlder for events on the bus object. * (Note: Insert event (hot-insert) is delivered on this object) */ status = AcpiInstallNotifyHandler(acpi_p->slot_dev_obj, ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler, (void *)ctrl_p); if (status != AE_OK) goto cleanup; /* * Install event hanlder for events on the device function object. * (Note: Eject device event (hot-remove) is delivered on this object) * * NOTE: Here the assumption is that Notify events are delivered * on all of the 8 possible device functions so, subscribing to * one of them is sufficient. */ status = AcpiInstallNotifyHandler(acpi_p->bus_obj, ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler, (void *)ctrl_p); return (status); cleanup: (void) AcpiRemoveNotifyHandler(acpi_p->slot_dev_obj, ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler); return (status); } /*ARGSUSED*/ static void pciehpc_acpi_notify_handler(ACPI_HANDLE device, uint32_t val, void *context) { pcie_hp_ctrl_t *ctrl_p = context; pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; pciehpc_acpi_t *acpi_p; ddi_hp_cn_state_t curr_state; int dev_state = 0; PCIE_DBG("received Notify(%d) event on slot #%d\n", val, slot_p->hs_phy_slot_num); mutex_enter(&ctrl_p->hc_mutex); /* * get the state of the device (from _STA method) */ acpi_p = ctrl_p->hc_misc_data; if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj, &dev_state) != AE_OK) { cmn_err(CE_WARN, "failed to get device status on slot %d", slot_p->hs_phy_slot_num); } PCIE_DBG("(1)device state on slot #%d: 0x%x\n", slot_p->hs_phy_slot_num, dev_state); curr_state = slot_p->hs_info.cn_state; pciehpc_get_slot_state(slot_p); switch (val) { case 0: /* (re)enumerate the device */ case 3: /* Request Eject */ { ddi_hp_cn_state_t target_state; /* * Ignore the event if ATTN button is not present (ACPI BIOS * problem). * * NOTE: This situation has been observed on some platforms * where the ACPI BIOS is generating the event for some other * (non hot-plug) operations (bug). */ if (ctrl_p->hc_has_attn == B_FALSE) { PCIE_DBG("Ignore the unexpected event " "on slot #%d (state 0x%x)", slot_p->hs_phy_slot_num, dev_state); break; } /* send the event to DDI Hotplug framework */ if (curr_state < DDI_HP_CN_STATE_POWERED) { /* Insertion. Upgrade state to ENABLED */ target_state = DDI_HP_CN_STATE_ENABLED; /* * When pressing ATTN button to enable a card, the slot * could be powered. Keep the slot state on PWOERED * other than ENABLED. */ if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_ENABLED) slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED; } else { /* Want to remove; Power off Connection */ target_state = DDI_HP_CN_STATE_EMPTY; } (void) ndi_hp_state_change_req(slot_p->hs_ctrl->hc_dip, slot_p->hs_info.cn_name, target_state, DDI_HP_REQ_ASYNC); break; } default: cmn_err(CE_NOTE, "Unknown Notify() event %d on slot #%d\n", val, slot_p->hs_phy_slot_num); break; } mutex_exit(&ctrl_p->hc_mutex); } static void pciehpc_acpi_uninstall_event_handler(pcie_hp_ctrl_t *ctrl_p) { pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data; pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; PCIE_DBG("Uninstall event handler for slot #%d\n", slot_p->hs_phy_slot_num); (void) AcpiRemoveNotifyHandler(acpi_p->slot_dev_obj, ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler); (void) AcpiRemoveNotifyHandler(acpi_p->bus_obj, ACPI_SYSTEM_NOTIFY, pciehpc_acpi_notify_handler); } /* * Run _PS0 method to turn on power to the slot. */ static ACPI_STATUS pciehpc_acpi_power_on_slot(pcie_hp_ctrl_t *ctrl_p) { int status = AE_OK; pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data; int dev_state = 0; pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; PCIE_DBG("turn ON power to the slot #%d\n", slot_p->hs_phy_slot_num); status = AcpiEvaluateObject(acpi_p->slot_dev_obj, "_PS0", NULL, NULL); /* get the state of the device (from _STA method) */ if (status == AE_OK) { if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj, &dev_state) != AE_OK) cmn_err(CE_WARN, "failed to get device status " "on slot #%d", slot_p->hs_phy_slot_num); } PCIE_DBG("(3)device state on slot #%d: 0x%x\n", slot_p->hs_phy_slot_num, dev_state); pciehpc_get_slot_state(slot_p); if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) { cmn_err(CE_WARN, "failed to power on the slot #%d" "(dev_state 0x%x, ACPI_STATUS 0x%x)", slot_p->hs_phy_slot_num, dev_state, status); return (AE_ERROR); } return (status); } /* * Run _EJ0 method to turn off power to the slot. */ static ACPI_STATUS pciehpc_acpi_power_off_slot(pcie_hp_ctrl_t *ctrl_p) { int status = AE_OK; pciehpc_acpi_t *acpi_p = ctrl_p->hc_misc_data; int dev_state = 0; pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0]; PCIE_DBG("turn OFF power to the slot #%d\n", slot_p->hs_phy_slot_num); status = AcpiEvaluateObject(acpi_p->slot_dev_obj, "_EJ0", NULL, NULL); /* get the state of the device (from _STA method) */ if (status == AE_OK) { if (pciehpc_acpi_get_dev_state(acpi_p->slot_dev_obj, &dev_state) != AE_OK) cmn_err(CE_WARN, "failed to get device status " "on slot #%d", slot_p->hs_phy_slot_num); } PCIE_DBG("(2)device state on slot #%d: 0x%x\n", slot_p->hs_phy_slot_num, dev_state); pciehpc_get_slot_state(slot_p); if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) { cmn_err(CE_WARN, "failed to power OFF the slot #%d" "(dev_state 0x%x, ACPI_STATUS 0x%x)", slot_p->hs_phy_slot_num, dev_state, status); return (AE_ERROR); } return (status); } /* * Get the status info (as returned by _STA method) for the device. */ static ACPI_STATUS pciehpc_acpi_get_dev_state(ACPI_HANDLE obj, int *statusp) { int status; ACPI_STATUS ret; ret = acpica_get_object_status(obj, &status); if (ACPI_SUCCESS(ret)) { *statusp = status; } return (ret); }