root/usr/src/uts/i86pc/io/pciex/npe_misc.c
/*
 * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2015 Joyent, Inc.
 */

/*
 *      Library file that has miscellaneous support for npe(4D)
 */

#include <sys/conf.h>
#include <sys/pci.h>
#include <sys/sunndi.h>
#include <sys/pci_cap.h>
#include <sys/pcie_impl.h>
#include <sys/x86_archext.h>
#include <io/pciex/pcie_nvidia.h>
#include <io/pciex/pcie_nb5000.h>
#include <sys/pci_cfgacc_x86.h>
#include <sys/cpuvar.h>

/*
 * Prototype declaration
 */
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);
int     npe_enable_htmsi(ddi_acc_handle_t cfg_hdl);
void    npe_enable_htmsi_children(dev_info_t *dip);

int     npe_enable_htmsi_flag = 1;

extern uint32_t npe_aer_uce_mask;

/*
 * 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)
{
        pcie_bus_t *bus_p = PCIE_DIP2BUS(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 && bus_p->bus_hp_sup_modes ==
            PCIE_NONE_HP_MODE) {
                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;

        if (!npe_enable_htmsi_flag)
                return;

        /*
         * Hypertransport MSI remapping only applies to AMD CPUs using
         * Hypertransport (K8 and above) and not other platforms with non-AMD
         * CPUs that may be using Hypertransport internally in the chipset(s)
         */
        if (!(cpuid_getvendor(CPU) == X86_VENDOR_AMD &&
            cpuid_getfamily(CPU) >= 0xf))
                return;

        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));
                        return;
                }

                (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);
}