]> xenbits.xensource.com Git - people/julieng/linux-arm.git/commitdiff
arm64/acpi/pci: provide hook for MCFG fixups
authorTomasz Nowicki <tomasz.nowicki@linaro.org>
Fri, 12 Dec 2014 08:41:23 +0000 (09:41 +0100)
committerJulien Grall <julien.grall@citrix.com>
Mon, 28 Sep 2015 11:05:21 +0000 (12:05 +0100)
Some MCFG tables may be broken or the underlying hardware may not
be fully compliant with the PCIe ECAM mechanism. This patch provides
a mechanism to override the default mmconfig read/write routines
and/or do other MCFG related fixups.

Signed-off-by: Mark Salter <msalter@redhat.com>
Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev@caviumnetworks.com>
arch/arm64/kernel/pci-acpi.c
drivers/acpi/mmconfig.c
include/asm-generic/vmlinux.lds.h
include/linux/mmconfig.h

index 1826b10e28f8d88b55657bb423f4835a11c6d908..517d570afb1316def12955b5ee7fcb38f6b64281 100644 (file)
@@ -297,6 +297,7 @@ probe_pci_root_info(struct pci_root_info *info, struct acpi_device *device,
 struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
 {
        struct acpi_device *device = root->device;
+       struct pci_mmcfg_region *mcfg;
        int domain = root->segment;
        int bus = root->secondary.start;
        struct pci_controller *controller;
@@ -305,6 +306,17 @@ struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root)
        struct pci_bus *pbus;
        int ret;
 
+       /* we need mmconfig */
+       mcfg = pci_mmconfig_lookup(domain, busnum);
+       if (!mcfg) {
+               pr_err("pci_bus %04x:%02x has no MCFG table\n",
+                      domain, busnum);
+               return NULL;
+       }
+
+       if (mcfg->fixup)
+               (*mcfg->fixup)(root, mcfg);
+
        controller = alloc_pci_controller(domain);
        if (!controller)
                return NULL;
index c9c6e05284bd6aa2d823660356d891be0e777655..9c6efa570e660fe15d307cd27bda0a505b952faf 100644 (file)
@@ -43,20 +43,36 @@ int __weak raw_pci_write(unsigned int domain, unsigned int bus,
        return pci_mmcfg_write(domain, bus, devfn, reg, len, val);
 }
 
-static char __iomem *pci_dev_base(unsigned int seg, unsigned int bus,
-                                 unsigned int devfn)
+static inline char __iomem *pci_dev_base(struct pci_mmcfg_region *cfg,
+                                        unsigned int bus, unsigned int devfn)
 {
-       struct pci_mmcfg_region *cfg = pci_mmconfig_lookup(seg, bus);
+       return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12));
+}
 
-       if (cfg && cfg->virt)
-               return cfg->virt + (PCI_MMCFG_BUS_OFFSET(bus) | (devfn << 12));
-       return NULL;
+static int __pci_mmcfg_read(struct pci_mmcfg_region *cfg, unsigned int bus,
+                           unsigned int devfn, int reg, int len, u32 *value)
+{
+       char __iomem *addr = pci_dev_base(cfg, bus, devfn);
+
+       switch (len) {
+       case 1:
+               *value = mmio_config_readb(addr + reg);
+               break;
+       case 2:
+               *value = mmio_config_readw(addr + reg);
+               break;
+       case 4:
+               *value = mmio_config_readl(addr + reg);
+               break;
+       }
+       return 0;
 }
 
 int __weak pci_mmcfg_read(unsigned int seg, unsigned int bus,
                          unsigned int devfn, int reg, int len, u32 *value)
 {
-       char __iomem *addr;
+       struct pci_mmcfg_region *cfg;
+       int ret;
 
        /* Why do we have this when nobody checks it. How about a BUG()!? -AK */
        if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095))) {
@@ -65,58 +81,66 @@ err:                *value = -1;
        }
 
        rcu_read_lock();
