Expand the support for PCI-e memory mapped configuration space access.
authorAlexander Polakov <polachok@gmail.com>
Sun, 1 Nov 2009 20:16:28 +0000 (23:16 +0300)
committerAlexander Polakov <polachok@gmail.com>
Sun, 8 Nov 2009 17:57:11 +0000 (20:57 +0300)
This defaults to off and must be explicitly
enabled by setting the loader tunable hw.pci.mcfg=1.
- Add support for the Intel 915GM chipsets by reading the BAR.
- Add parsing of the ACPI MCFG table to discover memory mapped configuration
  access on modern machines.
- For config requests to busses not listed in ACPI's min/max valid buses,
  fall back to using type #1 configuration access instead.
- Add a workaround for some K8 chipsets that do not expose all devices on
  bus 0 via MCFG and fall back to type #1 for those devices instead.

Obtained-from: FreeBSD

sys/bus/pci/i386/pci_cfgreg.c
sys/bus/pci/i386/pci_cfgreg.h
sys/dev/acpica5/acpi.c

index 2099553..9eb9da1 100644 (file)
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * $FreeBSD: src/sys/i386/pci/pci_cfgreg.c,v 1.124.2.2.6.1 2009/04/15 03:14:26 kensmith Exp $
+ * $FreeBSD: src/sys/i386/pci/pci_cfgreg.c,v 1.124.2.3 2009/05/04 21:04:29 jhb
  */
 
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/bus.h>
+#include <sys/kernel.h>
 #include <sys/lock.h>
 #include <sys/malloc.h>
 #include <sys/thread2.h>
@@ -76,7 +77,9 @@ enum {
 };
 
 static TAILQ_HEAD(pcie_cfg_list, pcie_cfg_elem) pcie_list[MAXCPU];
-static uint32_t pciebar;
+static uint64_t pcie_base;
+static int pcie_minbus, pcie_maxbus;
+static uint32_t pcie_badslots;
 static int cfgmech;
 static int devmax;
 #if defined(__DragonFly__)
@@ -84,16 +87,21 @@ static struct spinlock pcicfg_mtx;
 #else
 static struct mtx pcicfg_mtx;
 #endif
+static int mcfg_enable = 0;
 
+TUNABLE_INT("hw.pci.mcfg", &mcfg_enable);
+
+static uint32_t        pci_docfgregread(int bus, int slot, int func, int reg, int bytes);
 static int     pcireg_cfgread(int bus, int slot, int func, int reg, int bytes);
 static void    pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes);
 static int     pcireg_cfgopen(void);
 
-static int     pciereg_cfgopen(void);
-static int     pciereg_cfgread(int bus, int slot, int func, int reg,
-                               int bytes);
-static void    pciereg_cfgwrite(int bus, int slot, int func, int reg,
-                                int data, int bytes);
+static int     pciereg_cfgread(int bus, unsigned slot, unsigned func,
+                   unsigned reg, unsigned bytes);
+static void    pciereg_cfgwrite(int bus, unsigned slot, unsigned func,
+                   unsigned reg, int data, unsigned bytes);
+
+
 
 /*
  * Some BIOS writers seem to want to ignore the spec and put
@@ -138,13 +146,14 @@ int
 pci_cfgregopen(void)
 {
        static int              opened = 0;
+       uint64_t                pciebar;
        u_int16_t               vid, did;
        u_int16_t               v;
        if (opened)
-               return(1);
+               return (1);
 
-       if (pcireg_cfgopen() == 0)
-               return(0);
+       if (cfgmech == CFGMECH_NONE && pcireg_cfgopen() == 0)
+               return (0);
 
        v = pcibios_get_version();
        if (v > 0)
@@ -157,6 +166,9 @@ pci_cfgregopen(void)
        if (v >= 0x0210)
                pci_pir_open();
 
+       if (cfgmech == CFGMECH_PCIE)
+               return (1);
+
        /*
         * Grope around in the PCI config space to see if this is a
         * chipset that is capable of doing memory-mapped config cycles.
@@ -166,21 +178,40 @@ pci_cfgregopen(void)
        /* Check for supported chipsets */
        vid = pci_cfgregread(0, 0, 0, PCIR_VENDOR, 2);
        did = pci_cfgregread(0, 0, 0, PCIR_DEVICE, 2);
