From: Tomasz Nowicki <tomasz.nowicki at linaro.org> 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 at redhat.com> Signed-off-by: Vadim Lomovtsev <Vadim.Lomovtsev at caviumnetworks.com> --- arch/arm64/kernel/pci-acpi.c | 12 +++++ drivers/acpi/mmconfig.c | 103 ++++++++++++++++++++++++++------------ include/asm-generic/vmlinux.lds.h | 7 +++ include/linux/mmconfig.h | 24 +++++++++ 4 files changed, 115 insertions(+), 31 deletions(-) diff --git a/arch/arm64/kernel/pci-acpi.c b/arch/arm64/kernel/pci-acpi.c index 1826b10..517d570 100644 --- a/arch/arm64/kernel/pci-acpi.c +++ b/arch/arm64/kernel/pci-acpi.c @@ -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; diff --git a/drivers/acpi/mmconfig.c b/drivers/acpi/mmconfig.c index c9c6e05..9c6efa5 100644 --- a/drivers/acpi/mmconfig.c +++ b/drivers/acpi/mmconfig.c @@ -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; diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 8bd374d..dd58f47 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -288,6 +288,13 @@ 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) = .; \ diff --git a/include/linux/mmconfig.h b/include/linux/mmconfig.h index ae8ec83..4360e9a 100644 --- a/include/linux/mmconfig.h +++ b/include/linux/mmconfig.h @@ -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); -- 2.4.3