[Arm-dev] [PATCH v1 76/87] arm64/acpi/pci: provide hook for MCFG fixups
Vadim Lomovtsev
Vadim.Lomovtsev at caviumnetworks.com
Thu Aug 13 13:19:13 UTC 2015
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
More information about the Arm-dev
mailing list