-       addr = pci_dev_base(seg, bus, devfn);
-       if (!addr) {
+       cfg = pci_mmconfig_lookup(seg, bus);
+       if (!cfg || !cfg->virt) {
                rcu_read_unlock();
                goto err;
        }
 
+       if (cfg->read)
+               ret = (*cfg->read)(cfg, bus, devfn, reg, len, value);
+       else
+               ret = __pci_mmcfg_read(cfg, bus, devfn, reg, len, value);
+
+       rcu_read_unlock();
+
+       return ret;
+}
+
+static int __pci_mmcfg_write(struct pci_mmcfg_region *cfg, unsigned int bus,
+                            unsigned int devfn, int reg, int len, u32 value)
+{
+       char __iomem *addr = pci_dev_base(cfg, bus, devfn);
+
        switch (len) {
        case 1:
-               *value = mmio_config_readb(addr + reg);
+               mmio_config_writeb(addr + reg, value);
                break;
        case 2:
-               *value = mmio_config_readw(addr + reg);
+               mmio_config_writew(addr + reg, value);
                break;
        case 4:
-               *value = mmio_config_readl(addr + reg);
+               mmio_config_writel(addr + reg, value);
                break;
        }
-       rcu_read_unlock();
-
        return 0;
 }
 
 int __weak pci_mmcfg_write(unsigned int seg, unsigned int bus,
                           unsigned int devfn, int reg, int len, u32 value)
 {
-       char __iomem *addr;
+       struct pci_mmcfg_region *cfg;
+       int ret;
 
        /* Why do we have this when nobody checks it. How about a BUG()!? -AK */
        if (unlikely((bus > 255) || (devfn > 255) || (reg > 4095)))
                return -EINVAL;
 
        rcu_read_lock();
-       addr = pci_dev_base(seg, bus, devfn);
-       if (!addr) {
+       cfg = pci_mmconfig_lookup(seg, bus);
+       if (!cfg || !cfg->virt) {
                rcu_read_unlock();
                return -EINVAL;
        }
 
-       switch (len) {
-       case 1:
-               mmio_config_writeb(addr + reg, value);
-               break;
-       case 2:
-               mmio_config_writew(addr + reg, value);
-               break;
-       case 4:
-               mmio_config_writel(addr + reg, value);
-               break;
-       }
+       if (cfg->write)
+               ret = (*cfg->write)(cfg, bus, devfn, reg, len, value);
+       else
+               ret = __pci_mmcfg_write(cfg, bus, devfn, reg, len, value);
+
        rcu_read_unlock();
 
-       return 0;
+       return ret;
 }
 
 static void __iomem *mcfg_ioremap(struct pci_mmcfg_region *cfg)
@@ -307,10 +331,15 @@ int __init __weak acpi_mcfg_check_entry(struct acpi_table_mcfg *mcfg,
        return 0;
 }
 
+extern struct acpi_mcfg_fixup __start_acpi_mcfg_fixups[];
+extern struct acpi_mcfg_fixup __end_acpi_mcfg_fixups[];
+
 int __init pci_parse_mcfg(struct acpi_table_header *header)
 {
        struct acpi_table_mcfg *mcfg;
        struct acpi_mcfg_allocation *cfg_table, *cfg;
+       struct acpi_mcfg_fixup *fixup;
+       struct pci_mmcfg_region *new;
        unsigned long i;
        int entries;
 
@@ -332,6 +361,15 @@ int __init pci_parse_mcfg(struct acpi_table_header *header)
                return -ENODEV;
        }
 
+       fixup = __start_acpi_mcfg_fixups;
+       while (fixup < __end_acpi_mcfg_fixups) {
+               if (!strncmp(fixup->oem_id, header->oem_id, 6) &&
+                   !strncmp(fixup->oem_table_id, header->oem_table_id, 8))
+                       break;
+               ++fixup;
+       }
+
+
        cfg_table = (struct acpi_mcfg_allocation *) &mcfg[1];
        for (i = 0; i < entries; i++) {
                cfg = &cfg_table[i];
@@ -340,12 +378,15 @@ int __init pci_parse_mcfg(struct acpi_table_header *header)
                        return -ENODEV;
                }
 
-               if (pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number,
-                                  cfg->end_bus_number, cfg->address) == NULL) {
+               new = pci_mmconfig_add(cfg->pci_segment, cfg->start_bus_number,
+                                      cfg->end_bus_number, cfg->address);
+               if (!new) {
                        pr_warn(PREFIX "no memory for MCFG entries\n");
                        free_all_mmcfg();
                        return -ENOMEM;
                }
+               if (fixup < __end_acpi_mcfg_fixups)
+                       new->fixup = fixup->hook;
        }
 
        return 0;
