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))) {
}
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)
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;
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];
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;
/* "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;
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);