-       if (vid == 0x8086) {
-               if (did == 0x3590 || did == 0x3592) {
+       switch (vid) {
+       case 0x8086:
+               switch (did) {
+               case 0x3590:
+               case 0x3592:
                        /* Intel 7520 or 7320 */
                        pciebar = pci_cfgregread(0, 0, 0, 0xce, 2) << 16;
-                       pciereg_cfgopen();
-               } else if (did == 0x2580 || did == 0x2584) {
-                       /* Intel 915 or 925 */
+                       pcie_cfgregopen(pciebar, 0, 255);
+                       break;
+               case 0x2580:
+               case 0x2584:
+               case 0x2590:
+                       /* Intel 915, 925, or 915GM */
                        pciebar = pci_cfgregread(0, 0, 0, 0x48, 4);
-                       pciereg_cfgopen();
+                       pcie_cfgregopen(pciebar, 0, 255);
+                       break;
                }
        }
 
        return(1);
 }
 
+static uint32_t
+pci_docfgregread(int bus, int slot, int func, int reg, int bytes)
+{
+
+       if (cfgmech == CFGMECH_PCIE &&
+           (bus >= pcie_minbus && bus <= pcie_maxbus) &&
+           (bus != 0 || !(1 << slot & pcie_badslots)))
+               return (pciereg_cfgread(bus, slot, func, reg, bytes));
+       else
+               return (pcireg_cfgread(bus, slot, func, reg, bytes));
+}
+
 /* 
  * Read configuration space register
  */
@@ -195,10 +226,10 @@ pci_cfgregread(int bus, int slot, int func, int reg, int bytes)
         * the code uses 255 as an invalid IRQ.
         */
        if (reg == PCIR_INTLINE && bytes == 1) {
-               line = pcireg_cfgread(bus, slot, func, PCIR_INTLINE, 1);
+               line = pci_docfgregread(bus, slot, func, PCIR_INTLINE, 1);
                return (pci_i386_map_intline(line));
        }
-       return (pcireg_cfgread(bus, slot, func, reg, bytes));
+       return (pci_docfgregread(bus, slot, func, reg, bytes));
 }
 
 /* 
@@ -208,7 +239,12 @@ void
 pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes)
 {
 
-       pcireg_cfgwrite(bus, slot, func, reg, data, bytes);
+       if (cfgmech == CFGMECH_PCIE &&
+           (bus >= pcie_minbus && bus <= pcie_maxbus) &&
+           (bus != 0 || !(1 << slot & pcie_badslots)))
+               pciereg_cfgwrite(bus, slot, func, reg, data, bytes);
+       else
+               pcireg_cfgwrite(bus, slot, func, reg, data, bytes);
 }
 
 /* 
@@ -262,6 +298,7 @@ pci_cfgenable(unsigned bus, unsigned slot, unsigned func, int reg, int bytes)
            && (unsigned) bytes <= 4
            && (reg & (bytes - 1)) == 0) {
                switch (cfgmech) {
+               case CFGMECH_PCIE:
                case CFGMECH_1:
                        outl(CONF1_ADDR_PORT, (1 << 31)
                            | (bus << 16) | (slot << 11) 
@@ -283,6 +320,7 @@ static void
 pci_cfgdisable(void)
 {
        switch (cfgmech) {
+       case CFGMECH_PCIE:
        case CFGMECH_1:
                /*
                 * Do nothing for the config mechanism 1 case.
@@ -303,11 +341,6 @@ pcireg_cfgread(int bus, int slot, int func, int reg, int bytes)
        int data = -1;
        int port;
 
-       if (cfgmech == CFGMECH_PCIE) {
-               data = pciereg_cfgread(bus, slot, func, reg, bytes);
-               return (data);
-       }
-
        mtx_lock_spin(&pcicfg_mtx);
        port = pci_cfgenable(bus, slot, func, reg, bytes);
        if (port != 0) {
@@ -333,11 +366,6 @@ pcireg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
 {
        int port;
 
-       if (cfgmech == CFGMECH_PCIE) {
-               pciereg_cfgwrite(bus, slot, func, reg, data, bytes);
-               return;
-       }
-
        mtx_lock_spin(&pcicfg_mtx);
        port = pci_cfgenable(bus, slot, func, reg, bytes);
        if (port != 0) {
@@ -485,8 +513,8 @@ pcireg_cfgopen(void)
        return (cfgmech);
 }
 
-static int
-pciereg_cfgopen(void)
+int
+pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus)
 {
 #ifdef PCIE_CFG_MECH
        struct pcie_cfg_list *pcielist;
@@ -495,10 +523,26 @@ pciereg_cfgopen(void)
        struct pcpu *pc;
 #endif
        vm_offset_t va;
-       int i;
+       uint32_t val1, val2;
+       int i, slot;
+
+       if (!mcfg_enable)
+               return (0);
+
+       if (minbus != 0)
+               return (0);
+
+       if (base >= 0x100000000) {
+               if (bootverbose)
+                       kprintf(
+               "PCI: Memory Mapped PCI configuration area base 0x%jx too high\n",
+                       (uintmax_t)base);
+               return (0);
+       }
 
        if (bootverbose)
-               kprintf("Setting up PCIe mappings for BAR 0x%x\n", pciebar);
+               kprintf("PCIe: Memory Mapped configuration base @ 0x%jx\n",
+                       (uintmax_t)base);
 
 #ifdef SMP
        SLIST_FOREACH(pc, &cpuhead, pc_allcpu)
@@ -530,9 +574,30 @@ pciereg_cfgopen(void)
                }
        }
 
-       
+       pcie_base = base;
+       pcie_minbus = minbus;
+       pcie_maxbus = maxbus;
        cfgmech = CFGMECH_PCIE;
        devmax = 32;
+
+       /*
+        * On some AMD systems, some of the devices on bus 0 are
+        * inaccessible using memory-mapped PCI config access.  Walk
+        * bus 0 looking for such devices.  For these devices, we will
+        * fall back to using type 1 config access instead.
+        */
+       if (pci_cfgregopen() != 0) {
+               for (slot = 0; slot < 32; slot++) {
+                       val1 = pcireg_cfgread(0, slot, 0, 0, 4);
+                       if (val1 == 0xffffffff)
+                               continue;
+
+                       val2 = pciereg_cfgread(0, slot, 0, 0, 4);
+                       if (val2 != val1)
+                               pcie_badslots |= (1 << slot);
+               }
+       }
+
        return (1);
 #else  /* !PCIE_CFG_MECH */
        return (0);
