x86/PCI: Prevent mmconfig memory corruption
[safe/jmp/linux-2.6] / arch / x86 / pci / mmconfig-shared.c
index 6eeeac0..8f3f9a5 100644 (file)
@@ -16,7 +16,6 @@
 #include <linux/sfi_acpi.h>
 #include <linux/bitmap.h>
 #include <linux/dmi.h>
-#include <linux/sort.h>
 #include <asm/e820.h>
 #include <asm/pci_x86.h>
 #include <asm/acpi.h>
 /* Indicate if the mmcfg resources have been placed into the resource table. */
 static int __initdata pci_mmcfg_resources_inserted;
 
+LIST_HEAD(pci_mmcfg_list);
+
+static __init void pci_mmconfig_remove(struct pci_mmcfg_region *cfg)
+{
+       if (cfg->res.parent)
+               release_resource(&cfg->res);
+       list_del(&cfg->list);
+       kfree(cfg);
+}
+
 static __init void free_all_mmcfg(void)
 {
-       int i;
-       struct pci_mmcfg_region *cfg;
+       struct pci_mmcfg_region *cfg, *tmp;
 
        pci_mmcfg_arch_free();
-       for (i = 0; i < pci_mmcfg_config_num; i++) {
-               cfg = &pci_mmcfg_config[i];
-               if (cfg->res.parent)
-                       release_resource(&cfg->res);
+       list_for_each_entry_safe(cfg, tmp, &pci_mmcfg_list, list)
+               pci_mmconfig_remove(cfg);
+}
+
+static __init void list_add_sorted(struct pci_mmcfg_region *new)
+{
+       struct pci_mmcfg_region *cfg;
+
+       /* keep list sorted by segment and starting bus number */
+       list_for_each_entry(cfg, &pci_mmcfg_list, list) {
+               if (cfg->segment > new->segment ||
+                   (cfg->segment == new->segment &&
+                    cfg->start_bus >= new->start_bus)) {
+                       list_add_tail(&new->list, &cfg->list);
+                       return;
+               }
        }
-       pci_mmcfg_config_num = 0;
-       kfree(pci_mmcfg_config);
-       pci_mmcfg_config = NULL;
+       list_add_tail(&new->list, &pci_mmcfg_list);
 }
 
 static __init struct pci_mmcfg_region *pci_mmconfig_add(int segment, int start,
                                                        int end, u64 addr)
 {
        struct pci_mmcfg_region *new;
-       int new_num = pci_mmcfg_config_num + 1;
-       int i = pci_mmcfg_config_num;
        int num_buses;
        struct resource *res;
 
        if (addr == 0)
                return NULL;
 
-       new = kzalloc(sizeof(pci_mmcfg_config[0]) * new_num, GFP_KERNEL);
+       new = kzalloc(sizeof(*new), GFP_KERNEL);
        if (!new)
                return NULL;
 
-       if (pci_mmcfg_config) {
-               memcpy(new, pci_mmcfg_config,
-                        sizeof(pci_mmcfg_config[0]) * new_num);
-               kfree(pci_mmcfg_config);
-       }
-       pci_mmcfg_config = new;
-       pci_mmcfg_config_num++;
-
-       new = &pci_mmcfg_config[i];
-
        new->address = addr;
        new->segment = segment;
        new->start_bus = start;
        new->end_bus = end;
 
+       list_add_sorted(new);
+
        num_buses = end - start + 1;
        res = &new->res;
        res->start = addr + PCI_MMCFG_BUS_OFFSET(start);
@@ -82,7 +90,23 @@ static __init struct pci_mmcfg_region *pci_mmconfig_add(int segment, int start,
                 "PCI MMCONFIG %04x [bus %02x-%02x]", segment, start, end);
        res->name = new->name;
 
-       return &pci_mmcfg_config[i];
+       printk(KERN_INFO PREFIX "MMCONFIG for domain %04x [bus %02x-%02x] at "
+              "%pR (base %#lx)\n", segment, start, end, &new->res,
+              (unsigned long) addr);
+
+       return new;
+}
+
+struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus)
+{
+       struct pci_mmcfg_region *cfg;
+
+       list_for_each_entry(cfg, &pci_mmcfg_list, list)
+               if (cfg->segment == segment &&
+                   cfg->start_bus <= bus && bus <= cfg->end_bus)
+                       return cfg;
+
+       return NULL;
 }
 
 static const char __init *pci_mmcfg_e7520(void)
@@ -214,7 +238,7 @@ static const char __init *pci_mmcfg_nvidia_mcp55(void)
        /*
         * do check if amd fam10h already took over
         */
