From ad618c9983be3136e9e7209ffd7c4102720d0739 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Tue, 9 Aug 2016 11:53:11 +0200 Subject: PCI: Drop CONFIG_KEXEC_CORE ifdeffery Drop the CONFIG_KEXEC_CORE #ifdef around reference to "kexec_in_progress". Commit 2b94ed245861 ("kexec: define kexec_in_progress in !CONFIG_KEXEC case") has made this unnecessary. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index e39a67c..edd78e0 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -466,7 +466,6 @@ static void pci_device_shutdown(struct device *dev) pci_msi_shutdown(pci_dev); pci_msix_shutdown(pci_dev); -#ifdef CONFIG_KEXEC_CORE /* * If this is a kexec reboot, turn off Bus Master bit on the * device to tell it to not continue to do DMA. Don't touch @@ -476,7 +475,6 @@ static void pci_device_shutdown(struct device *dev) */ if (kexec_in_progress && (pci_dev->current_state <= PCI_D3hot)) pci_clear_master(pci_dev); -#endif } #ifdef CONFIG_PM -- cgit v0.10.2 From 9bb04a0c4e261187be904d05c2bcd1da0eebc20c Mon Sep 17 00:00:00 2001 From: Jonathan Yong Date: Sat, 11 Jun 2016 14:13:38 -0500 Subject: PCI: Add Precision Time Measurement (PTM) support Add Precision Time Measurement (PTM) support (see PCIe r3.1, sec 6.22). Enable PTM on PTM Root devices and switch ports. This does not enable PTM on endpoints. There currently are no PTM-capable devices on the market, but it is expected to be supported by the Intel Apollo Lake platform. [bhelgaas: complete rework] Signed-off-by: Jonathan Yong Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9730c47..194521b 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -332,6 +332,12 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, void pci_enable_acs(struct pci_dev *dev); +#ifdef CONFIG_PCIE_PTM +void pci_ptm_init(struct pci_dev *dev); +#else +static inline void pci_ptm_init(struct pci_dev *dev) { } +#endif + struct pci_dev_reset_methods { u16 vendor; u16 device; diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 7fcea75..7ce7763 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -92,3 +92,14 @@ config PCIE_DPC 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. + +config PCIE_PTM + bool "PCIe Precision Time Measurement support" + default n + depends on PCIEPORTBUS + help + This enables PCI Express Precision Time Measurement (PTM) + support. + + This is only useful if you have devices that support PTM, but it + is safe to enable even if you don't. diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index b24525b..36e35ea 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_PCIEAER) += aer/ obj-$(CONFIG_PCIE_PME) += pme.o obj-$(CONFIG_PCIE_DPC) += pcie-dpc.o +obj-$(CONFIG_PCIE_PTM) += ptm.o diff --git a/drivers/pci/pcie/ptm.c b/drivers/pci/pcie/ptm.c new file mode 100644 index 0000000..48eea4e --- /dev/null +++ b/drivers/pci/pcie/ptm.c @@ -0,0 +1,70 @@ +/* + * PCI Express Precision Time Measurement + * Copyright (c) 2016, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include "../pci.h" + +static void pci_ptm_info(struct pci_dev *dev) +{ + dev_info(&dev->dev, "PTM enabled%s\n", dev->ptm_root ? " (root)" : ""); +} + +void pci_ptm_init(struct pci_dev *dev) +{ + int pos; + u32 cap, ctrl; + struct pci_dev *ups; + + if (!pci_is_pcie(dev)) + return; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); + if (!pos) + return; + + /* + * Enable PTM only on interior devices (root ports, switch ports, + * etc.) on the assumption that it causes no link traffic until an + * endpoint enables it. + */ + if ((pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT || + pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END)) + return; + + pci_read_config_dword(dev, pos + PCI_PTM_CAP, &cap); + + /* + * There's no point in enabling PTM unless it's enabled in the + * upstream device or this device can be a PTM Root itself. Per + * the spec recommendation (PCIe r3.1, sec 7.32.3), select the + * furthest upstream Time Source as the PTM Root. + */ + ups = pci_upstream_bridge(dev); + if (ups && ups->ptm_enabled) { + ctrl = PCI_PTM_CTRL_ENABLE; + } else { + if (cap & PCI_PTM_CAP_ROOT) { + ctrl = PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT; + dev->ptm_root = 1; + } else + return; + } + + pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); + dev->ptm_enabled = 1; + + pci_ptm_info(dev); +} diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 93f280d..e2e4244 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1667,6 +1667,9 @@ static void pci_init_capabilities(struct pci_dev *dev) pci_enable_acs(dev); pci_cleanup_aer_error_status_regs(dev); + + /* Precision Time Measurement */ + pci_ptm_init(dev); } /* diff --git a/include/linux/pci.h b/include/linux/pci.h index 2599a98..96c509f 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -367,6 +367,11 @@ struct pci_dev { int rom_attr_enabled; /* has display of the rom attribute been enabled? */ struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */ struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ + +#ifdef CONFIG_PCIE_PTM + unsigned int ptm_root:1; + unsigned int ptm_enabled:1; +#endif #ifdef CONFIG_PCI_MSI const struct attribute_group **msi_irq_groups; #endif diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 4040951..926fff4 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -671,7 +671,8 @@ #define PCI_EXT_CAP_ID_PMUX 0x1A /* Protocol Multiplexing */ #define PCI_EXT_CAP_ID_PASID 0x1B /* Process Address Space ID */ #define PCI_EXT_CAP_ID_DPC 0x1D /* Downstream Port Containment */ -#define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_DPC +#define PCI_EXT_CAP_ID_PTM 0x1F /* Precision Time Measurement */ +#define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_PTM #define PCI_EXT_CAP_DSN_SIZEOF 12 #define PCI_EXT_CAP_MCAST_ENDPOINT_SIZEOF 40 @@ -964,4 +965,11 @@ #define PCI_EXP_DPC_SOURCE_ID 10 /* DPC Source Identifier */ +/* Precision Time Measurement */ +#define PCI_PTM_CAP 0x04 /* PTM Capability */ +#define PCI_PTM_CAP_ROOT 0x00000004 /* Root capable */ +#define PCI_PTM_CTRL 0x08 /* PTM Control */ +#define PCI_PTM_CTRL_ENABLE 0x00000001 /* PTM enable */ +#define PCI_PTM_CTRL_ROOT 0x00000002 /* Root select */ + #endif /* LINUX_PCI_REGS_H */ -- cgit v0.10.2 From 446fc23fb6f0cab15011d7daae856091856a65cc Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 17 Aug 2016 14:17:58 -0500 Subject: PCI: designware: Return data directly from dw_pcie_readl_rc() dw_pcie_readl_rc() reads a u32 value. Previously we stored that value in space supplied by the caller. Return the u32 value directly instead. This makes the calling code read better and makes it obvious that the caller need not initialize the storage. In the following example it isn't clear whether "val" is initialized before being used: dw_pcie_readl_rc(pp, PCI_COMMAND, &val); if (val & PCI_COMMAND_MEMORY) ... No functional change intended. Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 2199761..490f8b6 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -425,12 +425,15 @@ static void exynos_pcie_enable_interrupts(struct pcie_port *pp) exynos_pcie_msi_init(pp); } -static inline void exynos_pcie_readl_rc(struct pcie_port *pp, - void __iomem *dbi_base, u32 *val) +static inline u32 exynos_pcie_readl_rc(struct pcie_port *pp, + void __iomem *dbi_base) { + u32 val; + exynos_pcie_sideband_dbi_r_mode(pp, true); - *val = readl(dbi_base); + val = readl(dbi_base); exynos_pcie_sideband_dbi_r_mode(pp, false); + return val; } static inline void exynos_pcie_writel_rc(struct pcie_port *pp, diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 12afce1..1f6bd6d 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -115,12 +115,12 @@ int dw_pcie_cfg_write(void __iomem *addr, int size, u32 val) return PCIBIOS_SUCCESSFUL; } -static inline void dw_pcie_readl_rc(struct pcie_port *pp, u32 reg, u32 *val) +static inline u32 dw_pcie_readl_rc(struct pcie_port *pp, u32 reg) { if (pp->ops->readl_rc) - pp->ops->readl_rc(pp, pp->dbi_base + reg, val); - else - *val = readl(pp->dbi_base + reg); + return pp->ops->readl_rc(pp, pp->dbi_base + reg); + + return readl(pp->dbi_base + reg); } static inline void dw_pcie_writel_rc(struct pcie_port *pp, u32 val, u32 reg) @@ -169,7 +169,7 @@ static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ - dw_pcie_readl_rc(pp, PCIE_ATU_CR2, &val); + val = dw_pcie_readl_rc(pp, PCIE_ATU_CR2); } static struct irq_chip dw_msi_irq_chip = { @@ -720,7 +720,7 @@ void dw_pcie_setup_rc(struct pcie_port *pp) u32 val; /* set the number of lanes */ - dw_pcie_readl_rc(pp, PCIE_PORT_LINK_CONTROL, &val); + val = dw_pcie_readl_rc(pp, PCIE_PORT_LINK_CONTROL); val &= ~PORT_LINK_MODE_MASK; switch (pp->lanes) { case 1: @@ -742,7 +742,7 @@ void dw_pcie_setup_rc(struct pcie_port *pp) dw_pcie_writel_rc(pp, val, PCIE_PORT_LINK_CONTROL); /* set link width speed control register */ - dw_pcie_readl_rc(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, &val); + val = dw_pcie_readl_rc(pp, PCIE_LINK_WIDTH_SPEED_CONTROL); val &= ~PORT_LOGIC_LINK_WIDTH_MASK; switch (pp->lanes) { case 1: @@ -765,19 +765,19 @@ void dw_pcie_setup_rc(struct pcie_port *pp) dw_pcie_writel_rc(pp, 0x00000000, PCI_BASE_ADDRESS_1); /* setup interrupt pins */ - dw_pcie_readl_rc(pp, PCI_INTERRUPT_LINE, &val); + val = dw_pcie_readl_rc(pp, PCI_INTERRUPT_LINE); val &= 0xffff00ff; val |= 0x00000100; dw_pcie_writel_rc(pp, val, PCI_INTERRUPT_LINE); /* setup bus numbers */ - dw_pcie_readl_rc(pp, PCI_PRIMARY_BUS, &val); + val = dw_pcie_readl_rc(pp, PCI_PRIMARY_BUS); val &= 0xff000000; val |= 0x00010100; dw_pcie_writel_rc(pp, val, PCI_PRIMARY_BUS); /* setup command register */ - dw_pcie_readl_rc(pp, PCI_COMMAND, &val); + val = dw_pcie_readl_rc(pp, PCI_COMMAND); val &= 0xffff0000; val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_SERR; diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index f437f9b..74a8fc6 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -57,8 +57,7 @@ struct pcie_port { }; struct pcie_host_ops { - void (*readl_rc)(struct pcie_port *pp, - void __iomem *dbi_base, u32 *val); + u32 (*readl_rc)(struct pcie_port *pp, void __iomem *dbi_base); void (*writel_rc)(struct pcie_port *pp, u32 val, void __iomem *dbi_base); int (*rd_own_conf)(struct pcie_port *pp, int where, int size, u32 *val); -- cgit v0.10.2 From c388de1c4f0e5da3e96b49efb0388e2a0d34e079 Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Wed, 10 Aug 2016 11:02:38 +0100 Subject: PCI: designware: Move link wait definitions to .c file Move the link wait sleep definitions to the .c file as suggested by Jisheng Zhang in a previous patch. Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas CC: Jisheng Zhang diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 1f6bd6d..e99f56e 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -26,6 +26,11 @@ #include "pcie-designware.h" +/* Parameters for the waiting for link up routine */ +#define LINK_WAIT_MAX_RETRIES 10 +#define LINK_WAIT_USLEEP_MIN 90000 +#define LINK_WAIT_USLEEP_MAX 100000 + /* Synopsis specific PCIE configuration registers */ #define PCIE_PORT_LINK_CONTROL 0x710 #define PORT_LINK_MODE_MASK (0x3f << 16) diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 74a8fc6..285e1ed 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -22,11 +22,6 @@ #define MAX_MSI_IRQS 32 #define MAX_MSI_CTRLS (MAX_MSI_IRQS / 32) -/* Parameters for the waiting for link up routine */ -#define LINK_WAIT_MAX_RETRIES 10 -#define LINK_WAIT_USLEEP_MIN 90000 -#define LINK_WAIT_USLEEP_MAX 100000 - struct pcie_port { struct device *dev; u8 root_bus_nr; -- cgit v0.10.2 From d8bbeb39fbf3ff06b6adae9d336f44bee4e3f3ec Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Wed, 17 Aug 2016 13:26:07 -0500 Subject: PCI: designware: Wait for iATU enable Add a loop with timeout to make sure the iATU is really enabled before subsequent config and I/O accesses. [bhelgaas: split to separate patch, use dev_err() instead of dev_dbg()] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index e99f56e..947fac3 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -31,7 +31,12 @@ #define LINK_WAIT_USLEEP_MIN 90000 #define LINK_WAIT_USLEEP_MAX 100000 -/* Synopsis specific PCIE configuration registers */ +/* Parameters for the waiting for iATU enabled routine */ +#define LINK_WAIT_MAX_IATU_RETRIES 5 +#define LINK_WAIT_IATU_MIN 9000 +#define LINK_WAIT_IATU_MAX 10000 + +/* Synopsys-specific PCIe configuration registers */ #define PCIE_PORT_LINK_CONTROL 0x710 #define PORT_LINK_MODE_MASK (0x3f << 16) #define PORT_LINK_MODE_1_LANES (0x1 << 16) @@ -157,7 +162,7 @@ static int dw_pcie_wr_own_conf(struct pcie_port *pp, int where, int size, static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, int type, u64 cpu_addr, u64 pci_addr, u32 size) { - u32 val; + u32 retries, val; dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, PCIE_ATU_VIEWPORT); @@ -174,7 +179,14 @@ static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ - val = dw_pcie_readl_rc(pp, PCIE_ATU_CR2); + for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { + val = dw_pcie_readl_rc(pp, PCIE_ATU_CR2); + if (val == PCIE_ATU_ENABLE) + return; + + usleep_range(LINK_WAIT_IATU_MIN, LINK_WAIT_IATU_MAX); + } + dev_err(pp->dev, "iATU is not being enabled\n"); } static struct irq_chip dw_msi_irq_chip = { -- cgit v0.10.2 From a0601a47053714eecec726aea5ebcd829f817497 Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Wed, 10 Aug 2016 11:02:39 +0100 Subject: PCI: designware: Add iATU Unroll feature Add support for the new iATU Unroll mechanism that will be used from Core version 4.80. The new Cores can support either iATU Unroll or the "old" iATU method, now called Legacy Mode. The driver is perfectly capable of performing well for both. [bhelgaas: split ATU enable timeout to separate patch] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 947fac3..44c63fb 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -80,6 +80,21 @@ #define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) #define PCIE_ATU_UPPER_TARGET 0x91C +/* + * iATU Unroll-specific register definitions + * From 4.80 core version the address translation will be made by unroll + */ +#define PCIE_ATU_UNR_REGION_CTRL1 0x00 +#define PCIE_ATU_UNR_REGION_CTRL2 0x04 +#define PCIE_ATU_UNR_LOWER_BASE 0x08 +#define PCIE_ATU_UNR_UPPER_BASE 0x0C +#define PCIE_ATU_UNR_LIMIT 0x10 +#define PCIE_ATU_UNR_LOWER_TARGET 0x14 +#define PCIE_ATU_UNR_UPPER_TARGET 0x18 + +/* Register address builder */ +#define PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(region) ((0x3 << 20) | (region << 9)) + /* PCIe Port Logic registers */ #define PLR_OFFSET 0x700 #define PCIE_PHY_DEBUG_R1 (PLR_OFFSET + 0x2c) @@ -141,6 +156,27 @@ static inline void dw_pcie_writel_rc(struct pcie_port *pp, u32 val, u32 reg) writel(val, pp->dbi_base + reg); } +static inline u32 dw_pcie_readl_unroll(struct pcie_port *pp, u32 index, u32 reg) +{ + u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); + + if (pp->ops->readl_rc) + return pp->ops->readl_rc(pp, pp->dbi_base + offset + reg); + + return readl(pp->dbi_base + offset + reg); +} + +static inline void dw_pcie_writel_unroll(struct pcie_port *pp, u32 index, + u32 val, u32 reg) +{ + u32 offset = PCIE_GET_ATU_OUTB_UNR_REG_OFFSET(index); + + if (pp->ops->writel_rc) + pp->ops->writel_rc(pp, val, pp->dbi_base + offset + reg); + else + writel(val, pp->dbi_base + offset + reg); +} + static int dw_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val) { @@ -164,23 +200,49 @@ static void dw_pcie_prog_outbound_atu(struct pcie_port *pp, int index, { u32 retries, val; - dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, - PCIE_ATU_VIEWPORT); - dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr), PCIE_ATU_LOWER_BASE); - dw_pcie_writel_rc(pp, upper_32_bits(cpu_addr), PCIE_ATU_UPPER_BASE); - dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr + size - 1), - PCIE_ATU_LIMIT); - dw_pcie_writel_rc(pp, lower_32_bits(pci_addr), PCIE_ATU_LOWER_TARGET); - dw_pcie_writel_rc(pp, upper_32_bits(pci_addr), PCIE_ATU_UPPER_TARGET); - dw_pcie_writel_rc(pp, type, PCIE_ATU_CR1); - dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); + if (pp->iatu_unroll_enabled) { + dw_pcie_writel_unroll(pp, index, + lower_32_bits(cpu_addr), PCIE_ATU_UNR_LOWER_BASE); + dw_pcie_writel_unroll(pp, index, + upper_32_bits(cpu_addr), PCIE_ATU_UNR_UPPER_BASE); + dw_pcie_writel_unroll(pp, index, + lower_32_bits(cpu_addr + size - 1), PCIE_ATU_UNR_LIMIT); + dw_pcie_writel_unroll(pp, index, + lower_32_bits(pci_addr), PCIE_ATU_UNR_LOWER_TARGET); + dw_pcie_writel_unroll(pp, index, + upper_32_bits(pci_addr), PCIE_ATU_UNR_UPPER_TARGET); + dw_pcie_writel_unroll(pp, index, + type, PCIE_ATU_UNR_REGION_CTRL1); + dw_pcie_writel_unroll(pp, index, + PCIE_ATU_ENABLE, PCIE_ATU_UNR_REGION_CTRL2); + } else { + dw_pcie_writel_rc(pp, PCIE_ATU_REGION_OUTBOUND | index, + PCIE_ATU_VIEWPORT); + dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr), + PCIE_ATU_LOWER_BASE); + dw_pcie_writel_rc(pp, upper_32_bits(cpu_addr), + PCIE_ATU_UPPER_BASE); + dw_pcie_writel_rc(pp, lower_32_bits(cpu_addr + size - 1), + PCIE_ATU_LIMIT); + dw_pcie_writel_rc(pp, lower_32_bits(pci_addr), + PCIE_ATU_LOWER_TARGET); + dw_pcie_writel_rc(pp, upper_32_bits(pci_addr), + PCIE_ATU_UPPER_TARGET); + dw_pcie_writel_rc(pp, type, PCIE_ATU_CR1); + dw_pcie_writel_rc(pp, PCIE_ATU_ENABLE, PCIE_ATU_CR2); + } /* * Make sure ATU enable takes effect before any subsequent config * and I/O accesses. */ for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) { - val = dw_pcie_readl_rc(pp, PCIE_ATU_CR2); + if (pp->iatu_unroll_enabled) + val = dw_pcie_readl_unroll(pp, index, + PCIE_ATU_UNR_REGION_CTRL2); + else + val = dw_pcie_readl_rc(pp, PCIE_ATU_CR2); + if (val == PCIE_ATU_ENABLE) return; @@ -445,6 +507,17 @@ static const struct irq_domain_ops msi_domain_ops = { .map = dw_pcie_msi_map, }; +static u8 dw_pcie_iatu_unroll_enabled(struct pcie_port *pp) +{ + u32 val; + + val = dw_pcie_readl_rc(pp, PCIE_ATU_VIEWPORT); + if (val == 0xffffffff) + return 1; + + return 0; +} + int dw_pcie_host_init(struct pcie_port *pp) { struct device_node *np = pp->dev->of_node; @@ -561,6 +634,8 @@ int dw_pcie_host_init(struct pcie_port *pp) } } + pp->iatu_unroll_enabled = dw_pcie_iatu_unroll_enabled(pp); + if (pp->ops->host_init) pp->ops->host_init(pp); diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 285e1ed..e74cc4a 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -48,6 +48,7 @@ struct pcie_port { int msi_irq; struct irq_domain *irq_domain; unsigned long msi_data; + u8 iatu_unroll_enabled; DECLARE_BITMAP(msi_irq_in_use, MAX_MSI_IRQS); }; -- cgit v0.10.2 From 01c076732e8288485c22ef50f20949455a783ca9 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 17 Aug 2016 15:57:37 -0500 Subject: PCI: designware: Check LTSSM training bit before deciding link is up The link may be up but still in link training. In this case, we can't think the link is up and operating correctly. Teach dw_pcie_link_up() to be aware of the PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING bit. Also rewrite PCIE_PHY_DEBUG_R1_LINK_UP definition so that it's consistent with other macros. Signed-off-by: Jisheng Zhang Signed-off-by: Bjorn Helgaas Acked-by: Joao Pinto diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 44c63fb..085fde2 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -98,7 +98,8 @@ /* PCIe Port Logic registers */ #define PLR_OFFSET 0x700 #define PCIE_PHY_DEBUG_R1 (PLR_OFFSET + 0x2c) -#define PCIE_PHY_DEBUG_R1_LINK_UP 0x00000010 +#define PCIE_PHY_DEBUG_R1_LINK_UP (0x1 << 4) +#define PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING (0x1 << 29) static struct pci_ops dw_pcie_ops; @@ -491,7 +492,8 @@ int dw_pcie_link_up(struct pcie_port *pp) return pp->ops->link_up(pp); val = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); - return val & PCIE_PHY_DEBUG_R1_LINK_UP; + return ((val & PCIE_PHY_DEBUG_R1_LINK_UP) && + (!(val & PCIE_PHY_DEBUG_R1_LINK_IN_TRAINING))); } static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, -- cgit v0.10.2 From 411dc32d8810e0a204c799ce5c97cb56990de1cb Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Mon, 15 Aug 2016 14:06:02 +0800 Subject: PCI: altera: Poll for link training status after retraining the link Poll for link training status is cleared before poll for link up status. This can help to get the reliable link up status, especially when PCIe is in Gen 3 speed. Signed-off-by: Ley Foon Tan Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 2b78376..58eef99 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -61,7 +61,8 @@ #define TLP_LOOP 500 #define RP_DEVFN 0 -#define LINK_UP_TIMEOUT 5000 +#define LINK_UP_TIMEOUT HZ +#define LINK_RETRAIN_TIMEOUT HZ #define INTX_NUM 4 @@ -99,11 +100,44 @@ static bool altera_pcie_link_is_up(struct altera_pcie *pcie) return !!((cra_readl(pcie, RP_LTSSM) & RP_LTSSM_MASK) == LTSSM_L0); } +static void altera_wait_link_retrain(struct pci_dev *dev) +{ + u16 reg16; + unsigned long start_jiffies; + struct altera_pcie *pcie = dev->bus->sysdata; + + /* Wait for link training end. */ + start_jiffies = jiffies; + for (;;) { + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_LT)) + break; + + if (time_after(jiffies, start_jiffies + LINK_RETRAIN_TIMEOUT)) { + dev_err(&pcie->pdev->dev, "link retrain timeout\n"); + break; + } + udelay(100); + } + + /* Wait for link is up */ + start_jiffies = jiffies; + for (;;) { + if (altera_pcie_link_is_up(pcie)) + break; + + if (time_after(jiffies, start_jiffies + LINK_UP_TIMEOUT)) { + dev_err(&pcie->pdev->dev, "link up timeout\n"); + break; + } + udelay(100); + } +} + static void altera_pcie_retrain(struct pci_dev *dev) { u16 linkcap, linkstat; struct altera_pcie *pcie = dev->bus->sysdata; - int timeout = 0; if (!altera_pcie_link_is_up(pcie)) return; @@ -121,12 +155,7 @@ static void altera_pcie_retrain(struct pci_dev *dev) if ((linkstat & PCI_EXP_LNKSTA_CLS) == PCI_EXP_LNKSTA_CLS_2_5GB) { pcie_capability_set_word(dev, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_RL); - while (!altera_pcie_link_is_up(pcie)) { - timeout++; - if (timeout > LINK_UP_TIMEOUT) - break; - udelay(5); - } + altera_wait_link_retrain(dev); } } DECLARE_PCI_FIXUP_EARLY(0x1172, PCI_ANY_ID, altera_pcie_retrain); -- cgit v0.10.2 From 8e2e03179923479ca0c0b6fdc7c93ecf89bce7a8 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Tue, 9 Aug 2016 16:41:31 +0200 Subject: PCI: Mark Atheros AR9580 to avoid bus reset Similar to the AR93xx and the AR94xx series, the AR95xx also have the same quirk for the Bus Reset. It will lead to instant system reset if the device is assigned via VFIO to a KVM VM. I've been able reproduce this behavior with a MikroTik R11e-2HnD. Fixes: c3e59ee4e766 ("PCI: Mark Atheros AR93xx to avoid bus reset") Signed-off-by: Maik Broemme Signed-off-by: Bjorn Helgaas CC: stable@vger.kernel.org # v3.14+ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 37ff015..7d82189 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3198,6 +3198,7 @@ static void quirk_no_bus_reset(struct pci_dev *dev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0030, quirk_no_bus_reset); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0032, quirk_no_bus_reset); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003c, quirk_no_bus_reset); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0033, quirk_no_bus_reset); static void quirk_no_pm_reset(struct pci_dev *dev) { -- cgit v0.10.2 From a7fbae213925488066e296f30cc81aaa2c5c2802 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 15 Aug 2016 17:31:31 +0200 Subject: PCI: tegra: Remove redundant _data suffix The struct tegra_pcie_soc_data represents SoC-specific data. The shorter name tegra_pcie_soc already describes that accurately enough, so the extra five characters are redundant. Also remove the suffix from various variable names to shorten the code a little. This also makes this driver more consistent with the naming used in other drivers that use a similar mechanism to differentiate between various SoC generations. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 6de0757..7756a79 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -240,7 +240,7 @@ struct tegra_msi { }; /* used to differentiate between Tegra SoC generations */ -struct tegra_pcie_soc_data { +struct tegra_pcie_soc { unsigned int num_ports; unsigned int msi_base_shift; u32 pads_pll_ctl; @@ -300,7 +300,7 @@ struct tegra_pcie { struct regulator_bulk_data *supplies; unsigned int num_supplies; - const struct tegra_pcie_soc_data *soc_data; + const struct tegra_pcie_soc *soc; struct dentry *debugfs; }; @@ -542,8 +542,8 @@ static void tegra_pcie_port_reset(struct tegra_pcie_port *port) static void tegra_pcie_port_enable(struct tegra_pcie_port *port) { - const struct tegra_pcie_soc_data *soc = port->pcie->soc_data; unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port); + const struct tegra_pcie_soc *soc = port->pcie->soc; unsigned long value; /* enable reference clock */ @@ -562,8 +562,8 @@ static void tegra_pcie_port_enable(struct tegra_pcie_port *port) static void tegra_pcie_port_disable(struct tegra_pcie_port *port) { - const struct tegra_pcie_soc_data *soc = port->pcie->soc_data; unsigned long ctrl = tegra_pcie_port_get_pex_ctrl(port); + const struct tegra_pcie_soc *soc = port->pcie->soc; unsigned long value; /* assert port reset */ @@ -774,7 +774,7 @@ static void tegra_pcie_setup_translations(struct tegra_pcie *pcie) static int tegra_pcie_pll_wait(struct tegra_pcie *pcie, unsigned long timeout) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; u32 value; timeout = jiffies + msecs_to_jiffies(timeout); @@ -790,7 +790,7 @@ static int tegra_pcie_pll_wait(struct tegra_pcie *pcie, unsigned long timeout) static int tegra_pcie_phy_enable(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; u32 value; int err; @@ -845,7 +845,7 @@ static int tegra_pcie_phy_enable(struct tegra_pcie *pcie) static int tegra_pcie_phy_disable(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; u32 value; /* disable TX/RX data */ @@ -906,7 +906,7 @@ static int tegra_pcie_port_phy_power_off(struct tegra_pcie_port *port) static int tegra_pcie_phy_power_on(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; struct tegra_pcie_port *port; int err; @@ -974,7 +974,7 @@ static int tegra_pcie_phy_power_off(struct tegra_pcie *pcie) static int tegra_pcie_enable_controller(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; struct tegra_pcie_port *port; unsigned long value; int err; @@ -1067,7 +1067,7 @@ static void tegra_pcie_power_off(struct tegra_pcie *pcie) static int tegra_pcie_power_on(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; int err; reset_control_assert(pcie->pcie_xrst); @@ -1117,7 +1117,7 @@ static int tegra_pcie_power_on(struct tegra_pcie *pcie) static int tegra_pcie_clocks_get(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; pcie->pex_clk = devm_clk_get(pcie->dev, "pex"); if (IS_ERR(pcie->pex_clk)) @@ -1234,7 +1234,7 @@ static int tegra_pcie_port_get_phys(struct tegra_pcie_port *port) static int tegra_pcie_phys_get(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; struct device_node *np = pcie->dev->of_node; struct tegra_pcie_port *port; int err; @@ -1486,7 +1486,7 @@ static const struct irq_domain_ops msi_domain_ops = { static int tegra_pcie_enable_msi(struct tegra_pcie *pcie) { struct platform_device *pdev = to_platform_device(pcie->dev); - const struct tegra_pcie_soc_data *soc = pcie->soc_data; + const struct tegra_pcie_soc *soc = pcie->soc; struct tegra_msi *msi = &pcie->msi; unsigned long base; int err; @@ -1799,8 +1799,8 @@ static int tegra_pcie_get_regulators(struct tegra_pcie *pcie, u32 lane_mask) static int tegra_pcie_parse_dt(struct tegra_pcie *pcie) { - const struct tegra_pcie_soc_data *soc = pcie->soc_data; struct device_node *np = pcie->dev->of_node, *port; + const struct tegra_pcie_soc *soc = pcie->soc; struct of_pci_range_parser parser; struct of_pci_range range; u32 lanes = 0, mask = 0; @@ -2043,7 +2043,7 @@ static int tegra_pcie_enable(struct tegra_pcie *pcie) return 0; } -static const struct tegra_pcie_soc_data tegra20_pcie_data = { +static const struct tegra_pcie_soc tegra20_pcie = { .num_ports = 2, .msi_base_shift = 0, .pads_pll_ctl = PADS_PLL_CTL_TEGRA20, @@ -2056,7 +2056,7 @@ static const struct tegra_pcie_soc_data tegra20_pcie_data = { .has_gen2 = false, }; -static const struct tegra_pcie_soc_data tegra30_pcie_data = { +static const struct tegra_pcie_soc tegra30_pcie = { .num_ports = 3, .msi_base_shift = 8, .pads_pll_ctl = PADS_PLL_CTL_TEGRA30, @@ -2070,7 +2070,7 @@ static const struct tegra_pcie_soc_data tegra30_pcie_data = { .has_gen2 = false, }; -static const struct tegra_pcie_soc_data tegra124_pcie_data = { +static const struct tegra_pcie_soc tegra124_pcie = { .num_ports = 2, .msi_base_shift = 8, .pads_pll_ctl = PADS_PLL_CTL_TEGRA30, @@ -2084,9 +2084,9 @@ static const struct tegra_pcie_soc_data tegra124_pcie_data = { }; static const struct of_device_id tegra_pcie_of_match[] = { - { .compatible = "nvidia,tegra124-pcie", .data = &tegra124_pcie_data }, - { .compatible = "nvidia,tegra30-pcie", .data = &tegra30_pcie_data }, - { .compatible = "nvidia,tegra20-pcie", .data = &tegra20_pcie_data }, + { .compatible = "nvidia,tegra124-pcie", .data = &tegra124_pcie }, + { .compatible = "nvidia,tegra30-pcie", .data = &tegra30_pcie }, + { .compatible = "nvidia,tegra20-pcie", .data = &tegra20_pcie }, { }, }; @@ -2215,7 +2215,7 @@ static int tegra_pcie_probe(struct platform_device *pdev) INIT_LIST_HEAD(&pcie->buses); INIT_LIST_HEAD(&pcie->ports); - pcie->soc_data = match->data; + pcie->soc = match->data; pcie->dev = &pdev->dev; err = tegra_pcie_parse_dt(pcie); -- cgit v0.10.2 From c460af94c3bedefc4218e07629d0ba06fd5ac80b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 15 Aug 2016 17:31:32 +0200 Subject: PCI: tegra: Use of_device_get_match_data() of_device_get_match_data() was added in v4.2 to reduce the the boilerplate required to get at SoC-specific data. Use it to simplify the code slightly. [bhelgaas: changelog] Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 7756a79..2d52075 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -2201,21 +2201,16 @@ remove: static int tegra_pcie_probe(struct platform_device *pdev) { - const struct of_device_id *match; struct tegra_pcie *pcie; int err; - match = of_match_device(tegra_pcie_of_match, &pdev->dev); - if (!match) - return -ENODEV; - pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); if (!pcie) return -ENOMEM; + pcie->soc = of_device_get_match_data(&pdev->dev); INIT_LIST_HEAD(&pcie->buses); INIT_LIST_HEAD(&pcie->ports); - pcie->soc = match->data; pcie->dev = &pdev->dev; err = tegra_pcie_parse_dt(pcie); -- cgit v0.10.2 From eec097d43100a8195fd4f678671ecd5d986dd675 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 13 Jun 2016 11:01:51 -0500 Subject: PCI: Add pci_enable_ptm() for drivers to enable PTM on endpoints Add an pci_enable_ptm() interface so drivers can enable PTM. The PCI core enables PTM on PTM Roots and switches automatically, but we don't enable PTM on endpoints unless a driver requests it. Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/ptm.c b/drivers/pci/pcie/ptm.c index 48eea4e..a14ac94 100644 --- a/drivers/pci/pcie/ptm.c +++ b/drivers/pci/pcie/ptm.c @@ -68,3 +68,48 @@ void pci_ptm_init(struct pci_dev *dev) pci_ptm_info(dev); } + +int pci_enable_ptm(struct pci_dev *dev, u8 *granularity) +{ + int pos; + u32 cap, ctrl; + struct pci_dev *ups; + + if (!pci_is_pcie(dev)) + return -EINVAL; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PTM); + if (!pos) + return -EINVAL; + + pci_read_config_dword(dev, pos + PCI_PTM_CAP, &cap); + if (!(cap & PCI_PTM_CAP_REQ)) + return -EINVAL; + + /* + * For a PCIe Endpoint, PTM is only useful if the endpoint can + * issue PTM requests to upstream devices that have PTM enabled. + * + * For Root Complex Integrated Endpoints, there is no upstream + * device, so there must be some implementation-specific way to + * associate the endpoint with a time source. + */ + if (pci_pcie_type(dev) == PCI_EXP_TYPE_ENDPOINT) { + ups = pci_upstream_bridge(dev); + if (!ups || !ups->ptm_enabled) + return -EINVAL; + } else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) { + } else + return -EINVAL; + + ctrl = PCI_PTM_CTRL_ENABLE; + pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); + dev->ptm_enabled = 1; + + pci_ptm_info(dev); + + if (granularity) + *granularity = 0; + return 0; +} +EXPORT_SYMBOL(pci_enable_ptm); diff --git a/include/linux/pci.h b/include/linux/pci.h index 96c509f..9e4b6d6 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1407,6 +1407,13 @@ static inline void pci_disable_ats(struct pci_dev *d) { } static inline int pci_ats_queue_depth(struct pci_dev *d) { return -ENODEV; } #endif +#ifdef CONFIG_PCIE_PTM +int pci_enable_ptm(struct pci_dev *dev, u8 *granularity); +#else +static inline int pci_enable_ptm(struct pci_dev *dev, u8 *granularity) +{ return -EINVAL; } +#endif + void pci_cfg_access_lock(struct pci_dev *dev); bool pci_cfg_access_trylock(struct pci_dev *dev); void pci_cfg_access_unlock(struct pci_dev *dev); diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 926fff4..72bbe14 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -967,6 +967,7 @@ /* Precision Time Measurement */ #define PCI_PTM_CAP 0x04 /* PTM Capability */ +#define PCI_PTM_CAP_REQ 0x00000001 /* Requester capable */ #define PCI_PTM_CAP_ROOT 0x00000004 /* Root capable */ #define PCI_PTM_CTRL 0x08 /* PTM Control */ #define PCI_PTM_CTRL_ENABLE 0x00000001 /* PTM enable */ -- cgit v0.10.2 From 8b7c8b46f111ae56df4fd196fcb0fd2495f3b966 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Wed, 3 Aug 2016 17:29:45 -0600 Subject: PCI: pciehp: Clear attention LED on device add Clear the LED attention status after a successful device add. It is possible the attention LED was on from a previous power fault or link failure, and a subsequent successful device insert insertion should clear it. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 880978b..7ea3e61 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -120,6 +120,7 @@ static int board_added(struct slot *p_slot) } pciehp_green_led_on(p_slot); + pciehp_set_attention_status(p_slot, 0); return 0; err_exit: -- cgit v0.10.2 From fe48cb8538421fbd16ecf8bf95829faf8d8c001e Mon Sep 17 00:00:00 2001 From: Pratyush Anand Date: Mon, 4 Jul 2016 21:44:42 +0530 Subject: PCI: designware: Keep viewport fixed for IO transaction if num_viewport > 2 Most of the platforms have 3 or more viewports. For such platforms, We do not need to share viewports between IO and CFG. Assign viewport 2 to IO transactions in such cases. Tested-by: Dong Bo Signed-off-by: Pratyush Anand Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring diff --git a/Documentation/devicetree/bindings/pci/designware-pcie.txt b/Documentation/devicetree/bindings/pci/designware-pcie.txt index 6c5322c..1392c70 100644 --- a/Documentation/devicetree/bindings/pci/designware-pcie.txt +++ b/Documentation/devicetree/bindings/pci/designware-pcie.txt @@ -17,6 +17,8 @@ Required properties: - num-lanes: number of lanes to use Optional properties: +- num-viewport: number of view ports configured in hardware. If a platform + does not specify it, the driver assumes 2. - num-lanes: number of lanes to use (this property should be specified unless the link is brought already up in BIOS) - reset-gpio: gpio pin number of power good signal @@ -44,4 +46,5 @@ Example configuration: interrupts = <25>, <24>; #interrupt-cells = <1>; num-lanes = <1>; + num-viewport = <3>; }; diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 085fde2..38460138 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -61,6 +61,7 @@ #define PCIE_ATU_VIEWPORT 0x900 #define PCIE_ATU_REGION_INBOUND (0x1 << 31) #define PCIE_ATU_REGION_OUTBOUND (0x0 << 31) +#define PCIE_ATU_REGION_INDEX2 (0x2 << 0) #define PCIE_ATU_REGION_INDEX1 (0x1 << 0) #define PCIE_ATU_REGION_INDEX0 (0x0 << 0) #define PCIE_ATU_CR1 0x904 @@ -616,6 +617,10 @@ int dw_pcie_host_init(struct pcie_port *pp) if (ret) pp->lanes = 0; + ret = of_property_read_u32(np, "num-viewport", &pp->num_viewport); + if (ret) + pp->num_viewport = 2; + if (IS_ENABLED(CONFIG_PCI_MSI)) { if (!pp->ops->msi_host_init) { pp->irq_domain = irq_domain_add_linear(pp->dev->of_node, @@ -707,9 +712,10 @@ static int dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus, type, cpu_addr, busdev, cfg_size); ret = dw_pcie_cfg_read(va_cfg_base + where, size, val); - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, - PCIE_ATU_TYPE_IO, pp->io_base, - pp->io_bus_addr, pp->io_size); + if (pp->num_viewport <= 2) + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + PCIE_ATU_TYPE_IO, pp->io_base, + pp->io_bus_addr, pp->io_size); return ret; } @@ -744,9 +750,10 @@ static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, type, cpu_addr, busdev, cfg_size); ret = dw_pcie_cfg_write(va_cfg_base + where, size, val); - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, - PCIE_ATU_TYPE_IO, pp->io_base, - pp->io_bus_addr, pp->io_size); + if (pp->num_viewport <= 2) + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + PCIE_ATU_TYPE_IO, pp->io_base, + pp->io_bus_addr, pp->io_size); return ret; } @@ -882,10 +889,15 @@ void dw_pcie_setup_rc(struct pcie_port *pp) * uses its own address translation component rather than ATU, so * we should not program the ATU here. */ - if (!pp->ops->rd_other_conf) + if (!pp->ops->rd_other_conf) { dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, PCIE_ATU_TYPE_MEM, pp->mem_base, pp->mem_bus_addr, pp->mem_size); + if (pp->num_viewport > 2) + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX2, + PCIE_ATU_TYPE_IO, pp->io_base, + pp->io_bus_addr, pp->io_size); + } dw_pcie_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index e74cc4a..c8e5bc6 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -44,6 +44,7 @@ struct pcie_port { struct resource *busn; int irq; u32 lanes; + u32 num_viewport; struct pcie_host_ops *ops; int msi_irq; struct irq_domain *irq_domain; -- cgit v0.10.2 From 51afa3cc2ded60884aed3aaac8d8c416e3cf6324 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Aug 2016 14:16:38 -0500 Subject: PCI: rcar: Consolidate register space lookup and ioremap Move the devm_ioremap_resource() of R-Car register space next to the of_address_to_resource() that extracts the resource. No functional change intended. Signed-off-by: Bjorn Helgaas Reviewed-by: Geert Uytterhoeven diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 65db7a2..e58bbbc 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -775,6 +775,10 @@ static int rcar_pcie_get_resources(struct platform_device *pdev, if (err) return err; + pcie->base = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(pcie->base)) + return PTR_ERR(pcie->base); + pcie->clk = devm_clk_get(&pdev->dev, "pcie"); if (IS_ERR(pcie->clk)) { dev_err(pcie->dev, "cannot get platform clock\n"); @@ -810,12 +814,6 @@ static int rcar_pcie_get_resources(struct platform_device *pdev, } pcie->msi.irq2 = i; - 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: -- cgit v0.10.2 From 8116acce5a7aba90d97170d04d4b497afa1b74cf Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 28 Jul 2016 16:16:18 +0000 Subject: PCI: keystone: Propagate request_irq() failure Previously, if request_irq() failed, ks_add_pcie_port() always returned zero (success). Return the request_irq() failure result instead. [bhelgaas: changelog] Signed-off-by: Wei Yongjun Signed-off-by: Bjorn Helgaas Acked-By: Murali Karicheri diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index 8ba2883..82b461b 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -334,8 +334,9 @@ static int __init ks_add_pcie_port(struct keystone_pcie *ks_pcie, if (ks_pcie->error_irq <= 0) dev_info(&pdev->dev, "no error IRQ defined\n"); else { - if (request_irq(ks_pcie->error_irq, pcie_err_irq_handler, - IRQF_SHARED, "pcie-error-irq", ks_pcie) < 0) { + ret = request_irq(ks_pcie->error_irq, pcie_err_irq_handler, + IRQF_SHARED, "pcie-error-irq", ks_pcie); + if (ret < 0) { dev_err(&pdev->dev, "failed to request error IRQ %d\n", ks_pcie->error_irq); return ret; -- cgit v0.10.2 From 4f1cb01a7892582d18483986fbc268cdef1b1dee Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 28 Jul 2016 16:16:48 +0000 Subject: PCI: hv: Use list_move_tail() instead of list_del() + list_add_tail() Use list_move_tail() instead of list_del() + list_add_tail(). No functional change intended Signed-off-by: Wei Yongjun Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index 6955ffdb..a8deeca 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -1466,8 +1466,7 @@ static void pci_devices_present_work(struct work_struct *work) if (hpdev->reported_missing) { found = true; put_pcichild(hpdev, hv_pcidev_ref_childlist); - list_del(&hpdev->list_entry); - list_add_tail(&hpdev->list_entry, &removed); + list_move_tail(&hpdev->list_entry, &removed); break; } } -- cgit v0.10.2 From 8b22335afe75bccbef65fac6d4714dd1f5ebd1bd Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 28 Jul 2016 16:17:14 +0000 Subject: PCI: aardvark: Remove redundant dev_err call in advk_pcie_probe() devm_ioremap_resource() emits an error message already, so remove the dev_err() call in advk_pcie_probe() to avoid redundant error messages. Signed-off-by: Wei Yongjun Signed-off-by: Bjorn Helgaas Reviewed-by: Thomas Petazzoni diff --git a/drivers/pci/host/pci-aardvark.c b/drivers/pci/host/pci-aardvark.c index ef9893f..05c33b5 100644 --- a/drivers/pci/host/pci-aardvark.c +++ b/drivers/pci/host/pci-aardvark.c @@ -925,10 +925,8 @@ static int advk_pcie_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); pcie->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(pcie->base)) { - dev_err(&pdev->dev, "Failed to map registers\n"); + if (IS_ERR(pcie->base)) return PTR_ERR(pcie->base); - } irq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, irq, advk_pcie_irq_handler, -- cgit v0.10.2 From 18224b3a732156d68d4ce5a74aa9148d922d08eb Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:41 -0400 Subject: PCI: altera: Make MSI explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_ALTERA_MSI drivers/pci/host/Kconfig: bool "Altera PCIe MSI feature" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Ley Foon Tan diff --git a/drivers/pci/host/pcie-altera-msi.c b/drivers/pci/host/pcie-altera-msi.c index 99177f4..369e033 100644 --- a/drivers/pci/host/pcie-altera-msi.c +++ b/drivers/pci/host/pcie-altera-msi.c @@ -1,4 +1,8 @@ /* + * Altera PCIe MSI support + * + * Author: Ley Foon Tan + * * Copyright Altera Corporation (C) 2013-2015. All rights reserved * * This program is free software; you can redistribute it and/or modify it @@ -16,7 +20,7 @@ #include #include -#include +#include #include #include #include @@ -308,7 +312,3 @@ static int __init altera_msi_init(void) return platform_driver_register(&altera_msi_driver); } subsys_initcall(altera_msi_init); - -MODULE_AUTHOR("Ley Foon Tan "); -MODULE_DESCRIPTION("Altera PCIe MSI support"); -MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From bb9b54ca3761111eb67401c0cea891586c24107d Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:42 -0400 Subject: PCI: altera: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_ALTERA drivers/pci/host/Kconfig: bool "Altera PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and module_init() translates to device_initcall(). [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Ley Foon Tan diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 2b78376..48f2736 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -1,6 +1,9 @@ /* * Copyright Altera Corporation (C) 2013-2015. All rights reserved * + * Author: Ley Foon Tan + * Description: Altera PCIe host controller driver + * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. @@ -17,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -568,7 +571,6 @@ static const struct of_device_id altera_pcie_of_match[] = { { .compatible = "altr,pcie-root-port-1.0", }, {}, }; -MODULE_DEVICE_TABLE(of, altera_pcie_of_match); static struct platform_driver altera_pcie_driver = { .probe = altera_pcie_probe, @@ -583,8 +585,4 @@ static int altera_pcie_init(void) { return platform_driver_register(&altera_pcie_driver); } -module_init(altera_pcie_init); - -MODULE_AUTHOR("Ley Foon Tan "); -MODULE_DESCRIPTION("Altera PCIe host controller driver"); -MODULE_LICENSE("GPL v2"); +device_initcall(altera_pcie_init); -- cgit v0.10.2 From f90d8e84c3f41ee424b4438f61c5ae12cfdbc7d8 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:43 -0400 Subject: PCI: imx6: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCI_IMX6 drivers/pci/host/Kconfig: bool "Freescale i.MX6 PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and module_init() translates to device_initcall(). [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Richard Zhu CC: Lucas Stach diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index b741a36..ead4a5c 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -739,7 +739,6 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx6qp-pcie", .data = (void *)IMX6QP, }, {}, }; -MODULE_DEVICE_TABLE(of, imx6_pcie_of_match); static struct platform_driver imx6_pcie_driver = { .driver = { @@ -749,14 +748,8 @@ static struct platform_driver imx6_pcie_driver = { .shutdown = imx6_pcie_shutdown, }; -/* Freescale PCIe driver does not allow module unload */ - static int __init imx6_pcie_init(void) { return platform_driver_probe(&imx6_pcie_driver, imx6_pcie_probe); } -module_init(imx6_pcie_init); - -MODULE_AUTHOR("Sean Cross "); -MODULE_DESCRIPTION("Freescale i.MX6 PCIe host controller driver"); -MODULE_LICENSE("GPL v2"); +device_initcall(imx6_pcie_init); -- cgit v0.10.2 From a7dadf45aedd414191222368a700534d2a652197 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:44 -0400 Subject: PCI: portdrv: Make explicitly non-modular This code is not being built as a module by anyone: pcieportdrv-y := portdrv_core.o portdrv_pci.o portdrv_bus.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o drivers/pci/pcie/Kconfig:config PCIEPORTBUS drivers/pci/pcie/Kconfig: bool "PCI Express Port Bus support" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and module_init() translates to device_initcall(). [bhelgaas: changelog, remove unused DRIVER_* macros] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Tom Long Nguyen diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 70d7ad8..79327cc 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -1,12 +1,13 @@ /* * File: portdrv_pci.c * Purpose: PCI Express Port Bus Driver + * Author: Tom Nguyen + * Version: v1.0 * * Copyright (C) 2004 Intel * Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com) */ -#include #include #include #include @@ -21,16 +22,6 @@ #include "portdrv.h" #include "aer/aerdrv.h" -/* - * Version Information - */ -#define DRIVER_VERSION "v1.0" -#define DRIVER_AUTHOR "tom.l.nguyen@intel.com" -#define DRIVER_DESC "PCIe Port Bus Driver" -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); - /* If this switch is set, PCIe port native services should not be enabled. */ bool pcie_ports_disabled; @@ -341,7 +332,6 @@ static const struct pci_device_id port_pci_ids[] = { { PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_PCI << 8) | 0x00), ~0), }, { /* end: all zeroes */ } }; -MODULE_DEVICE_TABLE(pci, port_pci_ids); static const struct pci_error_handlers pcie_portdrv_err_handler = { .error_detected = pcie_portdrv_error_detected, @@ -406,5 +396,4 @@ static int __init pcie_portdrv_init(void) out: return retval; } - -module_init(pcie_portdrv_init); +device_initcall(pcie_portdrv_init); -- cgit v0.10.2 From 56540c77c50bcd93fe2ffa7eb92316bd41eea745 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:45 -0400 Subject: PCI: spear: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_SPEAR13XX drivers/pci/host/Kconfig: bool "STMicroelectronics SPEAr PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and module_init() translates to device_initcall(). [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Pratyush Anand diff --git a/drivers/pci/host/pcie-spear13xx.c b/drivers/pci/host/pcie-spear13xx.c index a4060b8..09aed85 100644 --- a/drivers/pci/host/pcie-spear13xx.c +++ b/drivers/pci/host/pcie-spear13xx.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -355,7 +355,6 @@ static const struct of_device_id spear13xx_pcie_of_match[] = { { .compatible = "st,spear1340-pcie", }, {}, }; -MODULE_DEVICE_TABLE(of, spear13xx_pcie_of_match); static struct platform_driver spear13xx_pcie_driver = { .probe = spear13xx_pcie_probe, @@ -365,14 +364,8 @@ static struct platform_driver spear13xx_pcie_driver = { }, }; -/* SPEAr13xx PCIe driver does not allow module unload */ - static int __init spear13xx_pcie_init(void) { return platform_driver_register(&spear13xx_pcie_driver); } -module_init(spear13xx_pcie_init); - -MODULE_DESCRIPTION("ST Microelectronics SPEAr13xx PCIe host controller driver"); -MODULE_AUTHOR("Pratyush Anand "); -MODULE_LICENSE("GPL v2"); +device_initcall(spear13xx_pcie_init); -- cgit v0.10.2 From e41faf0779193c477089db6486a2f42650c34f0e Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:46 -0400 Subject: PCI: designware: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_DW drivers/pci/host/Kconfig: bool Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Jingoo Han CC: Pratyush Anand diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 12afce1..1637420 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -802,7 +801,3 @@ void dw_pcie_setup_rc(struct pcie_port *pp) val |= PORT_LOGIC_SPEED_CHANGE; dw_pcie_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); } - -MODULE_AUTHOR("Jingoo Han "); -MODULE_DESCRIPTION("Designware PCIe host controller driver"); -MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From caf5548caaece50b552117d30e33a13f51f425d1 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:47 -0400 Subject: PCI: exynos: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCI_EXYNOS drivers/pci/host/Kconfig: bool "Samsung Exynos PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas Acked-by: Krzysztof Kozlowski CC: Jingoo Han CC: Kukjin Kim diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index 2199761..c3ae9c7 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include @@ -624,7 +624,6 @@ static const struct of_device_id exynos_pcie_of_match[] = { { .compatible = "samsung,exynos5440-pcie", }, {}, }; -MODULE_DEVICE_TABLE(of, exynos_pcie_of_match); static struct platform_driver exynos_pcie_driver = { .remove = __exit_p(exynos_pcie_remove), @@ -641,7 +640,3 @@ static int __init exynos_pcie_init(void) return platform_driver_probe(&exynos_pcie_driver, exynos_pcie_probe); } subsys_initcall(exynos_pcie_init); - -MODULE_AUTHOR("Jingoo Han "); -MODULE_DESCRIPTION("Samsung PCIe host controller driver"); -MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 68a0bfec72cb4f117198ae31df114dad4c5e405d Mon Sep 17 00:00:00 2001 From: Dong Bo Date: Mon, 4 Jul 2016 21:44:43 +0530 Subject: PCI: designware: Exchange viewport of `MEMORYs' and `CFGs/IOs' When we have only two view ports in a DesignWare PCIe platform, iatu0 is used for both CFG and IO accesses. When CFGs are sent to peripherals (e.g., lspci), iatu0 frequently switches between CFG and IO. For such scenarios, a MEMORY might be sent as an IOs by mistake. Considering the following configurations: MEMORY -> BASE_ADDR: 0xb4100000, LIMIT: 0xb4100FFF, TYPE=mem CFG -> BASE_ADDR: 0xb4000000, LIMIT: 0xb4000FFF, TYPE=cfg IO -> BASE_ADDR: 0xFFFFFFFF, LIMIT: 0xFFFFFFFE, TYPE=io Suppose PCIe has just completed a CFG access. To switch back to IO, it sets the BASE_ADDR to 0xFFFFFFFF, LIMIT 0xFFFFFFFE and TYPE to IO. When another CFG comes, the BASE_ADDR is set to 0xb4000000 to switch to CFG. At this moment, a MEMORY access shows up, since it matches with iatu0 (due to 0xb4000000 <= MEMORY BASE_ADDR <= MEMORY LIMIT <= 0xFFFFFFF), it is treated as an IO access by mistake, then sent to perpheral. This patch fixes the problem by exchanging the assignments of `MEMORYs' and `CFGs/IOs', which assigning MEMORYs to iatu0, CFGs and IOs to iatu1. We can still have issues with IO transfer, however memory transfer is used predominantly therefore we are just minimizing the risk of failure. Actually, we can not do much when we have only two viewports. We can either not allow the less frequent IO transfers at all, or can live with a remote possibility of getting it corrupted. Signed-off-by: Dong Bo [pratyush.anand@gmail.com: Modified commit log to capture remote risk] Signed-off-by: Pratyush Anand Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 38460138..c3b88bb 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -708,12 +708,12 @@ static int dw_pcie_rd_other_conf(struct pcie_port *pp, struct pci_bus *bus, va_cfg_base = pp->va_cfg1_base; } - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, type, cpu_addr, busdev, cfg_size); ret = dw_pcie_cfg_read(va_cfg_base + where, size, val); if (pp->num_viewport <= 2) - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, PCIE_ATU_TYPE_IO, pp->io_base, pp->io_bus_addr, pp->io_size); @@ -746,12 +746,12 @@ static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus, va_cfg_base = pp->va_cfg1_base; } - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, type, cpu_addr, busdev, cfg_size); ret = dw_pcie_cfg_write(va_cfg_base + where, size, val); if (pp->num_viewport <= 2) - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, PCIE_ATU_TYPE_IO, pp->io_base, pp->io_bus_addr, pp->io_size); @@ -890,7 +890,7 @@ void dw_pcie_setup_rc(struct pcie_port *pp) * we should not program the ATU here. */ if (!pp->ops->rd_other_conf) { - dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, + dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX0, PCIE_ATU_TYPE_MEM, pp->mem_base, pp->mem_bus_addr, pp->mem_size); if (pp->num_viewport > 2) -- cgit v0.10.2 From 639c5323257f940a8627e86c982f6b93e9e2615c Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Thu, 28 Jul 2016 16:14:54 +0000 Subject: PCI: designware: Remove redundant platform_get_resource() return value check devm_ioremap_resource() fails gracefully when given a NULL resource pointer, so we don't need to check separately for failure from platform_get_resource_byname(). Remove the redundant check. [bhelgaas: changelog] Signed-off-by: Wei Yongjun Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-designware-plat.c b/drivers/pci/host/pcie-designware-plat.c index c8079dc..17da005 100644 --- a/drivers/pci/host/pcie-designware-plat.c +++ b/drivers/pci/host/pcie-designware-plat.c @@ -100,9 +100,6 @@ static int dw_plat_pcie_probe(struct platform_device *pdev) pp->dev = &pdev->dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - dw_plat_pcie->mem_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(dw_plat_pcie->mem_base)) return PTR_ERR(dw_plat_pcie->mem_base); -- cgit v0.10.2 From 4068bd192a124585b9c55673a4ac5b3988abd981 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 22 Aug 2016 17:59:48 -0400 Subject: PCI: generic: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCI_HOST_COMMON drivers/pci/host/Kconfig: bool Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Will Deacon diff --git a/drivers/pci/host/pci-host-common.c b/drivers/pci/host/pci-host-common.c index 9d9d34e..946382f 100644 --- a/drivers/pci/host/pci-host-common.c +++ b/drivers/pci/host/pci-host-common.c @@ -1,4 +1,6 @@ /* + * Generic PCI host driver common code + * * 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. @@ -17,7 +19,6 @@ */ #include -#include #include #include #include @@ -162,7 +163,3 @@ int pci_host_common_probe(struct platform_device *pdev, pci_bus_add_devices(bus); return 0; } - -MODULE_DESCRIPTION("Generic PCI host driver common code"); -MODULE_AUTHOR("Will Deacon "); -MODULE_LICENSE("GPL v2"); -- cgit v0.10.2 From 61612e6dd49d08e174e12f9aca58a4aca297609b Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:44 -0400 Subject: PCI: Make DPC explicitly non-modular This code is not being built as a module by anyone: drivers/pci/pcie/Kconfig:config PCIE_DPC drivers/pci/pcie/Kconfig: bool "PCIe Downstream Port Containment support" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, module_init() translates to device_initcall(). [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Keith Busch CC: Mika Westerberg diff --git a/drivers/pci/pcie/pcie-dpc.c b/drivers/pci/pcie/pcie-dpc.c index 250f878..9811b14 100644 --- a/drivers/pci/pcie/pcie-dpc.c +++ b/drivers/pci/pcie/pcie-dpc.c @@ -1,5 +1,7 @@ /* * PCI Express Downstream Port Containment services driver + * Author: Keith Busch + * * Copyright (C) 2016 Intel Corp. * * This file is subject to the terms and conditions of the GNU General Public @@ -9,7 +11,7 @@ #include #include -#include +#include #include #include @@ -143,16 +145,4 @@ 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 "); -MODULE_LICENSE("GPL"); -MODULE_VERSION("0.1"); - -module_init(dpc_service_init); -module_exit(dpc_service_exit); +device_initcall(dpc_service_init); -- cgit v0.10.2 From d7def20400773c7c0be8dcee58434ddbb0f6f8d9 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:45 -0400 Subject: PCI/PME: Make explicitly non-modular This code is not being built as a module by anyone: config PCIE_PME def_bool y depends on PCIEPORTBUS && PM Remove traces of modularity so that when reading the driver there is no doubt it is builtin-only. Also delete the .remove function, since that doesn't seem to have a sensible use case. With "normal" endpoint drivers, we have in the past set the suppress_bind_attrs bit to make it clear that the use of ".remove" in a builtin driver was deleted, but here for PCI, it seems overkill to jump through the pcie_port_service_driver and into the struct device_driver in order to finally try and do something similar with the bind setting. Note that for non-modular code, module_init() translates to device_initcall(). Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/pme.c b/drivers/pci/pcie/pme.c index 1ae4c73..884bad5 100644 --- a/drivers/pci/pcie/pme.c +++ b/drivers/pci/pcie/pme.c @@ -10,7 +10,6 @@ * for more details. */ -#include #include #include #include @@ -449,17 +448,6 @@ static int pcie_pme_resume(struct pcie_device *srv) return 0; } -/** - * pcie_pme_remove - Prepare PCIe PME service device for removal. - * @srv - PCIe service device to remove. - */ -static void pcie_pme_remove(struct pcie_device *srv) -{ - pcie_pme_suspend(srv); - free_irq(srv->irq, srv); - kfree(get_service_data(srv)); -} - static struct pcie_port_service_driver pcie_pme_driver = { .name = "pcie_pme", .port_type = PCI_EXP_TYPE_ROOT_PORT, @@ -468,7 +456,6 @@ static struct pcie_port_service_driver pcie_pme_driver = { .probe = pcie_pme_probe, .suspend = pcie_pme_suspend, .resume = pcie_pme_resume, - .remove = pcie_pme_remove, }; /** @@ -478,5 +465,4 @@ static int __init pcie_pme_service_init(void) { return pcie_port_service_register(&pcie_pme_driver); } - -module_init(pcie_pme_service_init); +device_initcall(pcie_pme_service_init); -- cgit v0.10.2 From 8756336c1d6d27d844a8cfc61127859dca4090f5 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:46 -0400 Subject: PCI/AER: Make explicitly non-modular This code is not being built as a module by anyone: obj-$(CONFIG_PCIEAER) += aerdriver.o aerdriver-objs := aerdrv_errprint.o aerdrv_core.o aerdrv.o drivers/pci/pcie/aer/Kconfig:config PCIEAER drivers/pci/pcie/aer/Kconfig: bool "Root Port Advanced Error Reporting support" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, module_init() translates to device_initcall(). [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Tom Long Nguyen diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c index 48d21e0..49805a4 100644 --- a/drivers/pci/pcie/aer/aerdrv.c +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -15,7 +15,6 @@ * */ -#include #include #include #include @@ -37,9 +36,6 @@ #define DRIVER_VERSION "v1.0" #define DRIVER_AUTHOR "tom.l.nguyen@intel.com" #define DRIVER_DESC "Root Port Advanced Error Reporting Driver" -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); static int aer_probe(struct pcie_device *dev); static void aer_remove(struct pcie_device *dev); @@ -417,16 +413,4 @@ static int __init aer_service_init(void) return -ENXIO; return pcie_port_service_register(&aerdriver); } - -/** - * aer_service_exit - unregister AER root service driver - * - * Invoked when AER root service driver is unloaded. - */ -static void __exit aer_service_exit(void) -{ - pcie_port_service_unregister(&aerdriver); -} - -module_init(aer_service_init); -module_exit(aer_service_exit); +device_initcall(aer_service_init); -- cgit v0.10.2 From d29438d64448196065a3dcc34a696599323d6822 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:47 -0400 Subject: PCI: dra7xx: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCI_DRA7XX drivers/pci/host/Kconfig: bool "TI DRA7xx PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and builtin_platform_driver_probe() uses the same init level priority as module_platform_driver_probe(), so this doesn't change init ordering. Explicitly disallow driver unbind, since that doesn't have a sensible use case anyway, and it allows us to drop the ".remove" code for non-modular drivers. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Kishon Vijay Abraham I diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c index 81b3949..19223ed 100644 --- a/drivers/pci/host/pci-dra7xx.c +++ b/drivers/pci/host/pci-dra7xx.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -443,25 +443,6 @@ err_phy: return ret; } -static int __exit dra7xx_pcie_remove(struct platform_device *pdev) -{ - struct dra7xx_pcie *dra7xx = platform_get_drvdata(pdev); - struct pcie_port *pp = &dra7xx->pp; - struct device *dev = &pdev->dev; - int count = dra7xx->phy_count; - - if (pp->irq_domain) - irq_domain_remove(pp->irq_domain); - pm_runtime_put(dev); - pm_runtime_disable(dev); - while (count--) { - phy_power_off(dra7xx->phy[count]); - phy_exit(dra7xx->phy[count]); - } - - return 0; -} - #ifdef CONFIG_PM_SLEEP static int dra7xx_pcie_suspend(struct device *dev) { @@ -545,19 +526,13 @@ static const struct of_device_id of_dra7xx_pcie_match[] = { { .compatible = "ti,dra7-pcie", }, {}, }; -MODULE_DEVICE_TABLE(of, of_dra7xx_pcie_match); static struct platform_driver dra7xx_pcie_driver = { - .remove = __exit_p(dra7xx_pcie_remove), .driver = { .name = "dra7-pcie", .of_match_table = of_dra7xx_pcie_match, + .suppress_bind_attrs = true, .pm = &dra7xx_pcie_pm_ops, }, }; - -module_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); - -MODULE_AUTHOR("Kishon Vijay Abraham I "); -MODULE_DESCRIPTION("TI PCIe controller driver"); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver_probe(dra7xx_pcie_driver, dra7xx_pcie_probe); -- cgit v0.10.2 From f9a6660083385fa329808d0747ea54c62bd9155c Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:48 -0400 Subject: PCI: qcom: Make explicitly non-modular This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_QCOM drivers/pci/host/Kconfig: bool "Qualcomm PCIe controller" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, MODULE_DEVICE_TABLE is a no-op and builtin_platform_driver() uses the same init level priority as module_platform_driver(), so this doesn't change init ordering. Explicitly disallow driver unbind, since that doesn't have a sensible use case anyway, and it allows us to drop the ".remove" code for non-modular drivers. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Stanimir Varbanov diff --git a/drivers/pci/host/pcie-qcom.c b/drivers/pci/host/pcie-qcom.c index f2f90c5..5ec2d44 100644 --- a/drivers/pci/host/pcie-qcom.c +++ b/drivers/pci/host/pcie-qcom.c @@ -1,7 +1,11 @@ /* + * Qualcomm PCIe root complex driver + * * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * Copyright 2015 Linaro Limited. * + * Author: Stanimir Varbanov + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. @@ -19,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -570,37 +574,19 @@ static int qcom_pcie_probe(struct platform_device *pdev) return 0; } -static int qcom_pcie_remove(struct platform_device *pdev) -{ - struct qcom_pcie *pcie = platform_get_drvdata(pdev); - - qcom_ep_reset_assert(pcie); - phy_power_off(pcie->phy); - phy_exit(pcie->phy); - pcie->ops->deinit(pcie); - - return 0; -} - static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-ipq8064", .data = &ops_v0 }, { .compatible = "qcom,pcie-apq8064", .data = &ops_v0 }, { .compatible = "qcom,pcie-apq8084", .data = &ops_v1 }, { } }; -MODULE_DEVICE_TABLE(of, qcom_pcie_match); static struct platform_driver qcom_pcie_driver = { .probe = qcom_pcie_probe, - .remove = qcom_pcie_remove, .driver = { .name = "qcom-pcie", + .suppress_bind_attrs = true, .of_match_table = qcom_pcie_match, }, }; - -module_platform_driver(qcom_pcie_driver); - -MODULE_AUTHOR("Stanimir Varbanov "); -MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver(qcom_pcie_driver); -- cgit v0.10.2 From da4eafcae3efe9225a36c062b053d49d80364c3f Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:49 -0400 Subject: PCI: xilinx: Make explicitly non-modular MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_XILINX drivers/pci/host/Kconfig: bool "Xilinx AXI PCIe host bridge support" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, builtin_platform_driver() uses the same init level priority as module_platform_driver(), so this doesn't change init ordering. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Michal Simek CC: "Sören Brinkmann" diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index a30e016..7b7dbd2 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -506,35 +506,6 @@ static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data) } /** - * xilinx_pcie_free_irq_domain - Free IRQ domain - * @port: PCIe port information - */ -static void xilinx_pcie_free_irq_domain(struct xilinx_pcie_port *port) -{ - int i; - u32 irq, num_irqs; - - /* Free IRQ Domain */ - if (IS_ENABLED(CONFIG_PCI_MSI)) { - - free_pages(port->msi_pages, 0); - - num_irqs = XILINX_NUM_MSI_IRQS; - } else { - /* INTx */ - num_irqs = 4; - } - - for (i = 0; i < num_irqs; i++) { - irq = irq_find_mapping(port->irq_domain, i); - if (irq > 0) - irq_dispose_mapping(irq); - } - - irq_domain_remove(port->irq_domain); -} - -/** * xilinx_pcie_init_irq_domain - Initialize IRQ domain * @port: PCIe port information * @@ -724,21 +695,6 @@ error: return err; } -/** - * xilinx_pcie_remove - Remove function - * @pdev: Platform device pointer - * - * Return: '0' always - */ -static int xilinx_pcie_remove(struct platform_device *pdev) -{ - struct xilinx_pcie_port *port = platform_get_drvdata(pdev); - - xilinx_pcie_free_irq_domain(port); - - return 0; -} - static struct of_device_id xilinx_pcie_of_match[] = { { .compatible = "xlnx,axi-pcie-host-1.00.a", }, {} @@ -751,10 +707,5 @@ static struct platform_driver xilinx_pcie_driver = { .suppress_bind_attrs = true, }, .probe = xilinx_pcie_probe, - .remove = xilinx_pcie_remove, }; -module_platform_driver(xilinx_pcie_driver); - -MODULE_AUTHOR("Xilinx Inc"); -MODULE_DESCRIPTION("Xilinx AXI PCIe driver"); -MODULE_LICENSE("GPL v2"); +builtin_platform_driver(xilinx_pcie_driver); -- cgit v0.10.2 From ff187e777cce8033dac2cd54d010bb7978e84642 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:50 -0400 Subject: PCI: xilinx-nwl: Make explicitly non-modular MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This code is not being built as a module by anyone: drivers/pci/host/Kconfig:config PCIE_XILINX_NWL drivers/pci/host/Kconfig: bool "NWL PCIe Core" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Explicitly disallow driver unbind, since that doesn't have a sensible use case anyway, and it allows us to drop the ".remove" code for non-modular drivers. Delete several functions only used by the remove function. Note that for non-modular code, builtin_platform_driver() uses the same init level priority as module_platform_driver(), so this doesn't change init ordering. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Michal Simek CC: "Sören Brinkmann" CC: Marc Zyngier CC: Bharat Kumar Gogada diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c index 0b597d9..df7fb8b 100644 --- a/drivers/pci/host/pcie-xilinx-nwl.c +++ b/drivers/pci/host/pcie-xilinx-nwl.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -459,40 +459,6 @@ static const struct irq_domain_ops dev_msi_domain_ops = { .free = nwl_irq_domain_free, }; -static void nwl_msi_free_irq_domain(struct nwl_pcie *pcie) -{ - struct nwl_msi *msi = &pcie->msi; - - if (msi->irq_msi0) - irq_set_chained_handler_and_data(msi->irq_msi0, NULL, NULL); - if (msi->irq_msi1) - irq_set_chained_handler_and_data(msi->irq_msi1, NULL, NULL); - - if (msi->msi_domain) - irq_domain_remove(msi->msi_domain); - if (msi->dev_domain) - irq_domain_remove(msi->dev_domain); - - kfree(msi->bitmap); - msi->bitmap = NULL; -} - -static void nwl_pcie_free_irq_domain(struct nwl_pcie *pcie) -{ - int i; - u32 irq; - - for (i = 0; i < INTX_NUM; i++) { - irq = irq_find_mapping(pcie->legacy_irq_domain, i + 1); - if (irq > 0) - irq_dispose_mapping(irq); - } - if (pcie->legacy_irq_domain) - irq_domain_remove(pcie->legacy_irq_domain); - - nwl_msi_free_irq_domain(pcie); -} - static int nwl_pcie_init_msi_irq_domain(struct nwl_pcie *pcie) { #ifdef CONFIG_PCI_MSI @@ -867,25 +833,12 @@ error: return err; } -static int nwl_pcie_remove(struct platform_device *pdev) -{ - struct nwl_pcie *pcie = platform_get_drvdata(pdev); - - nwl_pcie_free_irq_domain(pcie); - platform_set_drvdata(pdev, NULL); - return 0; -} - static struct platform_driver nwl_pcie_driver = { .driver = { .name = "nwl-pcie", + .suppress_bind_attrs = true, .of_match_table = nwl_pcie_of_match, }, .probe = nwl_pcie_probe, - .remove = nwl_pcie_remove, }; -module_platform_driver(nwl_pcie_driver); - -MODULE_AUTHOR("Xilinx, Inc"); -MODULE_DESCRIPTION("NWL PCIe driver"); -MODULE_LICENSE("GPL"); +builtin_platform_driver(nwl_pcie_driver); -- cgit v0.10.2 From 57b51b9adb4ca67d15c4f4b61268771da014c44a Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:51 -0400 Subject: PCI: hotplug: Make core explicitly non-modular This code is not being built as a module by anyone: obj-$(CONFIG_HOTPLUG_PCI) += pci_hotplug.o [...] pci_hotplug-objs := pci_hotplug_core.o drivers/pci/hotplug/Kconfig:menuconfig HOTPLUG_PCI drivers/pci/hotplug/Kconfig: bool "Support for PCI Hotplug" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Remove orphaned exit function in cpci_hotplug_core.c. Note that for non-modular code, module_init() translates to device_initcall(). One could argue that we should use subsys_initcall() here, but for now we stick with runtime equivalence. We would delete module.h and just keep the moduleparam.h include (since the file does use module_param), but there is a try_module_get and module_put pairing that prevents us from doing that. [bhelgaas: changelog] Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Scott Murray CC: Kristen Carlson Accardi diff --git a/drivers/pci/hotplug/cpci_hotplug.h b/drivers/pci/hotplug/cpci_hotplug.h index 555bcde..60e66e0 100644 --- a/drivers/pci/hotplug/cpci_hotplug.h +++ b/drivers/pci/hotplug/cpci_hotplug.h @@ -101,10 +101,8 @@ int cpci_unconfigure_slot(struct slot *slot); #ifdef CONFIG_HOTPLUG_PCI_CPCI int cpci_hotplug_init(int debug); -void cpci_hotplug_exit(void); #else static inline int cpci_hotplug_init(int debug) { return 0; } -static inline void cpci_hotplug_exit(void) { } #endif #endif /* _CPCI_HOTPLUG_H */ diff --git a/drivers/pci/hotplug/cpci_hotplug_core.c b/drivers/pci/hotplug/cpci_hotplug_core.c index 7d3866c..7ec8a8f 100644 --- a/drivers/pci/hotplug/cpci_hotplug_core.c +++ b/drivers/pci/hotplug/cpci_hotplug_core.c @@ -719,13 +719,3 @@ cpci_hotplug_init(int debug) cpci_debug = debug; return 0; } - -void __exit -cpci_hotplug_exit(void) -{ - /* - * Clean everything up. - */ - cpci_hp_stop(); - cpci_hp_unregister_controller(controller); -} diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c index 9acd199..fea0b8b 100644 --- a/drivers/pci/hotplug/pci_hotplug_core.c +++ b/drivers/pci/hotplug/pci_hotplug_core.c @@ -25,7 +25,7 @@ * */ -#include +#include /* try_module_get & module_put */ #include #include #include @@ -537,17 +537,11 @@ static int __init pci_hotplug_init(void) info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); return result; } +device_initcall(pci_hotplug_init); -static void __exit pci_hotplug_exit(void) -{ - cpci_hotplug_exit(); -} - -module_init(pci_hotplug_init); -module_exit(pci_hotplug_exit); - -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); +/* + * not really modular, but the easiest way to keep compat with existing + * bootargs behaviour is to continue using module_param here. + */ module_param(debug, bool, 0644); MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); -- cgit v0.10.2 From 70626d88385174397b8a17d767282e5600e67415 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Wed, 24 Aug 2016 16:57:52 -0400 Subject: PCI: pciehp: Make explicitly non-modular This code is not being built as a module by anyone: obj-$(CONFIG_HOTPLUG_PCI_PCIE) += pciehp.o pciehp-objs := pciehp_core.o \ drivers/pci/pcie/Kconfig:config HOTPLUG_PCI_PCIE drivers/pci/pcie/Kconfig: bool "PCI Express Hotplug driver" Remove uses of MODULE_DESCRIPTION(), MODULE_AUTHOR(), MODULE_LICENSE(), etc., so that when reading the driver there is no doubt it is builtin-only. The information is preserved in comments at the top of the file. Note that for non-modular code, module_init() translates to device_initcall(). One could argue that we should use subsys_initcall() here, but for now we stick with runtime equivalence. We delete module.h but we keep the moduleparam.h include, since we are keeping the module_param() that the file has as-is for now. Signed-off-by: Paul Gortmaker Signed-off-by: Bjorn Helgaas CC: Kristen Carlson Accardi diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index ac531e6..fb0f863 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -27,7 +27,6 @@ * */ -#include #include #include #include @@ -47,10 +46,10 @@ static bool pciehp_force; #define DRIVER_AUTHOR "Dan Zink , Greg Kroah-Hartman , Dely Sy " #define DRIVER_DESC "PCI Express Hot Plug Controller Driver" -MODULE_AUTHOR(DRIVER_AUTHOR); -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); - +/* + * not really modular, but the easiest way to keep compat with existing + * bootargs behaviour is to continue using module_param here. + */ module_param(pciehp_debug, bool, 0644); module_param(pciehp_poll_mode, bool, 0644); module_param(pciehp_poll_time, int, 0644); @@ -337,13 +336,4 @@ static int __init pcied_init(void) return retval; } - -static void __exit pcied_cleanup(void) -{ - dbg("unload_pciehpd()\n"); - pcie_port_service_unregister(&hpdriver_portdrv); - info(DRIVER_DESC " version: " DRIVER_VERSION " unloaded\n"); -} - -module_init(pcied_init); -module_exit(pcied_cleanup); +device_initcall(pcied_init); -- cgit v0.10.2 From 8b2ec318eece89be5e33d5313a25461a55a3177a Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Sun, 12 Jun 2016 16:26:40 -0500 Subject: PCI: Add PTM clock granularity information The PTM Control register (PCIe r3.1, sec 7.32.3) contains an Effective Granularity field: This provides information relating to the expected accuracy of the PTM clock, but does not otherwise affect the PTM mechanism. Set the Effective Granularity based on the PTM Root and any intervening PTM Time Sources. This does not set Effective Granularity for Root Complex Integrated Endpoints because I don't know how to figure out clock granularity for them. The spec says: ... system software must set [Effective Granularity] to the value reported in the Local Clock Granularity field by the associated PTM Time Source. but I don't know how to identify the associated PTM Time Source. Normally it's the upstream bridge, but an integrated endpoint has no upstream bridge. Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/ptm.c b/drivers/pci/pcie/ptm.c index a14ac94..bab8ac6 100644 --- a/drivers/pci/pcie/ptm.c +++ b/drivers/pci/pcie/ptm.c @@ -19,13 +19,29 @@ static void pci_ptm_info(struct pci_dev *dev) { - dev_info(&dev->dev, "PTM enabled%s\n", dev->ptm_root ? " (root)" : ""); + char clock_desc[8]; + + switch (dev->ptm_granularity) { + case 0: + snprintf(clock_desc, sizeof(clock_desc), "unknown"); + break; + case 255: + snprintf(clock_desc, sizeof(clock_desc), ">254ns"); + break; + default: + snprintf(clock_desc, sizeof(clock_desc), "%udns", + dev->ptm_granularity); + break; + } + dev_info(&dev->dev, "PTM enabled%s, %s granularity\n", + dev->ptm_root ? " (root)" : "", clock_desc); } void pci_ptm_init(struct pci_dev *dev) { int pos; u32 cap, ctrl; + u8 local_clock; struct pci_dev *ups; if (!pci_is_pcie(dev)) @@ -45,6 +61,7 @@ void pci_ptm_init(struct pci_dev *dev) return; pci_read_config_dword(dev, pos + PCI_PTM_CAP, &cap); + local_clock = (cap & PCI_PTM_GRANULARITY_MASK) >> 8; /* * There's no point in enabling PTM unless it's enabled in the @@ -55,14 +72,20 @@ void pci_ptm_init(struct pci_dev *dev) ups = pci_upstream_bridge(dev); if (ups && ups->ptm_enabled) { ctrl = PCI_PTM_CTRL_ENABLE; + if (ups->ptm_granularity == 0) + dev->ptm_granularity = 0; + else if (ups->ptm_granularity > local_clock) + dev->ptm_granularity = ups->ptm_granularity; } else { if (cap & PCI_PTM_CAP_ROOT) { ctrl = PCI_PTM_CTRL_ENABLE | PCI_PTM_CTRL_ROOT; dev->ptm_root = 1; + dev->ptm_granularity = local_clock; } else return; } + ctrl |= dev->ptm_granularity << 8; pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); dev->ptm_enabled = 1; @@ -98,18 +121,22 @@ int pci_enable_ptm(struct pci_dev *dev, u8 *granularity) ups = pci_upstream_bridge(dev); if (!ups || !ups->ptm_enabled) return -EINVAL; + + dev->ptm_granularity = ups->ptm_granularity; } else if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END) { + dev->ptm_granularity = 0; } else return -EINVAL; ctrl = PCI_PTM_CTRL_ENABLE; + ctrl |= dev->ptm_granularity << 8; pci_write_config_dword(dev, pos + PCI_PTM_CTRL, ctrl); dev->ptm_enabled = 1; pci_ptm_info(dev); if (granularity) - *granularity = 0; + *granularity = dev->ptm_granularity; return 0; } EXPORT_SYMBOL(pci_enable_ptm); diff --git a/include/linux/pci.h b/include/linux/pci.h index 9e4b6d6..7256f33 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -371,6 +371,7 @@ struct pci_dev { #ifdef CONFIG_PCIE_PTM unsigned int ptm_root:1; unsigned int ptm_enabled:1; + u8 ptm_granularity; #endif #ifdef CONFIG_PCI_MSI const struct attribute_group **msi_irq_groups; diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 72bbe14..d812172 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -969,6 +969,7 @@ #define PCI_PTM_CAP 0x04 /* PTM Capability */ #define PCI_PTM_CAP_REQ 0x00000001 /* Requester capable */ #define PCI_PTM_CAP_ROOT 0x00000004 /* Root capable */ +#define PCI_PTM_GRANULARITY_MASK 0x0000FF00 /* Clock granularity */ #define PCI_PTM_CTRL 0x08 /* PTM Control */ #define PCI_PTM_CTRL_ENABLE 0x00000001 /* PTM enable */ #define PCI_PTM_CTRL_ROOT 0x00000002 /* Root select */ -- cgit v0.10.2 From 31fc0ad47e2e0b8417616aa0f1ddcc67edf1e109 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Fri, 26 Aug 2016 09:47:24 +0800 Subject: PCI: altera: Rework config accessors for use without a struct pci_bus Rework configs accessors so a future patch can use them in _probe() with struct altera_pcie instead of struct pci_bus. Signed-off-by: Ley Foon Tan Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 58eef99..34e6258 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -330,22 +330,14 @@ static int tlp_cfg_dword_write(struct altera_pcie *pcie, u8 bus, u32 devfn, return PCIBIOS_SUCCESSFUL; } -static int altera_pcie_cfg_read(struct pci_bus *bus, unsigned int devfn, - int where, int size, u32 *value) +static int _altera_pcie_cfg_read(struct altera_pcie *pcie, u8 busno, + unsigned int devfn, int where, int size, + u32 *value) { - struct altera_pcie *pcie = bus->sysdata; int ret; u32 data; u8 byte_en; - if (altera_pcie_hide_rc_bar(bus, devfn, where)) - return PCIBIOS_BAD_REGISTER_NUMBER; - - if (!altera_pcie_valid_config(pcie, bus, PCI_SLOT(devfn))) { - *value = 0xffffffff; - return PCIBIOS_DEVICE_NOT_FOUND; - } - switch (size) { case 1: byte_en = 1 << (where & 3); @@ -358,7 +350,7 @@ static int altera_pcie_cfg_read(struct pci_bus *bus, unsigned int devfn, break; } - ret = tlp_cfg_dword_read(pcie, bus->number, devfn, + ret = tlp_cfg_dword_read(pcie, busno, devfn, (where & ~DWORD_MASK), byte_en, &data); if (ret != PCIBIOS_SUCCESSFUL) return ret; @@ -378,20 +370,14 @@ static int altera_pcie_cfg_read(struct pci_bus *bus, unsigned int devfn, return PCIBIOS_SUCCESSFUL; } -static int altera_pcie_cfg_write(struct pci_bus *bus, unsigned int devfn, - int where, int size, u32 value) +static int _altera_pcie_cfg_write(struct altera_pcie *pcie, u8 busno, + unsigned int devfn, int where, int size, + u32 value) { - struct altera_pcie *pcie = bus->sysdata; u32 data32; u32 shift = 8 * (where & 3); u8 byte_en; - if (altera_pcie_hide_rc_bar(bus, devfn, where)) - return PCIBIOS_BAD_REGISTER_NUMBER; - - if (!altera_pcie_valid_config(pcie, bus, PCI_SLOT(devfn))) - return PCIBIOS_DEVICE_NOT_FOUND; - switch (size) { case 1: data32 = (value & 0xff) << shift; @@ -407,8 +393,40 @@ static int altera_pcie_cfg_write(struct pci_bus *bus, unsigned int devfn, break; } - return tlp_cfg_dword_write(pcie, bus->number, devfn, - (where & ~DWORD_MASK), byte_en, data32); + return tlp_cfg_dword_write(pcie, busno, devfn, (where & ~DWORD_MASK), + byte_en, data32); +} + +static int altera_pcie_cfg_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *value) +{ + struct altera_pcie *pcie = bus->sysdata; + + if (altera_pcie_hide_rc_bar(bus, devfn, where)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + if (!altera_pcie_valid_config(pcie, bus, PCI_SLOT(devfn))) { + *value = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + return _altera_pcie_cfg_read(pcie, bus->number, devfn, where, size, + value); +} + +static int altera_pcie_cfg_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 value) +{ + struct altera_pcie *pcie = bus->sysdata; + + if (altera_pcie_hide_rc_bar(bus, devfn, where)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + if (!altera_pcie_valid_config(pcie, bus, PCI_SLOT(devfn))) + return PCIBIOS_DEVICE_NOT_FOUND; + + return _altera_pcie_cfg_write(pcie, bus->number, devfn, where, size, + value); } static struct pci_ops altera_pcie_ops = { -- cgit v0.10.2 From d5b0dc86a2b93bdf1b3e2309153b85d596d4503d Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Thu, 4 Aug 2016 15:52:07 +0800 Subject: dt-bindings: PCI: rockchip: Add DT bindings for Rockchip PCIe controller Add a binding that describes the Rockchip PCIe controller found on Rockchip SoCs PCIe interface. Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas Reviewed-by: Brian Norris Acked-by: Rob Herring diff --git a/Documentation/devicetree/bindings/pci/rockchip-pcie.txt b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt new file mode 100644 index 0000000..ba67b39 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/rockchip-pcie.txt @@ -0,0 +1,106 @@ +* Rockchip AXI PCIe Root Port Bridge DT description + +Required properties: +- #address-cells: Address representation for root ports, set to <3> +- #size-cells: Size representation for root ports, set to <2> +- #interrupt-cells: specifies the number of cells needed to encode an + interrupt source. The value must be 1. +- compatible: Should contain "rockchip,rk3399-pcie" +- reg: Two register ranges as listed in the reg-names property +- reg-names: Must include the following names + - "axi-base" + - "apb-base" +- clocks: Must contain an entry for each entry in clock-names. + See ../clocks/clock-bindings.txt for details. +- clock-names: Must include the following entries: + - "aclk" + - "aclk-perf" + - "hclk" + - "pm" +- msi-map: Maps a Requester ID to an MSI controller and associated + msi-specifier data. See ./pci-msi.txt +- phys: From PHY bindings: Phandle for the Generic PHY for PCIe. +- phy-names: MUST be "pcie-phy". +- interrupts: Three interrupt entries must be specified. +- interrupt-names: Must include the following names + - "sys" + - "legacy" + - "client" +- resets: Must contain five entries for each entry in reset-names. + See ../reset/reset.txt for details. +- reset-names: Must include the following names + - "core" + - "mgmt" + - "mgmt-sticky" + - "pipe" +- pinctrl-names : The pin control state names +- pinctrl-0: The "default" pinctrl state +- #interrupt-cells: specifies the number of cells needed to encode an + interrupt source. The value must be 1. +- interrupt-map-mask and interrupt-map: standard PCI properties + +Optional Property: +- ep-gpios: contain the entry for pre-reset gpio +- num-lanes: number of lanes to use +- vpcie3v3-supply: The phandle to the 3.3v regulator to use for PCIe. +- vpcie1v8-supply: The phandle to the 1.8v regulator to use for PCIe. +- vpcie0v9-supply: The phandle to the 0.9v regulator to use for PCIe. + +*Interrupt controller child node* +The core controller provides a single interrupt for legacy INTx. The PCIe node +should contain an interrupt controller node as a target for the PCI +'interrupt-map' property. This node represents the domain at which the four +INTx interrupts are decoded and routed. + + +Required properties for Interrupt controller child node: +- interrupt-controller: identifies the node as an interrupt controller +- #address-cells: specifies the number of cells needed to encode an + address. The value must be 0. +- #interrupt-cells: specifies the number of cells needed to encode an + interrupt source. The value must be 1. + +Example: + +pcie0: pcie@f8000000 { + compatible = "rockchip,rk3399-pcie"; + #address-cells = <3>; + #size-cells = <2>; + clocks = <&cru ACLK_PCIE>, <&cru ACLK_PERF_PCIE>, + <&cru PCLK_PCIE>, <&cru SCLK_PCIE_PM>; + clock-names = "aclk", "aclk-perf", + "hclk", "pm"; + bus-range = <0x0 0x1>; + interrupts = , + , + ; + interrupt-names = "sys", "legacy", "client"; + assigned-clocks = <&cru SCLK_PCIEPHY_REF>; + assigned-clock-parents = <&cru SCLK_PCIEPHY_REF100M>; + assigned-clock-rates = <100000000>; + ep-gpios = <&gpio3 13 GPIO_ACTIVE_HIGH>; + ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x600000 + 0x81000000 0x0 0xfa600000 0x0 0xfa600000 0x0 0x100000>; + num-lanes = <4>; + msi-map = <0x0 &its 0x0 0x1000>; + reg = <0x0 0xf8000000 0x0 0x2000000>, <0x0 0xfd000000 0x0 0x1000000>; + reg-names = "axi-base", "apb-base"; + resets = <&cru SRST_PCIE_CORE>, <&cru SRST_PCIE_MGMT>, + <&cru SRST_PCIE_MGMT_STICKY>, <&cru SRST_PCIE_PIPE>; + reset-names = "core", "mgmt", "mgmt-sticky", "pipe"; + phys = <&pcie_phy>; + phy-names = "pcie-phy"; + pinctrl-names = "default"; + pinctrl-0 = <&pcie_clkreq>; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &pcie0_intc 0>, + <0 0 0 2 &pcie0_intc 1>, + <0 0 0 3 &pcie0_intc 2>, + <0 0 0 4 &pcie0_intc 3>; + pcie0_intc: interrupt-controller { + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <1>; + }; +}; -- cgit v0.10.2 From e77f847df54c6b01f4628dd352f4168db3082aa8 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Sat, 3 Sep 2016 11:41:09 -0500 Subject: PCI: rockchip: Add Rockchip PCIe controller support Add support for the Rockchip PCIe controller found on RK3399 SoC platform. [bhelgaas: fold in Brian's rockchip_pcie_client_irq_handler() OR fix, other fixes and cleanups from Guenter Roeck and me, uninitialized variable fix from Arnd Bergmann ] Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas Reviewed-by: Brian Norris diff --git a/MAINTAINERS b/MAINTAINERS index 20bb1d0..c0e623f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9083,6 +9083,15 @@ S: Maintained F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt F: drivers/pci/host/pcie-hisi.c +PCIE DRIVER FOR ROCKCHIP +M: Shawn Lin +M: Wenrui Li +L: linux-pci@vger.kernel.org +L: linux-rockchip@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/pci/rockchip-pcie.txt +F: drivers/pci/host/pcie-rockchip.c + PCIE DRIVER FOR QUALCOMM MSM M: Stanimir Varbanov L: linux-pci@vger.kernel.org diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 9b485d8..90f5e89 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -274,4 +274,15 @@ config PCIE_ARTPEC6 Say Y here to enable PCIe controller support on Axis ARTPEC-6 SoCs. This PCIe controller uses the DesignWare core. +config PCIE_ROCKCHIP + bool "Rockchip PCIe controller" + depends on ARCH_ROCKCHIP + depends on OF + depends on PCI_MSI_IRQ_DOMAIN + select MFD_SYSCON + help + Say Y here if you want internal PCI support on Rockchip SoC. + There is 1 internal PCIe port available to support GEN2 with + 4 slots. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 8843410..a8afc16 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o +obj-$(CONFIG_PCIE_ROCKCHIP) += pcie-rockchip.o diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c new file mode 100644 index 0000000..8bedc1e --- /dev/null +++ b/drivers/pci/host/pcie-rockchip.c @@ -0,0 +1,1198 @@ +/* + * Rockchip AXI PCIe host controller driver + * + * Copyright (c) 2016 Rockchip, Inc. + * + * Author: Shawn Lin + * Wenrui Li + * + * Bits taken from Synopsys Designware Host controller driver and + * ARM PCI Host generic driver. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The upper 16 bits of PCIE_CLIENT_CONFIG are a write mask for the lower 16 + * bits. This allows atomic updates of the register without locking. + */ +#define HIWORD_UPDATE(mask, val) (((mask) << 16) | (val)) +#define HIWORD_UPDATE_BIT(val) HIWORD_UPDATE(val, val) + +#define ENCODE_LANES(x) ((((x) >> 1) & 3) << 4) + +#define PCIE_CLIENT_BASE 0x0 +#define PCIE_CLIENT_CONFIG (PCIE_CLIENT_BASE + 0x00) +#define PCIE_CLIENT_CONF_ENABLE HIWORD_UPDATE_BIT(0x0001) +#define PCIE_CLIENT_LINK_TRAIN_ENABLE HIWORD_UPDATE_BIT(0x0002) +#define PCIE_CLIENT_ARI_ENABLE HIWORD_UPDATE_BIT(0x0008) +#define PCIE_CLIENT_CONF_LANE_NUM(x) HIWORD_UPDATE(0x0030, ENCODE_LANES(x)) +#define PCIE_CLIENT_MODE_RC HIWORD_UPDATE_BIT(0x0040) +#define PCIE_CLIENT_GEN_SEL_2 HIWORD_UPDATE_BIT(0x0080) +#define PCIE_CLIENT_BASIC_STATUS1 (PCIE_CLIENT_BASE + 0x48) +#define PCIE_CLIENT_LINK_STATUS_UP 0x00300000 +#define PCIE_CLIENT_LINK_STATUS_MASK 0x00300000 +#define PCIE_CLIENT_INT_MASK (PCIE_CLIENT_BASE + 0x4c) +#define PCIE_CLIENT_INT_STATUS (PCIE_CLIENT_BASE + 0x50) +#define PCIE_CLIENT_INTR_MASK GENMASK(8, 5) +#define PCIE_CLIENT_INTR_SHIFT 5 +#define PCIE_CLIENT_INT_LEGACY_DONE BIT(15) +#define PCIE_CLIENT_INT_MSG BIT(14) +#define PCIE_CLIENT_INT_HOT_RST BIT(13) +#define PCIE_CLIENT_INT_DPA BIT(12) +#define PCIE_CLIENT_INT_FATAL_ERR BIT(11) +#define PCIE_CLIENT_INT_NFATAL_ERR BIT(10) +#define PCIE_CLIENT_INT_CORR_ERR BIT(9) +#define PCIE_CLIENT_INT_INTD BIT(8) +#define PCIE_CLIENT_INT_INTC BIT(7) +#define PCIE_CLIENT_INT_INTB BIT(6) +#define PCIE_CLIENT_INT_INTA BIT(5) +#define PCIE_CLIENT_INT_LOCAL BIT(4) +#define PCIE_CLIENT_INT_UDMA BIT(3) +#define PCIE_CLIENT_INT_PHY BIT(2) +#define PCIE_CLIENT_INT_HOT_PLUG BIT(1) +#define PCIE_CLIENT_INT_PWR_STCG BIT(0) + +#define PCIE_CLIENT_INT_LEGACY \ + (PCIE_CLIENT_INT_INTA | PCIE_CLIENT_INT_INTB | \ + PCIE_CLIENT_INT_INTC | PCIE_CLIENT_INT_INTD) + +#define PCIE_CLIENT_INT_CLI \ + (PCIE_CLIENT_INT_CORR_ERR | PCIE_CLIENT_INT_NFATAL_ERR | \ + PCIE_CLIENT_INT_FATAL_ERR | PCIE_CLIENT_INT_DPA | \ + PCIE_CLIENT_INT_HOT_RST | PCIE_CLIENT_INT_MSG | \ + PCIE_CLIENT_INT_LEGACY_DONE | PCIE_CLIENT_INT_LEGACY | \ + PCIE_CLIENT_INT_PHY) + +#define PCIE_CORE_CTRL_MGMT_BASE 0x900000 +#define PCIE_CORE_CTRL (PCIE_CORE_CTRL_MGMT_BASE + 0x000) +#define PCIE_CORE_PL_CONF_SPEED_5G 0x00000008 +#define PCIE_CORE_PL_CONF_SPEED_MASK 0x00000018 +#define PCIE_CORE_PL_CONF_LANE_MASK 0x00000006 +#define PCIE_CORE_PL_CONF_LANE_SHIFT 1 +#define PCIE_CORE_INT_STATUS (PCIE_CORE_CTRL_MGMT_BASE + 0x20c) +#define PCIE_CORE_INT_PRFPE BIT(0) +#define PCIE_CORE_INT_CRFPE BIT(1) +#define PCIE_CORE_INT_RRPE BIT(2) +#define PCIE_CORE_INT_PRFO BIT(3) +#define PCIE_CORE_INT_CRFO BIT(4) +#define PCIE_CORE_INT_RT BIT(5) +#define PCIE_CORE_INT_RTR BIT(6) +#define PCIE_CORE_INT_PE BIT(7) +#define PCIE_CORE_INT_MTR BIT(8) +#define PCIE_CORE_INT_UCR BIT(9) +#define PCIE_CORE_INT_FCE BIT(10) +#define PCIE_CORE_INT_CT BIT(11) +#define PCIE_CORE_INT_UTC BIT(18) +#define PCIE_CORE_INT_MMVC BIT(19) +#define PCIE_CORE_INT_MASK (PCIE_CORE_CTRL_MGMT_BASE + 0x210) +#define PCIE_RC_BAR_CONF (PCIE_CORE_CTRL_MGMT_BASE + 0x300) + +#define PCIE_CORE_INT \ + (PCIE_CORE_INT_PRFPE | PCIE_CORE_INT_CRFPE | \ + PCIE_CORE_INT_RRPE | PCIE_CORE_INT_CRFO | \ + PCIE_CORE_INT_RT | PCIE_CORE_INT_RTR | \ + PCIE_CORE_INT_PE | PCIE_CORE_INT_MTR | \ + PCIE_CORE_INT_UCR | PCIE_CORE_INT_FCE | \ + PCIE_CORE_INT_CT | PCIE_CORE_INT_UTC | \ + PCIE_CORE_INT_MMVC) + +#define PCIE_RC_CONFIG_BASE 0xa00000 +#define PCIE_RC_CONFIG_VENDOR (PCIE_RC_CONFIG_BASE + 0x00) +#define PCIE_RC_CONFIG_RID_CCR (PCIE_RC_CONFIG_BASE + 0x08) +#define PCIE_RC_CONFIG_SCC_SHIFT 16 +#define PCIE_RC_CONFIG_LCS (PCIE_RC_CONFIG_BASE + 0xd0) +#define PCIE_RC_CONFIG_LCS_RETRAIN_LINK BIT(5) +#define PCIE_RC_CONFIG_LCS_LBMIE BIT(10) +#define PCIE_RC_CONFIG_LCS_LABIE BIT(11) +#define PCIE_RC_CONFIG_LCS_LBMS BIT(30) +#define PCIE_RC_CONFIG_LCS_LAMS BIT(31) +#define PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2 (PCIE_RC_CONFIG_BASE + 0x90c) + +#define PCIE_CORE_AXI_CONF_BASE 0xc00000 +#define PCIE_CORE_OB_REGION_ADDR0 (PCIE_CORE_AXI_CONF_BASE + 0x0) +#define PCIE_CORE_OB_REGION_ADDR0_NUM_BITS 0x3f +#define PCIE_CORE_OB_REGION_ADDR0_LO_ADDR 0xffffff00 +#define PCIE_CORE_OB_REGION_ADDR1 (PCIE_CORE_AXI_CONF_BASE + 0x4) +#define PCIE_CORE_OB_REGION_DESC0 (PCIE_CORE_AXI_CONF_BASE + 0x8) +#define PCIE_CORE_OB_REGION_DESC1 (PCIE_CORE_AXI_CONF_BASE + 0xc) + +#define PCIE_CORE_AXI_INBOUND_BASE 0xc00800 +#define PCIE_RP_IB_ADDR0 (PCIE_CORE_AXI_INBOUND_BASE + 0x0) +#define PCIE_CORE_IB_REGION_ADDR0_NUM_BITS 0x3f +#define PCIE_CORE_IB_REGION_ADDR0_LO_ADDR 0xffffff00 +#define PCIE_RP_IB_ADDR1 (PCIE_CORE_AXI_INBOUND_BASE + 0x4) + +/* Size of one AXI Region (not Region 0) */ +#define AXI_REGION_SIZE BIT(20) +/* Size of Region 0, equal to sum of sizes of other regions */ +#define AXI_REGION_0_SIZE (32 * (0x1 << 20)) +#define OB_REG_SIZE_SHIFT 5 +#define IB_ROOT_PORT_REG_SIZE_SHIFT 3 +#define AXI_WRAPPER_IO_WRITE 0x6 +#define AXI_WRAPPER_MEM_WRITE 0x2 + +#define MAX_AXI_IB_ROOTPORT_REGION_NUM 3 +#define MIN_AXI_ADDR_BITS_PASSED 8 +#define ROCKCHIP_VENDOR_ID 0x1d87 +#define PCIE_ECAM_BUS(x) (((x) & 0xff) << 20) +#define PCIE_ECAM_DEV(x) (((x) & 0x1f) << 15) +#define PCIE_ECAM_FUNC(x) (((x) & 0x7) << 12) +#define PCIE_ECAM_REG(x) (((x) & 0xfff) << 0) +#define PCIE_ECAM_ADDR(bus, dev, func, reg) \ + (PCIE_ECAM_BUS(bus) | PCIE_ECAM_DEV(dev) | \ + PCIE_ECAM_FUNC(func) | PCIE_ECAM_REG(reg)) + +#define RC_REGION_0_ADDR_TRANS_H 0x00000000 +#define RC_REGION_0_ADDR_TRANS_L 0x00000000 +#define RC_REGION_0_PASS_BITS (25 - 1) +#define MAX_AXI_WRAPPER_REGION_NUM 33 + +struct rockchip_pcie { + void __iomem *reg_base; /* DT axi-base */ + void __iomem *apb_base; /* DT apb-base */ + struct phy *phy; + struct reset_control *core_rst; + struct reset_control *mgmt_rst; + struct reset_control *mgmt_sticky_rst; + struct reset_control *pipe_rst; + struct clk *aclk_pcie; + struct clk *aclk_perf_pcie; + struct clk *hclk_pcie; + struct clk *clk_pcie_pm; + struct regulator *vpcie3v3; /* 3.3V power supply */ + struct regulator *vpcie1v8; /* 1.8V power supply */ + struct regulator *vpcie0v9; /* 0.9V power supply */ + struct gpio_desc *ep_gpio; + u32 lanes; + u8 root_bus_nr; + struct device *dev; + struct irq_domain *irq_domain; +}; + +static u32 rockchip_pcie_read(struct rockchip_pcie *rockchip, u32 reg) +{ + return readl(rockchip->apb_base + reg); +} + +static void rockchip_pcie_write(struct rockchip_pcie *rockchip, u32 val, + u32 reg) +{ + writel(val, rockchip->apb_base + reg); +} + +static void rockchip_pcie_enable_bw_int(struct rockchip_pcie *rockchip) +{ + u32 status; + + status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LCS); + status |= (PCIE_RC_CONFIG_LCS_LBMIE | PCIE_RC_CONFIG_LCS_LABIE); + rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS); +} + +static void rockchip_pcie_clr_bw_int(struct rockchip_pcie *rockchip) +{ + u32 status; + + status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LCS); + status |= (PCIE_RC_CONFIG_LCS_LBMS | PCIE_RC_CONFIG_LCS_LAMS); + rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS); +} + +static int rockchip_pcie_valid_device(struct rockchip_pcie *rockchip, + struct pci_bus *bus, int dev) +{ + /* access only one slot on each root port */ + if (bus->number == rockchip->root_bus_nr && dev > 0) + return 0; + + /* + * do not read more than one device on the bus directly attached + * to RC's downstream side. + */ + if (bus->primary == rockchip->root_bus_nr && dev > 0) + return 0; + + return 1; +} + +static int rockchip_pcie_rd_own_conf(struct rockchip_pcie *rockchip, + int where, int size, u32 *val) +{ + void __iomem *addr = rockchip->apb_base + PCIE_RC_CONFIG_BASE + where; + + if (!IS_ALIGNED((uintptr_t)addr, size)) { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + if (size == 4) { + *val = readl(addr); + } else if (size == 2) { + *val = readw(addr); + } else if (size == 1) { + *val = readb(addr); + } else { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + return PCIBIOS_SUCCESSFUL; +} + +static int rockchip_pcie_wr_own_conf(struct rockchip_pcie *rockchip, + int where, int size, u32 val) +{ + u32 mask, tmp, offset; + + offset = where & ~0x3; + + if (size == 4) { + writel(val, rockchip->apb_base + PCIE_RC_CONFIG_BASE + offset); + return PCIBIOS_SUCCESSFUL; + } + + mask = ~(((1 << (size * 8)) - 1) << ((where & 0x3) * 8)); + + /* + * N.B. This read/modify/write isn't safe in general because it can + * corrupt RW1C bits in adjacent registers. But the hardware + * doesn't support smaller writes. + */ + tmp = readl(rockchip->apb_base + PCIE_RC_CONFIG_BASE + offset) & mask; + tmp |= val << ((where & 0x3) * 8); + writel(tmp, rockchip->apb_base + PCIE_RC_CONFIG_BASE + offset); + + return PCIBIOS_SUCCESSFUL; +} + +static int rockchip_pcie_rd_other_conf(struct rockchip_pcie *rockchip, + struct pci_bus *bus, u32 devfn, + int where, int size, u32 *val) +{ + u32 busdev; + + busdev = PCIE_ECAM_ADDR(bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), where); + + if (!IS_ALIGNED(busdev, size)) { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + + if (size == 4) { + *val = readl(rockchip->reg_base + busdev); + } else if (size == 2) { + *val = readw(rockchip->reg_base + busdev); + } else if (size == 1) { + *val = readb(rockchip->reg_base + busdev); + } else { + *val = 0; + return PCIBIOS_BAD_REGISTER_NUMBER; + } + return PCIBIOS_SUCCESSFUL; +} + +static int rockchip_pcie_wr_other_conf(struct rockchip_pcie *rockchip, + struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + u32 busdev; + + busdev = PCIE_ECAM_ADDR(bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), where); + if (!IS_ALIGNED(busdev, size)) + return PCIBIOS_BAD_REGISTER_NUMBER; + + if (size == 4) + writel(val, rockchip->reg_base + busdev); + else if (size == 2) + writew(val, rockchip->reg_base + busdev); + else if (size == 1) + writeb(val, rockchip->reg_base + busdev); + else + return PCIBIOS_BAD_REGISTER_NUMBER; + + return PCIBIOS_SUCCESSFUL; +} + +static int rockchip_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, + int size, u32 *val) +{ + struct rockchip_pcie *rockchip = bus->sysdata; + + if (!rockchip_pcie_valid_device(rockchip, bus, PCI_SLOT(devfn))) { + *val = 0xffffffff; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + if (bus->number == rockchip->root_bus_nr) + return rockchip_pcie_rd_own_conf(rockchip, where, size, val); + + return rockchip_pcie_rd_other_conf(rockchip, bus, devfn, where, size, val); +} + +static int rockchip_pcie_wr_conf(struct pci_bus *bus, u32 devfn, + int where, int size, u32 val) +{ + struct rockchip_pcie *rockchip = bus->sysdata; + + if (!rockchip_pcie_valid_device(rockchip, bus, PCI_SLOT(devfn))) + return PCIBIOS_DEVICE_NOT_FOUND; + + if (bus->number == rockchip->root_bus_nr) + return rockchip_pcie_wr_own_conf(rockchip, where, size, val); + + return rockchip_pcie_wr_other_conf(rockchip, bus, devfn, where, size, val); +} + +static struct pci_ops rockchip_pcie_ops = { + .read = rockchip_pcie_rd_conf, + .write = rockchip_pcie_wr_conf, +}; + +/** + * rockchip_pcie_init_port - Initialize hardware + * @rockchip: PCIe port information + */ +static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) +{ + struct device *dev = rockchip->dev; + int err; + u32 status; + unsigned long timeout; + + gpiod_set_value(rockchip->ep_gpio, 0); + + err = phy_init(rockchip->phy); + if (err < 0) { + dev_err(dev, "fail to init phy, err %d\n", err); + return err; + } + + err = reset_control_assert(rockchip->core_rst); + if (err) { + dev_err(dev, "assert core_rst err %d\n", err); + return err; + } + + err = reset_control_assert(rockchip->mgmt_rst); + if (err) { + dev_err(dev, "assert mgmt_rst err %d\n", err); + return err; + } + + err = reset_control_assert(rockchip->mgmt_sticky_rst); + if (err) { + dev_err(dev, "assert mgmt_sticky_rst err %d\n", err); + return err; + } + + err = reset_control_assert(rockchip->pipe_rst); + if (err) { + dev_err(dev, "assert pipe_rst err %d\n", err); + return err; + } + + rockchip_pcie_write(rockchip, + PCIE_CLIENT_CONF_ENABLE | + PCIE_CLIENT_LINK_TRAIN_ENABLE | + PCIE_CLIENT_ARI_ENABLE | + PCIE_CLIENT_CONF_LANE_NUM(rockchip->lanes) | + PCIE_CLIENT_MODE_RC | + PCIE_CLIENT_GEN_SEL_2, + PCIE_CLIENT_CONFIG); + + err = phy_power_on(rockchip->phy); + if (err) { + dev_err(dev, "fail to power on phy, err %d\n", err); + return err; + } + + err = reset_control_deassert(rockchip->core_rst); + if (err) { + dev_err(dev, "deassert core_rst err %d\n", err); + return err; + } + + err = reset_control_deassert(rockchip->mgmt_rst); + if (err) { + dev_err(dev, "deassert mgmt_rst err %d\n", err); + return err; + } + + err = reset_control_deassert(rockchip->mgmt_sticky_rst); + if (err) { + dev_err(dev, "deassert mgmt_sticky_rst err %d\n", err); + return err; + } + + err = reset_control_deassert(rockchip->pipe_rst); + if (err) { + dev_err(dev, "deassert pipe_rst err %d\n", err); + return err; + } + + /* + * We need to read/write PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2 before + * enabling ASPM. Otherwise L1PwrOnSc and L1PwrOnVal isn't + * reliable and enabling ASPM doesn't work. This is a controller + * bug we need to work around. + */ + status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2); + rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2); + + /* Enable Gen1 training */ + rockchip_pcie_write(rockchip, PCIE_CLIENT_LINK_TRAIN_ENABLE, + PCIE_CLIENT_CONFIG); + + gpiod_set_value(rockchip->ep_gpio, 1); + + /* 500ms timeout value should be enough for Gen1/2 training */ + timeout = jiffies + msecs_to_jiffies(500); + + for (;;) { + status = rockchip_pcie_read(rockchip, + PCIE_CLIENT_BASIC_STATUS1); + if ((status & PCIE_CLIENT_LINK_STATUS_MASK) == + PCIE_CLIENT_LINK_STATUS_UP) { + dev_dbg(dev, "PCIe link training gen1 pass!\n"); + break; + } + + if (time_after(jiffies, timeout)) { + dev_err(dev, "PCIe link training gen1 timeout!\n"); + return -ETIMEDOUT; + } + + msleep(20); + } + + /* + * Enable retrain for gen2. This should be configured only after + * gen1 finished. + */ + status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_LCS); + status |= PCIE_RC_CONFIG_LCS_RETRAIN_LINK; + rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS); + + timeout = jiffies + msecs_to_jiffies(500); + for (;;) { + status = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL); + if ((status & PCIE_CORE_PL_CONF_SPEED_MASK) == + PCIE_CORE_PL_CONF_SPEED_5G) { + dev_dbg(dev, "PCIe link training gen2 pass!\n"); + break; + } + + if (time_after(jiffies, timeout)) { + dev_dbg(dev, "PCIe link training gen2 timeout, fall back to gen1!\n"); + break; + } + + msleep(20); + } + + /* Check the final link width from negotiated lane counter from MGMT */ + status = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL); + status = 0x1 << ((status & PCIE_CORE_PL_CONF_LANE_MASK) >> + PCIE_CORE_PL_CONF_LANE_MASK); + dev_dbg(dev, "current link width is x%d\n", status); + + rockchip_pcie_write(rockchip, ROCKCHIP_VENDOR_ID, + PCIE_RC_CONFIG_VENDOR); + rockchip_pcie_write(rockchip, + PCI_CLASS_BRIDGE_PCI << PCIE_RC_CONFIG_SCC_SHIFT, + PCIE_RC_CONFIG_RID_CCR); + rockchip_pcie_write(rockchip, 0x0, PCIE_RC_BAR_CONF); + + rockchip_pcie_write(rockchip, + (RC_REGION_0_ADDR_TRANS_L + RC_REGION_0_PASS_BITS), + PCIE_CORE_OB_REGION_ADDR0); + rockchip_pcie_write(rockchip, RC_REGION_0_ADDR_TRANS_H, + PCIE_CORE_OB_REGION_ADDR1); + rockchip_pcie_write(rockchip, 0x0080000a, PCIE_CORE_OB_REGION_DESC0); + rockchip_pcie_write(rockchip, 0x0, PCIE_CORE_OB_REGION_DESC1); + + return 0; +} + +static irqreturn_t rockchip_pcie_subsys_irq_handler(int irq, void *arg) +{ + struct rockchip_pcie *rockchip = arg; + struct device *dev = rockchip->dev; + u32 reg; + u32 sub_reg; + + reg = rockchip_pcie_read(rockchip, PCIE_CLIENT_INT_STATUS); + if (reg & PCIE_CLIENT_INT_LOCAL) { + dev_dbg(dev, "local interrupt received\n"); + sub_reg = rockchip_pcie_read(rockchip, PCIE_CORE_INT_STATUS); + if (sub_reg & PCIE_CORE_INT_PRFPE) + dev_dbg(dev, "parity error detected while reading from the PNP receive FIFO RAM\n"); + + if (sub_reg & PCIE_CORE_INT_CRFPE) + dev_dbg(dev, "parity error detected while reading from the Completion Receive FIFO RAM\n"); + + if (sub_reg & PCIE_CORE_INT_RRPE) + dev_dbg(dev, "parity error detected while reading from replay buffer RAM\n"); + + if (sub_reg & PCIE_CORE_INT_PRFO) + dev_dbg(dev, "overflow occurred in the PNP receive FIFO\n"); + + if (sub_reg & PCIE_CORE_INT_CRFO) + dev_dbg(dev, "overflow occurred in the completion receive FIFO\n"); + + if (sub_reg & PCIE_CORE_INT_RT) + dev_dbg(dev, "replay timer timed out\n"); + + if (sub_reg & PCIE_CORE_INT_RTR) + dev_dbg(dev, "replay timer rolled over after 4 transmissions of the same TLP\n"); + + if (sub_reg & PCIE_CORE_INT_PE) + dev_dbg(dev, "phy error detected on receive side\n"); + + if (sub_reg & PCIE_CORE_INT_MTR) + dev_dbg(dev, "malformed TLP received from the link\n"); + + if (sub_reg & PCIE_CORE_INT_UCR) + dev_dbg(dev, "malformed TLP received from the link\n"); + + if (sub_reg & PCIE_CORE_INT_FCE) + dev_dbg(dev, "an error was observed in the flow control advertisements from the other side\n"); + + if (sub_reg & PCIE_CORE_INT_CT) + dev_dbg(dev, "a request timed out waiting for completion\n"); + + if (sub_reg & PCIE_CORE_INT_UTC) + dev_dbg(dev, "unmapped TC error\n"); + + if (sub_reg & PCIE_CORE_INT_MMVC) + dev_dbg(dev, "MSI mask register changes\n"); + + rockchip_pcie_write(rockchip, sub_reg, PCIE_CORE_INT_STATUS); + } else if (reg & PCIE_CLIENT_INT_PHY) { + dev_dbg(dev, "phy link changes\n"); + rockchip_pcie_clr_bw_int(rockchip); + } + + rockchip_pcie_write(rockchip, reg & PCIE_CLIENT_INT_LOCAL, + PCIE_CLIENT_INT_STATUS); + + return IRQ_HANDLED; +} + +static irqreturn_t rockchip_pcie_client_irq_handler(int irq, void *arg) +{ + struct rockchip_pcie *rockchip = arg; + struct device *dev = rockchip->dev; + u32 reg; + + reg = rockchip_pcie_read(rockchip, PCIE_CLIENT_INT_STATUS); + if (reg & PCIE_CLIENT_INT_LEGACY_DONE) + dev_dbg(dev, "legacy done interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_MSG) + dev_dbg(dev, "message done interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_HOT_RST) + dev_dbg(dev, "hot reset interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_DPA) + dev_dbg(dev, "dpa interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_FATAL_ERR) + dev_dbg(dev, "fatal error interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_NFATAL_ERR) + dev_dbg(dev, "no fatal error interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_CORR_ERR) + dev_dbg(dev, "correctable error interrupt received\n"); + + if (reg & PCIE_CLIENT_INT_PHY) + dev_dbg(dev, "phy interrupt received\n"); + + rockchip_pcie_write(rockchip, reg & (PCIE_CLIENT_INT_LEGACY_DONE | + PCIE_CLIENT_INT_MSG | PCIE_CLIENT_INT_HOT_RST | + PCIE_CLIENT_INT_DPA | PCIE_CLIENT_INT_FATAL_ERR | + PCIE_CLIENT_INT_NFATAL_ERR | + PCIE_CLIENT_INT_CORR_ERR | + PCIE_CLIENT_INT_PHY), + PCIE_CLIENT_INT_STATUS); + + return IRQ_HANDLED; +} + +static void rockchip_pcie_legacy_int_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct rockchip_pcie *rockchip = irq_desc_get_handler_data(desc); + struct device *dev = rockchip->dev; + u32 reg; + u32 hwirq; + u32 virq; + + chained_irq_enter(chip, desc); + + reg = rockchip_pcie_read(rockchip, PCIE_CLIENT_INT_STATUS); + reg = (reg & PCIE_CLIENT_INTR_MASK) >> PCIE_CLIENT_INTR_SHIFT; + + while (reg) { + hwirq = ffs(reg) - 1; + reg &= ~BIT(hwirq); + + virq = irq_find_mapping(rockchip->irq_domain, hwirq); + if (virq) + generic_handle_irq(virq); + else + dev_err(dev, "unexpected IRQ, INT%d\n", hwirq); + } + + chained_irq_exit(chip, desc); +} + + +/** + * rockchip_pcie_parse_dt - Parse Device Tree + * @rockchip: PCIe port information + * + * Return: '0' on success and error value on failure + */ +static int rockchip_pcie_parse_dt(struct rockchip_pcie *rockchip) +{ + struct device *dev = rockchip->dev; + struct platform_device *pdev = to_platform_device(dev); + struct device_node *node = dev->of_node; + struct resource *regs; + int irq; + int err; + + regs = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "axi-base"); + rockchip->reg_base = devm_ioremap_resource(dev, regs); + if (IS_ERR(rockchip->reg_base)) + return PTR_ERR(rockchip->reg_base); + + regs = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "apb-base"); + rockchip->apb_base = devm_ioremap_resource(dev, regs); + if (IS_ERR(rockchip->apb_base)) + return PTR_ERR(rockchip->apb_base); + + rockchip->phy = devm_phy_get(dev, "pcie-phy"); + if (IS_ERR(rockchip->phy)) { + if (PTR_ERR(rockchip->phy) != -EPROBE_DEFER) + dev_err(dev, "missing phy\n"); + return PTR_ERR(rockchip->phy); + } + + rockchip->lanes = 1; + err = of_property_read_u32(node, "num-lanes", &rockchip->lanes); + if (!err && (rockchip->lanes == 0 || + rockchip->lanes == 3 || + rockchip->lanes > 4)) { + dev_warn(dev, "invalid num-lanes, default to use one lane\n"); + rockchip->lanes = 1; + } + + rockchip->core_rst = devm_reset_control_get(dev, "core"); + if (IS_ERR(rockchip->core_rst)) { + if (PTR_ERR(rockchip->core_rst) != -EPROBE_DEFER) + dev_err(dev, "missing core reset property in node\n"); + return PTR_ERR(rockchip->core_rst); + } + + rockchip->mgmt_rst = devm_reset_control_get(dev, "mgmt"); + if (IS_ERR(rockchip->mgmt_rst)) { + if (PTR_ERR(rockchip->mgmt_rst) != -EPROBE_DEFER) + dev_err(dev, "missing mgmt reset property in node\n"); + return PTR_ERR(rockchip->mgmt_rst); + } + + rockchip->mgmt_sticky_rst = devm_reset_control_get(dev, "mgmt-sticky"); + if (IS_ERR(rockchip->mgmt_sticky_rst)) { + if (PTR_ERR(rockchip->mgmt_sticky_rst) != -EPROBE_DEFER) + dev_err(dev, "missing mgmt-sticky reset property in node\n"); + return PTR_ERR(rockchip->mgmt_sticky_rst); + } + + rockchip->pipe_rst = devm_reset_control_get(dev, "pipe"); + if (IS_ERR(rockchip->pipe_rst)) { + if (PTR_ERR(rockchip->pipe_rst) != -EPROBE_DEFER) + dev_err(dev, "missing pipe reset property in node\n"); + return PTR_ERR(rockchip->pipe_rst); + } + + rockchip->ep_gpio = devm_gpiod_get(dev, "ep", GPIOD_OUT_HIGH); + if (IS_ERR(rockchip->ep_gpio)) { + dev_err(dev, "missing ep-gpios property in node\n"); + return PTR_ERR(rockchip->ep_gpio); + } + + rockchip->aclk_pcie = devm_clk_get(dev, "aclk"); + if (IS_ERR(rockchip->aclk_pcie)) { + dev_err(dev, "aclk clock not found\n"); + return PTR_ERR(rockchip->aclk_pcie); + } + + rockchip->aclk_perf_pcie = devm_clk_get(dev, "aclk-perf"); + if (IS_ERR(rockchip->aclk_perf_pcie)) { + dev_err(dev, "aclk_perf clock not found\n"); + return PTR_ERR(rockchip->aclk_perf_pcie); + } + + rockchip->hclk_pcie = devm_clk_get(dev, "hclk"); + if (IS_ERR(rockchip->hclk_pcie)) { + dev_err(dev, "hclk clock not found\n"); + return PTR_ERR(rockchip->hclk_pcie); + } + + rockchip->clk_pcie_pm = devm_clk_get(dev, "pm"); + if (IS_ERR(rockchip->clk_pcie_pm)) { + dev_err(dev, "pm clock not found\n"); + return PTR_ERR(rockchip->clk_pcie_pm); + } + + irq = platform_get_irq_byname(pdev, "sys"); + if (irq < 0) { + dev_err(dev, "missing sys IRQ resource\n"); + return -EINVAL; + } + + err = devm_request_irq(dev, irq, rockchip_pcie_subsys_irq_handler, + IRQF_SHARED, "pcie-sys", rockchip); + if (err) { + dev_err(dev, "failed to request PCIe subsystem IRQ\n"); + return err; + } + + irq = platform_get_irq_byname(pdev, "legacy"); + if (irq < 0) { + dev_err(dev, "missing legacy IRQ resource\n"); + return -EINVAL; + } + + irq_set_chained_handler_and_data(irq, + rockchip_pcie_legacy_int_handler, + rockchip); + + irq = platform_get_irq_byname(pdev, "client"); + if (irq < 0) { + dev_err(dev, "missing client IRQ resource\n"); + return -EINVAL; + } + + err = devm_request_irq(dev, irq, rockchip_pcie_client_irq_handler, + IRQF_SHARED, "pcie-client", rockchip); + if (err) { + dev_err(dev, "failed to request PCIe client IRQ\n"); + return err; + } + + rockchip->vpcie3v3 = devm_regulator_get_optional(dev, "vpcie3v3"); + if (IS_ERR(rockchip->vpcie3v3)) { + if (PTR_ERR(rockchip->vpcie3v3) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(dev, "no vpcie3v3 regulator found\n"); + } + + rockchip->vpcie1v8 = devm_regulator_get_optional(dev, "vpcie1v8"); + if (IS_ERR(rockchip->vpcie1v8)) { + if (PTR_ERR(rockchip->vpcie1v8) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(dev, "no vpcie1v8 regulator found\n"); + } + + rockchip->vpcie0v9 = devm_regulator_get_optional(dev, "vpcie0v9"); + if (IS_ERR(rockchip->vpcie0v9)) { + if (PTR_ERR(rockchip->vpcie0v9) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_info(dev, "no vpcie0v9 regulator found\n"); + } + + return 0; +} + +static int rockchip_pcie_set_vpcie(struct rockchip_pcie *rockchip) +{ + struct device *dev = rockchip->dev; + int err; + + if (!IS_ERR(rockchip->vpcie3v3)) { + err = regulator_enable(rockchip->vpcie3v3); + if (err) { + dev_err(dev, "fail to enable vpcie3v3 regulator\n"); + goto err_out; + } + } + + if (!IS_ERR(rockchip->vpcie1v8)) { + err = regulator_enable(rockchip->vpcie1v8); + if (err) { + dev_err(dev, "fail to enable vpcie1v8 regulator\n"); + goto err_disable_3v3; + } + } + + if (!IS_ERR(rockchip->vpcie0v9)) { + err = regulator_enable(rockchip->vpcie0v9); + if (err) { + dev_err(dev, "fail to enable vpcie0v9 regulator\n"); + goto err_disable_1v8; + } + } + + return 0; + +err_disable_1v8: + if (!IS_ERR(rockchip->vpcie1v8)) + regulator_disable(rockchip->vpcie1v8); +err_disable_3v3: + if (!IS_ERR(rockchip->vpcie3v3)) + regulator_disable(rockchip->vpcie3v3); +err_out: + return err; +} + +static void rockchip_pcie_enable_interrupts(struct rockchip_pcie *rockchip) +{ + rockchip_pcie_write(rockchip, (PCIE_CLIENT_INT_CLI << 16) & + (~PCIE_CLIENT_INT_CLI), PCIE_CLIENT_INT_MASK); + rockchip_pcie_write(rockchip, (u32)(~PCIE_CORE_INT), + PCIE_CORE_INT_MASK); + + rockchip_pcie_enable_bw_int(rockchip); +} + +static int rockchip_pcie_intx_map(struct irq_domain *domain, unsigned int irq, + irq_hw_number_t hwirq) +{ + irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq); + irq_set_chip_data(irq, domain->host_data); + + return 0; +} + +static const struct irq_domain_ops intx_domain_ops = { + .map = rockchip_pcie_intx_map, +}; + +static int rockchip_pcie_init_irq_domain(struct rockchip_pcie *rockchip) +{ + struct device *dev = rockchip->dev; + struct device_node *intc = of_get_next_child(dev->of_node, NULL); + + if (!intc) { + dev_err(dev, "missing child interrupt-controller node\n"); + return -EINVAL; + } + + rockchip->irq_domain = irq_domain_add_linear(intc, 4, + &intx_domain_ops, rockchip); + if (!rockchip->irq_domain) { + dev_err(dev, "failed to get a INTx IRQ domain\n"); + return -EINVAL; + } + + return 0; +} + +static int rockchip_pcie_prog_ob_atu(struct rockchip_pcie *rockchip, + int region_no, int type, u8 num_pass_bits, + u32 lower_addr, u32 upper_addr) +{ + u32 ob_addr_0; + u32 ob_addr_1; + u32 ob_desc_0; + u32 aw_offset; + + if (region_no >= MAX_AXI_WRAPPER_REGION_NUM) + return -EINVAL; + if (num_pass_bits + 1 < 8) + return -EINVAL; + if (num_pass_bits > 63) + return -EINVAL; + if (region_no == 0) { + if (AXI_REGION_0_SIZE < (2ULL << num_pass_bits)) + return -EINVAL; + } + if (region_no != 0) { + if (AXI_REGION_SIZE < (2ULL << num_pass_bits)) + return -EINVAL; + } + + aw_offset = (region_no << OB_REG_SIZE_SHIFT); + + ob_addr_0 = num_pass_bits & PCIE_CORE_OB_REGION_ADDR0_NUM_BITS; + ob_addr_0 |= lower_addr & PCIE_CORE_OB_REGION_ADDR0_LO_ADDR; + ob_addr_1 = upper_addr; + ob_desc_0 = (1 << 23 | type); + + rockchip_pcie_write(rockchip, ob_addr_0, + PCIE_CORE_OB_REGION_ADDR0 + aw_offset); + rockchip_pcie_write(rockchip, ob_addr_1, + PCIE_CORE_OB_REGION_ADDR1 + aw_offset); + rockchip_pcie_write(rockchip, ob_desc_0, + PCIE_CORE_OB_REGION_DESC0 + aw_offset); + rockchip_pcie_write(rockchip, 0, + PCIE_CORE_OB_REGION_DESC1 + aw_offset); + + return 0; +} + +static int rockchip_pcie_prog_ib_atu(struct rockchip_pcie *rockchip, + int region_no, u8 num_pass_bits, + u32 lower_addr, u32 upper_addr) +{ + u32 ib_addr_0; + u32 ib_addr_1; + u32 aw_offset; + + if (region_no > MAX_AXI_IB_ROOTPORT_REGION_NUM) + return -EINVAL; + if (num_pass_bits + 1 < MIN_AXI_ADDR_BITS_PASSED) + return -EINVAL; + if (num_pass_bits > 63) + return -EINVAL; + + aw_offset = (region_no << IB_ROOT_PORT_REG_SIZE_SHIFT); + + ib_addr_0 = num_pass_bits & PCIE_CORE_IB_REGION_ADDR0_NUM_BITS; + ib_addr_0 |= (lower_addr << 8) & PCIE_CORE_IB_REGION_ADDR0_LO_ADDR; + ib_addr_1 = upper_addr; + + rockchip_pcie_write(rockchip, ib_addr_0, PCIE_RP_IB_ADDR0 + aw_offset); + rockchip_pcie_write(rockchip, ib_addr_1, PCIE_RP_IB_ADDR1 + aw_offset); + + return 0; +} + +static int rockchip_pcie_probe(struct platform_device *pdev) +{ + struct rockchip_pcie *rockchip; + struct device *dev = &pdev->dev; + struct pci_bus *bus, *child; + struct resource_entry *win; + resource_size_t io_base; + struct resource *mem; + struct resource *io; + phys_addr_t io_bus_addr = 0; + u32 io_size; + phys_addr_t mem_bus_addr = 0; + u32 mem_size = 0; + int reg_no; + int err; + int offset; + + LIST_HEAD(res); + + if (!dev->of_node) + return -ENODEV; + + rockchip = devm_kzalloc(dev, sizeof(*rockchip), GFP_KERNEL); + if (!rockchip) + return -ENOMEM; + + rockchip->dev = dev; + + err = rockchip_pcie_parse_dt(rockchip); + if (err) + return err; + + err = clk_prepare_enable(rockchip->aclk_pcie); + if (err) { + dev_err(dev, "unable to enable aclk_pcie clock\n"); + goto err_aclk_pcie; + } + + err = clk_prepare_enable(rockchip->aclk_perf_pcie); + if (err) { + dev_err(dev, "unable to enable aclk_perf_pcie clock\n"); + goto err_aclk_perf_pcie; + } + + err = clk_prepare_enable(rockchip->hclk_pcie); + if (err) { + dev_err(dev, "unable to enable hclk_pcie clock\n"); + goto err_hclk_pcie; + } + + err = clk_prepare_enable(rockchip->clk_pcie_pm); + if (err) { + dev_err(dev, "unable to enable hclk_pcie clock\n"); + goto err_pcie_pm; + } + + err = rockchip_pcie_set_vpcie(rockchip); + if (err) { + dev_err(dev, "failed to set vpcie regulator\n"); + goto err_set_vpcie; + } + + err = rockchip_pcie_init_port(rockchip); + if (err) + goto err_vpcie; + + platform_set_drvdata(pdev, rockchip); + + rockchip_pcie_enable_interrupts(rockchip); + + err = rockchip_pcie_init_irq_domain(rockchip); + if (err < 0) + goto err_vpcie; + + err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, + &res, &io_base); + if (err) + goto err_vpcie; + + err = devm_request_pci_bus_resources(dev, &res); + if (err) + goto err_vpcie; + + /* Get the I/O and memory ranges from DT */ + io_size = 0; + resource_list_for_each_entry(win, &res) { + switch (resource_type(win->res)) { + case IORESOURCE_IO: + io = win->res; + io->name = "I/O"; + io_size = resource_size(io); + io_bus_addr = io->start - win->offset; + err = pci_remap_iospace(io, io_base); + if (err) { + dev_warn(dev, "error %d: failed to map resource %pR\n", + err, io); + continue; + } + break; + case IORESOURCE_MEM: + mem = win->res; + mem->name = "MEM"; + mem_size = resource_size(mem); + mem_bus_addr = mem->start - win->offset; + break; + case IORESOURCE_BUS: + rockchip->root_bus_nr = win->res->start; + break; + default: + continue; + } + } + + if (mem_size) { + for (reg_no = 0; reg_no < (mem_size >> 20); reg_no++) { + err = rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1, + AXI_WRAPPER_MEM_WRITE, + 20 - 1, + mem_bus_addr + + (reg_no << 20), + 0); + if (err) { + dev_err(dev, "program RC mem outbound ATU failed\n"); + goto err_vpcie; + } + } + } + + err = rockchip_pcie_prog_ib_atu(rockchip, 2, 32 - 1, 0x0, 0); + if (err) { + dev_err(dev, "program RC mem inbound ATU failed\n"); + goto err_vpcie; + } + + offset = mem_size >> 20; + + if (io_size) { + for (reg_no = 0; reg_no < (io_size >> 20); reg_no++) { + err = rockchip_pcie_prog_ob_atu(rockchip, + reg_no + 1 + offset, + AXI_WRAPPER_IO_WRITE, + 20 - 1, + io_bus_addr + + (reg_no << 20), + 0); + if (err) { + dev_err(dev, "program RC io outbound ATU failed\n"); + goto err_vpcie; + } + } + } + + bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res); + if (!bus) { + err = -ENOMEM; + goto err_vpcie; + } + + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + + pci_bus_add_devices(bus); + + dev_warn(dev, "only 32-bit config accesses supported; smaller writes may corrupt adjacent RW1C fields\n"); + + return err; + +err_vpcie: + if (!IS_ERR(rockchip->vpcie3v3)) + regulator_disable(rockchip->vpcie3v3); + if (!IS_ERR(rockchip->vpcie1v8)) + regulator_disable(rockchip->vpcie1v8); + if (!IS_ERR(rockchip->vpcie0v9)) + regulator_disable(rockchip->vpcie0v9); +err_set_vpcie: + clk_disable_unprepare(rockchip->clk_pcie_pm); +err_pcie_pm: + clk_disable_unprepare(rockchip->hclk_pcie); +err_hclk_pcie: + clk_disable_unprepare(rockchip->aclk_perf_pcie); +err_aclk_perf_pcie: + clk_disable_unprepare(rockchip->aclk_pcie); +err_aclk_pcie: + return err; +} + +static const struct of_device_id rockchip_pcie_of_match[] = { + { .compatible = "rockchip,rk3399-pcie", }, + {} +}; + +static struct platform_driver rockchip_pcie_driver = { + .driver = { + .name = "rockchip-pcie", + .of_match_table = rockchip_pcie_of_match, + }, + .probe = rockchip_pcie_probe, + +}; +builtin_platform_driver(rockchip_pcie_driver); -- cgit v0.10.2 From f8430eae9f1b439154dbe8bd77d697c4cece145b Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Tue, 9 Aug 2016 16:49:37 +0100 Subject: PCI/MSI: Enable PCI_MSI_IRQ_DOMAIN support for ARC Add ARC as an arch that supports PCI_MSI_IRQ_DOMAIN and add generation of msi.h in the ARC arch. Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas Acked-by: Vineet Gupta diff --git a/arch/arc/include/asm/Kbuild b/arch/arc/include/asm/Kbuild index 0b10ef2..c332604 100644 --- a/arch/arc/include/asm/Kbuild +++ b/arch/arc/include/asm/Kbuild @@ -25,6 +25,7 @@ generic-y += mcs_spinlock.h generic-y += mm-arch-hooks.h generic-y += mman.h generic-y += msgbuf.h +generic-y += msi.h generic-y += param.h generic-y += parport.h generic-y += pci.h diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 67f9916..6555eb7 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -25,7 +25,7 @@ config PCI_MSI If you don't know what to do here, say Y. config PCI_MSI_IRQ_DOMAIN - def_bool ARM || ARM64 || X86 + def_bool ARC || ARM || ARM64 || X86 depends on PCI_MSI select GENERIC_MSI_IRQ_DOMAIN -- cgit v0.10.2 From 0c6045d8c0eff0f7784f310407ccad44f622aa40 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 23 Aug 2016 04:45:51 +0000 Subject: PCI: hv: Use zero-length array in struct pci_packet Use zero-length array in struct pci_packet and rename struct pci_message's field "message_type" to "type". This makes the code more readable. No functionality change. Signed-off-by: Dexuan Cui Signed-off-by: Bjorn Helgaas Acked-by: KY Srinivasan CC: Jake Oshins CC: Haiyang Zhang CC: Vitaly Kuznetsov diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index a8deeca..6fa40f1 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -200,11 +200,11 @@ struct tran_int_desc { */ struct pci_message { - u32 message_type; + u32 type; } __packed; struct pci_child_message { - u32 message_type; + struct pci_message message_type; union win_slot_encoding wslot; } __packed; @@ -222,7 +222,8 @@ struct pci_packet { void (*completion_func)(void *context, struct pci_response *resp, int resp_packet_size); void *compl_ctxt; - struct pci_message message; + + struct pci_message message[0]; }; /* @@ -314,7 +315,7 @@ struct pci_dev_incoming { } __packed; struct pci_eject_response { - u32 message_type; + struct pci_message message_type; union win_slot_encoding wslot; u32 status; } __packed; @@ -694,13 +695,12 @@ static void hv_int_desc_free(struct hv_pci_dev *hpdev, struct pci_delete_interrupt *int_pkt; struct { struct pci_packet pkt; - u8 buffer[sizeof(struct pci_delete_interrupt) - - sizeof(struct pci_message)]; + u8 buffer[sizeof(struct pci_delete_interrupt)]; } ctxt; memset(&ctxt, 0, sizeof(ctxt)); int_pkt = (struct pci_delete_interrupt *)&ctxt.pkt.message; - int_pkt->message_type.message_type = + int_pkt->message_type.type = PCI_DELETE_INTERRUPT_MESSAGE; int_pkt->wslot.slot = hpdev->desc.win_slot.slot; int_pkt->int_desc = *int_desc; @@ -847,8 +847,7 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) struct cpumask *affinity; struct { struct pci_packet pkt; - u8 buffer[sizeof(struct pci_create_interrupt) - - sizeof(struct pci_message)]; + u8 buffer[sizeof(struct pci_create_interrupt)]; } ctxt; int cpu; int ret; @@ -876,7 +875,7 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ctxt.pkt.completion_func = hv_pci_compose_compl; ctxt.pkt.compl_ctxt = ∁ int_pkt = (struct pci_create_interrupt *)&ctxt.pkt.message; - int_pkt->message_type.message_type = PCI_CREATE_INTERRUPT_MESSAGE; + int_pkt->message_type.type = PCI_CREATE_INTERRUPT_MESSAGE; int_pkt->wslot.slot = hpdev->desc.win_slot.slot; int_pkt->int_desc.vector = cfg->vector; int_pkt->int_desc.vector_count = 1; @@ -1289,7 +1288,7 @@ static struct hv_pci_dev *new_pcichild_device(struct hv_pcibus_device *hbus, pkt.init_packet.compl_ctxt = &comp_pkt; pkt.init_packet.completion_func = q_resource_requirements; res_req = (struct pci_child_message *)&pkt.init_packet.message; - res_req->message_type = PCI_QUERY_RESOURCE_REQUIREMENTS; + res_req->message_type.type = PCI_QUERY_RESOURCE_REQUIREMENTS; res_req->wslot.slot = desc->win_slot.slot; ret = vmbus_sendpacket(hbus->hdev->channel, res_req, @@ -1557,8 +1556,7 @@ static void hv_eject_device_work(struct work_struct *work) int wslot; struct { struct pci_packet pkt; - u8 buffer[sizeof(struct pci_eject_response) - - sizeof(struct pci_message)]; + u8 buffer[sizeof(struct pci_eject_response)]; } ctxt; hpdev = container_of(work, struct hv_pci_dev, wrk); @@ -1584,7 +1582,7 @@ static void hv_eject_device_work(struct work_struct *work) memset(&ctxt, 0, sizeof(ctxt)); ejct_pkt = (struct pci_eject_response *)&ctxt.pkt.message; - ejct_pkt->message_type = PCI_EJECTION_COMPLETE; + ejct_pkt->message_type.type = PCI_EJECTION_COMPLETE; ejct_pkt->wslot.slot = hpdev->desc.win_slot.slot; vmbus_sendpacket(hpdev->hbus->hdev->channel, ejct_pkt, sizeof(*ejct_pkt), (unsigned long)&ctxt.pkt, @@ -1687,7 +1685,7 @@ static void hv_pci_onchannelcallback(void *context) case VM_PKT_DATA_INBAND: new_message = (struct pci_incoming_message *)buffer; - switch (new_message->message_type.message_type) { + switch (new_message->message_type.type) { case PCI_BUS_RELATIONS: bus_rel = (struct pci_bus_relations *)buffer; @@ -1718,7 +1716,7 @@ static void hv_pci_onchannelcallback(void *context) default: dev_warn(&hbus->hdev->device, "Unimplemented protocol message %x\n", - new_message->message_type.message_type); + new_message->message_type.type); break; } break; @@ -1771,7 +1769,7 @@ static int hv_pci_protocol_negotiation(struct hv_device *hdev) pkt->completion_func = hv_pci_generic_compl; pkt->compl_ctxt = &comp_pkt; version_req = (struct pci_version_request *)&pkt->message; - version_req->message_type.message_type = PCI_QUERY_PROTOCOL_VERSION; + version_req->message_type.type = PCI_QUERY_PROTOCOL_VERSION; version_req->protocol_version = PCI_PROTOCOL_VERSION_CURRENT; ret = vmbus_sendpacket(hdev->channel, version_req, @@ -1972,7 +1970,7 @@ static int hv_pci_enter_d0(struct hv_device *hdev) pkt->completion_func = hv_pci_generic_compl; pkt->compl_ctxt = &comp_pkt; d0_entry = (struct pci_bus_d0_entry *)&pkt->message; - d0_entry->message_type.message_type = PCI_BUS_D0ENTRY; + d0_entry->message_type.type = PCI_BUS_D0ENTRY; d0_entry->mmio_base = hbus->mem_config->start; ret = vmbus_sendpacket(hdev->channel, d0_entry, sizeof(*d0_entry), @@ -2018,7 +2016,7 @@ static int hv_pci_query_relations(struct hv_device *hdev) return -ENOTEMPTY; memset(&message, 0, sizeof(message)); - message.message_type = PCI_QUERY_BUS_RELATIONS; + message.type = PCI_QUERY_BUS_RELATIONS; ret = vmbus_sendpacket(hdev->channel, &message, sizeof(message), 0, VM_PKT_DATA_INBAND, 0); @@ -2071,8 +2069,8 @@ static int hv_send_resources_allocated(struct hv_device *hdev) init_completion(&comp_pkt.host_event); pkt->completion_func = hv_pci_generic_compl; pkt->compl_ctxt = &comp_pkt; - pkt->message.message_type = PCI_RESOURCES_ASSIGNED; res_assigned = (struct pci_resources_assigned *)&pkt->message; + res_assigned->message_type.type = PCI_RESOURCES_ASSIGNED; res_assigned->wslot.slot = hpdev->desc.win_slot.slot; put_pcichild(hpdev, hv_pcidev_ref_by_slot); @@ -2122,7 +2120,7 @@ static int hv_send_resources_released(struct hv_device *hdev) continue; memset(&pkt, 0, sizeof(pkt)); - pkt.message_type = PCI_RESOURCES_RELEASED; + pkt.message_type.type = PCI_RESOURCES_RELEASED; pkt.wslot.slot = hpdev->desc.win_slot.slot; put_pcichild(hpdev, hv_pcidev_ref_by_slot); @@ -2289,7 +2287,7 @@ static int hv_pci_remove(struct hv_device *hdev) init_completion(&comp_pkt.host_event); pkt.teardown_packet.completion_func = hv_pci_generic_compl; pkt.teardown_packet.compl_ctxt = &comp_pkt; - pkt.teardown_packet.message.message_type = PCI_BUS_D0EXIT; + pkt.teardown_packet.message[0].type = PCI_BUS_D0EXIT; ret = vmbus_sendpacket(hdev->channel, &pkt.teardown_packet.message, sizeof(struct pci_message), -- cgit v0.10.2 From 7d0f8eec976ae5a364af84fd8e92f6da9ec05a41 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 23 Aug 2016 04:46:39 +0000 Subject: PCI: hv: Use pci_function_description[0] in struct definitions The 2 structs can use a zero-length array here, because dynamic memory of the correct size is allocated in hv_pci_devices_present() and we don't need this extra element. No functional change. Signed-off-by: Dexuan Cui Signed-off-by: Bjorn Helgaas Acked-by: KY Srinivasan CC: Jake Oshins CC: Haiyang Zhang CC: Vitaly Kuznetsov diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index 6fa40f1..735fcc5 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -259,7 +259,7 @@ struct pci_bus_d0_entry { struct pci_bus_relations { struct pci_incoming_message incoming; u32 device_count; - struct pci_function_description func[1]; + struct pci_function_description func[0]; } __packed; struct pci_q_res_req_response { @@ -394,7 +394,7 @@ struct hv_dr_work { struct hv_dr_state { struct list_head list_entry; u32 device_count; - struct pci_function_description func[1]; + struct pci_function_description func[0]; }; enum hv_pcichild_state { -- cgit v0.10.2 From 617ceb62eaa1a180e8af1be9903d960c3a0b2ebc Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 23 Aug 2016 04:47:23 +0000 Subject: PCI: hv: Remove the unused 'wrk' in struct hv_pcibus_device Remove the unused 'wrk' member in struct hv_pcibus_device. Signed-off-by: Dexuan Cui Signed-off-by: Bjorn Helgaas Acked-by: KY Srinivasan CC: Jake Oshins CC: Haiyang Zhang CC: Vitaly Kuznetsov diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index 735fcc5..3034d48 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -374,7 +374,6 @@ struct hv_pcibus_device { struct list_head children; struct list_head dr_list; - struct work_struct wrk; struct msi_domain_info msi_info; struct msi_controller msi_chip; -- cgit v0.10.2 From 665e2245eb46a217926d45031b83f1212a1bb344 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 23 Aug 2016 04:48:11 +0000 Subject: PCI: hv: Handle vmbus_sendpacket() failure in hv_compose_msi_msg() Handle vmbus_sendpacket() failure in hv_compose_msi_msg(). I happened to find this when reading the code. I didn't get a real issue however. Signed-off-by: Dexuan Cui Signed-off-by: Bjorn Helgaas Acked-by: KY Srinivasan CC: Jake Oshins CC: Haiyang Zhang CC: Vitaly Kuznetsov diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index 3034d48..e9307af 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -895,8 +895,10 @@ static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) sizeof(*int_pkt), (unsigned long)&ctxt.pkt, VM_PKT_DATA_INBAND, VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); - if (!ret) - wait_for_completion(&comp.comp_pkt.host_event); + if (ret) + goto free_int_desc; + + wait_for_completion(&comp.comp_pkt.host_event); if (comp.comp_pkt.completion_status < 0) { dev_err(&hbus->hdev->device, -- cgit v0.10.2 From a5b45b7b952822aa5fbe842d2ee497c7c9dd7f55 Mon Sep 17 00:00:00 2001 From: Dexuan Cui Date: Tue, 23 Aug 2016 04:49:22 +0000 Subject: PCI: hv: Handle hv_pci_generic_compl() error case 'completion_status' is used in some places, e.g., hv_pci_protocol_negotiation(), so we should make sure it's initialized in error case too, though the error is unlikely here. [bhelgaas: fix changelog typo and nearby whitespace] Signed-off-by: Dexuan Cui Signed-off-by: Bjorn Helgaas Acked-by: KY Srinivasan CC: Jake Oshins CC: Haiyang Zhang CC: Vitaly Kuznetsov diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c index e9307af..763ff87 100644 --- a/drivers/pci/host/pci-hyperv.c +++ b/drivers/pci/host/pci-hyperv.c @@ -447,15 +447,16 @@ struct hv_pci_compl { * for any message for which the completion packet contains a * status and nothing else. */ -static -void -hv_pci_generic_compl(void *context, struct pci_response *resp, - int resp_packet_size) +static void hv_pci_generic_compl(void *context, struct pci_response *resp, + int resp_packet_size) { struct hv_pci_compl *comp_pkt = context; if (resp_packet_size >= offsetofend(struct pci_response, status)) comp_pkt->completion_status = resp->status; + else + comp_pkt->completion_status = -1; + complete(&comp_pkt->host_event); } -- cgit v0.10.2 From db047f8a931275e50563dd79c3d62d977074959a Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:41 +0100 Subject: PCI: aardvark: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridge's memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI aardvark host bridge driver does not remove the PCI IO resource from the host bridge resource windows if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Fix the PCI host bridge driver pci_remap_iospace() failure path, by destroying the PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, therefore preventing the kernel from adding the respective PCI IO resource to the list of PCI host bridge valid resources, fixing the issue. Fixes: 8c39d710363c ("PCI: aardvark: Add Aardvark PCI host controller driver") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas CC: Thomas Petazzoni diff --git a/drivers/pci/host/pci-aardvark.c b/drivers/pci/host/pci-aardvark.c index ef9893f..4f5e567 100644 --- a/drivers/pci/host/pci-aardvark.c +++ b/drivers/pci/host/pci-aardvark.c @@ -848,7 +848,7 @@ static int advk_pcie_parse_request_of_pci_ranges(struct advk_pcie *pcie) int err, res_valid = 0; struct device *dev = &pcie->pdev->dev; struct device_node *np = dev->of_node; - struct resource_entry *win; + struct resource_entry *win, *tmp; resource_size_t iobase; INIT_LIST_HEAD(&pcie->resources); @@ -862,7 +862,7 @@ static int advk_pcie_parse_request_of_pci_ranges(struct advk_pcie *pcie) if (err) goto out_release_res; - resource_list_for_each_entry(win, &pcie->resources) { + resource_list_for_each_entry_safe(win, tmp, &pcie->resources) { struct resource *res = win->res; switch (resource_type(res)) { @@ -874,9 +874,11 @@ static int advk_pcie_parse_request_of_pci_ranges(struct advk_pcie *pcie) lower_32_bits(res->start), OB_PCIE_IO); err = pci_remap_iospace(res, iobase); - if (err) + if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); + resource_list_destroy_entry(win); + } break; case IORESOURCE_MEM: advk_pcie_set_ob_win(pcie, 0, -- cgit v0.10.2 From bcd7b7186fcba434e7486648de85cf93a56c845c Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:42 +0100 Subject: PCI: designware: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridges memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI designware host bridge driver does not remove the PCI IO resource from the host bridge resource windows if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Fix the PCI host bridge driver pci_remap_iospace() failure path, by destroying the PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, therefore preventing the kernel from adding the respective PCI IO resource to the list of PCI host bridge valid resources, fixing the issue. Fixes: cbce7900598c ("PCI: designware: Make driver arch-agnostic") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas CC: Jingoo Han CC: Pratyush Anand diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 12afce1..2a500f2 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -436,7 +436,7 @@ int dw_pcie_host_init(struct pcie_port *pp) struct resource *cfg_res; int i, ret; LIST_HEAD(res); - struct resource_entry *win; + struct resource_entry *win, *tmp; cfg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); if (cfg_res) { @@ -457,17 +457,20 @@ int dw_pcie_host_init(struct pcie_port *pp) goto error; /* Get the I/O and memory ranges from DT */ - resource_list_for_each_entry(win, &res) { + resource_list_for_each_entry_safe(win, tmp, &res) { switch (resource_type(win->res)) { case IORESOURCE_IO: - pp->io = win->res; - pp->io->name = "I/O"; - pp->io_size = resource_size(pp->io); - pp->io_bus_addr = pp->io->start - win->offset; - ret = pci_remap_iospace(pp->io, pp->io_base); - if (ret) + ret = pci_remap_iospace(win->res, pp->io_base); + if (ret) { dev_warn(pp->dev, "error %d: failed to map resource %pR\n", - ret, pp->io); + ret, win->res); + resource_list_destroy_entry(win); + } else { + pp->io = win->res; + pp->io->name = "I/O"; + pp->io_size = resource_size(pp->io); + pp->io_bus_addr = pp->io->start - win->offset; + } break; case IORESOURCE_MEM: pp->mem = win->res; -- cgit v0.10.2 From 53f4f7ee28076a36e427274d7d5c33b23dfc6221 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:43 +0100 Subject: PCI: versatile: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridges memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI versatile host bridge driver does not remove the PCI IO resource from the host bridge resource windows if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Fix the PCI host bridge driver pci_remap_iospace() failure path, by destroying the PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, therefore preventing the kernel from adding the respective PCI IO resource to the list of PCI host bridge valid resources, fixing the issue. Fixes: b7e78170efd4 ("PCI: versatile: Add DT-based ARM Versatile PB PCIe host driver") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas CC: Rob Herring diff --git a/drivers/pci/host/pci-versatile.c b/drivers/pci/host/pci-versatile.c index f2344057..b7dc070 100644 --- a/drivers/pci/host/pci-versatile.c +++ b/drivers/pci/host/pci-versatile.c @@ -74,7 +74,7 @@ static int versatile_pci_parse_request_of_pci_ranges(struct device *dev, int err, mem = 1, res_valid = 0; struct device_node *np = dev->of_node; resource_size_t iobase; - struct resource_entry *win; + struct resource_entry *win, *tmp; err = of_pci_get_host_bridge_resources(np, 0, 0xff, res, &iobase); if (err) @@ -84,15 +84,17 @@ static int versatile_pci_parse_request_of_pci_ranges(struct device *dev, if (err) goto out_release_res; - resource_list_for_each_entry(win, res) { + resource_list_for_each_entry_safe(win, tmp, res) { struct resource *res = win->res; switch (resource_type(res)) { case IORESOURCE_IO: err = pci_remap_iospace(res, iobase); - if (err) + if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); + resource_list_destroy_entry(win); + } break; case IORESOURCE_MEM: res_valid |= !(res->flags & IORESOURCE_PREFETCH); -- cgit v0.10.2 From 5e8c873270cc618e3326eb6a47437b517ef85c52 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:44 +0100 Subject: PCI: rcar: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridges memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI rcar host bridge driver does not remove the PCI IO resource from the host bridge resource windows if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Fix the PCI host bridge driver pci_remap_iospace() failure path, by destroying the PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, therefore preventing the kernel from adding the respective PCI IO resource to the list of PCI host bridge valid resources, fixing the issue. Fixes: 5d2917d469fa ("PCI: rcar: Convert to DT resource parsing API") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas CC: Phil Edworthy CC: Simon Horman diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 65db7a2..5f7fcc9 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -945,7 +945,7 @@ static int rcar_pcie_parse_request_of_pci_ranges(struct rcar_pcie *pci) struct device *dev = pci->dev; struct device_node *np = dev->of_node; resource_size_t iobase; - struct resource_entry *win; + struct resource_entry *win, *tmp; err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, &iobase); if (err) @@ -955,14 +955,17 @@ static int rcar_pcie_parse_request_of_pci_ranges(struct rcar_pcie *pci) if (err) goto out_release_res; - resource_list_for_each_entry(win, &pci->resources) { + resource_list_for_each_entry_safe(win, tmp, &pci->resources) { struct resource *res = win->res; if (resource_type(res) == IORESOURCE_IO) { err = pci_remap_iospace(res, iobase); - if (err) + if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); + + resource_list_destroy_entry(win); + } } } -- cgit v0.10.2 From 43281ede019ede33fd0c40a14a86b304a51e4555 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:45 +0100 Subject: PCI: generic: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridges memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI common host bridge driver does not remove the PCI IO resource from the host bridge resource windows if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Fix the PCI host bridge driver pci_remap_iospace() failure path, by destroying the PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, therefore preventing the kernel from adding the respective PCI IO resource to the list of PCI host bridge valid resources, fixing the issue. Fixes: 4e64dbe226e7 ("PCI: generic: Expose pci_host_common_probe() for use by other drivers") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Will Deacon diff --git a/drivers/pci/host/pci-host-common.c b/drivers/pci/host/pci-host-common.c index 9d9d34e..61eb4d4 100644 --- a/drivers/pci/host/pci-host-common.c +++ b/drivers/pci/host/pci-host-common.c @@ -29,7 +29,7 @@ static int gen_pci_parse_request_of_pci_ranges(struct device *dev, int err, res_valid = 0; struct device_node *np = dev->of_node; resource_size_t iobase; - struct resource_entry *win; + struct resource_entry *win, *tmp; err = of_pci_get_host_bridge_resources(np, 0, 0xff, resources, &iobase); if (err) @@ -39,15 +39,17 @@ static int gen_pci_parse_request_of_pci_ranges(struct device *dev, if (err) return err; - resource_list_for_each_entry(win, resources) { + resource_list_for_each_entry_safe(win, tmp, resources) { struct resource *res = win->res; switch (resource_type(res)) { case IORESOURCE_IO: err = pci_remap_iospace(res, iobase); - if (err) + if (err) { dev_warn(dev, "error %d: failed to map resource %pR\n", err, res); + resource_list_destroy_entry(win); + } break; case IORESOURCE_MEM: res_valid |= !(res->flags & IORESOURCE_PREFETCH); -- cgit v0.10.2 From 13f392ebc37e31568fae72a73ee378ae22a9740f Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Mon, 15 Aug 2016 17:50:46 +0100 Subject: PCI: tegra: Fix pci_remap_iospace() failure path On ARM/ARM64 architectures, PCI IO ports are emulated through memory mapped IO, by reserving a chunk of virtual address space starting at PCI_IOBASE and by mapping the PCI host bridges memory address space driving PCI IO cycles to it. PCI host bridge drivers that enable downstream PCI IO cycles map the host bridge memory address responding to PCI IO cycles to the fixed virtual address space through the pci_remap_iospace() API. This means that if the pci_remap_iospace() function fails, the corresponding host bridge PCI IO resource must be considered invalid, in that there is no way for the kernel to actually drive PCI IO transactions if the memory addresses responding to PCI IO cycles cannot be mapped into the CPU virtual address space. The PCI tegra host bridge driver adds the PCI IO resource retrieved from firmware to the host bridge resource windows even if the pci_remap_iospace() call fails; this is an actual bug in that the PCI host bridge would consider the PCI IO resource valid (and possibly assign it to downstream devices) even if the kernel was not able to map the PCI host bridge memory address driving IO cycle to the CPU virtual address space (ie pci_remap_iospace() failures). Add the PCI host bridge driver pci_remap_iospace() failure path and do not add the corresponding PCI host bridge PCI IO resources retrieved through firmware when the pci_remap_iospace() function call fails, fixing the issue. Fixes: e6e9f471f5fe ("PCI: tegra: Use generic pci_remap_iospace() rather than ARM32-specific one") Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas CC: Thierry Reding diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 6de0757..8c2590d 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -621,7 +621,11 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) if (err < 0) return err; - pci_add_resource_offset(&sys->resources, &pcie->pio, sys->io_offset); + err = pci_remap_iospace(&pcie->pio, pcie->io.start); + if (!err) + pci_add_resource_offset(&sys->resources, &pcie->pio, + sys->io_offset); + pci_add_resource_offset(&sys->resources, &pcie->mem, sys->mem_offset); pci_add_resource_offset(&sys->resources, &pcie->prefetch, sys->mem_offset); @@ -631,7 +635,6 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) if (err < 0) return err; - pci_remap_iospace(&pcie->pio, pcie->io.start); return 1; } -- cgit v0.10.2 From 032c3d86b4acc4c21e435c85c454eac670c15851 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Thu, 25 Aug 2016 17:26:10 -0600 Subject: PCI/AER: Add bus flag to skip source ID matching Allow root port buses to choose to skip source id matching when finding the faulting device. Certain root port devices may return an incorrect source ID and recommend to scan child device registers for AER notifications. Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index 521e39c..8f5e14c 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -132,7 +132,9 @@ static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) * When bus id is equal to 0, it might be a bad id * reported by root port. */ - if (!nosourceid && (PCI_BUS_NUM(e_info->id) != 0)) { + if (!nosourceid && + (PCI_BUS_NUM(e_info->id) != 0) && + !(dev->bus->bus_flags & PCI_BUS_FLAGS_NO_AERSID)) { /* Device ID match? */ if (e_info->id == ((dev->bus->number << 8) | dev->devfn)) return true; @@ -147,7 +149,8 @@ static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) * 1) nosourceid==y; * 2) bus id is equal to 0. Some ports might lose the bus * id of error source id; - * 3) There are multiple errors and prior id comparing fails; + * 3) bus flag PCI_BUS_FLAGS_NO_AERSID is set + * 4) There are multiple errors and prior ID comparing fails; * We check AER status registers to find possible reporter. */ if (atomic_read(&dev->enable_cnt) == 0) diff --git a/include/linux/pci.h b/include/linux/pci.h index 2599a98..57bc838 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -187,8 +187,9 @@ enum pci_irq_reroute_variant { typedef unsigned short __bitwise pci_bus_flags_t; enum pci_bus_flags { - PCI_BUS_FLAGS_NO_MSI = (__force pci_bus_flags_t) 1, - PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2, + PCI_BUS_FLAGS_NO_MSI = (__force pci_bus_flags_t) 1, + PCI_BUS_FLAGS_NO_MMRBC = (__force pci_bus_flags_t) 2, + PCI_BUS_FLAGS_NO_AERSID = (__force pci_bus_flags_t) 4, }; /* These values come from the PCI Express Spec */ -- cgit v0.10.2 From 443b40ba21aa346388ddfb8fa7f2baee6b74e8ee Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Tue, 6 Sep 2016 14:15:24 -0500 Subject: x86/PCI: VMD: Add quirk for AER to ignore source ID VMD root ports change all source ids to the VMD device ID. To find the sender of the AER notification, we need to scan all child devices for the AER sender, rather than relying on the source ID from the message. Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 37ff015..6297942 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4428,3 +4428,20 @@ static void quirk_intel_qat_vf_cap(struct pci_dev *pdev) } } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x443, quirk_intel_qat_vf_cap); + +/* + * VMD-enabled root ports will change the source ID for all messages + * to the VMD device. Rather than doing device matching with the source + * ID, the AER driver should traverse the child device tree, reading + * AER registers to find the faulting device. + */ +static void quirk_no_aersid(struct pci_dev *pdev) +{ + /* VMD Domain */ + if (pdev->bus->sysdata && pci_domain_nr(pdev->bus) >= 0x10000) + pdev->bus->bus_flags |= PCI_BUS_FLAGS_NO_AERSID; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2030, quirk_no_aersid); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2031, quirk_no_aersid); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2032, quirk_no_aersid); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x2033, quirk_no_aersid); -- cgit v0.10.2 From a8499f20d30e0f9632017fd27a8f1d8c146a6a33 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 8 Sep 2016 17:30:38 -0500 Subject: PCI: pciehp: Rename pcie_isr() locals for clarity Rename "detected" and "intr_loc" to "status" and "events" for clarity. "status" is the value we read from the Slot Status register; "events" is the set of hot-plug events we need to process. No functional change intended. Tested-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 08e84d6..264df36 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -542,7 +542,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) struct pci_bus *subordinate = pdev->subordinate; struct pci_dev *dev; struct slot *slot = ctrl->slot; - u16 detected, intr_loc; + u16 status, events; u8 present; bool link; @@ -555,31 +555,35 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) * serviced, we need to re-inspect Slot Status register after * clearing what is presumed to be the last pending interrupt. */ - intr_loc = 0; + events = 0; do { - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &detected); - if (detected == (u16) ~0) { + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); + if (status == (u16) ~0) { ctrl_info(ctrl, "%s: no response from device\n", __func__); return IRQ_HANDLED; } - detected &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | - PCI_EXP_SLTSTA_PDC | - PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_DLLSC); - detected &= ~intr_loc; - intr_loc |= detected; - if (!intr_loc) + /* + * Slot Status contains plain status bits as well as event + * notification bits; right now we only want the event bits. + */ + status &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_DLLSC); + status &= ~events; + events |= status; + if (!events) return IRQ_NONE; - if (detected) + if (status) pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, - intr_loc); - } while (detected); + events); + } while (status); - ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", intr_loc); + ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); /* Check Command Complete Interrupt Pending */ - if (intr_loc & PCI_EXP_SLTSTA_CC) { + if (events & PCI_EXP_SLTSTA_CC) { ctrl->cmd_busy = 0; smp_mb(); wake_up(&ctrl->queue); @@ -589,24 +593,24 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) list_for_each_entry(dev, &subordinate->devices, bus_list) { if (dev->ignore_hotplug) { ctrl_dbg(ctrl, "ignoring hotplug event %#06x (%s requested no hotplug)\n", - intr_loc, pci_name(dev)); + events, pci_name(dev)); return IRQ_HANDLED; } } } - if (!(intr_loc & ~PCI_EXP_SLTSTA_CC)) + if (!(events & ~PCI_EXP_SLTSTA_CC)) return IRQ_HANDLED; /* Check Attention Button Pressed */ - if (intr_loc & PCI_EXP_SLTSTA_ABP) { + if (events & PCI_EXP_SLTSTA_ABP) { ctrl_info(ctrl, "Button pressed on Slot(%s)\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS); } /* Check Presence Detect Changed */ - if (intr_loc & PCI_EXP_SLTSTA_PDC) { + if (events & PCI_EXP_SLTSTA_PDC) { pciehp_get_adapter_status(slot, &present); ctrl_info(ctrl, "Card %spresent on Slot(%s)\n", present ? "" : "not ", slot_name(slot)); @@ -615,13 +619,13 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) } /* Check Power Fault Detected */ - if ((intr_loc & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { + if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { ctrl->power_fault_detected = 1; ctrl_err(ctrl, "Power fault on slot %s\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_POWER_FAULT); } - if (intr_loc & PCI_EXP_SLTSTA_DLLSC) { + if (events & PCI_EXP_SLTSTA_DLLSC) { link = pciehp_check_link_active(ctrl); ctrl_info(ctrl, "slot(%s): Link %s event\n", slot_name(slot), link ? "Up" : "Down"); -- cgit v0.10.2 From 156c55325d30261d250e88ed3a39f22008f4ca16 Mon Sep 17 00:00:00 2001 From: Po Liu Date: Mon, 29 Aug 2016 15:28:01 +0800 Subject: PCI: Check for pci_setup_device() failure in pci_iov_add_virtfn() If pci_setup_device() returns failure, we must return failure from pci_iov_add_virtfn(). If we ignore the failure and continue with an uninitialized pci_dev for virtfn, we crash later when we try to use those uninitialized parts. Signed-off-by: Po Liu Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 2194b44..e30f05c 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -136,7 +136,10 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id, int reset) virtfn->devfn = pci_iov_virtfn_devfn(dev, id); virtfn->vendor = dev->vendor; pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_DID, &virtfn->device); - pci_setup_device(virtfn); + rc = pci_setup_device(virtfn); + if (rc) + goto failed0; + virtfn->dev.parent = dev->dev.parent; virtfn->physfn = pci_dev_get(dev); virtfn->is_virtfn = 1; -- cgit v0.10.2 From d99e30b7936ad2214548e33d8de15584c741bdb4 Mon Sep 17 00:00:00 2001 From: Po Liu Date: Mon, 29 Aug 2016 15:26:58 +0800 Subject: PCI: altera: Relax device number checking to allow SR-IOV Previously we only allowed device 0 to be directly attached to the root port. But SR-IOV devices may use non-zero device numbers for VFs. Remove the restriction that only device 0 may be attached to a root port. [bhelgaas: changelog] Signed-off-by: Po Liu Signed-off-by: Bjorn Helgaas Acked-by: Ley Foon Tan diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 2b78376..edbe0a7 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -171,13 +171,6 @@ static bool altera_pcie_valid_config(struct altera_pcie *pcie, if (bus->number == pcie->root_bus_nr && dev > 0) return false; - /* - * Do not read more than one device on the bus directly attached - * to root port, root port can only attach to one downstream port. - */ - if (bus->primary == pcie->root_bus_nr && dev > 0) - return false; - return true; } -- cgit v0.10.2 From e18934b5e9c78bb8c223b323275c14a0968e2737 Mon Sep 17 00:00:00 2001 From: Po Liu Date: Mon, 29 Aug 2016 15:26:58 +0800 Subject: PCI: designware: Relax device number checking to allow SR-IOV Previously we only allowed device 0 to be directly attached to the root port. But SR-IOV devices may use non-zero device numbers for VFs. Remove the restriction that only device 0 may be attached to a root port. [bhelgaas: changelog] Signed-off-by: Po Liu Signed-off-by: Bjorn Helgaas Acked-by: Jingoo Han diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 12afce1..dd20eb2 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -670,13 +670,6 @@ static int dw_pcie_valid_config(struct pcie_port *pp, if (bus->number == pp->root_bus_nr && dev > 0) return 0; - /* - * do not read more than one device on the bus directly attached - * to RC's (Virtual Bridge's) DS side. - */ - if (bus->primary == pp->root_bus_nr && dev > 0) - return 0; - return 1; } -- cgit v0.10.2 From 8e7ca8ca5fd8a3242289105723c429733be8b73b Mon Sep 17 00:00:00 2001 From: Po Liu Date: Mon, 29 Aug 2016 15:26:58 +0800 Subject: PCI: xilinx: Relax device number checking to allow SR-IOV Previously we only allowed device 0 to be directly attached to the root port. But SR-IOV devices may use non-zero device numbers for VFs. Remove the restriction that only device 0 may be attached to a root port. [bhelgaas: changelog] Signed-off-by: Po Liu Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index a30e016..75c89db 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -168,13 +168,6 @@ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) if (bus->number == port->root_busno && devfn > 0) return false; - /* - * Do not read more than one device on the bus directly attached - * to RC. - */ - if (bus->primary == port->root_busno && devfn > 0) - return false; - return true; } -- cgit v0.10.2 From ce4f1c7ad490aa7129bde5632d6e53943f8a866c Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Fri, 26 Aug 2016 09:47:25 +0800 Subject: PCI: altera: Move retrain from fixup to altera_pcie_host_init() Previously we used a PCI early fixup to initiate a link retrain on Altera devices. But Altera PCIe IP can be configured as either a Root Port or an Endpoint, and they might have same vendor ID, so the fixup would be run for both. We only want to initiate a link retrain for Altera Root Port devices, not for Endpoints, so move the link retrain functionality from the fixup to altera_pcie_host_init(). [bhelgaas: changelog] Signed-off-by: Ley Foon Tan Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 34e6258..4ca50a2 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -43,6 +43,7 @@ #define RP_LTSSM_MASK 0x1f #define LTSSM_L0 0xf +#define PCIE_CAP_OFFSET 0x80 /* TLP configuration type 0 and 1 */ #define TLP_FMTTYPE_CFGRD0 0x04 /* Configuration Read Type 0 */ #define TLP_FMTTYPE_CFGWR0 0x44 /* Configuration Write Type 0 */ @@ -100,66 +101,6 @@ static bool altera_pcie_link_is_up(struct altera_pcie *pcie) return !!((cra_readl(pcie, RP_LTSSM) & RP_LTSSM_MASK) == LTSSM_L0); } -static void altera_wait_link_retrain(struct pci_dev *dev) -{ - u16 reg16; - unsigned long start_jiffies; - struct altera_pcie *pcie = dev->bus->sysdata; - - /* Wait for link training end. */ - start_jiffies = jiffies; - for (;;) { - pcie_capability_read_word(dev, PCI_EXP_LNKSTA, ®16); - if (!(reg16 & PCI_EXP_LNKSTA_LT)) - break; - - if (time_after(jiffies, start_jiffies + LINK_RETRAIN_TIMEOUT)) { - dev_err(&pcie->pdev->dev, "link retrain timeout\n"); - break; - } - udelay(100); - } - - /* Wait for link is up */ - start_jiffies = jiffies; - for (;;) { - if (altera_pcie_link_is_up(pcie)) - break; - - if (time_after(jiffies, start_jiffies + LINK_UP_TIMEOUT)) { - dev_err(&pcie->pdev->dev, "link up timeout\n"); - break; - } - udelay(100); - } -} - -static void altera_pcie_retrain(struct pci_dev *dev) -{ - u16 linkcap, linkstat; - struct altera_pcie *pcie = dev->bus->sysdata; - - if (!altera_pcie_link_is_up(pcie)) - return; - - /* - * Set the retrain bit if the PCIe rootport support > 2.5GB/s, but - * current speed is 2.5 GB/s. - */ - pcie_capability_read_word(dev, PCI_EXP_LNKCAP, &linkcap); - - if ((linkcap & PCI_EXP_LNKCAP_SLS) <= PCI_EXP_LNKCAP_SLS_2_5GB) - return; - - pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &linkstat); - if ((linkstat & PCI_EXP_LNKSTA_CLS) == PCI_EXP_LNKSTA_CLS_2_5GB) { - pcie_capability_set_word(dev, PCI_EXP_LNKCTL, - PCI_EXP_LNKCTL_RL); - altera_wait_link_retrain(dev); - } -} -DECLARE_PCI_FIXUP_EARLY(0x1172, PCI_ANY_ID, altera_pcie_retrain); - /* * Altera PCIe port uses BAR0 of RC's configuration space as the translation * from PCI bus to native BUS. Entire DDR region is mapped into PCIe space @@ -434,6 +375,90 @@ static struct pci_ops altera_pcie_ops = { .write = altera_pcie_cfg_write, }; +static int altera_read_cap_word(struct altera_pcie *pcie, u8 busno, + unsigned int devfn, int offset, u16 *value) +{ + u32 data; + int ret; + + ret = _altera_pcie_cfg_read(pcie, busno, devfn, + PCIE_CAP_OFFSET + offset, sizeof(*value), + &data); + *value = data; + return ret; +} + +static int altera_write_cap_word(struct altera_pcie *pcie, u8 busno, + unsigned int devfn, int offset, u16 value) +{ + return _altera_pcie_cfg_write(pcie, busno, devfn, + PCIE_CAP_OFFSET + offset, sizeof(value), + value); +} + +static void altera_wait_link_retrain(struct altera_pcie *pcie) +{ + u16 reg16; + unsigned long start_jiffies; + + /* Wait for link training end. */ + start_jiffies = jiffies; + for (;;) { + altera_read_cap_word(pcie, pcie->root_bus_nr, RP_DEVFN, + PCI_EXP_LNKSTA, ®16); + if (!(reg16 & PCI_EXP_LNKSTA_LT)) + break; + + if (time_after(jiffies, start_jiffies + LINK_RETRAIN_TIMEOUT)) { + dev_err(&pcie->pdev->dev, "link retrain timeout\n"); + break; + } + udelay(100); + } + + /* Wait for link is up */ + start_jiffies = jiffies; + for (;;) { + if (altera_pcie_link_is_up(pcie)) + break; + + if (time_after(jiffies, start_jiffies + LINK_UP_TIMEOUT)) { + dev_err(&pcie->pdev->dev, "link up timeout\n"); + break; + } + udelay(100); + } +} + +static void altera_pcie_retrain(struct altera_pcie *pcie) +{ + u16 linkcap, linkstat, linkctl; + + if (!altera_pcie_link_is_up(pcie)) + return; + + /* + * Set the retrain bit if the PCIe rootport support > 2.5GB/s, but + * current speed is 2.5 GB/s. + */ + altera_read_cap_word(pcie, pcie->root_bus_nr, RP_DEVFN, PCI_EXP_LNKCAP, + &linkcap); + if ((linkcap & PCI_EXP_LNKCAP_SLS) <= PCI_EXP_LNKCAP_SLS_2_5GB) + return; + + altera_read_cap_word(pcie, pcie->root_bus_nr, RP_DEVFN, PCI_EXP_LNKSTA, + &linkstat); + if ((linkstat & PCI_EXP_LNKSTA_CLS) == PCI_EXP_LNKSTA_CLS_2_5GB) { + altera_read_cap_word(pcie, pcie->root_bus_nr, RP_DEVFN, + PCI_EXP_LNKCTL, &linkctl); + linkctl |= PCI_EXP_LNKCTL_RL; + altera_write_cap_word(pcie, pcie->root_bus_nr, RP_DEVFN, + PCI_EXP_LNKCTL, linkctl); + + altera_wait_link_retrain(pcie); + } +} + static int altera_pcie_intx_map(struct irq_domain *domain, unsigned int irq, irq_hw_number_t hwirq) { @@ -555,6 +580,11 @@ static int altera_pcie_parse_dt(struct altera_pcie *pcie) return 0; } +static void altera_pcie_host_init(struct altera_pcie *pcie) +{ + altera_pcie_retrain(pcie); +} + static int altera_pcie_probe(struct platform_device *pdev) { struct altera_pcie *pcie; @@ -592,6 +622,7 @@ static int altera_pcie_probe(struct platform_device *pdev) cra_writel(pcie, P2A_INT_STS_ALL, P2A_INT_STATUS); /* enable all interrupts */ cra_writel(pcie, P2A_INT_ENA_ALL, P2A_INT_ENABLE); + altera_pcie_host_init(pcie); bus = pci_scan_root_bus(&pdev->dev, pcie->root_bus_nr, &altera_pcie_ops, pcie, &pcie->resources); -- cgit v0.10.2 From 6c8b12080e9775d13dd99a4cd3d9e79448bf93f7 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Aug 2016 14:12:11 -0500 Subject: PCI: altera: Remove redundant platform_get_resource() return value check devm_ioremap_resource() fails gracefully when given a NULL resource pointer, so we don't need to check separately for failure from platform_get_resource_byname(). Remove the redundant check. Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-altera-msi.c b/drivers/pci/host/pcie-altera-msi.c index 99177f4..0629ae1 100644 --- a/drivers/pci/host/pcie-altera-msi.c +++ b/drivers/pci/host/pcie-altera-msi.c @@ -237,11 +237,6 @@ static int altera_msi_probe(struct platform_device *pdev) msi->pdev = pdev; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr"); - if (!res) { - dev_err(&pdev->dev, "no csr memory resource defined\n"); - return -ENODEV; - } - msi->csr_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(msi->csr_base)) { dev_err(&pdev->dev, "failed to map csr memory\n"); @@ -250,11 +245,6 @@ static int altera_msi_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vector_slave"); - if (!res) { - dev_err(&pdev->dev, "no vector_slave memory resource defined\n"); - return -ENODEV; - } - msi->vector_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(msi->vector_base)) { dev_err(&pdev->dev, "failed to map vector_slave memory\n"); -- cgit v0.10.2 From 3d664b070ccb2b1522174ac36c606f92808a26fa Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Wed, 31 Aug 2016 11:28:22 +0200 Subject: PCI: rcar: Don't disable/unprepare clocks on prepare/enable failure If clk_prepare_enable() fails, we must not call clk_disable_unprepare() in the error path. Signed-off-by: Geert Uytterhoeven Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index e58bbbc..45dc3cd 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -786,7 +786,7 @@ static int rcar_pcie_get_resources(struct platform_device *pdev, } err = clk_prepare_enable(pcie->clk); if (err) - goto fail_clk; + return err; pcie->bus_clk = devm_clk_get(&pdev->dev, "pcie_bus"); if (IS_ERR(pcie->bus_clk)) { @@ -796,7 +796,7 @@ static int rcar_pcie_get_resources(struct platform_device *pdev, } err = clk_prepare_enable(pcie->bus_clk); if (err) - goto err_map_reg; + goto fail_clk; i = irq_of_parse_and_map(pdev->dev.of_node, 0); if (!i) { -- cgit v0.10.2 From c2a7ff18edcdb9b04c655f361b0385bdbe3be935 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Tue, 30 Aug 2016 16:09:16 +0530 Subject: PCI: xilinx-nwl: Expand error logging The current driver logs PCIe core errors. Add logging for individual core events. [bhelgaas: changelog] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c index 0b597d9..dfdf58f 100644 --- a/drivers/pci/host/pcie-xilinx-nwl.c +++ b/drivers/pci/host/pcie-xilinx-nwl.c @@ -85,10 +85,15 @@ #define MSGF_MISC_SR_MASTER_ERR BIT(5) #define MSGF_MISC_SR_I_ADDR_ERR BIT(6) #define MSGF_MISC_SR_E_ADDR_ERR BIT(7) -#define MSGF_MISC_SR_UR_DETECT BIT(20) - -#define MSGF_MISC_SR_PCIE_CORE GENMASK(18, 16) -#define MSGF_MISC_SR_PCIE_CORE_ERR GENMASK(31, 22) +#define MSGF_MISC_SR_FATAL_AER BIT(16) +#define MSGF_MISC_SR_NON_FATAL_AER BIT(17) +#define MSGF_MISC_SR_CORR_AER BIT(18) +#define MSGF_MISC_SR_UR_DETECT BIT(20) +#define MSGF_MISC_SR_NON_FATAL_DEV BIT(22) +#define MSGF_MISC_SR_FATAL_DEV BIT(23) +#define MSGF_MISC_SR_LINK_DOWN BIT(24) +#define MSGF_MSIC_SR_LINK_AUTO_BWIDTH BIT(25) +#define MSGF_MSIC_SR_LINK_BWIDTH BIT(26) #define MSGF_MISC_SR_MASKALL (MSGF_MISC_SR_RXMSG_AVAIL | \ MSGF_MISC_SR_RXMSG_OVER | \ @@ -96,9 +101,15 @@ MSGF_MISC_SR_MASTER_ERR | \ MSGF_MISC_SR_I_ADDR_ERR | \ MSGF_MISC_SR_E_ADDR_ERR | \ + MSGF_MISC_SR_FATAL_AER | \ + MSGF_MISC_SR_NON_FATAL_AER | \ + MSGF_MISC_SR_CORR_AER | \ MSGF_MISC_SR_UR_DETECT | \ - MSGF_MISC_SR_PCIE_CORE | \ - MSGF_MISC_SR_PCIE_CORE_ERR) + MSGF_MISC_SR_NON_FATAL_DEV | \ + MSGF_MISC_SR_FATAL_DEV | \ + MSGF_MISC_SR_LINK_DOWN | \ + MSGF_MSIC_SR_LINK_AUTO_BWIDTH | \ + MSGF_MSIC_SR_LINK_BWIDTH) /* Legacy interrupt status mask bits */ #define MSGF_LEG_SR_INTA BIT(0) @@ -291,8 +302,29 @@ static irqreturn_t nwl_pcie_misc_handler(int irq, void *data) dev_err(pcie->dev, "In Misc Egress address translation error\n"); - if (misc_stat & MSGF_MISC_SR_PCIE_CORE_ERR) - dev_err(pcie->dev, "PCIe Core error\n"); + if (misc_stat & MSGF_MISC_SR_FATAL_AER) + dev_err(pcie->dev, "Fatal Error in AER Capability\n"); + + if (misc_stat & MSGF_MISC_SR_NON_FATAL_AER) + dev_err(pcie->dev, "Non-Fatal Error in AER Capability\n"); + + if (misc_stat & MSGF_MISC_SR_CORR_AER) + dev_err(pcie->dev, "Correctable Error in AER Capability\n"); + + if (misc_stat & MSGF_MISC_SR_UR_DETECT) + dev_err(pcie->dev, "Unsupported request Detected\n"); + + if (misc_stat & MSGF_MISC_SR_NON_FATAL_DEV) + dev_err(pcie->dev, "Non-Fatal Error Detected\n"); + + if (misc_stat & MSGF_MISC_SR_FATAL_DEV) + dev_err(pcie->dev, "Fatal Error Detected\n"); + + if (misc_stat & MSGF_MSIC_SR_LINK_AUTO_BWIDTH) + dev_info(pcie->dev, "Link Autonomous Bandwidth Management Status bit set\n"); + + if (misc_stat & MSGF_MSIC_SR_LINK_BWIDTH) + dev_info(pcie->dev, "Link Bandwidth Management Status bit set\n"); /* Clear misc interrupt status */ nwl_bridge_writel(pcie, misc_stat, MSGF_MISC_STATUS); -- cgit v0.10.2 From f665bd1515aceb80990fdbc91b7c8aed045f5da8 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Tue, 30 Aug 2016 16:09:17 +0530 Subject: PCI: xilinx-nwl: Enable all MSI interrupts using MSI mask The current mask enables and allows only one MSI interrupt on each MSI line. Enable all MSI interrupts, which will also support Endpoints with multi-MSI support. Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c index dfdf58f..07261a7 100644 --- a/drivers/pci/host/pcie-xilinx-nwl.c +++ b/drivers/pci/host/pcie-xilinx-nwl.c @@ -120,8 +120,8 @@ MSGF_LEG_SR_INTC | MSGF_LEG_SR_INTD) /* MSI interrupt status mask bits */ -#define MSGF_MSI_SR_LO_MASK BIT(0) -#define MSGF_MSI_SR_HI_MASK BIT(0) +#define MSGF_MSI_SR_LO_MASK GENMASK(31, 0) +#define MSGF_MSI_SR_HI_MASK GENMASK(31, 0) #define MSII_PRESENT BIT(0) #define MSII_ENABLE BIT(0) -- cgit v0.10.2 From b584fa1fde71aa57fb63d32f66ff6c192ff7f2c5 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 1 Sep 2016 15:44:41 +0530 Subject: PCI: xilinx: Keep both legacy and MSI interrupt domain references When built with MSI support, the legacy domain reference was being overwritten with MSI. Create two separate domains for MSI and legacy interrupts. Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index a30e016..bd64677 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -101,7 +101,8 @@ * @msi_pages: MSI pages * @root_busno: Root Bus number * @dev: Device pointer - * @irq_domain: IRQ domain pointer + * @msi_domain: MSI IRQ domain pointer + * @leg_domain: Legacy IRQ domain pointer * @resources: Bus Resources */ struct xilinx_pcie_port { @@ -110,7 +111,8 @@ struct xilinx_pcie_port { unsigned long msi_pages; u8 root_busno; struct device *dev; - struct irq_domain *irq_domain; + struct irq_domain *msi_domain; + struct irq_domain *leg_domain; struct list_head resources; }; @@ -281,7 +283,7 @@ static int xilinx_pcie_msi_setup_irq(struct msi_controller *chip, if (hwirq < 0) return hwirq; - irq = irq_create_mapping(port->irq_domain, hwirq); + irq = irq_create_mapping(port->msi_domain, hwirq); if (!irq) return -EINVAL; @@ -443,7 +445,7 @@ static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data) /* Handle INTx Interrupt */ val = ((val & XILINX_PCIE_RPIFR1_INTR_MASK) >> XILINX_PCIE_RPIFR1_INTR_SHIFT) + 1; - generic_handle_irq(irq_find_mapping(port->irq_domain, + generic_handle_irq(irq_find_mapping(port->leg_domain, val)); } } @@ -526,12 +528,14 @@ static void xilinx_pcie_free_irq_domain(struct xilinx_pcie_port *port) } for (i = 0; i < num_irqs; i++) { - irq = irq_find_mapping(port->irq_domain, i); + irq = irq_find_mapping(port->leg_domain, i); if (irq > 0) irq_dispose_mapping(irq); } - - irq_domain_remove(port->irq_domain); + if (port->leg_domain) + irq_domain_remove(port->leg_domain); + if (port->msi_domain) + irq_domain_remove(port->msi_domain); } /** @@ -553,21 +557,21 @@ static int xilinx_pcie_init_irq_domain(struct xilinx_pcie_port *port) return -ENODEV; } - port->irq_domain = irq_domain_add_linear(pcie_intc_node, 4, + port->leg_domain = irq_domain_add_linear(pcie_intc_node, 4, &intx_domain_ops, port); - if (!port->irq_domain) { + if (!port->leg_domain) { dev_err(dev, "Failed to get a INTx IRQ domain\n"); return -ENODEV; } /* Setup MSI */ if (IS_ENABLED(CONFIG_PCI_MSI)) { - port->irq_domain = irq_domain_add_linear(node, + port->msi_domain = irq_domain_add_linear(node, XILINX_NUM_MSI_IRQS, &msi_domain_ops, &xilinx_pcie_msi_chip); - if (!port->irq_domain) { + if (!port->msi_domain) { dev_err(dev, "Failed to get a MSI IRQ domain\n"); return -ENODEV; } -- cgit v0.10.2 From 3cd049ab9edd48a41955b8d05f0bc57ead918456 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 1 Sep 2016 15:44:42 +0530 Subject: PCI: xilinx: Clear interrupt register for invalid interrupt The interrupt decode register is not being cleared if an invalid interrupt arises. Clear the decode register in this case. Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index bd64677..de1c758 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -434,7 +434,7 @@ static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data) /* Check whether interrupt valid */ if (!(val & XILINX_PCIE_RPIFR1_INTR_VALID)) { dev_warn(port->dev, "RP Intr FIFO1 read error\n"); - return IRQ_HANDLED; + goto error; } if (!(val & XILINX_PCIE_RPIFR1_MSI_INTR)) { @@ -456,7 +456,7 @@ static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data) if (!(val & XILINX_PCIE_RPIFR1_INTR_VALID)) { dev_warn(port->dev, "RP Intr FIFO1 read error\n"); - return IRQ_HANDLED; + goto error; } if (val & XILINX_PCIE_RPIFR1_MSI_INTR) { @@ -501,6 +501,7 @@ static irqreturn_t xilinx_pcie_intr_handler(int irq, void *data) if (status & XILINX_PCIE_INTR_MST_ERRP) dev_warn(port->dev, "Master error poison\n"); +error: /* Clear the Interrupt Decode register */ pcie_write(port, status, XILINX_PCIE_REG_IDR); -- cgit v0.10.2 From 8a4036edf9463b6cf8c2537727b4511c4411d198 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 1 Sep 2016 15:44:43 +0530 Subject: PCI: xilinx: Clear correct MSI set bit Kernel provides virtual IRQ number at teardown. Get hwirq number from virtual IRQ and clear correct MSI set bit. Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index de1c758..ec2844a 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -221,13 +221,15 @@ static void xilinx_pcie_destroy_msi(unsigned int irq) { struct msi_desc *msi; struct xilinx_pcie_port *port; + struct irq_data *d = irq_get_irq_data(irq); + irq_hw_number_t hwirq = irqd_to_hwirq(d); - if (!test_bit(irq, msi_irq_in_use)) { + if (!test_bit(hwirq, msi_irq_in_use)) { msi = irq_get_msi_desc(irq); port = msi_desc_to_pci_sysdata(msi); dev_err(port->dev, "Trying to free unused MSI#%d\n", irq); } else { - clear_bit(irq, msi_irq_in_use); + clear_bit(hwirq, msi_irq_in_use); } } -- cgit v0.10.2 From b328f3ce993c647c055f917afa947d6c2369461d Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 1 Sep 2016 15:44:44 +0530 Subject: PCI: xilinx: Dispose of MSI virtual IRQ Dispose of virtual IRQ being created for MSI interrupts. Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index ec2844a..a276fa6 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -261,6 +261,7 @@ static void xilinx_msi_teardown_irq(struct msi_controller *chip, unsigned int irq) { xilinx_pcie_destroy_msi(irq); + irq_dispose_mapping(irq); } /** -- cgit v0.10.2 From 9413d968f24371bede2c82ec7bb8d040eede8417 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 1 Sep 2016 15:44:46 +0530 Subject: microblaze/PCI: Add multidomain support for procfs We create a procfs directory for every PCI bus. Previously, the directory name was just the bus number, so using the same bus number in different domains caused a kernel crash when we tried to create a duplicate directory. Make pci_proc_domain() return the domain number, so procfs directories for buses in domain 0 are named with just the bus number, and directories for buses in other domains include both the domain number and the bus number. [bhelgaas: changelog] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c index 81556b8..7f696f9 100644 --- a/arch/microblaze/pci/pci-common.c +++ b/arch/microblaze/pci/pci-common.c @@ -632,10 +632,10 @@ void pci_process_bridge_OF_ranges(struct pci_controller *hose, } } -/* Decide whether to display the domain number in /proc */ +/* Display the domain number in /proc */ int pci_proc_domain(struct pci_bus *bus) { - return 0; + return pci_domain_nr(bus); } /* This header fixup will do the resource fixup for all devices as they are -- cgit v0.10.2 From 70e8b40176c75d3544024e7c934720b11a8a11bf Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 8 Sep 2016 16:43:40 -0500 Subject: PCI: pciehp: Return IRQ_NONE when we can't read interrupt status After 1469d17dd341 ("PCI: pciehp: Handle invalid data when reading from non-existent devices"), we returned IRQ_HANDLED when we failed to read interrupt status from the bridge. I think it's better to return IRQ_NONE, as we do in other cases where there's no interrupt pending. This will facilitate refactoring the loop in pcie_isr(): we'll be able to call the ISR in a loop as long as it returns IRQ_HANDLED. Return IRQ_NONE if we couldn't read interrupt status. Tested-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 264df36..b8efe1b 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -561,7 +561,7 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) if (status == (u16) ~0) { ctrl_info(ctrl, "%s: no response from device\n", __func__); - return IRQ_HANDLED; + return IRQ_NONE; } /* -- cgit v0.10.2 From fad214b0aa726ee21adcac4308d388efcb89d6bd Mon Sep 17 00:00:00 2001 From: Mayurkumar Patel Date: Thu, 8 Sep 2016 15:07:56 -0500 Subject: PCI: pciehp: Process all hotplug events before looking for new ones Previously we accumulated hotplug events, then processed them, essentially like this: events = 0 do { status = read(Slot Status) status &= EVENT_MASK # only look at events events |= status # accumulate events write(Slot Status, events) # clear events } while (status) process events The problem is that as soon as we clear events in Slot Status, the hardware may send notifications for new events, and we lose information about the first events. For example, we might see two Presence Detect Changed events, but lose the fact that the slot was temporarily empty: read PCI_EXP_SLTSTA_PDC set, PCI_EXP_SLTSTA_PDS clear # slot empty write PCI_EXP_SLTSTA_PDC # clear PDC event read PCI_EXP_SLTSTA_PDC set, PCI_EXP_SLTSTA_PDS set # slot occupied The current code does not process a removal; it only processes the insertion, which fails because we didn't remove the original device. To avoid this problem, read Slot Status once and process all the events before reading it again, like this: do { read events clear events process events } while (events) [bhelgaas: changelog, add external loop around pciehp_isr()] Tested-by: Lukas Wunner Signed-off-by: Mayurkumar Patel Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index b8efe1b..625fa6a 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -535,7 +535,7 @@ void pciehp_power_off_slot(struct slot *slot) PCI_EXP_SLTCTL_PWR_OFF); } -static irqreturn_t pcie_isr(int irq, void *dev_id) +static irqreturn_t pciehp_isr(int irq, void *dev_id) { struct controller *ctrl = (struct controller *)dev_id; struct pci_dev *pdev = ctrl_dev(ctrl); @@ -550,36 +550,23 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) if (pdev->current_state == PCI_D3cold) return IRQ_NONE; + pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); + if (status == (u16) ~0) { + ctrl_info(ctrl, "%s: no response from device\n", __func__); + return IRQ_NONE; + } + /* - * In order to guarantee that all interrupt events are - * serviced, we need to re-inspect Slot Status register after - * clearing what is presumed to be the last pending interrupt. + * Slot Status contains plain status bits as well as event + * notification bits; right now we only want the event bits. */ - events = 0; - do { - pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &status); - if (status == (u16) ~0) { - ctrl_info(ctrl, "%s: no response from device\n", - __func__); - return IRQ_NONE; - } - - /* - * Slot Status contains plain status bits as well as event - * notification bits; right now we only want the event bits. - */ - status &= (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | + events = status & (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_DLLSC); - status &= ~events; - events |= status; - if (!events) - return IRQ_NONE; - if (status) - pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, - events); - } while (status); + if (!events) + return IRQ_NONE; + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); /* Check Command Complete Interrupt Pending */ @@ -636,6 +623,25 @@ static irqreturn_t pcie_isr(int irq, void *dev_id) return IRQ_HANDLED; } +static irqreturn_t pcie_isr(int irq, void *dev_id) +{ + irqreturn_t rc, handled = IRQ_NONE; + + /* + * To guarantee that all interrupt events are serviced, we need to + * re-inspect Slot Status register after clearing what is presumed + * to be the last pending interrupt. + */ + do { + rc = pciehp_isr(irq, dev_id); + if (rc == IRQ_HANDLED) + handled = IRQ_HANDLED; + } while (rc == IRQ_HANDLED); + + /* Return IRQ_HANDLED if we handled one or more events */ + return handled; +} + void pcie_enable_notification(struct controller *ctrl) { u16 cmd, mask; -- cgit v0.10.2 From 0c923d1da394b96727b813d1e64412b72f1dc580 Mon Sep 17 00:00:00 2001 From: Mayurkumar Patel Date: Fri, 9 Sep 2016 09:10:17 -0500 Subject: PCI: pciehp: Don't re-read Slot Status when queuing hotplug event Previously we read Slot Status to learn about hotplug events, then cleared the events, then re-read Slot Status to find out what happened. But Slot Status might have changed before the second read. Capture the Slot Status once before clearing the events. Also capture the Link Status if we had a link status change. [bhelgaas: changelog, split to separate patch] Tested-by: Lukas Wunner Signed-off-by: Mayurkumar Patel Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 625fa6a..fe99b45 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -566,6 +566,10 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) if (!events) return IRQ_NONE; + /* Capture link status before clearing interrupts */ + if (events & PCI_EXP_SLTSTA_DLLSC) + link = pciehp_check_link_active(ctrl); + pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, events); ctrl_dbg(ctrl, "pending interrupts %#06x from Slot Status\n", events); @@ -598,7 +602,7 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) /* Check Presence Detect Changed */ if (events & PCI_EXP_SLTSTA_PDC) { - pciehp_get_adapter_status(slot, &present); + present = !!(status & PCI_EXP_SLTSTA_PDS); ctrl_info(ctrl, "Card %spresent on Slot(%s)\n", present ? "" : "not ", slot_name(slot)); pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON : @@ -613,7 +617,6 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) } if (events & PCI_EXP_SLTSTA_DLLSC) { - link = pciehp_check_link_active(ctrl); ctrl_info(ctrl, "slot(%s): Link %s event\n", slot_name(slot), link ? "Up" : "Down"); pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP : -- cgit v0.10.2 From 69bd3c5b28e7c988886efb3c9a7614a612eef45d Mon Sep 17 00:00:00 2001 From: Mayurkumar Patel Date: Tue, 23 Aug 2016 08:58:51 +0000 Subject: PCI: pciehp: Don't re-read Slot Status when handling surprise event Previously we read Slot Status when handling a surprise event. But Slot Status might have changed since we identified the event, and the event_type already tells us whether to enable or disable the slot, so there's no need to read it again. Remove handle_surprise_event() and queue the power work directly. [bhelgaas: changelog] Tested-by: Lukas Wunner Signed-off-by: Mayurkumar Patel Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg Acked-by: Rajat Jain diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index 7ea3e61..a787684 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -302,20 +302,6 @@ static void handle_button_press_event(struct slot *p_slot) /* * Note: This function must be called with slot->lock held */ -static void handle_surprise_event(struct slot *p_slot) -{ - u8 getstatus; - - pciehp_get_adapter_status(p_slot, &getstatus); - if (!getstatus) - pciehp_queue_power_work(p_slot, DISABLE_REQ); - else - pciehp_queue_power_work(p_slot, ENABLE_REQ); -} - -/* - * Note: This function must be called with slot->lock held - */ static void handle_link_event(struct slot *p_slot, u32 event) { struct controller *ctrl = p_slot->ctrl; @@ -378,14 +364,14 @@ static void interrupt_event_handler(struct work_struct *work) pciehp_green_led_off(p_slot); break; case INT_PRESENCE_ON: - handle_surprise_event(p_slot); + pciehp_queue_power_work(p_slot, ENABLE_REQ); break; case INT_PRESENCE_OFF: /* * Regardless of surprise capability, we need to * definitely remove a card that has been pulled out! */ - handle_surprise_event(p_slot); + pciehp_queue_power_work(p_slot, DISABLE_REQ); break; case INT_LINK_UP: case INT_LINK_DOWN: -- cgit v0.10.2 From 4947793916e31ec0c4c56f979e2aff89d15480bf Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 8 Sep 2016 15:15:24 -0500 Subject: PCI: pciehp: Remove unnecessary guard In pcie_isr(), we return early if no status bits other than PCI_EXP_SLTSTA_CC are set. This was introduced by dbd79aed1aea ("pciehp: fix NULL dereference in interrupt handler"), but it is no longer necessary because all the subsequent pcie_isr() code is already predicated on a status bit being set. Remove the unnecessary test for ~PCI_EXP_SLTSTA_CC. No functional change intended. Tested-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index fe99b45..60e1d55 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -590,9 +590,6 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) } } - if (!(events & ~PCI_EXP_SLTSTA_CC)) - return IRQ_HANDLED; - /* Check Attention Button Pressed */ if (events & PCI_EXP_SLTSTA_ABP) { ctrl_info(ctrl, "Button pressed on Slot(%s)\n", -- cgit v0.10.2 From 6e49b304e379f93c8aa7ebb164628aec1209f371 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 8 Sep 2016 15:19:58 -0500 Subject: PCI: pciehp: Clean up dmesg "Slot(%s)" messages Print slot name consistently as "Slot(%s)". I don't know whether that's ideal, but we can at least do it the same way all the time. No functional change intended. Tested-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index a787684..bf50f26 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -106,7 +106,7 @@ static int board_added(struct slot *p_slot) /* Check for a power fault */ if (ctrl->power_fault_detected || pciehp_query_power_fault(p_slot)) { - ctrl_err(ctrl, "Power fault on slot %s\n", slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(p_slot)); retval = -EIO; goto err_exit; } @@ -254,11 +254,11 @@ static void handle_button_press_event(struct slot *p_slot) pciehp_get_power_status(p_slot, &getstatus); if (getstatus) { p_slot->state = BLINKINGOFF_STATE; - ctrl_info(ctrl, "PCI slot #%s - powering off due to button press\n", + ctrl_info(ctrl, "Slot(%s): Powering off due to button press\n", slot_name(p_slot)); } else { p_slot->state = BLINKINGON_STATE; - ctrl_info(ctrl, "PCI slot #%s - powering on due to button press\n", + ctrl_info(ctrl, "Slot(%s) Powering on due to button press\n", slot_name(p_slot)); } /* blink green LED and turn off amber */ @@ -273,14 +273,14 @@ static void handle_button_press_event(struct slot *p_slot) * press the attention again before the 5 sec. limit * expires to cancel hot-add or hot-remove */ - ctrl_info(ctrl, "Button cancel on Slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): Button cancel\n", slot_name(p_slot)); cancel_delayed_work(&p_slot->work); if (p_slot->state == BLINKINGOFF_STATE) pciehp_green_led_on(p_slot); else pciehp_green_led_off(p_slot); pciehp_set_attention_status(p_slot, 0); - ctrl_info(ctrl, "PCI slot #%s - action canceled due to button press\n", + ctrl_info(ctrl, "Slot(%s): Action canceled due to button press\n", slot_name(p_slot)); p_slot->state = STATIC_STATE; break; @@ -291,10 +291,12 @@ static void handle_button_press_event(struct slot *p_slot) * this means that the previous attention button action * to hot-add or hot-remove is undergoing */ - ctrl_info(ctrl, "Button ignore on Slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): Button ignored\n", + slot_name(p_slot)); break; default: - ctrl_warn(ctrl, "ignoring invalid state %#x\n", p_slot->state); + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } } @@ -317,31 +319,27 @@ static void handle_link_event(struct slot *p_slot, u32 event) break; case POWERON_STATE: if (event == INT_LINK_UP) { - ctrl_info(ctrl, - "Link Up event ignored on slot(%s): already powering on\n", + ctrl_info(ctrl, "Slot(%s): Link Up event ignored; already powering on\n", slot_name(p_slot)); } else { - ctrl_info(ctrl, - "Link Down event queued on slot(%s): currently getting powered on\n", + ctrl_info(ctrl, "Slot(%s): Link Down event queued; currently getting powered on\n", slot_name(p_slot)); pciehp_queue_power_work(p_slot, DISABLE_REQ); } break; case POWEROFF_STATE: if (event == INT_LINK_UP) { - ctrl_info(ctrl, - "Link Up event queued on slot(%s): currently getting powered off\n", + ctrl_info(ctrl, "Slot(%s): Link Up event queued; currently getting powered off\n", slot_name(p_slot)); pciehp_queue_power_work(p_slot, ENABLE_REQ); } else { - ctrl_info(ctrl, - "Link Down event ignored on slot(%s): already powering off\n", + ctrl_info(ctrl, "Slot(%s): Link Down event ignored; already powering off\n", slot_name(p_slot)); } break; default: - ctrl_err(ctrl, "ignoring invalid state %#x on slot(%s)\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Ignoring invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } } @@ -396,13 +394,13 @@ int pciehp_enable_slot(struct slot *p_slot) pciehp_get_adapter_status(p_slot, &getstatus); if (!getstatus) { - ctrl_info(ctrl, "No adapter on slot(%s)\n", slot_name(p_slot)); + ctrl_info(ctrl, "Slot(%s): No adapter\n", slot_name(p_slot)); return -ENODEV; } if (MRL_SENS(p_slot->ctrl)) { pciehp_get_latch_status(p_slot, &getstatus); if (getstatus) { - ctrl_info(ctrl, "Latch open on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Latch open\n", slot_name(p_slot)); return -ENODEV; } @@ -411,7 +409,7 @@ int pciehp_enable_slot(struct slot *p_slot) if (POWER_CTRL(p_slot->ctrl)) { pciehp_get_power_status(p_slot, &getstatus); if (getstatus) { - ctrl_info(ctrl, "Already enabled on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Already enabled\n", slot_name(p_slot)); return -EINVAL; } @@ -440,7 +438,7 @@ int pciehp_disable_slot(struct slot *p_slot) if (POWER_CTRL(p_slot->ctrl)) { pciehp_get_power_status(p_slot, &getstatus); if (!getstatus) { - ctrl_info(ctrl, "Already disabled on slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Already disabled\n", slot_name(p_slot)); return -EINVAL; } @@ -468,17 +466,17 @@ int pciehp_sysfs_enable_slot(struct slot *p_slot) p_slot->state = STATIC_STATE; break; case POWERON_STATE: - ctrl_info(ctrl, "Slot %s is already in powering on state\n", + ctrl_info(ctrl, "Slot(%s): Already in powering on state\n", slot_name(p_slot)); break; case BLINKINGOFF_STATE: case POWEROFF_STATE: - ctrl_info(ctrl, "Already enabled on slot %s\n", + ctrl_info(ctrl, "Slot(%s): Already enabled\n", slot_name(p_slot)); break; default: - ctrl_err(ctrl, "invalid state %#x on slot %s\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } mutex_unlock(&p_slot->lock); @@ -505,17 +503,17 @@ int pciehp_sysfs_disable_slot(struct slot *p_slot) p_slot->state = STATIC_STATE; break; case POWEROFF_STATE: - ctrl_info(ctrl, "Slot %s is already in powering off state\n", + ctrl_info(ctrl, "Slot(%s): Already in powering off state\n", slot_name(p_slot)); break; case BLINKINGON_STATE: case POWERON_STATE: - ctrl_info(ctrl, "Already disabled on slot %s\n", + ctrl_info(ctrl, "Slot(%s): Already disabled\n", slot_name(p_slot)); break; default: - ctrl_err(ctrl, "invalid state %#x on slot %s\n", - p_slot->state, slot_name(p_slot)); + ctrl_err(ctrl, "Slot(%s): Invalid state %#x\n", + slot_name(p_slot), p_slot->state); break; } mutex_unlock(&p_slot->lock); diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 60e1d55..4582fdf 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -592,7 +592,7 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) /* Check Attention Button Pressed */ if (events & PCI_EXP_SLTSTA_ABP) { - ctrl_info(ctrl, "Button pressed on Slot(%s)\n", + ctrl_info(ctrl, "Slot(%s): Attention button pressed\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_BUTTON_PRESS); } @@ -600,8 +600,8 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) /* Check Presence Detect Changed */ if (events & PCI_EXP_SLTSTA_PDC) { present = !!(status & PCI_EXP_SLTSTA_PDS); - ctrl_info(ctrl, "Card %spresent on Slot(%s)\n", - present ? "" : "not ", slot_name(slot)); + ctrl_info(ctrl, "Slot(%s): Card %spresent\n", slot_name(slot), + present ? "" : "not "); pciehp_queue_interrupt_event(slot, present ? INT_PRESENCE_ON : INT_PRESENCE_OFF); } @@ -609,13 +609,13 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id) /* Check Power Fault Detected */ if ((events & PCI_EXP_SLTSTA_PFD) && !ctrl->power_fault_detected) { ctrl->power_fault_detected = 1; - ctrl_err(ctrl, "Power fault on slot %s\n", slot_name(slot)); + ctrl_err(ctrl, "Slot(%s): Power fault\n", slot_name(slot)); pciehp_queue_interrupt_event(slot, INT_POWER_FAULT); } if (events & PCI_EXP_SLTSTA_DLLSC) { - ctrl_info(ctrl, "slot(%s): Link %s event\n", - slot_name(slot), link ? "Up" : "Down"); + ctrl_info(ctrl, "Slot(%s): Link %s\n", slot_name(slot), + link ? "Up" : "Down"); pciehp_queue_interrupt_event(slot, link ? INT_LINK_UP : INT_LINK_DOWN); } -- cgit v0.10.2 From 29a654e59f3698d70d85e289fc5ce7261493bba2 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 7 Sep 2016 17:50:30 -0500 Subject: PCI: pciehp: Remove useless pciehp_get_latch_status() calls Long ago, we updated a "switch_save" field based on the latch status. But switch_save was unused, and ed6cbcf2ac70 ("[PATCH] pciehp: miscellaneous cleanups") removed it. We no longer use the latch status, so remove calls to pciehp_get_latch_status(). No functional change intended. Tested-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index bf50f26..efe69e8 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -389,7 +389,6 @@ static void interrupt_event_handler(struct work_struct *work) int pciehp_enable_slot(struct slot *p_slot) { u8 getstatus = 0; - int rc; struct controller *ctrl = p_slot->ctrl; pciehp_get_adapter_status(p_slot, &getstatus); @@ -415,13 +414,7 @@ int pciehp_enable_slot(struct slot *p_slot) } } - pciehp_get_latch_status(p_slot, &getstatus); - - rc = board_added(p_slot); - if (rc) - pciehp_get_latch_status(p_slot, &getstatus); - - return rc; + return board_added(p_slot); } /* -- cgit v0.10.2 From 9ff25e6b3eb0012297288dfa87930c7b62ef6ab1 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 6 Sep 2016 16:09:19 -0500 Subject: PCI/AER: Remove aerdriver.nosourceid kernel parameter The aerdriver.nosourceid kernel parameter was intended for working around broken chipsets don't supply the source ID for AER events. We recently added PCI_BUS_FLAGS_NO_AERSID, which can be set by quirks for the same purpose. Remove the aerdriver.nosourceid kernel parameter. For anything other than debugging, asking users to find and use kernel parameters is a poor user experience. Instead, we should add PCI_BUS_FLAGS_NO_AERSID quirks for any hardware that needs it. Signed-off-by: Bjorn Helgaas diff --git a/Documentation/PCI/pcieaer-howto.txt b/Documentation/PCI/pcieaer-howto.txt index b4987c0..4956df3 100644 --- a/Documentation/PCI/pcieaer-howto.txt +++ b/Documentation/PCI/pcieaer-howto.txt @@ -61,10 +61,6 @@ be initiated although firmwares have no _OSC support. To enable the walkaround, pls. add aerdriver.forceload=y to kernel boot parameter line when booting kernel. Note that forceload=n by default. -nosourceid, another parameter of type bool, can be used when broken -hardware (mostly chipsets) has root ports that cannot obtain the reporting -source ID. nosourceid=n by default. - 2.3 AER error output When a PCI-E AER error is captured, an error message will be outputted to console. If it's a correctable error, it is outputted as a warning. diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index 8f5e14c..f8a9b17 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -28,9 +28,7 @@ #include "aerdrv.h" static bool forceload; -static bool nosourceid; module_param(forceload, bool, 0); -module_param(nosourceid, bool, 0); #define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) @@ -132,8 +130,7 @@ static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) * When bus id is equal to 0, it might be a bad id * reported by root port. */ - if (!nosourceid && - (PCI_BUS_NUM(e_info->id) != 0) && + if ((PCI_BUS_NUM(e_info->id) != 0) && !(dev->bus->bus_flags & PCI_BUS_FLAGS_NO_AERSID)) { /* Device ID match? */ if (e_info->id == ((dev->bus->number << 8) | dev->devfn)) @@ -146,11 +143,10 @@ static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) /* * When either - * 1) nosourceid==y; - * 2) bus id is equal to 0. Some ports might lose the bus + * 1) bus id is equal to 0. Some ports might lose the bus * id of error source id; - * 3) bus flag PCI_BUS_FLAGS_NO_AERSID is set - * 4) There are multiple errors and prior ID comparing fails; + * 2) bus flag PCI_BUS_FLAGS_NO_AERSID is set + * 3) There are multiple errors and prior ID comparing fails; * We check AER status registers to find possible reporter. */ if (atomic_read(&dev->enable_cnt) == 0) -- cgit v0.10.2 From 7ece14175376051b18a9b97f0e6125cb8b864155 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 6 Sep 2016 16:24:37 -0500 Subject: PCI/AER: Remove aerdriver.forceload kernel parameter Per the PCI Firmware spec, r3.0, sec 4.5.1, on ACPI systems, the OS must not use AER unless _OSC is present and _OSC grants AER control to the OS. The aerdriver.forceload kernel parameter was a way to enable Linux AER support on ACPI systems that lack _OSC or fail to grant control the the OS. Enabling Linux AER support when the firmware doesn't want us to is a recipe for problems, e.g., the firmware might be handling AER itself. Remove the aerdriver.forceload kernel parameter and related supporting code. Signed-off-by: Bjorn Helgaas diff --git a/Documentation/PCI/pcieaer-howto.txt b/Documentation/PCI/pcieaer-howto.txt index 4956df3..ea8cafb 100644 --- a/Documentation/PCI/pcieaer-howto.txt +++ b/Documentation/PCI/pcieaer-howto.txt @@ -49,21 +49,17 @@ depends on CONFIG_PCIEPORTBUS, so pls. set CONFIG_PCIEPORTBUS=y and CONFIG_PCIEAER = y. 2.2 Load PCI Express AER Root Driver -There is a case where a system has AER support in BIOS. Enabling the AER -Root driver and having AER support in BIOS may result unpredictable -behavior. To avoid this conflict, a successful load of the AER Root driver -requires ACPI _OSC support in the BIOS to allow the AER Root driver to -request for native control of AER. See the PCI FW 3.0 Specification for -details regarding OSC usage. Currently, lots of firmwares don't provide -_OSC support while they use PCI Express. To support such firmwares, -forceload, a parameter of type bool, could enable AER to continue to -be initiated although firmwares have no _OSC support. To enable the -walkaround, pls. add aerdriver.forceload=y to kernel boot parameter line -when booting kernel. Note that forceload=n by default. + +Some systems have AER support in firmware. Enabling Linux AER support at +the same time the firmware handles AER may result in unpredictable +behavior. Therefore, Linux does not handle AER events unless the firmware +grants AER control to the OS via the ACPI _OSC method. See the PCI FW 3.0 +Specification for details regarding _OSC usage. 2.3 AER error output -When a PCI-E AER error is captured, an error message will be outputted to -console. If it's a correctable error, it is outputted as a warning. + +When a PCIe AER error is captured, an error message will be output to +console. If it's a correctable error, it is output as a warning. Otherwise, it is printed as an error. So users could choose different log level to filter out correctable error messages. diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c index 48d21e0..08ce257 100644 --- a/drivers/pci/pcie/aer/aerdrv.c +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -70,7 +70,7 @@ static int pcie_aer_disable; void pci_no_aer(void) { - pcie_aer_disable = 1; /* has priority over 'forceload' */ + pcie_aer_disable = 1; } bool pci_aer_available(void) @@ -304,11 +304,6 @@ static int aer_probe(struct pcie_device *dev) struct aer_rpc *rpc; struct device *device = &dev->device; - /* Init */ - status = aer_init(dev); - if (status) - return status; - /* Alloc rpc data structure */ rpc = aer_alloc_rpc(dev); if (!rpc) { diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h index 945c939..f15ca8d 100644 --- a/drivers/pci/pcie/aer/aerdrv.h +++ b/drivers/pci/pcie/aer/aerdrv.h @@ -105,7 +105,6 @@ static inline pci_ers_result_t merge_result(enum pci_ers_result orig, } extern struct bus_type pcie_port_bus_type; -int aer_init(struct pcie_device *dev); void aer_isr(struct work_struct *work); void aer_print_error(struct pci_dev *dev, struct aer_err_info *info); void aer_print_port_info(struct pci_dev *dev, struct aer_err_info *info); @@ -121,11 +120,4 @@ static inline int pcie_aer_get_firmware_first(struct pci_dev *pci_dev) return 0; } #endif - -static inline void pcie_aer_force_firmware_first(struct pci_dev *pci_dev, - int enable) -{ - pci_dev->__aer_firmware_first = !!enable; - pci_dev->__aer_firmware_first_valid = 1; -} #endif /* _AERDRV_H_ */ diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index f8a9b17..8262527 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -27,9 +27,6 @@ #include #include "aerdrv.h" -static bool forceload; -module_param(forceload, bool, 0); - #define PCI_EXP_AER_FLAGS (PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | \ PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE) @@ -811,19 +808,3 @@ void aer_isr(struct work_struct *work) aer_isr_one_error(p_device, &e_src); mutex_unlock(&rpc->rpc_mutex); } - -/** - * aer_init - provide AER initialization - * @dev: pointer to AER pcie device - * - * Invoked when AER service driver is loaded. - */ -int aer_init(struct pcie_device *dev) -{ - if (forceload) { - dev_printk(KERN_DEBUG, &dev->device, - "aerdrv forceload requested.\n"); - pcie_aer_force_firmware_first(dev->port, 0); - } - return 0; -} -- cgit v0.10.2 From e3123c20c8075f1771947e10cb7e9681166f2e89 Mon Sep 17 00:00:00 2001 From: Grigory Kletsko Date: Thu, 8 Sep 2016 22:32:59 +0300 Subject: PCI: rcar: Add multi-MSI support Implement the MSI .setup_irqs() method which enables allocation of several MSIs at once. [Sergei Shtylyov: removed unrelated/unneeded changes, fixed too long lines, reordered the variable declarations, reworded the summary/description.] Signed-off-by: Grigory Kletsko Signed-off-by: Sergei Shtylyov Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 45dc3cd..d6e72db 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -608,6 +608,18 @@ static int rcar_msi_alloc(struct rcar_msi *chip) return msi; } +static int rcar_msi_alloc_region(struct rcar_msi *chip, int no_irqs) +{ + int msi; + + mutex_lock(&chip->lock); + msi = bitmap_find_free_region(chip->used, INT_PCI_MSI_NR, + order_base_2(no_irqs)); + mutex_unlock(&chip->lock); + + return msi; +} + static void rcar_msi_free(struct rcar_msi *chip, unsigned long irq) { mutex_lock(&chip->lock); @@ -665,7 +677,7 @@ static int rcar_msi_setup_irq(struct msi_controller *chip, struct pci_dev *pdev, if (hwirq < 0) return hwirq; - irq = irq_create_mapping(msi->domain, hwirq); + irq = irq_find_mapping(msi->domain, hwirq); if (!irq) { rcar_msi_free(msi, hwirq); return -EINVAL; @@ -682,6 +694,58 @@ static int rcar_msi_setup_irq(struct msi_controller *chip, struct pci_dev *pdev, return 0; } +static int rcar_msi_setup_irqs(struct msi_controller *chip, + struct pci_dev *pdev, int nvec, int type) +{ + struct rcar_pcie *pcie = container_of(chip, struct rcar_pcie, msi.chip); + struct rcar_msi *msi = to_rcar_msi(chip); + struct msi_desc *desc; + struct msi_msg msg; + unsigned int irq; + int hwirq; + int i; + + /* MSI-X interrupts are not supported */ + if (type == PCI_CAP_ID_MSIX) + return -EINVAL; + + WARN_ON(!list_is_singular(&pdev->dev.msi_list)); + desc = list_entry(pdev->dev.msi_list.next, struct msi_desc, list); + + hwirq = rcar_msi_alloc_region(msi, nvec); + if (hwirq < 0) + return -ENOSPC; + + irq = irq_find_mapping(msi->domain, hwirq); + if (!irq) + return -ENOSPC; + + for (i = 0; i < nvec; i++) { + /* + * irq_create_mapping() called from rcar_pcie_probe() pre- + * allocates descs, so there is no need to allocate descs here. + * We can therefore assume that if irq_find_mapping() above + * returns non-zero, then the descs are also successfully + * allocated. + */ + if (irq_set_msi_desc_off(irq, i, desc)) { + /* TODO: clear */ + return -EINVAL; + } + } + + desc->nvec_used = nvec; + desc->msi_attrib.multiple = order_base_2(nvec); + + msg.address_lo = rcar_pci_read_reg(pcie, PCIEMSIALR) & ~MSIFE; + msg.address_hi = rcar_pci_read_reg(pcie, PCIEMSIAUR); + msg.data = hwirq; + + pci_write_msi_msg(irq, &msg); + + return 0; +} + static void rcar_msi_teardown_irq(struct msi_controller *chip, unsigned int irq) { struct rcar_msi *msi = to_rcar_msi(chip); @@ -716,12 +780,13 @@ 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; + int err, i; mutex_init(&msi->lock); msi->chip.dev = pcie->dev; msi->chip.setup_irq = rcar_msi_setup_irq; + msi->chip.setup_irqs = rcar_msi_setup_irqs; msi->chip.teardown_irq = rcar_msi_teardown_irq; msi->domain = irq_domain_add_linear(pcie->dev->of_node, INT_PCI_MSI_NR, @@ -731,6 +796,9 @@ static int rcar_pcie_enable_msi(struct rcar_pcie *pcie) return -ENOMEM; } + for (i = 0; i < INT_PCI_MSI_NR; i++) + irq_create_mapping(msi->domain, i); + /* 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 | IRQF_NO_THREAD, -- cgit v0.10.2 From f7bc63802d53139f7e6b62b222f2b26dca3f5e89 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Fri, 9 Sep 2016 01:26:18 +0300 Subject: PCI: rcar: Fix some checkpatch warnings The R-Car PCIe driver causes 13 warnings from scripts/checkpatch.pl -- let's fix at least 10 easier ones: - line over 80 characters; - blank line missing after declarations; - statements not starting on a tabstop. Signed-off-by: Sergei Shtylyov Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index d6e72db..35aee9c 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -931,12 +931,16 @@ static int rcar_pcie_inbound_ranges(struct rcar_pcie *pcie, * Set up 64-bit inbound regions as the range parser doesn't * distinguish between 32 and 64-bit types. */ - rcar_pci_write_reg(pcie, lower_32_bits(pci_addr), PCIEPRAR(idx)); + rcar_pci_write_reg(pcie, lower_32_bits(pci_addr), + PCIEPRAR(idx)); rcar_pci_write_reg(pcie, lower_32_bits(cpu_addr), PCIELAR(idx)); - rcar_pci_write_reg(pcie, lower_32_bits(mask) | flags, PCIELAMR(idx)); + rcar_pci_write_reg(pcie, lower_32_bits(mask) | flags, + PCIELAMR(idx)); - rcar_pci_write_reg(pcie, upper_32_bits(pci_addr), PCIEPRAR(idx+1)); - rcar_pci_write_reg(pcie, upper_32_bits(cpu_addr), PCIELAR(idx+1)); + rcar_pci_write_reg(pcie, upper_32_bits(pci_addr), + PCIEPRAR(idx + 1)); + rcar_pci_write_reg(pcie, upper_32_bits(cpu_addr), + PCIELAR(idx + 1)); rcar_pci_write_reg(pcie, 0, PCIELAMR(idx + 1)); pci_addr += size; @@ -985,6 +989,7 @@ static int rcar_pcie_parse_map_dma_ranges(struct rcar_pcie *pcie, /* 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); @@ -998,9 +1003,12 @@ static int rcar_pcie_parse_map_dma_ranges(struct rcar_pcie *pcie, static const struct of_device_id rcar_pcie_of_match[] = { { .compatible = "renesas,pcie-r8a7779", .data = rcar_pcie_hw_init_h1 }, - { .compatible = "renesas,pcie-rcar-gen2", .data = rcar_pcie_hw_init_gen2 }, - { .compatible = "renesas,pcie-r8a7790", .data = rcar_pcie_hw_init_gen2 }, - { .compatible = "renesas,pcie-r8a7791", .data = rcar_pcie_hw_init_gen2 }, + { .compatible = "renesas,pcie-rcar-gen2", + .data = rcar_pcie_hw_init_gen2 }, + { .compatible = "renesas,pcie-r8a7790", + .data = rcar_pcie_hw_init_gen2 }, + { .compatible = "renesas,pcie-r8a7791", + .data = rcar_pcie_hw_init_gen2 }, { .compatible = "renesas,pcie-r8a7795", .data = rcar_pcie_hw_init }, {}, }; @@ -1013,7 +1021,8 @@ static int rcar_pcie_parse_request_of_pci_ranges(struct rcar_pcie *pci) resource_size_t iobase; struct resource_entry *win; - err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, &iobase); + err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, + &iobase); if (err) return err; @@ -1064,8 +1073,8 @@ static int rcar_pcie_probe(struct platform_device *pdev) return err; } - err = rcar_pcie_parse_map_dma_ranges(pcie, pdev->dev.of_node); - if (err) + 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); -- cgit v0.10.2 From b58ddf17470aeaaed2dde0de7cbfdc88065f1d88 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 9 Sep 2016 09:45:30 +0200 Subject: PCI: artpec6: Drop __init from artpec6_add_pcie_port() artpec6_add_pcie_port() is called from artpec6_pcie_probe(), which is not marked __init. It is wrong to call an __init function from a non-__init one, so remove __init from artpec6_add_pcie_port(). [bhelgaas: changelog] Signed-off-by: Niklas Cassel Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-artpec6.c b/drivers/pci/host/pcie-artpec6.c index 16ba70b7..39bf1a6 100644 --- a/drivers/pci/host/pcie-artpec6.c +++ b/drivers/pci/host/pcie-artpec6.c @@ -191,8 +191,8 @@ static irqreturn_t artpec6_pcie_msi_handler(int irq, void *arg) return dw_handle_msi_irq(pp); } -static int __init artpec6_add_pcie_port(struct pcie_port *pp, - struct platform_device *pdev) +static int artpec6_add_pcie_port(struct pcie_port *pp, + struct platform_device *pdev) { int ret; -- cgit v0.10.2 From c68db51589052ef9adee4dd699462681737849a2 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Mon, 29 Aug 2016 11:19:01 -0600 Subject: x86/PCI: VMD: Allocate IRQ lists with correct MSI-X count To reduce the amount of memory required for IRQ lists, only allocate their space after calling pci_msix_enable_range() which may reduce the number of MSI-X vectors allocated. Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas Reviewed-by: Keith Busch diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index b814ca6..7a8538d 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -671,11 +671,6 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) if (vmd->msix_count < 0) return -ENODEV; - vmd->irqs = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->irqs), - GFP_KERNEL); - if (!vmd->irqs) - return -ENOMEM; - vmd->msix_entries = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->msix_entries), GFP_KERNEL); @@ -689,6 +684,11 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) if (vmd->msix_count < 0) return vmd->msix_count; + vmd->irqs = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->irqs), + GFP_KERNEL); + if (!vmd->irqs) + return -ENOMEM; + for (i = 0; i < vmd->msix_count; i++) { INIT_LIST_HEAD(&vmd->irqs[i].irq_list); vmd->irqs[i].vmd_vector = vmd->msix_entries[i].vector; -- cgit v0.10.2 From 75de9b4cd3119eaddce6616776f62a5b30fb0fae Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Mon, 29 Aug 2016 11:19:02 -0600 Subject: x86/PCI: VMD: Convert to use pci_alloc_irq_vectors() API Convert to use the pci_alloc_irq_vectors() API. Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas Reviewed-by: Keith Busch diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index 7a8538d..f576dcb 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -76,7 +76,6 @@ struct vmd_dev { char __iomem *cfgbar; int msix_count; - struct msix_entry *msix_entries; struct vmd_irq_list *irqs; struct pci_sysdata sysdata; @@ -671,16 +670,8 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) if (vmd->msix_count < 0) return -ENODEV; - vmd->msix_entries = devm_kcalloc(&dev->dev, vmd->msix_count, - sizeof(*vmd->msix_entries), - GFP_KERNEL); - if (!vmd->msix_entries) - return -ENOMEM; - for (i = 0; i < vmd->msix_count; i++) - vmd->msix_entries[i].entry = i; - - vmd->msix_count = pci_enable_msix_range(vmd->dev, vmd->msix_entries, 1, - vmd->msix_count); + vmd->msix_count = pci_alloc_irq_vectors(dev, 1, vmd->msix_count, + PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); if (vmd->msix_count < 0) return vmd->msix_count; @@ -691,7 +682,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) for (i = 0; i < vmd->msix_count; i++) { INIT_LIST_HEAD(&vmd->irqs[i].irq_list); - vmd->irqs[i].vmd_vector = vmd->msix_entries[i].vector; + vmd->irqs[i].vmd_vector = pci_irq_vector(dev, i); vmd->irqs[i].index = i; err = devm_request_irq(&dev->dev, vmd->irqs[i].vmd_vector, -- cgit v0.10.2 From 53db86adc2c9b3ce0454bece4487e8eca96e2614 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Fri, 2 Sep 2016 11:53:04 -0600 Subject: x86/PCI: VMD: Eliminate vmd_vector member from list type Eliminate unused vmd and vector members from vmd_irq_list and discover the vector using pci_irq_vector(). Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas Acked-by: Keith Busch diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index f576dcb..61a97ff 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -56,15 +56,12 @@ struct vmd_irq { /** * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector * @irq_list: the list of irq's the VMD one demuxes to. - * @vmd_vector: the h/w IRQ assigned to the VMD. * @index: index into the VMD MSI-X table; used for message routing. * @count: number of child IRQs assigned to this vector; used to track * sharing. */ struct vmd_irq_list { struct list_head irq_list; - struct vmd_dev *vmd; - unsigned int vmd_vector; unsigned int index; unsigned int count; }; @@ -201,8 +198,10 @@ static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info, vmdirq->irq = vmd_next_irq(vmd, desc); vmdirq->virq = virq; - irq_domain_set_info(domain, virq, vmdirq->irq->vmd_vector, info->chip, - vmdirq, handle_untracked_irq, vmd, NULL); + irq_domain_set_info(domain, virq, + pci_irq_vector(vmd->dev, vmdirq->irq->index), + info->chip, vmdirq, + handle_untracked_irq, vmd, NULL); return 0; } @@ -682,10 +681,8 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) for (i = 0; i < vmd->msix_count; i++) { INIT_LIST_HEAD(&vmd->irqs[i].irq_list); - vmd->irqs[i].vmd_vector = pci_irq_vector(dev, i); vmd->irqs[i].index = i; - - err = devm_request_irq(&dev->dev, vmd->irqs[i].vmd_vector, + err = devm_request_irq(&dev->dev, pci_irq_vector(dev, i), vmd_irq, 0, "vmd", &vmd->irqs[i]); if (err) return err; -- cgit v0.10.2 From b31822277abcd7c83d1c1c0af876da9ccdf3b7d6 Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Fri, 2 Sep 2016 11:53:05 -0600 Subject: x86/PCI: VMD: Eliminate index member from IRQ list Use math to discover the IRQ list index number relative to the IRQ list head. Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas Acked-by: Keith Busch diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index 61a97ff..e785907 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -56,13 +56,11 @@ struct vmd_irq { /** * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector * @irq_list: the list of irq's the VMD one demuxes to. - * @index: index into the VMD MSI-X table; used for message routing. * @count: number of child IRQs assigned to this vector; used to track * sharing. */ struct vmd_irq_list { struct list_head irq_list; - unsigned int index; unsigned int count; }; @@ -91,6 +89,12 @@ static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) return container_of(bus->sysdata, struct vmd_dev, sysdata); } +static inline unsigned int index_from_irqs(struct vmd_dev *vmd, + struct vmd_irq_list *irqs) +{ + return irqs - vmd->irqs; +} + /* * Drivers managing a device in a VMD domain allocate their own IRQs as before, * but the MSI entry for the hardware it's driving will be programmed with a @@ -103,9 +107,11 @@ static void vmd_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) { struct vmd_irq *vmdirq = data->chip_data; struct vmd_irq_list *irq = vmdirq->irq; + struct vmd_dev *vmd = irq_data_get_irq_handler_data(data); msg->address_hi = MSI_ADDR_BASE_HI; - msg->address_lo = MSI_ADDR_BASE_LO | MSI_ADDR_DEST_ID(irq->index); + msg->address_lo = MSI_ADDR_BASE_LO | + MSI_ADDR_DEST_ID(index_from_irqs(vmd, irq)); msg->data = 0; } @@ -190,6 +196,7 @@ static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info, struct msi_desc *desc = arg->desc; struct vmd_dev *vmd = vmd_from_bus(msi_desc_to_pci_dev(desc)->bus); struct vmd_irq *vmdirq = kzalloc(sizeof(*vmdirq), GFP_KERNEL); + unsigned int index, vector; if (!vmdirq) return -ENOMEM; @@ -197,10 +204,10 @@ static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info, INIT_LIST_HEAD(&vmdirq->node); vmdirq->irq = vmd_next_irq(vmd, desc); vmdirq->virq = virq; + index = index_from_irqs(vmd, vmdirq->irq); + vector = pci_irq_vector(vmd->dev, index); - irq_domain_set_info(domain, virq, - pci_irq_vector(vmd->dev, vmdirq->irq->index), - info->chip, vmdirq, + irq_domain_set_info(domain, virq, vector, info->chip, vmdirq, handle_untracked_irq, vmd, NULL); return 0; } @@ -681,7 +688,6 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) for (i = 0; i < vmd->msix_count; i++) { INIT_LIST_HEAD(&vmd->irqs[i].irq_list); - vmd->irqs[i].index = i; err = devm_request_irq(&dev->dev, pci_irq_vector(dev, i), vmd_irq, 0, "vmd", &vmd->irqs[i]); if (err) -- cgit v0.10.2 From ee6ee49fd09fa17c92aadf07961d0ff406fceab8 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Thu, 4 Aug 2016 16:09:09 -0600 Subject: x86/PCI: VMD: Synchronize with RCU freeing MSI IRQ descs Fix a potential race when disabling MSI/MSI-X on a VMD domain device. If the VMD interrupt service is running, it may see a disabled IRQ. We can synchronize RCU just before freeing the MSI descriptor. This is safe since the irq_desc lock isn't held, and the descriptor is valid even though it is disabled. After vmd_msi_free(), though, the handler is reinitialized to handle_bad_irq(), so we can't let the VMD ISR's list iteration see the disabled IRQ after this. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas Acked-by Jon Derrick: diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index e785907..5705852 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -218,6 +218,8 @@ static void vmd_msi_free(struct irq_domain *domain, struct vmd_irq *vmdirq = irq_get_chip_data(virq); unsigned long flags; + synchronize_rcu(); + /* XXX: Potential optimization to rebalance */ raw_spin_lock_irqsave(&list_lock, flags); vmdirq->irq->count--; -- cgit v0.10.2 From 95c35491f663962e476179076d24d0d2c45a8fb5 Mon Sep 17 00:00:00 2001 From: Tyler Baicar Date: Wed, 14 Sep 2016 15:14:45 -0600 Subject: PCI/AER: Remove duplicate AER severity translation Currently the AER severity is being translated twice in the code flow for PCIe errors. It is first translated in ghes_do_proc() before calling into the AER driver. Then it is translated again when the AER driver calls cper_print_aer(). This causes the severity that is used in cper_print_aer() to be incorrect. Remove the second translation that is in cper_print_aer() since this function is already receiving the correct AER severity. Signed-off-by: Tyler Baicar Signed-off-by: Bjorn Helgaas Reviewed-by: Borislav Petkov diff --git a/drivers/pci/pcie/aer/aerdrv_errprint.c b/drivers/pci/pcie/aer/aerdrv_errprint.c index 167fe41..54c4b69 100644 --- a/drivers/pci/pcie/aer/aerdrv_errprint.c +++ b/drivers/pci/pcie/aer/aerdrv_errprint.c @@ -219,15 +219,13 @@ int cper_severity_to_aer(int cper_severity) } EXPORT_SYMBOL_GPL(cper_severity_to_aer); -void cper_print_aer(struct pci_dev *dev, int cper_severity, +void cper_print_aer(struct pci_dev *dev, int aer_severity, struct aer_capability_regs *aer) { - int aer_severity, layer, agent, status_strs_size, tlp_header_valid = 0; + int layer, agent, status_strs_size, tlp_header_valid = 0; u32 status, mask; const char **status_strs; - aer_severity = cper_severity_to_aer(cper_severity); - if (aer_severity == AER_CORRECTABLE) { status = aer->cor_status; mask = aer->cor_mask; diff --git a/include/linux/aer.h b/include/linux/aer.h index 1640493..04602cb 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -63,7 +63,7 @@ static inline int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) } #endif -void cper_print_aer(struct pci_dev *dev, int cper_severity, +void cper_print_aer(struct pci_dev *dev, int aer_severity, struct aer_capability_regs *aer); int cper_severity_to_aer(int cper_severity); void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, -- cgit v0.10.2 From 2458d66b245e39786bc5b51062e6d30aa5ad0282 Mon Sep 17 00:00:00 2001 From: Tyler Baicar Date: Wed, 14 Sep 2016 15:14:46 -0600 Subject: ACPI / APEI: Send correct severity to calculate AER severity Currently the AER severity is calculated by calling cper_severity_to_aer(), but the parameter sent is actually the GHES severity. This causes the AER severity to be incorrect. Fix the parameter to be the CPER severity instead of the GHES severity. Signed-off-by: Tyler Baicar Signed-off-by: Bjorn Helgaas Reviewed-by: Borislav Petkov diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 60746ef..f0a029e 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -457,7 +457,7 @@ static void ghes_do_proc(struct ghes *ghes, devfn = PCI_DEVFN(pcie_err->device_id.device, pcie_err->device_id.function); - aer_severity = cper_severity_to_aer(sev); + aer_severity = cper_severity_to_aer(gdata->error_severity); /* * If firmware reset the component to contain -- cgit v0.10.2 From 576243b3f9eaa47ab568ac49574b3a095c2365f1 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 13 Sep 2016 10:31:59 -0600 Subject: PCI: pciehp: Allow exclusive userspace control of indicators PCIe hotplug supports optional Attention and Power Indicators, which are used internally by pciehp. Users can't control the Power Indicator, but they can control the Attention Indicator by writing to a sysfs "attention" file. The Slot Control register has two bits for each indicator, and the PCIe spec defines the encodings for each as (Reserved/On/Blinking/Off). For sysfs "attention" writes, pciehp_set_attention_status() maps into these encodings, so the only useful write values are 0 (Off), 1 (On), and 2 (Blinking). However, some platforms use all four bits for platform-specific indicators, and they need to allow direct user control of them while preventing pciehp from using them at all. Add a "hotplug_user_indicators" flag to the pci_dev structure. When set, pciehp does not use either the Attention Indicator or the Power Indicator, and the low four bits (values 0x0 - 0xf) of sysfs "attention" write values are written directly to the Attention Indicator Control and Power Indicator Control fields. [bhelgaas: changelog, rename flag and accessors to s/attention/indicator/] Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h index e764918..37d70b5 100644 --- a/drivers/pci/hotplug/pciehp.h +++ b/drivers/pci/hotplug/pciehp.h @@ -152,6 +152,9 @@ bool pciehp_check_link_active(struct controller *ctrl); void pciehp_release_ctrl(struct controller *ctrl); int pciehp_reset_slot(struct slot *slot, int probe); +int pciehp_set_raw_indicator_status(struct hotplug_slot *h_slot, u8 status); +int pciehp_get_raw_indicator_status(struct hotplug_slot *h_slot, u8 *status); + static inline const char *slot_name(struct slot *slot) { return hotplug_slot_name(slot->hotplug_slot); diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c index ac531e6..276e39c 100644 --- a/drivers/pci/hotplug/pciehp_core.c +++ b/drivers/pci/hotplug/pciehp_core.c @@ -114,6 +114,9 @@ static int init_slot(struct controller *ctrl) if (ATTN_LED(ctrl)) { ops->get_attention_status = get_attention_status; ops->set_attention_status = set_attention_status; + } else if (ctrl->pcie->port->hotplug_user_indicators) { + ops->get_attention_status = pciehp_get_raw_indicator_status; + ops->set_attention_status = pciehp_set_raw_indicator_status; } /* register this slot with the hotplug pci core */ diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 4582fdf..b57fc6d 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -355,6 +355,18 @@ static int pciehp_link_enable(struct controller *ctrl) return __pciehp_link_set(ctrl, true); } +int pciehp_get_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 *status) +{ + struct slot *slot = hotplug_slot->private; + struct pci_dev *pdev = ctrl_dev(slot->ctrl); + u16 slot_ctrl; + + pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl); + *status = (slot_ctrl & (PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC)) >> 6; + return 0; +} + void pciehp_get_attention_status(struct slot *slot, u8 *status) { struct controller *ctrl = slot->ctrl; @@ -431,6 +443,17 @@ int pciehp_query_power_fault(struct slot *slot) return !!(slot_status & PCI_EXP_SLTSTA_PFD); } +int pciehp_set_raw_indicator_status(struct hotplug_slot *hotplug_slot, + u8 status) +{ + struct slot *slot = hotplug_slot->private; + struct controller *ctrl = slot->ctrl; + + pcie_write_cmd_nowait(ctrl, status << 6, + PCI_EXP_SLTCTL_AIC | PCI_EXP_SLTCTL_PIC); + return 0; +} + void pciehp_set_attention_status(struct slot *slot, u8 value) { struct controller *ctrl = slot->ctrl; @@ -814,6 +837,10 @@ struct controller *pcie_init(struct pcie_device *dev) } ctrl->pcie = dev; pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, &slot_cap); + + if (pdev->hotplug_user_indicators) + slot_cap &= ~(PCI_EXP_SLTCAP_AIP | PCI_EXP_SLTCAP_PIP); + ctrl->slot_cap = slot_cap; mutex_init(&ctrl->ctrl_lock); init_waitqueue_head(&ctrl->queue); diff --git a/include/linux/pci.h b/include/linux/pci.h index 2599a98..c81fbf7 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -308,6 +308,9 @@ struct pci_dev { powered on/off by the corresponding bridge */ unsigned int ignore_hotplug:1; /* Ignore hotplug events */ + unsigned int hotplug_user_indicators:1; /* SlotCtl indicators + controlled exclusively by + user sysfs */ unsigned int d3_delay; /* D3->D0 transition time in ms */ unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ -- cgit v0.10.2 From 3161832d58c7f3bf8b190a2887086be0932d8dd3 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 13 Sep 2016 09:05:40 -0600 Subject: x86/PCI: VMD: Request userspace control of PCIe hotplug indicators Add set_dev_domain_options() to set PCI domain-specific options as devices are added. The first usage is to request exclusive userspace control of PCIe hotplug indicators in VMD domains. Devices in a VMD domain use PCIe hotplug Attention and Power Indicators in a non-standard way; tell pciehp to ignore the indicators so userspace can control them via the sysfs "attention" file. To determine whether a bus is within a VMD domain, add a bool to the pci_sysdata structure that the VMD driver sets during initialization. [bhelgaas: changelog] Requested-by: Kapil Karkra Tested-by: Artur Paszkiewicz Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas diff --git a/arch/x86/include/asm/pci.h b/arch/x86/include/asm/pci.h index 9ab7507..1411dbe 100644 --- a/arch/x86/include/asm/pci.h +++ b/arch/x86/include/asm/pci.h @@ -23,6 +23,9 @@ struct pci_sysdata { #ifdef CONFIG_PCI_MSI_IRQ_DOMAIN void *fwnode; /* IRQ domain for MSI assignment */ #endif +#if IS_ENABLED(CONFIG_VMD) + bool vmd_domain; /* True if in Intel VMD domain */ +#endif }; extern int pci_routeirq; @@ -56,6 +59,17 @@ static inline void *_pci_root_bus_fwnode(struct pci_bus *bus) #define pci_root_bus_fwnode _pci_root_bus_fwnode #endif +static inline bool is_vmd(struct pci_bus *bus) +{ +#if IS_ENABLED(CONFIG_VMD) + struct pci_sysdata *sd = bus->sysdata; + + return sd->vmd_domain; +#else + return false; +#endif +} + /* Can be used to override the logic in pci_scan_bus for skipping already-configured bus numbers - to be used for buggy BIOSes or architectures with incomplete PCI setup by the loader */ diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index 7b6a9d1..a4fdfa7 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -677,6 +677,12 @@ static void set_dma_domain_ops(struct pci_dev *pdev) static void set_dma_domain_ops(struct pci_dev *pdev) {} #endif +static void set_dev_domain_options(struct pci_dev *pdev) +{ + if (is_vmd(pdev->bus)) + pdev->hotplug_user_indicators = 1; +} + int pcibios_add_device(struct pci_dev *dev) { struct setup_data *data; @@ -707,6 +713,7 @@ int pcibios_add_device(struct pci_dev *dev) iounmap(data); } set_dma_domain_ops(dev); + set_dev_domain_options(dev); return 0; } diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c index b814ca6..a021b7b 100644 --- a/arch/x86/pci/vmd.c +++ b/arch/x86/pci/vmd.c @@ -596,6 +596,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd) .parent = res, }; + sd->vmd_domain = true; sd->domain = vmd_find_free_domain(); if (sd->domain < 0) return sd->domain; -- cgit v0.10.2 From 4b202b716e4e282c26c4a95952ea33e318c363ab Mon Sep 17 00:00:00 2001 From: Jon Derrick Date: Wed, 14 Sep 2016 10:38:55 -0600 Subject: PCI/AER: Avoid memory allocation in interrupt handling path When handling AER events, we previously allocated a struct aer_err_info, processed the error, and freed the struct. But aer_isr_one_error() is serialized by rpc_mutex, so we never need more than one copy of the struct, and the struct is only about 70 bytes, so we're not saving much by allocating it dynamically. Embed a struct aer_err_info directly in struct aer_rpc, which is allocated at probe-time by aer_probe(). [bhelgaas: changelog] Suggested-by: Bjorn Helgaas Signed-off-by: Jon Derrick Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/aer/aerdrv.h b/drivers/pci/pcie/aer/aerdrv.h index f15ca8d..d51e4a5 100644 --- a/drivers/pci/pcie/aer/aerdrv.h +++ b/drivers/pci/pcie/aer/aerdrv.h @@ -60,6 +60,7 @@ struct aer_rpc { struct pcie_device *rpd; /* Root Port device */ struct work_struct dpc_handler; struct aer_err_source e_sources[AER_ERROR_SOURCES_MAX]; + struct aer_err_info e_info; unsigned short prod_idx; /* Error Producer Index */ unsigned short cons_idx; /* Error Consumer Index */ int isr; diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index 8262527..9fd18a0 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -711,15 +711,8 @@ static inline void aer_process_err_devices(struct pcie_device *p_device, static void aer_isr_one_error(struct pcie_device *p_device, struct aer_err_source *e_src) { - struct aer_err_info *e_info; - - /* struct aer_err_info might be big, so we allocate it with slab */ - e_info = kmalloc(sizeof(struct aer_err_info), GFP_KERNEL); - if (!e_info) { - dev_printk(KERN_DEBUG, &p_device->port->dev, - "Can't allocate mem when processing AER errors\n"); - return; - } + struct aer_rpc *rpc = get_service_data(p_device); + struct aer_err_info *e_info = &rpc->e_info; /* * There is a possibility that both correctable error and @@ -758,8 +751,6 @@ static void aer_isr_one_error(struct pcie_device *p_device, if (find_source_device(p_device->port, e_info)) aer_process_err_devices(p_device, e_info); } - - kfree(e_info); } /** -- cgit v0.10.2 From 66b808099146166c44157600a166c8372172cd76 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 27 Sep 2016 16:23:34 -0400 Subject: PCI/AER: Cache capability position Save the position of the error reporting capability so it doesn't need to be rediscovered during error handling. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas CC: Lukas Wunner diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c index 08ce257..e99efaa 100644 --- a/drivers/pci/pcie/aer/aerdrv.c +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -134,7 +134,7 @@ static void aer_enable_rootport(struct aer_rpc *rpc) pcie_capability_clear_word(pdev, PCI_EXP_RTCTL, SYSTEM_ERROR_INTR_ON_MESG_MASK); - aer_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + aer_pos = pdev->aer_cap; /* Clear error status */ pci_read_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, ®32); pci_write_config_dword(pdev, aer_pos + PCI_ERR_ROOT_STATUS, reg32); @@ -173,7 +173,7 @@ static void aer_disable_rootport(struct aer_rpc *rpc) */ set_downstream_devices_error_reporting(pdev, false); - pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + pos = pdev->aer_cap; /* Disable Root's interrupt in response to error messages */ pci_read_config_dword(pdev, pos + PCI_ERR_ROOT_COMMAND, ®32); reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; @@ -200,7 +200,7 @@ irqreturn_t aer_irq(int irq, void *context) unsigned long flags; int pos; - pos = pci_find_ext_capability(pdev->port, PCI_EXT_CAP_ID_ERR); + pos = pdev->port->aer_cap; /* * Must lock access to Root Error Status Reg, Root Error ID Reg, * and Root error producer/consumer index @@ -338,7 +338,7 @@ static pci_ers_result_t aer_root_reset(struct pci_dev *dev) u32 reg32; int pos; - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; /* Disable Root's interrupt in response to error messages */ pci_read_config_dword(dev, pos + PCI_ERR_ROOT_COMMAND, ®32); @@ -391,7 +391,7 @@ static void aer_error_resume(struct pci_dev *dev) pcie_capability_write_word(dev, PCI_EXP_DEVSTA, reg16); /* Clean AER Root Error Status */ - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_SEVER, &mask); if (dev->error_state == pci_channel_io_normal) diff --git a/drivers/pci/pcie/aer/aerdrv_core.c b/drivers/pci/pcie/aer/aerdrv_core.c index 9fd18a0..b1303b3 100644 --- a/drivers/pci/pcie/aer/aerdrv_core.c +++ b/drivers/pci/pcie/aer/aerdrv_core.c @@ -35,7 +35,7 @@ int pci_enable_pcie_error_reporting(struct pci_dev *dev) if (pcie_aer_get_firmware_first(dev)) return -EIO; - if (!pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR)) + if (!dev->aer_cap) return -EIO; return pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS); @@ -57,7 +57,7 @@ int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) int pos; u32 status; - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; if (!pos) return -EIO; @@ -78,7 +78,7 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) if (!pci_is_pcie(dev)) return -ENODEV; - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; if (!pos) return -EIO; @@ -97,6 +97,12 @@ int pci_cleanup_aer_error_status_regs(struct pci_dev *dev) return 0; } +int pci_aer_init(struct pci_dev *dev) +{ + dev->aer_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + return pci_cleanup_aer_error_status_regs(dev); +} + /** * add_error_device - list device to be handled * @e_info: pointer to error info @@ -154,7 +160,7 @@ static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) if (!(reg16 & PCI_EXP_AER_FLAGS)) return false; - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; if (!pos) return false; @@ -551,7 +557,7 @@ static void handle_error_source(struct pcie_device *aerdev, * Correctable error does not need software intervention. * No need to go through error recovery process. */ - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; if (pos) pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, info->status); @@ -643,7 +649,7 @@ static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) info->status = 0; info->tlp_header_valid = 0; - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + pos = dev->aer_cap; /* The device might not support AER */ if (!pos) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 93f280d..1575724 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1666,7 +1666,8 @@ static void pci_init_capabilities(struct pci_dev *dev) /* Enable ACS P2P upstream forwarding */ pci_enable_acs(dev); - pci_cleanup_aer_error_status_regs(dev); + /* Advanced Error Reporting */ + pci_aer_init(dev); } /* diff --git a/include/linux/pci.h b/include/linux/pci.h index 57bc838..ab6b027 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -269,6 +269,9 @@ struct pci_dev { unsigned int class; /* 3 bytes: (base,sub,prog-if) */ u8 revision; /* PCI revision, low byte of class word */ u8 hdr_type; /* PCI header type (`multi' flag masked out) */ +#ifdef CONFIG_PCIEAER + u16 aer_cap; /* AER capability offset */ +#endif u8 pcie_cap; /* PCIe capability offset */ u8 msi_cap; /* MSI capability offset */ u8 msix_cap; /* MSI-X capability offset */ @@ -1369,9 +1372,11 @@ static inline bool pcie_aspm_support_enabled(void) { return false; } #ifdef CONFIG_PCIEAER void pci_no_aer(void); bool pci_aer_available(void); +int pci_aer_init(struct pci_dev *dev); #else static inline void pci_no_aer(void) { } static inline bool pci_aer_available(void) { return false; } +static inline int pci_aer_init(struct pci_dev *d) { return -ENODEV; } #endif #ifdef CONFIG_PCIE_ECRC -- cgit v0.10.2 From 4132a577a0a7e75b938d2ae49c7a16b358f60661 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 18 Sep 2016 05:39:20 +0200 Subject: PCI: Afford direct-complete to devices with non-standard PM There are devices not power-manageable by the platform, but still able to runtime suspend to D3cold with a non-standard mechanism. One example is laptop hybrid graphics where the discrete GPU and its built-in HDA controller are power-managed either with a _DSM (AMD PowerXpress, Nvidia Optimus) or a separate gmux controller (MacBook Pro). Another example is Thunderbolt on Macs which is power-managed with custom ACPI methods. When putting the system to sleep, we currently handle such devices improperly by transitioning them from D3cold to D3hot (the default power state defined at the top of pci_target_state()). This wastes energy and prolongs the suspend sequence (powering up the Thunderbolt controller takes 2 seconds). Avoid that by assuming that a non-standard PM mechanism is at work if the device is not platform-power-manageable but currently in D3cold. If the device is wakeup enabled, we might still have to wake it up from D3cold if PME cannot be signaled from that power state. The check for devices without PM capability comes before the check for D3cold since such devices could in theory also be powered down by non-standard means and should then be afforded direct-complete as well. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index aab9d51..2f818c3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1959,9 +1959,22 @@ static pci_power_t pci_target_state(struct pci_dev *dev) default: target_state = state; } - } else if (!dev->pm_cap) { + + return target_state; + } + + if (!dev->pm_cap) target_state = PCI_D0; - } else if (device_may_wakeup(&dev->dev)) { + + /* + * If the device is in D3cold even though it's not power-manageable by + * the platform, it may have been powered down by non-standard means. + * Best to let it slumber. + */ + if (dev->current_state == PCI_D3cold) + target_state = PCI_D3cold; + + if (device_may_wakeup(&dev->dev)) { /* * Find the deepest state from which the device can generate * wake-up events, make it the target state and enable device -- cgit v0.10.2 From cc7cc02bada84f0d707aa5b6d2ef8728a2e1f911 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 18 Sep 2016 05:39:20 +0200 Subject: PCI: Query platform firmware for device power state Usually the most accurate way to determine a PCI device's power state is to read its PM Control & Status Register. There are two cases however when this is not an option: If the device doesn't have the PM capability at all, or if it is in D3cold (in which case its config space is inaccessible). In both cases, we can alternatively query the platform firmware for its opinion on the device's power state. To facilitate this, augment struct pci_platform_pm_ops with a ->get_power callback and implement it for acpi_pci_platform_pm (the only pci_platform_pm_ops existing so far). It is used by a forthcoming commit to let pci_update_current_state() recognize D3cold. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 9a033e8..d966d47 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -452,6 +452,27 @@ static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state) return error; } +static pci_power_t acpi_pci_get_power_state(struct pci_dev *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(&dev->dev); + static const pci_power_t state_conv[] = { + [ACPI_STATE_D0] = PCI_D0, + [ACPI_STATE_D1] = PCI_D1, + [ACPI_STATE_D2] = PCI_D2, + [ACPI_STATE_D3_HOT] = PCI_D3hot, + [ACPI_STATE_D3_COLD] = PCI_D3cold, + }; + int state; + + if (!adev || !acpi_device_power_manageable(adev)) + return PCI_UNKNOWN; + + if (acpi_device_get_power(adev, &state) || state == ACPI_STATE_UNKNOWN) + return PCI_UNKNOWN; + + return state_conv[state]; +} + static bool acpi_pci_can_wakeup(struct pci_dev *dev) { struct acpi_device *adev = ACPI_COMPANION(&dev->dev); @@ -534,6 +555,7 @@ static bool acpi_pci_need_resume(struct pci_dev *dev) static const struct pci_platform_pm_ops acpi_pci_platform_pm = { .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, + .get_state = acpi_pci_get_power_state, .choose_state = acpi_pci_choose_state, .sleep_wake = acpi_pci_sleep_wake, .run_wake = acpi_pci_run_wake, diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 2f818c3..2e18e9a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -552,8 +552,9 @@ static const struct pci_platform_pm_ops *pci_platform_pm; int pci_set_platform_pm(const struct pci_platform_pm_ops *ops) { - if (!ops->is_manageable || !ops->set_state || !ops->choose_state || - !ops->sleep_wake || !ops->run_wake || !ops->need_resume) + if (!ops->is_manageable || !ops->set_state || !ops->get_state || + !ops->choose_state || !ops->sleep_wake || !ops->run_wake || + !ops->need_resume) return -EINVAL; pci_platform_pm = ops; return 0; @@ -570,6 +571,11 @@ static inline int platform_pci_set_power_state(struct pci_dev *dev, return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS; } +static inline pci_power_t platform_pci_get_power_state(struct pci_dev *dev) +{ + return pci_platform_pm ? pci_platform_pm->get_state(dev) : PCI_UNKNOWN; +} + static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev) { return pci_platform_pm ? diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9730c47..01d5206 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -42,6 +42,8 @@ int pci_probe_reset_function(struct pci_dev *dev); * * @set_state: invokes the platform firmware to set the device's power state * + * @get_state: queries the platform firmware for a device's current power state + * * @choose_state: returns PCI power state of given device preferred by the * platform; to be used during system-wide transitions from a * sleeping state to the working state and vice versa @@ -62,6 +64,7 @@ int pci_probe_reset_function(struct pci_dev *dev); struct pci_platform_pm_ops { bool (*is_manageable)(struct pci_dev *dev); int (*set_state)(struct pci_dev *dev, pci_power_t state); + pci_power_t (*get_state)(struct pci_dev *dev); pci_power_t (*choose_state)(struct pci_dev *dev); int (*sleep_wake)(struct pci_dev *dev, bool enable); int (*run_wake)(struct pci_dev *dev, bool enable); -- cgit v0.10.2 From a6a64026c0cd1a76a0c8ab1c05a421aa4821887b Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 18 Sep 2016 05:39:20 +0200 Subject: PCI: Recognize D3cold in pci_update_current_state() Whenever a device is resumed or its power state is changed using the platform, its new power state is read from the PM Control & Status Register and cached in pci_dev->current_state by calling pci_update_current_state(). If the device is in D3cold, reading from config space typically results in a fabricated "all ones" response. But if it's in D3hot, the two bits representing the power state in the PMCSR are *also* set to 1. Thus D3hot and D3cold are not discernible by just reading the PMCSR. To account for this, pci_update_current_state() uses two workarounds: - When transitioning to D3cold using pci_platform_power_transition(), the new power state is set blindly by pci_update_current_state(), i.e. without verifying that the device actually *is* in D3cold. This is achieved by setting the "state" argument to PCI_D3cold. The "state" argument was originally intended to convey the new state in case the device doesn't have the PM capability. It is *also* used to convey the device state if the PM capability is present and the new state is D3cold, but this was never explained in the kerneldoc. - Once the current_state is set to D3cold, further invocations of pci_update_current_state() will blindly assume that the device is still in D3cold and leave the current_state unmodified. To get out of this impasse, the current_state has to be set directly, typically by calling pci_raw_set_power_state() or pci_enable_device(). It would be desirable if pci_update_current_state() could reliably detect D3cold by itself. That would allow us to do away with these workarounds, and it would allow for a smarter, more energy conserving runtime resume strategy after system sleep: Currently devices which utilize direct_complete are mandatorily runtime resumed in their ->complete stage. This can be avoided if their power state after system sleep is the same as before, but it requires a mechanism to detect the power state reliably. We've just gained the ability to query the platform firmware for its opinion on the device's power state. On platforms conforming to ACPI 4.0 or newer, this allows recognition of D3cold. Pre-4.0 platforms lack _PR3 and therefore the deepest power state that will ever be reported is D3hot, even though the device may actually be in D3cold. To detect D3cold in those cases, accessibility of the vendor ID in config space is probed using pci_device_is_present(). This also works for devices which are not platform-power-manageable at all, but can be suspended to D3cold using a nonstandard mechanism (e.g. some hybrid graphics laptops or Thunderbolt on the Mac). Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 2e18e9a..b2be895 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -707,26 +707,25 @@ static int pci_raw_set_power_state(struct pci_dev *dev, pci_power_t state) } /** - * pci_update_current_state - Read PCI power state of given device from its - * PCI PM registers and cache it + * pci_update_current_state - Read power state of given device and cache it * @dev: PCI device to handle. * @state: State to cache in case the device doesn't have the PM capability + * + * The power state is read from the PMCSR register, which however is + * inaccessible in D3cold. The platform firmware is therefore queried first + * to detect accessibility of the register. In case the platform firmware + * reports an incorrect state or the device isn't power manageable by the + * platform at all, we try to detect D3cold by testing accessibility of the + * vendor ID in config space. */ void pci_update_current_state(struct pci_dev *dev, pci_power_t state) { - if (dev->pm_cap) { + if (platform_pci_get_power_state(dev) == PCI_D3cold || + !pci_device_is_present(dev)) { + dev->current_state = PCI_D3cold; + } else if (dev->pm_cap) { u16 pmcsr; - /* - * Configuration space is not accessible for device in - * D3cold, so just keep or set D3cold for safety - */ - if (dev->current_state == PCI_D3cold) - return; - if (state == PCI_D3cold) { - dev->current_state = PCI_D3cold; - return; - } pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); } else { -- cgit v0.10.2 From a0d2a959d3da343554523d26902de1d40a9e5c28 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 18 Sep 2016 05:39:20 +0200 Subject: PCI: Avoid unnecessary resume after direct-complete Commit 58a1fbbb2ee8 ("PM / PCI / ACPI: Kick devices that might have been reset by firmware") added a runtime resume for devices that were runtime suspended when the system entered sleep. The motivation was that devices might be in a reset-power-on state after waking from system sleep, so their power state as perceived by Linux (stored in pci_dev->current_state) would no longer reflect reality. By resuming such devices, we allow them to return to a low-power state via autosuspend and also bring their current_state in sync with reality. However for devices that are *not* in a reset-power-on state, doing an unconditional resume wastes energy. A more refined approach is called for which issues a runtime resume only if the power state after direct-complete is shallower than it was before. To achieve this, update the device's current_state and compare it to its pre-sleep value. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index e39a67c..fd4b9c4 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -684,8 +684,19 @@ static int pci_pm_prepare(struct device *dev) static void pci_pm_complete(struct device *dev) { - pci_dev_complete_resume(to_pci_dev(dev)); - pm_complete_with_resume_check(dev); + struct pci_dev *pci_dev = to_pci_dev(dev); + + pci_dev_complete_resume(pci_dev); + pm_generic_complete(dev); + + /* Resume device if platform firmware has put it in reset-power-on */ + if (dev->power.direct_complete && pm_resume_via_firmware()) { + pci_power_t pre_sleep_state = pci_dev->current_state; + + pci_update_current_state(pci_dev, pci_dev->current_state); + if (pci_dev->current_state < pre_sleep_state) + pm_request_resume(dev); + } } #else /* !CONFIG_PM_SLEEP */ -- cgit v0.10.2 From f0b99f70e92dcdc4fecf5cf7ce2f6857ddd82c65 Mon Sep 17 00:00:00 2001 From: Yongji Xie Date: Tue, 13 Sep 2016 17:00:31 +0800 Subject: PCI: Ignore requested alignment for PROBE_ONLY and fixed resources Users may request additional alignment of PCI resources, e.g., to align BARs on page boundaries so they can be shared with guests via VFIO. This of course may require reallocation if firmware has already assigned the BARs with smaller alignments. If the platform has requested PCI_PROBE_ONLY, we should never change any PCI BARs, so we can't provide any additional alignment. Also, if a BAR is marked as IORESOURCE_PCI_FIXED, e.g., for PCI Enhanced Allocation or if the firmware depends on the current BAR value, we can't change the alignment. In these cases, log a message and ignore the user's alignment requests. [bhelgaas: changelog, use goto to simplify PCI_PROBE_ONLY check] Signed-off-by: Yongji Xie Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index aab9d51..ed9447a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4959,6 +4959,13 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev) spin_lock(&resource_alignment_lock); p = resource_alignment_param; + if (!*p) + goto out; + if (pci_has_flag(PCI_PROBE_ONLY)) { + pr_info_once("PCI: Ignoring requested alignments (PCI_PROBE_ONLY)\n"); + goto out; + } + while (*p) { count = 0; if (sscanf(p, "%d%n", &align_order, &count) == 1 && @@ -5023,6 +5030,7 @@ static resource_size_t pci_specified_resource_alignment(struct pci_dev *dev) } p++; } +out: spin_unlock(&resource_alignment_lock); return align; } @@ -5063,6 +5071,12 @@ void pci_reassigndev_resource_alignment(struct pci_dev *dev) r = &dev->resource[i]; if (!(r->flags & IORESOURCE_MEM)) continue; + if (r->flags & IORESOURCE_PCI_FIXED) { + dev_info(&dev->dev, "Ignoring requested alignment for BAR%d: %pR\n", + i, r); + continue; + } + size = resource_size(r); if (size < align) { size = align; -- cgit v0.10.2 From 62d9a78f32d9a1b0f6fdae70751deeae6335e74b Mon Sep 17 00:00:00 2001 From: Yongji Xie Date: Tue, 13 Sep 2016 17:00:32 +0800 Subject: PCI: Ignore requested alignment for VF BARs Resource allocation for VFs is done via the VF BARx registers in the PF's SR-IOV Capability, and the BARs in the VFs themselves are read-only zeros (see SR-IOV spec r1.1, secs 3.3.14 and 3.4.1.11). Even though the actual VF BARs are read-only zeros, the VF dev->resource[] structs describe the space allocated for the VF (this is a piece of the space described by the VF BARx register in the PF's SR-IOV capability). It's meaningless to request additional alignment for a VF: the VF BAR alignment is completely determined by the alignment of the VF BARx in the PF and the size of the VF BAR. Ignore the user's alignment requests for VF devices. Signed-off-by: Yongji Xie Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index ed9447a..b5b8333 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5049,6 +5049,15 @@ void pci_reassigndev_resource_alignment(struct pci_dev *dev) resource_size_t align, size; u16 command; + /* + * VF BARs are read-only zero according to SR-IOV spec r1.1, sec + * 3.4.1.11. Their resources are allocated from the space + * described by the VF BARx register in the PF's SR-IOV capability. + * We can't influence their alignment here. + */ + if (dev->is_virtfn) + return; + /* check if specified PCI is target device to reassign */ align = pci_specified_resource_alignment(dev); if (!align) -- cgit v0.10.2 From 6b20f728549030056402d7f68ea670eea1eb8198 Mon Sep 17 00:00:00 2001 From: Cao jin Date: Fri, 30 Sep 2016 10:06:23 -0500 Subject: PCI/AER: Fix aer_probe() kernel-doc comment 0516c8bcd252 ("PCI: PCIe portdrv: Simplily probe callback of service drivers") removed the "id" argument of aer_probe() but neglected to remove the kernel-doc comment. Update the comment. [bhelgaas: changelog] Signed-off-by: Cao jin Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/pcie/aer/aerdrv.c b/drivers/pci/pcie/aer/aerdrv.c index e99efaa..b2992e6 100644 --- a/drivers/pci/pcie/aer/aerdrv.c +++ b/drivers/pci/pcie/aer/aerdrv.c @@ -294,7 +294,6 @@ static void aer_remove(struct pcie_device *dev) /** * aer_probe - initialize resources * @dev: pointer to the pcie_dev data structure - * @id: pointer to the service id data structure * * Invoked when PCI Express bus loads AER service driver. */ -- cgit v0.10.2 From b3327f7fae66daf918ce72214eb464e434193523 Mon Sep 17 00:00:00 2001 From: Sergei Shtylyov Date: Thu, 22 Sep 2016 23:20:18 +0300 Subject: PCI: rcar: Try increasing PCIe link speed to 5 GT/s at boot The PCIe link speed is initially set to 2.5 GT/s. Try to increase the link speed to 5 GT/s. Based on original patch by Grigory Kletsko . [bhelgaas: remove "Trying speed up" message, remove unused SPCHG] Signed-off-by: Sergei Shtylyov Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 35aee9c..6db5326 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -84,8 +84,18 @@ #define IDSETR1 0x011004 #define TLCTLR 0x011048 #define MACSR 0x011054 +#define SPCHGFIN (1 << 4) +#define SPCHGFAIL (1 << 6) +#define SPCHGSUC (1 << 7) +#define LINK_SPEED (0xf << 16) +#define LINK_SPEED_2_5GTS (1 << 16) +#define LINK_SPEED_5_0GTS (2 << 16) #define MACCTLR 0x011058 +#define SPEED_CHANGE (1 << 24) #define SCRAMBLE_DISABLE (1 << 27) +#define MACS2R 0x011078 +#define MACCGSPSETR 0x011084 +#define SPCNGRSN (1 << 31) /* R-Car H1 PHY */ #define H1_PCIEPHYADRR 0x04000c @@ -385,11 +395,67 @@ static int rcar_pcie_setup(struct list_head *resource, struct rcar_pcie *pci) return 1; } +static void rcar_pcie_force_speedup(struct rcar_pcie *pcie) +{ + unsigned int timeout = 1000; + u32 macsr; + + if ((rcar_pci_read_reg(pcie, MACS2R) & LINK_SPEED) != LINK_SPEED_5_0GTS) + return; + + if (rcar_pci_read_reg(pcie, MACCTLR) & SPEED_CHANGE) { + dev_err(pcie->dev, "Speed change already in progress\n"); + return; + } + + macsr = rcar_pci_read_reg(pcie, MACSR); + if ((macsr & LINK_SPEED) == LINK_SPEED_5_0GTS) + goto done; + + /* Set target link speed to 5.0 GT/s */ + rcar_rmw32(pcie, EXPCAP(12), PCI_EXP_LNKSTA_CLS, + PCI_EXP_LNKSTA_CLS_5_0GB); + + /* Set speed change reason as intentional factor */ + rcar_rmw32(pcie, MACCGSPSETR, SPCNGRSN, 0); + + /* Clear SPCHGFIN, SPCHGSUC, and SPCHGFAIL */ + if (macsr & (SPCHGFIN | SPCHGSUC | SPCHGFAIL)) + rcar_pci_write_reg(pcie, macsr, MACSR); + + /* Start link speed change */ + rcar_rmw32(pcie, MACCTLR, SPEED_CHANGE, SPEED_CHANGE); + + while (timeout--) { + macsr = rcar_pci_read_reg(pcie, MACSR); + if (macsr & SPCHGFIN) { + /* Clear the interrupt bits */ + rcar_pci_write_reg(pcie, macsr, MACSR); + + if (macsr & SPCHGFAIL) + dev_err(pcie->dev, "Speed change failed\n"); + + goto done; + } + + msleep(1); + }; + + dev_err(pcie->dev, "Speed change timed out\n"); + +done: + dev_info(pcie->dev, "Current link speed is %s GT/s\n", + (macsr & LINK_SPEED) == LINK_SPEED_5_0GTS ? "5" : "2.5"); +} + static int rcar_pcie_enable(struct rcar_pcie *pcie) { struct pci_bus *bus, *child; LIST_HEAD(res); + /* Try setting 5 GT/s link speed */ + rcar_pcie_force_speedup(pcie); + rcar_pcie_setup(&res, pcie); pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); -- cgit v0.10.2 From 277743ef616defc870d101f9cf9d3488aba1c1b6 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Thu, 22 Sep 2016 17:50:42 -0700 Subject: PCI: rockchip: Increase the Max Credit update interval Increase the likelihood of link state to automatically go to L1 and save some power. The default credit update interval of 7.5 us results in the rootport sending UpdateFC-P and UpdateFC-NP packets too often, thus resulting in the link never going to L1, and always staying in L0/L0s. The value 24 us was chosen after some experiments and peeking over the PCIe bus to see that we do enter L1 substate when there is not enough traffic on the PCIe bus. Signed-off-by: Rajat Jain Signed-off-by: Bjorn Helgaas Acked-by: Shawn Lin diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 8bedc1e..91f6fa2 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -95,6 +95,11 @@ #define PCIE_CORE_PL_CONF_SPEED_MASK 0x00000018 #define PCIE_CORE_PL_CONF_LANE_MASK 0x00000006 #define PCIE_CORE_PL_CONF_LANE_SHIFT 1 +#define PCIE_CORE_TXCREDIT_CFG1 (PCIE_CORE_CTRL_MGMT_BASE + 0x020) +#define PCIE_CORE_TXCREDIT_CFG1_MUI_MASK 0xFFFF0000 +#define PCIE_CORE_TXCREDIT_CFG1_MUI_SHIFT 16 +#define PCIE_CORE_TXCREDIT_CFG1_MUI_ENCODE(x) \ + (((x) >> 3) << PCIE_CORE_TXCREDIT_CFG1_MUI_SHIFT) #define PCIE_CORE_INT_STATUS (PCIE_CORE_CTRL_MGMT_BASE + 0x20c) #define PCIE_CORE_INT_PRFPE BIT(0) #define PCIE_CORE_INT_CRFPE BIT(1) @@ -224,6 +229,17 @@ static void rockchip_pcie_clr_bw_int(struct rockchip_pcie *rockchip) rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_LCS); } +static void rockchip_pcie_update_txcredit_mui(struct rockchip_pcie *rockchip) +{ + u32 val; + + /* Update Tx credit maximum update interval */ + val = rockchip_pcie_read(rockchip, PCIE_CORE_TXCREDIT_CFG1); + val &= ~PCIE_CORE_TXCREDIT_CFG1_MUI_MASK; + val |= PCIE_CORE_TXCREDIT_CFG1_MUI_ENCODE(24000); /* ns */ + rockchip_pcie_write(rockchip, val, PCIE_CORE_TXCREDIT_CFG1); +} + static int rockchip_pcie_valid_device(struct rockchip_pcie *rockchip, struct pci_bus *bus, int dev) { @@ -597,6 +613,7 @@ static irqreturn_t rockchip_pcie_subsys_irq_handler(int irq, void *arg) rockchip_pcie_write(rockchip, sub_reg, PCIE_CORE_INT_STATUS); } else if (reg & PCIE_CLIENT_INT_PHY) { dev_dbg(dev, "phy link changes\n"); + rockchip_pcie_update_txcredit_mui(rockchip); rockchip_pcie_clr_bw_int(rockchip); } -- cgit v0.10.2 From 58c6990c5ee772c2551193f053e51a52b9984b49 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 23 Sep 2016 10:05:59 +0800 Subject: PCI: rockchip: Improve the deassert sequence of four reset pins Per TRM, we need to deassert the four reset pins simultaneously. Currently the reset framework doesn't support that so we did it one by one. It seems no side effect found but it does impact the state machine of controller, so sometimes the change speed bit is not set when sending training sequence from recover state. After the silicon RTL review from SoC guys, we don't need to do the sequence recommended by TRM, and could just move the deassert of mgmt_sticky_rst to the first place. Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 91f6fa2..43b765e 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -449,21 +449,25 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) return err; } - err = reset_control_deassert(rockchip->core_rst); + /* + * Please don't reorder the deassert sequence of the following + * four reset pins. + */ + err = reset_control_deassert(rockchip->mgmt_sticky_rst); if (err) { - dev_err(dev, "deassert core_rst err %d\n", err); + dev_err(dev, "deassert mgmt_sticky_rst err %d\n", err); return err; } - err = reset_control_deassert(rockchip->mgmt_rst); + err = reset_control_deassert(rockchip->core_rst); if (err) { - dev_err(dev, "deassert mgmt_rst err %d\n", err); + dev_err(dev, "deassert core_rst err %d\n", err); return err; } - err = reset_control_deassert(rockchip->mgmt_sticky_rst); + err = reset_control_deassert(rockchip->mgmt_rst); if (err) { - dev_err(dev, "deassert mgmt_sticky_rst err %d\n", err); + dev_err(dev, "deassert mgmt_rst err %d\n", err); return err; } -- cgit v0.10.2 From ca1989084054e64da25662e1f974f77312083eb3 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Tue, 4 Oct 2016 12:20:22 -0500 Subject: PCI: rockchip: Fix wrong transmitted FTS count If the expected number of FTS aren't received by RC when exiting from L0s, the LTSSM will fall into recover state, which means it will need to send TS for retraining which makes the latency of exiting from L0s a little longer than expected. This issue is caused by an incorrect reset value of FTS count on PLC1 register (offset 0x4). The expected value for Gen1/2 should be more than 240 and we may leave a little margin here. Fix this before starting Gen1 training which will make TS1 contain the correct FTS count. Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas diff --git a/drivers/pci/host/pcie-rockchip.c b/drivers/pci/host/pcie-rockchip.c index 43b765e..b8c82fc 100644 --- a/drivers/pci/host/pcie-rockchip.c +++ b/drivers/pci/host/pcie-rockchip.c @@ -95,6 +95,10 @@ #define PCIE_CORE_PL_CONF_SPEED_MASK 0x00000018 #define PCIE_CORE_PL_CONF_LANE_MASK 0x00000006 #define PCIE_CORE_PL_CONF_LANE_SHIFT 1 +#define PCIE_CORE_CTRL_PLC1 (PCIE_CORE_CTRL_MGMT_BASE + 0x004) +#define PCIE_CORE_CTRL_PLC1_FTS_MASK GENMASK(23, 8) +#define PCIE_CORE_CTRL_PLC1_FTS_SHIFT 8 +#define PCIE_CORE_CTRL_PLC1_FTS_CNT 0xffff #define PCIE_CORE_TXCREDIT_CFG1 (PCIE_CORE_CTRL_MGMT_BASE + 0x020) #define PCIE_CORE_TXCREDIT_CFG1_MUI_MASK 0xFFFF0000 #define PCIE_CORE_TXCREDIT_CFG1_MUI_SHIFT 16 @@ -486,6 +490,12 @@ static int rockchip_pcie_init_port(struct rockchip_pcie *rockchip) status = rockchip_pcie_read(rockchip, PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2); rockchip_pcie_write(rockchip, status, PCIE_RC_CONFIG_L1_SUBSTATE_CTRL2); + /* Fix the transmitted FTS count desired to exit from L0s. */ + status = rockchip_pcie_read(rockchip, PCIE_CORE_CTRL_PLC1); + status = (status & PCIE_CORE_CTRL_PLC1_FTS_MASK) | + (PCIE_CORE_CTRL_PLC1_FTS_CNT << PCIE_CORE_CTRL_PLC1_FTS_SHIFT); + rockchip_pcie_write(rockchip, status, PCIE_CORE_CTRL_PLC1); + /* Enable Gen1 training */ rockchip_pcie_write(rockchip, PCIE_CLIENT_LINK_TRAIN_ENABLE, PCIE_CLIENT_CONFIG); -- cgit v0.10.2 From 181ffd19cc9898f795646bf9a897072a419a51ed Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 4 Oct 2016 12:26:37 -0500 Subject: x86/PCI: VMD: Move VMD driver to drivers/pci/host Move the driver source and Kconfig to the PCI host bridge drivers directory and move the config option to a more appropriate sub-menu instead of occupying the top-level location. Update the Kconfig option with the X86_64 dependency that was implicitly included from the previous location, and add information about the module name when built as a loadable module. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas CC: Jon Derrick diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 5c6e747..c320838 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2744,19 +2744,6 @@ config PMC_ATOM def_bool y depends on PCI -config VMD - depends on PCI_MSI - tristate "Volume Management Device Driver" - default N - ---help--- - Adds support for the Intel Volume Management Device (VMD). VMD is a - secondary PCI host bridge that allows PCI Express root ports, - and devices attached to them, to be removed from the default - PCI domain and placed within the VMD domain. This provides - more bus resources than are otherwise possible with a - single domain. If you know your system provides one of these and - has devices attached to it, say Y; if you are not sure, say N. - source "net/Kconfig" source "drivers/Kconfig" diff --git a/arch/x86/pci/Makefile b/arch/x86/pci/Makefile index 97062a6..5c6fc35 100644 --- a/arch/x86/pci/Makefile +++ b/arch/x86/pci/Makefile @@ -23,8 +23,6 @@ obj-y += bus_numa.o obj-$(CONFIG_AMD_NB) += amd_bus.o obj-$(CONFIG_PCI_CNB20LE_QUIRK) += broadcom_bus.o -obj-$(CONFIG_VMD) += vmd.o - ifeq ($(CONFIG_PCI_DEBUG),y) EXTRA_CFLAGS += -DDEBUG endif diff --git a/arch/x86/pci/vmd.c b/arch/x86/pci/vmd.c deleted file mode 100644 index 5705852..0000000 --- a/arch/x86/pci/vmd.c +++ /dev/null @@ -1,761 +0,0 @@ -/* - * Volume Management Device driver - * Copyright (c) 2015, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define VMD_CFGBAR 0 -#define VMD_MEMBAR1 2 -#define VMD_MEMBAR2 4 - -/* - * Lock for manipulating VMD IRQ lists. - */ -static DEFINE_RAW_SPINLOCK(list_lock); - -/** - * struct vmd_irq - private data to map driver IRQ to the VMD shared vector - * @node: list item for parent traversal. - * @rcu: RCU callback item for freeing. - * @irq: back pointer to parent. - * @virq: the virtual IRQ value provided to the requesting driver. - * - * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to - * a VMD IRQ using this structure. - */ -struct vmd_irq { - struct list_head node; - struct rcu_head rcu; - struct vmd_irq_list *irq; - unsigned int virq; -}; - -/** - * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector - * @irq_list: the list of irq's the VMD one demuxes to. - * @count: number of child IRQs assigned to this vector; used to track - * sharing. - */ -struct vmd_irq_list { - struct list_head irq_list; - unsigned int count; -}; - -struct vmd_dev { - struct pci_dev *dev; - - spinlock_t cfg_lock; - char __iomem *cfgbar; - - int msix_count; - struct vmd_irq_list *irqs; - - struct pci_sysdata sysdata; - struct resource resources[3]; - struct irq_domain *irq_domain; - struct pci_bus *bus; - -#ifdef CONFIG_X86_DEV_DMA_OPS - struct dma_map_ops dma_ops; - struct dma_domain dma_domain; -#endif -}; - -static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) -{ - return container_of(bus->sysdata, struct vmd_dev, sysdata); -} - -static inline unsigned int index_from_irqs(struct vmd_dev *vmd, - struct vmd_irq_list *irqs) -{ - return irqs - vmd->irqs; -} - -/* - * Drivers managing a device in a VMD domain allocate their own IRQs as before, - * but the MSI entry for the hardware it's driving will be programmed with a - * destination ID for the VMD MSI-X table. The VMD muxes interrupts in its - * domain into one of its own, and the VMD driver de-muxes these for the - * handlers sharing that VMD IRQ. The vmd irq_domain provides the operations - * and irq_chip to set this up. - */ -static void vmd_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) -{ - struct vmd_irq *vmdirq = data->chip_data; - struct vmd_irq_list *irq = vmdirq->irq; - struct vmd_dev *vmd = irq_data_get_irq_handler_data(data); - - msg->address_hi = MSI_ADDR_BASE_HI; - msg->address_lo = MSI_ADDR_BASE_LO | - MSI_ADDR_DEST_ID(index_from_irqs(vmd, irq)); - msg->data = 0; -} - -/* - * We rely on MSI_FLAG_USE_DEF_CHIP_OPS to set the IRQ mask/unmask ops. - */ -static void vmd_irq_enable(struct irq_data *data) -{ - struct vmd_irq *vmdirq = data->chip_data; - unsigned long flags; - - raw_spin_lock_irqsave(&list_lock, flags); - list_add_tail_rcu(&vmdirq->node, &vmdirq->irq->irq_list); - raw_spin_unlock_irqrestore(&list_lock, flags); - - data->chip->irq_unmask(data); -} - -static void vmd_irq_disable(struct irq_data *data) -{ - struct vmd_irq *vmdirq = data->chip_data; - unsigned long flags; - - data->chip->irq_mask(data); - - raw_spin_lock_irqsave(&list_lock, flags); - list_del_rcu(&vmdirq->node); - INIT_LIST_HEAD_RCU(&vmdirq->node); - raw_spin_unlock_irqrestore(&list_lock, flags); -} - -/* - * XXX: Stubbed until we develop acceptable way to not create conflicts with - * other devices sharing the same vector. - */ -static int vmd_irq_set_affinity(struct irq_data *data, - const struct cpumask *dest, bool force) -{ - return -EINVAL; -} - -static struct irq_chip vmd_msi_controller = { - .name = "VMD-MSI", - .irq_enable = vmd_irq_enable, - .irq_disable = vmd_irq_disable, - .irq_compose_msi_msg = vmd_compose_msi_msg, - .irq_set_affinity = vmd_irq_set_affinity, -}; - -static irq_hw_number_t vmd_get_hwirq(struct msi_domain_info *info, - msi_alloc_info_t *arg) -{ - return 0; -} - -/* - * XXX: We can be even smarter selecting the best IRQ once we solve the - * affinity problem. - */ -static struct vmd_irq_list *vmd_next_irq(struct vmd_dev *vmd, struct msi_desc *desc) -{ - int i, best = 1; - unsigned long flags; - - if (!desc->msi_attrib.is_msix || vmd->msix_count == 1) - return &vmd->irqs[0]; - - raw_spin_lock_irqsave(&list_lock, flags); - for (i = 1; i < vmd->msix_count; i++) - if (vmd->irqs[i].count < vmd->irqs[best].count) - best = i; - vmd->irqs[best].count++; - raw_spin_unlock_irqrestore(&list_lock, flags); - - return &vmd->irqs[best]; -} - -static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info, - unsigned int virq, irq_hw_number_t hwirq, - msi_alloc_info_t *arg) -{ - struct msi_desc *desc = arg->desc; - struct vmd_dev *vmd = vmd_from_bus(msi_desc_to_pci_dev(desc)->bus); - struct vmd_irq *vmdirq = kzalloc(sizeof(*vmdirq), GFP_KERNEL); - unsigned int index, vector; - - if (!vmdirq) - return -ENOMEM; - - INIT_LIST_HEAD(&vmdirq->node); - vmdirq->irq = vmd_next_irq(vmd, desc); - vmdirq->virq = virq; - index = index_from_irqs(vmd, vmdirq->irq); - vector = pci_irq_vector(vmd->dev, index); - - irq_domain_set_info(domain, virq, vector, info->chip, vmdirq, - handle_untracked_irq, vmd, NULL); - return 0; -} - -static void vmd_msi_free(struct irq_domain *domain, - struct msi_domain_info *info, unsigned int virq) -{ - struct vmd_irq *vmdirq = irq_get_chip_data(virq); - unsigned long flags; - - synchronize_rcu(); - - /* XXX: Potential optimization to rebalance */ - raw_spin_lock_irqsave(&list_lock, flags); - vmdirq->irq->count--; - raw_spin_unlock_irqrestore(&list_lock, flags); - - kfree_rcu(vmdirq, rcu); -} - -static int vmd_msi_prepare(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *arg) -{ - struct pci_dev *pdev = to_pci_dev(dev); - struct vmd_dev *vmd = vmd_from_bus(pdev->bus); - - if (nvec > vmd->msix_count) - return vmd->msix_count; - - memset(arg, 0, sizeof(*arg)); - return 0; -} - -static void vmd_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) -{ - arg->desc = desc; -} - -static struct msi_domain_ops vmd_msi_domain_ops = { - .get_hwirq = vmd_get_hwirq, - .msi_init = vmd_msi_init, - .msi_free = vmd_msi_free, - .msi_prepare = vmd_msi_prepare, - .set_desc = vmd_set_desc, -}; - -static struct msi_domain_info vmd_msi_domain_info = { - .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_PCI_MSIX, - .ops = &vmd_msi_domain_ops, - .chip = &vmd_msi_controller, -}; - -#ifdef CONFIG_X86_DEV_DMA_OPS -/* - * VMD replaces the requester ID with its own. DMA mappings for devices in a - * VMD domain need to be mapped for the VMD, not the device requiring - * the mapping. - */ -static struct device *to_vmd_dev(struct device *dev) -{ - struct pci_dev *pdev = to_pci_dev(dev); - struct vmd_dev *vmd = vmd_from_bus(pdev->bus); - - return &vmd->dev->dev; -} - -static struct dma_map_ops *vmd_dma_ops(struct device *dev) -{ - return get_dma_ops(to_vmd_dev(dev)); -} - -static void *vmd_alloc(struct device *dev, size_t size, dma_addr_t *addr, - gfp_t flag, unsigned long attrs) -{ - return vmd_dma_ops(dev)->alloc(to_vmd_dev(dev), size, addr, flag, - attrs); -} - -static void vmd_free(struct device *dev, size_t size, void *vaddr, - dma_addr_t addr, unsigned long attrs) -{ - return vmd_dma_ops(dev)->free(to_vmd_dev(dev), size, vaddr, addr, - attrs); -} - -static int vmd_mmap(struct device *dev, struct vm_area_struct *vma, - void *cpu_addr, dma_addr_t addr, size_t size, - unsigned long attrs) -{ - return vmd_dma_ops(dev)->mmap(to_vmd_dev(dev), vma, cpu_addr, addr, - size, attrs); -} - -static int vmd_get_sgtable(struct device *dev, struct sg_table *sgt, - void *cpu_addr, dma_addr_t addr, size_t size, - unsigned long attrs) -{ - return vmd_dma_ops(dev)->get_sgtable(to_vmd_dev(dev), sgt, cpu_addr, - addr, size, attrs); -} - -static dma_addr_t vmd_map_page(struct device *dev, struct page *page, - unsigned long offset, size_t size, - enum dma_data_direction dir, - unsigned long attrs) -{ - return vmd_dma_ops(dev)->map_page(to_vmd_dev(dev), page, offset, size, - dir, attrs); -} - -static void vmd_unmap_page(struct device *dev, dma_addr_t addr, size_t size, - enum dma_data_direction dir, unsigned long attrs) -{ - vmd_dma_ops(dev)->unmap_page(to_vmd_dev(dev), addr, size, dir, attrs); -} - -static int vmd_map_sg(struct device *dev, struct scatterlist *sg, int nents, - enum dma_data_direction dir, unsigned long attrs) -{ - return vmd_dma_ops(dev)->map_sg(to_vmd_dev(dev), sg, nents, dir, attrs); -} - -static void vmd_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, - enum dma_data_direction dir, unsigned long attrs) -{ - vmd_dma_ops(dev)->unmap_sg(to_vmd_dev(dev), sg, nents, dir, attrs); -} - -static void vmd_sync_single_for_cpu(struct device *dev, dma_addr_t addr, - size_t size, enum dma_data_direction dir) -{ - vmd_dma_ops(dev)->sync_single_for_cpu(to_vmd_dev(dev), addr, size, dir); -} - -static void vmd_sync_single_for_device(struct device *dev, dma_addr_t addr, - size_t size, enum dma_data_direction dir) -{ - vmd_dma_ops(dev)->sync_single_for_device(to_vmd_dev(dev), addr, size, - dir); -} - -static void vmd_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, - int nents, enum dma_data_direction dir) -{ - vmd_dma_ops(dev)->sync_sg_for_cpu(to_vmd_dev(dev), sg, nents, dir); -} - -static void vmd_sync_sg_for_device(struct device *dev, struct scatterlist *sg, - int nents, enum dma_data_direction dir) -{ - vmd_dma_ops(dev)->sync_sg_for_device(to_vmd_dev(dev), sg, nents, dir); -} - -static int vmd_mapping_error(struct device *dev, dma_addr_t addr) -{ - return vmd_dma_ops(dev)->mapping_error(to_vmd_dev(dev), addr); -} - -static int vmd_dma_supported(struct device *dev, u64 mask) -{ - return vmd_dma_ops(dev)->dma_supported(to_vmd_dev(dev), mask); -} - -#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK -static u64 vmd_get_required_mask(struct device *dev) -{ - return vmd_dma_ops(dev)->get_required_mask(to_vmd_dev(dev)); -} -#endif - -static void vmd_teardown_dma_ops(struct vmd_dev *vmd) -{ - struct dma_domain *domain = &vmd->dma_domain; - - if (get_dma_ops(&vmd->dev->dev)) - del_dma_domain(domain); -} - -#define ASSIGN_VMD_DMA_OPS(source, dest, fn) \ - do { \ - if (source->fn) \ - dest->fn = vmd_##fn; \ - } while (0) - -static void vmd_setup_dma_ops(struct vmd_dev *vmd) -{ - const struct dma_map_ops *source = get_dma_ops(&vmd->dev->dev); - struct dma_map_ops *dest = &vmd->dma_ops; - struct dma_domain *domain = &vmd->dma_domain; - - domain->domain_nr = vmd->sysdata.domain; - domain->dma_ops = dest; - - if (!source) - return; - ASSIGN_VMD_DMA_OPS(source, dest, alloc); - ASSIGN_VMD_DMA_OPS(source, dest, free); - ASSIGN_VMD_DMA_OPS(source, dest, mmap); - ASSIGN_VMD_DMA_OPS(source, dest, get_sgtable); - ASSIGN_VMD_DMA_OPS(source, dest, map_page); - ASSIGN_VMD_DMA_OPS(source, dest, unmap_page); - ASSIGN_VMD_DMA_OPS(source, dest, map_sg); - ASSIGN_VMD_DMA_OPS(source, dest, unmap_sg); - ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_cpu); - ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_device); - ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_cpu); - ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_device); - ASSIGN_VMD_DMA_OPS(source, dest, mapping_error); - ASSIGN_VMD_DMA_OPS(source, dest, dma_supported); -#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK - ASSIGN_VMD_DMA_OPS(source, dest, get_required_mask); -#endif - add_dma_domain(domain); -} -#undef ASSIGN_VMD_DMA_OPS -#else -static void vmd_teardown_dma_ops(struct vmd_dev *vmd) {} -static void vmd_setup_dma_ops(struct vmd_dev *vmd) {} -#endif - -static char __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, - unsigned int devfn, int reg, int len) -{ - char __iomem *addr = vmd->cfgbar + - (bus->number << 20) + (devfn << 12) + reg; - - if ((addr - vmd->cfgbar) + len >= - resource_size(&vmd->dev->resource[VMD_CFGBAR])) - return NULL; - - return addr; -} - -/* - * CPU may deadlock if config space is not serialized on some versions of this - * hardware, so all config space access is done under a spinlock. - */ -static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg, - int len, u32 *value) -{ - struct vmd_dev *vmd = vmd_from_bus(bus); - char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); - unsigned long flags; - int ret = 0; - - if (!addr) - return -EFAULT; - - spin_lock_irqsave(&vmd->cfg_lock, flags); - switch (len) { - case 1: - *value = readb(addr); - break; - case 2: - *value = readw(addr); - break; - case 4: - *value = readl(addr); - break; - default: - ret = -EINVAL; - break; - } - spin_unlock_irqrestore(&vmd->cfg_lock, flags); - return ret; -} - -/* - * VMD h/w converts non-posted config writes to posted memory writes. The - * read-back in this function forces the completion so it returns only after - * the config space was written, as expected. - */ -static int vmd_pci_write(struct pci_bus *bus, unsigned int devfn, int reg, - int len, u32 value) -{ - struct vmd_dev *vmd = vmd_from_bus(bus); - char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); - unsigned long flags; - int ret = 0; - - if (!addr) - return -EFAULT; - - spin_lock_irqsave(&vmd->cfg_lock, flags); - switch (len) { - case 1: - writeb(value, addr); - readb(addr); - break; - case 2: - writew(value, addr); - readw(addr); - break; - case 4: - writel(value, addr); - readl(addr); - break; - default: - ret = -EINVAL; - break; - } - spin_unlock_irqrestore(&vmd->cfg_lock, flags); - return ret; -} - -static struct pci_ops vmd_ops = { - .read = vmd_pci_read, - .write = vmd_pci_write, -}; - -static void vmd_attach_resources(struct vmd_dev *vmd) -{ - vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[1]; - vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[2]; -} - -static void vmd_detach_resources(struct vmd_dev *vmd) -{ - vmd->dev->resource[VMD_MEMBAR1].child = NULL; - vmd->dev->resource[VMD_MEMBAR2].child = NULL; -} - -/* - * VMD domains start at 0x1000 to not clash with ACPI _SEG domains. - */ -static int vmd_find_free_domain(void) -{ - int domain = 0xffff; - struct pci_bus *bus = NULL; - - while ((bus = pci_find_next_bus(bus)) != NULL) - domain = max_t(int, domain, pci_domain_nr(bus)); - return domain + 1; -} - -static int vmd_enable_domain(struct vmd_dev *vmd) -{ - struct pci_sysdata *sd = &vmd->sysdata; - struct resource *res; - u32 upper_bits; - unsigned long flags; - LIST_HEAD(resources); - - res = &vmd->dev->resource[VMD_CFGBAR]; - vmd->resources[0] = (struct resource) { - .name = "VMD CFGBAR", - .start = 0, - .end = (resource_size(res) >> 20) - 1, - .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, - }; - - /* - * If the window is below 4GB, clear IORESOURCE_MEM_64 so we can - * put 32-bit resources in the window. - * - * There's no hardware reason why a 64-bit window *couldn't* - * contain a 32-bit resource, but pbus_size_mem() computes the - * bridge window size assuming a 64-bit window will contain no - * 32-bit resources. __pci_assign_resource() enforces that - * artificial restriction to make sure everything will fit. - * - * The only way we could use a 64-bit non-prefechable MEMBAR is - * if its address is <4GB so that we can convert it to a 32-bit - * resource. To be visible to the host OS, all VMD endpoints must - * be initially configured by platform BIOS, which includes setting - * up these resources. We can assume the device is configured - * according to the platform needs. - */ - res = &vmd->dev->resource[VMD_MEMBAR1]; - upper_bits = upper_32_bits(res->end); - flags = res->flags & ~IORESOURCE_SIZEALIGN; - if (!upper_bits) - flags &= ~IORESOURCE_MEM_64; - vmd->resources[1] = (struct resource) { - .name = "VMD MEMBAR1", - .start = res->start, - .end = res->end, - .flags = flags, - .parent = res, - }; - - res = &vmd->dev->resource[VMD_MEMBAR2]; - upper_bits = upper_32_bits(res->end); - flags = res->flags & ~IORESOURCE_SIZEALIGN; - if (!upper_bits) - flags &= ~IORESOURCE_MEM_64; - vmd->resources[2] = (struct resource) { - .name = "VMD MEMBAR2", - .start = res->start + 0x2000, - .end = res->end, - .flags = flags, - .parent = res, - }; - - sd->domain = vmd_find_free_domain(); - if (sd->domain < 0) - return sd->domain; - - sd->node = pcibus_to_node(vmd->dev->bus); - - vmd->irq_domain = pci_msi_create_irq_domain(NULL, &vmd_msi_domain_info, - x86_vector_domain); - if (!vmd->irq_domain) - return -ENODEV; - - pci_add_resource(&resources, &vmd->resources[0]); - pci_add_resource(&resources, &vmd->resources[1]); - pci_add_resource(&resources, &vmd->resources[2]); - vmd->bus = pci_create_root_bus(&vmd->dev->dev, 0, &vmd_ops, sd, - &resources); - if (!vmd->bus) { - pci_free_resource_list(&resources); - irq_domain_remove(vmd->irq_domain); - return -ENODEV; - } - - vmd_attach_resources(vmd); - vmd_setup_dma_ops(vmd); - dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain); - pci_rescan_bus(vmd->bus); - - WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj, - "domain"), "Can't create symlink to domain\n"); - return 0; -} - -static irqreturn_t vmd_irq(int irq, void *data) -{ - struct vmd_irq_list *irqs = data; - struct vmd_irq *vmdirq; - - rcu_read_lock(); - list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) - generic_handle_irq(vmdirq->virq); - rcu_read_unlock(); - - return IRQ_HANDLED; -} - -static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - struct vmd_dev *vmd; - int i, err; - - if (resource_size(&dev->resource[VMD_CFGBAR]) < (1 << 20)) - return -ENOMEM; - - vmd = devm_kzalloc(&dev->dev, sizeof(*vmd), GFP_KERNEL); - if (!vmd) - return -ENOMEM; - - vmd->dev = dev; - err = pcim_enable_device(dev); - if (err < 0) - return err; - - vmd->cfgbar = pcim_iomap(dev, VMD_CFGBAR, 0); - if (!vmd->cfgbar) - return -ENOMEM; - - pci_set_master(dev); - if (dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(64)) && - dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32))) - return -ENODEV; - - vmd->msix_count = pci_msix_vec_count(dev); - if (vmd->msix_count < 0) - return -ENODEV; - - vmd->msix_count = pci_alloc_irq_vectors(dev, 1, vmd->msix_count, - PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); - if (vmd->msix_count < 0) - return vmd->msix_count; - - vmd->irqs = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->irqs), - GFP_KERNEL); - if (!vmd->irqs) - return -ENOMEM; - - for (i = 0; i < vmd->msix_count; i++) { - INIT_LIST_HEAD(&vmd->irqs[i].irq_list); - err = devm_request_irq(&dev->dev, pci_irq_vector(dev, i), - vmd_irq, 0, "vmd", &vmd->irqs[i]); - if (err) - return err; - } - - spin_lock_init(&vmd->cfg_lock); - pci_set_drvdata(dev, vmd); - err = vmd_enable_domain(vmd); - if (err) - return err; - - dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n", - vmd->sysdata.domain); - return 0; -} - -static void vmd_remove(struct pci_dev *dev) -{ - struct vmd_dev *vmd = pci_get_drvdata(dev); - - vmd_detach_resources(vmd); - pci_set_drvdata(dev, NULL); - sysfs_remove_link(&vmd->dev->dev.kobj, "domain"); - pci_stop_root_bus(vmd->bus); - pci_remove_root_bus(vmd->bus); - vmd_teardown_dma_ops(vmd); - irq_domain_remove(vmd->irq_domain); -} - -#ifdef CONFIG_PM -static int vmd_suspend(struct device *dev) -{ - struct pci_dev *pdev = to_pci_dev(dev); - - pci_save_state(pdev); - return 0; -} - -static int vmd_resume(struct device *dev) -{ - struct pci_dev *pdev = to_pci_dev(dev); - - pci_restore_state(pdev); - return 0; -} -#endif -static SIMPLE_DEV_PM_OPS(vmd_dev_pm_ops, vmd_suspend, vmd_resume); - -static const struct pci_device_id vmd_ids[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x201d),}, - {0,} -}; -MODULE_DEVICE_TABLE(pci, vmd_ids); - -static struct pci_driver vmd_drv = { - .name = "vmd", - .id_table = vmd_ids, - .probe = vmd_probe, - .remove = vmd_remove, - .driver = { - .pm = &vmd_dev_pm_ops, - }, -}; -module_pci_driver(vmd_drv); - -MODULE_AUTHOR("Intel Corporation"); -MODULE_LICENSE("GPL v2"); -MODULE_VERSION("0.6"); diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 9b485d8..93865eb 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -274,4 +274,20 @@ config PCIE_ARTPEC6 Say Y here to enable PCIe controller support on Axis ARTPEC-6 SoCs. This PCIe controller uses the DesignWare core. +config VMD + depends on PCI_MSI && X86_64 + tristate "Intel Volume Management Device Driver" + default N + ---help--- + Adds support for the Intel Volume Management Device (VMD). VMD is a + secondary PCI host bridge that allows PCI Express root ports, + and devices attached to them, to be removed from the default + PCI domain and placed within the VMD domain. This provides + more bus resources than are otherwise possible with a + single domain. If you know your system provides one of these and + has devices attached to it, say Y; if you are not sure, say N. + + To compile this driver as a module, choose M here: the + module will be called vmd. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 8843410..afea1c6 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o obj-$(CONFIG_PCIE_ARMADA_8K) += pcie-armada8k.o obj-$(CONFIG_PCIE_ARTPEC6) += pcie-artpec6.o +obj-$(CONFIG_VMD) += vmd.o diff --git a/drivers/pci/host/vmd.c b/drivers/pci/host/vmd.c new file mode 100644 index 0000000..5705852 --- /dev/null +++ b/drivers/pci/host/vmd.c @@ -0,0 +1,761 @@ +/* + * Volume Management Device driver + * Copyright (c) 2015, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define VMD_CFGBAR 0 +#define VMD_MEMBAR1 2 +#define VMD_MEMBAR2 4 + +/* + * Lock for manipulating VMD IRQ lists. + */ +static DEFINE_RAW_SPINLOCK(list_lock); + +/** + * struct vmd_irq - private data to map driver IRQ to the VMD shared vector + * @node: list item for parent traversal. + * @rcu: RCU callback item for freeing. + * @irq: back pointer to parent. + * @virq: the virtual IRQ value provided to the requesting driver. + * + * Every MSI/MSI-X IRQ requested for a device in a VMD domain will be mapped to + * a VMD IRQ using this structure. + */ +struct vmd_irq { + struct list_head node; + struct rcu_head rcu; + struct vmd_irq_list *irq; + unsigned int virq; +}; + +/** + * struct vmd_irq_list - list of driver requested IRQs mapping to a VMD vector + * @irq_list: the list of irq's the VMD one demuxes to. + * @count: number of child IRQs assigned to this vector; used to track + * sharing. + */ +struct vmd_irq_list { + struct list_head irq_list; + unsigned int count; +}; + +struct vmd_dev { + struct pci_dev *dev; + + spinlock_t cfg_lock; + char __iomem *cfgbar; + + int msix_count; + struct vmd_irq_list *irqs; + + struct pci_sysdata sysdata; + struct resource resources[3]; + struct irq_domain *irq_domain; + struct pci_bus *bus; + +#ifdef CONFIG_X86_DEV_DMA_OPS + struct dma_map_ops dma_ops; + struct dma_domain dma_domain; +#endif +}; + +static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) +{ + return container_of(bus->sysdata, struct vmd_dev, sysdata); +} + +static inline unsigned int index_from_irqs(struct vmd_dev *vmd, + struct vmd_irq_list *irqs) +{ + return irqs - vmd->irqs; +} + +/* + * Drivers managing a device in a VMD domain allocate their own IRQs as before, + * but the MSI entry for the hardware it's driving will be programmed with a + * destination ID for the VMD MSI-X table. The VMD muxes interrupts in its + * domain into one of its own, and the VMD driver de-muxes these for the + * handlers sharing that VMD IRQ. The vmd irq_domain provides the operations + * and irq_chip to set this up. + */ +static void vmd_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct vmd_irq *vmdirq = data->chip_data; + struct vmd_irq_list *irq = vmdirq->irq; + struct vmd_dev *vmd = irq_data_get_irq_handler_data(data); + + msg->address_hi = MSI_ADDR_BASE_HI; + msg->address_lo = MSI_ADDR_BASE_LO | + MSI_ADDR_DEST_ID(index_from_irqs(vmd, irq)); + msg->data = 0; +} + +/* + * We rely on MSI_FLAG_USE_DEF_CHIP_OPS to set the IRQ mask/unmask ops. + */ +static void vmd_irq_enable(struct irq_data *data) +{ + struct vmd_irq *vmdirq = data->chip_data; + unsigned long flags; + + raw_spin_lock_irqsave(&list_lock, flags); + list_add_tail_rcu(&vmdirq->node, &vmdirq->irq->irq_list); + raw_spin_unlock_irqrestore(&list_lock, flags); + + data->chip->irq_unmask(data); +} + +static void vmd_irq_disable(struct irq_data *data) +{ + struct vmd_irq *vmdirq = data->chip_data; + unsigned long flags; + + data->chip->irq_mask(data); + + raw_spin_lock_irqsave(&list_lock, flags); + list_del_rcu(&vmdirq->node); + INIT_LIST_HEAD_RCU(&vmdirq->node); + raw_spin_unlock_irqrestore(&list_lock, flags); +} + +/* + * XXX: Stubbed until we develop acceptable way to not create conflicts with + * other devices sharing the same vector. + */ +static int vmd_irq_set_affinity(struct irq_data *data, + const struct cpumask *dest, bool force) +{ + return -EINVAL; +} + +static struct irq_chip vmd_msi_controller = { + .name = "VMD-MSI", + .irq_enable = vmd_irq_enable, + .irq_disable = vmd_irq_disable, + .irq_compose_msi_msg = vmd_compose_msi_msg, + .irq_set_affinity = vmd_irq_set_affinity, +}; + +static irq_hw_number_t vmd_get_hwirq(struct msi_domain_info *info, + msi_alloc_info_t *arg) +{ + return 0; +} + +/* + * XXX: We can be even smarter selecting the best IRQ once we solve the + * affinity problem. + */ +static struct vmd_irq_list *vmd_next_irq(struct vmd_dev *vmd, struct msi_desc *desc) +{ + int i, best = 1; + unsigned long flags; + + if (!desc->msi_attrib.is_msix || vmd->msix_count == 1) + return &vmd->irqs[0]; + + raw_spin_lock_irqsave(&list_lock, flags); + for (i = 1; i < vmd->msix_count; i++) + if (vmd->irqs[i].count < vmd->irqs[best].count) + best = i; + vmd->irqs[best].count++; + raw_spin_unlock_irqrestore(&list_lock, flags); + + return &vmd->irqs[best]; +} + +static int vmd_msi_init(struct irq_domain *domain, struct msi_domain_info *info, + unsigned int virq, irq_hw_number_t hwirq, + msi_alloc_info_t *arg) +{ + struct msi_desc *desc = arg->desc; + struct vmd_dev *vmd = vmd_from_bus(msi_desc_to_pci_dev(desc)->bus); + struct vmd_irq *vmdirq = kzalloc(sizeof(*vmdirq), GFP_KERNEL); + unsigned int index, vector; + + if (!vmdirq) + return -ENOMEM; + + INIT_LIST_HEAD(&vmdirq->node); + vmdirq->irq = vmd_next_irq(vmd, desc); + vmdirq->virq = virq; + index = index_from_irqs(vmd, vmdirq->irq); + vector = pci_irq_vector(vmd->dev, index); + + irq_domain_set_info(domain, virq, vector, info->chip, vmdirq, + handle_untracked_irq, vmd, NULL); + return 0; +} + +static void vmd_msi_free(struct irq_domain *domain, + struct msi_domain_info *info, unsigned int virq) +{ + struct vmd_irq *vmdirq = irq_get_chip_data(virq); + unsigned long flags; + + synchronize_rcu(); + + /* XXX: Potential optimization to rebalance */ + raw_spin_lock_irqsave(&list_lock, flags); + vmdirq->irq->count--; + raw_spin_unlock_irqrestore(&list_lock, flags); + + kfree_rcu(vmdirq, rcu); +} + +static int vmd_msi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *arg) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct vmd_dev *vmd = vmd_from_bus(pdev->bus); + + if (nvec > vmd->msix_count) + return vmd->msix_count; + + memset(arg, 0, sizeof(*arg)); + return 0; +} + +static void vmd_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) +{ + arg->desc = desc; +} + +static struct msi_domain_ops vmd_msi_domain_ops = { + .get_hwirq = vmd_get_hwirq, + .msi_init = vmd_msi_init, + .msi_free = vmd_msi_free, + .msi_prepare = vmd_msi_prepare, + .set_desc = vmd_set_desc, +}; + +static struct msi_domain_info vmd_msi_domain_info = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_PCI_MSIX, + .ops = &vmd_msi_domain_ops, + .chip = &vmd_msi_controller, +}; + +#ifdef CONFIG_X86_DEV_DMA_OPS +/* + * VMD replaces the requester ID with its own. DMA mappings for devices in a + * VMD domain need to be mapped for the VMD, not the device requiring + * the mapping. + */ +static struct device *to_vmd_dev(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct vmd_dev *vmd = vmd_from_bus(pdev->bus); + + return &vmd->dev->dev; +} + +static struct dma_map_ops *vmd_dma_ops(struct device *dev) +{ + return get_dma_ops(to_vmd_dev(dev)); +} + +static void *vmd_alloc(struct device *dev, size_t size, dma_addr_t *addr, + gfp_t flag, unsigned long attrs) +{ + return vmd_dma_ops(dev)->alloc(to_vmd_dev(dev), size, addr, flag, + attrs); +} + +static void vmd_free(struct device *dev, size_t size, void *vaddr, + dma_addr_t addr, unsigned long attrs) +{ + return vmd_dma_ops(dev)->free(to_vmd_dev(dev), size, vaddr, addr, + attrs); +} + +static int vmd_mmap(struct device *dev, struct vm_area_struct *vma, + void *cpu_addr, dma_addr_t addr, size_t size, + unsigned long attrs) +{ + return vmd_dma_ops(dev)->mmap(to_vmd_dev(dev), vma, cpu_addr, addr, + size, attrs); +} + +static int vmd_get_sgtable(struct device *dev, struct sg_table *sgt, + void *cpu_addr, dma_addr_t addr, size_t size, + unsigned long attrs) +{ + return vmd_dma_ops(dev)->get_sgtable(to_vmd_dev(dev), sgt, cpu_addr, + addr, size, attrs); +} + +static dma_addr_t vmd_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, + enum dma_data_direction dir, + unsigned long attrs) +{ + return vmd_dma_ops(dev)->map_page(to_vmd_dev(dev), page, offset, size, + dir, attrs); +} + +static void vmd_unmap_page(struct device *dev, dma_addr_t addr, size_t size, + enum dma_data_direction dir, unsigned long attrs) +{ + vmd_dma_ops(dev)->unmap_page(to_vmd_dev(dev), addr, size, dir, attrs); +} + +static int vmd_map_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, unsigned long attrs) +{ + return vmd_dma_ops(dev)->map_sg(to_vmd_dev(dev), sg, nents, dir, attrs); +} + +static void vmd_unmap_sg(struct device *dev, struct scatterlist *sg, int nents, + enum dma_data_direction dir, unsigned long attrs) +{ + vmd_dma_ops(dev)->unmap_sg(to_vmd_dev(dev), sg, nents, dir, attrs); +} + +static void vmd_sync_single_for_cpu(struct device *dev, dma_addr_t addr, + size_t size, enum dma_data_direction dir) +{ + vmd_dma_ops(dev)->sync_single_for_cpu(to_vmd_dev(dev), addr, size, dir); +} + +static void vmd_sync_single_for_device(struct device *dev, dma_addr_t addr, + size_t size, enum dma_data_direction dir) +{ + vmd_dma_ops(dev)->sync_single_for_device(to_vmd_dev(dev), addr, size, + dir); +} + +static void vmd_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir) +{ + vmd_dma_ops(dev)->sync_sg_for_cpu(to_vmd_dev(dev), sg, nents, dir); +} + +static void vmd_sync_sg_for_device(struct device *dev, struct scatterlist *sg, + int nents, enum dma_data_direction dir) +{ + vmd_dma_ops(dev)->sync_sg_for_device(to_vmd_dev(dev), sg, nents, dir); +} + +static int vmd_mapping_error(struct device *dev, dma_addr_t addr) +{ + return vmd_dma_ops(dev)->mapping_error(to_vmd_dev(dev), addr); +} + +static int vmd_dma_supported(struct device *dev, u64 mask) +{ + return vmd_dma_ops(dev)->dma_supported(to_vmd_dev(dev), mask); +} + +#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK +static u64 vmd_get_required_mask(struct device *dev) +{ + return vmd_dma_ops(dev)->get_required_mask(to_vmd_dev(dev)); +} +#endif + +static void vmd_teardown_dma_ops(struct vmd_dev *vmd) +{ + struct dma_domain *domain = &vmd->dma_domain; + + if (get_dma_ops(&vmd->dev->dev)) + del_dma_domain(domain); +} + +#define ASSIGN_VMD_DMA_OPS(source, dest, fn) \ + do { \ + if (source->fn) \ + dest->fn = vmd_##fn; \ + } while (0) + +static void vmd_setup_dma_ops(struct vmd_dev *vmd) +{ + const struct dma_map_ops *source = get_dma_ops(&vmd->dev->dev); + struct dma_map_ops *dest = &vmd->dma_ops; + struct dma_domain *domain = &vmd->dma_domain; + + domain->domain_nr = vmd->sysdata.domain; + domain->dma_ops = dest; + + if (!source) + return; + ASSIGN_VMD_DMA_OPS(source, dest, alloc); + ASSIGN_VMD_DMA_OPS(source, dest, free); + ASSIGN_VMD_DMA_OPS(source, dest, mmap); + ASSIGN_VMD_DMA_OPS(source, dest, get_sgtable); + ASSIGN_VMD_DMA_OPS(source, dest, map_page); + ASSIGN_VMD_DMA_OPS(source, dest, unmap_page); + ASSIGN_VMD_DMA_OPS(source, dest, map_sg); + ASSIGN_VMD_DMA_OPS(source, dest, unmap_sg); + ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_cpu); + ASSIGN_VMD_DMA_OPS(source, dest, sync_single_for_device); + ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_cpu); + ASSIGN_VMD_DMA_OPS(source, dest, sync_sg_for_device); + ASSIGN_VMD_DMA_OPS(source, dest, mapping_error); + ASSIGN_VMD_DMA_OPS(source, dest, dma_supported); +#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK + ASSIGN_VMD_DMA_OPS(source, dest, get_required_mask); +#endif + add_dma_domain(domain); +} +#undef ASSIGN_VMD_DMA_OPS +#else +static void vmd_teardown_dma_ops(struct vmd_dev *vmd) {} +static void vmd_setup_dma_ops(struct vmd_dev *vmd) {} +#endif + +static char __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus, + unsigned int devfn, int reg, int len) +{ + char __iomem *addr = vmd->cfgbar + + (bus->number << 20) + (devfn << 12) + reg; + + if ((addr - vmd->cfgbar) + len >= + resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; + + return addr; +} + +/* + * CPU may deadlock if config space is not serialized on some versions of this + * hardware, so all config space access is done under a spinlock. + */ +static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg, + int len, u32 *value) +{ + struct vmd_dev *vmd = vmd_from_bus(bus); + char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); + unsigned long flags; + int ret = 0; + + if (!addr) + return -EFAULT; + + spin_lock_irqsave(&vmd->cfg_lock, flags); + switch (len) { + case 1: + *value = readb(addr); + break; + case 2: + *value = readw(addr); + break; + case 4: + *value = readl(addr); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&vmd->cfg_lock, flags); + return ret; +} + +/* + * VMD h/w converts non-posted config writes to posted memory writes. The + * read-back in this function forces the completion so it returns only after + * the config space was written, as expected. + */ +static int vmd_pci_write(struct pci_bus *bus, unsigned int devfn, int reg, + int len, u32 value) +{ + struct vmd_dev *vmd = vmd_from_bus(bus); + char __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len); + unsigned long flags; + int ret = 0; + + if (!addr) + return -EFAULT; + + spin_lock_irqsave(&vmd->cfg_lock, flags); + switch (len) { + case 1: + writeb(value, addr); + readb(addr); + break; + case 2: + writew(value, addr); + readw(addr); + break; + case 4: + writel(value, addr); + readl(addr); + break; + default: + ret = -EINVAL; + break; + } + spin_unlock_irqrestore(&vmd->cfg_lock, flags); + return ret; +} + +static struct pci_ops vmd_ops = { + .read = vmd_pci_read, + .write = vmd_pci_write, +}; + +static void vmd_attach_resources(struct vmd_dev *vmd) +{ + vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[1]; + vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[2]; +} + +static void vmd_detach_resources(struct vmd_dev *vmd) +{ + vmd->dev->resource[VMD_MEMBAR1].child = NULL; + vmd->dev->resource[VMD_MEMBAR2].child = NULL; +} + +/* + * VMD domains start at 0x1000 to not clash with ACPI _SEG domains. + */ +static int vmd_find_free_domain(void) +{ + int domain = 0xffff; + struct pci_bus *bus = NULL; + + while ((bus = pci_find_next_bus(bus)) != NULL) + domain = max_t(int, domain, pci_domain_nr(bus)); + return domain + 1; +} + +static int vmd_enable_domain(struct vmd_dev *vmd) +{ + struct pci_sysdata *sd = &vmd->sysdata; + struct resource *res; + u32 upper_bits; + unsigned long flags; + LIST_HEAD(resources); + + res = &vmd->dev->resource[VMD_CFGBAR]; + vmd->resources[0] = (struct resource) { + .name = "VMD CFGBAR", + .start = 0, + .end = (resource_size(res) >> 20) - 1, + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + }; + + /* + * If the window is below 4GB, clear IORESOURCE_MEM_64 so we can + * put 32-bit resources in the window. + * + * There's no hardware reason why a 64-bit window *couldn't* + * contain a 32-bit resource, but pbus_size_mem() computes the + * bridge window size assuming a 64-bit window will contain no + * 32-bit resources. __pci_assign_resource() enforces that + * artificial restriction to make sure everything will fit. + * + * The only way we could use a 64-bit non-prefechable MEMBAR is + * if its address is <4GB so that we can convert it to a 32-bit + * resource. To be visible to the host OS, all VMD endpoints must + * be initially configured by platform BIOS, which includes setting + * up these resources. We can assume the device is configured + * according to the platform needs. + */ + res = &vmd->dev->resource[VMD_MEMBAR1]; + upper_bits = upper_32_bits(res->end); + flags = res->flags & ~IORESOURCE_SIZEALIGN; + if (!upper_bits) + flags &= ~IORESOURCE_MEM_64; + vmd->resources[1] = (struct resource) { + .name = "VMD MEMBAR1", + .start = res->start, + .end = res->end, + .flags = flags, + .parent = res, + }; + + res = &vmd->dev->resource[VMD_MEMBAR2]; + upper_bits = upper_32_bits(res->end); + flags = res->flags & ~IORESOURCE_SIZEALIGN; + if (!upper_bits) + flags &= ~IORESOURCE_MEM_64; + vmd->resources[2] = (struct resource) { + .name = "VMD MEMBAR2", + .start = res->start + 0x2000, + .end = res->end, + .flags = flags, + .parent = res, + }; + + sd->domain = vmd_find_free_domain(); + if (sd->domain < 0) + return sd->domain; + + sd->node = pcibus_to_node(vmd->dev->bus); + + vmd->irq_domain = pci_msi_create_irq_domain(NULL, &vmd_msi_domain_info, + x86_vector_domain); + if (!vmd->irq_domain) + return -ENODEV; + + pci_add_resource(&resources, &vmd->resources[0]); + pci_add_resource(&resources, &vmd->resources[1]); + pci_add_resource(&resources, &vmd->resources[2]); + vmd->bus = pci_create_root_bus(&vmd->dev->dev, 0, &vmd_ops, sd, + &resources); + if (!vmd->bus) { + pci_free_resource_list(&resources); + irq_domain_remove(vmd->irq_domain); + return -ENODEV; + } + + vmd_attach_resources(vmd); + vmd_setup_dma_ops(vmd); + dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain); + pci_rescan_bus(vmd->bus); + + WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj, + "domain"), "Can't create symlink to domain\n"); + return 0; +} + +static irqreturn_t vmd_irq(int irq, void *data) +{ + struct vmd_irq_list *irqs = data; + struct vmd_irq *vmdirq; + + rcu_read_lock(); + list_for_each_entry_rcu(vmdirq, &irqs->irq_list, node) + generic_handle_irq(vmdirq->virq); + rcu_read_unlock(); + + return IRQ_HANDLED; +} + +static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct vmd_dev *vmd; + int i, err; + + if (resource_size(&dev->resource[VMD_CFGBAR]) < (1 << 20)) + return -ENOMEM; + + vmd = devm_kzalloc(&dev->dev, sizeof(*vmd), GFP_KERNEL); + if (!vmd) + return -ENOMEM; + + vmd->dev = dev; + err = pcim_enable_device(dev); + if (err < 0) + return err; + + vmd->cfgbar = pcim_iomap(dev, VMD_CFGBAR, 0); + if (!vmd->cfgbar) + return -ENOMEM; + + pci_set_master(dev); + if (dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(64)) && + dma_set_mask_and_coherent(&dev->dev, DMA_BIT_MASK(32))) + return -ENODEV; + + vmd->msix_count = pci_msix_vec_count(dev); + if (vmd->msix_count < 0) + return -ENODEV; + + vmd->msix_count = pci_alloc_irq_vectors(dev, 1, vmd->msix_count, + PCI_IRQ_MSIX | PCI_IRQ_AFFINITY); + if (vmd->msix_count < 0) + return vmd->msix_count; + + vmd->irqs = devm_kcalloc(&dev->dev, vmd->msix_count, sizeof(*vmd->irqs), + GFP_KERNEL); + if (!vmd->irqs) + return -ENOMEM; + + for (i = 0; i < vmd->msix_count; i++) { + INIT_LIST_HEAD(&vmd->irqs[i].irq_list); + err = devm_request_irq(&dev->dev, pci_irq_vector(dev, i), + vmd_irq, 0, "vmd", &vmd->irqs[i]); + if (err) + return err; + } + + spin_lock_init(&vmd->cfg_lock); + pci_set_drvdata(dev, vmd); + err = vmd_enable_domain(vmd); + if (err) + return err; + + dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n", + vmd->sysdata.domain); + return 0; +} + +static void vmd_remove(struct pci_dev *dev) +{ + struct vmd_dev *vmd = pci_get_drvdata(dev); + + vmd_detach_resources(vmd); + pci_set_drvdata(dev, NULL); + sysfs_remove_link(&vmd->dev->dev.kobj, "domain"); + pci_stop_root_bus(vmd->bus); + pci_remove_root_bus(vmd->bus); + vmd_teardown_dma_ops(vmd); + irq_domain_remove(vmd->irq_domain); +} + +#ifdef CONFIG_PM +static int vmd_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + pci_save_state(pdev); + return 0; +} + +static int vmd_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + pci_restore_state(pdev); + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(vmd_dev_pm_ops, vmd_suspend, vmd_resume); + +static const struct pci_device_id vmd_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x201d),}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, vmd_ids); + +static struct pci_driver vmd_drv = { + .name = "vmd", + .id_table = vmd_ids, + .probe = vmd_probe, + .remove = vmd_remove, + .driver = { + .pm = &vmd_dev_pm_ops, + }, +}; +module_pci_driver(vmd_drv); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.6"); -- cgit v0.10.2