summaryrefslogtreecommitdiffstats
path: root/drivers/pci/pcie
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-05-19 13:10:54 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-05-19 13:10:54 -0700
commit7afd16f882887c9adc69cd1794f5e57777723217 (patch)
tree73683d08184a227cf627230d8b887a92c51a32e5 /drivers/pci/pcie
parenta37571a29eca963562ff5a9233db4a5c73c72cf9 (diff)
parente257ef55ce51d7ec399193ee85acda8b8759d930 (diff)
downloadlinux-7afd16f882887c9adc69cd1794f5e57777723217.tar.bz2
Merge tag 'pci-v4.7-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci
Pull PCI updates from Bjorn Helgaas: "Enumeration: - Refine PCI support check in pcibios_init() (Adrian-Ken Rueegsegger) - Provide common functions for ECAM mapping (Jayachandran C) - Allow all PCIe services on non-ACPI host bridges (Jon Derrick) - Remove return values from pcie_port_platform_notify() and relatives (Jon Derrick) - Widen portdrv service type from 4 bits to 8 bits (Keith Busch) - Add Downstream Port Containment portdrv service type (Keith Busch) - Add Downstream Port Containment driver (Keith Busch) Resource management: - Identify Enhanced Allocation (EA) BAR Equivalent resources in sysfs (Alex Williamson) - Supply CPU physical address (not bus address) to iomem_is_exclusive() (Bjorn Helgaas) - alpha: Call iomem_is_exclusive() for IORESOURCE_MEM, but not IORESOURCE_IO (Bjorn Helgaas) - Mark Broadwell-EP Home Agent 1 as having non-compliant BARs (Prarit Bhargava) - Disable all BAR sizing for devices with non-compliant BARs (Prarit Bhargava) - Move PCI I/O space management from OF to PCI core code (Tomasz Nowicki) PCI device hotplug: - acpiphp_ibm: Avoid uninitialized variable reference (Dan Carpenter) - Use cached copy of PCI_EXP_SLTCAP_HPC bit (Lukas Wunner) Virtualization: - Mark Intel i40e NIC INTx masking as broken (Alex Williamson) - Reverse standard ACS vs device-specific ACS enabling (Alex Williamson) - Work around Intel Sunrise Point PCH incorrect ACS capability (Alex Williamson) IOMMU: - Add pci_add_dma_alias() to abstract implementation (Bjorn Helgaas) - Move informational printk to pci_add_dma_alias() (Bjorn Helgaas) - Add support for multiple DMA aliases (Jacek Lawrynowicz) - Add DMA alias quirk for mic_x200_dma (Jacek Lawrynowicz) Thunderbolt: - Fix double free of drom buffer (Andreas Noever) - Add Intel Thunderbolt device IDs (Lukas Wunner) - Fix typos and magic number (Lukas Wunner) - Support 1st gen Light Ridge controller (Lukas Wunner) Generic host bridge driver: - Use generic ECAM API (Jayachandran C) Cavium ThunderX host bridge driver: - Don't clobber read-only bits in bridge config registers (David Daney) - Use generic ECAM API (Jayachandran C) Freescale i.MX6 host bridge driver: - Use enum instead of bool for variant indicator (Andrey Smirnov) - Implement reset sequence for i.MX6+ (Andrey Smirnov) - Factor out ref clock enable (Bjorn Helgaas) - Add initial imx6sx support (Christoph Fritz) - Add reset-gpio-active-high boolean property to DT (Petr Štetiar) - Add DT property for link gen, default to Gen1 (Tim Harvey) - dts: Specify imx6qp version of PCIe core (Andrey Smirnov) - dts: Fix PCIe reset GPIO polarity on Toradex Apalis Ixora (Petr Štetiar) Marvell Armada host bridge driver: - add DT binding for Marvell Armada 7K/8K PCIe controller (Thomas Petazzoni) - Add driver for Marvell Armada 7K/8K PCIe controller (Thomas Petazzoni) Marvell MVEBU host bridge driver: - Constify mvebu_pcie_pm_ops structure (Jisheng Zhang) - Use SET_NOIRQ_SYSTEM_SLEEP_PM_OPS for mvebu_pcie_pm_ops (Jisheng Zhang) Microsoft Hyper-V host bridge driver: - Report resources release after stopping the bus (Vitaly Kuznetsov) - Add explicit barriers to config space access (Vitaly Kuznetsov) Renesas R-Car host bridge driver: - Select PCI_MSI_IRQ_DOMAIN (Arnd Bergmann) Synopsys DesignWare host bridge driver: - Remove incorrect RC memory base/limit configuration (Gabriele Paoloni) - Move Root Complex setup code to dw_pcie_setup_rc() (Jisheng Zhang) TI Keystone host bridge driver: - Add error IRQ handler (Murali Karicheri) - Remove unnecessary goto statement (Murali Karicheri) Miscellaneous: - Fix spelling errors (Colin Ian King)" * tag 'pci-v4.7-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci: (48 commits) PCI: Disable all BAR sizing for devices with non-compliant BARs x86/PCI: Mark Broadwell-EP Home Agent 1 as having non-compliant BARs PCI: Identify Enhanced Allocation (EA) BAR Equivalent resources in sysfs PCI, of: Move PCI I/O space management to PCI core code PCI: generic, thunder: Use generic ECAM API PCI: Provide common functions for ECAM mapping PCI: hv: Add explicit barriers to config space access PCI: Use cached copy of PCI_EXP_SLTCAP_HPC bit PCI: Add Downstream Port Containment driver PCI: Add Downstream Port Containment portdrv service type PCI: Widen portdrv service type from 4 bits to 8 bits PCI: designware: Remove incorrect RC memory base/limit configuration PCI: hv: Report resources release after stopping the bus ARM: dts: imx6qp: Specify imx6qp version of PCIe core PCI: imx6: Implement reset sequence for i.MX6+ PCI: imx6: Use enum instead of bool for variant indicator PCI: thunder: Don't clobber read-only bits in bridge config registers thunderbolt: Fix double free of drom buffer PCI: rcar: Select PCI_MSI_IRQ_DOMAIN PCI: armada: Add driver for Marvell Armada 7K/8K PCIe controller ...
Diffstat (limited to 'drivers/pci/pcie')
-rw-r--r--drivers/pci/pcie/Kconfig14
-rw-r--r--drivers/pci/pcie/Makefile2
-rw-r--r--drivers/pci/pcie/pcie-dpc.c163
-rw-r--r--drivers/pci/pcie/portdrv.h15
-rw-r--r--drivers/pci/pcie/portdrv_acpi.c12
-rw-r--r--drivers/pci/pcie/portdrv_core.c36
6 files changed, 204 insertions, 38 deletions
diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig
index 72db7f4209ca..22ca6412bd15 100644
--- a/drivers/pci/pcie/Kconfig
+++ b/drivers/pci/pcie/Kconfig
@@ -81,3 +81,17 @@ endchoice
config PCIE_PME
def_bool y
depends on PCIEPORTBUS && PM
+
+config PCIE_DPC
+ tristate "PCIe Downstream Port Containment support"
+ depends on PCIEPORTBUS
+ default n
+ help
+ This enables PCI Express Downstream Port Containment (DPC)
+ driver support. DPC events from Root and Downstream ports
+ will be handled by the DPC driver. If your system doesn't
+ have this capability or you do not want to use this feature,
+ it is safe to answer N.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pcie-dpc.
diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile
index 00c62df5a9fc..b24525b3dec1 100644
--- a/drivers/pci/pcie/Makefile
+++ b/drivers/pci/pcie/Makefile
@@ -14,3 +14,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o
obj-$(CONFIG_PCIEAER) += aer/
obj-$(CONFIG_PCIE_PME) += pme.o
+
+obj-$(CONFIG_PCIE_DPC) += pcie-dpc.o
diff --git a/drivers/pci/pcie/pcie-dpc.c b/drivers/pci/pcie/pcie-dpc.c
new file mode 100644
index 000000000000..ab552f1bc08f
--- /dev/null
+++ b/drivers/pci/pcie/pcie-dpc.c
@@ -0,0 +1,163 @@
+/*
+ * PCI Express Downstream Port Containment services driver
+ * Copyright (C) 2016 Intel Corp.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pcieport_if.h>
+
+struct dpc_dev {
+ struct pcie_device *dev;
+ struct work_struct work;
+ int cap_pos;
+};
+
+static void dpc_wait_link_inactive(struct pci_dev *pdev)
+{
+ unsigned long timeout = jiffies + HZ;
+ u16 lnk_status;
+
+ pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
+ while (lnk_status & PCI_EXP_LNKSTA_DLLLA &&
+ !time_after(jiffies, timeout)) {
+ msleep(10);
+ pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnk_status);
+ }
+ if (lnk_status & PCI_EXP_LNKSTA_DLLLA)
+ dev_warn(&pdev->dev, "Link state not disabled for DPC event");
+}
+
+static void interrupt_event_handler(struct work_struct *work)
+{
+ struct dpc_dev *dpc = container_of(work, struct dpc_dev, work);
+ struct pci_dev *dev, *temp, *pdev = dpc->dev->port;
+ struct pci_bus *parent = pdev->subordinate;
+
+ pci_lock_rescan_remove();
+ list_for_each_entry_safe_reverse(dev, temp, &parent->devices,
+ bus_list) {
+ pci_dev_get(dev);
+ pci_stop_and_remove_bus_device(dev);
+ pci_dev_put(dev);
+ }
+ pci_unlock_rescan_remove();
+
+ dpc_wait_link_inactive(pdev);
+ pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_STATUS,
+ PCI_EXP_DPC_STATUS_TRIGGER | PCI_EXP_DPC_STATUS_INTERRUPT);
+}
+
+static irqreturn_t dpc_irq(int irq, void *context)
+{
+ struct dpc_dev *dpc = (struct dpc_dev *)context;
+ struct pci_dev *pdev = dpc->dev->port;
+ u16 status, source;
+
+ pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_STATUS, &status);
+ pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_SOURCE_ID,
+ &source);
+ if (!status)
+ return IRQ_NONE;
+
+ dev_info(&dpc->dev->device, "DPC containment event, status:%#06x source:%#06x\n",
+ status, source);
+
+ if (status & PCI_EXP_DPC_STATUS_TRIGGER) {
+ u16 reason = (status >> 1) & 0x3;
+
+ dev_warn(&dpc->dev->device, "DPC %s triggered, remove downstream devices\n",
+ (reason == 0) ? "unmasked uncorrectable error" :
+ (reason == 1) ? "ERR_NONFATAL" :
+ (reason == 2) ? "ERR_FATAL" : "extended error");
+ schedule_work(&dpc->work);
+ }
+ return IRQ_HANDLED;
+}
+
+#define FLAG(x, y) (((x) & (y)) ? '+' : '-')
+static int dpc_probe(struct pcie_device *dev)
+{
+ struct dpc_dev *dpc;
+ struct pci_dev *pdev = dev->port;
+ int status;
+ u16 ctl, cap;
+
+ dpc = kzalloc(sizeof(*dpc), GFP_KERNEL);
+ if (!dpc)
+ return -ENOMEM;
+
+ dpc->cap_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC);
+ dpc->dev = dev;
+ INIT_WORK(&dpc->work, interrupt_event_handler);
+ set_service_data(dev, dpc);
+
+ status = request_irq(dev->irq, dpc_irq, IRQF_SHARED, "pcie-dpc", dpc);
+ if (status) {
+ dev_warn(&dev->device, "request IRQ%d failed: %d\n", dev->irq,
+ status);
+ goto out;
+ }
+
+ pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CAP, &cap);
+ pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl);
+
+ ctl |= PCI_EXP_DPC_CTL_EN_NONFATAL | PCI_EXP_DPC_CTL_INT_EN;
+ pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl);
+
+ dev_info(&dev->device, "DPC error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n",
+ cap & 0xf, FLAG(cap, PCI_EXP_DPC_CAP_RP_EXT),
+ FLAG(cap, PCI_EXP_DPC_CAP_POISONED_TLP),
+ FLAG(cap, PCI_EXP_DPC_CAP_SW_TRIGGER), (cap >> 8) & 0xf,
+ FLAG(cap, PCI_EXP_DPC_CAP_DL_ACTIVE));
+ return status;
+ out:
+ kfree(dpc);
+ return status;
+}
+
+static void dpc_remove(struct pcie_device *dev)
+{
+ struct dpc_dev *dpc = get_service_data(dev);
+ struct pci_dev *pdev = dev->port;
+ u16 ctl;
+
+ pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl);
+ ctl &= ~(PCI_EXP_DPC_CTL_EN_NONFATAL | PCI_EXP_DPC_CTL_INT_EN);
+ pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl);
+
+ free_irq(dev->irq, dpc);
+ kfree(dpc);
+}
+
+static struct pcie_port_service_driver dpcdriver = {
+ .name = "dpc",
+ .port_type = PCI_EXP_TYPE_ROOT_PORT | PCI_EXP_TYPE_DOWNSTREAM,
+ .service = PCIE_PORT_SERVICE_DPC,
+ .probe = dpc_probe,
+ .remove = dpc_remove,
+};
+
+static int __init dpc_service_init(void)
+{
+ return pcie_port_service_register(&dpcdriver);
+}
+
+static void __exit dpc_service_exit(void)
+{
+ pcie_port_service_unregister(&dpcdriver);
+}
+
+MODULE_DESCRIPTION("PCI Express Downstream Port Containment driver");
+MODULE_AUTHOR("Keith Busch <keith.busch@intel.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1");
+
+module_init(dpc_service_init);
+module_exit(dpc_service_exit);
diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h
index d525548404d6..587aef36030d 100644
--- a/drivers/pci/pcie/portdrv.h
+++ b/drivers/pci/pcie/portdrv.h
@@ -11,14 +11,14 @@
#include <linux/compiler.h>
-#define PCIE_PORT_DEVICE_MAXSERVICES 4
+#define PCIE_PORT_DEVICE_MAXSERVICES 5
/*
* According to the PCI Express Base Specification 2.0, the indices of
* the MSI-X table entries used by port services must not exceed 31
*/
#define PCIE_PORT_MAX_MSIX_ENTRIES 32
-#define get_descriptor_id(type, service) (((type - 4) << 4) | service)
+#define get_descriptor_id(type, service) (((type - 4) << 8) | service)
extern struct bus_type pcie_port_bus_type;
int pcie_port_device_register(struct pci_dev *dev);
@@ -67,17 +67,14 @@ static inline void pcie_pme_interrupt_enable(struct pci_dev *dev, bool en) {}
#endif /* !CONFIG_PCIE_PME */
#ifdef CONFIG_ACPI
-int pcie_port_acpi_setup(struct pci_dev *port, int *mask);
+void pcie_port_acpi_setup(struct pci_dev *port, int *mask);
-static inline int pcie_port_platform_notify(struct pci_dev *port, int *mask)
+static inline void pcie_port_platform_notify(struct pci_dev *port, int *mask)
{
- return pcie_port_acpi_setup(port, mask);
+ pcie_port_acpi_setup(port, mask);
}
#else /* !CONFIG_ACPI */
-static inline int pcie_port_platform_notify(struct pci_dev *port, int *mask)
-{
- return 0;
-}
+static inline void pcie_port_platform_notify(struct pci_dev *port, int *mask){}
#endif /* !CONFIG_ACPI */
#endif /* _PORTDRV_H_ */
diff --git a/drivers/pci/pcie/portdrv_acpi.c b/drivers/pci/pcie/portdrv_acpi.c
index b4d2894ee3fc..6b8c2f1d0e71 100644
--- a/drivers/pci/pcie/portdrv_acpi.c
+++ b/drivers/pci/pcie/portdrv_acpi.c
@@ -32,32 +32,30 @@
* NOTE: It turns out that we cannot do that for individual port services
* separately, because that would make some systems work incorrectly.
*/
-int pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask)
+void pcie_port_acpi_setup(struct pci_dev *port, int *srv_mask)
{
struct acpi_pci_root *root;
acpi_handle handle;
u32 flags;
if (acpi_pci_disabled)
- return 0;
+ return;
handle = acpi_find_root_bridge_handle(port);
if (!handle)
- return -EINVAL;
+ return;
root = acpi_pci_find_root(handle);
if (!root)
- return -ENODEV;
+ return;
flags = root->osc_control_set;
- *srv_mask = PCIE_PORT_SERVICE_VC;
+ *srv_mask = PCIE_PORT_SERVICE_VC | PCIE_PORT_SERVICE_DPC;
if (flags & OSC_PCI_EXPRESS_NATIVE_HP_CONTROL)
*srv_mask |= PCIE_PORT_SERVICE_HP;
if (flags & OSC_PCI_EXPRESS_PME_CONTROL)
*srv_mask |= PCIE_PORT_SERVICE_PME;
if (flags & OSC_PCI_EXPRESS_AER_CONTROL)
*srv_mask |= PCIE_PORT_SERVICE_AER;
-
- return 0;
}
diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c
index 88122dc2e1b1..32d4d0a3d20e 100644
--- a/drivers/pci/pcie/portdrv_core.c
+++ b/drivers/pci/pcie/portdrv_core.c
@@ -254,38 +254,28 @@ static void cleanup_service_irqs(struct pci_dev *dev)
static int get_port_device_capability(struct pci_dev *dev)
{
int services = 0;
- u32 reg32;
int cap_mask = 0;
- int err;
if (pcie_ports_disabled)
return 0;
cap_mask = PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP
- | PCIE_PORT_SERVICE_VC;
+ | PCIE_PORT_SERVICE_VC | PCIE_PORT_SERVICE_DPC;
if (pci_aer_available())
cap_mask |= PCIE_PORT_SERVICE_AER;
- if (pcie_ports_auto) {
- err = pcie_port_platform_notify(dev, &cap_mask);
- if (err)
- return 0;
- }
+ if (pcie_ports_auto)
+ pcie_port_platform_notify(dev, &cap_mask);
/* Hot-Plug Capable */
- if ((cap_mask & PCIE_PORT_SERVICE_HP) &&
- pcie_caps_reg(dev) & PCI_EXP_FLAGS_SLOT) {
- pcie_capability_read_dword(dev, PCI_EXP_SLTCAP, &reg32);
- if (reg32 & PCI_EXP_SLTCAP_HPC) {
- services |= PCIE_PORT_SERVICE_HP;
- /*
- * Disable hot-plug interrupts in case they have been
- * enabled by the BIOS and the hot-plug service driver
- * is not loaded.
- */
- pcie_capability_clear_word(dev, PCI_EXP_SLTCTL,
- PCI_EXP_SLTCTL_CCIE | PCI_EXP_SLTCTL_HPIE);
- }
+ if ((cap_mask & PCIE_PORT_SERVICE_HP) && dev->is_hotplug_bridge) {
+ services |= PCIE_PORT_SERVICE_HP;
+ /*
+ * Disable hot-plug interrupts in case they have been enabled
+ * by the BIOS and the hot-plug service driver is not loaded.
+ */
+ pcie_capability_clear_word(dev, PCI_EXP_SLTCTL,
+ PCI_EXP_SLTCTL_CCIE | PCI_EXP_SLTCTL_HPIE);
}
/* AER capable */
if ((cap_mask & PCIE_PORT_SERVICE_AER)
@@ -311,6 +301,8 @@ static int get_port_device_capability(struct pci_dev *dev)
*/
pcie_pme_interrupt_enable(dev, false);
}
+ if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DPC))
+ services |= PCIE_PORT_SERVICE_DPC;
return services;
}
@@ -338,7 +330,7 @@ static int pcie_device_init(struct pci_dev *pdev, int service, int irq)
device = &pcie->device;
device->bus = &pcie_port_bus_type;
device->release = release_pcie_device; /* callback to free pcie dev */
- dev_set_name(device, "%s:pcie%02x",
+ dev_set_name(device, "%s:pcie%03x",
pci_name(pdev),
get_descriptor_id(pci_pcie_type(pdev), service));
device->parent = &pdev->dev;