From 9ada07b1100a9c7d3107c47673664194574b02a0 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Thu, 6 Mar 2014 21:11:22 +0100 Subject: PCI/portdrv: Use pci_enable_msix_exact() instead of pci_enable_msix() As result of deprecation of MSI-X/MSI enablement functions pci_enable_msix() and pci_enable_msi_block() all drivers using these two interfaces need to be updated to use the new pci_enable_msi_range() or pci_enable_msi_exact() and pci_enable_msix_range() or pci_enable_msix_exact() interfaces. Signed-off-by: Alexander Gordeev Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 986f8eadfd3..0b1efb2760d 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -99,7 +99,7 @@ static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask) for (i = 0; i < nr_entries; i++) msix_entries[i].entry = i; - status = pci_enable_msix(dev, msix_entries, nr_entries); + status = pci_enable_msix_exact(dev, msix_entries, nr_entries); if (status) goto Exit; @@ -171,7 +171,7 @@ static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask) pci_disable_msix(dev); /* Now allocate the MSI-X vectors for real */ - status = pci_enable_msix(dev, msix_entries, nvec); + status = pci_enable_msix_exact(dev, msix_entries, nvec); if (status) goto Exit; } -- cgit v1.2.3-70-g09d2 From 1406276c1254d761da7c16b30aa32e3af2b3612a Mon Sep 17 00:00:00 2001 From: Jan Beulich Date: Mon, 14 Apr 2014 14:59:50 -0600 Subject: PCI/MSI: Simplify populate_msi_sysfs() Simplify populate_msi_sysfs() by - Swapping the order of the two allocations and storing the msi_dev_attr-derived pointer right after allocation, allowing the cleanup code to pick things up without extra effort. - Using kasprintf() instead of the kmalloc()/sprintf() pair. Signed-off-by: Jan Beulich Signed-off-by: Bjorn Helgaas Acked-by: Greg Kroah-Hartman --- drivers/pci/msi.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index 955ab7990c5..04130c3f9cf 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -544,22 +544,18 @@ static int populate_msi_sysfs(struct pci_dev *pdev) if (!msi_attrs) return -ENOMEM; list_for_each_entry(entry, &pdev->msi_list, list) { - char *name = kmalloc(20, GFP_KERNEL); - if (!name) - goto error_attrs; - msi_dev_attr = kzalloc(sizeof(*msi_dev_attr), GFP_KERNEL); - if (!msi_dev_attr) { - kfree(name); + if (!msi_dev_attr) goto error_attrs; - } + msi_attrs[count] = &msi_dev_attr->attr; - sprintf(name, "%d", entry->irq); sysfs_attr_init(&msi_dev_attr->attr); - msi_dev_attr->attr.name = name; + msi_dev_attr->attr.name = kasprintf(GFP_KERNEL, "%d", + entry->irq); + if (!msi_dev_attr->attr.name) + goto error_attrs; msi_dev_attr->attr.mode = S_IRUGO; msi_dev_attr->show = msi_mode_show; - msi_attrs[count] = &msi_dev_attr->attr; ++count; } -- cgit v1.2.3-70-g09d2 From 8e56aed0b0579b667489bcb1d94c223726f0eaa1 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Apr 2014 16:04:55 -0600 Subject: PCI: hotplug: Remove unnecessary "dev->bus" test Every pci_dev is on a valid pci_bus, so we don't need to test whether dev->bus is NULL or not. The only exceptions are a few legacy cases like alpha_core_agp_setup(), parisc_agp_setup(), and megaraid's make_local_pdev(), where we allocate a pci_dev with a NULL bus pointer. These are dubious uses (especially the megaraid one), and I don't think it's possible to exercise this pci_configure_slot() path with any of them. Found by Coverity (CID 146446). Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pcihp_slot.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/pcihp_slot.c b/drivers/pci/hotplug/pcihp_slot.c index 16f92035231..e246a10a0d2 100644 --- a/drivers/pci/hotplug/pcihp_slot.c +++ b/drivers/pci/hotplug/pcihp_slot.c @@ -160,8 +160,7 @@ void pci_configure_slot(struct pci_dev *dev) (dev->class >> 8) == PCI_CLASS_BRIDGE_PCI))) return; - if (dev->bus) - pcie_bus_configure_settings(dev->bus); + pcie_bus_configure_settings(dev->bus); memset(&hpp, 0, sizeof(hpp)); ret = pci_get_hp_params(dev, &hpp); -- cgit v1.2.3-70-g09d2 From 17f830bb83ff64533f02cfd4b114d4a5957c2ee7 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Sat, 5 Apr 2014 15:05:07 -0600 Subject: PCI: pciehp: Use PCI_EXP_SLTCAP_PSN define Use PCI_EXP_SLTCAP_PSN to make it easier to find code that uses the Physical Slot Number field in the PCIe Slot Capabilities register. No functional change. Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index 8a66866b8cf..8e9012dca45 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -127,7 +127,7 @@ struct controller { #define HP_SUPR_RM(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_HPS) #define EMI(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_EIP) #define NO_CMD_CMPL(ctrl) ((ctrl)->slot_cap & PCI_EXP_SLTCAP_NCCS) -#define PSN(ctrl) ((ctrl)->slot_cap >> 19) +#define PSN(ctrl) (((ctrl)->slot_cap & PCI_EXP_SLTCAP_PSN) >> 19) int pciehp_sysfs_enable_slot(struct slot *slot); int pciehp_sysfs_disable_slot(struct slot *slot); -- cgit v1.2.3-70-g09d2 From 56a3d18279f00c7ccbcdc193ceaf1a2f88c51457 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Sat, 5 Apr 2014 15:13:33 -0600 Subject: PCI: Remove unnecessary includes of None of these files are actually using any __init type directives and hence don't need to include . Most are just a left over from __devinit and __cpuinit removal, or simply due to code getting copied from one driver to the next. Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas --- drivers/pci/bus.c | 1 - drivers/pci/host-bridge.c | 1 - drivers/pci/hotplug/acpiphp_glue.c | 1 - drivers/pci/hotplug/cpqphp_nvram.c | 1 - drivers/pci/hotplug/s390_pci_hpc.c | 1 - drivers/pci/msi.c | 1 - drivers/pci/search.c | 1 - drivers/pci/setup-irq.c | 1 - drivers/pci/setup-res.c | 1 - 9 files changed, 9 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index fb8aed307c2..ba2bf55a38d 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include "pci.h" diff --git a/drivers/pci/host-bridge.c b/drivers/pci/host-bridge.c index 47aaf22d814..0e5f3c95af5 100644 --- a/drivers/pci/host-bridge.c +++ b/drivers/pci/host-bridge.c @@ -3,7 +3,6 @@ */ #include -#include #include #include diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index bccc27ee103..0238a02b4cb 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -41,7 +41,6 @@ #define pr_fmt(fmt) "acpiphp_glue: " fmt -#include #include #include diff --git a/drivers/pci/hotplug/cpqphp_nvram.c b/drivers/pci/hotplug/cpqphp_nvram.c index 76ba8a1c774..9600a392eaa 100644 --- a/drivers/pci/hotplug/cpqphp_nvram.c +++ b/drivers/pci/hotplug/cpqphp_nvram.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include "cpqphp.h" #include "cpqphp_nvram.h" diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c index 8d2ce22151e..d1332d2f873 100644 --- a/drivers/pci/hotplug/s390_pci_hpc.c +++ b/drivers/pci/hotplug/s390_pci_hpc.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index 955ab7990c5..aaf8d3243cd 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/pci/search.c b/drivers/pci/search.c index 4a1b972efe7..8e495bda678 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -7,7 +7,6 @@ * Copyright (C) 2003 -- 2004 Greg Kroah-Hartman */ -#include #include #include #include diff --git a/drivers/pci/setup-irq.c b/drivers/pci/setup-irq.c index 9bd6864ec5d..dbc4ffcf42d 100644 --- a/drivers/pci/setup-irq.c +++ b/drivers/pci/setup-irq.c @@ -10,7 +10,6 @@ */ -#include #include #include #include diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 7eed671d558..1e37c590a18 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -16,7 +16,6 @@ * Resource sorting */ -#include #include #include #include -- cgit v1.2.3-70-g09d2 From 1a30fd0dba778f7ef1a6254989060a141fca8e23 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 31 Mar 2014 12:21:38 -0600 Subject: PCI: Add Patsburg (X79) to Intel PCH root port ACS quirk Intel has updated Red Hat bz1037684 to note that X79 PCH root ports also provide isolation and the same ACS quirks apply. Some sources indicate additional device IDs for X79, but this patch includes only the ones specifically identified by Intel: https://bugzilla.redhat.com/show_bug.cgi?id=1037684#c11 Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas Acked-by: Don Dugger --- drivers/pci/quirks.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index e7292065a1b..5e4ac635368 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3453,6 +3453,8 @@ static const u16 pci_quirk_intel_pch_acs_ids[] = { /* Wildcat PCH */ 0x9c90, 0x9c91, 0x9c92, 0x9c93, 0x9c94, 0x9c95, 0x9c96, 0x9c97, 0x9c98, 0x9c99, 0x9c9a, 0x9c9b, + /* Patsburg (X79) PCH */ + 0x1d10, 0x1d12, 0x1d14, 0x1d16, 0x1d18, 0x1d1a, 0x1d1c, 0x1d1e, }; static bool pci_quirk_intel_pch_acs_match(struct pci_dev *dev) -- cgit v1.2.3-70-g09d2 From b9bfe1bca8ecccf3d1f015fd18fad52ed95f8033 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 7 Apr 2014 11:30:20 +0200 Subject: PCI: rcar: Use new OF interrupt mapping when possible Use new OF interrupt mapping (of_irq_parse_and_map_pci()) when possible. This is the recommended method of doing the IRQ mapping. For old devicetrees we fall back to the previous practice. This allows interrupts to be remapped across bridges. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman --- drivers/pci/host/pci-rcar-gen2.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-rcar-gen2.c b/drivers/pci/host/pci-rcar-gen2.c index fd3e3ab5650..4fe349dcaf5 100644 --- a/drivers/pci/host/pci-rcar-gen2.c +++ b/drivers/pci/host/pci-rcar-gen2.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -180,8 +181,13 @@ static int rcar_pci_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) { struct pci_sys_data *sys = dev->bus->sysdata; struct rcar_pci_priv *priv = sys->private_data; + int irq; + + irq = of_irq_parse_and_map_pci(dev, slot, pin); + if (!irq) + irq = priv->irq; - return priv->irq; + return irq; } #ifdef CONFIG_PCI_DEBUG -- cgit v1.2.3-70-g09d2 From 476a357fd9045a4c91faa055fadabf550aedb8c5 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Thu, 20 Feb 2014 17:42:31 -0800 Subject: PCI: pciehp: Acknowledge spurious "cmd completed" event In case of a spurious "cmd completed", pcie_write_cmd() does not clear it, but yet expects more "cmd completed" events to be generated. This does not happen because the previous (spurious) event has not been acknowledged. Fix that. Signed-off-by: Rajat Jain Signed-off-by: Rajat Jain Signed-off-by: Guenter Roeck Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_hpc.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index d7d058fa19a..1463412cf7f 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -159,6 +159,8 @@ static void pcie_write_cmd(struct controller *ctrl, u16 cmd, u16 mask) pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &slot_status); if (slot_status & PCI_EXP_SLTSTA_CC) { + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_CC); if (!ctrl->no_cmd_complete) { /* * After 1 sec and CMD_COMPLETED still not set, just -- cgit v1.2.3-70-g09d2 From 374a91404314cef882b27b293dd34d89e2a3c0b6 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Mon, 31 Mar 2014 16:51:23 -0700 Subject: PCI: Allow hotplug service drivers to operate in polling mode Today the PCIe port bus driver disables the Hot-plug service if the port device does not have the capability to generate interrupts. However, a user must be able to use the "pciehp_poll_mode" parameter to use the pciehp in polling method in such a case. Today it is not possible. This patch allows a hotplug service driver to decide whether or not it would like to continue in the absence of interrupts. Signed-off-by: Rajat Jain Signed-off-by: Rajat Jain Signed-off-by: Guenter Roeck Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index 986f8eadfd3..f6503add2f3 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -379,10 +379,13 @@ int pcie_port_device_register(struct pci_dev *dev) /* * Initialize service irqs. Don't use service devices that * require interrupts if there is no way to generate them. + * However, some drivers may have a polling mode (e.g. pciehp_poll_mode) + * that can be used in the absence of irqs. Allow them to determine + * if that is to be used. */ status = init_service_irqs(dev, irqs, capabilities); if (status) { - capabilities &= PCIE_PORT_SERVICE_VC; + capabilities &= PCIE_PORT_SERVICE_VC | PCIE_PORT_SERVICE_HP; if (!capabilities) goto error_disable; } -- cgit v1.2.3-70-g09d2 From 67ebd8140dc8923c65451fa0f6a8eee003c4dcd3 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Sat, 5 Apr 2014 15:14:22 -0600 Subject: PCI: Fix incorrect vgaarb conditional in WARN_ON() 3448a19da479 "vgaarb: use bridges to control VGA routing where possible" added the "flags & PCI_VGA_STATE_CHANGE_DECODES" condition to an existing WARN_ON(), but used bitwise AND (&) instead of logical AND (&&), so the condition is never true. Replace with logical AND. Found by Coverity (CID 142811). Fixes: 3448a19da479 "vgaarb: use bridges to control VGA routing where possible" Signed-off-by: Bjorn Helgaas Acked-by: Yinghai Lu Acked-by: David Airlie --- drivers/pci/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7325d43bf03..39012831867 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4125,7 +4125,7 @@ int pci_set_vga_state(struct pci_dev *dev, bool decode, u16 cmd; int rc; - WARN_ON((flags & PCI_VGA_STATE_CHANGE_DECODES) & (command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY))); + WARN_ON((flags & PCI_VGA_STATE_CHANGE_DECODES) && (command_bits & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY))); /* ARCH specific VGA enables */ rc = pci_set_vga_state_arch(dev, decode, command_bits, flags); -- cgit v1.2.3-70-g09d2 From 761ce53330a4f02c58768631027d1c1dd0d538f7 Mon Sep 17 00:00:00 2001 From: Laurent Dufour Date: Thu, 10 Apr 2014 15:02:13 +0200 Subject: PCI: rphahp: Fix endianess issues Numerical values stored in the device tree are encoded in Big Endian and should be byte swapped when running in Little Endian. The RPA hotplug module should convert those values as well. Note that in rpaphp_get_drc_props(), the comparison between indexes[i+1] and *index is done using the BE values (whatever is the current endianess). This doesn't matter since we are checking for equality here. This way only the returned value is byte swapped. RPA also made RTAS calls which implies BE values to be used. According to the patch done in RTAS (http://patchwork.ozlabs.org/patch/336865), no additional conversion is required in RPA. Signed-off-by: Laurent Dufour Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/rpaphp_core.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c index 4796c15fba9..984d708552f 100644 --- a/drivers/pci/hotplug/rpaphp_core.c +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -223,16 +223,16 @@ int rpaphp_get_drc_props(struct device_node *dn, int *drc_index, type_tmp = (char *) &types[1]; /* Iterate through parent properties, looking for my-drc-index */ - for (i = 0; i < indexes[0]; i++) { + for (i = 0; i < be32_to_cpu(indexes[0]); i++) { if ((unsigned int) indexes[i + 1] == *my_index) { if (drc_name) *drc_name = name_tmp; if (drc_type) *drc_type = type_tmp; if (drc_index) - *drc_index = *my_index; + *drc_index = be32_to_cpu(*my_index); if (drc_power_domain) - *drc_power_domain = domains[i+1]; + *drc_power_domain = be32_to_cpu(domains[i+1]); return 0; } name_tmp += (strlen(name_tmp) + 1); @@ -321,16 +321,19 @@ int rpaphp_add_slot(struct device_node *dn) /* register PCI devices */ name = (char *) &names[1]; type = (char *) &types[1]; - for (i = 0; i < indexes[0]; i++) { + for (i = 0; i < be32_to_cpu(indexes[0]); i++) { + int index; - slot = alloc_slot_struct(dn, indexes[i + 1], name, power_domains[i + 1]); + index = be32_to_cpu(indexes[i + 1]); + slot = alloc_slot_struct(dn, index, name, + be32_to_cpu(power_domains[i + 1])); if (!slot) return -ENOMEM; slot->type = simple_strtoul(type, NULL, 10); dbg("Found drc-index:0x%x drc-name:%s drc-type:%s\n", - indexes[i + 1], name, type); + index, name, type); retval = rpaphp_enable_slot(slot); if (!retval) -- cgit v1.2.3-70-g09d2 From 7c82126a94e69bbbac586f0249e7ef11e681246c Mon Sep 17 00:00:00 2001 From: Thomas Jarosch Date: Mon, 7 Apr 2014 15:10:32 +0200 Subject: PCI: Add new ID for Intel GPU "spurious interrupt" quirk After a CPU upgrade while keeping the same mainboard, we faced "spurious interrupt" problems again. It turned out that the new CPU also featured a new GPU with a different PCI ID. Add this PCI ID to the quirk table. Probably all other Intel GPU PCI IDs are affected, too, but I don't want to add them without a test system. See f67fd55fa96f ("PCI: Add quirk for still enabled interrupts on Intel Sandy Bridge GPUs") for some history. [bhelgaas: add f67fd55fa96f reference, stable tag] Signed-off-by: Thomas Jarosch Signed-off-by: Bjorn Helgaas CC: stable@vger.kernel.org # v3.4+ --- drivers/pci/quirks.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pci') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index e7292065a1b..0feb4a32a94 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2954,6 +2954,7 @@ static void disable_igfx_irq(struct pci_dev *dev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0102, disable_igfx_irq); DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x010a, disable_igfx_irq); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x0152, disable_igfx_irq); /* * PCI devices which are on Intel chips can skip the 10ms delay -- cgit v1.2.3-70-g09d2 From 8895d3bcb8ba960b1b83f95d772b641352ea8e51 Mon Sep 17 00:00:00 2001 From: Bandan Das Date: Tue, 1 Apr 2014 21:32:59 -0400 Subject: PCI: Fail new_id for vendor/device values already built into driver While using the sysfs new_id interface, the user can unintentionally feed incorrect values if the driver static table has a matching entry. This is possible since only the device and vendor fields are mandatory and the rest are optional. As a result, store_new_id() will fill in default values that are then passed on to the driver and can have unintended consequences. As an example, consider the ixgbe driver and the 82599EB network card: echo "8086 10fb" > /sys/bus/pci/drivers/ixgbe/new_id This will pass a pci_device_id with driver_data = 0 to ixgbe_probe(), which uses that zero to index a table of card operations. The zeroth entry of the table does *not* correspond to the 82599 operations. This change returns an error if the user attempts to add a dynid for a vendor/device combination for which a static entry already exists. However, if the user intentionally wants a different set of values, she must provide all the 7 fields and that will be accepted. [bhelgaas: drop KVM text since the problem isn't KVM-specific] Signed-off-by: Bandan Das Signed-off-by: Bjorn Helgaas Reviewed-by: Alex Williamson --- drivers/pci/pci-driver.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index d911e0c1f35..650ce8935fd 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -107,7 +107,7 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) subdevice=PCI_ANY_ID, class=0, class_mask=0; unsigned long driver_data=0; int fields=0; - int retval; + int retval = 0; fields = sscanf(buf, "%x %x %x %x %x %x %lx", &vendor, &device, &subvendor, &subdevice, @@ -115,6 +115,26 @@ store_new_id(struct device_driver *driver, const char *buf, size_t count) if (fields < 2) return -EINVAL; + if (fields != 7) { + struct pci_dev *pdev = kzalloc(sizeof(*pdev), GFP_KERNEL); + if (!pdev) + return -ENOMEM; + + pdev->vendor = vendor; + pdev->device = device; + pdev->subsystem_vendor = subvendor; + pdev->subsystem_device = subdevice; + pdev->class = class; + + if (pci_match_id(pdrv->id_table, pdev)) + retval = -EEXIST; + + kfree(pdev); + + if (retval) + return retval; + } + /* Only accept driver_data values that match an existing id_table entry */ if (ids) { -- cgit v1.2.3-70-g09d2 From 10874f5a00266343a06e95da680e8a5a383d9a80 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Apr 2014 16:11:40 -0600 Subject: PCI: Remove unnecessary __ref annotations Some PCI functions used to be marked __devinit. When CONFIG_HOTPLUG was not set, these functions were discarded after boot. A few callers of these __devinit functions were marked __ref to indicate that they could safely call the __devinit functions even though the callers were not __devinit. But CONFIG_HOTPLUG and __devinit are now gone, and the need for the __ref annotations is also gone, so remove them. Relevant historical commits: 54b956b90360 Remove __dev* markings from init.h a8e4b9c101ae PCI: add generic pci_hp_add_bridge() 0ab2b57f8db8 PCI: fix section mismatch warning in pci_scan_child_bus 451124a7cc6c PCI: fix 4x section mismatch warnings Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug-pci.c | 2 +- drivers/pci/hotplug/acpiphp_glue.c | 2 +- drivers/pci/hotplug/cpci_hotplug_pci.c | 2 +- drivers/pci/hotplug/shpchp_pci.c | 2 +- drivers/pci/pci.h | 8 ++++---- drivers/pci/probe.c | 8 ++++---- drivers/pci/setup-bus.c | 25 ++++++++++++------------- 7 files changed, 24 insertions(+), 25 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug-pci.c b/drivers/pci/hotplug-pci.c index 6258dc260d9..c68366cee6b 100644 --- a/drivers/pci/hotplug-pci.c +++ b/drivers/pci/hotplug-pci.c @@ -4,7 +4,7 @@ #include #include "pci.h" -int __ref pci_hp_add_bridge(struct pci_dev *dev) +int pci_hp_add_bridge(struct pci_dev *dev) { struct pci_bus *parent = dev->bus; int pass, busnr, start = parent->busn_res.start; diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 0238a02b4cb..bb945e33b1e 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -500,7 +500,7 @@ static int acpiphp_rescan_slot(struct acpiphp_slot *slot) * This function should be called per *physical slot*, * not per each slot object in ACPI namespace. */ -static void __ref enable_slot(struct acpiphp_slot *slot) +static void enable_slot(struct acpiphp_slot *slot) { struct pci_dev *dev; struct pci_bus *bus = slot->bus; diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c index 8c146485176..b238a1a2837 100644 --- a/drivers/pci/hotplug/cpci_hotplug_pci.c +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -250,7 +250,7 @@ int cpci_led_off(struct slot* slot) * Device configuration functions */ -int __ref cpci_configure_slot(struct slot *slot) +int cpci_configure_slot(struct slot *slot) { struct pci_dev *dev; struct pci_bus *parent; diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c index 2bf69fe1926..18209ebc097 100644 --- a/drivers/pci/hotplug/shpchp_pci.c +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -34,7 +34,7 @@ #include "../pci.h" #include "shpchp.h" -int __ref shpchp_configure_device(struct slot *p_slot) +int shpchp_configure_device(struct slot *p_slot) { struct pci_dev *dev; struct controller *ctrl = p_slot->ctrl; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 6bd082299e3..fe233a3099c 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -201,11 +201,11 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, struct resource *res, unsigned int reg); int pci_resource_bar(struct pci_dev *dev, int resno, enum pci_bar_type *type); void pci_configure_ari(struct pci_dev *dev); -void __ref __pci_bus_size_bridges(struct pci_bus *bus, +void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head); -void __ref __pci_bus_assign_resources(const struct pci_bus *bus, - struct list_head *realloc_head, - struct list_head *fail_head); +void __pci_bus_assign_resources(const struct pci_bus *bus, + struct list_head *realloc_head, + struct list_head *fail_head); /** * pci_ari_enabled - query ARI forwarding status diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ef09f5f2fe6..fe89a982a3d 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -719,7 +719,7 @@ add_dev: return child; } -struct pci_bus *__ref pci_add_new_bus(struct pci_bus *parent, struct pci_dev *dev, int busnr) +struct pci_bus *pci_add_new_bus(struct pci_bus *parent, struct pci_dev *dev, int busnr) { struct pci_bus *child; @@ -1369,7 +1369,7 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) WARN_ON(ret < 0); } -struct pci_dev *__ref pci_scan_single_device(struct pci_bus *bus, int devfn) +struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn) { struct pci_dev *dev; @@ -1958,7 +1958,7 @@ EXPORT_SYMBOL(pci_scan_bus); * * Returns the max number of subordinate bus discovered. */ -unsigned int __ref pci_rescan_bus_bridge_resize(struct pci_dev *bridge) +unsigned int pci_rescan_bus_bridge_resize(struct pci_dev *bridge) { unsigned int max; struct pci_bus *bus = bridge->subordinate; @@ -1981,7 +1981,7 @@ unsigned int __ref pci_rescan_bus_bridge_resize(struct pci_dev *bridge) * * Returns the max number of subordinate bus discovered. */ -unsigned int __ref pci_rescan_bus(struct pci_bus *bus) +unsigned int pci_rescan_bus(struct pci_bus *bus) { unsigned int max; diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 138bdd6393b..d219d44709b 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1113,8 +1113,7 @@ handle_done: ; } -void __ref __pci_bus_size_bridges(struct pci_bus *bus, - struct list_head *realloc_head) +void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head) { struct pci_dev *dev; unsigned long mask, prefmask; @@ -1178,15 +1177,15 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, } } -void __ref pci_bus_size_bridges(struct pci_bus *bus) +void pci_bus_size_bridges(struct pci_bus *bus) { __pci_bus_size_bridges(bus, NULL); } EXPORT_SYMBOL(pci_bus_size_bridges); -void __ref __pci_bus_assign_resources(const struct pci_bus *bus, - struct list_head *realloc_head, - struct list_head *fail_head) +void __pci_bus_assign_resources(const struct pci_bus *bus, + struct list_head *realloc_head, + struct list_head *fail_head) { struct pci_bus *b; struct pci_dev *dev; @@ -1218,15 +1217,15 @@ void __ref __pci_bus_assign_resources(const struct pci_bus *bus, } } -void __ref pci_bus_assign_resources(const struct pci_bus *bus) +void pci_bus_assign_resources(const struct pci_bus *bus) { __pci_bus_assign_resources(bus, NULL, NULL); } EXPORT_SYMBOL(pci_bus_assign_resources); -static void __ref __pci_bridge_assign_resources(const struct pci_dev *bridge, - struct list_head *add_head, - struct list_head *fail_head) +static void __pci_bridge_assign_resources(const struct pci_dev *bridge, + struct list_head *add_head, + struct list_head *fail_head) { struct pci_bus *b; @@ -1304,9 +1303,9 @@ enum release_type { * try to release pci bridge resources that is from leaf bridge, * so we can allocate big new one later */ -static void __ref pci_bus_release_bridge_resources(struct pci_bus *bus, - unsigned long type, - enum release_type rel_type) +static void pci_bus_release_bridge_resources(struct pci_bus *bus, + unsigned long type, + enum release_type rel_type) { struct pci_dev *dev; bool is_leaf_bridge = true; -- cgit v1.2.3-70-g09d2 From efdd4070f38e962d69c11f23c5aa033121a8cf0f Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Sat, 5 Apr 2014 15:08:22 -0600 Subject: PCI: Remove dead code "pdev" can never be NULL here, so remove the test. Found by Coverity (CID 744313). Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-driver.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 650ce8935fd..ff236ed4f5c 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -1345,8 +1345,6 @@ static int pci_uevent(struct device *dev, struct kobj_uevent_env *env) return -ENODEV; pdev = to_pci_dev(dev); - if (!pdev) - return -ENODEV; if (add_uevent_var(env, "PCI_CLASS=%04X", pdev->class)) return -ENOMEM; @@ -1367,6 +1365,7 @@ static int pci_uevent(struct device *dev, struct kobj_uevent_env *env) (u8)(pdev->class >> 16), (u8)(pdev->class >> 8), (u8)(pdev->class))) return -ENOMEM; + return 0; } -- cgit v1.2.3-70-g09d2 From 1e358f94c00570f88a590cabe718daf835440cc9 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 29 Apr 2014 12:51:55 -0600 Subject: PCI: Fix use of uninitialized MPS value If "pcie_bus_config == PCIE_BUS_PERFORMANCE", we don't initialize "smpss", so we pass a pointer to garbage into pcie_bus_configure_set(), where we compute "mps" based on the garbage. We then pass the garbage "mps" to pcie_write_mps(), which ignores it in the PCIE_BUS_PERFORMANCE case. Coverity isn't smart enough to deduce that we ignore the garbage (it's a lot to expect from a human, too), so initialize "smpss" to a safe value in all cases. Found by Coverity (CID 146454). Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index fe89a982a3d..490031fd210 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1617,7 +1617,7 @@ static int pcie_bus_configure_set(struct pci_dev *dev, void *data) */ void pcie_bus_configure_settings(struct pci_bus *bus) { - u8 smpss; + u8 smpss = 0; if (!bus->self) return; -- cgit v1.2.3-70-g09d2 From dfc73e7acd9925b434a355eeeed86d44cb435f9c Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Thu, 17 Apr 2014 19:46:15 +0200 Subject: PCI: Move Open Firmware devspec attribute to PCI common code Move the devspec OF attribute to PCI common code's set of device attributes since it's not architecture dependent. As a side effect microblaze and powerpc no longer need to use pcibios_add_platform_entries(). [bhelgaas: fold in #include for compile error] Link: https://lkml.kernel.org/r/alpine.LFD.2.11.1404141101500.1529@denkbrett Signed-off-by: Sebastian Ott Signed-off-by: Bjorn Helgaas Acked-by: Benjamin Herrenschmidt --- arch/microblaze/pci/pci-common.c | 20 -------------------- arch/powerpc/kernel/pci-common.c | 20 -------------------- drivers/pci/pci-sysfs.c | 18 ++++++++++++++++++ 3 files changed, 18 insertions(+), 40 deletions(-) (limited to 'drivers/pci') diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c index 70996cc66aa..a59de1bc1ce 100644 --- a/arch/microblaze/pci/pci-common.c +++ b/arch/microblaze/pci/pci-common.c @@ -168,26 +168,6 @@ struct pci_controller *pci_find_hose_for_OF_device(struct device_node *node) return NULL; } -static ssize_t pci_show_devspec(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct pci_dev *pdev; - struct device_node *np; - - pdev = to_pci_dev(dev); - np = pci_device_to_OF_node(pdev); - if (np == NULL || np->full_name == NULL) - return 0; - return sprintf(buf, "%s", np->full_name); -} -static DEVICE_ATTR(devspec, S_IRUGO, pci_show_devspec, NULL); - -/* Add sysfs properties */ -int pcibios_add_platform_entries(struct pci_dev *pdev) -{ - return device_create_file(&pdev->dev, &dev_attr_devspec); -} - void pcibios_set_master(struct pci_dev *dev) { /* No special bus mastering setup handling */ diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c index d9476c1fc95..24d342e9179 100644 --- a/arch/powerpc/kernel/pci-common.c +++ b/arch/powerpc/kernel/pci-common.c @@ -201,26 +201,6 @@ struct pci_controller* pci_find_hose_for_OF_device(struct device_node* node) return NULL; } -static ssize_t pci_show_devspec(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct pci_dev *pdev; - struct device_node *np; - - pdev = to_pci_dev (dev); - np = pci_device_to_OF_node(pdev); - if (np == NULL || np->full_name == NULL) - return 0; - return sprintf(buf, "%s", np->full_name); -} -static DEVICE_ATTR(devspec, S_IRUGO, pci_show_devspec, NULL); - -/* Add sysfs properties */ -int pcibios_add_platform_entries(struct pci_dev *pdev) -{ - return device_create_file(&pdev->dev, &dev_attr_devspec); -} - /* * Reads the interrupt pin to determine if interrupt is use by card. * If the interrupt is used, then gets the interrupt line from the diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 4e0acefb756..3db1c7ff5dd 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "pci.h" static int sysfs_initialized; /* = 0 */ @@ -416,6 +417,20 @@ static ssize_t d3cold_allowed_show(struct device *dev, static DEVICE_ATTR_RW(d3cold_allowed); #endif +#ifdef CONFIG_OF +static ssize_t devspec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct device_node *np = pci_device_to_OF_node(pdev); + + if (np == NULL || np->full_name == NULL) + return 0; + return sprintf(buf, "%s", np->full_name); +} +static DEVICE_ATTR_RO(devspec); +#endif + #ifdef CONFIG_PCI_IOV static ssize_t sriov_totalvfs_show(struct device *dev, struct device_attribute *attr, @@ -520,6 +535,9 @@ static struct attribute *pci_dev_attrs[] = { &dev_attr_msi_bus.attr, #if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI) &dev_attr_d3cold_allowed.attr, +#endif +#ifdef CONFIG_OF + &dev_attr_devspec.attr, #endif NULL, }; -- cgit v1.2.3-70-g09d2 From 034cd97ebda4062eb4402a6cf963ccd262caa86a Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Mon, 14 Apr 2014 15:28:35 +0200 Subject: PCI/MSI: Remove pci_enable_msi_block() There are no users of pci_enable_msi_block() function left. Obsolete it in favor of pci_enable_msi_range() and pci_enable_msi_exact() functions. Previously, we called arch_setup_msi_irqs() once, requesting the same vector count we passed to arch_msi_check_device(). Now we may call it several times: if it returns failure, we may retry and request fewer vectors. We don't keep track of the vector count we initially passed to arch_msi_check_device(). We only keep track of the number of vectors successfully set up by arch_setup_msi_irqs(), and this is what we use to clean things up when disabling MSI. Therefore, we assume that arch_msi_check_device() does nothing that will have to be cleaned up later. [bhelgaas: changelog] Signed-off-by: Alexander Gordeev Signed-off-by: Bjorn Helgaas --- drivers/pci/msi.c | 79 ++++++++++++++++++++++------------------------------- include/linux/pci.h | 5 +--- 2 files changed, 34 insertions(+), 50 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/msi.c b/drivers/pci/msi.c index 04130c3f9cf..36dd0caa175 100644 --- a/drivers/pci/msi.c +++ b/drivers/pci/msi.c @@ -879,50 +879,6 @@ int pci_msi_vec_count(struct pci_dev *dev) } EXPORT_SYMBOL(pci_msi_vec_count); -/** - * pci_enable_msi_block - configure device's MSI capability structure - * @dev: device to configure - * @nvec: number of interrupts to configure - * - * Allocate IRQs for a device with the MSI capability. - * This function returns a negative errno if an error occurs. If it - * is unable to allocate the number of interrupts requested, it returns - * the number of interrupts it might be able to allocate. If it successfully - * allocates at least the number of interrupts requested, it returns 0 and - * updates the @dev's irq member to the lowest new interrupt number; the - * other interrupt numbers allocated to this device are consecutive. - */ -int pci_enable_msi_block(struct pci_dev *dev, int nvec) -{ - int status, maxvec; - - if (dev->current_state != PCI_D0) - return -EINVAL; - - maxvec = pci_msi_vec_count(dev); - if (maxvec < 0) - return maxvec; - if (nvec > maxvec) - return maxvec; - - status = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSI); - if (status) - return status; - - WARN_ON(!!dev->msi_enabled); - - /* Check whether driver already requested MSI-X irqs */ - if (dev->msix_enabled) { - dev_info(&dev->dev, "can't enable MSI " - "(MSI-X already enabled)\n"); - return -EINVAL; - } - - status = msi_capability_init(dev, nvec); - return status; -} -EXPORT_SYMBOL(pci_enable_msi_block); - void pci_msi_shutdown(struct pci_dev *dev) { struct msi_desc *desc; @@ -1128,14 +1084,45 @@ void pci_msi_init_pci_dev(struct pci_dev *dev) **/ int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec) { - int nvec = maxvec; + int nvec; int rc; + if (dev->current_state != PCI_D0) + return -EINVAL; + + WARN_ON(!!dev->msi_enabled); + + /* Check whether driver already requested MSI-X irqs */ + if (dev->msix_enabled) { + dev_info(&dev->dev, + "can't enable MSI (MSI-X already enabled)\n"); + return -EINVAL; + } + if (maxvec < minvec) return -ERANGE; + nvec = pci_msi_vec_count(dev); + if (nvec < 0) + return nvec; + else if (nvec < minvec) + return -EINVAL; + else if (nvec > maxvec) + nvec = maxvec; + + do { + rc = pci_msi_check_device(dev, nvec, PCI_CAP_ID_MSI); + if (rc < 0) { + return rc; + } else if (rc > 0) { + if (rc < minvec) + return -ENOSPC; + nvec = rc; + } + } while (rc); + do { - rc = pci_enable_msi_block(dev, nvec); + rc = msi_capability_init(dev, nvec); if (rc < 0) { return rc; } else if (rc > 0) { diff --git a/include/linux/pci.h b/include/linux/pci.h index aab57b4abe7..499755e6dab 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1158,7 +1158,6 @@ struct msix_entry { #ifdef CONFIG_PCI_MSI int pci_msi_vec_count(struct pci_dev *dev); -int pci_enable_msi_block(struct pci_dev *dev, int nvec); void pci_msi_shutdown(struct pci_dev *dev); void pci_disable_msi(struct pci_dev *dev); int pci_msix_vec_count(struct pci_dev *dev); @@ -1188,8 +1187,6 @@ static inline int pci_enable_msix_exact(struct pci_dev *dev, } #else static inline int pci_msi_vec_count(struct pci_dev *dev) { return -ENOSYS; } -static inline int pci_enable_msi_block(struct pci_dev *dev, int nvec) -{ return -ENOSYS; } static inline void pci_msi_shutdown(struct pci_dev *dev) { } static inline void pci_disable_msi(struct pci_dev *dev) { } static inline int pci_msix_vec_count(struct pci_dev *dev) { return -ENOSYS; } @@ -1244,7 +1241,7 @@ static inline void pcie_set_ecrc_checking(struct pci_dev *dev) { } static inline void pcie_ecrc_get_policy(char *str) { } #endif -#define pci_enable_msi(pdev) pci_enable_msi_block(pdev, 1) +#define pci_enable_msi(pdev) pci_enable_msi_exact(pdev, 1) #ifdef CONFIG_HT_IRQ /* The functions a driver should call */ -- cgit v1.2.3-70-g09d2 From 3cb30b73ad71b384c6289243d4ccd31ab90bce6f Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Thu, 1 May 2014 14:36:31 -0600 Subject: PCI: Mark RTL8110SC INTx masking as broken INTx masking does not work on this device. To see this, configure the network device UP on an active network, note that the interrupt count continues to increment for the device in /proc/interrupts. Use setpci to set the PCI_COMMAND_INTX_DISABLE bit in the PCI_COMMAND register. As expected, the interrupt count ceases to increment. However, reading the PCI_STATUS_INTERRUPT bit of the PCI_STATUS register does not indicate that interrupts are pending and clearing PCI_COMMAND_INTX_DISABLE in the PCI_COMMAND register does not allow the device to continue operation. This does not affect operation of the host r8169 driver, but it does prevent the device from being functional when assigned to a VM, such as with QEMU and VFIO. The guest driver successfully probes the device, but there is no traffic. Mark INTx masking as broken, allowing the more restrictive APIC masking to be used instead. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 5e4ac635368..72faccfb107 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2991,6 +2991,14 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x0030, quirk_broken_intx_masking); DECLARE_PCI_FIXUP_HEADER(0x1814, 0x0601, /* Ralink RT2800 802.11n PCI */ quirk_broken_intx_masking); +/* + * Realtek RTL8169 PCI Gigabit Ethernet Controller (rev 10) + * Subsystem: Realtek RTL8169/8110 Family PCI Gigabit Ethernet NIC + * + * RTL8110SC - Fails under PCI device assignment using DisINTx masking. + */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_REALTEK, 0x8169, + quirk_broken_intx_masking); static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, struct pci_fixup *end) -- cgit v1.2.3-70-g09d2 From 9edbcd2252b5ef148177c9f2c11a56469cf5db52 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Thu, 17 Apr 2014 19:48:07 +0200 Subject: PCI: Remove pcibios_add_platform_entries() Remove pcibios_add_platform_entries(). Architecture-specific attributes can be achieved by setting pdev->dev.groups. Link: https://lkml.kernel.org/r/alpine.LFD.2.11.1404141101500.1529@denkbrett Signed-off-by: Sebastian Ott Signed-off-by: Bjorn Helgaas Acked-by: Greg Kroah-Hartman --- drivers/pci/pci-sysfs.c | 10 ---------- include/linux/pci.h | 1 - 2 files changed, 11 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 3db1c7ff5dd..b7333fa5f80 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1273,11 +1273,6 @@ static struct bin_attribute pcie_config_attr = { .write = pci_write_config, }; -int __weak pcibios_add_platform_entries(struct pci_dev *dev) -{ - return 0; -} - static ssize_t reset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -1393,11 +1388,6 @@ int __must_check pci_create_sysfs_dev_files (struct pci_dev *pdev) pdev->rom_attr = attr; } - /* add platform-specific attributes */ - retval = pcibios_add_platform_entries(pdev); - if (retval) - goto err_rom_file; - /* add sysfs entries for various capabilities */ retval = pci_create_capabilities_sysfs(pdev); if (retval) diff --git a/include/linux/pci.h b/include/linux/pci.h index a95aac7ad37..84182b153b2 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1572,7 +1572,6 @@ extern unsigned long pci_hotplug_io_size; extern unsigned long pci_hotplug_mem_size; /* Architecture-specific versions may override these (weak) */ -int pcibios_add_platform_entries(struct pci_dev *dev); void pcibios_disable_device(struct pci_dev *dev); void pcibios_set_master(struct pci_dev *dev); int pcibios_set_pcie_reset_state(struct pci_dev *dev, -- cgit v1.2.3-70-g09d2 From 23b13bc76f359e99140baf083dc44314f4eb1b87 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Apr 2014 15:25:54 -0600 Subject: PCI: Fail safely if we can't handle BARs larger than 4GB We can only handle BARs larger than 4GB if both dma_addr_t and resource_size_t are 64 bits wide. If dma_addr_t is 32 bits, we can't represent all the bus addresses, and if resource_size_t is 32 bits, we can't represent all the CPU addresses. Previously we cleared res->flags (at "fail:") for resources that were too large. That means we think the BAR doesn't exist at all, which in turn means that we could enable the device even though we can't keep track of where the BAR is and we can't make sure it doesn't overlap something else. This preserves the type flags (MEM/IO) so we can keep from enabling the device. Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ef09f5f2fe6..c7f8b717c2e 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -171,6 +171,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, struct resource *res, unsigned int pos) { u32 l, sz, mask; + u64 l64, sz64, mask64; u16 orig_cmd; struct pci_bus_region region, inverted_region; bool bar_too_big = false, bar_disabled = false; @@ -226,9 +227,9 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, } if (res->flags & IORESOURCE_MEM_64) { - u64 l64 = l; - u64 sz64 = sz; - u64 mask64 = mask | (u64)~0 << 32; + l64 = l; + sz64 = sz; + mask64 = mask | (u64)~0 << 32; pci_read_config_dword(dev, pos + 4, &l); pci_write_config_dword(dev, pos + 4, ~0); @@ -243,9 +244,13 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, if (!sz64) goto fail; - if ((sizeof(resource_size_t) < 8) && (sz64 > 0x100000000ULL)) { + if ((sizeof(dma_addr_t) < 8 || sizeof(resource_size_t) < 8) && + sz64 > 0x100000000ULL) { + res->flags |= IORESOURCE_UNSET | IORESOURCE_DISABLED; + res->start = 0; + res->end = 0; bar_too_big = true; - goto fail; + goto out; } if ((sizeof(resource_size_t) < 8) && l) { @@ -303,7 +308,8 @@ out: pci_write_config_word(dev, PCI_COMMAND, orig_cmd); if (bar_too_big) - dev_err(&dev->dev, "reg 0x%x: can't handle 64-bit BAR\n", pos); + dev_err(&dev->dev, "reg 0x%x: can't handle BAR larger than 4GB (size %#010llx)\n", + pos, (unsigned long long) sz64); if (res->flags && !bar_disabled) dev_printk(KERN_DEBUG, &dev->dev, "reg 0x%x: %pR\n", pos, res); -- cgit v1.2.3-70-g09d2 From d1a313e4b6ccbb61c746ee10ac198970516e9afc Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 29 Apr 2014 18:33:09 -0600 Subject: PCI: Reject BAR above 4GB if dma_addr_t is too small We can only handle BARs above 4GB if dma_addr_t (not resource_size_t) is 64 bits wide. If we have a 64-bit resource_size_t and a 32-bit dma_addr_t, we can't deal with BARs above 4GB. Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c7f8b717c2e..afae3bf405f 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -253,7 +253,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, goto out; } - if ((sizeof(resource_size_t) < 8) && l) { + if ((sizeof(dma_addr_t) < 8) && l) { /* Address above 32-bit boundary; disable the BAR */ pci_write_config_dword(dev, pos, 0); pci_write_config_dword(dev, pos + 4, 0); -- cgit v1.2.3-70-g09d2 From 72dc5601fe5fec37cc1bd0efb19d99948fe7e54c Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 29 Apr 2014 18:42:49 -0600 Subject: PCI: Don't convert BAR address to resource if dma_addr_t is too small If dma_addr_t is too small to represent the BAR value, pcibios_bus_to_resource() will fail, so just remember the BAR size directly in the resource. The resource is already marked UNSET, so we know the address isn't valid anyway. Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index afae3bf405f..82cd75f6118 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -258,9 +258,10 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, pci_write_config_dword(dev, pos, 0); pci_write_config_dword(dev, pos + 4, 0); res->flags |= IORESOURCE_UNSET; - region.start = 0; - region.end = sz64; + res->start = 0; + res->end = sz64; bar_disabled = true; + goto out; } else { region.start = l64; region.end = l64 + sz64; -- cgit v1.2.3-70-g09d2 From 31e9dd2565a6e27a3e698d7e3adf929db8d6c767 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 29 Apr 2014 18:37:47 -0600 Subject: PCI: Don't set BAR to zero if dma_addr_t is too small If a BAR is above 4GB and our dma_addr_t is too small, don't clear the BAR to zero: that doesn't disable the BAR, and it makes it more likely that the BAR will conflict with things if we turn on the memory enable bit (as we will at "out:" if the device was already enabled at the handoff). We should also print the BAR info and its original size so we can follow the process when we try to assign space to it. Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 82cd75f6118..dd710b12d34 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -174,7 +174,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, u64 l64, sz64, mask64; u16 orig_cmd; struct pci_bus_region region, inverted_region; - bool bar_too_big = false, bar_disabled = false; + bool bar_too_big = false, bar_too_high = false; mask = type ? PCI_ROM_ADDRESS_MASK : ~0; @@ -254,13 +254,11 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, } if ((sizeof(dma_addr_t) < 8) && l) { - /* Address above 32-bit boundary; disable the BAR */ - pci_write_config_dword(dev, pos, 0); - pci_write_config_dword(dev, pos + 4, 0); + /* Above 32-bit boundary; try to reallocate */ res->flags |= IORESOURCE_UNSET; res->start = 0; res->end = sz64; - bar_disabled = true; + bar_too_high = true; goto out; } else { region.start = l64; @@ -311,7 +309,10 @@ out: if (bar_too_big) dev_err(&dev->dev, "reg 0x%x: can't handle BAR larger than 4GB (size %#010llx)\n", pos, (unsigned long long) sz64); - if (res->flags && !bar_disabled) + if (bar_too_high) + dev_info(&dev->dev, "reg 0x%x: can't handle BAR above 4G (bus address %#010llx)\n", + pos, (unsigned long long) l64); + if (res->flags) dev_printk(KERN_DEBUG, &dev->dev, "reg 0x%x: %pR\n", pos, res); return (res->flags & IORESOURCE_MEM_64) ? 1 : 0; -- cgit v1.2.3-70-g09d2 From 26370fc6647b63eefb85a675382d661d0fed30a1 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Apr 2014 15:26:50 -0600 Subject: PCI: Don't print anything while decoding is disabled If the console is a PCI device, and we try to print to it while its decoding is disabled, the system will hang. This particular printk hasn't caused a problem yet, but it could, so this fixes it. See also 0ff9514b579b ("PCI: Don't print anything while decoding is disabled"). Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index dd710b12d34..3bc149b848a 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -174,7 +174,7 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, u64 l64, sz64, mask64; u16 orig_cmd; struct pci_bus_region region, inverted_region; - bool bar_too_big = false, bar_too_high = false; + bool bar_too_big = false, bar_too_high = false, bar_invalid = false; mask = type ? PCI_ROM_ADDRESS_MASK : ~0; @@ -289,11 +289,10 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, * be claimed by the device. */ if (inverted_region.start != region.start) { - dev_info(&dev->dev, "reg 0x%x: initial BAR value %pa invalid; forcing reassignment\n", - pos, ®ion.start); res->flags |= IORESOURCE_UNSET; - res->end -= res->start; res->start = 0; + res->end = region.end - region.start; + bar_invalid = true; } goto out; @@ -312,6 +311,9 @@ out: if (bar_too_high) dev_info(&dev->dev, "reg 0x%x: can't handle BAR above 4G (bus address %#010llx)\n", pos, (unsigned long long) l64); + if (bar_invalid) + dev_info(&dev->dev, "reg 0x%x: initial BAR value %#010llx invalid\n", + pos, (unsigned long long) region.start); if (res->flags) dev_printk(KERN_DEBUG, &dev->dev, "reg 0x%x: %pR\n", pos, res); -- cgit v1.2.3-70-g09d2 From d739a099d0248c78d374b1b610cdb679c7bc052d Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 14 Apr 2014 16:10:54 -0600 Subject: PCI: Don't add disabled subtractive decode bus resources For a subtractive decode bridge, we previously added and printed all resources of the primary bus, even if they were not valid. In the example below, the bridge 00:1c.3 has no windows enabled, so there are no valid resources on bus 02. But since 02:00.0 is subtractive decode bridge, we add and print all those invalid resources, which don't really make sense: pci 0000:00:1c.3: PCI bridge to [bus 02-03] pci 0000:02:00.0: PCI bridge to [bus 03] (subtractive decode) pci 0000:02:00.0: bridge window [??? 0x00000000 flags 0x0] (subtractive decode) Add and print the subtractively-decoded resources only if they are valid. There's an example in the dmesg log attached to the bugzilla below (but this patch doesn't fix the bug reported there). Link: https://bugzilla.kernel.org/show_bug.cgi?id=73141 Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 3bc149b848a..96651401097 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -475,7 +475,7 @@ void pci_read_bridge_bases(struct pci_bus *child) if (dev->transparent) { pci_bus_for_each_resource(child->parent, res, i) { - if (res) { + if (res && res->flags) { pci_bus_add_resource(child, res, PCI_SUBTRACTIVE_DECODE); dev_printk(KERN_DEBUG, &dev->dev, -- cgit v1.2.3-70-g09d2 From 14c8530dbc1b7cd5020c44b391e34bdb731fd098 Mon Sep 17 00:00:00 2001 From: Alan Date: Mon, 19 May 2014 14:03:14 +0100 Subject: PCI: Support BAR sizes up to 8GB This is needed for some of the Xeon Phi type systems. [bhelgaas: added Nikhil, use ARRAY_SIZE() to connect with decl, folded in Kevin's "order < 0" fix to ARRAY_SIZE() usage] Signed-off-by: Nikhil P Rao Signed-off-by: Alan Cox Signed-off-by: Kevin Hilman Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 138bdd6393b..9b3498c9b73 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -921,7 +921,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, { struct pci_dev *dev; resource_size_t min_align, align, size, size0, size1; - resource_size_t aligns[12]; /* Alignments from 1Mb to 2Gb */ + resource_size_t aligns[14]; /* Alignments from 1Mb to 8Gb */ int order, max_order; struct resource *b_res = find_free_bus_resource(bus, type); unsigned int mem64_mask = 0; @@ -957,10 +957,17 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, continue; } #endif - /* For bridges size != alignment */ + /* + * aligns[0] is for 1MB (since bridge memory + * windows are always at least 1MB aligned), so + * keep "order" from being negative for smaller + * resources. + */ align = pci_resource_alignment(dev, r); order = __ffs(align) - 20; - if (order > 11) { + if (order < 0) + order = 0; + if (order >= ARRAY_SIZE(aligns)) { dev_warn(&dev->dev, "disabling BAR %d: %pR " "(bad alignment %#llx)\n", i, r, (unsigned long long) align); @@ -968,8 +975,6 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, continue; } size += r_size; - if (order < 0) - order = 0; /* Exclude ranges with size > align from calculation of the alignment. */ if (r_size == align) -- cgit v1.2.3-70-g09d2 From 5b28541552ef5eeffc41d6936105f38c2508e566 Mon Sep 17 00:00:00 2001 From: Yinghai Lu Date: Mon, 19 May 2014 17:01:55 -0600 Subject: PCI: Restrict 64-bit prefetchable bridge windows to 64-bit resources This patch changes the way we handle 64-bit prefetchable bridge windows to make it more likely that we can assign space to all devices. Previously we put all prefetchable resources in the prefetchable bridge window. If any of those resources was 32-bit only, we restricted the window to be below 4GB. After this patch, we only put 64-bit prefetchable resources in a 64-bit prefetchable window. We put all 32-bit prefetchable resources in the non-prefetchable window, even if there are no 64-bit prefetchable resources. With the previous approach, if there was a 32-bit prefetchable resource behind a bridge, we forced the bridge's prefetchable window below 4GB, which meant that even if there was plenty of space above 4GB available, we couldn't use it, and assignment of large 64-bit resources could fail, as in the bugzilla below. The new strategy is: 1) If the prefetchable window is 64 bits wide, we put only 64-bit prefetchable resources in it. Any 32-bit prefetchable resources go in the non-prefetchable window. 2) If the prefetchable window is 32 bits wide, we put both 32- and 64-bit prefetchable resources in it. 3) If there is no prefetchable window, all MMIO resources go in the non-prefetchable window. This reduces performance for 32-bit prefetchable resources below a bridge with a 64-bit prefetchable window. We previously assigned prefetchable space, but now we'll assign non-prefetchable space. This is the case even if there are no 64-bit prefetchable resources, or if they would all fit below 4GB. In those cases, the old strategy would work and would have better performance. [bhelgaas: write changelog, add bugzilla link, fold in mem64_mask removal] Link: https://bugzilla.kernel.org/show_bug.cgi?id=74151 Tested-by: Guo Chao Tested-by: Wei Yang Signed-off-by: Yinghai Lu Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 150 ++++++++++++++++++++++++++++++++---------------- drivers/pci/setup-res.c | 20 ++++++- 2 files changed, 117 insertions(+), 53 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 9b3498c9b73..b6585cb9bce 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -713,12 +713,11 @@ static void pci_bridge_check_ranges(struct pci_bus *bus) bus resource of a given type. Note: we intentionally skip the bus resources which have already been assigned (that is, have non-NULL parent resource). */ -static struct resource *find_free_bus_resource(struct pci_bus *bus, unsigned long type) +static struct resource *find_free_bus_resource(struct pci_bus *bus, + unsigned long type_mask, unsigned long type) { int i; struct resource *r; - unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH; pci_bus_for_each_resource(bus, r, i) { if (r == &ioport_resource || r == &iomem_resource) @@ -815,7 +814,8 @@ static void pbus_size_io(struct pci_bus *bus, resource_size_t min_size, resource_size_t add_size, struct list_head *realloc_head) { struct pci_dev *dev; - struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO); + struct resource *b_res = find_free_bus_resource(bus, IORESOURCE_IO, + IORESOURCE_IO); resource_size_t size = 0, size0 = 0, size1 = 0; resource_size_t children_add_size = 0; resource_size_t min_align, align; @@ -907,6 +907,8 @@ static inline resource_size_t calculate_mem_align(resource_size_t *aligns, * @bus : the bus * @mask: mask the resource flag, then compare it with type * @type: the type of free resource from bridge + * @type2: second match type + * @type3: third match type * @min_size : the minimum memory window that must to be allocated * @add_size : additional optional memory window * @realloc_head : track the additional memory window on this list @@ -915,16 +917,17 @@ static inline resource_size_t calculate_mem_align(resource_size_t *aligns, * guarantees that all child resources fit in this size. */ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, - unsigned long type, resource_size_t min_size, - resource_size_t add_size, - struct list_head *realloc_head) + unsigned long type, unsigned long type2, + unsigned long type3, + resource_size_t min_size, resource_size_t add_size, + struct list_head *realloc_head) { struct pci_dev *dev; resource_size_t min_align, align, size, size0, size1; resource_size_t aligns[14]; /* Alignments from 1Mb to 8Gb */ int order, max_order; - struct resource *b_res = find_free_bus_resource(bus, type); - unsigned int mem64_mask = 0; + struct resource *b_res = find_free_bus_resource(bus, + mask | IORESOURCE_PREFETCH, type); resource_size_t children_add_size = 0; if (!b_res) @@ -934,9 +937,6 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, max_order = 0; size = 0; - mem64_mask = b_res->flags & IORESOURCE_MEM_64; - b_res->flags &= ~IORESOURCE_MEM_64; - list_for_each_entry(dev, &bus->devices, bus_list) { int i; @@ -944,7 +944,9 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, struct resource *r = &dev->resource[i]; resource_size_t r_size; - if (r->parent || (r->flags & mask) != type) + if (r->parent || ((r->flags & mask) != type && + (r->flags & mask) != type2 && + (r->flags & mask) != type3)) continue; r_size = resource_size(r); #ifdef CONFIG_PCI_IOV @@ -981,7 +983,6 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, aligns[order] += align; if (order > max_order) max_order = order; - mem64_mask &= r->flags & IORESOURCE_MEM_64; if (realloc_head) children_add_size += get_res_add_size(realloc_head, r); @@ -1006,7 +1007,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, } b_res->start = min_align; b_res->end = size0 + min_align - 1; - b_res->flags |= IORESOURCE_STARTALIGN | mem64_mask; + b_res->flags |= IORESOURCE_STARTALIGN; if (size1 > size0 && realloc_head) { add_to_list(realloc_head, bus->self, b_res, size1-size0, min_align); dev_printk(KERN_DEBUG, &bus->self->dev, "bridge window " @@ -1122,8 +1123,9 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head) { struct pci_dev *dev; - unsigned long mask, prefmask; + unsigned long mask, prefmask, type2 = 0, type3 = 0; resource_size_t additional_mem_size = 0, additional_io_size = 0; + struct resource *b_res; list_for_each_entry(dev, &bus->devices, bus_list) { struct pci_bus *b = dev->subordinate; @@ -1168,15 +1170,37 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, has already been allocated by arch code, try non-prefetchable range for both types of PCI memory resources. */ + b_res = &bus->self->resource[PCI_BRIDGE_RESOURCES]; mask = IORESOURCE_MEM; prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; - if (pbus_size_mem(bus, prefmask, prefmask, + if (b_res[2].flags & IORESOURCE_MEM_64) { + prefmask |= IORESOURCE_MEM_64; + if (pbus_size_mem(bus, prefmask, prefmask, + prefmask, prefmask, realloc_head ? 0 : additional_mem_size, - additional_mem_size, realloc_head)) - mask = prefmask; /* Success, size non-prefetch only. */ - else - additional_mem_size += additional_mem_size; - pbus_size_mem(bus, mask, IORESOURCE_MEM, + additional_mem_size, realloc_head)) { + /* + * Success, with pref mmio64, + * next will size non-pref or + * non-mmio64 */ + mask = prefmask; + type2 = prefmask & ~IORESOURCE_MEM_64; + type3 = prefmask & ~IORESOURCE_PREFETCH; + } + } + if (!type2) { + prefmask &= ~IORESOURCE_MEM_64; + if (pbus_size_mem(bus, prefmask, prefmask, + prefmask, prefmask, + realloc_head ? 0 : additional_mem_size, + additional_mem_size, realloc_head)) { + /* Success, next will size non-prefetch. */ + mask = prefmask; + } else + additional_mem_size += additional_mem_size; + type2 = type3 = IORESOURCE_MEM; + } + pbus_size_mem(bus, mask, IORESOURCE_MEM, type2, type3, realloc_head ? 0 : additional_mem_size, additional_mem_size, realloc_head); break; @@ -1262,42 +1286,66 @@ static void __ref __pci_bridge_assign_resources(const struct pci_dev *bridge, static void pci_bridge_release_resources(struct pci_bus *bus, unsigned long type) { - int idx; - bool changed = false; - struct pci_dev *dev; + struct pci_dev *dev = bus->self; struct resource *r; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH; + IORESOURCE_PREFETCH | IORESOURCE_MEM_64; + unsigned old_flags = 0; + struct resource *b_res; + int idx = 1; - dev = bus->self; - for (idx = PCI_BRIDGE_RESOURCES; idx <= PCI_BRIDGE_RESOURCE_END; - idx++) { - r = &dev->resource[idx]; - if ((r->flags & type_mask) != type) - continue; - if (!r->parent) - continue; - /* - * if there are children under that, we should release them - * all - */ - release_child_resources(r); - if (!release_resource(r)) { - dev_printk(KERN_DEBUG, &dev->dev, - "resource %d %pR released\n", idx, r); - /* keep the old size */ - r->end = resource_size(r) - 1; - r->start = 0; - r->flags = 0; - changed = true; - } - } + b_res = &dev->resource[PCI_BRIDGE_RESOURCES]; + + /* + * 1. if there is io port assign fail, will release bridge + * io port. + * 2. if there is non pref mmio assign fail, release bridge + * nonpref mmio. + * 3. if there is 64bit pref mmio assign fail, and bridge pref + * is 64bit, release bridge pref mmio. + * 4. if there is pref mmio assign fail, and bridge pref is + * 32bit mmio, release bridge pref mmio + * 5. if there is pref mmio assign fail, and bridge pref is not + * assigned, release bridge nonpref mmio. + */ + if (type & IORESOURCE_IO) + idx = 0; + else if (!(type & IORESOURCE_PREFETCH)) + idx = 1; + else if ((type & IORESOURCE_MEM_64) && + (b_res[2].flags & IORESOURCE_MEM_64)) + idx = 2; + else if (!(b_res[2].flags & IORESOURCE_MEM_64) && + (b_res[2].flags & IORESOURCE_PREFETCH)) + idx = 2; + else + idx = 1; + + r = &b_res[idx]; + + if (!r->parent) + return; + + /* + * if there are children under that, we should release them + * all + */ + release_child_resources(r); + if (!release_resource(r)) { + type = old_flags = r->flags & type_mask; + dev_printk(KERN_DEBUG, &dev->dev, "resource %d %pR released\n", + PCI_BRIDGE_RESOURCES + idx, r); + /* keep the old size */ + r->end = resource_size(r) - 1; + r->start = 0; + r->flags = 0; - if (changed) { /* avoiding touch the one without PREF */ if (type & IORESOURCE_PREFETCH) type = IORESOURCE_PREFETCH; __pci_setup_bridge(bus, type); + /* for next child res under same bridge */ + r->flags = old_flags; } } @@ -1476,7 +1524,7 @@ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus) LIST_HEAD(fail_head); struct pci_dev_resource *fail_res; unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM | - IORESOURCE_PREFETCH; + IORESOURCE_PREFETCH | IORESOURCE_MEM_64; int pci_try_num = 1; enum enable_type enable_local; diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 7eed671d558..2473f091a9c 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -211,15 +211,31 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, /* First, try exact prefetching match.. */ ret = pci_bus_alloc_resource(bus, res, size, align, min, - IORESOURCE_PREFETCH, + IORESOURCE_PREFETCH | IORESOURCE_MEM_64, pcibios_align_resource, dev); - if (ret < 0 && (res->flags & IORESOURCE_PREFETCH)) { + if (ret < 0 && + (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) == + (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) { + /* + * That failed. + * + * Try 32bit pref + */ + ret = pci_bus_alloc_resource(bus, res, size, align, min, + IORESOURCE_PREFETCH, + pcibios_align_resource, dev); + } + + if (ret < 0 && + (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64))) { /* * That failed. * * But a prefetching area can handle a non-prefetching * window (it will just not perform as well). + * + * Also can put 64bit under 32bit range. (below 4g). */ ret = pci_bus_alloc_resource(bus, res, size, align, min, 0, pcibios_align_resource, dev); -- cgit v1.2.3-70-g09d2 From 30afe8d00b994416b24c63f8c5bbf1c13869ec3c Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 19 May 2014 18:28:37 -0600 Subject: PCI: Change pbus_size_mem() return values to be more conventional pbus_size_mem() previously returned 0 for failure and 1 for success. Change it to return -ENOSPC for failure and 0 for success. No functional change. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index b6585cb9bce..12ab50ffdfe 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -915,6 +915,10 @@ static inline resource_size_t calculate_mem_align(resource_size_t *aligns, * * Calculate the size of the bus and minimal alignment which * guarantees that all child resources fit in this size. + * + * Returns -ENOSPC if there's no available bus resource of the desired type. + * Otherwise, sets the bus resource start/end to indicate the required + * size, adds things to realloc_head (if supplied), and returns 0. */ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, unsigned long type, unsigned long type2, @@ -931,7 +935,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, resource_size_t children_add_size = 0; if (!b_res) - return 0; + return -ENOSPC; memset(aligns, 0, sizeof(aligns)); max_order = 0; @@ -1003,7 +1007,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, "%pR to %pR (unused)\n", b_res, &bus->busn_res); b_res->flags = 0; - return 1; + return 0; } b_res->start = min_align; b_res->end = size0 + min_align - 1; @@ -1014,7 +1018,7 @@ static int pbus_size_mem(struct pci_bus *bus, unsigned long mask, "%pR to %pR add_size %llx\n", b_res, &bus->busn_res, (unsigned long long)size1-size0); } - return 1; + return 0; } unsigned long pci_cardbus_resource_alignment(struct resource *res) @@ -1126,6 +1130,7 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, unsigned long mask, prefmask, type2 = 0, type3 = 0; resource_size_t additional_mem_size = 0, additional_io_size = 0; struct resource *b_res; + int ret; list_for_each_entry(dev, &bus->devices, bus_list) { struct pci_bus *b = dev->subordinate; @@ -1175,25 +1180,27 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; if (b_res[2].flags & IORESOURCE_MEM_64) { prefmask |= IORESOURCE_MEM_64; - if (pbus_size_mem(bus, prefmask, prefmask, + ret = pbus_size_mem(bus, prefmask, prefmask, prefmask, prefmask, realloc_head ? 0 : additional_mem_size, - additional_mem_size, realloc_head)) { - /* - * Success, with pref mmio64, - * next will size non-pref or - * non-mmio64 */ - mask = prefmask; - type2 = prefmask & ~IORESOURCE_MEM_64; - type3 = prefmask & ~IORESOURCE_PREFETCH; + additional_mem_size, realloc_head); + if (ret == 0) { + /* + * Success, with pref mmio64, + * next will size non-pref or + * non-mmio64 */ + mask = prefmask; + type2 = prefmask & ~IORESOURCE_MEM_64; + type3 = prefmask & ~IORESOURCE_PREFETCH; } } if (!type2) { prefmask &= ~IORESOURCE_MEM_64; - if (pbus_size_mem(bus, prefmask, prefmask, + ret = pbus_size_mem(bus, prefmask, prefmask, prefmask, prefmask, realloc_head ? 0 : additional_mem_size, - additional_mem_size, realloc_head)) { + additional_mem_size, realloc_head); + if (ret == 0) { /* Success, next will size non-prefetch. */ mask = prefmask; } else -- cgit v1.2.3-70-g09d2 From d3689df04445c568c8b3dfcd8db4b562e1b18cfb Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 19 May 2014 18:39:07 -0600 Subject: PCI: Simplify __pci_assign_resource() coding style If an allocation succeeds, we can return success immediately. Then we don't have to test for success in the subsequent code. No functional change. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-res.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 2473f091a9c..3bdac9dc4a8 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -213,9 +213,10 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, ret = pci_bus_alloc_resource(bus, res, size, align, min, IORESOURCE_PREFETCH | IORESOURCE_MEM_64, pcibios_align_resource, dev); + if (ret == 0) + return 0; - if (ret < 0 && - (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) == + if ((res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) == (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) { /* * That failed. @@ -225,10 +226,11 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, ret = pci_bus_alloc_resource(bus, res, size, align, min, IORESOURCE_PREFETCH, pcibios_align_resource, dev); + if (ret == 0) + return 0; } - if (ret < 0 && - (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64))) { + if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) { /* * That failed. * -- cgit v1.2.3-70-g09d2 From 67d29b5c6c40e91b124695e9250c2fd24915e24a Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 19 May 2014 18:32:18 -0600 Subject: PCI: Add resource allocation comments Add comments in the code to match the allocation strategy of 7c671426dfc3 ("PCI: Restrict 64-bit prefetchable bridge windows to 64-bit resources"). No functional change. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 58 ++++++++++++++++++++++++++++++++++++------------- drivers/pci/setup-res.c | 35 +++++++++++++++-------------- 2 files changed, 62 insertions(+), 31 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 12ab50ffdfe..455ee036495 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1164,17 +1164,16 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, additional_io_size = pci_hotplug_io_size; additional_mem_size = pci_hotplug_mem_size; } - /* - * Follow thru - */ + /* Fall through */ default: pbus_size_io(bus, realloc_head ? 0 : additional_io_size, additional_io_size, realloc_head); - /* If the bridge supports prefetchable range, size it - separately. If it doesn't, or its prefetchable window - has already been allocated by arch code, try - non-prefetchable range for both types of PCI memory - resources. */ + + /* + * If there's a 64-bit prefetchable MMIO window, compute + * the size required to put all 64-bit prefetchable + * resources in it. + */ b_res = &bus->self->resource[PCI_BRIDGE_RESOURCES]; mask = IORESOURCE_MEM; prefmask = IORESOURCE_MEM | IORESOURCE_PREFETCH; @@ -1184,29 +1183,58 @@ void __ref __pci_bus_size_bridges(struct pci_bus *bus, prefmask, prefmask, realloc_head ? 0 : additional_mem_size, additional_mem_size, realloc_head); + + /* + * If successful, all non-prefetchable resources + * and any 32-bit prefetchable resources will go in + * the non-prefetchable window. + */ if (ret == 0) { - /* - * Success, with pref mmio64, - * next will size non-pref or - * non-mmio64 */ mask = prefmask; type2 = prefmask & ~IORESOURCE_MEM_64; type3 = prefmask & ~IORESOURCE_PREFETCH; } } + + /* + * If there is no 64-bit prefetchable window, compute the + * size required to put all prefetchable resources in the + * 32-bit prefetchable window (if there is one). + */ if (!type2) { prefmask &= ~IORESOURCE_MEM_64; ret = pbus_size_mem(bus, prefmask, prefmask, prefmask, prefmask, realloc_head ? 0 : additional_mem_size, additional_mem_size, realloc_head); - if (ret == 0) { - /* Success, next will size non-prefetch. */ + + /* + * If successful, only non-prefetchable resources + * will go in the non-prefetchable window. + */ + if (ret == 0) mask = prefmask; - } else + else additional_mem_size += additional_mem_size; + type2 = type3 = IORESOURCE_MEM; } + + /* + * Compute the size required to put everything else in the + * non-prefetchable window. This includes: + * + * - all non-prefetchable resources + * - 32-bit prefetchable resources if there's a 64-bit + * prefetchable window or no prefetchable window at all + * - 64-bit prefetchable resources if there's no + * prefetchable window at all + * + * Note that the strategy in __pci_assign_resource() must + * match that used here. Specifically, we cannot put a + * 32-bit prefetchable resource in a 64-bit prefetchable + * window. + */ pbus_size_mem(bus, mask, IORESOURCE_MEM, type2, type3, realloc_head ? 0 : additional_mem_size, additional_mem_size, realloc_head); diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 3bdac9dc4a8..3da2542eb4d 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -209,20 +209,25 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; - /* First, try exact prefetching match.. */ + /* + * First, try exact prefetching match. Even if a 64-bit + * prefetchable bridge window is below 4GB, we can't put a 32-bit + * prefetchable resource in it because pbus_size_mem() assumes a + * 64-bit window will contain no 32-bit resources. If we assign + * things differently than they were sized, not everything will fit. + */ ret = pci_bus_alloc_resource(bus, res, size, align, min, IORESOURCE_PREFETCH | IORESOURCE_MEM_64, pcibios_align_resource, dev); if (ret == 0) return 0; + /* + * If the prefetchable window is only 32 bits wide, we can put + * 64-bit prefetchable resources in it. + */ if ((res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) == (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) { - /* - * That failed. - * - * Try 32bit pref - */ ret = pci_bus_alloc_resource(bus, res, size, align, min, IORESOURCE_PREFETCH, pcibios_align_resource, dev); @@ -230,18 +235,16 @@ static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, return 0; } - if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) { - /* - * That failed. - * - * But a prefetching area can handle a non-prefetching - * window (it will just not perform as well). - * - * Also can put 64bit under 32bit range. (below 4g). - */ + /* + * If we didn't find a better match, we can put any memory resource + * in a non-prefetchable window. If this resource is 32 bits and + * non-prefetchable, the first call already tried the only possibility + * so we don't need to try again. + */ + if (res->flags & (IORESOURCE_PREFETCH | IORESOURCE_MEM_64)) ret = pci_bus_alloc_resource(bus, res, size, align, min, 0, pcibios_align_resource, dev); - } + return ret; } -- cgit v1.2.3-70-g09d2 From 3ebe7f9f7e4a4fd1f6461ecd01ff2961317a483a Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 2 May 2014 10:40:42 -0600 Subject: PCI: Notify driver before and after device reset Notify a PCI device driver when its device's access is about to be disabled for an impending reset attempt, then after the attempt completes and device access is restored. The notification is via the pci_error_handlers interface. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 21 +++++++++++++++++++++ include/linux/pci.h | 3 +++ 2 files changed, 24 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 7325d43bf03..43d87b26ec5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3305,8 +3305,27 @@ static void pci_dev_unlock(struct pci_dev *dev) pci_cfg_access_unlock(dev); } +/** + * pci_reset_notify - notify device driver of reset + * @dev: device to be notified of reset + * @prepare: 'true' if device is about to be reset; 'false' if reset attempt + * completed + * + * Must be called prior to device access being disabled and after device + * access is restored. + */ +static void pci_reset_notify(struct pci_dev *dev, bool prepare) +{ + const struct pci_error_handlers *err_handler = + dev->driver ? dev->driver->err_handler : NULL; + if (err_handler && err_handler->reset_notify) + err_handler->reset_notify(dev, prepare); +} + static void pci_dev_save_and_disable(struct pci_dev *dev) { + pci_reset_notify(dev, true); + /* * Wake-up device prior to save. PM registers default to D0 after * reset and a simple register restore doesn't reliably return @@ -3328,6 +3347,7 @@ static void pci_dev_save_and_disable(struct pci_dev *dev) static void pci_dev_restore(struct pci_dev *dev) { pci_restore_state(dev); + pci_reset_notify(dev, false); } static int pci_dev_reset(struct pci_dev *dev, int probe) @@ -3344,6 +3364,7 @@ static int pci_dev_reset(struct pci_dev *dev, int probe) return rc; } + /** * __pci_reset_function - reset a PCI device function * @dev: PCI device to reset diff --git a/include/linux/pci.h b/include/linux/pci.h index aab57b4abe7..31c43093e53 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -603,6 +603,9 @@ struct pci_error_handlers { /* PCI slot has been reset */ pci_ers_result_t (*slot_reset)(struct pci_dev *dev); + /* PCI function reset prepare or completed */ + void (*reset_notify)(struct pci_dev *dev, bool prepare); + /* Device driver may resume normal operations */ void (*resume)(struct pci_dev *dev); }; -- cgit v1.2.3-70-g09d2 From 326c1cdae74186bfab3464e8f3378c7fa324be12 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:36 +0800 Subject: PCI: Rename pci_is_bridge() to pci_has_subordinate() Previously, pci_is_bridge() returned true only when a subordinate bus existed. Rename pci_is_bridge() to pci_has_subordinate() to better indicate what we're checking. No functional change. [bhelgaas: changelog] Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-driver.c | 8 ++++---- drivers/pci/pci.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index d911e0c1f35..b7850cb4b90 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -580,14 +580,14 @@ static void pci_pm_default_resume(struct pci_dev *pci_dev) { pci_fixup_device(pci_fixup_resume, pci_dev); - if (!pci_is_bridge(pci_dev)) + if (!pci_has_subordinate(pci_dev)) pci_enable_wake(pci_dev, PCI_D0, false); } static void pci_pm_default_suspend(struct pci_dev *pci_dev) { /* Disable non-bridge devices without PM support */ - if (!pci_is_bridge(pci_dev)) + if (!pci_has_subordinate(pci_dev)) pci_disable_enabled_device(pci_dev); } @@ -717,7 +717,7 @@ static int pci_pm_suspend_noirq(struct device *dev) if (!pci_dev->state_saved) { pci_save_state(pci_dev); - if (!pci_is_bridge(pci_dev)) + if (!pci_has_subordinate(pci_dev)) pci_prepare_to_sleep(pci_dev); } @@ -971,7 +971,7 @@ static int pci_pm_poweroff_noirq(struct device *dev) return error; } - if (!pci_dev->state_saved && !pci_is_bridge(pci_dev)) + if (!pci_dev->state_saved && !pci_has_subordinate(pci_dev)) pci_prepare_to_sleep(pci_dev); /* diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 6bd082299e3..65108fc6f11 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -77,7 +77,7 @@ static inline void pci_wakeup_event(struct pci_dev *dev) pm_wakeup_event(&dev->dev, 100); } -static inline bool pci_is_bridge(struct pci_dev *pci_dev) +static inline bool pci_has_subordinate(struct pci_dev *pci_dev) { return !!(pci_dev->subordinate); } -- cgit v1.2.3-70-g09d2 From 6788a51fe3391817c8ded5f43dd4c57f3d212c17 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:38 +0800 Subject: PCI: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 8 +------- drivers/pci/probe.c | 3 +-- drivers/pci/setup-bus.c | 4 +--- 3 files changed, 3 insertions(+), 12 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index f49abef8848..ca4927ba843 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -309,13 +309,7 @@ static struct acpi_device *acpi_pci_find_companion(struct device *dev) bool check_children; u64 addr; - /* - * pci_is_bridge() is not suitable here, because pci_dev->subordinate - * is set only after acpi_pci_find_device() has been called for the - * given device. - */ - check_children = pci_dev->hdr_type == PCI_HEADER_TYPE_BRIDGE - || pci_dev->hdr_type == PCI_HEADER_TYPE_CARDBUS; + check_children = pci_is_bridge(pci_dev); /* Please ref to ACPI spec for the syntax of _ADR */ addr = (PCI_SLOT(pci_dev->devfn) << 16) | PCI_FUNC(pci_dev->devfn); return acpi_find_child_device(ACPI_COMPANION(dev->parent), addr, diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ef09f5f2fe6..f831dd80fa2 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1670,8 +1670,7 @@ unsigned int pci_scan_child_bus(struct pci_bus *bus) for (pass=0; pass < 2; pass++) list_for_each_entry(dev, &bus->devices, bus_list) { - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + if (pci_is_bridge(dev)) max = pci_scan_bridge(bus, dev, max, pass); } diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 138bdd6393b..e399d00ed5f 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1629,9 +1629,7 @@ void pci_assign_unassigned_bus_resources(struct pci_bus *bus) down_read(&pci_bus_sem); list_for_each_entry(dev, &bus->devices, bus_list) - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) - if (dev->subordinate) + if (pci_is_bridge(dev) && pci_has_subordinate(dev)) __pci_bus_size_bridges(dev->subordinate, &add_list); up_read(&pci_bus_sem); -- cgit v1.2.3-70-g09d2 From f86e1f152e949b24ef336c640009183c01cfd9bf Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:43 +0800 Subject: PCI: rpaphp: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas Acked-by: Benjamin Herrenschmidt --- drivers/pci/hotplug/rpadlpar_core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c index 4fcdeedda31..7660232ef46 100644 --- a/drivers/pci/hotplug/rpadlpar_core.c +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -157,8 +157,7 @@ static void dlpar_pci_add_bus(struct device_node *dn) } /* Scan below the new bridge */ - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + if (pci_is_bridge(dev)) of_scan_pci_bridge(dev); /* Map IO space for child bus, which may or may not succeed */ -- cgit v1.2.3-70-g09d2 From 087cfa93e0fb5f06c0e976e6f468ffedc83b3223 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:44 +0800 Subject: PCI: shpchp: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/shpchp_pci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c index 2bf69fe1926..ea8ad313275 100644 --- a/drivers/pci/hotplug/shpchp_pci.c +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -64,8 +64,7 @@ int __ref shpchp_configure_device(struct slot *p_slot) list_for_each_entry(dev, &parent->devices, bus_list) { if (PCI_SLOT(dev->devfn) != p_slot->device) continue; - if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || - (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) + if (pci_is_bridge(dev)) pci_hp_add_bridge(dev); } -- cgit v1.2.3-70-g09d2 From 5cbe5d15aefcd57038a90aa8db4787bc84968941 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:45 +0800 Subject: PCI: cpcihp: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/cpci_hotplug_pci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c index 8c146485176..98433716978 100644 --- a/drivers/pci/hotplug/cpci_hotplug_pci.c +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -289,8 +289,7 @@ int __ref cpci_configure_slot(struct slot *slot) list_for_each_entry(dev, &parent->devices, bus_list) if (PCI_SLOT(dev->devfn) != PCI_SLOT(slot->devfn)) continue; - if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || - (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) + if (pci_is_bridge(dev)) pci_hp_add_bridge(dev); -- cgit v1.2.3-70-g09d2 From c7a071f6a9f02c9fd2d0aa81d000c814a7adb914 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:46 +0800 Subject: PCI: acpiphp: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/acpiphp_glue.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index bccc27ee103..f1f9bd10103 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -516,8 +516,7 @@ static void __ref enable_slot(struct acpiphp_slot *slot) if (PCI_SLOT(dev->devfn) != slot->device) continue; - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) { + if (pci_is_bridge(dev)) { max = pci_scan_bridge(bus, dev, max, pass); if (pass && dev->subordinate) { check_hotplug_bridge(slot, dev); -- cgit v1.2.3-70-g09d2 From 930067e235f23c98e444615a03e1f01148cc6d71 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Sun, 4 May 2014 12:23:48 +0800 Subject: PCI: pciehp: Use pci_is_bridge() to simplify code Use pci_is_bridge() to simplify code. No functional change. Requires: 326c1cdae741 PCI: Rename pci_is_bridge() to pci_has_subordinate() Requires: 1c86438c9423 PCI: Add new pci_is_bridge() interface Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_pci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c index 1b533060ce6..b6cb1df6709 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -62,8 +62,7 @@ int pciehp_configure_device(struct slot *p_slot) } list_for_each_entry(dev, &parent->devices, bus_list) - if ((dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) || - (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)) + if (pci_is_bridge(dev)) pci_hp_add_bridge(dev); pci_assign_unassigned_bridge_resources(bridge); -- cgit v1.2.3-70-g09d2 From 78916b00f0096059c872f537306b1a464c84fb30 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 5 May 2014 14:20:51 -0600 Subject: PCI: Test for std config alias when testing extended config space When a PCI-to-PCIe bridge is stacked on a PCIe-to-PCI bridge, we can have PCIe endpoints masked by a conventional PCI bus. This makes the extended config space of the PCIe endpoint inaccessible. The PCIe-to-PCI bridge is supposed to handle any type 1 configuration transactions where the extended config offset bits are non-zero as an Unsupported Request rather than forward it to the secondary interface. As noted here, there are a couple known offenders to this rule. These bridges drop the extended offset bits, resulting in the conventional config space being aliased many times across the extended config space. For Intel NICs, this alias often seems to expose a bogus SR-IOV cap. Stacking bridges may seem like an uncommon scenario, but note that any conventional PCI slot in a modern PC is already the secondary interface of an onboard PCIe-to-PCI bridge. The user need only add a PCI-to-PCIe adapter and PCIe device to encounter this problem. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 490031fd210..b47c2dd5b9e 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -983,6 +983,43 @@ void set_pcie_hotplug_bridge(struct pci_dev *pdev) } +/** + * pci_ext_cfg_is_aliased - is ext config space just an alias of std config? + * @dev: PCI device + * + * PCI Express to PCI/PCI-X Bridge Specification, rev 1.0, 4.1.4 says that + * when forwarding a type1 configuration request the bridge must check that + * the extended register address field is zero. The bridge is not permitted + * to forward the transactions and must handle it as an Unsupported Request. + * Some bridges do not follow this rule and simply drop the extended register + * bits, resulting in the standard config space being aliased, every 256 + * bytes across the entire configuration space. Test for this condition by + * comparing the first dword of each potential alias to the vendor/device ID. + * Known offenders: + * ASM1083/1085 PCIe-to-PCI Reversible Bridge (1b21:1080, rev 01 & 03) + * AMD/ATI SBx00 PCI to PCI Bridge (1002:4384, rev 40) + */ +static bool pci_ext_cfg_is_aliased(struct pci_dev *dev) +{ +#ifdef CONFIG_PCI_QUIRKS + int pos; + u32 header, tmp; + + pci_read_config_dword(dev, PCI_VENDOR_ID, &header); + + for (pos = PCI_CFG_SPACE_SIZE; + pos < PCI_CFG_SPACE_EXP_SIZE; pos += PCI_CFG_SPACE_SIZE) { + if (pci_read_config_dword(dev, pos, &tmp) != PCIBIOS_SUCCESSFUL + || header != tmp) + return false; + } + + return true; +#else + return false; +#endif +} + /** * pci_cfg_space_size - get the configuration space size of the PCI device. * @dev: PCI device @@ -1001,7 +1038,7 @@ static int pci_cfg_space_size_ext(struct pci_dev *dev) if (pci_read_config_dword(dev, pos, &status) != PCIBIOS_SUCCESSFUL) goto fail; - if (status == 0xffffffff) + if (status == 0xffffffff || pci_ext_cfg_is_aliased(dev)) goto fail; return PCI_CFG_SPACE_EXP_SIZE; -- cgit v1.2.3-70-g09d2 From a43ae58c848cfbadaba81c8d63202b4487f922a0 Mon Sep 17 00:00:00 2001 From: Hanjun Guo Date: Tue, 6 May 2014 11:29:52 +0800 Subject: PCI: Turn pcibios_penalize_isa_irq() into a weak function pcibios_penalize_isa_irq() is only implemented by x86 now, and legacy ISA is not used by some architectures. Make pcibios_penalize_isa_irq() a __weak function to simplify the code. This removes the need for new platforms to add stub implementations of pcibios_penalize_isa_irq(). [bhelgaas: changelog, comments] Signed-off-by: Hanjun Guo Signed-off-by: Bjorn Helgaas Acked-by: Arnd Bergmann --- arch/alpha/include/asm/pci.h | 5 ----- arch/arm/include/asm/pci.h | 5 ----- arch/blackfin/include/asm/pci.h | 5 ----- arch/cris/include/asm/pci.h | 1 - arch/frv/include/asm/pci.h | 2 -- arch/frv/mb93090-mb00/pci-irq.c | 4 ---- arch/ia64/include/asm/pci.h | 6 ------ arch/microblaze/include/asm/pci.h | 5 ----- arch/mips/include/asm/pci.h | 5 ----- arch/mn10300/include/asm/pci.h | 1 - arch/mn10300/unit-asb2305/pci-irq.c | 4 ---- arch/parisc/include/asm/pci.h | 5 ----- arch/powerpc/include/asm/pci.h | 5 ----- arch/sh/include/asm/pci.h | 5 ----- arch/sparc/include/asm/pci_32.h | 5 ----- arch/sparc/include/asm/pci_64.h | 5 ----- arch/unicore32/include/asm/pci.h | 5 ----- arch/x86/include/asm/pci.h | 1 - arch/xtensa/include/asm/pci.h | 5 ----- drivers/pci/pci.c | 11 +++++++++++ include/linux/pci.h | 1 + 21 files changed, 12 insertions(+), 79 deletions(-) (limited to 'drivers/pci') diff --git a/arch/alpha/include/asm/pci.h b/arch/alpha/include/asm/pci.h index d01afb78919..f7f680f7457 100644 --- a/arch/alpha/include/asm/pci.h +++ b/arch/alpha/include/asm/pci.h @@ -59,11 +59,6 @@ struct pci_controller { extern void pcibios_set_master(struct pci_dev *dev); -extern inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* IOMMU controls. */ /* The PCI address space does not equal the physical memory address space. diff --git a/arch/arm/include/asm/pci.h b/arch/arm/include/asm/pci.h index 680a83e9446..7e95d8535e2 100644 --- a/arch/arm/include/asm/pci.h +++ b/arch/arm/include/asm/pci.h @@ -31,11 +31,6 @@ static inline int pci_proc_domain(struct pci_bus *bus) } #endif /* CONFIG_PCI_DOMAINS */ -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* * The PCI address space does equal the physical memory address space. * The networking and block device layers use this boolean for bounce diff --git a/arch/blackfin/include/asm/pci.h b/arch/blackfin/include/asm/pci.h index 74352c4597d..c737909fba4 100644 --- a/arch/blackfin/include/asm/pci.h +++ b/arch/blackfin/include/asm/pci.h @@ -10,9 +10,4 @@ #define PCIBIOS_MIN_IO 0x00001000 #define PCIBIOS_MIN_MEM 0x10000000 -static inline void pcibios_penalize_isa_irq(int irq) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #endif /* _ASM_BFIN_PCI_H */ diff --git a/arch/cris/include/asm/pci.h b/arch/cris/include/asm/pci.h index f666734926d..cc2399c175e 100644 --- a/arch/cris/include/asm/pci.h +++ b/arch/cris/include/asm/pci.h @@ -20,7 +20,6 @@ void pcibios_config_init(void); struct pci_bus * pcibios_scan_root(int bus); void pcibios_set_master(struct pci_dev *dev); -void pcibios_penalize_isa_irq(int irq); struct irq_routing_table *pcibios_get_irq_routing_table(void); int pcibios_set_irq_routing(struct pci_dev *dev, int pin, int irq); diff --git a/arch/frv/include/asm/pci.h b/arch/frv/include/asm/pci.h index ef03baf5d89..2035a4d3f9b 100644 --- a/arch/frv/include/asm/pci.h +++ b/arch/frv/include/asm/pci.h @@ -24,8 +24,6 @@ struct pci_dev; extern void pcibios_set_master(struct pci_dev *dev); -extern void pcibios_penalize_isa_irq(int irq); - #ifdef CONFIG_MMU extern void *consistent_alloc(gfp_t gfp, size_t size, dma_addr_t *dma_handle); extern void consistent_free(void *vaddr); diff --git a/arch/frv/mb93090-mb00/pci-irq.c b/arch/frv/mb93090-mb00/pci-irq.c index c677b9d81d3..1c35c93f942 100644 --- a/arch/frv/mb93090-mb00/pci-irq.c +++ b/arch/frv/mb93090-mb00/pci-irq.c @@ -55,10 +55,6 @@ void __init pcibios_fixup_irqs(void) } } -void __init pcibios_penalize_isa_irq(int irq) -{ -} - void pcibios_enable_irq(struct pci_dev *dev) { pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); diff --git a/arch/ia64/include/asm/pci.h b/arch/ia64/include/asm/pci.h index 7d41cc08982..52af5ed9f60 100644 --- a/arch/ia64/include/asm/pci.h +++ b/arch/ia64/include/asm/pci.h @@ -50,12 +50,6 @@ struct pci_dev; extern unsigned long ia64_max_iommu_merge_mask; #define PCI_DMA_BUS_IS_PHYS (ia64_max_iommu_merge_mask == ~0UL) -static inline void -pcibios_penalize_isa_irq (int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #include #ifdef CONFIG_PCI diff --git a/arch/microblaze/include/asm/pci.h b/arch/microblaze/include/asm/pci.h index 935f9bec414..335524040ff 100644 --- a/arch/microblaze/include/asm/pci.h +++ b/arch/microblaze/include/asm/pci.h @@ -44,11 +44,6 @@ struct pci_dev; */ #define pcibios_assign_all_busses() 0 -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #ifdef CONFIG_PCI extern void set_pci_dma_ops(struct dma_map_ops *dma_ops); extern struct dma_map_ops *get_pci_dma_ops(void); diff --git a/arch/mips/include/asm/pci.h b/arch/mips/include/asm/pci.h index 12d6842962b..974b0e30896 100644 --- a/arch/mips/include/asm/pci.h +++ b/arch/mips/include/asm/pci.h @@ -73,11 +73,6 @@ extern unsigned long PCIBIOS_MIN_MEM; extern void pcibios_set_master(struct pci_dev *dev); -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #define HAVE_PCI_MMAP extern int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma, diff --git a/arch/mn10300/include/asm/pci.h b/arch/mn10300/include/asm/pci.h index 16632382468..5f70af25c7d 100644 --- a/arch/mn10300/include/asm/pci.h +++ b/arch/mn10300/include/asm/pci.h @@ -48,7 +48,6 @@ extern void unit_pci_init(void); #define PCIBIOS_MIN_MEM 0xB8000000 void pcibios_set_master(struct pci_dev *dev); -void pcibios_penalize_isa_irq(int irq); /* Dynamic DMA mapping stuff. * i386 has everything mapped statically. diff --git a/arch/mn10300/unit-asb2305/pci-irq.c b/arch/mn10300/unit-asb2305/pci-irq.c index 77439da0467..fcb28ceb824 100644 --- a/arch/mn10300/unit-asb2305/pci-irq.c +++ b/arch/mn10300/unit-asb2305/pci-irq.c @@ -40,10 +40,6 @@ void __init pcibios_fixup_irqs(void) } } -void __init pcibios_penalize_isa_irq(int irq) -{ -} - void pcibios_enable_irq(struct pci_dev *dev) { pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); diff --git a/arch/parisc/include/asm/pci.h b/arch/parisc/include/asm/pci.h index 465154076d2..20df2b04fc0 100644 --- a/arch/parisc/include/asm/pci.h +++ b/arch/parisc/include/asm/pci.h @@ -215,11 +215,6 @@ static inline void pci_dma_burst_advice(struct pci_dev *pdev, } #endif -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't need to penalize isa irq's */ -} - static inline int pci_get_legacy_ide_irq(struct pci_dev *dev, int channel) { return channel ? 15 : 14; diff --git a/arch/powerpc/include/asm/pci.h b/arch/powerpc/include/asm/pci.h index 95145a15c70..1b0739bc14b 100644 --- a/arch/powerpc/include/asm/pci.h +++ b/arch/powerpc/include/asm/pci.h @@ -46,11 +46,6 @@ struct pci_dev; #define pcibios_assign_all_busses() \ (pci_has_flag(PCI_REASSIGN_ALL_BUS)) -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #define HAVE_ARCH_PCI_GET_LEGACY_IDE_IRQ static inline int pci_get_legacy_ide_irq(struct pci_dev *dev, int channel) { diff --git a/arch/sh/include/asm/pci.h b/arch/sh/include/asm/pci.h index bff96c2e7d2..5b451155299 100644 --- a/arch/sh/include/asm/pci.h +++ b/arch/sh/include/asm/pci.h @@ -70,11 +70,6 @@ extern int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma, enum pci_mmap_state mmap_state, int write_combine); extern void pcibios_set_master(struct pci_dev *dev); -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* Dynamic DMA mapping stuff. * SuperH has everything mapped statically like x86. */ diff --git a/arch/sparc/include/asm/pci_32.h b/arch/sparc/include/asm/pci_32.h index dc503297481..53e9b4987db 100644 --- a/arch/sparc/include/asm/pci_32.h +++ b/arch/sparc/include/asm/pci_32.h @@ -16,11 +16,6 @@ #define PCI_IRQ_NONE 0xffffffff -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* Dynamic DMA mapping stuff. */ #define PCI_DMA_BUS_IS_PHYS (0) diff --git a/arch/sparc/include/asm/pci_64.h b/arch/sparc/include/asm/pci_64.h index 1633b718d3b..c6c7396e762 100644 --- a/arch/sparc/include/asm/pci_64.h +++ b/arch/sparc/include/asm/pci_64.h @@ -16,11 +16,6 @@ #define PCI_IRQ_NONE 0xffffffff -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* The PCI address space does not equal the physical memory * address space. The networking and block device layers use * this boolean for bounce buffer decisions. diff --git a/arch/unicore32/include/asm/pci.h b/arch/unicore32/include/asm/pci.h index f5e108f4a15..654407e9861 100644 --- a/arch/unicore32/include/asm/pci.h +++ b/arch/unicore32/include/asm/pci.h @@ -18,11 +18,6 @@ #include #include /* for PCIBIOS_MIN_* */ -static inline void pcibios_penalize_isa_irq(int irq, int active) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - #ifdef CONFIG_PCI static inline void pci_dma_burst_advice(struct pci_dev *pdev, enum pci_dma_burst_strategy *strat, diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h index 96ae4f4040b..0892ea0e683 100644 --- a/arch/x86/include/asm/pci.h +++ b/arch/x86/include/asm/pci.h @@ -68,7 +68,6 @@ void pcibios_config_init(void); void pcibios_scan_root(int bus); void pcibios_set_master(struct pci_dev *dev); -void pcibios_penalize_isa_irq(int irq, int active); struct irq_routing_table *pcibios_get_irq_routing_table(void); int pcibios_set_irq_routing(struct pci_dev *dev, int pin, int irq); diff --git a/arch/xtensa/include/asm/pci.h b/arch/xtensa/include/asm/pci.h index 614be031a79..5d52dc43dfe 100644 --- a/arch/xtensa/include/asm/pci.h +++ b/arch/xtensa/include/asm/pci.h @@ -22,11 +22,6 @@ extern struct pci_controller* pcibios_alloc_controller(void); -static inline void pcibios_penalize_isa_irq(int irq) -{ - /* We don't do dynamic PCI IRQ allocation */ -} - /* Assume some values. (We should revise them, if necessary) */ #define PCIBIOS_MIN_IO 0x2000 diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 39012831867..11f24912523 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1468,6 +1468,17 @@ void __weak pcibios_release_device(struct pci_dev *dev) {} */ void __weak pcibios_disable_device (struct pci_dev *dev) {} +/** + * pcibios_penalize_isa_irq - penalize an ISA IRQ + * @irq: ISA IRQ to penalize + * @active: IRQ active or not + * + * Permits the platform to provide architecture-specific functionality when + * penalizing ISA IRQs. This is the default implementation. Architecture + * implementations can override this. + */ +void __weak pcibios_penalize_isa_irq(int irq, int active) {} + static void do_pci_disable_device(struct pci_dev *dev) { u16 pci_command; diff --git a/include/linux/pci.h b/include/linux/pci.h index 84182b153b2..018877b8b4e 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1578,6 +1578,7 @@ int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state); int pcibios_add_device(struct pci_dev *dev); void pcibios_release_device(struct pci_dev *dev); +void pcibios_penalize_isa_irq(int irq, int active); #ifdef CONFIG_HIBERNATE_CALLBACKS extern struct dev_pm_ops pcibios_pm_ops; -- cgit v1.2.3-70-g09d2 From 755ba5e406e5ddd876e85a881dc50c7f54a8fd6b Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Fri, 9 May 2014 14:31:25 +0900 Subject: PCI: exynos: Remove unnecessary OOM messages The site-specific OOM messages are unnecessary, because they duplicate the MM subsystem generic OOM message. Signed-off-by: Jingoo Han Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-exynos.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 3de6bfbbe8e..81aa700a8ad 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -568,10 +568,8 @@ static int __init exynos_pcie_probe(struct platform_device *pdev) exynos_pcie = devm_kzalloc(&pdev->dev, sizeof(*exynos_pcie), GFP_KERNEL); - if (!exynos_pcie) { - dev_err(&pdev->dev, "no memory for exynos pcie\n"); + if (!exynos_pcie) return -ENOMEM; - } pp = &exynos_pcie->pp; -- cgit v1.2.3-70-g09d2 From d97ffe236894856d08146390ef3fbe6448a8ac2b Mon Sep 17 00:00:00 2001 From: Gavin Shan Date: Wed, 21 May 2014 15:23:30 +1000 Subject: PCI: Fix return value from pci_user_{read,write}_config_*() The PCI user-space config accessors pci_user_{read,write}_config_*() return negative error numbers, which were introduced by commit 34e3207205ef ("PCI: handle positive error codes"). That patch converted all positive error numbers from platform-specific PCI config accessors to -EINVAL, which means the callers don't know anything about the specific cause of the failure. The patch fixes the issue by converting the positive PCIBIOS_* error values to generic negative error numbers with pcibios_err_to_errno(). [bhelgaas: changelog] Signed-off-by: Gavin Shan Signed-off-by: Bjorn Helgaas Acked-by: Greg Thelen --- drivers/pci/access.c | 12 ++++-------- include/linux/pci.h | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 7f8b78c0887..8c148f39e8d 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -148,7 +148,7 @@ static noinline void pci_wait_cfg(struct pci_dev *dev) int pci_user_read_config_##size \ (struct pci_dev *dev, int pos, type *val) \ { \ - int ret = 0; \ + int ret = PCIBIOS_SUCCESSFUL; \ u32 data = -1; \ if (PCI_##size##_BAD) \ return -EINVAL; \ @@ -159,9 +159,7 @@ int pci_user_read_config_##size \ pos, sizeof(type), &data); \ raw_spin_unlock_irq(&pci_lock); \ *val = (type)data; \ - if (ret > 0) \ - ret = -EINVAL; \ - return ret; \ + return pcibios_err_to_errno(ret); \ } \ EXPORT_SYMBOL_GPL(pci_user_read_config_##size); @@ -170,7 +168,7 @@ EXPORT_SYMBOL_GPL(pci_user_read_config_##size); int pci_user_write_config_##size \ (struct pci_dev *dev, int pos, type val) \ { \ - int ret = -EIO; \ + int ret = PCIBIOS_SUCCESSFUL; \ if (PCI_##size##_BAD) \ return -EINVAL; \ raw_spin_lock_irq(&pci_lock); \ @@ -179,9 +177,7 @@ int pci_user_write_config_##size \ ret = dev->bus->ops->write(dev->bus, dev->devfn, \ pos, sizeof(type), val); \ raw_spin_unlock_irq(&pci_lock); \ - if (ret > 0) \ - ret = -EINVAL; \ - return ret; \ + return pcibios_err_to_errno(ret); \ } \ EXPORT_SYMBOL_GPL(pci_user_write_config_##size); diff --git a/include/linux/pci.h b/include/linux/pci.h index 018877b8b4e..322335aaa7e 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -518,7 +518,7 @@ static inline int pcibios_err_to_errno(int err) case PCIBIOS_FUNC_NOT_SUPPORTED: return -ENOENT; case PCIBIOS_BAD_VENDOR_ID: - return -EINVAL; + return -ENOTTY; case PCIBIOS_DEVICE_NOT_FOUND: return -ENODEV; case PCIBIOS_BAD_REGISTER_NUMBER: @@ -529,7 +529,7 @@ static inline int pcibios_err_to_errno(int err) return -ENOSPC; } - return -ENOTTY; + return -ERANGE; } /* Low-level architecture-dependent routines */ -- cgit v1.2.3-70-g09d2 From c25da4778803b41e11fd82dd5576c35c09b5f0e0 Mon Sep 17 00:00:00 2001 From: Phil Edworthy Date: Mon, 12 May 2014 11:57:48 +0100 Subject: PCI: rcar: Add Renesas R-Car PCIe driver This PCIe Host driver currently does not support MSI, so cards fall back to INTx interrupts. Signed-off-by: Phil Edworthy Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman --- drivers/pci/host/Kconfig | 6 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-rcar.c | 768 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 775 insertions(+) create mode 100644 drivers/pci/host/pcie-rcar.c (limited to 'drivers/pci') diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index a6f67ec8882..24d290d097b 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -33,4 +33,10 @@ config PCI_RCAR_GEN2 There are 3 internal PCI controllers available with a single built-in EHCI/OHCI host controller present on each one. +config PCI_RCAR_GEN2_PCIE + bool "Renesas R-Car PCIe controller" + depends on ARCH_SHMOBILE || (ARM && COMPILE_TEST) + help + Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 13fb3333aa0..19946f9a452 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_IMX6) += pci-imx6.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o +obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c new file mode 100644 index 00000000000..3c524b9e60e --- /dev/null +++ b/drivers/pci/host/pcie-rcar.c @@ -0,0 +1,768 @@ +/* + * PCIe driver for Renesas R-Car SoCs + * Copyright (C) 2014 Renesas Electronics Europe Ltd + * + * Based on: + * arch/sh/drivers/pci/pcie-sh7786.c + * arch/sh/drivers/pci/ops-sh7786.c + * Copyright (C) 2009 - 2011 Paul Mundt + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "rcar-pcie" + +#define PCIECAR 0x000010 +#define PCIECCTLR 0x000018 +#define CONFIG_SEND_ENABLE (1 << 31) +#define TYPE0 (0 << 8) +#define TYPE1 (1 << 8) +#define PCIECDR 0x000020 +#define PCIEMSR 0x000028 +#define PCIEINTXR 0x000400 + +/* Transfer control */ +#define PCIETCTLR 0x02000 +#define CFINIT 1 +#define PCIETSTR 0x02004 +#define DATA_LINK_ACTIVE 1 +#define PCIEERRFR 0x02020 +#define UNSUPPORTED_REQUEST (1 << 4) + +/* root port address */ +#define PCIEPRAR(x) (0x02080 + ((x) * 0x4)) + +/* local address reg & mask */ +#define PCIELAR(x) (0x02200 + ((x) * 0x20)) +#define PCIELAMR(x) (0x02208 + ((x) * 0x20)) +#define LAM_PREFETCH (1 << 3) +#define LAM_64BIT (1 << 2) +#define LAR_ENABLE (1 << 1) + +/* PCIe address reg & mask */ +#define PCIEPARL(x) (0x03400 + ((x) * 0x20)) +#define PCIEPARH(x) (0x03404 + ((x) * 0x20)) +#define PCIEPAMR(x) (0x03408 + ((x) * 0x20)) +#define PCIEPTCTLR(x) (0x0340c + ((x) * 0x20)) +#define PAR_ENABLE (1 << 31) +#define IO_SPACE (1 << 8) + +/* Configuration */ +#define PCICONF(x) (0x010000 + ((x) * 0x4)) +#define PMCAP(x) (0x010040 + ((x) * 0x4)) +#define EXPCAP(x) (0x010070 + ((x) * 0x4)) +#define VCCAP(x) (0x010100 + ((x) * 0x4)) + +/* link layer */ +#define IDSETR1 0x011004 +#define TLCTLR 0x011048 +#define MACSR 0x011054 +#define MACCTLR 0x011058 +#define SCRAMBLE_DISABLE (1 << 27) + +/* R-Car H1 PHY */ +#define H1_PCIEPHYADRR 0x04000c +#define WRITE_CMD (1 << 16) +#define PHY_ACK (1 << 24) +#define RATE_POS 12 +#define LANE_POS 8 +#define ADR_POS 0 +#define H1_PCIEPHYDOUTR 0x040014 +#define H1_PCIEPHYSR 0x040018 + +#define RCONF(x) (PCICONF(0)+(x)) +#define RPMCAP(x) (PMCAP(0)+(x)) +#define REXPCAP(x) (EXPCAP(0)+(x)) +#define RVCCAP(x) (VCCAP(0)+(x)) + +#define PCIE_CONF_BUS(b) (((b) & 0xff) << 24) +#define PCIE_CONF_DEV(d) (((d) & 0x1f) << 19) +#define PCIE_CONF_FUNC(f) (((f) & 0x7) << 16) + +#define PCI_MAX_RESOURCES 4 +#define MAX_NR_INBOUND_MAPS 6 + +/* Structure representing the PCIe interface */ +struct rcar_pcie { + struct device *dev; + void __iomem *base; + struct resource res[PCI_MAX_RESOURCES]; + struct resource busn; + int root_bus_nr; + struct clk *clk; + struct clk *bus_clk; +}; + +static inline struct rcar_pcie *sys_to_pcie(struct pci_sys_data *sys) +{ + return sys->private_data; +} + +static void pci_write_reg(struct rcar_pcie *pcie, unsigned long val, + unsigned long reg) +{ + writel(val, pcie->base + reg); +} + +static unsigned long pci_read_reg(struct rcar_pcie *pcie, unsigned long reg) +{ + return readl(pcie->base + reg); +} + +enum { + PCI_ACCESS_READ, + PCI_ACCESS_WRITE, +}; + +static void rcar_rmw32(struct rcar_pcie *pcie, int where, u32 mask, u32 data) +{ + int shift = 8 * (where & 3); + u32 val = pci_read_reg(pcie, where & ~3); + + val &= ~(mask << shift); + val |= data << shift; + pci_write_reg(pcie, val, where & ~3); +} + +static u32 rcar_read_conf(struct rcar_pcie *pcie, int where) +{ + int shift = 8 * (where & 3); + u32 val = pci_read_reg(pcie, where & ~3); + + return val >> shift; +} + +/* Serialization is provided by 'pci_lock' in drivers/pci/access.c */ +static int rcar_pcie_config_access(struct rcar_pcie *pcie, + unsigned char access_type, struct pci_bus *bus, + unsigned int devfn, int where, u32 *data) +{ + int dev, func, reg, index; + + dev = PCI_SLOT(devfn); + func = PCI_FUNC(devfn); + reg = where & ~3; + index = reg / 4; + + /* + * While each channel has its own memory-mapped extended config + * space, it's generally only accessible when in endpoint mode. + * When in root complex mode, the controller is unable to target + * itself with either type 0 or type 1 accesses, and indeed, any + * controller initiated target transfer to its own config space + * result in a completer abort. + * + * Each channel effectively only supports a single device, but as + * the same channel <-> device access works for any PCI_SLOT() + * value, we cheat a bit here and bind the controller's config + * space to devfn 0 in order to enable self-enumeration. In this + * case the regular ECAR/ECDR path is sidelined and the mangled + * config access itself is initiated as an internal bus transaction. + */ + if (pci_is_root_bus(bus)) { + if (dev != 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (access_type == PCI_ACCESS_READ) { + *data = pci_read_reg(pcie, PCICONF(index)); + } else { + /* Keep an eye out for changes to the root bus number */ + if (pci_is_root_bus(bus) && (reg == PCI_PRIMARY_BUS)) + pcie->root_bus_nr = *data & 0xff; + + pci_write_reg(pcie, *data, PCICONF(index)); + } + + return PCIBIOS_SUCCESSFUL; + } + + if (pcie->root_bus_nr < 0) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* Clear errors */ + pci_write_reg(pcie, pci_read_reg(pcie, PCIEERRFR), PCIEERRFR); + + /* Set the PIO address */ + pci_write_reg(pcie, PCIE_CONF_BUS(bus->number) | PCIE_CONF_DEV(dev) | + PCIE_CONF_FUNC(func) | reg, PCIECAR); + + /* Enable the configuration access */ + if (bus->parent->number == pcie->root_bus_nr) + pci_write_reg(pcie, CONFIG_SEND_ENABLE | TYPE0, PCIECCTLR); + else + pci_write_reg(pcie, CONFIG_SEND_ENABLE | TYPE1, PCIECCTLR); + + /* Check for errors */ + if (pci_read_reg(pcie, PCIEERRFR) & UNSUPPORTED_REQUEST) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* Check for master and target aborts */ + if (rcar_read_conf(pcie, RCONF(PCI_STATUS)) & + (PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT)) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (access_type == PCI_ACCESS_READ) + *data = pci_read_reg(pcie, PCIECDR); + else + pci_write_reg(pcie, *data, PCIECDR); + + /* Disable the configuration access */ + pci_write_reg(pcie, 0, PCIECCTLR); + + return PCIBIOS_SUCCESSFUL; +} + +static int rcar_pcie_read_conf(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct rcar_pcie *pcie = sys_to_pcie(bus->sysdata); + int ret; + + if ((size == 2) && (where & 1)) + return PCIBIOS_BAD_REGISTER_NUMBER; + else if ((size == 4) && (where & 3)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + ret = rcar_pcie_config_access(pcie, PCI_ACCESS_READ, + bus, devfn, where, val); + if (ret != PCIBIOS_SUCCESSFUL) { + *val = 0xffffffff; + return ret; + } + + if (size == 1) + *val = (*val >> (8 * (where & 3))) & 0xff; + else if (size == 2) + *val = (*val >> (8 * (where & 2))) & 0xffff; + + dev_dbg(&bus->dev, "pcie-config-read: bus=%3d devfn=0x%04x " + "where=0x%04x size=%d val=0x%08lx\n", bus->number, + devfn, where, size, (unsigned long)*val); + + return ret; +} + +/* Serialization is provided by 'pci_lock' in drivers/pci/access.c */ +static int rcar_pcie_write_conf(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct rcar_pcie *pcie = sys_to_pcie(bus->sysdata); + int shift, ret; + u32 data; + + if ((size == 2) && (where & 1)) + return PCIBIOS_BAD_REGISTER_NUMBER; + else if ((size == 4) && (where & 3)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + ret = rcar_pcie_config_access(pcie, PCI_ACCESS_READ, + bus, devfn, where, &data); + if (ret != PCIBIOS_SUCCESSFUL) + return ret; + + dev_dbg(&bus->dev, "pcie-config-write: bus=%3d devfn=0x%04x " + "where=0x%04x size=%d val=0x%08lx\n", bus->number, + devfn, where, size, (unsigned long)val); + + if (size == 1) { + shift = 8 * (where & 3); + data &= ~(0xff << shift); + data |= ((val & 0xff) << shift); + } else if (size == 2) { + shift = 8 * (where & 2); + data &= ~(0xffff << shift); + data |= ((val & 0xffff) << shift); + } else + data = val; + + ret = rcar_pcie_config_access(pcie, PCI_ACCESS_WRITE, + bus, devfn, where, &data); + + return ret; +} + +static struct pci_ops rcar_pcie_ops = { + .read = rcar_pcie_read_conf, + .write = rcar_pcie_write_conf, +}; + +static void rcar_pcie_setup_window(int win, struct resource *res, + struct rcar_pcie *pcie) +{ + /* Setup PCIe address space mappings for each resource */ + resource_size_t size; + u32 mask; + + pci_write_reg(pcie, 0x00000000, PCIEPTCTLR(win)); + + /* + * The PAMR mask is calculated in units of 128Bytes, which + * keeps things pretty simple. + */ + size = resource_size(res); + mask = (roundup_pow_of_two(size) / SZ_128) - 1; + pci_write_reg(pcie, mask << 7, PCIEPAMR(win)); + + pci_write_reg(pcie, upper_32_bits(res->start), PCIEPARH(win)); + pci_write_reg(pcie, lower_32_bits(res->start), PCIEPARL(win)); + + /* First resource is for IO */ + mask = PAR_ENABLE; + if (res->flags & IORESOURCE_IO) + mask |= IO_SPACE; + + pci_write_reg(pcie, mask, PCIEPTCTLR(win)); +} + +static int rcar_pcie_setup(int nr, struct pci_sys_data *sys) +{ + struct rcar_pcie *pcie = sys_to_pcie(sys); + struct resource *res; + int i; + + pcie->root_bus_nr = -1; + + /* Setup PCI resources */ + for (i = 0; i < PCI_MAX_RESOURCES; i++) { + + res = &pcie->res[i]; + if (!res->flags) + continue; + + rcar_pcie_setup_window(i, res, pcie); + + if (res->flags & IORESOURCE_IO) + pci_ioremap_io(nr * SZ_64K, res->start); + else + pci_add_resource(&sys->resources, res); + } + pci_add_resource(&sys->resources, &pcie->busn); + + return 1; +} + +struct hw_pci rcar_pci = { + .setup = rcar_pcie_setup, + .map_irq = of_irq_parse_and_map_pci, + .ops = &rcar_pcie_ops, +}; + +static void rcar_pcie_enable(struct rcar_pcie *pcie) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + + rcar_pci.nr_controllers = 1; + rcar_pci.private_data = (void **)&pcie; + + pci_common_init_dev(&pdev->dev, &rcar_pci); +#ifdef CONFIG_PCI_DOMAINS + rcar_pci.domain++; +#endif +} + +static int phy_wait_for_ack(struct rcar_pcie *pcie) +{ + unsigned int timeout = 100; + + while (timeout--) { + if (pci_read_reg(pcie, H1_PCIEPHYADRR) & PHY_ACK) + return 0; + + udelay(100); + } + + dev_err(pcie->dev, "Access to PCIe phy timed out\n"); + + return -ETIMEDOUT; +} + +static void phy_write_reg(struct rcar_pcie *pcie, + unsigned int rate, unsigned int addr, + unsigned int lane, unsigned int data) +{ + unsigned long phyaddr; + + phyaddr = WRITE_CMD | + ((rate & 1) << RATE_POS) | + ((lane & 0xf) << LANE_POS) | + ((addr & 0xff) << ADR_POS); + + /* Set write data */ + pci_write_reg(pcie, data, H1_PCIEPHYDOUTR); + pci_write_reg(pcie, phyaddr, H1_PCIEPHYADRR); + + /* Ignore errors as they will be dealt with if the data link is down */ + phy_wait_for_ack(pcie); + + /* Clear command */ + pci_write_reg(pcie, 0, H1_PCIEPHYDOUTR); + pci_write_reg(pcie, 0, H1_PCIEPHYADRR); + + /* Ignore errors as they will be dealt with if the data link is down */ + phy_wait_for_ack(pcie); +} + +static int rcar_pcie_wait_for_dl(struct rcar_pcie *pcie) +{ + unsigned int timeout = 10; + + while (timeout--) { + if ((pci_read_reg(pcie, PCIETSTR) & DATA_LINK_ACTIVE)) + return 0; + + msleep(5); + } + + return -ETIMEDOUT; +} + +static int rcar_pcie_hw_init(struct rcar_pcie *pcie) +{ + int err; + + /* Begin initialization */ + pci_write_reg(pcie, 0, PCIETCTLR); + + /* Set mode */ + pci_write_reg(pcie, 1, PCIEMSR); + + /* + * Initial header for port config space is type 1, set the device + * class to match. Hardware takes care of propagating the IDSETR + * settings, so there is no need to bother with a quirk. + */ + pci_write_reg(pcie, PCI_CLASS_BRIDGE_PCI << 16, IDSETR1); + + /* + * Setup Secondary Bus Number & Subordinate Bus Number, even though + * they aren't used, to avoid bridge being detected as broken. + */ + rcar_rmw32(pcie, RCONF(PCI_SECONDARY_BUS), 0xff, 1); + rcar_rmw32(pcie, RCONF(PCI_SUBORDINATE_BUS), 0xff, 1); + + /* Initialize default capabilities. */ + rcar_rmw32(pcie, REXPCAP(0), 0, PCI_CAP_ID_EXP); + rcar_rmw32(pcie, REXPCAP(PCI_EXP_FLAGS), + PCI_EXP_FLAGS_TYPE, PCI_EXP_TYPE_ROOT_PORT << 4); + rcar_rmw32(pcie, RCONF(PCI_HEADER_TYPE), 0x7f, + PCI_HEADER_TYPE_BRIDGE); + + /* Enable data link layer active state reporting */ + rcar_rmw32(pcie, REXPCAP(PCI_EXP_LNKCAP), 0, PCI_EXP_LNKCAP_DLLLARC); + + /* Write out the physical slot number = 0 */ + rcar_rmw32(pcie, REXPCAP(PCI_EXP_SLTCAP), PCI_EXP_SLTCAP_PSN, 0); + + /* Set the completion timer timeout to the maximum 50ms. */ + rcar_rmw32(pcie, TLCTLR+1, 0x3f, 50); + + /* Terminate list of capabilities (Next Capability Offset=0) */ + rcar_rmw32(pcie, RVCCAP(0), 0xfff0, 0); + + /* Enable MAC data scrambling. */ + rcar_rmw32(pcie, MACCTLR, SCRAMBLE_DISABLE, 0); + + /* Finish initialization - establish a PCI Express link */ + pci_write_reg(pcie, CFINIT, PCIETCTLR); + + /* This will timeout if we don't have a link. */ + err = rcar_pcie_wait_for_dl(pcie); + if (err) + return err; + + /* Enable INTx interrupts */ + rcar_rmw32(pcie, PCIEINTXR, 0, 0xF << 8); + + /* Enable slave Bus Mastering */ + rcar_rmw32(pcie, RCONF(PCI_STATUS), PCI_STATUS_DEVSEL_MASK, + PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | + PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_FAST); + + wmb(); + + return 0; +} + +static int rcar_pcie_hw_init_h1(struct rcar_pcie *pcie) +{ + unsigned int timeout = 10; + + /* Initialize the phy */ + phy_write_reg(pcie, 0, 0x42, 0x1, 0x0EC34191); + phy_write_reg(pcie, 1, 0x42, 0x1, 0x0EC34180); + phy_write_reg(pcie, 0, 0x43, 0x1, 0x00210188); + phy_write_reg(pcie, 1, 0x43, 0x1, 0x00210188); + phy_write_reg(pcie, 0, 0x44, 0x1, 0x015C0014); + phy_write_reg(pcie, 1, 0x44, 0x1, 0x015C0014); + phy_write_reg(pcie, 1, 0x4C, 0x1, 0x786174A0); + phy_write_reg(pcie, 1, 0x4D, 0x1, 0x048000BB); + phy_write_reg(pcie, 0, 0x51, 0x1, 0x079EC062); + phy_write_reg(pcie, 0, 0x52, 0x1, 0x20000000); + phy_write_reg(pcie, 1, 0x52, 0x1, 0x20000000); + phy_write_reg(pcie, 1, 0x56, 0x1, 0x00003806); + + phy_write_reg(pcie, 0, 0x60, 0x1, 0x004B03A5); + phy_write_reg(pcie, 0, 0x64, 0x1, 0x3F0F1F0F); + phy_write_reg(pcie, 0, 0x66, 0x1, 0x00008000); + + while (timeout--) { + if (pci_read_reg(pcie, H1_PCIEPHYSR)) + return rcar_pcie_hw_init(pcie); + + msleep(5); + } + + return -ETIMEDOUT; +} + +static int rcar_pcie_get_resources(struct platform_device *pdev, + struct rcar_pcie *pcie) +{ + struct resource res; + int err; + + err = of_address_to_resource(pdev->dev.of_node, 0, &res); + if (err) + return err; + + pcie->clk = devm_clk_get(&pdev->dev, "pcie"); + if (IS_ERR(pcie->clk)) { + dev_err(pcie->dev, "cannot get platform clock\n"); + return PTR_ERR(pcie->clk); + } + err = clk_prepare_enable(pcie->clk); + if (err) + goto fail_clk; + + pcie->bus_clk = devm_clk_get(&pdev->dev, "pcie_bus"); + if (IS_ERR(pcie->bus_clk)) { + dev_err(pcie->dev, "cannot get pcie bus clock\n"); + err = PTR_ERR(pcie->bus_clk); + goto fail_clk; + } + err = clk_prepare_enable(pcie->bus_clk); + if (err) + goto err_map_reg; + + pcie->base = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(pcie->base)) { + err = PTR_ERR(pcie->base); + goto err_map_reg; + } + + return 0; + +err_map_reg: + clk_disable_unprepare(pcie->bus_clk); +fail_clk: + clk_disable_unprepare(pcie->clk); + + return err; +} + +static int rcar_pcie_inbound_ranges(struct rcar_pcie *pcie, + struct of_pci_range *range, + int *index) +{ + u64 restype = range->flags; + u64 cpu_addr = range->cpu_addr; + u64 cpu_end = range->cpu_addr + range->size; + u64 pci_addr = range->pci_addr; + u32 flags = LAM_64BIT | LAR_ENABLE; + u64 mask; + u64 size; + int idx = *index; + + if (restype & IORESOURCE_PREFETCH) + flags |= LAM_PREFETCH; + + /* + * If the size of the range is larger than the alignment of the start + * address, we have to use multiple entries to perform the mapping. + */ + if (cpu_addr > 0) { + unsigned long nr_zeros = __ffs64(cpu_addr); + u64 alignment = 1ULL << nr_zeros; + size = min(range->size, alignment); + } else { + size = range->size; + } + /* Hardware supports max 4GiB inbound region */ + size = min(size, 1ULL << 32); + + mask = roundup_pow_of_two(size) - 1; + mask &= ~0xf; + + while (cpu_addr < cpu_end) { + /* + * Set up 64-bit inbound regions as the range parser doesn't + * distinguish between 32 and 64-bit types. + */ + pci_write_reg(pcie, lower_32_bits(pci_addr), PCIEPRAR(idx)); + pci_write_reg(pcie, lower_32_bits(cpu_addr), PCIELAR(idx)); + pci_write_reg(pcie, lower_32_bits(mask) | flags, PCIELAMR(idx)); + + pci_write_reg(pcie, upper_32_bits(pci_addr), PCIEPRAR(idx+1)); + pci_write_reg(pcie, upper_32_bits(cpu_addr), PCIELAR(idx+1)); + pci_write_reg(pcie, 0, PCIELAMR(idx+1)); + + pci_addr += size; + cpu_addr += size; + idx += 2; + + if (idx > MAX_NR_INBOUND_MAPS) { + dev_err(pcie->dev, "Failed to map inbound regions!\n"); + return -EINVAL; + } + } + *index = idx; + + return 0; +} + +static int pci_dma_range_parser_init(struct of_pci_range_parser *parser, + struct device_node *node) +{ + const int na = 3, ns = 2; + int rlen; + + parser->node = node; + parser->pna = of_n_addr_cells(node); + parser->np = parser->pna + na + ns; + + parser->range = of_get_property(node, "dma-ranges", &rlen); + if (!parser->range) + return -ENOENT; + + parser->end = parser->range + rlen / sizeof(__be32); + return 0; +} + +static int rcar_pcie_parse_map_dma_ranges(struct rcar_pcie *pcie, + struct device_node *np) +{ + struct of_pci_range range; + struct of_pci_range_parser parser; + int index = 0; + int err; + + if (pci_dma_range_parser_init(&parser, np)) + return -EINVAL; + + /* Get the dma-ranges from DT */ + for_each_of_pci_range(&parser, &range) { + u64 end = range.cpu_addr + range.size - 1; + dev_dbg(pcie->dev, "0x%08x 0x%016llx..0x%016llx -> 0x%016llx\n", + range.flags, range.cpu_addr, end, range.pci_addr); + + err = rcar_pcie_inbound_ranges(pcie, &range, &index); + if (err) + return err; + } + + return 0; +} + +static const struct of_device_id rcar_pcie_of_match[] = { + { .compatible = "renesas,pcie-r8a7779", .data = rcar_pcie_hw_init_h1 }, + { .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init }, + { .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init }, + {}, +}; +MODULE_DEVICE_TABLE(of, rcar_pcie_of_match); + +static int rcar_pcie_probe(struct platform_device *pdev) +{ + struct rcar_pcie *pcie; + unsigned int data; + struct of_pci_range range; + struct of_pci_range_parser parser; + const struct of_device_id *of_id; + int err, win = 0; + int (*hw_init_fn)(struct rcar_pcie *); + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &pdev->dev; + platform_set_drvdata(pdev, pcie); + + /* Get the bus range */ + if (of_pci_parse_bus_range(pdev->dev.of_node, &pcie->busn)) { + dev_err(&pdev->dev, "failed to parse bus-range property\n"); + return -EINVAL; + } + + if (of_pci_range_parser_init(&parser, pdev->dev.of_node)) { + dev_err(&pdev->dev, "missing ranges property\n"); + return -EINVAL; + } + + err = rcar_pcie_get_resources(pdev, pcie); + if (err < 0) { + dev_err(&pdev->dev, "failed to request resources: %d\n", err); + return err; + } + + for_each_of_pci_range(&parser, &range) { + of_pci_range_to_resource(&range, pdev->dev.of_node, + &pcie->res[win++]); + + if (win > PCI_MAX_RESOURCES) + break; + } + + err = rcar_pcie_parse_map_dma_ranges(pcie, pdev->dev.of_node); + if (err) + return err; + + of_id = of_match_device(rcar_pcie_of_match, pcie->dev); + if (!of_id || !of_id->data) + return -EINVAL; + hw_init_fn = of_id->data; + + /* Failure to get a link might just be that no cards are inserted */ + err = hw_init_fn(pcie); + if (err) { + dev_info(&pdev->dev, "PCIe link down\n"); + return 0; + } + + data = pci_read_reg(pcie, MACSR); + dev_info(&pdev->dev, "PCIe x%d: link up\n", (data >> 20) & 0x3f); + + rcar_pcie_enable(pcie); + + return 0; +} + +static struct platform_driver rcar_pcie_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = rcar_pcie_of_match, + .suppress_bind_attrs = true, + }, + .probe = rcar_pcie_probe, +}; +module_platform_driver(rcar_pcie_driver); + +MODULE_AUTHOR("Phil Edworthy "); +MODULE_DESCRIPTION("Renesas R-Car PCIe driver"); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3-70-g09d2 From 290c1fb35860540223bef1be021944d02232889d Mon Sep 17 00:00:00 2001 From: Phil Edworthy Date: Mon, 12 May 2014 11:57:49 +0100 Subject: PCI: rcar: Add MSI support for PCIe Add MSI support to the R-Car PCIe driver. Signed-off-by: Phil Edworthy Signed-off-by: Bjorn Helgaas Reviewed-by: Lucas Stach Acked-by: Simon Horman --- drivers/pci/host/pcie-rcar.c | 242 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 3c524b9e60e..8e06124aa80 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -15,8 +15,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -35,6 +38,7 @@ #define PCIECDR 0x000020 #define PCIEMSR 0x000028 #define PCIEINTXR 0x000400 +#define PCIEMSITXR 0x000840 /* Transfer control */ #define PCIETCTLR 0x02000 @@ -43,6 +47,11 @@ #define DATA_LINK_ACTIVE 1 #define PCIEERRFR 0x02020 #define UNSUPPORTED_REQUEST (1 << 4) +#define PCIEMSIFR 0x02044 +#define PCIEMSIALR 0x02048 +#define MSIFE 1 +#define PCIEMSIAUR 0x0204c +#define PCIEMSIIER 0x02050 /* root port address */ #define PCIEPRAR(x) (0x02080 + ((x) * 0x4)) @@ -85,6 +94,8 @@ #define H1_PCIEPHYDOUTR 0x040014 #define H1_PCIEPHYSR 0x040018 +#define INT_PCI_MSI_NR 32 + #define RCONF(x) (PCICONF(0)+(x)) #define RPMCAP(x) (PMCAP(0)+(x)) #define REXPCAP(x) (EXPCAP(0)+(x)) @@ -97,6 +108,21 @@ #define PCI_MAX_RESOURCES 4 #define MAX_NR_INBOUND_MAPS 6 +struct rcar_msi { + DECLARE_BITMAP(used, INT_PCI_MSI_NR); + struct irq_domain *domain; + struct msi_chip chip; + unsigned long pages; + struct mutex lock; + int irq1; + int irq2; +}; + +static inline struct rcar_msi *to_rcar_msi(struct msi_chip *chip) +{ + return container_of(chip, struct rcar_msi, chip); +} + /* Structure representing the PCIe interface */ struct rcar_pcie { struct device *dev; @@ -106,6 +132,7 @@ struct rcar_pcie { int root_bus_nr; struct clk *clk; struct clk *bus_clk; + struct rcar_msi msi; }; static inline struct rcar_pcie *sys_to_pcie(struct pci_sys_data *sys) @@ -356,10 +383,20 @@ static int rcar_pcie_setup(int nr, struct pci_sys_data *sys) return 1; } +static void rcar_pcie_add_bus(struct pci_bus *bus) +{ + if (IS_ENABLED(CONFIG_PCI_MSI)) { + struct rcar_pcie *pcie = sys_to_pcie(bus->sysdata); + + bus->msi = &pcie->msi.chip; + } +} + struct hw_pci rcar_pci = { .setup = rcar_pcie_setup, .map_irq = of_irq_parse_and_map_pci, .ops = &rcar_pcie_ops, + .add_bus = rcar_pcie_add_bus, }; static void rcar_pcie_enable(struct rcar_pcie *pcie) @@ -477,6 +514,10 @@ static int rcar_pcie_hw_init(struct rcar_pcie *pcie) /* Enable MAC data scrambling. */ rcar_rmw32(pcie, MACCTLR, SCRAMBLE_DISABLE, 0); + /* Enable MSI */ + if (IS_ENABLED(CONFIG_PCI_MSI)) + pci_write_reg(pcie, 0x101f0000, PCIEMSITXR); + /* Finish initialization - establish a PCI Express link */ pci_write_reg(pcie, CFINIT, PCIETCTLR); @@ -530,11 +571,184 @@ static int rcar_pcie_hw_init_h1(struct rcar_pcie *pcie) return -ETIMEDOUT; } +static int rcar_msi_alloc(struct rcar_msi *chip) +{ + int msi; + + mutex_lock(&chip->lock); + + msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR); + if (msi < INT_PCI_MSI_NR) + set_bit(msi, chip->used); + else + msi = -ENOSPC; + + mutex_unlock(&chip->lock); + + return msi; +} + +static void rcar_msi_free(struct rcar_msi *chip, unsigned long irq) +{ + mutex_lock(&chip->lock); + clear_bit(irq, chip->used); + mutex_unlock(&chip->lock); +} + +static irqreturn_t rcar_pcie_msi_irq(int irq, void *data) +{ + struct rcar_pcie *pcie = data; + struct rcar_msi *msi = &pcie->msi; + unsigned long reg; + + reg = pci_read_reg(pcie, PCIEMSIFR); + + /* MSI & INTx share an interrupt - we only handle MSI here */ + if (!reg) + return IRQ_NONE; + + while (reg) { + unsigned int index = find_first_bit(®, 32); + unsigned int irq; + + /* clear the interrupt */ + pci_write_reg(pcie, 1 << index, PCIEMSIFR); + + irq = irq_find_mapping(msi->domain, index); + if (irq) { + if (test_bit(index, msi->used)) + generic_handle_irq(irq); + else + dev_info(pcie->dev, "unhandled MSI\n"); + } else { + /* Unknown MSI, just clear it */ + dev_dbg(pcie->dev, "unexpected MSI\n"); + } + + /* see if there's any more pending in this vector */ + reg = pci_read_reg(pcie, PCIEMSIFR); + } + + return IRQ_HANDLED; +} + +static int rcar_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev, + struct msi_desc *desc) +{ + struct rcar_msi *msi = to_rcar_msi(chip); + struct rcar_pcie *pcie = container_of(chip, struct rcar_pcie, msi.chip); + struct msi_msg msg; + unsigned int irq; + int hwirq; + + hwirq = rcar_msi_alloc(msi); + if (hwirq < 0) + return hwirq; + + irq = irq_create_mapping(msi->domain, hwirq); + if (!irq) { + rcar_msi_free(msi, hwirq); + return -EINVAL; + } + + irq_set_msi_desc(irq, desc); + + msg.address_lo = pci_read_reg(pcie, PCIEMSIALR) & ~MSIFE; + msg.address_hi = pci_read_reg(pcie, PCIEMSIAUR); + msg.data = hwirq; + + write_msi_msg(irq, &msg); + + return 0; +} + +static void rcar_msi_teardown_irq(struct msi_chip *chip, unsigned int irq) +{ + struct rcar_msi *msi = to_rcar_msi(chip); + struct irq_data *d = irq_get_irq_data(irq); + + rcar_msi_free(msi, d->hwirq); +} + +static struct irq_chip rcar_msi_irq_chip = { + .name = "R-Car PCIe MSI", + .irq_enable = unmask_msi_irq, + .irq_disable = mask_msi_irq, + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, +}; + +static int rcar_msi_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &rcar_msi_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + set_irq_flags(irq, IRQF_VALID); + + return 0; +} + +static const struct irq_domain_ops msi_domain_ops = { + .map = rcar_msi_map, +}; + +static int rcar_pcie_enable_msi(struct rcar_pcie *pcie) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + struct rcar_msi *msi = &pcie->msi; + unsigned long base; + int err; + + mutex_init(&msi->lock); + + msi->chip.dev = pcie->dev; + msi->chip.setup_irq = rcar_msi_setup_irq; + msi->chip.teardown_irq = rcar_msi_teardown_irq; + + msi->domain = irq_domain_add_linear(pcie->dev->of_node, INT_PCI_MSI_NR, + &msi_domain_ops, &msi->chip); + if (!msi->domain) { + dev_err(&pdev->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + + /* Two irqs are for MSI, but they are also used for non-MSI irqs */ + err = devm_request_irq(&pdev->dev, msi->irq1, rcar_pcie_msi_irq, + IRQF_SHARED, rcar_msi_irq_chip.name, pcie); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto err; + } + + err = devm_request_irq(&pdev->dev, msi->irq2, rcar_pcie_msi_irq, + IRQF_SHARED, rcar_msi_irq_chip.name, pcie); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ: %d\n", err); + goto err; + } + + /* setup MSI data target */ + msi->pages = __get_free_pages(GFP_KERNEL, 0); + base = virt_to_phys((void *)msi->pages); + + pci_write_reg(pcie, base | MSIFE, PCIEMSIALR); + pci_write_reg(pcie, 0, PCIEMSIAUR); + + /* enable all MSI interrupts */ + pci_write_reg(pcie, 0xffffffff, PCIEMSIIER); + + return 0; + +err: + irq_domain_remove(msi->domain); + return err; +} + static int rcar_pcie_get_resources(struct platform_device *pdev, struct rcar_pcie *pcie) { struct resource res; - int err; + int err, i; err = of_address_to_resource(pdev->dev.of_node, 0, &res); if (err) @@ -559,6 +773,22 @@ static int rcar_pcie_get_resources(struct platform_device *pdev, if (err) goto err_map_reg; + i = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (i < 0) { + dev_err(pcie->dev, "cannot get platform resources for msi interrupt\n"); + err = -ENOENT; + goto err_map_reg; + } + pcie->msi.irq1 = i; + + i = irq_of_parse_and_map(pdev->dev.of_node, 1); + if (i < 0) { + dev_err(pcie->dev, "cannot get platform resources for msi interrupt\n"); + err = -ENOENT; + goto err_map_reg; + } + pcie->msi.irq2 = i; + pcie->base = devm_ioremap_resource(&pdev->dev, &res); if (IS_ERR(pcie->base)) { err = PTR_ERR(pcie->base); @@ -732,6 +962,16 @@ static int rcar_pcie_probe(struct platform_device *pdev) if (err) return err; + if (IS_ENABLED(CONFIG_PCI_MSI)) { + err = rcar_pcie_enable_msi(pcie); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable MSI support: %d\n", + err); + return err; + } + } + of_id = of_match_device(rcar_pcie_of_match, pcie->dev); if (!of_id || !of_id->data) return -EINVAL; -- cgit v1.2.3-70-g09d2 From cab9a128dad0fa0c8400a03d9375c781dda43982 Mon Sep 17 00:00:00 2001 From: Rickard Strandqvist Date: Sun, 18 May 2014 18:02:57 +0200 Subject: PCI: cpqphp: Fix possible null pointer dereference There is otherwise a risk of a null pointer dereference. Found by cppcheck, a static code analysis program. Signed-off-by: Rickard Strandqvist Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/cpqphp_ctrl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/hotplug/cpqphp_ctrl.c b/drivers/pci/hotplug/cpqphp_ctrl.c index 11845b79679..f593585f278 100644 --- a/drivers/pci/hotplug/cpqphp_ctrl.c +++ b/drivers/pci/hotplug/cpqphp_ctrl.c @@ -709,7 +709,8 @@ static struct pci_resource *get_max_resource(struct pci_resource **head, u32 siz temp = temp->next; } - temp->next = max->next; + if (temp) + temp->next = max->next; } max->next = NULL; -- cgit v1.2.3-70-g09d2 From d47b62f4b1c0374517ccbdf3ee4a7cceead58905 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 20 May 2014 01:10:20 +0400 Subject: PCI: rcar: Add gen2 device tree support Add device tree probing support to the 'pci-rcar-gen2' driver. [Sergei: numerous fixes/cleanups/additions] [bhelgaas: whitespace fix] Signed-off-by: Ben Dooks Signed-off-by: Sergei Shtylyov Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman Acked-by: Arnd Bergmann --- .../devicetree/bindings/pci/pci-rcar-gen2.txt | 66 ++++++++++++++++++++++ drivers/pci/host/pci-rcar-gen2.c | 31 +++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/pci/pci-rcar-gen2.txt (limited to 'drivers/pci') diff --git a/Documentation/devicetree/bindings/pci/pci-rcar-gen2.txt b/Documentation/devicetree/bindings/pci/pci-rcar-gen2.txt new file mode 100644 index 00000000000..d8ef5bf50f1 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/pci-rcar-gen2.txt @@ -0,0 +1,66 @@ +Renesas AHB to PCI bridge +------------------------- + +This is the bridge used internally to connect the USB controllers to the +AHB. There is one bridge instance per USB port connected to the internal +OHCI and EHCI controllers. + +Required properties: +- compatible: "renesas,pci-r8a7790" for the R8A7790 SoC; + "renesas,pci-r8a7791" for the R8A7791 SoC. +- reg: A list of physical regions to access the device: the first is + the operational registers for the OHCI/EHCI controllers and the + second is for the bridge configuration and control registers. +- interrupts: interrupt for the device. +- clocks: The reference to the device clock. +- bus-range: The PCI bus number range; as this is a single bus, the range + should be specified as the same value twice. +- #address-cells: must be 3. +- #size-cells: must be 2. +- #interrupt-cells: must be 1. +- interrupt-map: standard property used to define the mapping of the PCI + interrupts to the GIC interrupts. +- interrupt-map-mask: standard property that helps to define the interrupt + mapping. + +Example SoC configuration: + + pci0: pci@ee090000 { + compatible = "renesas,pci-r8a7790"; + clocks = <&mstp7_clks R8A7790_CLK_EHCI>; + reg = <0x0 0xee090000 0x0 0xc00>, + <0x0 0xee080000 0x0 0x1100>; + interrupts = <0 108 IRQ_TYPE_LEVEL_HIGH>; + status = "disabled"; + + bus-range = <0 0>; + #address-cells = <3>; + #size-cells = <2>; + #interrupt-cells = <1>; + interrupt-map-mask = <0xff00 0 0 0x7>; + interrupt-map = <0x0000 0 0 1 &gic 0 108 IRQ_TYPE_LEVEL_HIGH + 0x0800 0 0 1 &gic 0 108 IRQ_TYPE_LEVEL_HIGH + 0x1000 0 0 2 &gic 0 108 IRQ_TYPE_LEVEL_HIGH>; + + pci@0,1 { + reg = <0x800 0 0 0 0>; + device_type = "pci"; + phys = <&usbphy 0 0>; + phy-names = "usb"; + }; + + pci@0,2 { + reg = <0x1000 0 0 0 0>; + device_type = "pci"; + phys = <&usbphy 0 0>; + phy-names = "usb"; + }; + }; + +Example board setup: + +&pci0 { + status = "okay"; + pinctrl-0 = <&usb0_pins>; + pinctrl-names = "default"; +}; diff --git a/drivers/pci/host/pci-rcar-gen2.c b/drivers/pci/host/pci-rcar-gen2.c index 4fe349dcaf5..3ef854f5a5b 100644 --- a/drivers/pci/host/pci-rcar-gen2.c +++ b/drivers/pci/host/pci-rcar-gen2.c @@ -99,6 +99,7 @@ struct rcar_pci_priv { struct resource io_res; struct resource mem_res; struct resource *cfg_res; + unsigned busnr; int irq; unsigned long window_size; }; @@ -318,8 +319,8 @@ static int rcar_pci_setup(int nr, struct pci_sys_data *sys) pci_add_resource(&sys->resources, &priv->io_res); pci_add_resource(&sys->resources, &priv->mem_res); - /* Setup bus number based on platform device id */ - sys->busnr = to_platform_device(priv->dev)->id; + /* Setup bus number based on platform device id / of bus-range */ + sys->busnr = priv->busnr; return 1; } @@ -372,6 +373,23 @@ static int rcar_pci_probe(struct platform_device *pdev) priv->window_size = SZ_1G; + if (pdev->dev.of_node) { + struct resource busnr; + int ret; + + ret = of_pci_parse_bus_range(pdev->dev.of_node, &busnr); + if (ret < 0) { + dev_err(&pdev->dev, "failed to parse bus-range\n"); + return ret; + } + + priv->busnr = busnr.start; + if (busnr.end != busnr.start) + dev_warn(&pdev->dev, "only one bus number supported\n"); + } else { + priv->busnr = pdev->id; + } + hw_private[0] = priv; memset(&hw, 0, sizeof(hw)); hw.nr_controllers = ARRAY_SIZE(hw_private); @@ -383,11 +401,20 @@ static int rcar_pci_probe(struct platform_device *pdev) return 0; } +static struct of_device_id rcar_pci_of_match[] = { + { .compatible = "renesas,pci-r8a7790", }, + { .compatible = "renesas,pci-r8a7791", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, rcar_pci_of_match); + static struct platform_driver rcar_pci_driver = { .driver = { .name = "pci-rcar-gen2", .owner = THIS_MODULE, .suppress_bind_attrs = true, + .of_match_table = rcar_pci_of_match, }, .probe = rcar_pci_probe, }; -- cgit v1.2.3-70-g09d2 From 782a985d7af26db39e86070d28f987cad21313c0 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Tue, 20 May 2014 08:53:21 -0600 Subject: PCI: Introduce new device binding path using pci_dev.driver_override The driver_override field allows us to specify the driver for a device rather than relying on the driver to provide a positive match of the device. This shortcuts the existing process of looking up the vendor and device ID, adding them to the driver new_id, binding the device, then removing the ID, but it also provides a couple advantages. First, the above existing process allows the driver to bind to any device matching the new_id for the window where it's enabled. This is often not desired, such as the case of trying to bind a single device to a meta driver like pci-stub or vfio-pci. Using driver_override we can do this deterministically using: echo pci-stub > /sys/bus/pci/devices/0000:03:00.0/driver_override echo 0000:03:00.0 > /sys/bus/pci/devices/0000:03:00.0/driver/unbind echo 0000:03:00.0 > /sys/bus/pci/drivers_probe Previously we could not invoke drivers_probe after adding a device to new_id for a driver as we get non-deterministic behavior whether the driver we intend or the standard driver will claim the device. Now it becomes a deterministic process, only the driver matching driver_override will probe the device. To return the device to the standard driver, we simply clear the driver_override and reprobe the device: echo > /sys/bus/pci/devices/0000:03:00.0/driver_override echo 0000:03:00.0 > /sys/bus/pci/devices/0000:03:00.0/driver/unbind echo 0000:03:00.0 > /sys/bus/pci/drivers_probe Another advantage to this approach is that we can specify a driver override to force a specific binding or prevent any binding. For instance when an IOMMU group is exposed to userspace through VFIO we require that all devices within that group are owned by VFIO. However, devices can be hot-added into an IOMMU group, in which case we want to prevent the device from binding to any driver (override driver = "none") or perhaps have it automatically bind to vfio-pci. With driver_override it's a simple matter for this field to be set internally when the device is first discovered to prevent driver matches. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas Reviewed-by: Konrad Rzeszutek Wilk Reviewed-by: Alexander Graf Acked-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-pci | 21 +++++++++++++++++ drivers/pci/pci-driver.c | 25 ++++++++++++++++++--- drivers/pci/pci-sysfs.c | 40 +++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 1 + include/linux/pci.h | 1 + 5 files changed, 85 insertions(+), 3 deletions(-) (limited to 'drivers/pci') diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci index a3c5a668503..898ddc4440e 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -250,3 +250,24 @@ Description: valid. For example, writing a 2 to this file when sriov_numvfs is not 0 and not 2 already will return an error. Writing a 10 when the value of sriov_totalvfs is 8 will return an error. + +What: /sys/bus/pci/devices/.../driver_override +Date: April 2014 +Contact: Alex Williamson +Description: + This file allows the driver for a device to be specified which + will override standard static and dynamic ID matching. When + specified, only a driver with a name matching the value written + to driver_override will have an opportunity to bind to the + device. The override is specified by writing a string to the + driver_override file (echo pci-stub > driver_override) and + may be cleared with an empty string (echo > driver_override). + This returns the device to standard matching rules binding. + Writing to driver_override does not automatically unbind the + device from its current driver or make any attempt to + automatically load the specified driver. If no driver with a + matching name is currently loaded in the kernel, the device + will not bind to any driver. This also allows devices to + opt-out of driver binding using a driver_override name such as + "none". Only a single driver may be specified in the override, + there is no support for parsing delimiters. diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index d911e0c1f35..4393c12e913 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -216,6 +216,13 @@ const struct pci_device_id *pci_match_id(const struct pci_device_id *ids, return NULL; } +static const struct pci_device_id pci_device_id_any = { + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, +}; + /** * pci_match_device - Tell if a PCI device structure has a matching PCI device id structure * @drv: the PCI driver to match against @@ -229,18 +236,30 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv, struct pci_dev *dev) { struct pci_dynid *dynid; + const struct pci_device_id *found_id = NULL; + + /* When driver_override is set, only bind to the matching driver */ + if (dev->driver_override && strcmp(dev->driver_override, drv->name)) + return NULL; /* Look at the dynamic ids first, before the static ones */ spin_lock(&drv->dynids.lock); list_for_each_entry(dynid, &drv->dynids.list, node) { if (pci_match_one_device(&dynid->id, dev)) { - spin_unlock(&drv->dynids.lock); - return &dynid->id; + found_id = &dynid->id; + break; } } spin_unlock(&drv->dynids.lock); - return pci_match_id(drv->id_table, dev); + if (!found_id) + found_id = pci_match_id(drv->id_table, dev); + + /* driver_override will always match, send a dummy id */ + if (!found_id && dev->driver_override) + found_id = &pci_device_id_any; + + return found_id; } struct drv_dev_and_id { diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 4e0acefb756..faa4ab554d6 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -499,6 +499,45 @@ static struct device_attribute sriov_numvfs_attr = sriov_numvfs_show, sriov_numvfs_store); #endif /* CONFIG_PCI_IOV */ +static ssize_t driver_override_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + char *driver_override, *old = pdev->driver_override, *cp; + + if (count > PATH_MAX) + return -EINVAL; + + driver_override = kstrndup(buf, count, GFP_KERNEL); + if (!driver_override) + return -ENOMEM; + + cp = strchr(driver_override, '\n'); + if (cp) + *cp = '\0'; + + if (strlen(driver_override)) { + pdev->driver_override = driver_override; + } else { + kfree(driver_override); + pdev->driver_override = NULL; + } + + kfree(old); + + return count; +} + +static ssize_t driver_override_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + return sprintf(buf, "%s\n", pdev->driver_override); +} +static DEVICE_ATTR_RW(driver_override); + static struct attribute *pci_dev_attrs[] = { &dev_attr_resource.attr, &dev_attr_vendor.attr, @@ -521,6 +560,7 @@ static struct attribute *pci_dev_attrs[] = { #if defined(CONFIG_PM_RUNTIME) && defined(CONFIG_ACPI) &dev_attr_d3cold_allowed.attr, #endif + &dev_attr_driver_override.attr, NULL, }; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ef09f5f2fe6..54268de45f5 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1215,6 +1215,7 @@ static void pci_release_dev(struct device *dev) pci_release_of_node(pci_dev); pcibios_release_device(pci_dev); pci_bus_put(pci_dev->bus); + kfree(pci_dev->driver_override); kfree(pci_dev); } diff --git a/include/linux/pci.h b/include/linux/pci.h index aab57b4abe7..b72af276f59 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -365,6 +365,7 @@ struct pci_dev { #endif phys_addr_t rom; /* Physical address of ROM if it's not from the BAR */ size_t romlen; /* Length of ROM if it's not from the BAR */ + char *driver_override; /* Driver name to force a match */ }; static inline struct pci_dev *pci_physfn(struct pci_dev *dev) -- cgit v1.2.3-70-g09d2 From 17d7acc8e1c81f8125730aa900c67412a2ac69e2 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Wed, 28 May 2014 15:18:45 +0530 Subject: PCI: exynos: Fix add_pcie_port() section mismatch warning add_pcie_port() is called only from exynos_pcie_probe(), which is annotated with __init. Thus it makes sense to annotate add_pcie_port() with __init to avoid the following section mismatch warning: WARNING: drivers/pci/built-in.o(.text.unlikely+0xf8): Section mismatch in reference from the function add_pcie_port() to the function .init.text:dw_pcie_host_init() The function add_pcie_port() references the function __init dw_pcie_host_init(). This is often because add_pcie_port lacks a __init annotation or the annotation of dw_pcie_host_init is wrong. [bhelgaas: changelog] Reported-by: kbuild test robot Signed-off-by: Sachin Kamat Signed-off-by: Bjorn Helgaas Acked-by: Jingoo Han --- drivers/pci/host/pci-exynos.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 81aa700a8ad..2c4b27cb4f6 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -511,7 +511,8 @@ static struct pcie_host_ops exynos_pcie_host_ops = { .host_init = exynos_pcie_host_init, }; -static int add_pcie_port(struct pcie_port *pp, struct platform_device *pdev) +static int __init add_pcie_port(struct pcie_port *pp, + struct platform_device *pdev) { int ret; -- cgit v1.2.3-70-g09d2 From c893d133eaccdda2516a3e71cd05a7dac2e14b00 Mon Sep 17 00:00:00 2001 From: Yijing Wang Date: Fri, 30 May 2014 11:01:03 +0800 Subject: PCI: Make pci_bus_add_device() void pci_bus_add_device() always returns 0, so there's no point in returning anything at all. Make it a void function and remove the tests of the return value from the callers. [bhelgaas: changelog, remove unused "err" from i82875p_setup_overfl_dev()] Signed-off-by: Yijing Wang Signed-off-by: Bjorn Helgaas --- drivers/edac/i82875p_edac.c | 8 +------- drivers/pci/bus.c | 10 ++-------- drivers/pci/iov.c | 2 +- drivers/platform/x86/asus-wmi.c | 3 +-- drivers/platform/x86/eeepc-laptop.c | 3 +-- include/linux/pci.h | 2 +- 6 files changed, 7 insertions(+), 21 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/edac/i82875p_edac.c b/drivers/edac/i82875p_edac.c index 8d0450b9b9a..4009077c883 100644 --- a/drivers/edac/i82875p_edac.c +++ b/drivers/edac/i82875p_edac.c @@ -275,7 +275,6 @@ static int i82875p_setup_overfl_dev(struct pci_dev *pdev, { struct pci_dev *dev; void __iomem *window; - int err; *ovrfl_pdev = NULL; *ovrfl_window = NULL; @@ -293,12 +292,7 @@ static int i82875p_setup_overfl_dev(struct pci_dev *pdev, if (dev == NULL) return 1; - err = pci_bus_add_device(dev); - if (err) { - i82875p_printk(KERN_ERR, - "%s(): pci_bus_add_device() Failed\n", - __func__); - } + pci_bus_add_device(dev); pci_bus_assign_resources(dev->bus); } diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index ba2bf55a38d..447d393725e 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -235,7 +235,7 @@ void __weak pcibios_resource_survey_bus(struct pci_bus *bus) { } * * This adds add sysfs entries and start device drivers */ -int pci_bus_add_device(struct pci_dev *dev) +void pci_bus_add_device(struct pci_dev *dev) { int retval; @@ -252,8 +252,6 @@ int pci_bus_add_device(struct pci_dev *dev) WARN_ON(retval < 0); dev->is_added = 1; - - return 0; } /** @@ -266,16 +264,12 @@ void pci_bus_add_devices(const struct pci_bus *bus) { struct pci_dev *dev; struct pci_bus *child; - int retval; list_for_each_entry(dev, &bus->devices, bus_list) { /* Skip already-added devices */ if (dev->is_added) continue; - retval = pci_bus_add_device(dev); - if (retval) - dev_err(&dev->dev, "Error adding device (%d)\n", - retval); + pci_bus_add_device(dev); } list_for_each_entry(dev, &bus->devices, bus_list) { diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index de7a74782f9..cb6f24740ee 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -106,7 +106,7 @@ static int virtfn_add(struct pci_dev *dev, int id, int reset) pci_device_add(virtfn, virtfn->bus); mutex_unlock(&iov->dev->sriov->lock); - rc = pci_bus_add_device(virtfn); + pci_bus_add_device(virtfn); sprintf(buf, "virtfn%u", id); rc = sysfs_create_link(&dev->dev.kobj, &virtfn->dev.kobj, buf); if (rc) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index c5e082fb82f..91ef69a5226 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -642,8 +642,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus) dev = pci_scan_single_device(bus, 0); if (dev) { pci_bus_assign_resources(bus); - if (pci_bus_add_device(dev)) - pr_err("Unable to hotplug wifi\n"); + pci_bus_add_device(dev); } } else { dev = pci_get_slot(bus, 0); diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index 399e8c56219..9b0c57cd1d4 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -633,8 +633,7 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) dev = pci_scan_single_device(bus, 0); if (dev) { pci_bus_assign_resources(bus); - if (pci_bus_add_device(dev)) - pr_err("Unable to hotplug wifi\n"); + pci_bus_add_device(dev); } } else { dev = pci_get_slot(bus, 0); diff --git a/include/linux/pci.h b/include/linux/pci.h index 322335aaa7e..785149a6aec 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -764,7 +764,7 @@ int pci_scan_slot(struct pci_bus *bus, int devfn); struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn); void pci_device_add(struct pci_dev *dev, struct pci_bus *bus); unsigned int pci_scan_child_bus(struct pci_bus *bus); -int __must_check pci_bus_add_device(struct pci_dev *dev); +void pci_bus_add_device(struct pci_dev *dev); void pci_read_bridge_bases(struct pci_bus *child); struct resource *pci_find_parent_resource(const struct pci_dev *dev, struct resource *res); -- cgit v1.2.3-70-g09d2 From 44cb5e94f96cef72a977fc5fdea8095bc0ae25ba Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 30 May 2014 12:08:48 +0530 Subject: PCI: imx6: Fix imx6_add_pcie_port() section mismatch warning imx6_add_pcie_port() is called only from from imx6_pcie_probe() which is annotated with __init. Thus it makes sense to annotate imx6_add_pcie_port() with __init to avoid section mismatch warnings. [bhelgaas: changelog] Signed-off-by: Sachin Kamat Signed-off-by: Bjorn Helgaas Cc: Sean Cross --- drivers/pci/host/pci-imx6.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index ee082509b0b..facbf8faa35 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -487,7 +487,7 @@ static struct pcie_host_ops imx6_pcie_host_ops = { .host_init = imx6_pcie_host_init, }; -static int imx6_add_pcie_port(struct pcie_port *pp, +static int __init imx6_add_pcie_port(struct pcie_port *pp, struct platform_device *pdev) { int ret; -- cgit v1.2.3-70-g09d2 From 57526136532408bacf2f68c26027abc2924b45d1 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 28 Mar 2014 17:52:55 +0100 Subject: PCI: imx6: Use new clock names As defined in the new binding. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu --- drivers/pci/host/pci-imx6.c | 74 ++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index facbf8faa35..e080ae326ba 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -35,10 +35,9 @@ struct imx6_pcie { int power_on_gpio; int wake_up_gpio; int disable_gpio; - struct clk *lvds_gate; - struct clk *sata_ref_100m; - struct clk *pcie_ref_125m; - struct clk *pcie_axi; + struct clk *pcie_bus; + struct clk *pcie_phy; + struct clk *pcie; struct pcie_port pp; struct regmap *iomuxc_gpr; void __iomem *mem_base; @@ -239,28 +238,22 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); - ret = clk_prepare_enable(imx6_pcie->sata_ref_100m); + ret = clk_prepare_enable(imx6_pcie->pcie_phy); if (ret) { - dev_err(pp->dev, "unable to enable sata_ref_100m\n"); - goto err_sata_ref; + dev_err(pp->dev, "unable to enable pcie_phy clock\n"); + goto err_pcie_phy; } - ret = clk_prepare_enable(imx6_pcie->pcie_ref_125m); + ret = clk_prepare_enable(imx6_pcie->pcie_bus); if (ret) { - dev_err(pp->dev, "unable to enable pcie_ref_125m\n"); - goto err_pcie_ref; + dev_err(pp->dev, "unable to enable pcie_bus clock\n"); + goto err_pcie_bus; } - ret = clk_prepare_enable(imx6_pcie->lvds_gate); + ret = clk_prepare_enable(imx6_pcie->pcie); if (ret) { - dev_err(pp->dev, "unable to enable lvds_gate\n"); - goto err_lvds_gate; - } - - ret = clk_prepare_enable(imx6_pcie->pcie_axi); - if (ret) { - dev_err(pp->dev, "unable to enable pcie_axi\n"); - goto err_pcie_axi; + dev_err(pp->dev, "unable to enable pcie clock\n"); + goto err_pcie; } /* allow the clocks to stabilize */ @@ -274,13 +267,11 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) } return 0; -err_pcie_axi: - clk_disable_unprepare(imx6_pcie->lvds_gate); -err_lvds_gate: - clk_disable_unprepare(imx6_pcie->pcie_ref_125m); -err_pcie_ref: - clk_disable_unprepare(imx6_pcie->sata_ref_100m); -err_sata_ref: +err_pcie: + clk_disable_unprepare(imx6_pcie->pcie_bus); +err_pcie_bus: + clk_disable_unprepare(imx6_pcie->pcie_phy); +err_pcie_phy: return ret; } @@ -583,32 +574,25 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) } /* Fetch clocks */ - imx6_pcie->lvds_gate = devm_clk_get(&pdev->dev, "lvds_gate"); - if (IS_ERR(imx6_pcie->lvds_gate)) { - dev_err(&pdev->dev, - "lvds_gate clock select missing or invalid\n"); - return PTR_ERR(imx6_pcie->lvds_gate); - } - - imx6_pcie->sata_ref_100m = devm_clk_get(&pdev->dev, "sata_ref_100m"); - if (IS_ERR(imx6_pcie->sata_ref_100m)) { + imx6_pcie->pcie_phy = devm_clk_get(&pdev->dev, "pcie_phy"); + if (IS_ERR(imx6_pcie->pcie_phy)) { dev_err(&pdev->dev, - "sata_ref_100m clock source missing or invalid\n"); - return PTR_ERR(imx6_pcie->sata_ref_100m); + "pcie_phy clock source missing or invalid\n"); + return PTR_ERR(imx6_pcie->pcie_phy); } - imx6_pcie->pcie_ref_125m = devm_clk_get(&pdev->dev, "pcie_ref_125m"); - if (IS_ERR(imx6_pcie->pcie_ref_125m)) { + imx6_pcie->pcie_bus = devm_clk_get(&pdev->dev, "pcie_bus"); + if (IS_ERR(imx6_pcie->pcie_bus)) { dev_err(&pdev->dev, - "pcie_ref_125m clock source missing or invalid\n"); - return PTR_ERR(imx6_pcie->pcie_ref_125m); + "pcie_bus clock source missing or invalid\n"); + return PTR_ERR(imx6_pcie->pcie_bus); } - imx6_pcie->pcie_axi = devm_clk_get(&pdev->dev, "pcie_axi"); - if (IS_ERR(imx6_pcie->pcie_axi)) { + imx6_pcie->pcie = devm_clk_get(&pdev->dev, "pcie"); + if (IS_ERR(imx6_pcie->pcie)) { dev_err(&pdev->dev, - "pcie_axi clock source missing or invalid\n"); - return PTR_ERR(imx6_pcie->pcie_axi); + "pcie clock source missing or invalid\n"); + return PTR_ERR(imx6_pcie->pcie); } /* Grab GPR config register range */ -- cgit v1.2.3-70-g09d2 From e521519a84f6d796d3cff756969cd5902c9550dd Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 28 Mar 2014 17:52:56 +0100 Subject: PCI: imx6: Drop old IRQ mapping We don't need this anymore. The IRQs are now properly mapped through the DT. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu --- drivers/pci/host/pci-imx6.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index e080ae326ba..ccaeed88597 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -483,12 +483,6 @@ static int __init imx6_add_pcie_port(struct pcie_port *pp, { int ret; - pp->irq = platform_get_irq(pdev, 0); - if (!pp->irq) { - dev_err(&pdev->dev, "failed to get irq\n"); - return -ENODEV; - } - pp->root_bus_nr = -1; pp->ops = &imx6_pcie_host_ops; -- cgit v1.2.3-70-g09d2 From 5c40eea7783bbcdd5795cd7d50b7b3fd9a94dc94 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 28 Mar 2014 17:52:57 +0100 Subject: PCI: imx6: Remove optional (and unused) IRQs They are dropped with the new binding. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu --- drivers/pci/host/pci-imx6.c | 42 ------------------------------------------ 1 file changed, 42 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index ccaeed88597..29e77508d28 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -32,9 +32,6 @@ struct imx6_pcie { int reset_gpio; - int power_on_gpio; - int wake_up_gpio; - int disable_gpio; struct clk *pcie_bus; struct clk *pcie_phy; struct clk *pcie; @@ -230,9 +227,6 @@ static int imx6_pcie_deassert_core_reset(struct pcie_port *pp) struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); int ret; - if (gpio_is_valid(imx6_pcie->power_on_gpio)) - gpio_set_value(imx6_pcie->power_on_gpio, 1); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_TEST_PD, 0 << 18); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, @@ -531,42 +525,6 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) } } - imx6_pcie->power_on_gpio = of_get_named_gpio(np, "power-on-gpio", 0); - if (gpio_is_valid(imx6_pcie->power_on_gpio)) { - ret = devm_gpio_request_one(&pdev->dev, - imx6_pcie->power_on_gpio, - GPIOF_OUT_INIT_LOW, - "PCIe power enable"); - if (ret) { - dev_err(&pdev->dev, "unable to get power-on gpio\n"); - return ret; - } - } - - imx6_pcie->wake_up_gpio = of_get_named_gpio(np, "wake-up-gpio", 0); - if (gpio_is_valid(imx6_pcie->wake_up_gpio)) { - ret = devm_gpio_request_one(&pdev->dev, - imx6_pcie->wake_up_gpio, - GPIOF_IN, - "PCIe wake up"); - if (ret) { - dev_err(&pdev->dev, "unable to get wake-up gpio\n"); - return ret; - } - } - - imx6_pcie->disable_gpio = of_get_named_gpio(np, "disable-gpio", 0); - if (gpio_is_valid(imx6_pcie->disable_gpio)) { - ret = devm_gpio_request_one(&pdev->dev, - imx6_pcie->disable_gpio, - GPIOF_OUT_INIT_HIGH, - "PCIe disable endpoint"); - if (ret) { - dev_err(&pdev->dev, "unable to get disable-ep gpio\n"); - return ret; - } - } - /* Fetch clocks */ imx6_pcie->pcie_phy = devm_clk_get(&pdev->dev, "pcie_phy"); if (IS_ERR(imx6_pcie->pcie_phy)) { -- cgit v1.2.3-70-g09d2 From 7f4f16eef5aeba31bdfb7702ced06a42f2777e04 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 28 Mar 2014 17:52:58 +0100 Subject: PCI: designware: Make MSI ISR shared IRQ aware On i.MX6 the host controller MSI IRQ is shared with PCI legacy INTD. Make sure we don't bail too early from the IRQ handler. The issue is fairly theoretical as it would require a system setup with a PCIe switch where one connected device is using legacy INTD and another one using MSI, but better fix it now. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Jingoo Han Acked-by: Richard Zhu --- drivers/pci/host/pci-exynos.c | 4 +--- drivers/pci/host/pcie-designware.c | 6 +++++- drivers/pci/host/pcie-designware.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 3de6bfbbe8e..b616d34922d 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -415,9 +415,7 @@ static irqreturn_t exynos_pcie_msi_irq_handler(int irq, void *arg) { struct pcie_port *pp = arg; - dw_handle_msi_irq(pp); - - return IRQ_HANDLED; + return dw_handle_msi_irq(pp); } static void exynos_pcie_msi_init(struct pcie_port *pp) diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 509a29d8450..77a649dd147 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -155,15 +155,17 @@ static struct irq_chip dw_msi_irq_chip = { }; /* MSI int handler */ -void dw_handle_msi_irq(struct pcie_port *pp) +irqreturn_t dw_handle_msi_irq(struct pcie_port *pp) { unsigned long val; int i, pos, irq; + irqreturn_t ret = IRQ_NONE; for (i = 0; i < MAX_MSI_CTRLS; i++) { dw_pcie_rd_own_conf(pp, PCIE_MSI_INTR0_STATUS + i * 12, 4, (u32 *)&val); if (val) { + ret = IRQ_HANDLED; pos = 0; while ((pos = find_next_bit(&val, 32, pos)) != 32) { irq = irq_find_mapping(pp->irq_domain, @@ -176,6 +178,8 @@ void dw_handle_msi_irq(struct pcie_port *pp) } } } + + return ret; } void dw_pcie_msi_init(struct pcie_port *pp) diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 3063b3594d8..a169d22d517 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -68,7 +68,7 @@ struct pcie_host_ops { int dw_pcie_cfg_read(void __iomem *addr, int where, int size, u32 *val); int dw_pcie_cfg_write(void __iomem *addr, int where, int size, u32 val); -void dw_handle_msi_irq(struct pcie_port *pp); +irqreturn_t dw_handle_msi_irq(struct pcie_port *pp); void dw_pcie_msi_init(struct pcie_port *pp); int dw_pcie_link_up(struct pcie_port *pp); void dw_pcie_setup_rc(struct pcie_port *pp); -- cgit v1.2.3-70-g09d2 From d1dc9749a5b8239d9ae718a176b5cd39ff89f976 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 28 Mar 2014 17:52:59 +0100 Subject: PCI: imx6: Add support for MSI This patch adds support for Message Signaled Interrupts in the imx6-pcie driver. Signed-off-by: Harro Haan Signed-off-by: Juergen Beisert Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu --- drivers/pci/host/pci-imx6.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'drivers/pci') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 29e77508d28..a5645ae4aef 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "pcie-designware.h" @@ -314,6 +315,13 @@ static int imx6_pcie_wait_for_link(struct pcie_port *pp) return 0; } +static irqreturn_t imx6_pcie_msi_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + return dw_handle_msi_irq(pp); +} + static int imx6_pcie_start_link(struct pcie_port *pp) { struct imx6_pcie *imx6_pcie = to_imx6_pcie(pp); @@ -388,6 +396,9 @@ static void imx6_pcie_host_init(struct pcie_port *pp) dw_pcie_setup_rc(pp); imx6_pcie_start_link(pp); + + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(pp); } static void imx6_pcie_reset_phy(struct pcie_port *pp) @@ -477,6 +488,22 @@ static int __init imx6_add_pcie_port(struct pcie_port *pp, { int ret; + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq_byname(pdev, "msi"); + if (pp->msi_irq <= 0) { + dev_err(&pdev->dev, "failed to get MSI irq\n"); + return -ENODEV; + } + + ret = devm_request_irq(&pdev->dev, pp->msi_irq, + imx6_pcie_msi_handler, + IRQF_SHARED, "mx6-pcie-msi", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request MSI irq\n"); + return -ENODEV; + } + } + pp->root_bus_nr = -1; pp->ops = &imx6_pcie_host_ops; -- cgit v1.2.3-70-g09d2 From ce292991d88b77160f348fb8a3a2cf6e78f4b456 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 22 Nov 2013 16:14:41 +0000 Subject: PCI: generic: Add generic PCI host controller driver Add support for a generic PCI host controller, such as a firmware-initialised device with static windows or an emulation by something such as kvmtool. The controller itself has no configuration registers and has its address spaces described entirely by the device-tree (using the bindings from ePAPR). Both CAM and ECAM are supported for Config Space accesses. Add corresponding documentation for the DT binding. [bhelgaas: currently uses the ARM-specific pci_common_init_dev() interface] Signed-off-by: Will Deacon Signed-off-by: Bjorn Helgaas Reviewed-by: Liviu Dudau --- .../devicetree/bindings/pci/host-generic-pci.txt | 100 ++++++ drivers/pci/host/Kconfig | 7 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-host-generic.c | 388 +++++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/host-generic-pci.txt create mode 100644 drivers/pci/host/pci-host-generic.c (limited to 'drivers/pci') diff --git a/Documentation/devicetree/bindings/pci/host-generic-pci.txt b/Documentation/devicetree/bindings/pci/host-generic-pci.txt new file mode 100644 index 00000000000..f0b0436807b --- /dev/null +++ b/Documentation/devicetree/bindings/pci/host-generic-pci.txt @@ -0,0 +1,100 @@ +* Generic PCI host controller + +Firmware-initialised PCI host controllers and PCI emulations, such as the +virtio-pci implementations found in kvmtool and other para-virtualised +systems, do not require driver support for complexities such as regulator +and clock management. In fact, the controller may not even require the +configuration of a control interface by the operating system, instead +presenting a set of fixed windows describing a subset of IO, Memory and +Configuration Spaces. + +Such a controller can be described purely in terms of the standardized device +tree bindings communicated in pci.txt: + + +Properties of the host controller node: + +- compatible : Must be "pci-host-cam-generic" or "pci-host-ecam-generic" + depending on the layout of configuration space (CAM vs + ECAM respectively). + +- device_type : Must be "pci". + +- ranges : As described in IEEE Std 1275-1994, but must provide + at least a definition of non-prefetchable memory. One + or both of prefetchable Memory and IO Space may also + be provided. + +- bus-range : Optional property (also described in IEEE Std 1275-1994) + to indicate the range of bus numbers for this controller. + If absent, defaults to <0 255> (i.e. all buses). + +- #address-cells : Must be 3. + +- #size-cells : Must be 2. + +- reg : The Configuration Space base address and size, as accessed + from the parent bus. + + +Properties of the /chosen node: + +- linux,pci-probe-only + : Optional property which takes a single-cell argument. + If '0', then Linux will assign devices in its usual manner, + otherwise it will not try to assign devices and instead use + them as they are configured already. + +Configuration Space is assumed to be memory-mapped (as opposed to being +accessed via an ioport) and laid out with a direct correspondence to the +geography of a PCI bus address by concatenating the various components to +form an offset. + +For CAM, this 24-bit offset is: + + cfg_offset(bus, device, function, register) = + bus << 16 | device << 11 | function << 8 | register + +Whilst ECAM extends this by 4 bits to accomodate 4k of function space: + + cfg_offset(bus, device, function, register) = + bus << 20 | device << 15 | function << 12 | register + +Interrupt mapping is exactly as described in `Open Firmware Recommended +Practice: Interrupt Mapping' and requires the following properties: + +- #interrupt-cells : Must be 1 + +- interrupt-map : + +- interrupt-map-mask : + + +Example: + +pci { + compatible = "pci-host-cam-generic" + device_type = "pci"; + #address-cells = <3>; + #size-cells = <2>; + bus-range = <0x0 0x1>; + + // CPU_PHYSICAL(2) SIZE(2) + reg = <0x0 0x40000000 0x0 0x1000000>; + + // BUS_ADDRESS(3) CPU_PHYSICAL(2) SIZE(2) + ranges = <0x01000000 0x0 0x01000000 0x0 0x01000000 0x0 0x00010000>, + <0x02000000 0x0 0x41000000 0x0 0x41000000 0x0 0x3f000000>; + + + #interrupt-cells = <0x1>; + + // PCI_DEVICE(3) INT#(1) CONTROLLER(PHANDLE) CONTROLLER_DATA(3) + interrupt-map = < 0x0 0x0 0x0 0x1 &gic 0x0 0x4 0x1 + 0x800 0x0 0x0 0x1 &gic 0x0 0x5 0x1 + 0x1000 0x0 0x0 0x1 &gic 0x0 0x6 0x1 + 0x1800 0x0 0x0 0x1 &gic 0x0 0x7 0x1>; + + // PCI_DEVICE(3) INT#(1) + interrupt-map-mask = <0xf800 0x0 0x0 0x7>; +} diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index a6f67ec8882..32d446effbb 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -33,4 +33,11 @@ config PCI_RCAR_GEN2 There are 3 internal PCI controllers available with a single built-in EHCI/OHCI host controller present on each one. +config PCI_HOST_GENERIC + bool "Generic PCI host controller" + depends on ARM && OF + help + Say Y here if you want to support a simple generic PCI host + controller, such as the one emulated by kvmtool. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 13fb3333aa0..bd1bf1ab4ac 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_PCI_IMX6) += pci-imx6.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o +obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c new file mode 100644 index 00000000000..44fe6aa6a43 --- /dev/null +++ b/drivers/pci/host/pci-host-generic.c @@ -0,0 +1,388 @@ +/* + * Simple, generic PCI host controller driver targetting firmware-initialised + * systems and virtual machines (e.g. the PCI emulation provided by kvmtool). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon + */ + +#include +#include +#include +#include +#include + +struct gen_pci_cfg_bus_ops { + u32 bus_shift; + void __iomem *(*map_bus)(struct pci_bus *, unsigned int, int); +}; + +struct gen_pci_cfg_windows { + struct resource res; + struct resource bus_range; + void __iomem **win; + + const struct gen_pci_cfg_bus_ops *ops; +}; + +struct gen_pci { + struct pci_host_bridge host; + struct gen_pci_cfg_windows cfg; + struct list_head resources; +}; + +static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus, + unsigned int devfn, + int where) +{ + struct pci_sys_data *sys = bus->sysdata; + struct gen_pci *pci = sys->private_data; + resource_size_t idx = bus->number - pci->cfg.bus_range.start; + + return pci->cfg.win[idx] + ((devfn << 8) | where); +} + +static struct gen_pci_cfg_bus_ops gen_pci_cfg_cam_bus_ops = { + .bus_shift = 16, + .map_bus = gen_pci_map_cfg_bus_cam, +}; + +static void __iomem *gen_pci_map_cfg_bus_ecam(struct pci_bus *bus, + unsigned int devfn, + int where) +{ + struct pci_sys_data *sys = bus->sysdata; + struct gen_pci *pci = sys->private_data; + resource_size_t idx = bus->number - pci->cfg.bus_range.start; + + return pci->cfg.win[idx] + ((devfn << 12) | where); +} + +static struct gen_pci_cfg_bus_ops gen_pci_cfg_ecam_bus_ops = { + .bus_shift = 20, + .map_bus = gen_pci_map_cfg_bus_ecam, +}; + +static int gen_pci_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + void __iomem *addr; + struct pci_sys_data *sys = bus->sysdata; + struct gen_pci *pci = sys->private_data; + + addr = pci->cfg.ops->map_bus(bus, devfn, where); + + switch (size) { + case 1: + *val = readb(addr); + break; + case 2: + *val = readw(addr); + break; + default: + *val = readl(addr); + } + + return PCIBIOS_SUCCESSFUL; +} + +static int gen_pci_config_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + void __iomem *addr; + struct pci_sys_data *sys = bus->sysdata; + struct gen_pci *pci = sys->private_data; + + addr = pci->cfg.ops->map_bus(bus, devfn, where); + + switch (size) { + case 1: + writeb(val, addr); + break; + case 2: + writew(val, addr); + break; + default: + writel(val, addr); + } + + return PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops gen_pci_ops = { + .read = gen_pci_config_read, + .write = gen_pci_config_write, +}; + +static const struct of_device_id gen_pci_of_match[] = { + { .compatible = "pci-host-cam-generic", + .data = &gen_pci_cfg_cam_bus_ops }, + + { .compatible = "pci-host-ecam-generic", + .data = &gen_pci_cfg_ecam_bus_ops }, + + { }, +}; +MODULE_DEVICE_TABLE(of, gen_pci_of_match); + +static int gen_pci_calc_io_offset(struct device *dev, + struct of_pci_range *range, + struct resource *res, + resource_size_t *offset) +{ + static atomic_t wins = ATOMIC_INIT(0); + int err, idx, max_win; + unsigned int window; + + if (!PAGE_ALIGNED(range->cpu_addr)) + return -EINVAL; + + max_win = (IO_SPACE_LIMIT + 1) / SZ_64K; + idx = atomic_inc_return(&wins); + if (idx > max_win) + return -ENOSPC; + + window = (idx - 1) * SZ_64K; + err = pci_ioremap_io(window, range->cpu_addr); + if (err) + return err; + + of_pci_range_to_resource(range, dev->of_node, res); + res->start = window; + res->end = res->start + range->size - 1; + *offset = window - range->pci_addr; + return 0; +} + +static int gen_pci_calc_mem_offset(struct device *dev, + struct of_pci_range *range, + struct resource *res, + resource_size_t *offset) +{ + of_pci_range_to_resource(range, dev->of_node, res); + *offset = range->cpu_addr - range->pci_addr; + return 0; +} + +static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) +{ + struct pci_host_bridge_window *win; + + list_for_each_entry(win, &pci->resources, list) + release_resource(win->res); + + pci_free_resource_list(&pci->resources); +} + +static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) +{ + struct of_pci_range range; + struct of_pci_range_parser parser; + int err, res_valid = 0; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + + if (of_pci_range_parser_init(&parser, np)) { + dev_err(dev, "missing \"ranges\" property\n"); + return -EINVAL; + } + + for_each_of_pci_range(&parser, &range) { + struct resource *parent, *res; + resource_size_t offset; + u32 restype = range.flags & IORESOURCE_TYPE_BITS; + + res = devm_kmalloc(dev, sizeof(*res), GFP_KERNEL); + if (!res) { + err = -ENOMEM; + goto out_release_res; + } + + switch (restype) { + case IORESOURCE_IO: + parent = &ioport_resource; + err = gen_pci_calc_io_offset(dev, &range, res, &offset); + break; + case IORESOURCE_MEM: + parent = &iomem_resource; + err = gen_pci_calc_mem_offset(dev, &range, res, &offset); + res_valid |= !(res->flags & IORESOURCE_PREFETCH || err); + break; + default: + err = -EINVAL; + continue; + } + + if (err) { + dev_warn(dev, + "error %d: failed to add resource [type 0x%x, %lld bytes]\n", + err, restype, range.size); + continue; + } + + err = request_resource(parent, res); + if (err) + goto out_release_res; + + pci_add_resource_offset(&pci->resources, res, offset); + } + + if (!res_valid) { + dev_err(dev, "non-prefetchable memory resource required\n"); + err = -EINVAL; + goto out_release_res; + } + + return 0; + +out_release_res: + gen_pci_release_of_pci_ranges(pci); + return err; +} + +static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) +{ + int err; + u8 bus_max; + resource_size_t busn; + struct resource *bus_range; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + + if (of_pci_parse_bus_range(np, &pci->cfg.bus_range)) + pci->cfg.bus_range = (struct resource) { + .name = np->name, + .start = 0, + .end = 0xff, + .flags = IORESOURCE_BUS, + }; + + err = of_address_to_resource(np, 0, &pci->cfg.res); + if (err) { + dev_err(dev, "missing \"reg\" property\n"); + return err; + } + + pci->cfg.win = devm_kcalloc(dev, resource_size(&pci->cfg.bus_range), + sizeof(*pci->cfg.win), GFP_KERNEL); + if (!pci->cfg.win) + return -ENOMEM; + + /* Limit the bus-range to fit within reg */ + bus_max = pci->cfg.bus_range.start + + (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; + pci->cfg.bus_range.end = min_t(resource_size_t, pci->cfg.bus_range.end, + bus_max); + + /* Map our Configuration Space windows */ + if (!devm_request_mem_region(dev, pci->cfg.res.start, + resource_size(&pci->cfg.res), + "Configuration Space")) + return -ENOMEM; + + bus_range = &pci->cfg.bus_range; + for (busn = bus_range->start; busn <= bus_range->end; ++busn) { + u32 idx = busn - bus_range->start; + u32 sz = 1 << pci->cfg.ops->bus_shift; + + pci->cfg.win[idx] = devm_ioremap(dev, + pci->cfg.res.start + busn * sz, + sz); + if (!pci->cfg.win[idx]) + return -ENOMEM; + } + + /* Register bus resource */ + pci_add_resource(&pci->resources, bus_range); + return 0; +} + +static int gen_pci_setup(int nr, struct pci_sys_data *sys) +{ + struct gen_pci *pci = sys->private_data; + list_splice_init(&pci->resources, &sys->resources); + return 1; +} + +static int gen_pci_probe(struct platform_device *pdev) +{ + int err; + const char *type; + const struct of_device_id *of_id; + const int *prop; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + struct hw_pci hw = { + .nr_controllers = 1, + .private_data = (void **)&pci, + .setup = gen_pci_setup, + .map_irq = of_irq_parse_and_map_pci, + .ops = &gen_pci_ops, + }; + + if (!pci) + return -ENOMEM; + + type = of_get_property(np, "device_type", NULL); + if (!type || strcmp(type, "pci")) { + dev_err(dev, "invalid \"device_type\" %s\n", type); + return -EINVAL; + } + + prop = of_get_property(of_chosen, "linux,pci-probe-only", NULL); + if (prop) { + if (*prop) + pci_add_flags(PCI_PROBE_ONLY); + else + pci_clear_flags(PCI_PROBE_ONLY); + } + + of_id = of_match_node(gen_pci_of_match, np); + pci->cfg.ops = of_id->data; + pci->host.dev.parent = dev; + INIT_LIST_HEAD(&pci->host.windows); + INIT_LIST_HEAD(&pci->resources); + + /* Parse our PCI ranges and request their resources */ + err = gen_pci_parse_request_of_pci_ranges(pci); + if (err) + return err; + + /* Parse and map our Configuration Space windows */ + err = gen_pci_parse_map_cfg_windows(pci); + if (err) { + gen_pci_release_of_pci_ranges(pci); + return err; + } + + pci_common_init_dev(dev, &hw); + return 0; +} + +static struct platform_driver gen_pci_driver = { + .driver = { + .name = "pci-host-generic", + .owner = THIS_MODULE, + .of_match_table = gen_pci_of_match, + }, + .probe = gen_pci_probe, +}; +module_platform_driver(gen_pci_driver); + +MODULE_DESCRIPTION("Generic PCI host driver"); +MODULE_AUTHOR("Will Deacon "); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3-70-g09d2