-       if (!acpi_disabled || pci_mmcfg_config_num || mcp55_checked)
+       if (!acpi_disabled || !list_empty(&pci_mmcfg_list) || mcp55_checked)
                return NULL;
 
        mcp55_checked = true;
@@ -275,43 +299,20 @@ static struct pci_mmcfg_hostbridge_probe pci_mmcfg_probes[] __initdata = {
          0x0369, pci_mmcfg_nvidia_mcp55 },
 };
 
-static int __init cmp_mmcfg(const void *x1, const void *x2)
-{
-       const struct pci_mmcfg_region *m1 = x1;
-       const struct pci_mmcfg_region *m2 = x2;
-       int start1, start2;
-
-       start1 = m1->start_bus;
-       start2 = m2->start_bus;
-
-       return start1 - start2;
-}
-
 static void __init pci_mmcfg_check_end_bus_number(void)
 {
-       int i;
        struct pci_mmcfg_region *cfg, *cfgx;
 
-       /* sort them at first */
-       sort(pci_mmcfg_config, pci_mmcfg_config_num,
-                sizeof(pci_mmcfg_config[0]), cmp_mmcfg, NULL);
-
-       /* last one*/
-       if (pci_mmcfg_config_num > 0) {
-               i = pci_mmcfg_config_num - 1;
-               cfg = &pci_mmcfg_config[i];
+       /* Fixup overlaps */
+       list_for_each_entry(cfg, &pci_mmcfg_list, list) {
                if (cfg->end_bus < cfg->start_bus)
                        cfg->end_bus = 255;
-       }
-
-       /* don't overlap please */
-       for (i = 0; i < pci_mmcfg_config_num - 1; i++) {
-               cfg = &pci_mmcfg_config[i];
-               cfgx = &pci_mmcfg_config[i+1];
 
-               if (cfg->end_bus < cfg->start_bus)
-                       cfg->end_bus = 255;
+               /* Don't access the list head ! */
+               if (cfg->list.next == &pci_mmcfg_list)
+                       break;
 
+               cfgx = list_entry(cfg->list.next, typeof(*cfg), list);
                if (cfg->end_bus >= cfgx->start_bus)
                        cfg->end_bus = cfgx->start_bus - 1;
        }
@@ -343,25 +344,22 @@ static int __init pci_mmcfg_check_hostbridge(void)
                        name = pci_mmcfg_probes[i].probe();
 
                if (name)
-                       printk(KERN_INFO "PCI: Found %s with MMCONFIG support.\n",
+                       printk(KERN_INFO PREFIX "%s with MMCONFIG support\n",
                               name);
        }
 
        /* some end_bus_number is crazy, fix it */
        pci_mmcfg_check_end_bus_number();
 
-       return pci_mmcfg_config_num != 0;
+       return !list_empty(&pci_mmcfg_list);
 }
 
 static void __init pci_mmcfg_insert_resources(void)
 {
-       int i;
        struct pci_mmcfg_region *cfg;
 
-       for (i = 0; i < pci_mmcfg_config_num; i++) {
-               cfg = &pci_mmcfg_config[i];
+       list_for_each_entry(cfg, &pci_mmcfg_list, list)
                insert_resource(&iomem_resource, &cfg->res);
-       }
 
        /* Mark that the resources have been inserted. */
        pci_mmcfg_resources_inserted = 1;
@@ -438,7 +436,7 @@ static int __init is_acpi_reserved(u64 start, u64 end, unsigned not_used)
 typedef int (*check_reserved_t)(u64 start, u64 end, unsigned type);
 
 static int __init is_mmconf_reserved(check_reserved_t is_reserved,
-               int i, struct pci_mmcfg_region *cfg, int with_e820)
+                                   struct pci_mmcfg_region *cfg, int with_e820)
 {
        u64 addr = cfg->res.start;
        u64 size = resource_size(&cfg->res);
@@ -452,9 +450,9 @@ static int __init is_mmconf_reserved(check_reserved_t is_reserved,
        }
 
        if (size >= (16UL<<20) || size == old_size) {
-               printk(KERN_NOTICE
-                      "PCI: MCFG area at %Lx reserved in %s\n",
-                       addr, with_e820?"E820":"ACPI motherboard resources");
+               printk(KERN_INFO PREFIX "MMCONFIG at %pR reserved in %s\n",
+                      &cfg->res,
+                      with_e820 ? "E820" : "ACPI motherboard resources");
                valid = 1;
 
                if (old_size != size) {
@@ -466,11 +464,11 @@ static int __init is_mmconf_reserved(check_reserved_t is_reserved,
                        snprintf(cfg->name, PCI_MMCFG_RESOURCE_NAME_LEN,
                                 "PCI MMCONFIG %04x [bus %02x-%02x]",
                                 cfg->segment, cfg->start_bus, cfg->end_bus);
-                       printk(KERN_NOTICE "PCI: updated MCFG configuration %d: base %lx "
-                              "segment %hu buses %u - %u\n",
-                              i, (unsigned long)cfg->address, cfg->segment,
-                              (unsigned int)cfg->start_bus,
-                              (unsigned int)cfg->end_bus);
+                       printk(KERN_INFO PREFIX
+                              "MMCONFIG for %04x [bus%02x-%02x] "
+                              "at %pR (base %#lx) (size reduced!)\n",
+                              cfg->segment, cfg->start_bus, cfg->end_bus,
+                              &cfg->res, (unsigned long) cfg->address);
                }
        }
 
@@ -480,36 +478,25 @@ static int __init is_mmconf_reserved(check_reserved_t is_reserved,
 static void __init pci_mmcfg_reject_broken(int early)
 {
        struct pci_mmcfg_region *cfg;
-       int i;
-
-       if (pci_mmcfg_config_num == 0)
-               return;
 
-       for (i = 0; i < pci_mmcfg_config_num; i++) {
+       list_for_each_entry(cfg, &pci_mmcfg_list, list) {
                int valid = 0;
 
-               cfg = &pci_mmcfg_config[i];
-               printk(KERN_NOTICE "PCI: MCFG configuration %d: base %lx "
-                      "segment %hu buses %u - %u\n",
-                      i, (unsigned long)cfg->address, cfg->segment,
-                      (unsigned int)cfg->start_bus,
-                      (unsigned int)cfg->end_bus);
-
                if (!early && !acpi_disabled)
-                       valid = is_mmconf_reserved(is_acpi_reserved, i, cfg, 0);
+                       valid = is_mmconf_reserved(is_acpi_reserved, cfg, 0);
 
                if (valid)
                        continue;
 
                if (!early)
-                       printk(KERN_ERR "PCI: BIOS Bug: MCFG area at %Lx is not"
-                              " reserved in ACPI motherboard resources\n",
-                              cfg->address);
+                       printk(KERN_ERR FW_BUG PREFIX
+                              "MMCONFIG at %pR not reserved in "
+                              "ACPI motherboard resources\n", &cfg->res);
 
                /* Don't try to do this check unless configuration
                   type 1 is available. how about type 2 ?*/
                if (raw_pci_ops)
-                       valid = is_mmconf_reserved(e820_all_mapped, i, cfg, 1);
+                       valid = is_mmconf_reserved(e820_all_mapped, cfg, 1);
 
                if (!valid)
                        goto reject;
@@ -518,16 +505,12 @@ static void __init pci_mmcfg_reject_broken(int early)
        return;
 
 reject:
-       printk(KERN_INFO "PCI: Not using MMCONFIG.\n");
+       printk(KERN_INFO PREFIX "not using MMCONFIG\n");
        free_all_mmcfg();
 }
 
 static int __initdata known_bridge;
 
-/* The physical address of the MMCONFIG aperture.  Set from ACPI tables. */
-struct pci_mmcfg_region *pci_mmcfg_config;
-int pci_mmcfg_config_num;
-
 static int __init acpi_mcfg_check_entry(struct acpi_table_mcfg *mcfg,
                                        struct acpi_mcfg_allocation *cfg)
 {
@@ -545,7 +528,7 @@ static int __init acpi_mcfg_check_entry(struct acpi_table_mcfg *mcfg,
                        return 0;
        }
 
-       printk(KERN_ERR PREFIX "MCFG region for %04x:%02x-%02x at %#llx "
+       printk(KERN_ERR PREFIX "MCFG region for %04x [bus %02x-%02x] at %#llx "
               "is above 4GB, ignored\n", cfg->pci_segment,
               cfg->start_bus_number, cfg->end_bus_number, cfg->address);
        return -EINVAL;
@@ -620,7 +603,7 @@ static void __init __pci_mmcfg_init(int early)
 
        pci_mmcfg_reject_broken(early);
 
-       if (pci_mmcfg_config_num == 0)
+       if (list_empty(&pci_mmcfg_list))
                return;
 
        if (pci_mmcfg_arch_init())
@@ -652,7 +635,7 @@ static int __init pci_mmcfg_late_insert_resources(void)
         */
        if ((pci_mmcfg_resources_inserted == 1) ||
            (pci_probe & PCI_PROBE_MMCONF) == 0 ||
-           (pci_mmcfg_config_num == 0))
+           list_empty(&pci_mmcfg_list))
                return 1;
 
        /*