index 8bd374d3cf21fb0ee32e217f7f7eb6915c992514..dd58f473692868cdc2d2e2e883fa67fd1c859d3f 100644 (file)
                VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .;      \
        }                                                               \
                                                                        \
+       /* ACPI quirks */                                               \
+       .acpi_fixup        : AT(ADDR(.acpi_fixup) - LOAD_OFFSET) {      \
+               VMLINUX_SYMBOL(__start_acpi_mcfg_fixups) = .;           \
+               *(.acpi_fixup_mcfg)                                     \
+               VMLINUX_SYMBOL(__end_acpi_mcfg_fixups) = .;             \
+       }                                                               \
+                                                                       \
        /* Built-in firmware blobs */                                   \
        .builtin_fw        : AT(ADDR(.builtin_fw) - LOAD_OFFSET) {      \
                VMLINUX_SYMBOL(__start_builtin_fw) = .;                 \
index ae8ec8340549978f97302af2fad8ad57714d415a..4360e9af0c788a4db22bf8625298222451fbdf9e 100644 (file)
@@ -9,9 +9,21 @@
 /* "PCI MMCONFIG %04x [bus %02x-%02x]" */
 #define PCI_MMCFG_RESOURCE_NAME_LEN (22 + 4 + 2 + 2)
 
+struct acpi_pci_root;
+struct pci_mmcfg_region;
+
+typedef int (*acpi_mcfg_fixup_t)(struct acpi_pci_root *root,
+                                struct pci_mmcfg_region *cfg);
+
 struct pci_mmcfg_region {
        struct list_head list;
        struct resource res;
+       int (*read)(struct pci_mmcfg_region *cfg, unsigned int bus,
+                   unsigned int devfn, int reg, int len, u32 *value);
+       int (*write)(struct pci_mmcfg_region *cfg, unsigned int bus,
+                    unsigned int devfn, int reg, int len, u32 value);
+       acpi_mcfg_fixup_t fixup;
+       void *data;
        u64 address;
        char __iomem *virt;
        u16 segment;
@@ -20,6 +32,18 @@ struct pci_mmcfg_region {
        char name[PCI_MMCFG_RESOURCE_NAME_LEN];
 };
 
+struct acpi_mcfg_fixup {
+       char oem_id[7];
+       char oem_table_id[9];
+       acpi_mcfg_fixup_t hook;
+};
+
+/* Designate a routine to fix up buggy MCFG */
+#define DECLARE_ACPI_MCFG_FIXUP(oem_id, table_id, hook)        \
+       static const struct acpi_mcfg_fixup __acpi_fixup_##hook __used  \
+       __attribute__((__section__(".acpi_fixup_mcfg"), aligned((sizeof(void *))))) \
+       = { {oem_id}, {table_id}, hook };
+
 void pci_mmcfg_early_init(void);
 void pci_mmcfg_late_init(void);
 struct pci_mmcfg_region *pci_mmconfig_lookup(int segment, int bus);