diff options
author | Bjorn Helgaas <bhelgaas@google.com> | 2017-11-14 12:11:25 -0600 |
---|---|---|
committer | Bjorn Helgaas <bhelgaas@google.com> | 2017-11-14 12:11:25 -0600 |
commit | 104d1e40cfcd69934f3f57c6abf13980eb703feb (patch) | |
tree | b57f657381267187610c1dbc2606de2142ceca7d | |
parent | 8dceeaf8fff35a0b70a8cf3ca66883ac4bf4c9dd (diff) | |
parent | a405f191f42ead45a03c000110a16683d30f7333 (diff) | |
download | linux-stable-104d1e40cfcd69934f3f57c6abf13980eb703feb.tar.gz linux-stable-104d1e40cfcd69934f3f57c6abf13980eb703feb.tar.bz2 linux-stable-104d1e40cfcd69934f3f57c6abf13980eb703feb.zip |
Merge branch 'pci/resource' into next
* pci/resource:
PCI: Fail pci_map_rom() if the option ROM is invalid
PCI: Move pci_map_rom() error path
x86/PCI: Enable a 64bit BAR on AMD Family 15h (Models 00-1f, 30-3f, 60-7f)
PCI: Add pci_resize_resource() for resizing BARs
PCI: Add resizable BAR infrastructure
PCI: Add PCI resource type mask #define
-rw-r--r-- | arch/x86/pci/fixup.c | 85 | ||||
-rw-r--r-- | drivers/pci/pci.c | 101 | ||||
-rw-r--r-- | drivers/pci/pci.h | 8 | ||||
-rw-r--r-- | drivers/pci/rom.c | 19 | ||||
-rw-r--r-- | drivers/pci/setup-bus.c | 115 | ||||
-rw-r--r-- | drivers/pci/setup-res.c | 58 | ||||
-rw-r--r-- | include/linux/pci.h | 3 | ||||
-rw-r--r-- | include/uapi/linux/pci_regs.h | 8 |
8 files changed, 380 insertions, 17 deletions
diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index f2228b150faa..e59378bf37d9 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -635,3 +635,88 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2030, quirk_no_aersid); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2031, quirk_no_aersid); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2032, quirk_no_aersid); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2033, quirk_no_aersid); + +#ifdef CONFIG_PHYS_ADDR_T_64BIT + +#define AMD_141b_MMIO_BASE(x) (0x80 + (x) * 0x8) +#define AMD_141b_MMIO_BASE_RE_MASK BIT(0) +#define AMD_141b_MMIO_BASE_WE_MASK BIT(1) +#define AMD_141b_MMIO_BASE_MMIOBASE_MASK GENMASK(31,8) + +#define AMD_141b_MMIO_LIMIT(x) (0x84 + (x) * 0x8) +#define AMD_141b_MMIO_LIMIT_MMIOLIMIT_MASK GENMASK(31,8) + +#define AMD_141b_MMIO_HIGH(x) (0x180 + (x) * 0x4) +#define AMD_141b_MMIO_HIGH_MMIOBASE_MASK GENMASK(7,0) +#define AMD_141b_MMIO_HIGH_MMIOLIMIT_SHIFT 16 +#define AMD_141b_MMIO_HIGH_MMIOLIMIT_MASK GENMASK(23,16) + +/* + * The PCI Firmware Spec, rev 3.2, notes that ACPI should optionally allow + * configuring host bridge windows using the _PRS and _SRS methods. + * + * But this is rarely implemented, so we manually enable a large 64bit BAR for + * PCIe device on AMD Family 15h (Models 00h-1fh, 30h-3fh, 60h-7fh) Processors + * here. + */ +static void pci_amd_enable_64bit_bar(struct pci_dev *dev) +{ + unsigned i; + u32 base, limit, high; + struct resource *res, *conflict; + + for (i = 0; i < 8; i++) { + pci_read_config_dword(dev, AMD_141b_MMIO_BASE(i), &base); + pci_read_config_dword(dev, AMD_141b_MMIO_HIGH(i), &high); + + /* Is this slot free? */ + if (!(base & (AMD_141b_MMIO_BASE_RE_MASK | + AMD_141b_MMIO_BASE_WE_MASK))) + break; + + base >>= 8; + base |= high << 24; + + /* Abort if a slot already configures a 64bit BAR. */ + if (base > 0x10000) + return; + } + if (i == 8) + return; + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (!res) + return; + + res->name = "PCI Bus 0000:00"; + res->flags = IORESOURCE_PREFETCH | IORESOURCE_MEM | + IORESOURCE_MEM_64 | IORESOURCE_WINDOW; + res->start = 0x100000000ull; + res->end = 0xfd00000000ull - 1; + + /* Just grab the free area behind system memory for this */ + while ((conflict = request_resource_conflict(&iomem_resource, res))) + res->start = conflict->end + 1; + + dev_info(&dev->dev, "adding root bus resource %pR\n", res); + + base = ((res->start >> 8) & AMD_141b_MMIO_BASE_MMIOBASE_MASK) | + AMD_141b_MMIO_BASE_RE_MASK | AMD_141b_MMIO_BASE_WE_MASK; + limit = ((res->end + 1) >> 8) & AMD_141b_MMIO_LIMIT_MMIOLIMIT_MASK; + high = ((res->start >> 40) & AMD_141b_MMIO_HIGH_MMIOBASE_MASK) | + ((((res->end + 1) >> 40) << AMD_141b_MMIO_HIGH_MMIOLIMIT_SHIFT) + & AMD_141b_MMIO_HIGH_MMIOLIMIT_MASK); + + pci_write_config_dword(dev, AMD_141b_MMIO_HIGH(i), high); + pci_write_config_dword(dev, AMD_141b_MMIO_LIMIT(i), limit); + pci_write_config_dword(dev, AMD_141b_MMIO_BASE(i), base); + + pci_bus_add_resource(dev->bus, res, 0); +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x1401, pci_amd_enable_64bit_bar); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x141b, pci_amd_enable_64bit_bar); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x1571, pci_amd_enable_64bit_bar); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x15b1, pci_amd_enable_64bit_bar); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x1601, pci_amd_enable_64bit_bar); + +#endif diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d50c58d6acd3..c5172b01164d 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2966,6 +2966,107 @@ bool pci_acs_path_enabled(struct pci_dev *start, } /** + * pci_rebar_find_pos - find position of resize ctrl reg for BAR + * @pdev: PCI device + * @bar: BAR to find + * + * Helper to find the position of the ctrl register for a BAR. + * Returns -ENOTSUPP if resizable BARs are not supported at all. + * Returns -ENOENT if no ctrl register for the BAR could be found. + */ +static int pci_rebar_find_pos(struct pci_dev *pdev, int bar) +{ + unsigned int pos, nbars, i; + u32 ctrl; + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_REBAR); + if (!pos) + return -ENOTSUPP; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + nbars = (ctrl & PCI_REBAR_CTRL_NBAR_MASK) >> + PCI_REBAR_CTRL_NBAR_SHIFT; + + for (i = 0; i < nbars; i++, pos += 8) { + int bar_idx; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; + if (bar_idx == bar) + return pos; + } + + return -ENOENT; +} + +/** + * pci_rebar_get_possible_sizes - get possible sizes for BAR + * @pdev: PCI device + * @bar: BAR to query + * + * Get the possible sizes of a resizable BAR as bitmask defined in the spec + * (bit 0=1MB, bit 19=512GB). Returns 0 if BAR isn't resizable. + */ +u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) +{ + int pos; + u32 cap; + + pos = pci_rebar_find_pos(pdev, bar); + if (pos < 0) + return 0; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); + return (cap & PCI_REBAR_CAP_SIZES) >> 4; +} + +/** + * pci_rebar_get_current_size - get the current size of a BAR + * @pdev: PCI device + * @bar: BAR to set size to + * + * Read the size of a BAR from the resizable BAR config. + * Returns size if found or negative error code. + */ +int pci_rebar_get_current_size(struct pci_dev *pdev, int bar) +{ + int pos; + u32 ctrl; + + pos = pci_rebar_find_pos(pdev, bar); + if (pos < 0) + return pos; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + return (ctrl & PCI_REBAR_CTRL_BAR_SIZE) >> 8; +} + +/** + * pci_rebar_set_size - set a new size for a BAR + * @pdev: PCI device + * @bar: BAR to set size to + * @size: new size as defined in the spec (0=1MB, 19=512GB) + * + * Set the new size of a BAR as defined in the spec. + * Returns zero if resizing was successful, error code otherwise. + */ +int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) +{ + int pos; + u32 ctrl; + + pos = pci_rebar_find_pos(pdev, bar); + if (pos < 0) + return pos; + + pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; + ctrl |= size << 8; + pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); + return 0; +} + +/** * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge * @dev: the PCI device * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 1db36f2e1ef5..186278fa6917 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -366,4 +366,12 @@ int acpi_get_rc_resources(struct device *dev, const char *hid, u16 segment, struct resource *res); #endif +u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar); +int pci_rebar_get_current_size(struct pci_dev *pdev, int bar); +int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size); +static inline u64 pci_rebar_size_to_bytes(int size) +{ + return 1ULL << (size + 20); +} + #endif /* DRIVERS_PCI_H */ diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index b6edb187d160..1f5e6af96c83 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -147,12 +147,8 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) return NULL; rom = ioremap(start, *size); - if (!rom) { - /* restore enable if ioremap fails */ - if (!(res->flags & IORESOURCE_ROM_ENABLE)) - pci_disable_rom(pdev); - return NULL; - } + if (!rom) + goto err_ioremap; /* * Try to find the true size of the ROM since sometimes the PCI window @@ -160,7 +156,18 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) * True size is important if the ROM is going to be copied. */ *size = pci_get_rom_size(pdev, rom, *size); + if (!*size) + goto invalid_rom; + return rom; + +invalid_rom: + iounmap(rom); +err_ioremap: + /* restore enable if ioremap fails */ + if (!(res->flags & IORESOURCE_ROM_ENABLE)) + pci_disable_rom(pdev); + return NULL; } EXPORT_SYMBOL(pci_map_rom); diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 5e547e0dc47b..b1ad466199ad 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1518,13 +1518,16 @@ static void __pci_bridge_assign_resources(const struct pci_dev *bridge, break; } } + +#define PCI_RES_TYPE_MASK \ + (IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH |\ + IORESOURCE_MEM_64) + static void pci_bridge_release_resources(struct pci_bus *bus, unsigned long type) { struct pci_dev *dev = bus->self; struct resource *r; - unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH | IORESOURCE_MEM_64; unsigned old_flags = 0; struct resource *b_res; int idx = 1; @@ -1567,7 +1570,7 @@ static void pci_bridge_release_resources(struct pci_bus *bus, */ release_child_resources(r); if (!release_resource(r)) { - type = old_flags = r->flags & type_mask; + type = old_flags = r->flags & PCI_RES_TYPE_MASK; dev_printk(KERN_DEBUG, &dev->dev, "resource %d %pR released\n", PCI_BRIDGE_RESOURCES + idx, r); /* keep the old size */ @@ -1758,8 +1761,6 @@ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus) enum release_type rel_type = leaf_only; LIST_HEAD(fail_head); struct pci_dev_resource *fail_res; - unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH | IORESOURCE_MEM_64; int pci_try_num = 1; enum enable_type enable_local; @@ -1818,7 +1819,7 @@ again: */ list_for_each_entry(fail_res, &fail_head, list) pci_bus_release_bridge_resources(fail_res->dev->bus, - fail_res->flags & type_mask, + fail_res->flags & PCI_RES_TYPE_MASK, rel_type); /* restore size and flags */ @@ -2031,8 +2032,6 @@ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) LIST_HEAD(fail_head); struct pci_dev_resource *fail_res; int retval; - unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH | IORESOURCE_MEM_64; again: __pci_bus_size_bridges(parent, &add_list); @@ -2066,7 +2065,7 @@ again: */ list_for_each_entry(fail_res, &fail_head, list) pci_bus_release_bridge_resources(fail_res->dev->bus, - fail_res->flags & type_mask, + fail_res->flags & PCI_RES_TYPE_MASK, whole_subtree); /* restore size and flags */ @@ -2091,6 +2090,104 @@ enable_all: } EXPORT_SYMBOL_GPL(pci_assign_unassigned_bridge_resources); +int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type) +{ + struct pci_dev_resource *dev_res; + struct pci_dev *next; + LIST_HEAD(saved); + LIST_HEAD(added); + LIST_HEAD(failed); + unsigned int i; + int ret; + + /* Walk to the root hub, releasing bridge BARs when possible */ + next = bridge; + do { + bridge = next; + for (i = PCI_BRIDGE_RESOURCES; i < PCI_BRIDGE_RESOURCE_END; + i++) { + struct resource *res = &bridge->resource[i]; + + if ((res->flags ^ type) & PCI_RES_TYPE_MASK) + continue; + + /* Ignore BARs which are still in use */ + if (res->child) + continue; + + ret = add_to_list(&saved, bridge, res, 0, 0); + if (ret) + goto cleanup; + + dev_info(&bridge->dev, "BAR %d: releasing %pR\n", + i, res); + + if (res->parent) + release_resource(res); + res->start = 0; + res->end = 0; + break; + } + if (i == PCI_BRIDGE_RESOURCE_END) + break; + + next = bridge->bus ? bridge->bus->self : NULL; + } while (next); + + if (list_empty(&saved)) + return -ENOENT; + + __pci_bus_size_bridges(bridge->subordinate, &added); + __pci_bridge_assign_resources(bridge, &added, &failed); + BUG_ON(!list_empty(&added)); + + if (!list_empty(&failed)) { + ret = -ENOSPC; + goto cleanup; + } + + list_for_each_entry(dev_res, &saved, list) { + /* Skip the bridge we just assigned resources for. */ + if (bridge == dev_res->dev) + continue; + + bridge = dev_res->dev; + pci_setup_bridge(bridge->subordinate); + } + + free_list(&saved); + return 0; + +cleanup: + /* restore size and flags */ + list_for_each_entry(dev_res, &failed, list) { + struct resource *res = dev_res->res; + + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; + } + free_list(&failed); + + /* Revert to the old configuration */ + list_for_each_entry(dev_res, &saved, list) { + struct resource *res = dev_res->res; + + bridge = dev_res->dev; + i = res - bridge->resource; + + res->start = dev_res->start; + res->end = dev_res->end; + res->flags = dev_res->flags; + + pci_claim_resource(bridge, i); + pci_setup_bridge(bridge->subordinate); + } + free_list(&saved); + + return ret; +} + void pci_assign_unassigned_bus_resources(struct pci_bus *bus) { struct pci_dev *dev; diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index e576e1a8d978..bf0089ef2177 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -396,6 +396,64 @@ int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsiz return 0; } +void pci_release_resource(struct pci_dev *dev, int resno) +{ + struct resource *res = dev->resource + resno; + + dev_info(&dev->dev, "BAR %d: releasing %pR\n", resno, res); + release_resource(res); + res->end = resource_size(res) - 1; + res->start = 0; + res->flags |= IORESOURCE_UNSET; +} +EXPORT_SYMBOL(pci_release_resource); + +int pci_resize_resource(struct pci_dev *dev, int resno, int size) +{ + struct resource *res = dev->resource + resno; + int old, ret; + u32 sizes; + u16 cmd; + + /* Make sure the resource isn't assigned before resizing it. */ + if (!(res->flags & IORESOURCE_UNSET)) + return -EBUSY; + + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (cmd & PCI_COMMAND_MEMORY) + return -EBUSY; + + sizes = pci_rebar_get_possible_sizes(dev, resno); + if (!sizes) + return -ENOTSUPP; + + if (!(sizes & BIT(size))) + return -EINVAL; + + old = pci_rebar_get_current_size(dev, resno); + if (old < 0) + return old; + + ret = pci_rebar_set_size(dev, resno, size); + if (ret) + return ret; + + res->end = res->start + pci_rebar_size_to_bytes(size) - 1; + + /* Check if the new config works by trying to assign everything. */ + ret = pci_reassign_bridge_resources(dev->bus->self, res->flags); + if (ret) + goto error_resize; + + return 0; + +error_resize: + pci_rebar_set_size(dev, resno, old); + res->end = res->start + pci_rebar_size_to_bytes(old) - 1; + return ret; +} +EXPORT_SYMBOL(pci_resize_resource); + int pci_enable_resources(struct pci_dev *dev, int mask) { u16 cmd, old_cmd; diff --git a/include/linux/pci.h b/include/linux/pci.h index 3dbe947b4152..f3b6909305ca 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1110,6 +1110,8 @@ void pci_reset_bridge_secondary_bus(struct pci_dev *dev); void pci_update_resource(struct pci_dev *dev, int resno); int __must_check pci_assign_resource(struct pci_dev *dev, int i); int __must_check pci_reassign_resource(struct pci_dev *dev, int i, resource_size_t add_size, resource_size_t align); +void pci_release_resource(struct pci_dev *dev, int resno); +int __must_check pci_resize_resource(struct pci_dev *dev, int i, int size); int pci_select_bars(struct pci_dev *dev, unsigned long flags); bool pci_device_is_present(struct pci_dev *pdev); void pci_ignore_hotplug(struct pci_dev *dev); @@ -1189,6 +1191,7 @@ void pci_assign_unassigned_resources(void); void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge); void pci_assign_unassigned_bus_resources(struct pci_bus *bus); void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus); +int pci_reassign_bridge_resources(struct pci_dev *bridge, unsigned long type); void pdev_enable_device(struct pci_dev *); int pci_enable_resources(struct pci_dev *, int mask); void pci_assign_irq(struct pci_dev *dev); diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 12241709092b..66cd64595d7e 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -940,9 +940,13 @@ #define PCI_SATA_SIZEOF_LONG 16 /* Resizable BARs */ +#define PCI_REBAR_CAP 4 /* capability register */ +#define PCI_REBAR_CAP_SIZES 0x00FFFFF0 /* supported BAR sizes */ #define PCI_REBAR_CTRL 8 /* control register */ -#define PCI_REBAR_CTRL_NBAR_MASK (7 << 5) /* mask for # bars */ -#define PCI_REBAR_CTRL_NBAR_SHIFT 5 /* shift for # bars */ +#define PCI_REBAR_CTRL_BAR_IDX 0x00000007 /* BAR index */ +#define PCI_REBAR_CTRL_NBAR_MASK 0x000000E0 /* # of resizable BARs */ +#define PCI_REBAR_CTRL_NBAR_SHIFT 5 /* shift for # of BARs */ +#define PCI_REBAR_CTRL_BAR_SIZE 0x00001F00 /* BAR size */ /* Dynamic Power Allocation */ #define PCI_DPA_CAP 4 /* capability register */ |