@@ -581,15 +646,20 @@ pciereg_findelem(vm_paddr_t papage)
 }
 
 static int
-pciereg_cfgread(int bus, int slot, int func, int reg, int bytes)
+pciereg_cfgread(int bus, unsigned slot, unsigned func, unsigned reg,
+       unsigned bytes)
 {
        struct pcie_cfg_elem *elem;
        volatile vm_offset_t va;
        vm_paddr_t pa, papage;
-       int data;
+       int data = -1;
+
+       if (bus < pcie_minbus || bus > pcie_maxbus || slot >= 32 ||
+           func > PCI_FUNCMAX || reg >= 0x1000 || bytes > 4 || bytes == 3)
+               return (-1);
 
        crit_enter();
-       pa = PCIE_PADDR(pciebar, reg, bus, slot, func);
+       pa = PCIE_PADDR(pcie_base, reg, bus, slot, func);
        papage = pa & ~PAGE_MASK;
        elem = pciereg_findelem(papage);
        va = elem->vapage | (pa & PAGE_MASK);
@@ -604,8 +674,6 @@ pciereg_cfgread(int bus, int slot, int func, int reg, int bytes)
        case 1:
                data = *(volatile uint8_t *)(va);
                break;
-       default:
-               panic("pciereg_cfgread: invalid width");
        }
 
        crit_exit();
