summaryrefslogtreecommitdiffstats
path: root/drivers/pci/pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/pci.c')
-rw-r--r--drivers/pci/pci.c497
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, &region, &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", &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)
{