/* * 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. */ /* * Library file that has miscellaneous support for npe(7d) */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Prototype declaration */ void npe_query_acpi_mcfg(dev_info_t *dip); void npe_ck804_fix_aer_ptr(ddi_acc_handle_t cfg_hdl); int npe_disable_empty_bridges_workaround(dev_info_t *child); void npe_nvidia_error_workaround(ddi_acc_handle_t cfg_hdl); void npe_intel_error_workaround(ddi_acc_handle_t cfg_hdl); boolean_t npe_is_child_pci(dev_info_t *dip); /* * Default ecfga base address */ int64_t npe_default_ecfga_base = 0xE0000000; extern uint32_t npe_aer_uce_mask; /* * Query the MCFG table using ACPI. If MCFG is found, setup the * 'ecfg' property accordingly. Otherwise, set the values * to the default values. */ void npe_query_acpi_mcfg(dev_info_t *dip) { MCFG_TABLE *mcfgp; CFG_BASE_ADDR_ALLOC *cfg_baap; char *cfg_baa_endp; int64_t ecfginfo[4]; int ecfg_found = 0; /* Query the MCFG table using ACPI */ if (AcpiGetTable(ACPI_SIG_MCFG, 1, (ACPI_TABLE_HEADER **)&mcfgp) == AE_OK) { cfg_baap = (CFG_BASE_ADDR_ALLOC *)mcfgp->CfgBaseAddrAllocList; cfg_baa_endp = ((char *)mcfgp) + mcfgp->Length; while ((char *)cfg_baap < cfg_baa_endp) { if (cfg_baap->base_addr != (uint64_t)0 && cfg_baap->segment == 0) { /* * Set up the 'ecfg' property to hold * base_addr, segment, and first/last bus. * We only do the first entry that maps * segment 0; nonzero segments are not yet * known, or handled. If they appear, * we'll need to figure out which bus node * should have which entry by examining the * ACPI _SEG method on each bus node. */ ecfginfo[0] = cfg_baap->base_addr; ecfginfo[1] = cfg_baap->segment; ecfginfo[2] = cfg_baap->start_bno; ecfginfo[3] = cfg_baap->end_bno; (void) ndi_prop_update_int64_array( DDI_DEV_T_NONE, dip, "ecfg", ecfginfo, 4); ecfg_found = 1; break; } cfg_baap++; } } if (ecfg_found) return; /* * If MCFG is not found or ecfga_base is not found in MCFG table, * set the property to the default values. */ ecfginfo[0] = npe_default_ecfga_base; ecfginfo[1] = 0; /* segment 0 */ ecfginfo[2] = 0; /* first bus 0 */ ecfginfo[3] = 0xff; /* last bus ff */ (void) ndi_prop_update_int64_array(DDI_DEV_T_NONE, dip, "ecfg", ecfginfo, 4); } /* * Enable reporting of AER capability next pointer. * This needs to be done only for CK8-04 devices * by setting NV_XVR_VEND_CYA1 (offset 0xf40) bit 13 * NOTE: BIOS is disabling this, it needs to be enabled temporarily */ void npe_ck804_fix_aer_ptr(ddi_acc_handle_t cfg_hdl) { ushort_t cya1; if ((pci_config_get16(cfg_hdl, PCI_CONF_VENID) == NVIDIA_VENDOR_ID) && (pci_config_get16(cfg_hdl, PCI_CONF_DEVID) == NVIDIA_CK804_DEVICE_ID) && (pci_config_get8(cfg_hdl, PCI_CONF_REVID) >= NVIDIA_CK804_AER_VALID_REVID)) { cya1 = pci_config_get16(cfg_hdl, NVIDIA_CK804_VEND_CYA1_OFF); if (!(cya1 & ~NVIDIA_CK804_VEND_CYA1_ERPT_MASK)) (void) pci_config_put16(cfg_hdl, NVIDIA_CK804_VEND_CYA1_OFF, cya1 | NVIDIA_CK804_VEND_CYA1_ERPT_VAL); } } /* * If the bridge is empty, disable it */ int npe_disable_empty_bridges_workaround(dev_info_t *child) { /* * Do not bind drivers to empty bridges. * Fail above, if the bridge is found to be hotplug capable */ if (ddi_driver_major(child) == ddi_name_to_major("pcieb") && ddi_get_child(child) == NULL && ddi_prop_get_int(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS, "pci-hotplug-type", INBAND_HPC_NONE) == INBAND_HPC_NONE) return (1); return (0); } void npe_nvidia_error_workaround(ddi_acc_handle_t cfg_hdl) { uint32_t regs; uint16_t vendor_id = pci_config_get16(cfg_hdl, PCI_CONF_VENID); uint16_t dev_id = pci_config_get16(cfg_hdl, PCI_CONF_DEVID); if ((vendor_id == NVIDIA_VENDOR_ID) && NVIDIA_PCIE_RC_DEV_ID(dev_id)) { /* Disable ECRC for all devices */ regs = pcie_get_aer_uce_mask() | npe_aer_uce_mask | PCIE_AER_UCE_ECRC; pcie_set_aer_uce_mask(regs); /* * Turn full scan on since the Error Source ID register may not * have the correct ID. */ pcie_force_fullscan(); } } void npe_intel_error_workaround(ddi_acc_handle_t cfg_hdl) { uint32_t regs; uint16_t vendor_id = pci_config_get16(cfg_hdl, PCI_CONF_VENID); uint16_t dev_id = pci_config_get16(cfg_hdl, PCI_CONF_DEVID); if (vendor_id == INTEL_VENDOR_ID) { /* * Due to an errata in Intel's ESB2 southbridge, all ECRCs * generation/checking need to be disabled. There is a * workaround by setting a proprietary bit in the ESB2, but it * is not well documented or understood. If that bit is set in * the future, then ECRC generation/checking should be enabled * again. * * Disable ECRC generation/checking by masking ECRC in the AER * UE Mask. The pcie misc module would then automatically * disable ECRC generation/checking in the AER Control register. */ regs = pcie_get_aer_uce_mask() | PCIE_AER_UCE_ECRC; pcie_set_aer_uce_mask(regs); if (INTEL_NB5500_PCIE_DEV_ID(dev_id) || INTEL_NB5520_PCIE_DEV_ID(dev_id)) { /* * Turn full scan on since the Error Source ID register * may not have the correct ID. See Intel 5520 and * Intel 5500 Chipsets errata #34 and #54 in the August * 2009 specification update, document number * 321329-006. */ pcie_force_fullscan(); } } } /* * Check's if this child is a PCI device. * Child is a PCI device if: * parent has a dev_type of "pci" * -and- * child does not have a dev_type of "pciex" * * If the parent is not of dev_type "pci", then assume it is "pciex" and all * children should support using PCIe style MMCFG access. * * If parent's dev_type is "pci" and child is "pciex", then also enable using * PCIe style MMCFG access. This covers the case where NPE is "pci" and a PCIe * RP is beneath. */ boolean_t npe_child_is_pci(dev_info_t *dip) { char *dev_type; boolean_t parent_is_pci, child_is_pciex; if (ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_get_parent(dip), DDI_PROP_DONTPASS, "device_type", &dev_type) == DDI_PROP_SUCCESS) { parent_is_pci = (strcmp(dev_type, "pci") == 0); ddi_prop_free(dev_type); } else { parent_is_pci = B_FALSE; } if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "device_type", &dev_type) == DDI_PROP_SUCCESS) { child_is_pciex = (strcmp(dev_type, "pciex") == 0); ddi_prop_free(dev_type); } else { child_is_pciex = B_FALSE; } return (parent_is_pci && !child_is_pciex); } /* * Checks to see if MMCFG is supported. * Returns: TRUE if MMCFG is supported, FALSE if not. * * If a device is attached to a parent whose "dev_type" is "pciex", * the device will support MMCFG access. Otherwise, use legacy IOCFG access. * * Enable Legacy PCI config space access for AMD K8 north bridges. * Host bridge: AMD HyperTransport Technology Configuration * Host bridge: AMD Address Map * Host bridge: AMD DRAM Controller * Host bridge: AMD Miscellaneous Control * These devices do not support MMCFG access. */ boolean_t npe_is_mmcfg_supported(dev_info_t *dip) { int vendor_id, device_id; vendor_id = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "vendor-id", -1); device_id = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "device-id", -1); return !(npe_child_is_pci(dip) || IS_BAD_AMD_NTBRIDGE(vendor_id, device_id)); } int npe_enable_htmsi(ddi_acc_handle_t cfg_hdl) { uint16_t ptr; uint16_t reg; if (pci_htcap_locate(cfg_hdl, PCI_HTCAP_TYPE_MASK, PCI_HTCAP_MSIMAP_TYPE, &ptr) != DDI_SUCCESS) return (DDI_FAILURE); reg = pci_config_get16(cfg_hdl, ptr + PCI_CAP_ID_REGS_OFF); reg |= PCI_HTCAP_MSIMAP_ENABLE; pci_config_put16(cfg_hdl, ptr + PCI_CAP_ID_REGS_OFF, reg); return (DDI_SUCCESS); } void npe_enable_htmsi_children(dev_info_t *dip) { dev_info_t *cdip = ddi_get_child(dip); ddi_acc_handle_t cfg_hdl; for (; cdip != NULL; cdip = ddi_get_next_sibling(cdip)) { if (pci_config_setup(cdip, &cfg_hdl) != DDI_SUCCESS) { cmn_err(CE_NOTE, "!npe_enable_htmsi_children: " "pci_config_setup failed for %s", ddi_node_name(cdip)); } (void) npe_enable_htmsi(cfg_hdl); pci_config_teardown(&cfg_hdl); } } /* * save config regs for HyperTransport devices without drivers of classes: * memory controller and hostbridge */ int npe_save_htconfig_children(dev_info_t *dip) { dev_info_t *cdip = ddi_get_child(dip); ddi_acc_handle_t cfg_hdl; uint16_t ptr; int rval = DDI_SUCCESS; uint8_t cl, scl; for (; cdip != NULL; cdip = ddi_get_next_sibling(cdip)) { if (ddi_driver_major(cdip) != DDI_MAJOR_T_NONE) continue; if (pci_config_setup(cdip, &cfg_hdl) != DDI_SUCCESS) return (DDI_FAILURE); cl = pci_config_get8(cfg_hdl, PCI_CONF_BASCLASS); scl = pci_config_get8(cfg_hdl, PCI_CONF_SUBCLASS); if (((cl == PCI_CLASS_MEM && scl == PCI_MEM_RAM) || (cl == PCI_CLASS_BRIDGE && scl == PCI_BRIDGE_HOST)) && pci_htcap_locate(cfg_hdl, 0, 0, &ptr) == DDI_SUCCESS) { if (pci_save_config_regs(cdip) != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to save HT config " "regs for %s\n", ddi_node_name(cdip)); rval = DDI_FAILURE; } else if (ddi_prop_update_int(DDI_DEV_T_NONE, cdip, "htconfig-saved", 1) != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to set htconfig-saved " "property for %s\n", ddi_node_name(cdip)); rval = DDI_FAILURE; } } pci_config_teardown(&cfg_hdl); } return (rval); } int npe_restore_htconfig_children(dev_info_t *dip) { dev_info_t *cdip = ddi_get_child(dip); int rval = DDI_SUCCESS; for (; cdip != NULL; cdip = ddi_get_next_sibling(cdip)) { if (ddi_prop_get_int(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, "htconfig-saved", 0) == 0) continue; if (pci_restore_config_regs(cdip) != DDI_SUCCESS) { cmn_err(CE_WARN, "Failed to restore HT config " "regs for %s\n", ddi_node_name(cdip)); rval = DDI_FAILURE; } } return (rval); }