[Arm-dev] [PATCH v1 76/87] arm64/acpi/pci: provide hook for MCFG fixups

Thu Aug 13 13:19:13 UTC 2015
Vadim Lomovtsev <Vadim.Lomovtsev at caviumnetworks.com>

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