@@ -613,14 +681,14 @@ pciereg_cfgread(int bus, int slot, int func, int reg, int bytes)
 }
 
 static void
-pciereg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
+pciereg_cfgwrite(int bus, unsigned slot, unsigned func, unsigned reg, int data, unsigned bytes)
 {
        struct pcie_cfg_elem *elem;
        volatile vm_offset_t va;
        vm_paddr_t pa, papage;
 
        crit_enter();
-       pa = PCIE_PADDR(pciebar, reg, bus, slot, func);
+       pa = PCIE_PADDR(pcie_base, reg, bus, slot, func);
        papage = pa & ~PAGE_MASK;
        elem = pciereg_findelem(papage);
        va = elem->vapage | (pa & PAGE_MASK);
@@ -635,8 +703,6 @@ pciereg_cfgwrite(int bus, int slot, int func, int reg, int data, int bytes)
        case 1:
                *(volatile uint8_t *)(va) = data;
                break;
-       default:
-               panic("pciereg_cfgwrite: invalid width");
        }
 
        crit_exit();
index 87bf5cf..7f58e96 100644 (file)
@@ -43,6 +43,7 @@
 #define CONF2_ENABLE_CHK   0x0e
 #define CONF2_ENABLE_RES   0x0e
 
+int             pcie_cfgregopen(uint64_t base, uint8_t minbus, uint8_t maxbus);
 int            pci_cfgregopen(void);
 u_int32_t      pci_cfgregread(int bus, int slot, int func, int reg, int bytes);
 void           pci_cfgregwrite(int bus, int slot, int func, int reg, u_int32_t data, int bytes);
index fd22ed0..f681ade 100644 (file)
@@ -62,6 +62,7 @@
 #include "acglobal.h"
 
 #include "pci_if.h"
+#include <bus/pci/pci_cfgreg.h>
 #include <bus/pci/pcivar.h>
 #include <bus/pci/pci_private.h>
 
@@ -156,6 +157,7 @@ static int  acpi_child_location_str_method(device_t acdev, device_t child,
                                               char *buf, size_t buflen);
 static int     acpi_child_pnpinfo_str_method(device_t acdev, device_t child,
                                              char *buf, size_t buflen);
+static void    acpi_enable_pcie(void);
 
 static device_method_t acpi_methods[] = {
     /* Device interface */
@@ -468,6 +470,9 @@ acpi_attach(device_t dev)
        goto out;
     }
 
+    /* Handle MCFG table if present. */
+    acpi_enable_pcie();
+
     /* Install the default address space handlers. */
     status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
                ACPI_ADR_SPACE_SYSTEM_MEMORY, ACPI_DEFAULT_HANDLER, NULL, NULL);
@@ -1490,6 +1495,34 @@ acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids)
 }
 
 /*
+ * Look for a MCFG table.  If it is present, use the settings for
+ * domain (segment) 0 to setup PCI config space access via the memory
+ * map.
+ */
+static void
+acpi_enable_pcie(void)
+{
+       ACPI_TABLE_HEADER *hdr;
+       ACPI_MCFG_ALLOCATION *alloc, *end;
+       ACPI_STATUS status;
+
+       status = AcpiGetTable(ACPI_SIG_MCFG, 1, &hdr);
+       if (ACPI_FAILURE(status))
+               return;
+
+       end = (ACPI_MCFG_ALLOCATION *)((char *)hdr + hdr->Length);
+       alloc = (ACPI_MCFG_ALLOCATION *)((ACPI_TABLE_MCFG *)hdr + 1);
+       while (alloc < end) {
+               if (alloc->PciSegment == 0) {
+                       pcie_cfgregopen(alloc->Address, alloc->StartBusNumber,
+                           alloc->EndBusNumber);
+                       return;
+               }
+               alloc++;
+       }
+}
+
+/*
  * Scan all of the ACPI namespace and attach child devices.
  *
  * We should only expect to find devices in the \_PR, \_TZ, \_SI, and