diff options
Diffstat (limited to 'drivers/pci/pci.c')
-rw-r--r-- | drivers/pci/pci.c | 497 |
1 files changed, 434 insertions, 63 deletions
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7d1024d8d1..e6370b5c7f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1,8 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only #define pr_fmt(fmt) "pci: " fmt #include <common.h> #include <linux/sizes.h> #include <linux/pci.h> +#include <linux/bitfield.h> static unsigned int pci_scan_bus(struct pci_bus *bus); @@ -40,36 +42,41 @@ static void pci_bus_register_devices(struct pci_bus *bus) pci_bus_register_devices(child_bus); } +void pci_controller_init(struct pci_controller *hose) +{ + INIT_LIST_HEAD(&hose->windows); + + of_pci_bridge_init(hose->parent, hose); +} + void register_pci_controller(struct pci_controller *hose) { struct pci_bus *bus; bus = pci_alloc_bus(); hose->bus = bus; - bus->parent = hose->parent; bus->host = hose; - bus->resource[PCI_BUS_RESOURCE_MEM] = hose->mem_resource; - bus->resource[PCI_BUS_RESOURCE_MEM_PREF] = hose->mem_pref_resource; - bus->resource[PCI_BUS_RESOURCE_IO] = hose->io_resource; - bus->number = bus_index++; - - if (hose->set_busno) - hose->set_busno(hose, bus->number); - - if (bus->resource[PCI_BUS_RESOURCE_MEM]) - last_mem = bus->resource[PCI_BUS_RESOURCE_MEM]->start; - else - last_mem = 0; - - if (bus->resource[PCI_BUS_RESOURCE_MEM_PREF]) - last_mem_pref = bus->resource[PCI_BUS_RESOURCE_MEM_PREF]->start; - else - last_mem_pref = 0; - if (bus->resource[PCI_BUS_RESOURCE_IO]) - last_io = bus->resource[PCI_BUS_RESOURCE_IO]->start; - else - last_io = 0; + if (pcibios_assign_all_busses()) { + bus->number = bus_index++; + if (hose->set_busno) + hose->set_busno(hose, bus->number); + + if (hose->mem_resource) + last_mem = hose->mem_resource->start; + else + last_mem = 0; + + if (hose->mem_pref_resource) + last_mem_pref = hose->mem_pref_resource->start; + else + last_mem_pref = 0; + + if (hose->io_resource) + last_io = hose->io_resource->start; + else + last_io = 0; + } pci_scan_bus(bus); pci_bus_register_devices(bus); @@ -149,6 +156,189 @@ static u32 pci_size(u32 base, u32 maxbase, u32 mask) return size + 1; } +static unsigned long pci_ea_flags(struct pci_dev *dev, u8 prop) +{ + unsigned long flags = IORESOURCE_PCI_FIXED; + + switch (prop) { + case PCI_EA_P_MEM: + case PCI_EA_P_VF_MEM: + flags |= IORESOURCE_MEM; + break; + case PCI_EA_P_MEM_PREFETCH: + case PCI_EA_P_VF_MEM_PREFETCH: + flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH; + break; + case PCI_EA_P_IO: + flags |= IORESOURCE_IO; + break; + default: + return 0; + } + + return flags; +} + +static struct resource *pci_ea_get_resource(struct pci_dev *dev, u8 bei, + u8 prop) +{ + if (bei <= PCI_EA_BEI_BAR5 && prop <= PCI_EA_P_IO) + return &dev->resource[bei]; + else if (bei == PCI_EA_BEI_ROM) + return &dev->resource[PCI_ROM_RESOURCE]; + else + return NULL; +} + +/* Read an Enhanced Allocation (EA) entry */ +static int pci_ea_read(struct pci_dev *dev, int offset) +{ + struct resource *res; + int ent_size, ent_offset = offset; + resource_size_t start, end; + unsigned long flags; + u32 dw0, bei, base, max_offset; + u8 prop; + bool support_64 = (sizeof(resource_size_t) >= 8); + + pci_read_config_dword(dev, ent_offset, &dw0); + ent_offset += 4; + + /* Entry size field indicates DWORDs after 1st */ + ent_size = (FIELD_GET(PCI_EA_ES, dw0) + 1) << 2; + + if (!(dw0 & PCI_EA_ENABLE)) /* Entry not enabled */ + goto out; + + bei = FIELD_GET(PCI_EA_BEI, dw0); + prop = FIELD_GET(PCI_EA_PP, dw0); + + /* + * If the Property is in the reserved range, try the Secondary + * Property instead. + */ + if (prop > PCI_EA_P_BRIDGE_IO && prop < PCI_EA_P_MEM_RESERVED) + prop = FIELD_GET(PCI_EA_SP, dw0); + if (prop > PCI_EA_P_BRIDGE_IO) + goto out; + + res = pci_ea_get_resource(dev, bei, prop); + if (!res) { + dev_dbg(&dev->dev, "Unsupported EA entry BEI: %u\n", bei); + goto out; + } + + flags = pci_ea_flags(dev, prop); + if (!flags) { + dev_err(&dev->dev, "Unsupported EA properties: %#x\n", prop); + goto out; + } + + /* Read Base */ + pci_read_config_dword(dev, ent_offset, &base); + start = (base & PCI_EA_FIELD_MASK); + ent_offset += 4; + + /* Read MaxOffset */ + pci_read_config_dword(dev, ent_offset, &max_offset); + ent_offset += 4; + + /* Read Base MSBs (if 64-bit entry) */ + if (base & PCI_EA_IS_64) { + u32 base_upper; + + pci_read_config_dword(dev, ent_offset, &base_upper); + ent_offset += 4; + + flags |= IORESOURCE_MEM_64; + + /* entry starts above 32-bit boundary, can't use */ + if (!support_64 && base_upper) + goto out; + + if (support_64) + start |= ((u64)base_upper << 32); + } + + end = start + (max_offset | 0x03); + + /* Read MaxOffset MSBs (if 64-bit entry) */ + if (max_offset & PCI_EA_IS_64) { + u32 max_offset_upper; + + pci_read_config_dword(dev, ent_offset, &max_offset_upper); + ent_offset += 4; + + flags |= IORESOURCE_MEM_64; + + /* entry too big, can't use */ + if (!support_64 && max_offset_upper) + goto out; + + if (support_64) + end += ((u64)max_offset_upper << 32); + } + + if (end < start) { + dev_err(&dev->dev, "EA Entry crosses address boundary\n"); + goto out; + } + + if (ent_size != ent_offset - offset) { + dev_err(&dev->dev, "EA Entry Size (%d) does not match length read (%d)\n", + ent_size, ent_offset - offset); + goto out; + } + + res->start = start; + res->end = end; + res->flags = flags; + + if (bei <= PCI_EA_BEI_BAR5) + dev_dbg(&dev->dev, "BAR %d: %pR (from Enhanced Allocation, properties %#02x)\n", + bei, res, prop); + else if (bei == PCI_EA_BEI_ROM) + dev_dbg(&dev->dev, "ROM: %pR (from Enhanced Allocation, properties %#02x)\n", + res, prop); + else if (bei >= PCI_EA_BEI_VF_BAR0 && bei <= PCI_EA_BEI_VF_BAR5) + dev_dbg(&dev->dev, "VF BAR %d: %pR (from Enhanced Allocation, properties %#02x)\n", + bei - PCI_EA_BEI_VF_BAR0, res, prop); + else + dev_dbg(&dev->dev, "BEI %d res: %pR (from Enhanced Allocation, properties %#02x)\n", + bei, res, prop); + +out: + return offset + ent_size; +} + +/* Enhanced Allocation Initialization */ +static void pci_ea_init(struct pci_dev *dev) +{ + int ea; + u8 num_ent; + int offset; + int i; + + /* find PCI EA capability in list */ + ea = pci_find_capability(dev, PCI_CAP_ID_EA); + if (!ea) + return; + + /* determine the number of entries */ + pci_bus_read_config_byte(dev->bus, dev->devfn, ea + PCI_EA_NUM_ENT, + &num_ent); + num_ent &= PCI_EA_NUM_ENT_MASK; + + offset = ea + PCI_EA_FIRST_ENT; + + /* Skip DWORD 2 for type 1 functions */ + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) + offset += 4; + + /* parse each EA entry */ + for (i = 0; i < num_ent; ++i) + offset = pci_ea_read(dev, offset); +} static void setup_device(struct pci_dev *dev, int max_bar) { @@ -156,17 +346,20 @@ static void setup_device(struct pci_dev *dev, int max_bar) u8 cmd; pci_read_config_byte(dev, PCI_COMMAND, &cmd); - pci_write_config_byte(dev, PCI_COMMAND, - cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY)); + + if (pcibios_assign_all_busses()) + pci_write_config_byte(dev, PCI_COMMAND, + cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY)); + for (bar = 0; bar < max_bar; bar++) { const int pci_base_address_0 = PCI_BASE_ADDRESS_0 + bar * 4; const int pci_base_address_1 = PCI_BASE_ADDRESS_1 + bar * 4; - resource_size_t *last_addr; + resource_size_t *last_addr, start; u32 orig, mask, size; unsigned long flags; const char *kind; - int busres; + struct resource *busres; pci_read_config_dword(dev, pci_base_address_0, &orig); pci_write_config_dword(dev, pci_base_address_0, 0xfffffffe); @@ -183,20 +376,20 @@ static void setup_device(struct pci_dev *dev, int max_bar) flags = IORESOURCE_IO; kind = "IO"; last_addr = &last_io; - busres = PCI_BUS_RESOURCE_IO; + busres = dev->bus->host->io_resource; } else if ((mask & PCI_BASE_ADDRESS_MEM_PREFETCH) && last_mem_pref) /* prefetchable MEM */ { size = pci_size(orig, mask, 0xfffffff0); flags = IORESOURCE_MEM | IORESOURCE_PREFETCH; kind = "P-MEM"; last_addr = &last_mem_pref; - busres = PCI_BUS_RESOURCE_MEM_PREF; + busres = dev->bus->host->mem_pref_resource;; } else { /* non-prefetch MEM */ size = pci_size(orig, mask, 0xfffffff0); flags = IORESOURCE_MEM; kind = "NP-MEM"; last_addr = &last_mem; - busres = PCI_BUS_RESOURCE_MEM; + busres = dev->bus->host->mem_resource; } if (!size) { @@ -207,32 +400,60 @@ static void setup_device(struct pci_dev *dev, int max_bar) pr_debug("pbar%d: mask=%08x %s %d bytes\n", bar, mask, kind, size); - if (ALIGN(*last_addr, size) + size > - dev->bus->resource[busres]->end) { - pr_debug("BAR does not fit within bus %s res\n", kind); - return; + if (pcibios_assign_all_busses()) { + struct resource res; + struct pci_bus_region region; + + if (ALIGN(*last_addr, size) + size > busres->end) { + pr_debug("BAR does not fit within bus %s res\n", kind); + continue; + } + + *last_addr = ALIGN(*last_addr, size); + + res.flags = flags; + res.start = *last_addr; + res.end = res.start + size - 1; + + pcibios_resource_to_bus(dev->bus, ®ion, &res); + + pci_write_config_dword(dev, pci_base_address_0, + lower_32_bits(region.start)); + if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) + pci_write_config_dword(dev, pci_base_address_1, + upper_32_bits(region.start)); + start = *last_addr; + *last_addr += size; + } else { + u32 tmp; + pci_read_config_dword(dev, pci_base_address_0, &tmp); + tmp &= mask & PCI_BASE_ADDRESS_SPACE_IO ? PCI_BASE_ADDRESS_IO_MASK + : PCI_BASE_ADDRESS_MEM_MASK; + start = tmp; + + if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) { + pci_read_config_dword(dev, pci_base_address_1, &tmp); + start |= (u64)tmp << 32; + } } - *last_addr = ALIGN(*last_addr, size); - pci_write_config_dword(dev, pci_base_address_0, *last_addr); dev->resource[bar].flags = flags; - dev->resource[bar].start = *last_addr; - dev->resource[bar].end = dev->resource[bar].start + size - 1; - - pr_debug("pbar%d: allocated at %pa\n", bar, last_addr); - - *last_addr += size; + dev->resource[bar].start = start; + dev->resource[bar].end = start + size - 1; if (mask & PCI_BASE_ADDRESS_MEM_TYPE_64) { dev->resource[bar].flags |= IORESOURCE_MEM_64; - pci_write_config_dword(dev, pci_base_address_1, 0); bar++; } } + pci_ea_init(dev); + pci_fixup_device(pci_fixup_header, dev); - pci_write_config_byte(dev, PCI_COMMAND, cmd); + if (pcibios_assign_all_busses()) + pci_write_config_byte(dev, PCI_COMMAND, cmd); + list_add_tail(&dev->bus_list, &dev->bus->devices); } @@ -240,6 +461,12 @@ static void prescan_setup_bridge(struct pci_dev *dev) { u16 cmdstat; + if (!pcibios_assign_all_busses()) { + pci_read_config_byte(dev, PCI_PRIMARY_BUS, &dev->bus->number); + pci_read_config_byte(dev, PCI_SECONDARY_BUS, &dev->subordinate->number); + return; + } + pci_read_config_word(dev, PCI_COMMAND, &cmdstat); /* Configure bus number registers */ @@ -283,6 +510,9 @@ static void prescan_setup_bridge(struct pci_dev *dev) static void postscan_setup_bridge(struct pci_dev *dev) { + if (!pcibios_assign_all_busses()) + return; + /* limit subordinate to last used bus number */ pci_write_config_byte(dev, PCI_SUBORDINATE_BUS, bus_index - 1); @@ -311,15 +541,15 @@ static void postscan_setup_bridge(struct pci_dev *dev) } static struct device_node * -pci_of_match_device(struct device_d *parent, unsigned int devfn) +pci_of_match_device(struct device *parent, unsigned int devfn) { struct device_node *np; u32 reg; - if (!IS_ENABLED(CONFIG_OFTREE) || !parent || !parent->device_node) + if (!IS_ENABLED(CONFIG_OFTREE) || !parent || !parent->of_node) return NULL; - for_each_child_of_node(parent->device_node, np) { + for_each_child_of_node(parent->of_node, np) { if (!of_property_read_u32_array(np, "reg", ®, 1)) { /* * Only match device/function pair of the device @@ -335,6 +565,38 @@ pci_of_match_device(struct device_d *parent, unsigned int devfn) return NULL; } +/** + * pcie_flr - initiate a PCIe function level reset + * @dev: device to reset + * + * Initiate a function level reset on @dev. + */ +int pci_flr(struct pci_dev *pdev) +{ + u16 val; + int pcie_off; + u32 cap; + + /* look for PCI Express Capability */ + pcie_off = pci_find_capability(pdev, PCI_CAP_ID_EXP); + if (!pcie_off) + return -ENOENT; + + /* check FLR capability */ + pci_read_config_dword(pdev, pcie_off + PCI_EXP_DEVCAP, &cap); + if (!(cap & PCI_EXP_DEVCAP_FLR)) + return -ENOENT; + + pci_read_config_word(pdev, pcie_off + PCI_EXP_DEVCTL, &val); + val |= PCI_EXP_DEVCTL_BCR_FLR; + pci_write_config_word(pdev, pcie_off + PCI_EXP_DEVCTL, val); + + /* wait 100ms, per PCI spec */ + mdelay(100); + + return 0; +} + static unsigned int pci_scan_bus(struct pci_bus *bus) { struct pci_dev *dev; @@ -349,6 +611,8 @@ static unsigned int pci_scan_bus(struct pci_bus *bus) max = bus->secondary; for (devfn = 0; devfn < 0xff; ++devfn) { + struct device *parent; + if (PCI_FUNC(devfn) && !is_multi) { /* not a multi-function device */ continue; @@ -368,11 +632,12 @@ static unsigned int pci_scan_bus(struct pci_bus *bus) dev->devfn = devfn; dev->vendor = l & 0xffff; dev->device = (l >> 16) & 0xffff; - dev->dev.parent = bus->parent; - dev->dev.device_node = pci_of_match_device(bus->parent, devfn); - if (dev->dev.device_node) - pr_debug("found DT node %s for device %04x:%04x\n", - dev->dev.device_node->full_name, + parent = bus->self ? &bus->self->dev : bus->host->parent; + dev->dev.parent = parent; + dev->dev.of_node = pci_of_match_device(parent, devfn); + if (dev->dev.of_node) + pr_debug("found DT node %pOF for device %04x:%04x\n", + dev->dev.of_node, dev->vendor, dev->device); /* non-destructively determine if device can be a master: */ @@ -396,28 +661,28 @@ static unsigned int pci_scan_bus(struct pci_bus *bus) pr_debug("%02x:%02x [%04x:%04x]\n", bus->number, dev->devfn, dev->vendor, dev->device); - switch (hdr_type & 0x7f) { + switch (hdr_type & PCI_HEADER_TYPE_MASK) { case PCI_HEADER_TYPE_NORMAL: if (class == PCI_CLASS_BRIDGE_PCI) goto bad; setup_device(dev, 6); + + pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device); + pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); break; case PCI_HEADER_TYPE_BRIDGE: child_bus = pci_alloc_bus(); /* inherit parent properties */ child_bus->host = bus->host; - child_bus->parent_bus = bus; - child_bus->resource[PCI_BUS_RESOURCE_MEM] = - bus->resource[PCI_BUS_RESOURCE_MEM]; - child_bus->resource[PCI_BUS_RESOURCE_MEM_PREF] = - bus->resource[PCI_BUS_RESOURCE_MEM_PREF]; - child_bus->resource[PCI_BUS_RESOURCE_IO] = - bus->resource[PCI_BUS_RESOURCE_IO]; - - child_bus->parent = &dev->dev; - child_bus->number = bus_index++; - child_bus->primary = bus->number; + child_bus->parent = bus; + + child_bus->self = dev; + + if (pcibios_assign_all_busses()) { + child_bus->number = bus_index++; + child_bus->primary = bus->number; + } list_add_tail(&child_bus->node, &bus->children); dev->subordinate = child_bus; @@ -506,6 +771,112 @@ int pci_enable_device(struct pci_dev *dev) } EXPORT_SYMBOL(pci_enable_device); +/** + * pci_select_bars - Make BAR mask from the type of resource + * @dev: the PCI device for which BAR mask is made + * @flags: resource type mask to be selected + * + * This helper routine makes bar mask from the type of resource. + */ +int pci_select_bars(struct pci_dev *dev, unsigned long flags) +{ + int i, bars = 0; + for (i = 0; i < PCI_NUM_RESOURCES; i++) + if (pci_resource_flags(dev, i) & flags) + bars |= (1 << i); + return bars; +} + +static u8 __pci_find_next_cap_ttl(struct pci_bus *bus, unsigned int devfn, + u8 pos, int cap, int *ttl) +{ + u8 id; + u16 ent; + + pci_bus_read_config_byte(bus, devfn, pos, &pos); + + while ((*ttl)--) { + if (pos < 0x40) + break; + pos &= ~3; + pci_bus_read_config_word(bus, devfn, pos, &ent); + + id = ent & 0xff; + if (id == 0xff) + break; + if (id == cap) + return pos; + pos = (ent >> 8); + } + return 0; +} + +static u8 __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn, + u8 pos, int cap) +{ + int ttl = PCI_FIND_CAP_TTL; + + return __pci_find_next_cap_ttl(bus, devfn, pos, cap, &ttl); +} + +u8 pci_find_next_capability(struct pci_dev *dev, u8 pos, int cap) +{ + return __pci_find_next_cap(dev->bus, dev->devfn, + pos + PCI_CAP_LIST_NEXT, cap); +} +EXPORT_SYMBOL_GPL(pci_find_next_capability); + +static u8 __pci_bus_find_cap_start(struct pci_bus *bus, + unsigned int devfn, u8 hdr_type) +{ + u16 status; + + pci_bus_read_config_word(bus, devfn, PCI_STATUS, &status); + if (!(status & PCI_STATUS_CAP_LIST)) + return 0; + + switch (hdr_type) { + case PCI_HEADER_TYPE_NORMAL: + case PCI_HEADER_TYPE_BRIDGE: + return PCI_CAPABILITY_LIST; + case PCI_HEADER_TYPE_CARDBUS: + return PCI_CB_CAPABILITY_LIST; + } + + return 0; +} + +/** + * pci_find_capability - query for devices' capabilities + * @dev: PCI device to query + * @cap: capability code + * + * Tell if a device supports a given PCI capability. + * Returns the address of the requested capability structure within the + * device's PCI configuration space or 0 in case the device does not + * support it. Possible values for @cap include: + * + * %PCI_CAP_ID_PM Power Management + * %PCI_CAP_ID_AGP Accelerated Graphics Port + * %PCI_CAP_ID_VPD Vital Product Data + * %PCI_CAP_ID_SLOTID Slot Identification + * %PCI_CAP_ID_MSI Message Signalled Interrupts + * %PCI_CAP_ID_CHSWP CompactPCI HotSwap + * %PCI_CAP_ID_PCIX PCI-X + * %PCI_CAP_ID_EXP PCI Express + */ +u8 pci_find_capability(struct pci_dev *dev, int cap) +{ + u8 pos; + + pos = __pci_bus_find_cap_start(dev->bus, dev->devfn, dev->hdr_type & PCI_HEADER_TYPE_MASK); + if (pos) + pos = __pci_find_next_cap(dev->bus, dev->devfn, pos, cap); + + return pos; +} +EXPORT_SYMBOL(pci_find_capability); + static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, struct pci_fixup *end) { |