From 53eeb48b49410a47a0309bbc0516534ad71c1350 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Fri, 15 Jan 2016 19:56:47 +0100 Subject: PCI: imx6: Move imx6_pcie_reset_phy() near other PHY handling functions Move imx6_pcie_reset_phy() near the other PHY related functions in the file. This is a cosmetic change, but also allows to do the following changes without introducing needless forward declarations. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-imx6.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index fe600964fa50..52c7a0b1254b 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -202,6 +202,23 @@ static int pcie_phy_write(void __iomem *dbi_base, int addr, int data) return 0; } +static void imx6_pcie_reset_phy(struct pcie_port *pp) +{ + u32 tmp; + + pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); + tmp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN | + PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); + + usleep_range(2000, 3000); + + pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); + tmp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN | + PHY_RX_OVRD_IN_LO_RX_PLL_EN); + pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); +} + /* Added for PCI abort handling */ static int imx6q_pcie_abort_handler(unsigned long addr, unsigned int fsr, struct pt_regs *regs) @@ -441,23 +458,6 @@ static void imx6_pcie_host_init(struct pcie_port *pp) dw_pcie_msi_init(pp); } -static void imx6_pcie_reset_phy(struct pcie_port *pp) -{ - u32 tmp; - - pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); - tmp |= (PHY_RX_OVRD_IN_LO_RX_DATA_EN | - PHY_RX_OVRD_IN_LO_RX_PLL_EN); - pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); - - usleep_range(2000, 3000); - - pcie_phy_read(pp->dbi_base, PHY_RX_OVRD_IN_LO, &tmp); - tmp &= ~(PHY_RX_OVRD_IN_LO_RX_DATA_EN | - PHY_RX_OVRD_IN_LO_RX_PLL_EN); - pcie_phy_write(pp->dbi_base, PHY_RX_OVRD_IN_LO, tmp); -} - static int imx6_pcie_link_up(struct pcie_port *pp) { u32 rc, debug_r0, rx_valid; -- cgit v1.2.3-59-g8ed1b From 54a47a83421a3b7ee0e0fab7f65d04179bdf59b6 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 25 Jan 2016 16:49:53 -0600 Subject: PCI: imx6: Move PHY reset into imx6_pcie_establish_link() This adds the PHY reset into a common error path of imx6_pcie_establish_link(), deduplicating some of the debug prints. Also reduce the severity of the "no-link" message in the one place where it is expected to be hit when no peripheral is attached. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-imx6.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 52c7a0b1254b..69f47ef2a264 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -355,10 +355,6 @@ static int imx6_pcie_wait_for_link(struct pcie_port *pp) usleep_range(100, 1000); } - dev_err(pp->dev, "phy link never came up\n"); - dev_dbg(pp->dev, "DEBUG_R0: 0x%08x, DEBUG_R1: 0x%08x\n", - readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), - readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); return -EINVAL; } @@ -407,8 +403,10 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) IMX6Q_GPR12_PCIE_CTL_2, 1 << 10); ret = imx6_pcie_wait_for_link(pp); - if (ret) - return ret; + if (ret) { + dev_info(pp->dev, "Link never came up\n"); + goto err_reset_phy; + } /* Allow Gen2 mode after the link is up. */ tmp = readl(pp->dbi_base + PCIE_RC_LCR); @@ -427,19 +425,28 @@ static int imx6_pcie_establish_link(struct pcie_port *pp) ret = imx6_pcie_wait_for_speed_change(pp); if (ret) { dev_err(pp->dev, "Failed to bring link up!\n"); - return ret; + goto err_reset_phy; } /* Make sure link training is finished as well! */ ret = imx6_pcie_wait_for_link(pp); if (ret) { dev_err(pp->dev, "Failed to bring link up!\n"); - return ret; + goto err_reset_phy; } tmp = readl(pp->dbi_base + PCIE_RC_LCSR); dev_dbg(pp->dev, "Link up, Gen=%i\n", (tmp >> 16) & 0xf); + return 0; + +err_reset_phy: + dev_dbg(pp->dev, "PHY DEBUG_R0=0x%08x DEBUG_R1=0x%08x\n", + readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), + readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); + imx6_pcie_reset_phy(pp); + + return ret; } static void imx6_pcie_host_init(struct pcie_port *pp) @@ -510,11 +517,6 @@ static int imx6_pcie_link_up(struct pcie_port *pp) if ((debug_r0 & 0x3f) != 0x0d) return 0; - dev_err(pp->dev, "transition to gen2 is stuck, reset PHY!\n"); - dev_dbg(pp->dev, "debug_r0=%08x debug_r1=%08x\n", debug_r0, rc); - - imx6_pcie_reset_phy(pp); - return 0; } -- cgit v1.2.3-59-g8ed1b From a77c5422d7586003643377afdb9915e76d07d21c Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 25 Jan 2016 16:49:57 -0600 Subject: PCI: imx6: Remove broken Gen2 workaround Remove the remnants of the workaround for erratum ERR005184 which was never completely implemented. The checks alone don't carry any value as we don't act properly on the result. A workaround should be added to the lane speed change in establish_link later. Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-imx6.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 69f47ef2a264..6d7dfdcf4d5c 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -467,7 +467,7 @@ static void imx6_pcie_host_init(struct pcie_port *pp) static int imx6_pcie_link_up(struct pcie_port *pp) { - u32 rc, debug_r0, rx_valid; + u32 rc; int count = 5; /* @@ -501,21 +501,6 @@ static int imx6_pcie_link_up(struct pcie_port *pp) */ usleep_range(1000, 2000); } - /* - * From L0, initiate MAC entry to gen2 if EP/RC supports gen2. - * Wait 2ms (LTSSM timeout is 24ms, PHY lock is ~5us in gen2). - * If (MAC/LTSSM.state == Recovery.RcvrLock) - * && (PHY/rx_valid==0) then pulse PHY/rx_reset. Transition - * to gen2 is stuck - */ - pcie_phy_read(pp->dbi_base, PCIE_PHY_RX_ASIC_OUT, &rx_valid); - debug_r0 = readl(pp->dbi_base + PCIE_PHY_DEBUG_R0); - - if (rx_valid & PCIE_PHY_RX_ASIC_OUT_VALID) - return 0; - - if ((debug_r0 & 0x3f) != 0x0d) - return 0; return 0; } -- cgit v1.2.3-59-g8ed1b From 4d107d3b5a686b5834e533a00b73bf7b1cf59df7 Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Mon, 25 Jan 2016 16:50:02 -0600 Subject: PCI: imx6: Move link up check into imx6_pcie_wait_for_link() imx6_pcie_link_up() previously used usleep_range() to wait for the link to come up. Since it may be called while holding the config spinlock, the sleep causes a "BUG: scheduling while atomic" error. Instead of waiting for the link to come up in imx6_pcie_link_up(), do the waiting in imx6_pcie_wait_for_link(), where we're not holding a lock and sleeping is allowed. [bhelgaas: changelog, references to bugzilla and f95d3ae77191] Link: https://bugzilla.kernel.org/show_bug.cgi?id=100031 Fixes: f95d3ae77191 ("PCI: imx6: Wait for retraining") Signed-off-by: Lucas Stach Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-imx6.c | 60 +++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index 6d7dfdcf4d5c..bd3f7d0ef943 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -349,10 +349,28 @@ static int imx6_pcie_wait_for_link(struct pcie_port *pp) { unsigned int retries; + /* + * Test if the PHY reports that the link is up and also that the LTSSM + * training finished. There are three possible states of the link when + * this code is called: + * 1) The link is DOWN (unlikely) + * The link didn't come up yet for some reason. This usually means + * we have a real problem somewhere, if it happens with a peripheral + * connected. This state calls for inspection of the DEBUG registers. + * 2) The link is UP, but still in LTSSM training + * Wait for the training to finish, which should take a very short + * time. If the training does not finish, we have a problem and we + * need to inspect the DEBUG registers. If the training does finish, + * the link is up and operating correctly. + * 3) The link is UP and no longer in LTSSM training + * The link is up and operating correctly. + */ for (retries = 0; retries < 200; retries++) { - if (dw_pcie_link_up(pp)) + u32 reg = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); + if ((reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_UP) && + !(reg & PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING)) return 0; - usleep_range(100, 1000); + usleep_range(1000, 2000); } return -EINVAL; @@ -467,42 +485,8 @@ static void imx6_pcie_host_init(struct pcie_port *pp) static int imx6_pcie_link_up(struct pcie_port *pp) { - u32 rc; - int count = 5; - - /* - * Test if the PHY reports that the link is up and also that the LTSSM - * training finished. There are three possible states of the link when - * this code is called: - * 1) The link is DOWN (unlikely) - * The link didn't come up yet for some reason. This usually means - * we have a real problem somewhere. Reset the PHY and exit. This - * state calls for inspection of the DEBUG registers. - * 2) The link is UP, but still in LTSSM training - * Wait for the training to finish, which should take a very short - * time. If the training does not finish, we have a problem and we - * need to inspect the DEBUG registers. If the training does finish, - * the link is up and operating correctly. - * 3) The link is UP and no longer in LTSSM training - * The link is up and operating correctly. - */ - while (1) { - rc = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); - if (!(rc & PCIE_PHY_DEBUG_R1_XMLH_LINK_UP)) - break; - if (!(rc & PCIE_PHY_DEBUG_R1_XMLH_LINK_IN_TRAINING)) - return 1; - if (!count--) - break; - dev_dbg(pp->dev, "Link is up, but still in training\n"); - /* - * Wait a little bit, then re-check if the link finished - * the training. - */ - usleep_range(1000, 2000); - } - - return 0; + return readl(pp->dbi_base + PCIE_PHY_DEBUG_R1) & + PCIE_PHY_DEBUG_R1_XMLH_LINK_UP; } static struct pcie_host_ops imx6_pcie_host_ops = { -- cgit v1.2.3-59-g8ed1b From 3b0a6d1a1b3335fde9cbbc659568ed619b07d24a Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 4 Feb 2016 14:02:45 -0600 Subject: PCI/AER: Rename pci_ops_aer to aer_inj_pci_ops Rename pci_ops_aer to aer_inj_pci_ops pci_read_aer() to aer_inj_read_config() pci_write_aer() to aer_inj_write_config() This is more conventional and more informative. No functional change. Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/aer/aer_inject.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 20db790465dd..148a301a6b3c 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -181,8 +181,8 @@ static u32 *find_pci_config_dword(struct aer_error *err, int where, return target; } -static int pci_read_aer(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 *val) +static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) { u32 *sim; struct aer_error *err; @@ -212,8 +212,8 @@ out: return ops->read(bus, devfn, where, size, val); } -static int pci_write_aer(struct pci_bus *bus, unsigned int devfn, int where, - int size, u32 val) +static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) { u32 *sim; struct aer_error *err; @@ -247,9 +247,9 @@ out: return ops->write(bus, devfn, where, size, val); } -static struct pci_ops pci_ops_aer = { - .read = pci_read_aer, - .write = pci_write_aer, +static struct pci_ops aer_inj_pci_ops = { + .read = aer_inj_read_config, + .write = aer_inj_write_config, }; static void pci_bus_ops_init(struct pci_bus_ops *bus_ops, @@ -270,9 +270,9 @@ static int pci_bus_set_aer_ops(struct pci_bus *bus) bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL); if (!bus_ops) return -ENOMEM; - ops = pci_bus_set_ops(bus, &pci_ops_aer); + ops = pci_bus_set_ops(bus, &aer_inj_pci_ops); spin_lock_irqsave(&inject_lock, flags); - if (ops == &pci_ops_aer) + if (ops == &aer_inj_pci_ops) goto out; pci_bus_ops_init(bus_ops, bus, ops); list_add(&bus_ops->list, &pci_bus_ops_list); -- cgit v1.2.3-59-g8ed1b From 7e8fbdc628760857369af05636ed4ddc4fc8569b Mon Sep 17 00:00:00 2001 From: David Daney Date: Tue, 22 Dec 2015 13:44:51 -0800 Subject: PCI/AER: Restore pci_ops pointer while calling original pci_ops The aer_inject module intercepts config space accesses by replacing the bus->ops pointer. If it forwards accesses to the original pci_ops, and those original ops use bus->ops, they see the aer_pci_ops instead of their own pci_ops, which can cause a crash. For example, pci_generic_config_read() uses the bus->ops->map_bus pointer. If bus->ops is set to aer_pci_ops, which doesn't supply .map_bus, pci_generic_config_read() will dereference an invalid pointer and cause a crash. Temporarily restore the original bus->ops pointer while calling ops->read() or ops->write(). Callers of these functions already hold pci_lock, which prevents other users of bus->ops until we're finished. [bhelgaas: changelog] Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/aer/aer_inject.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 148a301a6b3c..79a5e112711a 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -188,7 +188,9 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, struct aer_error *err; unsigned long flags; struct pci_ops *ops; + struct pci_ops *my_ops; int domain; + int rv; spin_lock_irqsave(&inject_lock, flags); if (size != sizeof(u32)) @@ -208,8 +210,19 @@ static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, } out: ops = __find_pci_bus_ops(bus); + /* + * pci_lock must already be held, so we can directly + * manipulate bus->ops. Many config access functions, + * including pci_generic_config_read() require the original + * bus->ops be installed to function, so temporarily put them + * back. + */ + my_ops = bus->ops; + bus->ops = ops; + rv = ops->read(bus, devfn, where, size, val); + bus->ops = my_ops; spin_unlock_irqrestore(&inject_lock, flags); - return ops->read(bus, devfn, where, size, val); + return rv; } static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, @@ -220,7 +233,9 @@ static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, unsigned long flags; int rw1cs; struct pci_ops *ops; + struct pci_ops *my_ops; int domain; + int rv; spin_lock_irqsave(&inject_lock, flags); if (size != sizeof(u32)) @@ -243,8 +258,19 @@ static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, } out: ops = __find_pci_bus_ops(bus); + /* + * pci_lock must already be held, so we can directly + * manipulate bus->ops. Many config access functions, + * including pci_generic_config_write() require the original + * bus->ops be installed to function, so temporarily put them + * back. + */ + my_ops = bus->ops; + bus->ops = ops; + rv = ops->write(bus, devfn, where, size, val); + bus->ops = my_ops; spin_unlock_irqrestore(&inject_lock, flags); - return ops->write(bus, devfn, where, size, val); + return rv; } static struct pci_ops aer_inj_pci_ops = { -- cgit v1.2.3-59-g8ed1b From 0e6053dc6e7a42c8ba9ce6e81adb3350c7df4bc8 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Fri, 22 Jan 2016 22:50:19 +0800 Subject: PCI/AER: Use list_first_entry_or_null() to simplify code Use list_first_entry_or_null() instead of list_empty() + list_entry() to simplify the code. Signed-off-by: Geliang Tang Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/aer/aer_inject.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 79a5e112711a..e2760a39a98a 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -124,16 +124,13 @@ static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus) static struct pci_bus_ops *pci_bus_ops_pop(void) { unsigned long flags; - struct pci_bus_ops *bus_ops = NULL; + struct pci_bus_ops *bus_ops; spin_lock_irqsave(&inject_lock, flags); - if (list_empty(&pci_bus_ops_list)) - bus_ops = NULL; - else { - struct list_head *lh = pci_bus_ops_list.next; - list_del(lh); - bus_ops = list_entry(lh, struct pci_bus_ops, list); - } + bus_ops = list_first_entry_or_null(&pci_bus_ops_list, + struct pci_bus_ops, list); + if (bus_ops) + list_del(&bus_ops->list); spin_unlock_irqrestore(&inject_lock, flags); return bus_ops; } -- cgit v1.2.3-59-g8ed1b From b404bcfbf035413dcce539c8ba2c9986d220d8ed Mon Sep 17 00:00:00 2001 From: Manish Jaggi Date: Sat, 30 Jan 2016 01:33:58 +0530 Subject: PCI: Add ACS quirk for all Cavium devices Cavium devices matching this quirk do not perform peer-to-peer with other functions, allowing masking out these bits as if they were unimplemented in the ACS capability. Signed-off-by: Manish Jaggi Signed-off-by: Bjorn Helgaas Acked-by: Tirumalesh Chalamarla --- drivers/pci/quirks.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 0575a1e026b4..85fa6a2a6dd2 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3832,6 +3832,19 @@ static int pci_quirk_amd_sb_acs(struct pci_dev *dev, u16 acs_flags) #endif } +static int pci_quirk_cavium_acs(struct pci_dev *dev, u16 acs_flags) +{ + /* + * Cavium devices matching this quirk do not perform peer-to-peer + * with other functions, allowing masking out these bits as if they + * were unimplemented in the ACS capability. + */ + acs_flags &= ~(PCI_ACS_SV | PCI_ACS_TB | PCI_ACS_RR | + PCI_ACS_CR | PCI_ACS_UF | PCI_ACS_DT); + + return acs_flags ? 0 : 1; +} + /* * Many Intel PCH root ports do provide ACS-like features to disable peer * transactions and validate bus numbers in requests, but do not provide an @@ -3984,6 +3997,8 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, pci_quirk_intel_pch_acs }, { 0x19a2, 0x710, pci_quirk_mf_endpoint_acs }, /* Emulex BE3-R */ { 0x10df, 0x720, pci_quirk_mf_endpoint_acs }, /* Emulex Skyhawk-R */ + /* Cavium ThunderX */ + { PCI_VENDOR_ID_CAVIUM, PCI_ANY_ID, pci_quirk_cavium_acs }, { 0 } }; -- cgit v1.2.3-59-g8ed1b From 3487c65659088c8205d8ab726b45469912271bbd Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 29 Jan 2016 11:29:31 +0000 Subject: PCI: rcar: Remove PCI_PROBE_ONLY handling The PCIe rcar host driver is not used in system configurations requiring the PCI_PROBE_ONLY flag to be set to prevent resources assignment, therefore the driver code handling the flag can be removed from the kernel. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Simon Horman Cc: Arnd Bergmann Cc: Phil Edworthy --- drivers/pci/host/pcie-rcar.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-rcar.c b/drivers/pci/host/pcie-rcar.c index 4edb5181f4e2..35092188039b 100644 --- a/drivers/pci/host/pcie-rcar.c +++ b/drivers/pci/host/pcie-rcar.c @@ -390,9 +390,7 @@ static int rcar_pcie_enable(struct rcar_pcie *pcie) rcar_pcie_setup(&res, pcie); - /* Do not reassign resources if probe only */ - if (!pci_has_flag(PCI_PROBE_ONLY)) - pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); + pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); if (IS_ENABLED(CONFIG_PCI_MSI)) bus = pci_scan_root_bus_msi(pcie->dev, pcie->root_bus_nr, @@ -408,13 +406,11 @@ static int rcar_pcie_enable(struct rcar_pcie *pcie) pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); - if (!pci_has_flag(PCI_PROBE_ONLY)) { - pci_bus_size_bridges(bus); - pci_bus_assign_resources(bus); + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); - list_for_each_entry(child, &bus->children, node) - pcie_bus_configure_settings(child); - } + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); pci_bus_add_devices(bus); -- cgit v1.2.3-59-g8ed1b From dbae40b76abef2f8a7e7bf1701f77df9e73def48 Mon Sep 17 00:00:00 2001 From: Yang Shi Date: Wed, 27 Jan 2016 09:32:05 -0800 Subject: PCI: layerscape: Add "fsl,ls2085a-pcie" compatible ID The Layerscape PCI host driver must recognize ls2085a compatible when using firmware with ls2085a compatible property, otherwise the PCI bus won't be detected even though ls2085a compatible is included by the dts. Signed-off-by: Yang Shi Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-layerscape.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/pci/host/pci-layerscape.c b/drivers/pci/host/pci-layerscape.c index 3923bed93c7e..c40d8b2ce330 100644 --- a/drivers/pci/host/pci-layerscape.c +++ b/drivers/pci/host/pci-layerscape.c @@ -203,6 +203,7 @@ static const struct of_device_id ls_pcie_of_match[] = { { .compatible = "fsl,ls1021a-pcie", .data = &ls1021_drvdata }, { .compatible = "fsl,ls1043a-pcie", .data = &ls1043_drvdata }, { .compatible = "fsl,ls2080a-pcie", .data = &ls2080_drvdata }, + { .compatible = "fsl,ls2085a-pcie", .data = &ls2080_drvdata }, { }, }; MODULE_DEVICE_TABLE(of, ls_pcie_of_match); -- cgit v1.2.3-59-g8ed1b From ab1a187bba5c4e5ed8aa6e7bb7dcd7cd7e065aef Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 27 Jan 2016 07:35:07 -0600 Subject: PCI: Check device_attach() return value always Previously we checked the device_attach() return value only when CONFIG_BUG=y. That caused this warning in builds where CONFIG_BUG is not set: drivers/pci/bus.c:237:6: warning: variable 'retval' set but not used [-Wunused-but-set-variable] Check the return value of device_attach() always and clean up after failure. Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki --- drivers/pci/bus.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 89b3befc7155..f2187d491475 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -291,7 +291,12 @@ void pci_bus_add_device(struct pci_dev *dev) dev->match_driver = true; retval = device_attach(&dev->dev); - WARN_ON(retval < 0); + if (retval < 0) { + dev_warn(&dev->dev, "device attach failed (%d)\n", retval); + pci_proc_detach_device(dev); + pci_remove_sysfs_dev_files(dev); + return; + } dev->is_added = 1; } -- cgit v1.2.3-59-g8ed1b From 4e48fe4148698ffd3935800f4967362e80a7ae92 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 5 Feb 2016 14:57:12 -0600 Subject: PCI/PME: Remove redundant port lookup We've already looked up srv->port a few lines earlier, and there's no need to do it again. Remove the redundant lookup. Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki suspend_level = PME_SUSPEND_WAKEUP; } if (!wakeup || ret) { - struct pci_dev *port = srv->port; - pcie_pme_interrupt_enable(port, false); pcie_clear_root_pme_status(port); data->suspend_level = PME_SUSPEND_NOIRQ; -- cgit v1.2.3-59-g8ed1b From 41ccebaecef50e56f822791a52b7bd9e9608e5e6 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 5 Feb 2016 14:57:19 -0600 Subject: PCI/PME: Restructure pcie_pme_suspend() to prevent compiler warning Previously we had this: if (wakeup) ret = enable_irq_wake(...); if (!wakeup || ret) ... "ret" is only evaluated when "wakeup" is true, and it is always initialized in that case, but gcc isn't smart enough to figure that out and warns: drivers/pci/pcie/pme.c:414:14: warning: 'ret' may be used uninitialized in this function [-Wmaybe-uninitialized] Restructure the code slightly to make it easier for gcc (and maybe for humans as well). Signed-off-by: Bjorn Helgaas Acked-by: Rafael J. Wysocki port; - bool wakeup; + bool wakeup, wake_irq_enabled = false; int ret; if (device_may_wakeup(&port->dev)) { @@ -409,9 +409,12 @@ static int pcie_pme_suspend(struct pcie_device *srv) spin_lock_irq(&data->lock); if (wakeup) { ret = enable_irq_wake(srv->irq); - data->suspend_level = PME_SUSPEND_WAKEUP; + if (ret == 0) { + data->suspend_level = PME_SUSPEND_WAKEUP; + wake_irq_enabled = true; + } } - if (!wakeup || ret) { + if (!wake_irq_enabled) { pcie_pme_interrupt_enable(port, false); pcie_clear_root_pme_status(port); data->suspend_level = PME_SUSPEND_NOIRQ; -- cgit v1.2.3-59-g8ed1b From 5bbe029ff7bc8ff82ef31d3480d68c95642a3c7a Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 5 Feb 2016 14:57:47 -0600 Subject: PCI: Move pci_set_flags() from asm-generic/pci-bridge.h to linux/pci.h The PCI flag management constants and functions were previously declared in include/asm-generic/pci-bridge.h. But they are not specific to bridges, and arches did not include pci-bridge.h consistently. Move the following interfaces and related constants to include/linux/pci.h and remove pci-bridge.h: pci_set_flags() pci_add_flags() pci_clear_flags() pci_has_flag() This fixes these warnings when building for some arches: drivers/pci/host/pcie-designware.c:562:20: error: 'PCI_PROBE_ONLY' undeclared (first use in this function) drivers/pci/host/pcie-designware.c:562:7: error: implicit declaration of function 'pci_has_flag' [-Werror=implicit-function-declaration] Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 7 +++++ include/asm-generic/pci-bridge.h | 65 ---------------------------------------- include/linux/pci.h | 22 ++++++++++++++ 3 files changed, 29 insertions(+), 65 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9bb0d5a..ead1ac1dc1e3 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1803,6 +1803,13 @@ static int only_one_child(struct pci_bus *bus) return 0; if (pci_pcie_type(parent) == PCI_EXP_TYPE_ROOT_PORT) return 1; + + /* + * PCIe downstream ports are bridges that normally lead to only a + * device 0, but if PCI_SCAN_ALL_PCIE_DEVS is set, scan all + * possible devices, not just device 0. See PCIe spec r3.0, + * sec 7.3.1. + */ if (parent->has_secondary_link && !pci_has_flag(PCI_SCAN_ALL_PCIE_DEVS)) return 1; diff --git a/include/asm-generic/pci-bridge.h b/include/asm-generic/pci-bridge.h index 20db2e5a0a69..1b1d82f66d57 100644 --- a/include/asm-generic/pci-bridge.h +++ b/include/asm-generic/pci-bridge.h @@ -6,69 +6,4 @@ */ #ifndef _ASM_GENERIC_PCI_BRIDGE_H #define _ASM_GENERIC_PCI_BRIDGE_H - -#ifdef __KERNEL__ - -enum { - /* Force re-assigning all resources (ignore firmware - * setup completely) - */ - PCI_REASSIGN_ALL_RSRC = 0x00000001, - - /* Re-assign all bus numbers */ - PCI_REASSIGN_ALL_BUS = 0x00000002, - - /* Do not try to assign, just use existing setup */ - PCI_PROBE_ONLY = 0x00000004, - - /* Don't bother with ISA alignment unless the bridge has - * ISA forwarding enabled - */ - PCI_CAN_SKIP_ISA_ALIGN = 0x00000008, - - /* Enable domain numbers in /proc */ - PCI_ENABLE_PROC_DOMAINS = 0x00000010, - /* ... except for domain 0 */ - PCI_COMPAT_DOMAIN_0 = 0x00000020, - - /* PCIe downstream ports are bridges that normally lead to only a - * device 0, but if this is set, we scan all possible devices, not - * just device 0. - */ - PCI_SCAN_ALL_PCIE_DEVS = 0x00000040, -}; - -#ifdef CONFIG_PCI -extern unsigned int pci_flags; - -static inline void pci_set_flags(int flags) -{ - pci_flags = flags; -} - -static inline void pci_add_flags(int flags) -{ - pci_flags |= flags; -} - -static inline void pci_clear_flags(int flags) -{ - pci_flags &= ~flags; -} - -static inline int pci_has_flag(int flag) -{ - return pci_flags & flag; -} -#else -static inline void pci_set_flags(int flags) { } -static inline void pci_add_flags(int flags) { } -static inline void pci_clear_flags(int flags) { } -static inline int pci_has_flag(int flag) -{ - return 0; -} -#endif /* CONFIG_PCI */ - -#endif /* __KERNEL__ */ #endif /* _ASM_GENERIC_PCI_BRIDGE_H */ diff --git a/include/linux/pci.h b/include/linux/pci.h index 27df4a6585da..3d371c150753 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -746,9 +746,26 @@ struct pci_driver { .vendor = PCI_VENDOR_ID_##vend, .device = (dev), \ .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, 0, 0 +enum { + PCI_REASSIGN_ALL_RSRC = 0x00000001, /* ignore firmware setup */ + PCI_REASSIGN_ALL_BUS = 0x00000002, /* reassign all bus numbers */ + PCI_PROBE_ONLY = 0x00000004, /* use existing setup */ + PCI_CAN_SKIP_ISA_ALIGN = 0x00000008, /* don't do ISA alignment */ + PCI_ENABLE_PROC_DOMAINS = 0x00000010, /* enable domains in /proc */ + PCI_COMPAT_DOMAIN_0 = 0x00000020, /* ... except domain 0 */ + PCI_SCAN_ALL_PCIE_DEVS = 0x00000040, /* scan all, not just dev 0 */ +}; + /* these external functions are only available when PCI support is enabled */ #ifdef CONFIG_PCI +extern unsigned int pci_flags; + +static inline void pci_set_flags(int flags) { pci_flags = flags; } +static inline void pci_add_flags(int flags) { pci_flags |= flags; } +static inline void pci_clear_flags(int flags) { pci_flags &= ~flags; } +static inline int pci_has_flag(int flag) { return pci_flags & flag; } + void pcie_bus_configure_settings(struct pci_bus *bus); enum pcie_bus_config_types { @@ -1405,6 +1422,11 @@ void pci_register_set_vga_state(arch_set_vga_state_t func); #else /* CONFIG_PCI is not enabled */ +static inline void pci_set_flags(int flags) { } +static inline void pci_add_flags(int flags) { } +static inline void pci_clear_flags(int flags) { } +static inline int pci_has_flag(int flag) { return 0; } + /* * If the system does not have PCI, clearly these return errors. Define * these as simple inline functions to avoid hair in drivers. -- cgit v1.2.3-59-g8ed1b From 5bd28338d681dcbde2c4bee4ebea0c4e0dfcd9e4 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 5 Feb 2016 14:57:54 -0600 Subject: PCI: Remove includes of empty asm-generic/pci-bridge.h include/asm-generic/pci-bridge.h is now empty, so remove every #include of it. Signed-off-by: Bjorn Helgaas Acked-by: Will Deacon (arm64) --- arch/alpha/include/asm/pci.h | 1 - arch/arm/include/asm/pci.h | 3 --- arch/arm64/include/asm/pci.h | 1 - arch/mips/include/asm/pci.h | 1 - arch/powerpc/include/asm/pci-bridge.h | 1 - arch/unicore32/include/asm/pci.h | 2 -- arch/x86/pci/common.c | 1 - drivers/of/of_pci.c | 1 - drivers/pci/pci.c | 1 - drivers/pci/probe.c | 1 - drivers/pci/setup-bus.c | 1 - 11 files changed, 14 deletions(-) (limited to 'drivers') diff --git a/arch/alpha/include/asm/pci.h b/arch/alpha/include/asm/pci.h index 98f2eeee8f68..75d8865d763d 100644 --- a/arch/alpha/include/asm/pci.h +++ b/arch/alpha/include/asm/pci.h @@ -7,7 +7,6 @@ #include #include #include -#include /* * The following structure is used to manage multiple PCI busses. diff --git a/arch/arm/include/asm/pci.h b/arch/arm/include/asm/pci.h index a5635444ca41..d7de19a77d51 100644 --- a/arch/arm/include/asm/pci.h +++ b/arch/arm/include/asm/pci.h @@ -3,8 +3,6 @@ #ifdef __KERNEL__ #include -#include - #include /* for pci_sys_data */ extern unsigned long pcibios_min_io; @@ -41,5 +39,4 @@ static inline int pci_get_legacy_ide_irq(struct pci_dev *dev, int channel) } #endif /* __KERNEL__ */ - #endif diff --git a/arch/arm64/include/asm/pci.h b/arch/arm64/include/asm/pci.h index b008a72f8bc0..f75b04e8d732 100644 --- a/arch/arm64/include/asm/pci.h +++ b/arch/arm64/include/asm/pci.h @@ -7,7 +7,6 @@ #include #include -#include #include #define PCIBIOS_MIN_IO 0x1000 diff --git a/arch/mips/include/asm/pci.h b/arch/mips/include/asm/pci.h index 98c31e5d9579..108d19376bb1 100644 --- a/arch/mips/include/asm/pci.h +++ b/arch/mips/include/asm/pci.h @@ -102,7 +102,6 @@ static inline void pci_resource_to_user(const struct pci_dev *dev, int bar, #include #include #include -#include struct pci_dev; diff --git a/arch/powerpc/include/asm/pci-bridge.h b/arch/powerpc/include/asm/pci-bridge.h index 54843ca5fa2b..78968c1ff931 100644 --- a/arch/powerpc/include/asm/pci-bridge.h +++ b/arch/powerpc/include/asm/pci-bridge.h @@ -10,7 +10,6 @@ #include #include #include -#include struct device_node; diff --git a/arch/unicore32/include/asm/pci.h b/arch/unicore32/include/asm/pci.h index 38b3f3785c3c..eb9dccecb338 100644 --- a/arch/unicore32/include/asm/pci.h +++ b/arch/unicore32/include/asm/pci.h @@ -14,7 +14,6 @@ #ifdef __KERNEL__ #include -#include #include #include /* for PCIBIOS_MIN_* */ @@ -23,5 +22,4 @@ extern int pci_mmap_page_range(struct pci_dev *dev, struct vm_area_struct *vma, enum pci_mmap_state mmap_state, int write_combine); #endif /* __KERNEL__ */ - #endif diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index 2879efc73a96..b4a9f23d77d9 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -12,7 +12,6 @@ #include #include -#include #include #include #include diff --git a/drivers/of/of_pci.c b/drivers/of/of_pci.c index b1449f71601c..13f4fed38048 100644 --- a/drivers/of/of_pci.c +++ b/drivers/of/of_pci.c @@ -5,7 +5,6 @@ #include #include #include -#include static inline int __of_pci_pci_compare(struct device_node *node, unsigned int data) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 602eb4223510..64c0a1215f84 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include "pci.h" diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ead1ac1dc1e3..5eb378fbe849 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 7796d0a5befa..55641a39a3e9 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -25,7 +25,6 @@ #include #include #include -#include #include "pci.h" unsigned int pci_flags; -- cgit v1.2.3-59-g8ed1b From 952bbcb0781bd1341f6a9f5c96fc32737392c04a Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 5 Feb 2016 14:58:12 -0600 Subject: PCI: Remove includes of asm/pci-bridge.h Drivers should include asm/pci-bridge.h only when they need the arch- specific things provided there. Outside of the arch/ directories, the only drivers that actually need things provided by asm/pci-bridge.h are the powerpc RPA hotplug drivers in drivers/pci/hotplug/rpa*. Remove the includes of asm/pci-bridge.h from the other drivers, adding an include of linux/pci.h if necessary. Signed-off-by: Bjorn Helgaas --- drivers/ata/pata_macio.c | 2 +- drivers/char/agp/uninorth-agp.c | 1 - drivers/gpu/drm/radeon/radeon_combios.c | 1 - drivers/ide/pdc202xx_new.c | 1 - drivers/ide/pmac.c | 1 - drivers/macintosh/macio_asic.c | 1 - drivers/misc/cxl/pci.c | 1 - drivers/net/ethernet/sun/sungem.c | 1 - drivers/net/ethernet/toshiba/spider_net.c | 1 - drivers/scsi/mac53c94.c | 2 +- drivers/scsi/mesh.c | 2 +- drivers/usb/core/hcd-pci.c | 1 - drivers/video/fbdev/aty/aty128fb.c | 1 - drivers/video/fbdev/aty/radeon_base.c | 1 - drivers/video/fbdev/imsttfb.c | 1 - drivers/video/fbdev/matrox/matroxfb_base.h | 1 - drivers/video/fbdev/offb.c | 4 ---- sound/ppc/pmac.c | 1 - 18 files changed, 3 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/ata/pata_macio.c b/drivers/ata/pata_macio.c index e3d4b059fcd1..e347e7acd8ed 100644 --- a/drivers/ata/pata_macio.c +++ b/drivers/ata/pata_macio.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -30,7 +31,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/char/agp/uninorth-agp.c b/drivers/char/agp/uninorth-agp.c index 05755441250c..fdced547ad59 100644 --- a/drivers/char/agp/uninorth-agp.c +++ b/drivers/char/agp/uninorth-agp.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include "agp.h" diff --git a/drivers/gpu/drm/radeon/radeon_combios.c b/drivers/gpu/drm/radeon/radeon_combios.c index a9b01bcf7d0a..432480ff9d22 100644 --- a/drivers/gpu/drm/radeon/radeon_combios.c +++ b/drivers/gpu/drm/radeon/radeon_combios.c @@ -34,7 +34,6 @@ #include #include #include -#include #endif /* CONFIG_PPC_PMAC */ /* from radeon_legacy_encoder.c */ diff --git a/drivers/ide/pdc202xx_new.c b/drivers/ide/pdc202xx_new.c index 9ad014a7afc7..b33646be699c 100644 --- a/drivers/ide/pdc202xx_new.c +++ b/drivers/ide/pdc202xx_new.c @@ -28,7 +28,6 @@ #ifdef CONFIG_PPC_PMAC #include -#include #endif #define DRV_NAME "pdc202xx_new" diff --git a/drivers/ide/pmac.c b/drivers/ide/pmac.c index 96a345248224..7f0434f7e486 100644 --- a/drivers/ide/pmac.c +++ b/drivers/ide/pmac.c @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/macintosh/macio_asic.c b/drivers/macintosh/macio_asic.c index 4f12c6f01fe7..b6819f0fc608 100644 --- a/drivers/macintosh/macio_asic.c +++ b/drivers/macintosh/macio_asic.c @@ -31,7 +31,6 @@ #include #include #include -#include #undef DEBUG diff --git a/drivers/misc/cxl/pci.c b/drivers/misc/cxl/pci.c index 4c1903f781fc..a21403238a4a 100644 --- a/drivers/misc/cxl/pci.c +++ b/drivers/misc/cxl/pci.c @@ -19,7 +19,6 @@ #include #include #include -#include /* for struct pci_controller */ #include #include diff --git a/drivers/net/ethernet/sun/sungem.c b/drivers/net/ethernet/sun/sungem.c index e23a642357e7..2437227712dc 100644 --- a/drivers/net/ethernet/sun/sungem.c +++ b/drivers/net/ethernet/sun/sungem.c @@ -51,7 +51,6 @@ #endif #ifdef CONFIG_PPC_PMAC -#include #include #include #include diff --git a/drivers/net/ethernet/toshiba/spider_net.c b/drivers/net/ethernet/toshiba/spider_net.c index 3c54a2cae5df..67610270d171 100644 --- a/drivers/net/ethernet/toshiba/spider_net.c +++ b/drivers/net/ethernet/toshiba/spider_net.c @@ -48,7 +48,6 @@ #include #include #include -#include #include #include "spider_net.h" diff --git a/drivers/scsi/mac53c94.c b/drivers/scsi/mac53c94.c index 141226631429..a6682c508c4c 100644 --- a/drivers/scsi/mac53c94.c +++ b/drivers/scsi/mac53c94.c @@ -18,11 +18,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include diff --git a/drivers/scsi/mesh.c b/drivers/scsi/mesh.c index 555367f00228..1753e42826dd 100644 --- a/drivers/scsi/mesh.c +++ b/drivers/scsi/mesh.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,7 +39,6 @@ #include #include #include -#include #include #include diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 9eb1cff28bd4..c3640f8a8fb3 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -28,7 +28,6 @@ #ifdef CONFIG_PPC_PMAC #include #include -#include #include #endif diff --git a/drivers/video/fbdev/aty/aty128fb.c b/drivers/video/fbdev/aty/aty128fb.c index c42ce2fdfd44..0a4626886b00 100644 --- a/drivers/video/fbdev/aty/aty128fb.c +++ b/drivers/video/fbdev/aty/aty128fb.c @@ -68,7 +68,6 @@ #include #include #include -#include #include "../macmodes.h" #endif diff --git a/drivers/video/fbdev/aty/radeon_base.c b/drivers/video/fbdev/aty/radeon_base.c index ce0b1d05a388..218339a4edaa 100644 --- a/drivers/video/fbdev/aty/radeon_base.c +++ b/drivers/video/fbdev/aty/radeon_base.c @@ -76,7 +76,6 @@ #ifdef CONFIG_PPC -#include #include "../macmodes.h" #ifdef CONFIG_BOOTX_TEXT diff --git a/drivers/video/fbdev/imsttfb.c b/drivers/video/fbdev/imsttfb.c index 9b167f7ef6c6..4363c64d74e8 100644 --- a/drivers/video/fbdev/imsttfb.c +++ b/drivers/video/fbdev/imsttfb.c @@ -33,7 +33,6 @@ #if defined(CONFIG_PPC) #include #include -#include #include "macmodes.h" #endif diff --git a/drivers/video/fbdev/matrox/matroxfb_base.h b/drivers/video/fbdev/matrox/matroxfb_base.h index 09b02cd1eb0e..7a90ea2c4613 100644 --- a/drivers/video/fbdev/matrox/matroxfb_base.h +++ b/drivers/video/fbdev/matrox/matroxfb_base.h @@ -47,7 +47,6 @@ #if defined(CONFIG_PPC_PMAC) #include -#include #include "../macmodes.h" #endif diff --git a/drivers/video/fbdev/offb.c b/drivers/video/fbdev/offb.c index 43a0a52fc527..fb60a8f0cc94 100644 --- a/drivers/video/fbdev/offb.c +++ b/drivers/video/fbdev/offb.c @@ -28,10 +28,6 @@ #include #include -#ifdef CONFIG_PPC64 -#include -#endif - #ifdef CONFIG_PPC32 #include #endif diff --git a/sound/ppc/pmac.c b/sound/ppc/pmac.c index 0095a80a997f..a5843fc5ff20 100644 --- a/sound/ppc/pmac.c +++ b/sound/ppc/pmac.c @@ -34,7 +34,6 @@ #include "pmac.h" #include #include -#include /* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */ -- cgit v1.2.3-59-g8ed1b From dd193929d91e1b44b90f81509feeff10c94ddc4d Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Thu, 7 Jan 2016 14:12:38 +0800 Subject: PCI: designware: Explain why we don't program ATU for some platforms Some platforms don't support ATU, e.g., pci-keystone.c. These platforms use their own address translation component rather than ATU, and they provide the rd_other_conf and wr_other_conf methods to program the translation component and perform the access. Add a comment to explain why we don't program the ATU for these platforms. [bhelgaas: changelog] Signed-off-by: Jisheng Zhang Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pcie-designware.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 21716827847a..8bcaed18f0dd 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -517,6 +517,11 @@ int dw_pcie_host_init(struct pcie_port *pp) if (pp->ops->host_init) pp->ops->host_init(pp); + /* + * If the platform provides ->rd_other_conf, it means the platform + * uses its own address translation component rather than ATU, so + * we should not program the ATU here. + */ if (!pp->ops->rd_other_conf) dw_pcie_prog_outbound_atu(pp, PCIE_ATU_REGION_INDEX1, PCIE_ATU_TYPE_MEM, pp->mem_base, -- cgit v1.2.3-59-g8ed1b From ed00c83cd4909dc268cc0639bf16804444ed8c79 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Fri, 29 Jan 2016 11:29:32 +0000 Subject: PCI: designware: Remove PCI_PROBE_ONLY handling The PCIe designware host driver is not used in system configurations requiring the PCI_PROBE_ONLY flag to be set to prevent resources assignment, therefore the driver code handling the flag can be removed from the kernel. Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand Acked-by: Jingoo Han Jingoo Han Cc: Arnd Bergmann Cc: Gabriele Paoloni Cc: Zhou Wang --- drivers/pci/host/pcie-designware.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index 8bcaed18f0dd..f85f10d22049 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -556,13 +556,11 @@ int dw_pcie_host_init(struct pcie_port *pp) pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); #endif - if (!pci_has_flag(PCI_PROBE_ONLY)) { - pci_bus_size_bridges(bus); - pci_bus_assign_resources(bus); + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); - list_for_each_entry(child, &bus->children, node) - pcie_bus_configure_settings(child); - } + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); pci_bus_add_devices(bus); return 0; -- cgit v1.2.3-59-g8ed1b From 788858ebc49a07fe5f812778f245a51b0d800d82 Mon Sep 17 00:00:00 2001 From: Jake Oshins Date: Tue, 16 Feb 2016 21:56:22 +0000 Subject: PCI: Look up IRQ domain by fwnode_handle If pci_host_bridge_msi_domain() can't find an IRQ domain through the OF tree, try to look it up directly through the fwnode_handle. [bhelgaas: changelog] Signed-off-by: Jake Oshins Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9bb0d5a..1e34d211389a 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "pci.h" @@ -675,6 +676,20 @@ static struct irq_domain *pci_host_bridge_msi_domain(struct pci_bus *bus) if (!d) d = pci_host_bridge_acpi_msi_domain(bus); +#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN + /* + * If no IRQ domain was found via the OF tree, try looking it up + * directly through the fwnode_handle. + */ + if (!d) { + struct fwnode_handle *fwnode = pci_root_bus_fwnode(bus); + + if (fwnode) + d = irq_find_matching_fwnode(fwnode, + DOMAIN_BUS_PCI_MSI); + } +#endif + return d; } -- cgit v1.2.3-59-g8ed1b From 4daace0d8ce851f8f8f91563c835e3000c954d5e Mon Sep 17 00:00:00 2001 From: Jake Oshins Date: Tue, 16 Feb 2016 21:56:23 +0000 Subject: PCI: hv: Add paravirtual PCI front-end for Microsoft Hyper-V VMs Add a new driver which exposes a root PCI bus whenever a PCI Express device is passed through to a guest VM under Hyper-V. The device can be single- or multi-function. The interrupts for the devices are managed by an IRQ domain, implemented within the driver. [bhelgaas: fold in race condition fix (http://lkml.kernel.org/r/1456340196-13717-1-git-send-email-jakeo@microsoft.com)] Signed-off-by: Jake Oshins Signed-off-by: Bjorn Helgaas --- MAINTAINERS | 1 + drivers/pci/Kconfig | 7 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-hyperv.c | 2346 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2355 insertions(+) create mode 100644 drivers/pci/host/pci-hyperv.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 30aca4aa5467..b68c015bc62f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5193,6 +5193,7 @@ F: arch/x86/kernel/cpu/mshyperv.c F: drivers/hid/hid-hyperv.c F: drivers/hv/ F: drivers/input/serio/hyperv-keyboard.c +F: drivers/pci/host/pci-hyperv.c F: drivers/net/hyperv/ F: drivers/scsi/storvsc_drv.c F: drivers/video/fbdev/hyperv_fb.c diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 73de4efcbe6e..54a544109b7a 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -118,4 +118,11 @@ config PCI_LABEL def_bool y if (DMI || ACPI) select NLS +config PCI_HYPERV + tristate "Hyper-V PCI Frontend" + depends on PCI && X86 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && X86_64 + help + The PCI device frontend driver allows the kernel to import arbitrary + PCI devices from a PCI backend to support PCI driver domains. + source "drivers/pci/host/Kconfig" diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 7b2f20c6ccc6..152daf970c1f 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o obj-$(CONFIG_PCI_IMX6) += pci-imx6.o +obj-$(CONFIG_PCI_HYPERV) += pci-hyperv.o obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o diff --git a/drivers/pci/host/pci-hyperv.c b/drivers/pci/host/pci-hyperv.c new file mode 100644 index 000000000000..ed651baa7c50 --- /dev/null +++ b/drivers/pci/host/pci-hyperv.c @@ -0,0 +1,2346 @@ +/* + * Copyright (c) Microsoft Corporation. + * + * Author: + * Jake Oshins + * + * This driver acts as a paravirtual front-end for PCI Express root buses. + * When a PCI Express function (either an entire device or an SR-IOV + * Virtual Function) is being passed through to the VM, this driver exposes + * a new bus to the guest VM. This is modeled as a root PCI bus because + * no bridges are being exposed to the VM. In fact, with a "Generation 2" + * VM within Hyper-V, there may seem to be no PCI bus at all in the VM + * until a device as been exposed using this driver. + * + * Each root PCI bus has its own PCI domain, which is called "Segment" in + * the PCI Firmware Specifications. Thus while each device passed through + * to the VM using this front-end will appear at "device 0", the domain will + * be unique. Typically, each bus will have one PCI function on it, though + * this driver does support more than one. + * + * In order to map the interrupts from the device through to the guest VM, + * this driver also implements an IRQ Domain, which handles interrupts (either + * MSI or MSI-X) associated with the functions on the bus. As interrupts are + * set up, torn down, or reaffined, this driver communicates with the + * underlying hypervisor to adjust the mappings in the I/O MMU so that each + * interrupt will be delivered to the correct virtual processor at the right + * vector. This driver does not support level-triggered (line-based) + * interrupts, and will report that the Interrupt Line register in the + * function's configuration space is zero. + * + * The rest of this driver mostly maps PCI concepts onto underlying Hyper-V + * facilities. For instance, the configuration space of a function exposed + * by Hyper-V is mapped into a single page of memory space, and the + * read and write handlers for config space must be aware of this mechanism. + * Similarly, device setup and teardown involves messages sent to and from + * the PCI back-end driver in Hyper-V. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Protocol versions. The low word is the minor version, the high word the + * major version. + */ + +#define PCI_MAKE_VERSION(major, minor) ((u32)(((major) << 16) | (major))) +#define PCI_MAJOR_VERSION(version) ((u32)(version) >> 16) +#define PCI_MINOR_VERSION(version) ((u32)(version) & 0xff) + +enum { + PCI_PROTOCOL_VERSION_1_1 = PCI_MAKE_VERSION(1, 1), + PCI_PROTOCOL_VERSION_CURRENT = PCI_PROTOCOL_VERSION_1_1 +}; + +#define PCI_CONFIG_MMIO_LENGTH 0x2000 +#define CFG_PAGE_OFFSET 0x1000 +#define CFG_PAGE_SIZE (PCI_CONFIG_MMIO_LENGTH - CFG_PAGE_OFFSET) + +#define MAX_SUPPORTED_MSI_MESSAGES 0x400 + +/* + * Message Types + */ + +enum pci_message_type { + /* + * Version 1.1 + */ + PCI_MESSAGE_BASE = 0x42490000, + PCI_BUS_RELATIONS = PCI_MESSAGE_BASE + 0, + PCI_QUERY_BUS_RELATIONS = PCI_MESSAGE_BASE + 1, + PCI_POWER_STATE_CHANGE = PCI_MESSAGE_BASE + 4, + PCI_QUERY_RESOURCE_REQUIREMENTS = PCI_MESSAGE_BASE + 5, + PCI_QUERY_RESOURCE_RESOURCES = PCI_MESSAGE_BASE + 6, + PCI_BUS_D0ENTRY = PCI_MESSAGE_BASE + 7, + PCI_BUS_D0EXIT = PCI_MESSAGE_BASE + 8, + PCI_READ_BLOCK = PCI_MESSAGE_BASE + 9, + PCI_WRITE_BLOCK = PCI_MESSAGE_BASE + 0xA, + PCI_EJECT = PCI_MESSAGE_BASE + 0xB, + PCI_QUERY_STOP = PCI_MESSAGE_BASE + 0xC, + PCI_REENABLE = PCI_MESSAGE_BASE + 0xD, + PCI_QUERY_STOP_FAILED = PCI_MESSAGE_BASE + 0xE, + PCI_EJECTION_COMPLETE = PCI_MESSAGE_BASE + 0xF, + PCI_RESOURCES_ASSIGNED = PCI_MESSAGE_BASE + 0x10, + PCI_RESOURCES_RELEASED = PCI_MESSAGE_BASE + 0x11, + PCI_INVALIDATE_BLOCK = PCI_MESSAGE_BASE + 0x12, + PCI_QUERY_PROTOCOL_VERSION = PCI_MESSAGE_BASE + 0x13, + PCI_CREATE_INTERRUPT_MESSAGE = PCI_MESSAGE_BASE + 0x14, + PCI_DELETE_INTERRUPT_MESSAGE = PCI_MESSAGE_BASE + 0x15, + PCI_MESSAGE_MAXIMUM +}; + +/* + * Structures defining the virtual PCI Express protocol. + */ + +union pci_version { + struct { + u16 minor_version; + u16 major_version; + } parts; + u32 version; +} __packed; + +/* + * Function numbers are 8-bits wide on Express, as interpreted through ARI, + * which is all this driver does. This representation is the one used in + * Windows, which is what is expected when sending this back and forth with + * the Hyper-V parent partition. + */ +union win_slot_encoding { + struct { + u32 func:8; + u32 reserved:24; + } bits; + u32 slot; +} __packed; + +/* + * Pretty much as defined in the PCI Specifications. + */ +struct pci_function_description { + u16 v_id; /* vendor ID */ + u16 d_id; /* device ID */ + u8 rev; + u8 prog_intf; + u8 subclass; + u8 base_class; + u32 subsystem_id; + union win_slot_encoding win_slot; + u32 ser; /* serial number */ +} __packed; + +/** + * struct hv_msi_desc + * @vector: IDT entry + * @delivery_mode: As defined in Intel's Programmer's + * Reference Manual, Volume 3, Chapter 8. + * @vector_count: Number of contiguous entries in the + * Interrupt Descriptor Table that are + * occupied by this Message-Signaled + * Interrupt. For "MSI", as first defined + * in PCI 2.2, this can be between 1 and + * 32. For "MSI-X," as first defined in PCI + * 3.0, this must be 1, as each MSI-X table + * entry would have its own descriptor. + * @reserved: Empty space + * @cpu_mask: All the target virtual processors. + */ +struct hv_msi_desc { + u8 vector; + u8 delivery_mode; + u16 vector_count; + u32 reserved; + u64 cpu_mask; +} __packed; + +/** + * struct tran_int_desc + * @reserved: unused, padding + * @vector_count: same as in hv_msi_desc + * @data: This is the "data payload" value that is + * written by the device when it generates + * a message-signaled interrupt, either MSI + * or MSI-X. + * @address: This is the address to which the data + * payload is written on interrupt + * generation. + */ +struct tran_int_desc { + u16 reserved; + u16 vector_count; + u32 data; + u64 address; +} __packed; + +/* + * A generic message format for virtual PCI. + * Specific message formats are defined later in the file. + */ + +struct pci_message { + u32 message_type; +} __packed; + +struct pci_child_message { + u32 message_type; + union win_slot_encoding wslot; +} __packed; + +struct pci_incoming_message { + struct vmpacket_descriptor hdr; + struct pci_message message_type; +} __packed; + +struct pci_response { + struct vmpacket_descriptor hdr; + s32 status; /* negative values are failures */ +} __packed; + +struct pci_packet { + void (*completion_func)(void *context, struct pci_response *resp, + int resp_packet_size); + void *compl_ctxt; + struct pci_message message; +}; + +/* + * Specific message types supporting the PCI protocol. + */ + +/* + * Version negotiation message. Sent from the guest to the host. + * The guest is free to try different versions until the host + * accepts the version. + * + * pci_version: The protocol version requested. + * is_last_attempt: If TRUE, this is the last version guest will request. + * reservedz: Reserved field, set to zero. + */ + +struct pci_version_request { + struct pci_message message_type; + enum pci_message_type protocol_version; +} __packed; + +/* + * Bus D0 Entry. This is sent from the guest to the host when the virtual + * bus (PCI Express port) is ready for action. + */ + +struct pci_bus_d0_entry { + struct pci_message message_type; + u32 reserved; + u64 mmio_base; +} __packed; + +struct pci_bus_relations { + struct pci_incoming_message incoming; + u32 device_count; + struct pci_function_description func[1]; +} __packed; + +struct pci_q_res_req_response { + struct vmpacket_descriptor hdr; + s32 status; /* negative values are failures */ + u32 probed_bar[6]; +} __packed; + +struct pci_set_power { + struct pci_message message_type; + union win_slot_encoding wslot; + u32 power_state; /* In Windows terms */ + u32 reserved; +} __packed; + +struct pci_set_power_response { + struct vmpacket_descriptor hdr; + s32 status; /* negative values are failures */ + union win_slot_encoding wslot; + u32 resultant_state; /* In Windows terms */ + u32 reserved; +} __packed; + +struct pci_resources_assigned { + struct pci_message message_type; + union win_slot_encoding wslot; + u8 memory_range[0x14][6]; /* not used here */ + u32 msi_descriptors; + u32 reserved[4]; +} __packed; + +struct pci_create_interrupt { + struct pci_message message_type; + union win_slot_encoding wslot; + struct hv_msi_desc int_desc; +} __packed; + +struct pci_create_int_response { + struct pci_response response; + u32 reserved; + struct tran_int_desc int_desc; +} __packed; + +struct pci_delete_interrupt { + struct pci_message message_type; + union win_slot_encoding wslot; + struct tran_int_desc int_desc; +} __packed; + +struct pci_dev_incoming { + struct pci_incoming_message incoming; + union win_slot_encoding wslot; +} __packed; + +struct pci_eject_response { + u32 message_type; + union win_slot_encoding wslot; + u32 status; +} __packed; + +static int pci_ring_size = (4 * PAGE_SIZE); + +/* + * Definitions or interrupt steering hypercall. + */ +#define HV_PARTITION_ID_SELF ((u64)-1) +#define HVCALL_RETARGET_INTERRUPT 0x7e + +struct retarget_msi_interrupt { + u64 partition_id; /* use "self" */ + u64 device_id; + u32 source; /* 1 for MSI(-X) */ + u32 reserved1; + u32 address; + u32 data; + u64 reserved2; + u32 vector; + u32 flags; + u64 vp_mask; +} __packed; + +/* + * Driver specific state. + */ + +enum hv_pcibus_state { + hv_pcibus_init = 0, + hv_pcibus_probed, + hv_pcibus_installed, + hv_pcibus_maximum +}; + +struct hv_pcibus_device { + struct pci_sysdata sysdata; + enum hv_pcibus_state state; + atomic_t remove_lock; + struct hv_device *hdev; + resource_size_t low_mmio_space; + resource_size_t high_mmio_space; + struct resource *mem_config; + struct resource *low_mmio_res; + struct resource *high_mmio_res; + struct completion *survey_event; + struct completion remove_event; + struct pci_bus *pci_bus; + spinlock_t config_lock; /* Avoid two threads writing index page */ + spinlock_t device_list_lock; /* Protect lists below */ + void __iomem *cfg_addr; + + struct semaphore enum_sem; + struct list_head resources_for_children; + + struct list_head children; + struct list_head dr_list; + struct work_struct wrk; + + struct msi_domain_info msi_info; + struct msi_controller msi_chip; + struct irq_domain *irq_domain; +}; + +/* + * Tracks "Device Relations" messages from the host, which must be both + * processed in order and deferred so that they don't run in the context + * of the incoming packet callback. + */ +struct hv_dr_work { + struct work_struct wrk; + struct hv_pcibus_device *bus; +}; + +struct hv_dr_state { + struct list_head list_entry; + u32 device_count; + struct pci_function_description func[1]; +}; + +enum hv_pcichild_state { + hv_pcichild_init = 0, + hv_pcichild_requirements, + hv_pcichild_resourced, + hv_pcichild_ejecting, + hv_pcichild_maximum +}; + +enum hv_pcidev_ref_reason { + hv_pcidev_ref_invalid = 0, + hv_pcidev_ref_initial, + hv_pcidev_ref_by_slot, + hv_pcidev_ref_packet, + hv_pcidev_ref_pnp, + hv_pcidev_ref_childlist, + hv_pcidev_irqdata, + hv_pcidev_ref_max +}; + +struct hv_pci_dev { + /* List protected by pci_rescan_remove_lock */ + struct list_head list_entry; + atomic_t refs; + enum hv_pcichild_state state; + struct pci_function_description desc; + bool reported_missing; + struct hv_pcibus_device *hbus; + struct work_struct wrk; + + /* + * What would be observed if one wrote 0xFFFFFFFF to a BAR and then + * read it back, for each of the BAR offsets within config space. + */ + u32 probed_bar[6]; +}; + +struct hv_pci_compl { + struct completion host_event; + s32 completion_status; +}; + +/** + * hv_pci_generic_compl() - Invoked for a completion packet + * @context: Set up by the sender of the packet. + * @resp: The response packet + * @resp_packet_size: Size in bytes of the packet + * + * This function is used to trigger an event and report status + * 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) +{ + struct hv_pci_compl *comp_pkt = context; + + if (resp_packet_size >= offsetofend(struct pci_response, status)) + comp_pkt->completion_status = resp->status; + complete(&comp_pkt->host_event); +} + +static struct hv_pci_dev *get_pcichild_wslot(struct hv_pcibus_device *hbus, + u32 wslot); +static void get_pcichild(struct hv_pci_dev *hv_pcidev, + enum hv_pcidev_ref_reason reason); +static void put_pcichild(struct hv_pci_dev *hv_pcidev, + enum hv_pcidev_ref_reason reason); + +static void get_hvpcibus(struct hv_pcibus_device *hv_pcibus); +static void put_hvpcibus(struct hv_pcibus_device *hv_pcibus); + +/** + * devfn_to_wslot() - Convert from Linux PCI slot to Windows + * @devfn: The Linux representation of PCI slot + * + * Windows uses a slightly different representation of PCI slot. + * + * Return: The Windows representation + */ +static u32 devfn_to_wslot(int devfn) +{ + union win_slot_encoding wslot; + + wslot.slot = 0; + wslot.bits.func = PCI_SLOT(devfn) | (PCI_FUNC(devfn) << 5); + + return wslot.slot; +} + +/** + * wslot_to_devfn() - Convert from Windows PCI slot to Linux + * @wslot: The Windows representation of PCI slot + * + * Windows uses a slightly different representation of PCI slot. + * + * Return: The Linux representation + */ +static int wslot_to_devfn(u32 wslot) +{ + union win_slot_encoding slot_no; + + slot_no.slot = wslot; + return PCI_DEVFN(0, slot_no.bits.func); +} + +/* + * PCI Configuration Space for these root PCI buses is implemented as a pair + * of pages in memory-mapped I/O space. Writing to the first page chooses + * the PCI function being written or read. Once the first page has been + * written to, the following page maps in the entire configuration space of + * the function. + */ + +/** + * _hv_pcifront_read_config() - Internal PCI config read + * @hpdev: The PCI driver's representation of the device + * @where: Offset within config space + * @size: Size of the transfer + * @val: Pointer to the buffer receiving the data + */ +static void _hv_pcifront_read_config(struct hv_pci_dev *hpdev, int where, + int size, u32 *val) +{ + unsigned long flags; + void __iomem *addr = hpdev->hbus->cfg_addr + CFG_PAGE_OFFSET + where; + + /* + * If the attempt is to read the IDs or the ROM BAR, simulate that. + */ + if (where + size <= PCI_COMMAND) { + memcpy(val, ((u8 *)&hpdev->desc.v_id) + where, size); + } else if (where >= PCI_CLASS_REVISION && where + size <= + PCI_CACHE_LINE_SIZE) { + memcpy(val, ((u8 *)&hpdev->desc.rev) + where - + PCI_CLASS_REVISION, size); + } else if (where >= PCI_SUBSYSTEM_VENDOR_ID && where + size <= + PCI_ROM_ADDRESS) { + memcpy(val, (u8 *)&hpdev->desc.subsystem_id + where - + PCI_SUBSYSTEM_VENDOR_ID, size); + } else if (where >= PCI_ROM_ADDRESS && where + size <= + PCI_CAPABILITY_LIST) { + /* ROM BARs are unimplemented */ + *val = 0; + } else if (where >= PCI_INTERRUPT_LINE && where + size <= + PCI_INTERRUPT_PIN) { + /* + * Interrupt Line and Interrupt PIN are hard-wired to zero + * because this front-end only supports message-signaled + * interrupts. + */ + *val = 0; + } else if (where + size <= CFG_PAGE_SIZE) { + spin_lock_irqsave(&hpdev->hbus->config_lock, flags); + /* Choose the function to be read. (See comment above) */ + writel(hpdev->desc.win_slot.slot, hpdev->hbus->cfg_addr); + /* Read from that function's config space. */ + switch (size) { + case 1: + *val = readb(addr); + break; + case 2: + *val = readw(addr); + break; + default: + *val = readl(addr); + break; + } + spin_unlock_irqrestore(&hpdev->hbus->config_lock, flags); + } else { + dev_err(&hpdev->hbus->hdev->device, + "Attempt to read beyond a function's config space.\n"); + } +} + +/** + * _hv_pcifront_write_config() - Internal PCI config write + * @hpdev: The PCI driver's representation of the device + * @where: Offset within config space + * @size: Size of the transfer + * @val: The data being transferred + */ +static void _hv_pcifront_write_config(struct hv_pci_dev *hpdev, int where, + int size, u32 val) +{ + unsigned long flags; + void __iomem *addr = hpdev->hbus->cfg_addr + CFG_PAGE_OFFSET + where; + + if (where >= PCI_SUBSYSTEM_VENDOR_ID && + where + size <= PCI_CAPABILITY_LIST) { + /* SSIDs and ROM BARs are read-only */ + } else if (where >= PCI_COMMAND && where + size <= CFG_PAGE_SIZE) { + spin_lock_irqsave(&hpdev->hbus->config_lock, flags); + /* Choose the function to be written. (See comment above) */ + writel(hpdev->desc.win_slot.slot, hpdev->hbus->cfg_addr); + /* Write to that function's config space. */ + switch (size) { + case 1: + writeb(val, addr); + break; + case 2: + writew(val, addr); + break; + default: + writel(val, addr); + break; + } + spin_unlock_irqrestore(&hpdev->hbus->config_lock, flags); + } else { + dev_err(&hpdev->hbus->hdev->device, + "Attempt to write beyond a function's config space.\n"); + } +} + +/** + * hv_pcifront_read_config() - Read configuration space + * @bus: PCI Bus structure + * @devfn: Device/function + * @where: Offset from base + * @size: Byte/word/dword + * @val: Value to be read + * + * Return: PCIBIOS_SUCCESSFUL on success + * PCIBIOS_DEVICE_NOT_FOUND on failure + */ +static int hv_pcifront_read_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct hv_pcibus_device *hbus = + container_of(bus->sysdata, struct hv_pcibus_device, sysdata); + struct hv_pci_dev *hpdev; + + hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(devfn)); + if (!hpdev) + return PCIBIOS_DEVICE_NOT_FOUND; + + _hv_pcifront_read_config(hpdev, where, size, val); + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); + return PCIBIOS_SUCCESSFUL; +} + +/** + * hv_pcifront_write_config() - Write configuration space + * @bus: PCI Bus structure + * @devfn: Device/function + * @where: Offset from base + * @size: Byte/word/dword + * @val: Value to be written to device + * + * Return: PCIBIOS_SUCCESSFUL on success + * PCIBIOS_DEVICE_NOT_FOUND on failure + */ +static int hv_pcifront_write_config(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct hv_pcibus_device *hbus = + container_of(bus->sysdata, struct hv_pcibus_device, sysdata); + struct hv_pci_dev *hpdev; + + hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(devfn)); + if (!hpdev) + return PCIBIOS_DEVICE_NOT_FOUND; + + _hv_pcifront_write_config(hpdev, where, size, val); + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); + return PCIBIOS_SUCCESSFUL; +} + +/* PCIe operations */ +static struct pci_ops hv_pcifront_ops = { + .read = hv_pcifront_read_config, + .write = hv_pcifront_write_config, +}; + +/* Interrupt management hooks */ +static void hv_int_desc_free(struct hv_pci_dev *hpdev, + struct tran_int_desc *int_desc) +{ + struct pci_delete_interrupt *int_pkt; + struct { + struct pci_packet pkt; + u8 buffer[sizeof(struct pci_delete_interrupt) - + sizeof(struct pci_message)]; + } ctxt; + + memset(&ctxt, 0, sizeof(ctxt)); + int_pkt = (struct pci_delete_interrupt *)&ctxt.pkt.message; + int_pkt->message_type.message_type = + PCI_DELETE_INTERRUPT_MESSAGE; + int_pkt->wslot.slot = hpdev->desc.win_slot.slot; + int_pkt->int_desc = *int_desc; + vmbus_sendpacket(hpdev->hbus->hdev->channel, int_pkt, sizeof(*int_pkt), + (unsigned long)&ctxt.pkt, VM_PKT_DATA_INBAND, 0); + kfree(int_desc); +} + +/** + * hv_msi_free() - Free the MSI. + * @domain: The interrupt domain pointer + * @info: Extra MSI-related context + * @irq: Identifies the IRQ. + * + * The Hyper-V parent partition and hypervisor are tracking the + * messages that are in use, keeping the interrupt redirection + * table up to date. This callback sends a message that frees + * the IRT entry and related tracking nonsense. + */ +static void hv_msi_free(struct irq_domain *domain, struct msi_domain_info *info, + unsigned int irq) +{ + struct hv_pcibus_device *hbus; + struct hv_pci_dev *hpdev; + struct pci_dev *pdev; + struct tran_int_desc *int_desc; + struct irq_data *irq_data = irq_domain_get_irq_data(domain, irq); + struct msi_desc *msi = irq_data_get_msi_desc(irq_data); + + pdev = msi_desc_to_pci_dev(msi); + hbus = info->data; + hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn)); + if (!hpdev) + return; + + int_desc = irq_data_get_irq_chip_data(irq_data); + if (int_desc) { + irq_data->chip_data = NULL; + hv_int_desc_free(hpdev, int_desc); + } + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); +} + +static int hv_set_affinity(struct irq_data *data, const struct cpumask *dest, + bool force) +{ + struct irq_data *parent = data->parent_data; + + return parent->chip->irq_set_affinity(parent, dest, force); +} + +void hv_irq_mask(struct irq_data *data) +{ + pci_msi_mask_irq(data); +} + +/** + * hv_irq_unmask() - "Unmask" the IRQ by setting its current + * affinity. + * @data: Describes the IRQ + * + * Build new a destination for the MSI and make a hypercall to + * update the Interrupt Redirection Table. "Device Logical ID" + * is built out of this PCI bus's instance GUID and the function + * number of the device. + */ +void hv_irq_unmask(struct irq_data *data) +{ + struct msi_desc *msi_desc = irq_data_get_msi_desc(data); + struct irq_cfg *cfg = irqd_cfg(data); + struct retarget_msi_interrupt params; + struct hv_pcibus_device *hbus; + struct cpumask *dest; + struct pci_bus *pbus; + struct pci_dev *pdev; + int cpu; + + dest = irq_data_get_affinity_mask(data); + pdev = msi_desc_to_pci_dev(msi_desc); + pbus = pdev->bus; + hbus = container_of(pbus->sysdata, struct hv_pcibus_device, sysdata); + + memset(¶ms, 0, sizeof(params)); + params.partition_id = HV_PARTITION_ID_SELF; + params.source = 1; /* MSI(-X) */ + params.address = msi_desc->msg.address_lo; + params.data = msi_desc->msg.data; + params.device_id = (hbus->hdev->dev_instance.b[5] << 24) | + (hbus->hdev->dev_instance.b[4] << 16) | + (hbus->hdev->dev_instance.b[7] << 8) | + (hbus->hdev->dev_instance.b[6] & 0xf8) | + PCI_FUNC(pdev->devfn); + params.vector = cfg->vector; + + for_each_cpu_and(cpu, dest, cpu_online_mask) + params.vp_mask |= (1ULL << vmbus_cpu_number_to_vp_number(cpu)); + + hv_do_hypercall(HVCALL_RETARGET_INTERRUPT, ¶ms, NULL); + + pci_msi_unmask_irq(data); +} + +struct compose_comp_ctxt { + struct hv_pci_compl comp_pkt; + struct tran_int_desc int_desc; +}; + +static void hv_pci_compose_compl(void *context, struct pci_response *resp, + int resp_packet_size) +{ + struct compose_comp_ctxt *comp_pkt = context; + struct pci_create_int_response *int_resp = + (struct pci_create_int_response *)resp; + + comp_pkt->comp_pkt.completion_status = resp->status; + comp_pkt->int_desc = int_resp->int_desc; + complete(&comp_pkt->comp_pkt.host_event); +} + +/** + * hv_compose_msi_msg() - Supplies a valid MSI address/data + * @data: Everything about this MSI + * @msg: Buffer that is filled in by this function + * + * This function unpacks the IRQ looking for target CPU set, IDT + * vector and mode and sends a message to the parent partition + * asking for a mapping for that tuple in this partition. The + * response supplies a data value and address to which that data + * should be written to trigger that interrupt. + */ +static void hv_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct irq_cfg *cfg = irqd_cfg(data); + struct hv_pcibus_device *hbus; + struct hv_pci_dev *hpdev; + struct pci_bus *pbus; + struct pci_dev *pdev; + struct pci_create_interrupt *int_pkt; + struct compose_comp_ctxt comp; + struct tran_int_desc *int_desc; + struct cpumask *affinity; + struct { + struct pci_packet pkt; + u8 buffer[sizeof(struct pci_create_interrupt) - + sizeof(struct pci_message)]; + } ctxt; + int cpu; + int ret; + + pdev = msi_desc_to_pci_dev(irq_data_get_msi_desc(data)); + pbus = pdev->bus; + hbus = container_of(pbus->sysdata, struct hv_pcibus_device, sysdata); + hpdev = get_pcichild_wslot(hbus, devfn_to_wslot(pdev->devfn)); + if (!hpdev) + goto return_null_message; + + /* Free any previous message that might have already been composed. */ + if (data->chip_data) { + int_desc = data->chip_data; + data->chip_data = NULL; + hv_int_desc_free(hpdev, int_desc); + } + + int_desc = kzalloc(sizeof(*int_desc), GFP_KERNEL); + if (!int_desc) + goto drop_reference; + + memset(&ctxt, 0, sizeof(ctxt)); + init_completion(&comp.comp_pkt.host_event); + 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->wslot.slot = hpdev->desc.win_slot.slot; + int_pkt->int_desc.vector = cfg->vector; + int_pkt->int_desc.vector_count = 1; + int_pkt->int_desc.delivery_mode = + (apic->irq_delivery_mode == dest_LowestPrio) ? 1 : 0; + + /* + * This bit doesn't have to work on machines with more than 64 + * processors because Hyper-V only supports 64 in a guest. + */ + affinity = irq_data_get_affinity_mask(data); + for_each_cpu_and(cpu, affinity, cpu_online_mask) { + int_pkt->int_desc.cpu_mask |= + (1ULL << vmbus_cpu_number_to_vp_number(cpu)); + } + + ret = vmbus_sendpacket(hpdev->hbus->hdev->channel, int_pkt, + 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 (comp.comp_pkt.completion_status < 0) { + dev_err(&hbus->hdev->device, + "Request for interrupt failed: 0x%x", + comp.comp_pkt.completion_status); + goto free_int_desc; + } + + /* + * Record the assignment so that this can be unwound later. Using + * irq_set_chip_data() here would be appropriate, but the lock it takes + * is already held. + */ + *int_desc = comp.int_desc; + data->chip_data = int_desc; + + /* Pass up the result. */ + msg->address_hi = comp.int_desc.address >> 32; + msg->address_lo = comp.int_desc.address & 0xffffffff; + msg->data = comp.int_desc.data; + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); + return; + +free_int_desc: + kfree(int_desc); +drop_reference: + put_pcichild(hpdev, hv_pcidev_ref_by_slot); +return_null_message: + msg->address_hi = 0; + msg->address_lo = 0; + msg->data = 0; +} + +/* HW Interrupt Chip Descriptor */ +static struct irq_chip hv_msi_irq_chip = { + .name = "Hyper-V PCIe MSI", + .irq_compose_msi_msg = hv_compose_msi_msg, + .irq_set_affinity = hv_set_affinity, + .irq_ack = irq_chip_ack_parent, + .irq_mask = hv_irq_mask, + .irq_unmask = hv_irq_unmask, +}; + +static irq_hw_number_t hv_msi_domain_ops_get_hwirq(struct msi_domain_info *info, + msi_alloc_info_t *arg) +{ + return arg->msi_hwirq; +} + +static struct msi_domain_ops hv_msi_ops = { + .get_hwirq = hv_msi_domain_ops_get_hwirq, + .msi_prepare = pci_msi_prepare, + .set_desc = pci_msi_set_desc, + .msi_free = hv_msi_free, +}; + +/** + * hv_pcie_init_irq_domain() - Initialize IRQ domain + * @hbus: The root PCI bus + * + * This function creates an IRQ domain which will be used for + * interrupts from devices that have been passed through. These + * devices only support MSI and MSI-X, not line-based interrupts + * or simulations of line-based interrupts through PCIe's + * fabric-layer messages. Because interrupts are remapped, we + * can support multi-message MSI here. + * + * Return: '0' on success and error value on failure + */ +static int hv_pcie_init_irq_domain(struct hv_pcibus_device *hbus) +{ + hbus->msi_info.chip = &hv_msi_irq_chip; + hbus->msi_info.ops = &hv_msi_ops; + hbus->msi_info.flags = (MSI_FLAG_USE_DEF_DOM_OPS | + MSI_FLAG_USE_DEF_CHIP_OPS | MSI_FLAG_MULTI_PCI_MSI | + MSI_FLAG_PCI_MSIX); + hbus->msi_info.handler = handle_edge_irq; + hbus->msi_info.handler_name = "edge"; + hbus->msi_info.data = hbus; + hbus->irq_domain = pci_msi_create_irq_domain(hbus->sysdata.fwnode, + &hbus->msi_info, + x86_vector_domain); + if (!hbus->irq_domain) { + dev_err(&hbus->hdev->device, + "Failed to build an MSI IRQ domain\n"); + return -ENODEV; + } + + return 0; +} + +/** + * get_bar_size() - Get the address space consumed by a BAR + * @bar_val: Value that a BAR returned after -1 was written + * to it. + * + * This function returns the size of the BAR, rounded up to 1 + * page. It has to be rounded up because the hypervisor's page + * table entry that maps the BAR into the VM can't specify an + * offset within a page. The invariant is that the hypervisor + * must place any BARs of smaller than page length at the + * beginning of a page. + * + * Return: Size in bytes of the consumed MMIO space. + */ +static u64 get_bar_size(u64 bar_val) +{ + return round_up((1 + ~(bar_val & PCI_BASE_ADDRESS_MEM_MASK)), + PAGE_SIZE); +} + +/** + * survey_child_resources() - Total all MMIO requirements + * @hbus: Root PCI bus, as understood by this driver + */ +static void survey_child_resources(struct hv_pcibus_device *hbus) +{ + struct list_head *iter; + struct hv_pci_dev *hpdev; + resource_size_t bar_size = 0; + unsigned long flags; + struct completion *event; + u64 bar_val; + int i; + + /* If nobody is waiting on the answer, don't compute it. */ + event = xchg(&hbus->survey_event, NULL); + if (!event) + return; + + /* If the answer has already been computed, go with it. */ + if (hbus->low_mmio_space || hbus->high_mmio_space) { + complete(event); + return; + } + + spin_lock_irqsave(&hbus->device_list_lock, flags); + + /* + * Due to an interesting quirk of the PCI spec, all memory regions + * for a child device are a power of 2 in size and aligned in memory, + * so it's sufficient to just add them up without tracking alignment. + */ + list_for_each(iter, &hbus->children) { + hpdev = container_of(iter, struct hv_pci_dev, list_entry); + for (i = 0; i < 6; i++) { + if (hpdev->probed_bar[i] & PCI_BASE_ADDRESS_SPACE_IO) + dev_err(&hbus->hdev->device, + "There's an I/O BAR in this list!\n"); + + if (hpdev->probed_bar[i] != 0) { + /* + * A probed BAR has all the upper bits set that + * can be changed. + */ + + bar_val = hpdev->probed_bar[i]; + if (bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64) + bar_val |= + ((u64)hpdev->probed_bar[++i] << 32); + else + bar_val |= 0xffffffff00000000ULL; + + bar_size = get_bar_size(bar_val); + + if (bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64) + hbus->high_mmio_space += bar_size; + else + hbus->low_mmio_space += bar_size; + } + } + } + + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + complete(event); +} + +/** + * prepopulate_bars() - Fill in BARs with defaults + * @hbus: Root PCI bus, as understood by this driver + * + * The core PCI driver code seems much, much happier if the BARs + * for a device have values upon first scan. So fill them in. + * The algorithm below works down from large sizes to small, + * attempting to pack the assignments optimally. The assumption, + * enforced in other parts of the code, is that the beginning of + * the memory-mapped I/O space will be aligned on the largest + * BAR size. + */ +static void prepopulate_bars(struct hv_pcibus_device *hbus) +{ + resource_size_t high_size = 0; + resource_size_t low_size = 0; + resource_size_t high_base = 0; + resource_size_t low_base = 0; + resource_size_t bar_size; + struct hv_pci_dev *hpdev; + struct list_head *iter; + unsigned long flags; + u64 bar_val; + u32 command; + bool high; + int i; + + if (hbus->low_mmio_space) { + low_size = 1ULL << (63 - __builtin_clzll(hbus->low_mmio_space)); + low_base = hbus->low_mmio_res->start; + } + + if (hbus->high_mmio_space) { + high_size = 1ULL << + (63 - __builtin_clzll(hbus->high_mmio_space)); + high_base = hbus->high_mmio_res->start; + } + + spin_lock_irqsave(&hbus->device_list_lock, flags); + + /* Pick addresses for the BARs. */ + do { + list_for_each(iter, &hbus->children) { + hpdev = container_of(iter, struct hv_pci_dev, + list_entry); + for (i = 0; i < 6; i++) { + bar_val = hpdev->probed_bar[i]; + if (bar_val == 0) + continue; + high = bar_val & PCI_BASE_ADDRESS_MEM_TYPE_64; + if (high) { + bar_val |= + ((u64)hpdev->probed_bar[i + 1] + << 32); + } else { + bar_val |= 0xffffffffULL << 32; + } + bar_size = get_bar_size(bar_val); + if (high) { + if (high_size != bar_size) { + i++; + continue; + } + _hv_pcifront_write_config(hpdev, + PCI_BASE_ADDRESS_0 + (4 * i), + 4, + (u32)(high_base & 0xffffff00)); + i++; + _hv_pcifront_write_config(hpdev, + PCI_BASE_ADDRESS_0 + (4 * i), + 4, (u32)(high_base >> 32)); + high_base += bar_size; + } else { + if (low_size != bar_size) + continue; + _hv_pcifront_write_config(hpdev, + PCI_BASE_ADDRESS_0 + (4 * i), + 4, + (u32)(low_base & 0xffffff00)); + low_base += bar_size; + } + } + if (high_size <= 1 && low_size <= 1) { + /* Set the memory enable bit. */ + _hv_pcifront_read_config(hpdev, PCI_COMMAND, 2, + &command); + command |= PCI_COMMAND_MEMORY; + _hv_pcifront_write_config(hpdev, PCI_COMMAND, 2, + command); + break; + } + } + + high_size >>= 1; + low_size >>= 1; + } while (high_size || low_size); + + spin_unlock_irqrestore(&hbus->device_list_lock, flags); +} + +/** + * create_root_hv_pci_bus() - Expose a new root PCI bus + * @hbus: Root PCI bus, as understood by this driver + * + * Return: 0 on success, -errno on failure + */ +static int create_root_hv_pci_bus(struct hv_pcibus_device *hbus) +{ + /* Register the device */ + hbus->pci_bus = pci_create_root_bus(&hbus->hdev->device, + 0, /* bus number is always zero */ + &hv_pcifront_ops, + &hbus->sysdata, + &hbus->resources_for_children); + if (!hbus->pci_bus) + return -ENODEV; + + hbus->pci_bus->msi = &hbus->msi_chip; + hbus->pci_bus->msi->dev = &hbus->hdev->device; + + pci_scan_child_bus(hbus->pci_bus); + pci_bus_assign_resources(hbus->pci_bus); + pci_bus_add_devices(hbus->pci_bus); + hbus->state = hv_pcibus_installed; + return 0; +} + +struct q_res_req_compl { + struct completion host_event; + struct hv_pci_dev *hpdev; +}; + +/** + * q_resource_requirements() - Query Resource Requirements + * @context: The completion context. + * @resp: The response that came from the host. + * @resp_packet_size: The size in bytes of resp. + * + * This function is invoked on completion of a Query Resource + * Requirements packet. + */ +static void q_resource_requirements(void *context, struct pci_response *resp, + int resp_packet_size) +{ + struct q_res_req_compl *completion = context; + struct pci_q_res_req_response *q_res_req = + (struct pci_q_res_req_response *)resp; + int i; + + if (resp->status < 0) { + dev_err(&completion->hpdev->hbus->hdev->device, + "query resource requirements failed: %x\n", + resp->status); + } else { + for (i = 0; i < 6; i++) { + completion->hpdev->probed_bar[i] = + q_res_req->probed_bar[i]; + } + } + + complete(&completion->host_event); +} + +static void get_pcichild(struct hv_pci_dev *hpdev, + enum hv_pcidev_ref_reason reason) +{ + atomic_inc(&hpdev->refs); +} + +static void put_pcichild(struct hv_pci_dev *hpdev, + enum hv_pcidev_ref_reason reason) +{ + if (atomic_dec_and_test(&hpdev->refs)) + kfree(hpdev); +} + +/** + * new_pcichild_device() - Create a new child device + * @hbus: The internal struct tracking this root PCI bus. + * @desc: The information supplied so far from the host + * about the device. + * + * This function creates the tracking structure for a new child + * device and kicks off the process of figuring out what it is. + * + * Return: Pointer to the new tracking struct + */ +static struct hv_pci_dev *new_pcichild_device(struct hv_pcibus_device *hbus, + struct pci_function_description *desc) +{ + struct hv_pci_dev *hpdev; + struct pci_child_message *res_req; + struct q_res_req_compl comp_pkt; + union { + struct pci_packet init_packet; + u8 buffer[0x100]; + } pkt; + unsigned long flags; + int ret; + + hpdev = kzalloc(sizeof(*hpdev), GFP_ATOMIC); + if (!hpdev) + return NULL; + + hpdev->hbus = hbus; + + memset(&pkt, 0, sizeof(pkt)); + init_completion(&comp_pkt.host_event); + comp_pkt.hpdev = hpdev; + 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->wslot.slot = desc->win_slot.slot; + + ret = vmbus_sendpacket(hbus->hdev->channel, res_req, + sizeof(struct pci_child_message), + (unsigned long)&pkt.init_packet, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + goto error; + + wait_for_completion(&comp_pkt.host_event); + + hpdev->desc = *desc; + get_pcichild(hpdev, hv_pcidev_ref_initial); + get_pcichild(hpdev, hv_pcidev_ref_childlist); + spin_lock_irqsave(&hbus->device_list_lock, flags); + list_add_tail(&hpdev->list_entry, &hbus->children); + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + return hpdev; + +error: + kfree(hpdev); + return NULL; +} + +/** + * get_pcichild_wslot() - Find device from slot + * @hbus: Root PCI bus, as understood by this driver + * @wslot: Location on the bus + * + * This function looks up a PCI device and returns the internal + * representation of it. It acquires a reference on it, so that + * the device won't be deleted while somebody is using it. The + * caller is responsible for calling put_pcichild() to release + * this reference. + * + * Return: Internal representation of a PCI device + */ +static struct hv_pci_dev *get_pcichild_wslot(struct hv_pcibus_device *hbus, + u32 wslot) +{ + unsigned long flags; + struct hv_pci_dev *iter, *hpdev = NULL; + + spin_lock_irqsave(&hbus->device_list_lock, flags); + list_for_each_entry(iter, &hbus->children, list_entry) { + if (iter->desc.win_slot.slot == wslot) { + hpdev = iter; + get_pcichild(hpdev, hv_pcidev_ref_by_slot); + break; + } + } + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + return hpdev; +} + +/** + * pci_devices_present_work() - Handle new list of child devices + * @work: Work struct embedded in struct hv_dr_work + * + * "Bus Relations" is the Windows term for "children of this + * bus." The terminology is preserved here for people trying to + * debug the interaction between Hyper-V and Linux. This + * function is called when the parent partition reports a list + * of functions that should be observed under this PCI Express + * port (bus). + * + * This function updates the list, and must tolerate being + * called multiple times with the same information. The typical + * number of child devices is one, with very atypical cases + * involving three or four, so the algorithms used here can be + * simple and inefficient. + * + * It must also treat the omission of a previously observed device as + * notification that the device no longer exists. + * + * Note that this function is a work item, and it may not be + * invoked in the order that it was queued. Back to back + * updates of the list of present devices may involve queuing + * multiple work items, and this one may run before ones that + * were sent later. As such, this function only does something + * if is the last one in the queue. + */ +static void pci_devices_present_work(struct work_struct *work) +{ + u32 child_no; + bool found; + struct list_head *iter; + struct pci_function_description *new_desc; + struct hv_pci_dev *hpdev; + struct hv_pcibus_device *hbus; + struct list_head removed; + struct hv_dr_work *dr_wrk; + struct hv_dr_state *dr = NULL; + unsigned long flags; + + dr_wrk = container_of(work, struct hv_dr_work, wrk); + hbus = dr_wrk->bus; + kfree(dr_wrk); + + INIT_LIST_HEAD(&removed); + + if (down_interruptible(&hbus->enum_sem)) { + put_hvpcibus(hbus); + return; + } + + /* Pull this off the queue and process it if it was the last one. */ + spin_lock_irqsave(&hbus->device_list_lock, flags); + while (!list_empty(&hbus->dr_list)) { + dr = list_first_entry(&hbus->dr_list, struct hv_dr_state, + list_entry); + list_del(&dr->list_entry); + + /* Throw this away if the list still has stuff in it. */ + if (!list_empty(&hbus->dr_list)) { + kfree(dr); + continue; + } + } + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + if (!dr) { + up(&hbus->enum_sem); + put_hvpcibus(hbus); + return; + } + + /* First, mark all existing children as reported missing. */ + spin_lock_irqsave(&hbus->device_list_lock, flags); + list_for_each(iter, &hbus->children) { + hpdev = container_of(iter, struct hv_pci_dev, + list_entry); + hpdev->reported_missing = true; + } + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + /* Next, add back any reported devices. */ + for (child_no = 0; child_no < dr->device_count; child_no++) { + found = false; + new_desc = &dr->func[child_no]; + + spin_lock_irqsave(&hbus->device_list_lock, flags); + list_for_each(iter, &hbus->children) { + hpdev = container_of(iter, struct hv_pci_dev, + list_entry); + if ((hpdev->desc.win_slot.slot == + new_desc->win_slot.slot) && + (hpdev->desc.v_id == new_desc->v_id) && + (hpdev->desc.d_id == new_desc->d_id) && + (hpdev->desc.ser == new_desc->ser)) { + hpdev->reported_missing = false; + found = true; + } + } + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + if (!found) { + hpdev = new_pcichild_device(hbus, new_desc); + if (!hpdev) + dev_err(&hbus->hdev->device, + "couldn't record a child device.\n"); + } + } + + /* Move missing children to a list on the stack. */ + spin_lock_irqsave(&hbus->device_list_lock, flags); + do { + found = false; + list_for_each(iter, &hbus->children) { + hpdev = container_of(iter, struct hv_pci_dev, + list_entry); + 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); + break; + } + } + } while (found); + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + /* Delete everything that should no longer exist. */ + while (!list_empty(&removed)) { + hpdev = list_first_entry(&removed, struct hv_pci_dev, + list_entry); + list_del(&hpdev->list_entry); + put_pcichild(hpdev, hv_pcidev_ref_initial); + } + + /* Tell the core to rescan bus because there may have been changes. */ + if (hbus->state == hv_pcibus_installed) { + pci_lock_rescan_remove(); + pci_scan_child_bus(hbus->pci_bus); + pci_unlock_rescan_remove(); + } else { + survey_child_resources(hbus); + } + + up(&hbus->enum_sem); + put_hvpcibus(hbus); + kfree(dr); +} + +/** + * hv_pci_devices_present() - Handles list of new children + * @hbus: Root PCI bus, as understood by this driver + * @relations: Packet from host listing children + * + * This function is invoked whenever a new list of devices for + * this bus appears. + */ +static void hv_pci_devices_present(struct hv_pcibus_device *hbus, + struct pci_bus_relations *relations) +{ + struct hv_dr_state *dr; + struct hv_dr_work *dr_wrk; + unsigned long flags; + + dr_wrk = kzalloc(sizeof(*dr_wrk), GFP_NOWAIT); + if (!dr_wrk) + return; + + dr = kzalloc(offsetof(struct hv_dr_state, func) + + (sizeof(struct pci_function_description) * + (relations->device_count)), GFP_NOWAIT); + if (!dr) { + kfree(dr_wrk); + return; + } + + INIT_WORK(&dr_wrk->wrk, pci_devices_present_work); + dr_wrk->bus = hbus; + dr->device_count = relations->device_count; + if (dr->device_count != 0) { + memcpy(dr->func, relations->func, + sizeof(struct pci_function_description) * + dr->device_count); + } + + spin_lock_irqsave(&hbus->device_list_lock, flags); + list_add_tail(&dr->list_entry, &hbus->dr_list); + spin_unlock_irqrestore(&hbus->device_list_lock, flags); + + get_hvpcibus(hbus); + schedule_work(&dr_wrk->wrk); +} + +/** + * hv_eject_device_work() - Asynchronously handles ejection + * @work: Work struct embedded in internal device struct + * + * This function handles ejecting a device. Windows will + * attempt to gracefully eject a device, waiting 60 seconds to + * hear back from the guest OS that this completed successfully. + * If this timer expires, the device will be forcibly removed. + */ +static void hv_eject_device_work(struct work_struct *work) +{ + struct pci_eject_response *ejct_pkt; + struct hv_pci_dev *hpdev; + struct pci_dev *pdev; + unsigned long flags; + int wslot; + struct { + struct pci_packet pkt; + u8 buffer[sizeof(struct pci_eject_response) - + sizeof(struct pci_message)]; + } ctxt; + + hpdev = container_of(work, struct hv_pci_dev, wrk); + + if (hpdev->state != hv_pcichild_ejecting) { + put_pcichild(hpdev, hv_pcidev_ref_pnp); + return; + } + + /* + * Ejection can come before or after the PCI bus has been set up, so + * attempt to find it and tear down the bus state, if it exists. This + * must be done without constructs like pci_domain_nr(hbus->pci_bus) + * because hbus->pci_bus may not exist yet. + */ + wslot = wslot_to_devfn(hpdev->desc.win_slot.slot); + pdev = pci_get_domain_bus_and_slot(hpdev->hbus->sysdata.domain, 0, + wslot); + if (pdev) { + pci_stop_and_remove_bus_device(pdev); + pci_dev_put(pdev); + } + + memset(&ctxt, 0, sizeof(ctxt)); + ejct_pkt = (struct pci_eject_response *)&ctxt.pkt.message; + ejct_pkt->message_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, + VM_PKT_DATA_INBAND, 0); + + spin_lock_irqsave(&hpdev->hbus->device_list_lock, flags); + list_del(&hpdev->list_entry); + spin_unlock_irqrestore(&hpdev->hbus->device_list_lock, flags); + + put_pcichild(hpdev, hv_pcidev_ref_childlist); + put_pcichild(hpdev, hv_pcidev_ref_pnp); + put_hvpcibus(hpdev->hbus); +} + +/** + * hv_pci_eject_device() - Handles device ejection + * @hpdev: Internal device tracking struct + * + * This function is invoked when an ejection packet arrives. It + * just schedules work so that we don't re-enter the packet + * delivery code handling the ejection. + */ +static void hv_pci_eject_device(struct hv_pci_dev *hpdev) +{ + hpdev->state = hv_pcichild_ejecting; + get_pcichild(hpdev, hv_pcidev_ref_pnp); + INIT_WORK(&hpdev->wrk, hv_eject_device_work); + get_hvpcibus(hpdev->hbus); + schedule_work(&hpdev->wrk); +} + +/** + * hv_pci_onchannelcallback() - Handles incoming packets + * @context: Internal bus tracking struct + * + * This function is invoked whenever the host sends a packet to + * this channel (which is private to this root PCI bus). + */ +static void hv_pci_onchannelcallback(void *context) +{ + const int packet_size = 0x100; + int ret; + struct hv_pcibus_device *hbus = context; + u32 bytes_recvd; + u64 req_id; + struct vmpacket_descriptor *desc; + unsigned char *buffer; + int bufferlen = packet_size; + struct pci_packet *comp_packet; + struct pci_response *response; + struct pci_incoming_message *new_message; + struct pci_bus_relations *bus_rel; + struct pci_dev_incoming *dev_message; + struct hv_pci_dev *hpdev; + + buffer = kmalloc(bufferlen, GFP_ATOMIC); + if (!buffer) + return; + + while (1) { + ret = vmbus_recvpacket_raw(hbus->hdev->channel, buffer, + bufferlen, &bytes_recvd, &req_id); + + if (ret == -ENOBUFS) { + kfree(buffer); + /* Handle large packet */ + bufferlen = bytes_recvd; + buffer = kmalloc(bytes_recvd, GFP_ATOMIC); + if (!buffer) + return; + continue; + } + + /* + * All incoming packets must be at least as large as a + * response. + */ + if (bytes_recvd <= sizeof(struct pci_response)) { + kfree(buffer); + return; + } + desc = (struct vmpacket_descriptor *)buffer; + + switch (desc->type) { + case VM_PKT_COMP: + + /* + * The host is trusted, and thus it's safe to interpret + * this transaction ID as a pointer. + */ + comp_packet = (struct pci_packet *)req_id; + response = (struct pci_response *)buffer; + comp_packet->completion_func(comp_packet->compl_ctxt, + response, + bytes_recvd); + kfree(buffer); + return; + + case VM_PKT_DATA_INBAND: + + new_message = (struct pci_incoming_message *)buffer; + switch (new_message->message_type.message_type) { + case PCI_BUS_RELATIONS: + + bus_rel = (struct pci_bus_relations *)buffer; + if (bytes_recvd < + offsetof(struct pci_bus_relations, func) + + (sizeof(struct pci_function_description) * + (bus_rel->device_count))) { + dev_err(&hbus->hdev->device, + "bus relations too small\n"); + break; + } + + hv_pci_devices_present(hbus, bus_rel); + break; + + case PCI_EJECT: + + dev_message = (struct pci_dev_incoming *)buffer; + hpdev = get_pcichild_wslot(hbus, + dev_message->wslot.slot); + if (hpdev) { + hv_pci_eject_device(hpdev); + put_pcichild(hpdev, + hv_pcidev_ref_by_slot); + } + break; + + default: + dev_warn(&hbus->hdev->device, + "Unimplemented protocol message %x\n", + new_message->message_type.message_type); + break; + } + break; + + default: + dev_err(&hbus->hdev->device, + "unhandled packet type %d, tid %llx len %d\n", + desc->type, req_id, bytes_recvd); + break; + } + break; + } +} + +/** + * hv_pci_protocol_negotiation() - Set up protocol + * @hdev: VMBus's tracking struct for this root PCI bus + * + * This driver is intended to support running on Windows 10 + * (server) and later versions. It will not run on earlier + * versions, as they assume that many of the operations which + * Linux needs accomplished with a spinlock held were done via + * asynchronous messaging via VMBus. Windows 10 increases the + * surface area of PCI emulation so that these actions can take + * place by suspending a virtual processor for their duration. + * + * This function negotiates the channel protocol version, + * failing if the host doesn't support the necessary protocol + * level. + */ +static int hv_pci_protocol_negotiation(struct hv_device *hdev) +{ + struct pci_version_request *version_req; + struct hv_pci_compl comp_pkt; + struct pci_packet *pkt; + int ret; + + /* + * Initiate the handshake with the host and negotiate + * a version that the host can support. We start with the + * highest version number and go down if the host cannot + * support it. + */ + pkt = kzalloc(sizeof(*pkt) + sizeof(*version_req), GFP_KERNEL); + if (!pkt) + return -ENOMEM; + + init_completion(&comp_pkt.host_event); + 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->protocol_version = PCI_PROTOCOL_VERSION_CURRENT; + + ret = vmbus_sendpacket(hdev->channel, version_req, + sizeof(struct pci_version_request), + (unsigned long)pkt, VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + goto exit; + + wait_for_completion(&comp_pkt.host_event); + + if (comp_pkt.completion_status < 0) { + dev_err(&hdev->device, + "PCI Pass-through VSP failed version request %x\n", + comp_pkt.completion_status); + ret = -EPROTO; + goto exit; + } + + ret = 0; + +exit: + kfree(pkt); + return ret; +} + +/** + * hv_pci_free_bridge_windows() - Release memory regions for the + * bus + * @hbus: Root PCI bus, as understood by this driver + */ +static void hv_pci_free_bridge_windows(struct hv_pcibus_device *hbus) +{ + /* + * Set the resources back to the way they looked when they + * were allocated by setting IORESOURCE_BUSY again. + */ + + if (hbus->low_mmio_space && hbus->low_mmio_res) { + hbus->low_mmio_res->flags |= IORESOURCE_BUSY; + release_mem_region(hbus->low_mmio_res->start, + resource_size(hbus->low_mmio_res)); + } + + if (hbus->high_mmio_space && hbus->high_mmio_res) { + hbus->high_mmio_res->flags |= IORESOURCE_BUSY; + release_mem_region(hbus->high_mmio_res->start, + resource_size(hbus->high_mmio_res)); + } +} + +/** + * hv_pci_allocate_bridge_windows() - Allocate memory regions + * for the bus + * @hbus: Root PCI bus, as understood by this driver + * + * This function calls vmbus_allocate_mmio(), which is itself a + * bit of a compromise. Ideally, we might change the pnp layer + * in the kernel such that it comprehends either PCI devices + * which are "grandchildren of ACPI," with some intermediate bus + * node (in this case, VMBus) or change it such that it + * understands VMBus. The pnp layer, however, has been declared + * deprecated, and not subject to change. + * + * The workaround, implemented here, is to ask VMBus to allocate + * MMIO space for this bus. VMBus itself knows which ranges are + * appropriate by looking at its own ACPI objects. Then, after + * these ranges are claimed, they're modified to look like they + * would have looked if the ACPI and pnp code had allocated + * bridge windows. These descriptors have to exist in this form + * in order to satisfy the code which will get invoked when the + * endpoint PCI function driver calls request_mem_region() or + * request_mem_region_exclusive(). + * + * Return: 0 on success, -errno on failure + */ +static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus) +{ + resource_size_t align; + int ret; + + if (hbus->low_mmio_space) { + align = 1ULL << (63 - __builtin_clzll(hbus->low_mmio_space)); + ret = vmbus_allocate_mmio(&hbus->low_mmio_res, hbus->hdev, 0, + (u64)(u32)0xffffffff, + hbus->low_mmio_space, + align, false); + if (ret) { + dev_err(&hbus->hdev->device, + "Need %#llx of low MMIO space. Consider reconfiguring the VM.\n", + hbus->low_mmio_space); + return ret; + } + + /* Modify this resource to become a bridge window. */ + hbus->low_mmio_res->flags |= IORESOURCE_WINDOW; + hbus->low_mmio_res->flags &= ~IORESOURCE_BUSY; + pci_add_resource(&hbus->resources_for_children, + hbus->low_mmio_res); + } + + if (hbus->high_mmio_space) { + align = 1ULL << (63 - __builtin_clzll(hbus->high_mmio_space)); + ret = vmbus_allocate_mmio(&hbus->high_mmio_res, hbus->hdev, + 0x100000000, -1, + hbus->high_mmio_space, align, + false); + if (ret) { + dev_err(&hbus->hdev->device, + "Need %#llx of high MMIO space. Consider reconfiguring the VM.\n", + hbus->high_mmio_space); + goto release_low_mmio; + } + + /* Modify this resource to become a bridge window. */ + hbus->high_mmio_res->flags |= IORESOURCE_WINDOW; + hbus->high_mmio_res->flags &= ~IORESOURCE_BUSY; + pci_add_resource(&hbus->resources_for_children, + hbus->high_mmio_res); + } + + return 0; + +release_low_mmio: + if (hbus->low_mmio_res) { + release_mem_region(hbus->low_mmio_res->start, + resource_size(hbus->low_mmio_res)); + } + + return ret; +} + +/** + * hv_allocate_config_window() - Find MMIO space for PCI Config + * @hbus: Root PCI bus, as understood by this driver + * + * This function claims memory-mapped I/O space for accessing + * configuration space for the functions on this bus. + * + * Return: 0 on success, -errno on failure + */ +static int hv_allocate_config_window(struct hv_pcibus_device *hbus) +{ + int ret; + + /* + * Set up a region of MMIO space to use for accessing configuration + * space. + */ + ret = vmbus_allocate_mmio(&hbus->mem_config, hbus->hdev, 0, -1, + PCI_CONFIG_MMIO_LENGTH, 0x1000, false); + if (ret) + return ret; + + /* + * vmbus_allocate_mmio() gets used for allocating both device endpoint + * resource claims (those which cannot be overlapped) and the ranges + * which are valid for the children of this bus, which are intended + * to be overlapped by those children. Set the flag on this claim + * meaning that this region can't be overlapped. + */ + + hbus->mem_config->flags |= IORESOURCE_BUSY; + + return 0; +} + +static void hv_free_config_window(struct hv_pcibus_device *hbus) +{ + release_mem_region(hbus->mem_config->start, PCI_CONFIG_MMIO_LENGTH); +} + +/** + * hv_pci_enter_d0() - Bring the "bus" into the D0 power state + * @hdev: VMBus's tracking struct for this root PCI bus + * + * Return: 0 on success, -errno on failure + */ +static int hv_pci_enter_d0(struct hv_device *hdev) +{ + struct hv_pcibus_device *hbus = hv_get_drvdata(hdev); + struct pci_bus_d0_entry *d0_entry; + struct hv_pci_compl comp_pkt; + struct pci_packet *pkt; + int ret; + + /* + * Tell the host that the bus is ready to use, and moved into the + * powered-on state. This includes telling the host which region + * of memory-mapped I/O space has been chosen for configuration space + * access. + */ + pkt = kzalloc(sizeof(*pkt) + sizeof(*d0_entry), GFP_KERNEL); + if (!pkt) + return -ENOMEM; + + init_completion(&comp_pkt.host_event); + 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->mmio_base = hbus->mem_config->start; + + ret = vmbus_sendpacket(hdev->channel, d0_entry, sizeof(*d0_entry), + (unsigned long)pkt, VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + goto exit; + + wait_for_completion(&comp_pkt.host_event); + + if (comp_pkt.completion_status < 0) { + dev_err(&hdev->device, + "PCI Pass-through VSP failed D0 Entry with status %x\n", + comp_pkt.completion_status); + ret = -EPROTO; + goto exit; + } + + ret = 0; + +exit: + kfree(pkt); + return ret; +} + +/** + * hv_pci_query_relations() - Ask host to send list of child + * devices + * @hdev: VMBus's tracking struct for this root PCI bus + * + * Return: 0 on success, -errno on failure + */ +static int hv_pci_query_relations(struct hv_device *hdev) +{ + struct hv_pcibus_device *hbus = hv_get_drvdata(hdev); + struct pci_message message; + struct completion comp; + int ret; + + /* Ask the host to send along the list of child devices */ + init_completion(&comp); + if (cmpxchg(&hbus->survey_event, NULL, &comp)) + return -ENOTEMPTY; + + memset(&message, 0, sizeof(message)); + message.message_type = PCI_QUERY_BUS_RELATIONS; + + ret = vmbus_sendpacket(hdev->channel, &message, sizeof(message), + 0, VM_PKT_DATA_INBAND, 0); + if (ret) + return ret; + + wait_for_completion(&comp); + return 0; +} + +/** + * hv_send_resources_allocated() - Report local resource choices + * @hdev: VMBus's tracking struct for this root PCI bus + * + * The host OS is expecting to be sent a request as a message + * which contains all the resources that the device will use. + * The response contains those same resources, "translated" + * which is to say, the values which should be used by the + * hardware, when it delivers an interrupt. (MMIO resources are + * used in local terms.) This is nice for Windows, and lines up + * with the FDO/PDO split, which doesn't exist in Linux. Linux + * is deeply expecting to scan an emulated PCI configuration + * space. So this message is sent here only to drive the state + * machine on the host forward. + * + * Return: 0 on success, -errno on failure + */ +static int hv_send_resources_allocated(struct hv_device *hdev) +{ + struct hv_pcibus_device *hbus = hv_get_drvdata(hdev); + struct pci_resources_assigned *res_assigned; + struct hv_pci_compl comp_pkt; + struct hv_pci_dev *hpdev; + struct pci_packet *pkt; + u32 wslot; + int ret; + + pkt = kmalloc(sizeof(*pkt) + sizeof(*res_assigned), GFP_KERNEL); + if (!pkt) + return -ENOMEM; + + ret = 0; + + for (wslot = 0; wslot < 256; wslot++) { + hpdev = get_pcichild_wslot(hbus, wslot); + if (!hpdev) + continue; + + memset(pkt, 0, sizeof(*pkt) + sizeof(*res_assigned)); + 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->wslot.slot = hpdev->desc.win_slot.slot; + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); + + ret = vmbus_sendpacket( + hdev->channel, &pkt->message, + sizeof(*res_assigned), + (unsigned long)pkt, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + break; + + wait_for_completion(&comp_pkt.host_event); + + if (comp_pkt.completion_status < 0) { + ret = -EPROTO; + dev_err(&hdev->device, + "resource allocated returned 0x%x", + comp_pkt.completion_status); + break; + } + } + + kfree(pkt); + return ret; +} + +/** + * hv_send_resources_released() - Report local resources + * released + * @hdev: VMBus's tracking struct for this root PCI bus + * + * Return: 0 on success, -errno on failure + */ +static int hv_send_resources_released(struct hv_device *hdev) +{ + struct hv_pcibus_device *hbus = hv_get_drvdata(hdev); + struct pci_child_message pkt; + struct hv_pci_dev *hpdev; + u32 wslot; + int ret; + + for (wslot = 0; wslot < 256; wslot++) { + hpdev = get_pcichild_wslot(hbus, wslot); + if (!hpdev) + continue; + + memset(&pkt, 0, sizeof(pkt)); + pkt.message_type = PCI_RESOURCES_RELEASED; + pkt.wslot.slot = hpdev->desc.win_slot.slot; + + put_pcichild(hpdev, hv_pcidev_ref_by_slot); + + ret = vmbus_sendpacket(hdev->channel, &pkt, sizeof(pkt), 0, + VM_PKT_DATA_INBAND, 0); + if (ret) + return ret; + } + + return 0; +} + +static void get_hvpcibus(struct hv_pcibus_device *hbus) +{ + atomic_inc(&hbus->remove_lock); +} + +static void put_hvpcibus(struct hv_pcibus_device *hbus) +{ + if (atomic_dec_and_test(&hbus->remove_lock)) + complete(&hbus->remove_event); +} + +/** + * hv_pci_probe() - New VMBus channel probe, for a root PCI bus + * @hdev: VMBus's tracking struct for this root PCI bus + * @dev_id: Identifies the device itself + * + * Return: 0 on success, -errno on failure + */ +static int hv_pci_probe(struct hv_device *hdev, + const struct hv_vmbus_device_id *dev_id) +{ + struct hv_pcibus_device *hbus; + int ret; + + hbus = kzalloc(sizeof(*hbus), GFP_KERNEL); + if (!hbus) + return -ENOMEM; + + /* + * The PCI bus "domain" is what is called "segment" in ACPI and + * other specs. Pull it from the instance ID, to get something + * unique. Bytes 8 and 9 are what is used in Windows guests, so + * do the same thing for consistency. Note that, since this code + * only runs in a Hyper-V VM, Hyper-V can (and does) guarantee + * that (1) the only domain in use for something that looks like + * a physical PCI bus (which is actually emulated by the + * hypervisor) is domain 0 and (2) there will be no overlap + * between domains derived from these instance IDs in the same + * VM. + */ + hbus->sysdata.domain = hdev->dev_instance.b[9] | + hdev->dev_instance.b[8] << 8; + + hbus->hdev = hdev; + atomic_inc(&hbus->remove_lock); + INIT_LIST_HEAD(&hbus->children); + INIT_LIST_HEAD(&hbus->dr_list); + INIT_LIST_HEAD(&hbus->resources_for_children); + spin_lock_init(&hbus->config_lock); + spin_lock_init(&hbus->device_list_lock); + sema_init(&hbus->enum_sem, 1); + init_completion(&hbus->remove_event); + + ret = vmbus_open(hdev->channel, pci_ring_size, pci_ring_size, NULL, 0, + hv_pci_onchannelcallback, hbus); + if (ret) + goto free_bus; + + hv_set_drvdata(hdev, hbus); + + ret = hv_pci_protocol_negotiation(hdev); + if (ret) + goto close; + + ret = hv_allocate_config_window(hbus); + if (ret) + goto close; + + hbus->cfg_addr = ioremap(hbus->mem_config->start, + PCI_CONFIG_MMIO_LENGTH); + if (!hbus->cfg_addr) { + dev_err(&hdev->device, + "Unable to map a virtual address for config space\n"); + ret = -ENOMEM; + goto free_config; + } + + hbus->sysdata.fwnode = irq_domain_alloc_fwnode(hbus); + if (!hbus->sysdata.fwnode) { + ret = -ENOMEM; + goto unmap; + } + + ret = hv_pcie_init_irq_domain(hbus); + if (ret) + goto free_fwnode; + + ret = hv_pci_query_relations(hdev); + if (ret) + goto free_irq_domain; + + ret = hv_pci_enter_d0(hdev); + if (ret) + goto free_irq_domain; + + ret = hv_pci_allocate_bridge_windows(hbus); + if (ret) + goto free_irq_domain; + + ret = hv_send_resources_allocated(hdev); + if (ret) + goto free_windows; + + prepopulate_bars(hbus); + + hbus->state = hv_pcibus_probed; + + ret = create_root_hv_pci_bus(hbus); + if (ret) + goto free_windows; + + return 0; + +free_windows: + hv_pci_free_bridge_windows(hbus); +free_irq_domain: + irq_domain_remove(hbus->irq_domain); +free_fwnode: + irq_domain_free_fwnode(hbus->sysdata.fwnode); +unmap: + iounmap(hbus->cfg_addr); +free_config: + hv_free_config_window(hbus); +close: + vmbus_close(hdev->channel); +free_bus: + kfree(hbus); + return ret; +} + +/** + * hv_pci_remove() - Remove routine for this VMBus channel + * @hdev: VMBus's tracking struct for this root PCI bus + * + * Return: 0 on success, -errno on failure + */ +static int hv_pci_remove(struct hv_device *hdev) +{ + int ret; + struct hv_pcibus_device *hbus; + union { + struct pci_packet teardown_packet; + u8 buffer[0x100]; + } pkt; + struct pci_bus_relations relations; + struct hv_pci_compl comp_pkt; + + hbus = hv_get_drvdata(hdev); + + ret = hv_send_resources_released(hdev); + if (ret) + dev_err(&hdev->device, + "Couldn't send resources released packet(s)\n"); + + memset(&pkt.teardown_packet, 0, sizeof(pkt.teardown_packet)); + 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; + + ret = vmbus_sendpacket(hdev->channel, &pkt.teardown_packet.message, + sizeof(struct pci_message), + (unsigned long)&pkt.teardown_packet, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (!ret) + wait_for_completion_timeout(&comp_pkt.host_event, 10 * HZ); + + if (hbus->state == hv_pcibus_installed) { + /* Remove the bus from PCI's point of view. */ + pci_lock_rescan_remove(); + pci_stop_root_bus(hbus->pci_bus); + pci_remove_root_bus(hbus->pci_bus); + pci_unlock_rescan_remove(); + } + + vmbus_close(hdev->channel); + + /* Delete any children which might still exist. */ + memset(&relations, 0, sizeof(relations)); + hv_pci_devices_present(hbus, &relations); + + iounmap(hbus->cfg_addr); + hv_free_config_window(hbus); + pci_free_resource_list(&hbus->resources_for_children); + hv_pci_free_bridge_windows(hbus); + irq_domain_remove(hbus->irq_domain); + irq_domain_free_fwnode(hbus->sysdata.fwnode); + put_hvpcibus(hbus); + wait_for_completion(&hbus->remove_event); + kfree(hbus); + return 0; +} + +static const struct hv_vmbus_device_id hv_pci_id_table[] = { + /* PCI Pass-through Class ID */ + /* 44C4F61D-4444-4400-9D52-802E27EDE19F */ + { HV_PCIE_GUID, }, + { }, +}; + +MODULE_DEVICE_TABLE(vmbus, hv_pci_id_table); + +static struct hv_driver hv_pci_drv = { + .name = "hv_pci", + .id_table = hv_pci_id_table, + .probe = hv_pci_probe, + .remove = hv_pci_remove, +}; + +static void __exit exit_hv_pci_drv(void) +{ + vmbus_driver_unregister(&hv_pci_drv); +} + +static int __init init_hv_pci_drv(void) +{ + return vmbus_driver_register(&hv_pci_drv); +} + +module_init(init_hv_pci_drv); +module_exit(exit_hv_pci_drv); + +MODULE_DESCRIPTION("Hyper-V PCI"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From b84106b4e2290c081cdab521fa832596cdfea246 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 25 Feb 2016 14:35:57 -0600 Subject: PCI: Disable IO/MEM decoding for devices with non-compliant BARs The PCI config header (first 64 bytes of each device's config space) is defined by the PCI spec so generic software can identify the device and manage its usage of I/O, memory, and IRQ resources. Some non-spec-compliant devices put registers other than BARs where the BARs should be. When the PCI core sizes these "BARs", the reads and writes it does may have unwanted side effects, and the "BAR" may appear to describe non-sensical address space. Add a flag bit to mark non-compliant devices so we don't touch their BARs. Turn off IO/MEM decoding to prevent the devices from consuming address space, since we can't read the BARs to find out what that address space would be. Signed-off-by: Bjorn Helgaas Tested-by: Andi Kleen CC: stable@vger.kernel.org --- drivers/pci/probe.c | 14 ++++++++++++++ include/linux/pci.h | 1 + 2 files changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9bb0d5a..6b0056e9c33e 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -179,6 +179,9 @@ int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, u16 orig_cmd; struct pci_bus_region region, inverted_region; + if (dev->non_compliant_bars) + return 0; + mask = type ? PCI_ROM_ADDRESS_MASK : ~0; /* No printks while decoding is disabled! */ @@ -1171,6 +1174,7 @@ static void pci_msi_setup_pci_dev(struct pci_dev *dev) int pci_setup_device(struct pci_dev *dev) { u32 class; + u16 cmd; u8 hdr_type; int pos = 0; struct pci_bus_region region; @@ -1214,6 +1218,16 @@ int pci_setup_device(struct pci_dev *dev) /* device class may be changed after fixup */ class = dev->class >> 8; + if (dev->non_compliant_bars) { + pci_read_config_word(dev, PCI_COMMAND, &cmd); + if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) { + dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n"); + cmd &= ~PCI_COMMAND_IO; + cmd &= ~PCI_COMMAND_MEMORY; + pci_write_config_word(dev, PCI_COMMAND, cmd); + } + } + switch (dev->hdr_type) { /* header type */ case PCI_HEADER_TYPE_NORMAL: /* standard header */ if (class == PCI_CLASS_BRIDGE_PCI) diff --git a/include/linux/pci.h b/include/linux/pci.h index 27df4a6585da..5f80661d3519 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -359,6 +359,7 @@ struct pci_dev { unsigned int io_window_1k:1; /* Intel P2P bridge 1K I/O windows */ unsigned int irq_managed:1; unsigned int has_secondary_link:1; + unsigned int non_compliant_bars:1; /* broken BARs; ignore them */ pci_dev_flags_t dev_flags; atomic_t enable_cnt; /* pci_enable_device has been called */ -- cgit v1.2.3-59-g8ed1b From 0a3d00b33b4ed873cbead8c241bd58b456d92d36 Mon Sep 17 00:00:00 2001 From: Kelly Zytaruk Date: Tue, 9 Feb 2016 13:08:58 -0500 Subject: PCI: Support SR-IOV on any function type Previously, we only supported SR-IOV on PCI Express Endpoints and Root Complex Integrated Endpoints. This restriction has been present since d1b054da8f59 ("PCI: initialize and release SR-IOV capability") added SR-IOV support, but the spec does not require it. In fact, the SR-IOV spec r1.1, sec 3.3, says the SR-IOV extended capability may be present for any Type 0 function. Remove the function type test, so we can support SR-IOV on any function. Some AMD GPUs have display outputs, use the VGA class code, are Legacy Endpoints, and support SR-IOV. This change allows Linux to enable SR-IOV on these devices. [bhelgaas: changelog] Link: https://bugzilla.kernel.org/show_bug.cgi?id=112221 Signed-off-by: Kelly Zytaruk Signed-off-by: Bjorn Helgaas --- drivers/pci/iov.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 31f31d460fc9..fe4bd0aa91a6 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -387,10 +387,6 @@ static int sriov_init(struct pci_dev *dev, int pos) struct resource *res; struct pci_dev *pdev; - if (pci_pcie_type(dev) != PCI_EXP_TYPE_RC_END && - pci_pcie_type(dev) != PCI_EXP_TYPE_ENDPOINT) - return -ENODEV; - pci_read_config_word(dev, pos + PCI_SRIOV_CTRL, &ctrl); if (ctrl & PCI_SRIOV_CTRL_VFE) { pci_write_config_word(dev, pos + PCI_SRIOV_CTRL, 0); -- cgit v1.2.3-59-g8ed1b From 6ca7227b3e24e4ba445464779a66558a8b9e0617 Mon Sep 17 00:00:00 2001 From: Naga Venkata Sai Indubhaskar Jupudi Date: Tue, 9 Feb 2016 14:10:16 -0800 Subject: PCI: Fix broken URL for Dell biosdevname Dell developed a way to consistently name devices, and their last proposal was accepted under the name biosdevname. Fix a broken URL to biosdevname documentation. Signed-off-by: Naga Venkata Sai Indubhaskar Jupudi Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-label.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pci-label.c b/drivers/pci/pci-label.c index 0ae74d96ed85..51357377efbc 100644 --- a/drivers/pci/pci-label.c +++ b/drivers/pci/pci-label.c @@ -16,7 +16,7 @@ * the instance number and string from the type 41 record and exports * it to sysfs. * - * Please see http://linux.dell.com/wiki/index.php/Oss/libnetdevname for more + * Please see http://linux.dell.com/files/biosdevname/ for more * information. */ -- cgit v1.2.3-59-g8ed1b From 28e3abe591e22717f7fabb9ffa839571b85df323 Mon Sep 17 00:00:00 2001 From: Justin Waters Date: Fri, 15 Jan 2016 10:24:35 -0500 Subject: PCI: imx6: Add DT bindings to configure PHY Tx driver settings The settings in GPR8 are dependent upon the particular layout of the hardware platform. As such, they should be configurable via the device tree. Look up PHY Tx driver settings from the device tree. Fall back to the original hard-coded values if they are not specified in the device tree. Signed-off-by: Justin Waters Signed-off-by: Bjorn Helgaas Acked-by: Lucas Stach --- .../devicetree/bindings/pci/fsl,imx6q-pcie.txt | 7 ++++ drivers/pci/host/pci-imx6.c | 42 +++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt index 6fbba53a309b..3be80c68941a 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.txt @@ -13,6 +13,13 @@ Required properties: - clock-names: Must include the following additional entries: - "pcie_phy" +Optional properties: +- fsl,tx-deemph-gen1: Gen1 De-emphasis value. Default: 0 +- fsl,tx-deemph-gen2-3p5db: Gen2 (3.5db) De-emphasis value. Default: 0 +- fsl,tx-deemph-gen2-6db: Gen2 (6db) De-emphasis value. Default: 20 +- fsl,tx-swing-full: Gen2 TX SWING FULL value. Default: 127 +- fsl,tx-swing-low: TX launch amplitude swing_low value. Default: 127 + Example: pcie@0x01000000 { diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index bd3f7d0ef943..8c9b3896d6f5 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -39,6 +39,11 @@ struct imx6_pcie { struct pcie_port pp; struct regmap *iomuxc_gpr; void __iomem *mem_base; + u32 tx_deemph_gen1; + u32 tx_deemph_gen2_3p5db; + u32 tx_deemph_gen2_6db; + u32 tx_swing_full; + u32 tx_swing_low; }; /* PCIe Root Complex registers (memory-mapped) */ @@ -334,15 +339,20 @@ static void imx6_pcie_init_phy(struct pcie_port *pp) IMX6Q_GPR12_LOS_LEVEL, 9 << 4); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN1, 0 << 0); + IMX6Q_GPR8_TX_DEEMPH_GEN1, + imx6_pcie->tx_deemph_gen1 << 0); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, 0 << 6); + IMX6Q_GPR8_TX_DEEMPH_GEN2_3P5DB, + imx6_pcie->tx_deemph_gen2_3p5db << 6); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, 20 << 12); + IMX6Q_GPR8_TX_DEEMPH_GEN2_6DB, + imx6_pcie->tx_deemph_gen2_6db << 12); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_SWING_FULL, 127 << 18); + IMX6Q_GPR8_TX_SWING_FULL, + imx6_pcie->tx_swing_full << 18); regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_SWING_LOW, 127 << 25); + IMX6Q_GPR8_TX_SWING_LOW, + imx6_pcie->tx_swing_low << 25); } static int imx6_pcie_wait_for_link(struct pcie_port *pp) @@ -533,6 +543,7 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) struct imx6_pcie *imx6_pcie; struct pcie_port *pp; struct resource *dbi_base; + struct device_node *node = pdev->dev.of_node; int ret; imx6_pcie = devm_kzalloc(&pdev->dev, sizeof(*imx6_pcie), GFP_KERNEL); @@ -585,6 +596,27 @@ static int __init imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx6_pcie->iomuxc_gpr); } + /* Grab PCIe PHY Tx Settings */ + if (of_property_read_u32(node, "fsl,tx-deemph-gen1", + &imx6_pcie->tx_deemph_gen1)) + imx6_pcie->tx_deemph_gen1 = 0; + + if (of_property_read_u32(node, "fsl,tx-deemph-gen2-3p5db", + &imx6_pcie->tx_deemph_gen2_3p5db)) + imx6_pcie->tx_deemph_gen2_3p5db = 0; + + if (of_property_read_u32(node, "fsl,tx-deemph-gen2-6db", + &imx6_pcie->tx_deemph_gen2_6db)) + imx6_pcie->tx_deemph_gen2_6db = 20; + + if (of_property_read_u32(node, "fsl,tx-swing-full", + &imx6_pcie->tx_swing_full)) + imx6_pcie->tx_swing_full = 127; + + if (of_property_read_u32(node, "fsl,tx-swing-low", + &imx6_pcie->tx_swing_low)) + imx6_pcie->tx_swing_low = 127; + ret = imx6_add_pcie_port(pp, pdev); if (ret < 0) return ret; -- cgit v1.2.3-59-g8ed1b From f52e5629f66d5fd79ae8b8145d76ae0ddb47e142 Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Mon, 15 Feb 2016 09:42:00 +0100 Subject: PCI: Allow access to VPD attributes with size 0 It is not always possible to determine the actual size of the VPD data, so allow access to them if the size is set to '0'. Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Hannes Reinecke Signed-off-by: Bjorn Helgaas Cc: Alexander Duyck --- drivers/pci/pci-sysfs.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 95d9e7bd933b..a730f54d1247 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -769,10 +769,12 @@ static ssize_t read_vpd_attr(struct file *filp, struct kobject *kobj, { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); - if (off > bin_attr->size) - count = 0; - else if (count > bin_attr->size - off) - count = bin_attr->size - off; + if (bin_attr->size > 0) { + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + } return pci_read_vpd(dev, off, count, buf); } @@ -783,10 +785,12 @@ static ssize_t write_vpd_attr(struct file *filp, struct kobject *kobj, { struct pci_dev *dev = to_pci_dev(kobj_to_dev(kobj)); - if (off > bin_attr->size) - count = 0; - else if (count > bin_attr->size - off) - count = bin_attr->size - off; + if (bin_attr->size > 0) { + if (off > bin_attr->size) + count = 0; + else if (count > bin_attr->size - off) + count = bin_attr->size - off; + } return pci_write_vpd(dev, off, count, buf); } -- cgit v1.2.3-59-g8ed1b From c5563887a96912f84efe5c1322bfbc4f9a30e882 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 14:04:07 -0600 Subject: PCI: Use bitfield instead of bool for struct pci_vpd_pci22.busy Make struct pci_vpd_pci22.busy a 1-bit field instead of a bool. We intend to add another flag, and two bitfields are cheaper than two bools. Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 8c05b5ceeaec..a7f00699d0cb 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -283,8 +283,8 @@ struct pci_vpd_pci22 { struct pci_vpd base; struct mutex lock; u16 flag; - bool busy; u8 cap; + u8 busy:1; }; /* @@ -313,7 +313,7 @@ static int pci_vpd_pci22_wait(struct pci_dev *dev) return ret; if ((status & PCI_VPD_ADDR_F) == vpd->flag) { - vpd->busy = false; + vpd->busy = 0; return 0; } @@ -355,7 +355,7 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, pos & ~3); if (ret < 0) break; - vpd->busy = true; + vpd->busy = 1; vpd->flag = PCI_VPD_ADDR_F; ret = pci_vpd_pci22_wait(dev); if (ret < 0) @@ -415,7 +415,7 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count if (ret < 0) break; - vpd->busy = true; + vpd->busy = 1; vpd->flag = 0; ret = pci_vpd_pci22_wait(dev); if (ret < 0) @@ -495,7 +495,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev) vpd->base.ops = &pci_vpd_pci22_ops; mutex_init(&vpd->lock); vpd->cap = cap; - vpd->busy = false; + vpd->busy = 0; dev->vpd = &vpd->base; return 0; } -- cgit v1.2.3-59-g8ed1b From 104daa71b39614343929e1982170d5fcb0569bb5 Mon Sep 17 00:00:00 2001 From: Hannes Reinecke Date: Mon, 15 Feb 2016 09:42:01 +0100 Subject: PCI: Determine actual VPD size on first access PCI-2.2 VPD entries have a maximum size of 32k, but might actually be smaller than that. To figure out the actual size one has to read the VPD area until the 'end marker' is reached. Per spec, reading outside of the VPD space is "not allowed." In practice, it may cause simple read errors or even crash the card. To make matters worse not every PCI card implements this properly, leaving us with no 'end' marker or even completely invalid data. Try to determine the size of the VPD data when it's first accessed. If no valid data can be read an I/O error will be returned when reading or writing the sysfs attribute. As the amount of VPD data is unknown initially the size of the sysfs attribute will always be set to '0'. [bhelgaas: changelog, use 0/1 (not false/true) for bitfield, tweak pci_vpd_pci22_read() error checking] Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Hannes Reinecke Signed-off-by: Bjorn Helgaas Cc: Alexander Duyck --- drivers/pci/access.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++-- drivers/pci/pci-sysfs.c | 2 +- 2 files changed, 86 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index a7f00699d0cb..4850f0612268 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -285,8 +285,63 @@ struct pci_vpd_pci22 { u16 flag; u8 cap; u8 busy:1; + u8 valid:1; }; +/** + * pci_vpd_size - determine actual size of Vital Product Data + * @dev: pci device struct + * @old_size: current assumed size, also maximum allowed size + */ +static size_t pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size) +{ + size_t off = 0; + unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */ + + while (off < old_size && + pci_read_vpd(dev, off, 1, header) == 1) { + unsigned char tag; + + if (header[0] & PCI_VPD_LRDT) { + /* Large Resource Data Type Tag */ + tag = pci_vpd_lrdt_tag(header); + /* Only read length from known tag items */ + if ((tag == PCI_VPD_LTIN_ID_STRING) || + (tag == PCI_VPD_LTIN_RO_DATA) || + (tag == PCI_VPD_LTIN_RW_DATA)) { + if (pci_read_vpd(dev, off+1, 2, + &header[1]) != 2) { + dev_warn(&dev->dev, + "invalid large VPD tag %02x size at offset %zu", + tag, off + 1); + return 0; + } + off += PCI_VPD_LRDT_TAG_SIZE + + pci_vpd_lrdt_size(header); + } + } else { + /* Short Resource Data Type Tag */ + off += PCI_VPD_SRDT_TAG_SIZE + + pci_vpd_srdt_size(header); + tag = pci_vpd_srdt_tag(header); + } + + if (tag == PCI_VPD_STIN_END) /* End tag descriptor */ + return off; + + if ((tag != PCI_VPD_LTIN_ID_STRING) && + (tag != PCI_VPD_LTIN_RO_DATA) && + (tag != PCI_VPD_LTIN_RW_DATA)) { + dev_warn(&dev->dev, + "invalid %s VPD tag %02x at offset %zu", + (header[0] & PCI_VPD_LRDT) ? "large" : "short", + tag, off); + return 0; + } + } + return 0; +} + /* * Wait for last operation to complete. * This code has to spin since there is no other notification from the PCI @@ -337,9 +392,25 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, loff_t end = pos + count; u8 *buf = arg; - if (pos < 0 || pos > vpd->base.len || end > vpd->base.len) + if (pos < 0) return -EINVAL; + if (!vpd->valid) { + vpd->valid = 1; + vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len); + } + + if (vpd->base.len == 0) + return -EIO; + + if (pos >= vpd->base.len) + return 0; + + if (end > vpd->base.len) { + end = vpd->base.len; + count = end - pos; + } + if (mutex_lock_killable(&vpd->lock)) return -EINTR; @@ -389,7 +460,18 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count loff_t end = pos + count; int ret = 0; - if (pos < 0 || (pos & 3) || (count & 3) || end > vpd->base.len) + if (pos < 0 || (pos & 3) || (count & 3)) + return -EINVAL; + + if (!vpd->valid) { + vpd->valid = 1; + vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len); + } + + if (vpd->base.len == 0) + return -EIO; + + if (end > vpd->base.len) return -EINVAL; if (mutex_lock_killable(&vpd->lock)) @@ -496,6 +578,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev) mutex_init(&vpd->lock); vpd->cap = cap; vpd->busy = 0; + vpd->valid = 0; dev->vpd = &vpd->base; return 0; } diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index a730f54d1247..ed39c093aa15 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1323,7 +1323,7 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev) return -ENOMEM; sysfs_bin_attr_init(attr); - attr->size = dev->vpd->len; + attr->size = 0; attr->attr.name = "vpd"; attr->attr.mode = S_IRUSR | S_IWUSR; attr->read = read_vpd_attr; -- cgit v1.2.3-59-g8ed1b From fc0a407e9eb8667f93820298a06a85050c42354e Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 13:57:50 -0600 Subject: PCI: Move pci_read_vpd() and pci_write_vpd() close to other VPD code pci_read_vpd() and pci_write_vpd() were stranded in the middle of config accessor functions. Move them close to the other VPD code in the file. No functional change. Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 62 +++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 4850f0612268..4c4c73420fb2 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -174,38 +174,6 @@ struct pci_ops *pci_bus_set_ops(struct pci_bus *bus, struct pci_ops *ops) } EXPORT_SYMBOL(pci_bus_set_ops); -/** - * pci_read_vpd - Read one entry from Vital Product Data - * @dev: pci device struct - * @pos: offset in vpd space - * @count: number of bytes to read - * @buf: pointer to where to store result - * - */ -ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf) -{ - if (!dev->vpd || !dev->vpd->ops) - return -ENODEV; - return dev->vpd->ops->read(dev, pos, count, buf); -} -EXPORT_SYMBOL(pci_read_vpd); - -/** - * pci_write_vpd - Write entry to Vital Product Data - * @dev: pci device struct - * @pos: offset in vpd space - * @count: number of bytes to write - * @buf: buffer containing write data - * - */ -ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void *buf) -{ - if (!dev->vpd || !dev->vpd->ops) - return -ENODEV; - return dev->vpd->ops->write(dev, pos, count, buf); -} -EXPORT_SYMBOL(pci_write_vpd); - /* * The following routines are to prevent the user from accessing PCI config * space when it's unsafe to do so. Some devices require this during BIST and @@ -277,6 +245,36 @@ PCI_USER_WRITE_CONFIG(dword, u32) /* VPD access through PCI 2.2+ VPD capability */ +/** + * pci_read_vpd - Read one entry from Vital Product Data + * @dev: pci device struct + * @pos: offset in vpd space + * @count: number of bytes to read + * @buf: pointer to where to store result + */ +ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf) +{ + if (!dev->vpd || !dev->vpd->ops) + return -ENODEV; + return dev->vpd->ops->read(dev, pos, count, buf); +} +EXPORT_SYMBOL(pci_read_vpd); + +/** + * pci_write_vpd - Write entry to Vital Product Data + * @dev: pci device struct + * @pos: offset in vpd space + * @count: number of bytes to write + * @buf: buffer containing write data + */ +ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void *buf) +{ + if (!dev->vpd || !dev->vpd->ops) + return -ENODEV; + return dev->vpd->ops->write(dev, pos, count, buf); +} +EXPORT_SYMBOL(pci_write_vpd); + #define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) struct pci_vpd_pci22 { -- cgit v1.2.3-59-g8ed1b From 64379079a9e37ab9e1c755921d7ff1c4fa3ac486 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 13:58:06 -0600 Subject: PCI: Move pci_vpd_release() from header file to pci/access.c Move pci_vpd_release() so it's next to the other VPD functions. This puts it next to pci_vpd_pci22_init(), which allocates the space freed by pci_vpd_release(). Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 6 ++++++ drivers/pci/pci.h | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 4c4c73420fb2..ca42a33bbb00 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -581,6 +581,12 @@ int pci_vpd_pci22_init(struct pci_dev *dev) return 0; } +void pci_vpd_release(struct pci_dev *dev) +{ + if (dev->vpd) + dev->vpd->ops->release(dev); +} + /** * pci_cfg_access_lock - Lock PCI config reads/writes * @dev: pci device struct diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9a1660f592ef..52e86b0d56d2 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -107,11 +107,7 @@ struct pci_vpd { }; int pci_vpd_pci22_init(struct pci_dev *dev); -static inline void pci_vpd_release(struct pci_dev *dev) -{ - if (dev->vpd) - dev->vpd->ops->release(dev); -} +void pci_vpd_release(struct pci_dev *dev); /* PCI /proc functions */ #ifdef CONFIG_PROC_FS -- cgit v1.2.3-59-g8ed1b From da006847231b2b2ce443b29f1d3df6df7415030d Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 13:58:20 -0600 Subject: PCI: Remove struct pci_vpd_ops.release function pointer The struct pci_vpd_ops.release function pointer is always pci_vpd_pci22_release(), so there's no need for the flexibility of a function pointer. Inline the pci_vpd_pci22_release() body into pci_vpd_release() and remove pci_vpd_pci22_release() and the struct pci_vpd_ops.release function pointer. Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 9 +-------- drivers/pci/pci.h | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index ca42a33bbb00..68ed22a4b116 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -508,15 +508,9 @@ out: return ret ? ret : count; } -static void pci_vpd_pci22_release(struct pci_dev *dev) -{ - kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); -} - static const struct pci_vpd_ops pci_vpd_pci22_ops = { .read = pci_vpd_pci22_read, .write = pci_vpd_pci22_write, - .release = pci_vpd_pci22_release, }; static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count, @@ -552,7 +546,6 @@ static ssize_t pci_vpd_f0_write(struct pci_dev *dev, loff_t pos, size_t count, static const struct pci_vpd_ops pci_vpd_f0_ops = { .read = pci_vpd_f0_read, .write = pci_vpd_f0_write, - .release = pci_vpd_pci22_release, }; int pci_vpd_pci22_init(struct pci_dev *dev) @@ -584,7 +577,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev) void pci_vpd_release(struct pci_dev *dev) { if (dev->vpd) - dev->vpd->ops->release(dev); + kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); } /** diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 52e86b0d56d2..b3e9daac67d4 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -97,7 +97,6 @@ static inline bool pci_has_subordinate(struct pci_dev *pci_dev) struct pci_vpd_ops { ssize_t (*read)(struct pci_dev *dev, loff_t pos, size_t count, void *buf); ssize_t (*write)(struct pci_dev *dev, loff_t pos, size_t count, const void *buf); - void (*release)(struct pci_dev *dev); }; struct pci_vpd { -- cgit v1.2.3-59-g8ed1b From f1cd93f9aabe3b8dcac53de1c88279dbd8e529c3 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 13:58:37 -0600 Subject: PCI: Rename VPD symbols to remove unnecessary "pci22" There's only one kind of VPD, so we don't need to qualify it as "the version described by PCI spec rev 2.2." Rename the following symbols to remove unnecessary "pci22": PCI_VPD_PCI22_SIZE -> PCI_VPD_MAX_SIZE pci_vpd_pci22_size() -> pci_vpd_size() pci_vpd_pci22_wait() -> pci_vpd_wait() pci_vpd_pci22_read() -> pci_vpd_read() pci_vpd_pci22_write() -> pci_vpd_write() pci_vpd_pci22_ops -> pci_vpd_ops pci_vpd_pci22_init() -> pci_vpd_init() Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 38 +++++++++++++++++++------------------- drivers/pci/pci.h | 2 +- drivers/pci/probe.c | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 68ed22a4b116..ee205deb95b4 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -275,7 +275,7 @@ ssize_t pci_write_vpd(struct pci_dev *dev, loff_t pos, size_t count, const void } EXPORT_SYMBOL(pci_write_vpd); -#define PCI_VPD_PCI22_SIZE (PCI_VPD_ADDR_MASK + 1) +#define PCI_VPD_MAX_SIZE (PCI_VPD_ADDR_MASK + 1) struct pci_vpd_pci22 { struct pci_vpd base; @@ -291,7 +291,7 @@ struct pci_vpd_pci22 { * @dev: pci device struct * @old_size: current assumed size, also maximum allowed size */ -static size_t pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size) +static size_t pci_vpd_size(struct pci_dev *dev, size_t old_size) { size_t off = 0; unsigned char header[1+2]; /* 1 byte tag, 2 bytes length */ @@ -348,7 +348,7 @@ static size_t pci_vpd_pci22_size(struct pci_dev *dev, size_t old_size) * * Returns 0 on success, negative values indicate error. */ -static int pci_vpd_pci22_wait(struct pci_dev *dev) +static int pci_vpd_wait(struct pci_dev *dev) { struct pci_vpd_pci22 *vpd = container_of(dev->vpd, struct pci_vpd_pci22, base); @@ -381,8 +381,8 @@ static int pci_vpd_pci22_wait(struct pci_dev *dev) } } -static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, - void *arg) +static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count, + void *arg) { struct pci_vpd_pci22 *vpd = container_of(dev->vpd, struct pci_vpd_pci22, base); @@ -395,7 +395,7 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, if (!vpd->valid) { vpd->valid = 1; - vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len); + vpd->base.len = pci_vpd_size(dev, vpd->base.len); } if (vpd->base.len == 0) @@ -412,7 +412,7 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, if (mutex_lock_killable(&vpd->lock)) return -EINTR; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) goto out; @@ -426,7 +426,7 @@ static ssize_t pci_vpd_pci22_read(struct pci_dev *dev, loff_t pos, size_t count, break; vpd->busy = 1; vpd->flag = PCI_VPD_ADDR_F; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) break; @@ -449,8 +449,8 @@ out: return ret ? ret : count; } -static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count, - const void *arg) +static ssize_t pci_vpd_write(struct pci_dev *dev, loff_t pos, size_t count, + const void *arg) { struct pci_vpd_pci22 *vpd = container_of(dev->vpd, struct pci_vpd_pci22, base); @@ -463,7 +463,7 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count if (!vpd->valid) { vpd->valid = 1; - vpd->base.len = pci_vpd_pci22_size(dev, vpd->base.len); + vpd->base.len = pci_vpd_size(dev, vpd->base.len); } if (vpd->base.len == 0) @@ -475,7 +475,7 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count if (mutex_lock_killable(&vpd->lock)) return -EINTR; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) goto out; @@ -497,7 +497,7 @@ static ssize_t pci_vpd_pci22_write(struct pci_dev *dev, loff_t pos, size_t count vpd->busy = 1; vpd->flag = 0; - ret = pci_vpd_pci22_wait(dev); + ret = pci_vpd_wait(dev); if (ret < 0) break; @@ -508,9 +508,9 @@ out: return ret ? ret : count; } -static const struct pci_vpd_ops pci_vpd_pci22_ops = { - .read = pci_vpd_pci22_read, - .write = pci_vpd_pci22_write, +static const struct pci_vpd_ops pci_vpd_ops = { + .read = pci_vpd_read, + .write = pci_vpd_write, }; static ssize_t pci_vpd_f0_read(struct pci_dev *dev, loff_t pos, size_t count, @@ -548,7 +548,7 @@ static const struct pci_vpd_ops pci_vpd_f0_ops = { .write = pci_vpd_f0_write, }; -int pci_vpd_pci22_init(struct pci_dev *dev) +int pci_vpd_init(struct pci_dev *dev) { struct pci_vpd_pci22 *vpd; u8 cap; @@ -561,11 +561,11 @@ int pci_vpd_pci22_init(struct pci_dev *dev) if (!vpd) return -ENOMEM; - vpd->base.len = PCI_VPD_PCI22_SIZE; + vpd->base.len = PCI_VPD_MAX_SIZE; if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) vpd->base.ops = &pci_vpd_f0_ops; else - vpd->base.ops = &pci_vpd_pci22_ops; + vpd->base.ops = &pci_vpd_ops; mutex_init(&vpd->lock); vpd->cap = cap; vpd->busy = 0; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index b3e9daac67d4..61917038dbf4 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -105,7 +105,7 @@ struct pci_vpd { struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ }; -int pci_vpd_pci22_init(struct pci_dev *dev); +int pci_vpd_init(struct pci_dev *dev); void pci_vpd_release(struct pci_dev *dev); /* PCI /proc functions */ diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9bb0d5a..39b017447b70 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1608,7 +1608,7 @@ static void pci_init_capabilities(struct pci_dev *dev) pci_pm_init(dev); /* Vital Product Data */ - pci_vpd_pci22_init(dev); + pci_vpd_init(dev); /* Alternative Routing-ID Forwarding */ pci_configure_ari(dev); -- cgit v1.2.3-59-g8ed1b From 408641e93aa5283e586fefd4dc72e67c92aae075 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 14:09:52 -0600 Subject: PCI: Fold struct pci_vpd_pci22 into struct pci_vpd We only support one flavor of VPD, so there's no need to complicate things by having a "generic" struct pci_vpd and a more specific struct pci_vpd_pci22. Fold struct pci_vpd_pci22 directly into struct pci_vpd. [bhelgaas: remove NULL check before kfree of dev->vpd (per kfreeaddr.cocci)] Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 47 +++++++++++++++++------------------------------ drivers/pci/pci.h | 7 ++++++- 2 files changed, 23 insertions(+), 31 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index ee205deb95b4..8449d6b58178 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -277,15 +277,6 @@ EXPORT_SYMBOL(pci_write_vpd); #define PCI_VPD_MAX_SIZE (PCI_VPD_ADDR_MASK + 1) -struct pci_vpd_pci22 { - struct pci_vpd base; - struct mutex lock; - u16 flag; - u8 cap; - u8 busy:1; - u8 valid:1; -}; - /** * pci_vpd_size - determine actual size of Vital Product Data * @dev: pci device struct @@ -350,8 +341,7 @@ static size_t pci_vpd_size(struct pci_dev *dev, size_t old_size) */ static int pci_vpd_wait(struct pci_dev *dev) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); + struct pci_vpd *vpd = dev->vpd; unsigned long timeout = jiffies + HZ/20 + 2; u16 status; int ret; @@ -384,8 +374,7 @@ static int pci_vpd_wait(struct pci_dev *dev) static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count, void *arg) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); + struct pci_vpd *vpd = dev->vpd; int ret; loff_t end = pos + count; u8 *buf = arg; @@ -395,17 +384,17 @@ static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count, if (!vpd->valid) { vpd->valid = 1; - vpd->base.len = pci_vpd_size(dev, vpd->base.len); + vpd->len = pci_vpd_size(dev, vpd->len); } - if (vpd->base.len == 0) + if (vpd->len == 0) return -EIO; - if (pos >= vpd->base.len) + if (pos > vpd->len) return 0; - if (end > vpd->base.len) { - end = vpd->base.len; + if (end > vpd->len) { + end = vpd->len; count = end - pos; } @@ -452,8 +441,7 @@ out: static ssize_t pci_vpd_write(struct pci_dev *dev, loff_t pos, size_t count, const void *arg) { - struct pci_vpd_pci22 *vpd = - container_of(dev->vpd, struct pci_vpd_pci22, base); + struct pci_vpd *vpd = dev->vpd; const u8 *buf = arg; loff_t end = pos + count; int ret = 0; @@ -463,13 +451,13 @@ static ssize_t pci_vpd_write(struct pci_dev *dev, loff_t pos, size_t count, if (!vpd->valid) { vpd->valid = 1; - vpd->base.len = pci_vpd_size(dev, vpd->base.len); + vpd->len = pci_vpd_size(dev, vpd->len); } - if (vpd->base.len == 0) + if (vpd->len == 0) return -EIO; - if (end > vpd->base.len) + if (end > vpd->len) return -EINVAL; if (mutex_lock_killable(&vpd->lock)) @@ -550,7 +538,7 @@ static const struct pci_vpd_ops pci_vpd_f0_ops = { int pci_vpd_init(struct pci_dev *dev) { - struct pci_vpd_pci22 *vpd; + struct pci_vpd *vpd; u8 cap; cap = pci_find_capability(dev, PCI_CAP_ID_VPD); @@ -561,23 +549,22 @@ int pci_vpd_init(struct pci_dev *dev) if (!vpd) return -ENOMEM; - vpd->base.len = PCI_VPD_MAX_SIZE; + vpd->len = PCI_VPD_MAX_SIZE; if (dev->dev_flags & PCI_DEV_FLAGS_VPD_REF_F0) - vpd->base.ops = &pci_vpd_f0_ops; + vpd->ops = &pci_vpd_f0_ops; else - vpd->base.ops = &pci_vpd_ops; + vpd->ops = &pci_vpd_ops; mutex_init(&vpd->lock); vpd->cap = cap; vpd->busy = 0; vpd->valid = 0; - dev->vpd = &vpd->base; + dev->vpd = vpd; return 0; } void pci_vpd_release(struct pci_dev *dev) { - if (dev->vpd) - kfree(container_of(dev->vpd, struct pci_vpd_pci22, base)); + kfree(dev->vpd); } /** diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 61917038dbf4..d0fb93481573 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -100,9 +100,14 @@ struct pci_vpd_ops { }; struct pci_vpd { - unsigned int len; const struct pci_vpd_ops *ops; struct bin_attribute *attr; /* descriptor for sysfs VPD entry */ + struct mutex lock; + unsigned int len; + u16 flag; + u8 cap; + u8 busy:1; + u8 valid:1; }; int pci_vpd_init(struct pci_dev *dev); -- cgit v1.2.3-59-g8ed1b From fe537670eab767157eecc50538bd28e8d9b0ce9f Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 7 Mar 2016 11:39:16 -0600 Subject: PCI: Consolidate PCI DMA constants and interfaces in linux/pci-dma-compat.h Christoph added a generic include/linux/pci-dma-compat.h, so now there's one place with most of the PCI DMA interfaces. Move more PCI DMA-related things there: - The PCI_DMA_* direction constants from linux/pci.h - The pci_set_dma_max_seg_size() and pci_set_dma_seg_boundary() CONFIG_PCI implementations from drivers/pci/pci.c - The pci_set_dma_max_seg_size() and pci_set_dma_seg_boundary() !CONFIG_PCI stubs from linux/pci.h - The pci_set_dma_mask() and pci_set_consistent_dma_mask() !CONFIG_PCI stubs from linux/pci.h Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 12 ------------ include/linux/pci-dma-compat.h | 29 +++++++++++++++++++++++++++++ include/linux/pci.h | 19 +------------------ 3 files changed, 30 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 64c0a1215f84..0a9c8db51c08 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3385,18 +3385,6 @@ bool pci_check_and_unmask_intx(struct pci_dev *dev) } EXPORT_SYMBOL_GPL(pci_check_and_unmask_intx); -int pci_set_dma_max_seg_size(struct pci_dev *dev, unsigned int size) -{ - return dma_set_max_seg_size(&dev->dev, size); -} -EXPORT_SYMBOL(pci_set_dma_max_seg_size); - -int pci_set_dma_seg_boundary(struct pci_dev *dev, unsigned long mask) -{ - return dma_set_seg_boundary(&dev->dev, mask); -} -EXPORT_SYMBOL(pci_set_dma_seg_boundary); - /** * pci_wait_for_pending_transaction - waits for pending transaction * @dev: the PCI device to operate on diff --git a/include/linux/pci-dma-compat.h b/include/linux/pci-dma-compat.h index eafce7b6f052..39726caef5b1 100644 --- a/include/linux/pci-dma-compat.h +++ b/include/linux/pci-dma-compat.h @@ -6,6 +6,12 @@ #include +/* This defines the direction arg to the DMA mapping routines. */ +#define PCI_DMA_BIDIRECTIONAL 0 +#define PCI_DMA_TODEVICE 1 +#define PCI_DMA_FROMDEVICE 2 +#define PCI_DMA_NONE 3 + static inline void * pci_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle) @@ -113,6 +119,29 @@ static inline int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask) { return dma_set_coherent_mask(&dev->dev, mask); } + +static inline int pci_set_dma_max_seg_size(struct pci_dev *dev, + unsigned int size) +{ + return dma_set_max_seg_size(&dev->dev, size); +} + +static inline int pci_set_dma_seg_boundary(struct pci_dev *dev, + unsigned long mask) +{ + return dma_set_seg_boundary(&dev->dev, mask); +} +#else +static inline int pci_set_dma_mask(struct pci_dev *dev, u64 mask) +{ return -EIO; } +static inline int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask) +{ return -EIO; } +static inline int pci_set_dma_max_seg_size(struct pci_dev *dev, + unsigned int size) +{ return -EIO; } +static inline int pci_set_dma_seg_boundary(struct pci_dev *dev, + unsigned long mask) +{ return -EIO; } #endif #endif diff --git a/include/linux/pci.h b/include/linux/pci.h index 5db6e0ce9504..5049bd60ba90 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -70,12 +70,6 @@ enum pci_mmap_state { pci_mmap_mem }; -/* This defines the direction arg to the DMA mapping routines. */ -#define PCI_DMA_BIDIRECTIONAL 0 -#define PCI_DMA_TODEVICE 1 -#define PCI_DMA_FROMDEVICE 2 -#define PCI_DMA_NONE 3 - /* * For PCI devices, the region numbers are assigned this way: */ @@ -1038,8 +1032,6 @@ void pci_intx(struct pci_dev *dev, int enable); bool pci_intx_mask_supported(struct pci_dev *dev); bool pci_check_and_mask_intx(struct pci_dev *dev); bool pci_check_and_unmask_intx(struct pci_dev *dev); -int pci_set_dma_max_seg_size(struct pci_dev *dev, unsigned int size); -int pci_set_dma_seg_boundary(struct pci_dev *dev, unsigned long mask); int pci_wait_for_pending(struct pci_dev *dev, int pos, u16 mask); int pci_wait_for_pending_transaction(struct pci_dev *dev); int pcix_get_max_mmrbc(struct pci_dev *dev); @@ -1255,6 +1247,7 @@ resource_size_t pcibios_iov_resource_alignment(struct pci_dev *dev, int resno); int pci_set_vga_state(struct pci_dev *pdev, bool decode, unsigned int command_bits, u32 flags); + /* kmem_cache style wrapper around pci_alloc_consistent() */ #include @@ -1466,16 +1459,6 @@ static inline struct pci_dev *pci_get_class(unsigned int class, static inline void pci_set_master(struct pci_dev *dev) { } static inline int pci_enable_device(struct pci_dev *dev) { return -EIO; } static inline void pci_disable_device(struct pci_dev *dev) { } -static inline int pci_set_dma_mask(struct pci_dev *dev, u64 mask) -{ return -EIO; } -static inline int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask) -{ return -EIO; } -static inline int pci_set_dma_max_seg_size(struct pci_dev *dev, - unsigned int size) -{ return -EIO; } -static inline int pci_set_dma_seg_boundary(struct pci_dev *dev, - unsigned long mask) -{ return -EIO; } static inline int pci_assign_resource(struct pci_dev *dev, int i) { return -EBUSY; } static inline int __pci_register_driver(struct pci_driver *drv, -- cgit v1.2.3-59-g8ed1b From 2ea4adf75235c14c5a52a90eb3c282383f79a86b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 1 Mar 2016 10:58:04 -0600 Subject: PCI: Don't assign or reassign immutable resources IORESOURCE_PCI_FIXED means the resource can't be moved, so if it's set, don't bother trying to assign or reassign the resource. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-res.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 604011e047d6..66c4d8f42233 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -276,6 +276,9 @@ int pci_assign_resource(struct pci_dev *dev, int resno) resource_size_t align, size; int ret; + if (res->flags & IORESOURCE_PCI_FIXED) + return 0; + res->flags |= IORESOURCE_UNSET; align = pci_resource_alignment(dev, res); if (!align) { @@ -321,6 +324,9 @@ int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsiz resource_size_t new_size; int ret; + if (res->flags & IORESOURCE_PCI_FIXED) + return 0; + flags = res->flags; res->flags |= IORESOURCE_UNSET; if (!res->parent) { -- cgit v1.2.3-59-g8ed1b From 4708f9a55b31062dd19b12788ecf614aa4e79bea Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 3 Mar 2016 08:53:47 -0600 Subject: PCI: Don't enable/disable ROM BAR if we're using a RAM shadow copy If we're using a RAM shadow copy instead of the ROM BAR, we don't need to touch the ROM BAR enable bit. Signed-off-by: Bjorn Helgaas --- drivers/pci/rom.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index 9eaca39ef38d..5da8d061d600 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -24,13 +24,17 @@ */ int pci_enable_rom(struct pci_dev *pdev) { - struct resource *res = pdev->resource + PCI_ROM_RESOURCE; + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; struct pci_bus_region region; u32 rom_addr; if (!res->flags) return -1; + /* Nothing to enable if we're using a shadow copy in RAM */ + if (res->flags & IORESOURCE_ROM_SHADOW) + return 0; + pcibios_resource_to_bus(pdev->bus, ®ion, res); pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr); rom_addr &= ~PCI_ROM_ADDRESS_MASK; @@ -49,7 +53,12 @@ EXPORT_SYMBOL_GPL(pci_enable_rom); */ void pci_disable_rom(struct pci_dev *pdev) { + struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; u32 rom_addr; + + if (res->flags & IORESOURCE_ROM_SHADOW) + return; + pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr); rom_addr &= ~PCI_ROM_ADDRESS_ENABLE; pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr); @@ -154,7 +163,6 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) if (!rom) { /* restore enable if ioremap fails */ if (!(res->flags & (IORESOURCE_ROM_ENABLE | - IORESOURCE_ROM_SHADOW | IORESOURCE_ROM_COPY))) pci_disable_rom(pdev); return NULL; @@ -186,8 +194,8 @@ void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom) iounmap(rom); - /* Disable again before continuing, leave enabled if pci=rom */ - if (!(res->flags & (IORESOURCE_ROM_ENABLE | IORESOURCE_ROM_SHADOW))) + /* Disable again before continuing */ + if (!(res->flags & IORESOURCE_ROM_ENABLE)) pci_disable_rom(pdev); } EXPORT_SYMBOL(pci_unmap_rom); -- cgit v1.2.3-59-g8ed1b From 0c0e0736acad4e76e718456c75d78ad95eea0011 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 1 Mar 2016 11:38:46 -0600 Subject: PCI: Set ROM shadow location in arch code, not in PCI core IORESOURCE_ROM_SHADOW means there is a copy of a device's option ROM in RAM. The existence of such a copy and its location are arch-specific. Previously the IORESOURCE_ROM_SHADOW flag was set in arch code, but the 0xC0000-0xDFFFF location was hard-coded into the PCI core. If we're using a shadow copy in RAM, disable the ROM BAR and release the address space it was consuming. Move the location information from the PCI core to the arch code that sets IORESOURCE_ROM_SHADOW. Save the location of the RAM copy in the struct resource for PCI_ROM_RESOURCE. After this change, pci_map_rom() will call pci_assign_resource() and pci_enable_rom() for these IORESOURCE_ROM_SHADOW resources, which we did not do before. This is safe because: - pci_assign_resource() will do nothing because the resource is marked IORESOURCE_PCI_FIXED, which means we can't move it, and - pci_enable_rom() will not turn on the ROM BAR's enable bit because the resource is marked IORESOURCE_ROM_SHADOW, which means it is in RAM rather than in PCI memory space. Storing the location in the struct resource means "lspci" will show the shadow location, not the value from the ROM BAR. Signed-off-by: Bjorn Helgaas --- arch/ia64/pci/fixup.c | 22 ++++++++++++++++------ arch/x86/pci/fixup.c | 22 ++++++++++++++++------ drivers/pci/rom.c | 11 ----------- include/linux/ioport.h | 2 +- 4 files changed, 33 insertions(+), 24 deletions(-) (limited to 'drivers') diff --git a/arch/ia64/pci/fixup.c b/arch/ia64/pci/fixup.c index fd9ceff2b5a5..41caa99add51 100644 --- a/arch/ia64/pci/fixup.c +++ b/arch/ia64/pci/fixup.c @@ -17,14 +17,14 @@ * * The standard boot ROM sequence for an x86 machine uses the BIOS * to select an initial video card for boot display. This boot video - * card will have it's BIOS copied to C0000 in system RAM. + * card will have its BIOS copied to 0xC0000 in system RAM. * IORESOURCE_ROM_SHADOW is used to associate the boot video * card with this copy. On laptops this copy has to be used since * the main ROM may be compressed or combined with another image. * See pci_map_rom() for use of this flag. Before marking the device * with IORESOURCE_ROM_SHADOW check if a vga_default_device is already set - * by either arch cde or vga-arbitration, if so only apply the fixup to this - * already determined primary video card. + * by either arch code or vga-arbitration; if so only apply the fixup to this + * already-determined primary video card. */ static void pci_fixup_video(struct pci_dev *pdev) @@ -32,6 +32,7 @@ static void pci_fixup_video(struct pci_dev *pdev) struct pci_dev *bridge; struct pci_bus *bus; u16 config; + struct resource *res; if ((strcmp(ia64_platform_name, "dig") != 0) && (strcmp(ia64_platform_name, "hpzx1") != 0)) @@ -61,9 +62,18 @@ static void pci_fixup_video(struct pci_dev *pdev) if (!vga_default_device() || pdev == vga_default_device()) { pci_read_config_word(pdev, PCI_COMMAND, &config); if (config & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) { - pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW | - IORESOURCE_PCI_FIXED; - dev_printk(KERN_DEBUG, &pdev->dev, "Video device with shadowed ROM\n"); + res = &pdev->resource[PCI_ROM_RESOURCE]; + + pci_disable_rom(pdev); + if (res->parent) + release_resource(res); + + res->start = 0xC0000; + res->end = res->start + 0x20000 - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_ROM_SHADOW | + IORESOURCE_PCI_FIXED; + dev_info(&pdev->dev, "Video device with shadowed ROM at %pR\n", + res); } } } diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index dac027c1adb1..b7de1929714b 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -297,14 +297,14 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_MCH_PC1, pcie_r * * The standard boot ROM sequence for an x86 machine uses the BIOS * to select an initial video card for boot display. This boot video - * card will have it's BIOS copied to C0000 in system RAM. + * card will have its BIOS copied to 0xC0000 in system RAM. * IORESOURCE_ROM_SHADOW is used to associate the boot video * card with this copy. On laptops this copy has to be used since * the main ROM may be compressed or combined with another image. * See pci_map_rom() for use of this flag. Before marking the device * with IORESOURCE_ROM_SHADOW check if a vga_default_device is already set - * by either arch cde or vga-arbitration, if so only apply the fixup to this - * already determined primary video card. + * by either arch code or vga-arbitration; if so only apply the fixup to this + * already-determined primary video card. */ static void pci_fixup_video(struct pci_dev *pdev) @@ -312,6 +312,7 @@ static void pci_fixup_video(struct pci_dev *pdev) struct pci_dev *bridge; struct pci_bus *bus; u16 config; + struct resource *res; /* Is VGA routed to us? */ bus = pdev->bus; @@ -336,9 +337,18 @@ static void pci_fixup_video(struct pci_dev *pdev) if (!vga_default_device() || pdev == vga_default_device()) { pci_read_config_word(pdev, PCI_COMMAND, &config); if (config & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) { - pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW | - IORESOURCE_PCI_FIXED; - dev_printk(KERN_DEBUG, &pdev->dev, "Video device with shadowed ROM\n"); + res = &pdev->resource[PCI_ROM_RESOURCE]; + + pci_disable_rom(pdev); + if (res->parent) + release_resource(res); + + res->start = 0xC0000; + res->end = res->start + 0x20000 - 1; + res->flags = IORESOURCE_MEM | IORESOURCE_ROM_SHADOW | + IORESOURCE_PCI_FIXED; + dev_info(&pdev->dev, "Video device with shadowed ROM at %pR\n", + res); } } } diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index 5da8d061d600..80e82b13292f 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -128,16 +128,6 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) loff_t start; void __iomem *rom; - /* - * IORESOURCE_ROM_SHADOW set on x86, x86_64 and IA64 supports legacy - * memory map if the VGA enable bit of the Bridge Control register is - * set for embedded VGA. - */ - if (res->flags & IORESOURCE_ROM_SHADOW) { - /* primary video rom always starts here */ - start = (loff_t)0xC0000; - *size = 0x20000; /* cover C000:0 through E000:0 */ - } else { if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) { *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); @@ -157,7 +147,6 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) if (pci_enable_rom(pdev)) return NULL; } - } rom = ioremap(start, *size); if (!rom) { diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 24bea087e7af..2cf16673f17a 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -98,7 +98,7 @@ struct resource { /* PCI ROM control bits (IORESOURCE_BITS) */ #define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */ -#define IORESOURCE_ROM_SHADOW (1<<1) /* ROM is copy at C000:0 */ +#define IORESOURCE_ROM_SHADOW (1<<1) /* Use RAM image, not ROM BAR */ #define IORESOURCE_ROM_COPY (1<<2) /* ROM is alloc'd copy, resource field overlaid */ #define IORESOURCE_ROM_BIOS_COPY (1<<3) /* ROM is BIOS copy, resource field overlaid */ -- cgit v1.2.3-59-g8ed1b From 0259882e342e5626d204a5c87d0c3fbbad4d7736 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 11 Feb 2016 21:58:07 +0530 Subject: PCI: xilinx: Use of_pci_get_host_bridge_resources() to parse DT Use the new of_pci_get_host_bridge_resources() API in place of the PCI OF DT parser. [bhelgaas: revise changelog to show similarity to 0021d22b73d6 ("PCI: designware: Use of_pci_get_host_bridge_resources() to parse DT")] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Ravi Kiran Gummaluri Signed-off-by: Bjorn Helgaas Reviewed-by: Arnd Bergmann Acked-by: Michal Simek --- drivers/pci/host/pcie-xilinx.c | 107 ++--------------------------------------- 1 file changed, 5 insertions(+), 102 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index 4cfa46360d12..477a6c338d52 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -94,9 +94,6 @@ /* Number of MSI IRQs */ #define XILINX_NUM_MSI_IRQS 128 -/* Number of Memory Resources */ -#define XILINX_MAX_NUM_RESOURCES 3 - /** * struct xilinx_pcie_port - PCIe port information * @reg_base: IO Mapped Register Base @@ -105,7 +102,6 @@ * @root_busno: Root Bus number * @dev: Device pointer * @irq_domain: IRQ domain pointer - * @bus_range: Bus range * @resources: Bus Resources */ struct xilinx_pcie_port { @@ -658,97 +654,6 @@ static struct pci_bus *xilinx_pcie_scan_bus(int nr, struct pci_sys_data *sys) return bus; } -/** - * xilinx_pcie_parse_and_add_res - Add resources by parsing ranges - * @port: PCIe port information - * - * Return: '0' on success and error value on failure - */ -static int xilinx_pcie_parse_and_add_res(struct xilinx_pcie_port *port) -{ - struct device *dev = port->dev; - struct device_node *node = dev->of_node; - struct resource *mem; - resource_size_t offset; - struct of_pci_range_parser parser; - struct of_pci_range range; - struct resource_entry *win; - int err = 0, mem_resno = 0; - - /* Get the ranges */ - if (of_pci_range_parser_init(&parser, node)) { - dev_err(dev, "missing \"ranges\" property\n"); - return -EINVAL; - } - - /* Parse the ranges and add the resources found to the list */ - for_each_of_pci_range(&parser, &range) { - - if (mem_resno >= XILINX_MAX_NUM_RESOURCES) { - dev_err(dev, "Maximum memory resources exceeded\n"); - return -EINVAL; - } - - mem = devm_kmalloc(dev, sizeof(*mem), GFP_KERNEL); - if (!mem) { - err = -ENOMEM; - goto free_resources; - } - - of_pci_range_to_resource(&range, node, mem); - - switch (mem->flags & IORESOURCE_TYPE_BITS) { - case IORESOURCE_MEM: - offset = range.cpu_addr - range.pci_addr; - mem_resno++; - break; - default: - err = -EINVAL; - break; - } - - if (err < 0) { - dev_warn(dev, "Invalid resource found %pR\n", mem); - continue; - } - - err = request_resource(&iomem_resource, mem); - if (err) - goto free_resources; - - pci_add_resource_offset(&port->resources, mem, offset); - } - - /* Get the bus range */ - if (of_pci_parse_bus_range(node, &port->bus_range)) { - u32 val = pcie_read(port, XILINX_PCIE_REG_BIR); - u8 last; - - last = (val & XILINX_PCIE_BIR_ECAM_SZ_MASK) >> - XILINX_PCIE_BIR_ECAM_SZ_SHIFT; - - port->bus_range = (struct resource) { - .name = node->name, - .start = 0, - .end = last, - .flags = IORESOURCE_BUS, - }; - } - - /* Register bus resource */ - pci_add_resource(&port->resources, &port->bus_range); - - return 0; - -free_resources: - release_child_resources(&iomem_resource); - resource_list_for_each_entry(win, &port->resources) - devm_kfree(dev, win->res); - pci_free_resource_list(&port->resources); - - return err; -} - /** * xilinx_pcie_parse_dt - Parse Device tree * @port: PCIe port information @@ -803,6 +708,8 @@ static int xilinx_pcie_probe(struct platform_device *pdev) struct hw_pci hw; struct device *dev = &pdev->dev; int err; + resource_size_t iobase = 0; + LIST_HEAD(res); if (!dev->of_node) return -ENODEV; @@ -827,14 +734,10 @@ static int xilinx_pcie_probe(struct platform_device *pdev) return err; } - /* - * Parse PCI ranges, configuration bus range and - * request their resources - */ - INIT_LIST_HEAD(&port->resources); - err = xilinx_pcie_parse_and_add_res(port); + err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, &res, + &iobase); if (err) { - dev_err(dev, "Failed adding resources\n"); + dev_err(dev, "Getting bridge resources failed\n"); return err; } -- cgit v1.2.3-59-g8ed1b From 4c01f3b089a0bb843aadfb4d7617180b8b88b880 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 11 Feb 2016 21:58:08 +0530 Subject: PCI: xilinx: Remove dependency on ARM-specific struct hw_pci The Xilinx PCIe host controller driver uses pci_common_init_dev(), which is ARM-specific and requires the ARM struct hw_pci. The part of pci_common_init_dev() that is needed is limited and can be done here without using hw_pci. Create and scan the root bus directly without using the ARM pci_common_init_dev() interface. [bhelgaas: revise changelog to show similarity to 79953dd22c1d ("PCI: rcar: Remove dependency on ARM-specific struct hw_pci")] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Ravi Kiran Gummaluri Signed-off-by: Bjorn Helgaas Reviewed-by: Arnd Bergmann Acked-by: Michal Simek --- drivers/pci/host/pcie-xilinx.c | 82 +++++++++--------------------------------- 1 file changed, 16 insertions(+), 66 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index 477a6c338d52..d21763d1a637 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -111,17 +111,11 @@ struct xilinx_pcie_port { u8 root_busno; struct device *dev; struct irq_domain *irq_domain; - struct resource bus_range; struct list_head resources; }; static DECLARE_BITMAP(msi_irq_in_use, XILINX_NUM_MSI_IRQS); -static inline struct xilinx_pcie_port *sys_to_pcie(struct pci_sys_data *sys) -{ - return sys->private_data; -} - static inline u32 pcie_read(struct xilinx_pcie_port *port, u32 reg) { return readl(port->reg_base + reg); @@ -163,7 +157,7 @@ static void xilinx_pcie_clear_err_interrupts(struct xilinx_pcie_port *port) */ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) { - struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata); + struct xilinx_pcie_port *port = bus->sysdata; /* Check if link is up when trying to access downstream ports */ if (bus->number != port->root_busno) @@ -196,7 +190,7 @@ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) static void __iomem *xilinx_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, int where) { - struct xilinx_pcie_port *port = sys_to_pcie(bus->sysdata); + struct xilinx_pcie_port *port = bus->sysdata; int relbus; if (!xilinx_pcie_valid_device(bus, devfn)) @@ -228,7 +222,7 @@ static void xilinx_pcie_destroy_msi(unsigned int irq) if (!test_bit(irq, msi_irq_in_use)) { msi = irq_get_msi_desc(irq); - port = sys_to_pcie(msi_desc_to_pci_sysdata(msi)); + 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); @@ -277,7 +271,7 @@ static int xilinx_pcie_msi_setup_irq(struct msi_controller *chip, struct pci_dev *pdev, struct msi_desc *desc) { - struct xilinx_pcie_port *port = sys_to_pcie(pdev->bus->sysdata); + struct xilinx_pcie_port *port = pdev->bus->sysdata; unsigned int irq; int hwirq; struct msi_msg msg; @@ -613,47 +607,6 @@ static void xilinx_pcie_init_port(struct xilinx_pcie_port *port) XILINX_PCIE_REG_RPSC); } -/** - * xilinx_pcie_setup - Setup memory resources - * @nr: Bus number - * @sys: Per controller structure - * - * Return: '1' on success and error value on failure - */ -static int xilinx_pcie_setup(int nr, struct pci_sys_data *sys) -{ - struct xilinx_pcie_port *port = sys_to_pcie(sys); - - list_splice_init(&port->resources, &sys->resources); - - return 1; -} - -/** - * xilinx_pcie_scan_bus - Scan PCIe bus for devices - * @nr: Bus number - * @sys: Per controller structure - * - * Return: Valid Bus pointer on success and NULL on failure - */ -static struct pci_bus *xilinx_pcie_scan_bus(int nr, struct pci_sys_data *sys) -{ - struct xilinx_pcie_port *port = sys_to_pcie(sys); - struct pci_bus *bus; - - port->root_busno = sys->busnr; - - if (IS_ENABLED(CONFIG_PCI_MSI)) - bus = pci_scan_root_bus_msi(port->dev, sys->busnr, - &xilinx_pcie_ops, sys, - &sys->resources, - &xilinx_pcie_msi_chip); - else - bus = pci_scan_root_bus(port->dev, sys->busnr, - &xilinx_pcie_ops, sys, &sys->resources); - return bus; -} - /** * xilinx_pcie_parse_dt - Parse Device tree * @port: PCIe port information @@ -705,8 +658,9 @@ static int xilinx_pcie_parse_dt(struct xilinx_pcie_port *port) static int xilinx_pcie_probe(struct platform_device *pdev) { struct xilinx_pcie_port *port; - struct hw_pci hw; struct device *dev = &pdev->dev; + struct pci_bus *bus; + int err; resource_size_t iobase = 0; LIST_HEAD(res); @@ -740,24 +694,20 @@ static int xilinx_pcie_probe(struct platform_device *pdev) dev_err(dev, "Getting bridge resources failed\n"); return err; } - - platform_set_drvdata(pdev, port); - - /* Register the device */ - memset(&hw, 0, sizeof(hw)); - hw = (struct hw_pci) { - .nr_controllers = 1, - .private_data = (void **)&port, - .setup = xilinx_pcie_setup, - .map_irq = of_irq_parse_and_map_pci, - .scan = xilinx_pcie_scan_bus, - .ops = &xilinx_pcie_ops, - }; + bus = pci_create_root_bus(&pdev->dev, 0, + &xilinx_pcie_ops, port, &res); + if (!bus) + return -ENOMEM; #ifdef CONFIG_PCI_MSI xilinx_pcie_msi_chip.dev = port->dev; + bus->msi = &xilinx_pcie_msi_chip; #endif - pci_common_init_dev(dev, &hw); + pci_scan_child_bus(bus); + pci_assign_unassigned_bus_resources(bus); + pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); + pci_bus_add_devices(bus); + platform_set_drvdata(pdev, port); return 0; } -- cgit v1.2.3-59-g8ed1b From 2c51391d2559a825a10e0abfbfb0e247d9c344b3 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 11 Feb 2016 21:58:09 +0530 Subject: PCI: xilinx: Don't call pci_fixup_irqs() on Microblaze The Xilinx AXI PCIe Host Bridge Soft IP driver was previously only supported on ARM (in particular, on ARCH_ZYNC), and pci_fixup_irqs() is available there. But Microblaze will do IRQ fixup in pcibios_add_device(), so pci_fixup_irqs() is not available on Microblaze. Don't call pci_fixup_irqs() on Microblaze, so the driver can work on both Zynq and Microblaze Architectures. [bhelgaas: revise changelog to show similarity to bdb8a1844f31 ("PCI: iproc: Call pci_fixup_irqs() for ARM64 as well as ARM")] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Ravi Kiran Gummaluri Signed-off-by: Bjorn Helgaas Reviewed-by: Arnd Bergmann Acked-by: Michal Simek --- drivers/pci/host/pcie-xilinx.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c index d21763d1a637..65f0fe0c2eaf 100644 --- a/drivers/pci/host/pcie-xilinx.c +++ b/drivers/pci/host/pcie-xilinx.c @@ -705,7 +705,9 @@ static int xilinx_pcie_probe(struct platform_device *pdev) #endif pci_scan_child_bus(bus); pci_assign_unassigned_bus_resources(bus); +#ifndef CONFIG_MICROBLAZE pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); +#endif pci_bus_add_devices(bus); platform_set_drvdata(pdev, port); -- cgit v1.2.3-59-g8ed1b From 01cf9d524ff06703dbed37014f34dba9c1b2434d Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Thu, 11 Feb 2016 21:58:11 +0530 Subject: microblaze/PCI: Support generic Xilinx AXI PCIe Host Bridge IP driver Modify the Microblaze PCI subsystem to work with the generic drivers/pci/host/pcie-xilinx.c driver on Microblaze and Zynq. [bhelgaas: changelog] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Ravi Kiran Gummaluri Signed-off-by: Bjorn Helgaas Acked-by: Michal Simek --- arch/microblaze/Kconfig | 3 +++ arch/microblaze/pci/pci-common.c | 56 +++++++--------------------------------- drivers/pci/host/Kconfig | 2 +- 3 files changed, 14 insertions(+), 47 deletions(-) (limited to 'drivers') diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index 53b69deceb99..3d793b55f60c 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -267,6 +267,9 @@ config PCI config PCI_DOMAINS def_bool PCI +config PCI_DOMAINS_GENERIC + def_bool PCI_DOMAINS + config PCI_SYSCALL def_bool PCI diff --git a/arch/microblaze/pci/pci-common.c b/arch/microblaze/pci/pci-common.c index ae838ed5fcf2..35654be3f1c0 100644 --- a/arch/microblaze/pci/pci-common.c +++ b/arch/microblaze/pci/pci-common.c @@ -123,17 +123,6 @@ unsigned long pci_address_to_pio(phys_addr_t address) } EXPORT_SYMBOL_GPL(pci_address_to_pio); -/* - * Return the domain number for this bus. - */ -int pci_domain_nr(struct pci_bus *bus) -{ - struct pci_controller *hose = pci_bus_to_host(bus); - - return hose->global_number; -} -EXPORT_SYMBOL(pci_domain_nr); - /* This routine is meant to be used early during boot, when the * PCI bus numbers have not yet been assigned, and you need to * issue PCI config cycles to an OF device. @@ -863,26 +852,10 @@ void pcibios_setup_bus_devices(struct pci_bus *bus) void pcibios_fixup_bus(struct pci_bus *bus) { - /* When called from the generic PCI probe, read PCI<->PCI bridge - * bases. This is -not- called when generating the PCI tree from - * the OF device-tree. - */ - if (bus->self != NULL) - pci_read_bridge_bases(bus); - - /* Now fixup the bus bus */ - pcibios_setup_bus_self(bus); - - /* Now fixup devices on that bus */ - pcibios_setup_bus_devices(bus); + /* nothing to do */ } EXPORT_SYMBOL(pcibios_fixup_bus); -static int skip_isa_ioresource_align(struct pci_dev *dev) -{ - return 0; -} - /* * We need to avoid collisions with `mirrored' VGA ports * and other strange ISA hardware, so we always want the @@ -899,20 +872,18 @@ static int skip_isa_ioresource_align(struct pci_dev *dev) resource_size_t pcibios_align_resource(void *data, const struct resource *res, resource_size_t size, resource_size_t align) { - struct pci_dev *dev = data; - resource_size_t start = res->start; - - if (res->flags & IORESOURCE_IO) { - if (skip_isa_ioresource_align(dev)) - return start; - if (start & 0x300) - start = (start + 0x3ff) & ~0x3ff; - } - - return start; + return res->start; } EXPORT_SYMBOL(pcibios_align_resource); +int pcibios_add_device(struct pci_dev *dev) +{ + dev->irq = of_irq_parse_and_map_pci(dev, 0, 0); + + return 0; +} +EXPORT_SYMBOL(pcibios_add_device); + /* * Reparent resource children of pr that conflict with res * under res, and make res replace those children. @@ -1333,13 +1304,6 @@ static void pcibios_setup_phb_resources(struct pci_controller *hose, (unsigned long)hose->io_base_virt - _IO_BASE); } -struct device_node *pcibios_get_phb_of_node(struct pci_bus *bus) -{ - struct pci_controller *hose = bus->sysdata; - - return of_node_get(hose->dn); -} - static void pcibios_scan_phb(struct pci_controller *hose) { LIST_HEAD(resources); diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a605426538..ee8eadd62a41 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -81,7 +81,7 @@ config PCI_KEYSTONE config PCIE_XILINX bool "Xilinx AXI PCIe host bridge support" - depends on ARCH_ZYNQ + depends on ARCH_ZYNQ || MICROBLAZE help Say 'Y' here if you want kernel to support the Xilinx AXI PCIe Host Bridge driver. -- cgit v1.2.3-59-g8ed1b From 5f8fc43217a01c98be88aa53794b4d1333a07a5a Mon Sep 17 00:00:00 2001 From: Bogicevic Sasa Date: Wed, 3 Feb 2016 13:24:22 -0800 Subject: PCI: Include pci/pcie/Kconfig directly from pci/Kconfig Include pci/pcie/Kconfig directly from pci/Kconfig, so arches don't have to source both pci/Kconfig and pci/pcie/Kconfig. Note that this effectively adds pci/pcie/Kconfig to the following arches, because they already sourced drivers/pci/Kconfig but they previously did not source drivers/pci/pcie/Kconfig: alpha avr32 blackfin frv m32r m68k microblaze mn10300 parisc sparc unicore32 xtensa [bhelgaas: changelog, source pci/pcie/Kconfig at top of pci/Kconfig, whitespace] Signed-off-by: Sasa Bogicevic Signed-off-by: Bjorn Helgaas --- arch/arm/Kconfig | 1 - arch/arm64/Kconfig | 1 - arch/ia64/Kconfig | 2 -- arch/mips/Kconfig | 2 -- arch/powerpc/Kconfig | 2 -- arch/s390/Kconfig | 1 - arch/sh/Kconfig | 2 -- arch/tile/Kconfig | 1 - arch/x86/Kconfig | 2 -- drivers/pci/Kconfig | 3 +++ 10 files changed, 3 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 4f799e567fc8..1d00da16d980 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1212,7 +1212,6 @@ config PCI_HOST_ITE8152 select DMABOUNCE source "drivers/pci/Kconfig" -source "drivers/pci/pcie/Kconfig" source "drivers/pcmcia/Kconfig" diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 8cc62289a63e..128ec3c8d475 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -235,7 +235,6 @@ config PCI_SYSCALL def_bool PCI source "drivers/pci/Kconfig" -source "drivers/pci/pcie/Kconfig" source "drivers/pci/hotplug/Kconfig" endmenu diff --git a/arch/ia64/Kconfig b/arch/ia64/Kconfig index fb0515eb639b..1bc4b5add65c 100644 --- a/arch/ia64/Kconfig +++ b/arch/ia64/Kconfig @@ -574,8 +574,6 @@ config PCI_DOMAINS config PCI_SYSCALL def_bool PCI -source "drivers/pci/pcie/Kconfig" - source "drivers/pci/Kconfig" source "drivers/pci/hotplug/Kconfig" diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 57a945e832f4..d21eec746afb 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -2876,8 +2876,6 @@ config PCI_DOMAINS source "drivers/pci/Kconfig" -source "drivers/pci/pcie/Kconfig" - # # ISA support is now enabled via select. Too many systems still have the one # or other ISA chip on the board that users don't know about so don't expect diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index e4824fd04bb7..247a09f01608 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -828,8 +828,6 @@ config PCI_8260 select PPC_INDIRECT_PCI default y -source "drivers/pci/pcie/Kconfig" - source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 3be9c832dec1..991843342005 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -605,7 +605,6 @@ config PCI_NR_MSI PCI devices. source "drivers/pci/Kconfig" -source "drivers/pci/pcie/Kconfig" source "drivers/pci/hotplug/Kconfig" endif # PCI diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index e13da05505dc..ce52af9ca6ba 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -847,8 +847,6 @@ config PCI config PCI_DOMAINS bool -source "drivers/pci/pcie/Kconfig" - source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" diff --git a/arch/tile/Kconfig b/arch/tile/Kconfig index de4a4fff9323..0c95266d102b 100644 --- a/arch/tile/Kconfig +++ b/arch/tile/Kconfig @@ -455,7 +455,6 @@ config TILE_PCI_IO source "drivers/pci/Kconfig" -source "drivers/pci/pcie/Kconfig" config TILE_USB tristate "Tilera USB host adapter support" diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 330e738ccfc1..ff7fe2dc881c 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2431,8 +2431,6 @@ config PCI_CNB20LE_QUIRK You should say N unless you know you need this. -source "drivers/pci/pcie/Kconfig" - source "drivers/pci/Kconfig" # x86_64 have no ISA slots, but can have ISA-style DMA. diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 73de4efcbe6e..04e00923d178 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -1,6 +1,9 @@ # # PCI configuration # + +source "drivers/pci/pcie/Kconfig" + config PCI_BUS_ADDR_T_64BIT def_bool y if (ARCH_DMA_ADDR_T_64BIT || 64BIT) depends on PCI -- cgit v1.2.3-59-g8ed1b From e7e127e3c767094aca96ee976729dc5f756666df Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 8 Mar 2016 14:57:21 -0600 Subject: PCI: Include pci/hotplug Kconfig directly from pci/Kconfig Include pci/hotplug/Kconfig directly from pci/Kconfig, so arches don't have to source both pci/Kconfig and pci/hotplug/Kconfig. Note that this effectively adds pci/hotplug/Kconfig to the following arches, because they already sourced drivers/pci/Kconfig but they previously did not source drivers/pci/hotplug/Kconfig: alpha arm avr32 frv m68k microblaze mn10300 sparc unicore32 Inspired-by-patch-from: Bogicevic Sasa Signed-off-by: Bjorn Helgaas --- arch/arm64/Kconfig | 1 - arch/blackfin/Kconfig | 2 -- arch/ia64/Kconfig | 2 -- arch/m32r/Kconfig | 2 -- arch/mips/Kconfig | 2 -- arch/powerpc/Kconfig | 2 -- arch/s390/Kconfig | 1 - arch/sh/Kconfig | 2 -- arch/tile/Kconfig | 3 --- arch/x86/Kconfig | 2 -- arch/xtensa/Kconfig | 2 -- drivers/parisc/Kconfig | 2 -- drivers/pci/Kconfig | 1 + 13 files changed, 1 insertion(+), 23 deletions(-) (limited to 'drivers') diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 128ec3c8d475..fc8089db20b1 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -235,7 +235,6 @@ config PCI_SYSCALL def_bool PCI source "drivers/pci/Kconfig" -source "drivers/pci/hotplug/Kconfig" endmenu diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig index af76634f8d98..a63c12259e77 100644 --- a/arch/blackfin/Kconfig +++ b/arch/blackfin/Kconfig @@ -1233,8 +1233,6 @@ source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - endmenu menu "Executable file formats" diff --git a/arch/ia64/Kconfig b/arch/ia64/Kconfig index 1bc4b5add65c..b534ebab36ea 100644 --- a/arch/ia64/Kconfig +++ b/arch/ia64/Kconfig @@ -576,8 +576,6 @@ config PCI_SYSCALL source "drivers/pci/Kconfig" -source "drivers/pci/hotplug/Kconfig" - source "drivers/pcmcia/Kconfig" endmenu diff --git a/arch/m32r/Kconfig b/arch/m32r/Kconfig index 836ac5a963c8..e1b123664815 100644 --- a/arch/m32r/Kconfig +++ b/arch/m32r/Kconfig @@ -386,8 +386,6 @@ config ISA source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - endmenu diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index d21eec746afb..d16204d8b926 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -2935,8 +2935,6 @@ config ZONE_DMA32 source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - config RAPIDIO tristate "RapidIO support" depends on PCI diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 247a09f01608..e28f86ee41b8 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -832,8 +832,6 @@ source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - config HAS_RAPIDIO bool default n diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 991843342005..e70d49837fb0 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -605,7 +605,6 @@ config PCI_NR_MSI PCI devices. source "drivers/pci/Kconfig" -source "drivers/pci/hotplug/Kconfig" endif # PCI diff --git a/arch/sh/Kconfig b/arch/sh/Kconfig index ce52af9ca6ba..17a4f1593d65 100644 --- a/arch/sh/Kconfig +++ b/arch/sh/Kconfig @@ -851,8 +851,6 @@ source "drivers/pci/Kconfig" source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - endmenu menu "Executable file formats" diff --git a/arch/tile/Kconfig b/arch/tile/Kconfig index 0c95266d102b..81719302b056 100644 --- a/arch/tile/Kconfig +++ b/arch/tile/Kconfig @@ -455,7 +455,6 @@ config TILE_PCI_IO source "drivers/pci/Kconfig" - config TILE_USB tristate "Tilera USB host adapter support" default y @@ -466,8 +465,6 @@ config TILE_USB Provides USB host adapter support for the built-in EHCI and OHCI interfaces on TILE-Gx chips. -source "drivers/pci/hotplug/Kconfig" - endmenu menu "Executable file formats" diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index ff7fe2dc881c..1912637d6178 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2586,8 +2586,6 @@ config AMD_NB source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - config RAPIDIO tristate "RapidIO support" depends on PCI diff --git a/arch/xtensa/Kconfig b/arch/xtensa/Kconfig index e9df1567d778..7e9464b0fc00 100644 --- a/arch/xtensa/Kconfig +++ b/arch/xtensa/Kconfig @@ -413,8 +413,6 @@ config FORCE_MAX_ZONEORDER source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - config PLATFORM_WANT_DEFAULT_MEM def_bool n diff --git a/drivers/parisc/Kconfig b/drivers/parisc/Kconfig index 592de566e72f..3a102a84d637 100644 --- a/drivers/parisc/Kconfig +++ b/drivers/parisc/Kconfig @@ -110,8 +110,6 @@ config IOMMU_HELPER source "drivers/pcmcia/Kconfig" -source "drivers/pci/hotplug/Kconfig" - endmenu menu "PA-RISC specific drivers" diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 04e00923d178..616165d72a17 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -121,4 +121,5 @@ config PCI_LABEL def_bool y if (DMI || ACPI) select NLS +source "drivers/pci/hotplug/Kconfig" source "drivers/pci/host/Kconfig" -- cgit v1.2.3-59-g8ed1b From 057bd2e0528ec68b3d0481ede0b26a31a9e5d2f1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 9 Feb 2016 15:30:47 +0100 Subject: PCI: Add pci_ops.{add,remove}_bus() callbacks Add pci_ops.{add,remove}_bus() callbacks, which will be called on every newly created bus and when a bus is being removed, respectively. This can be used by drivers to implement driver-specific initialization and teardown of the bus, in addition to the architecture-specifics implemented by the pcibios_add_bus() and the pcibios_remove_bus() functions. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas --- drivers/pci/probe.c | 6 ++++++ drivers/pci/remove.c | 4 ++++ include/linux/pci.h | 2 ++ 3 files changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 6d7ab9bb0d5a..3ea4de1c6926 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -758,6 +758,12 @@ add_dev: pcibios_add_bus(child); + if (child->ops->add_bus) { + ret = child->ops->add_bus(child); + if (WARN_ON(ret < 0)) + dev_err(&child->dev, "failed to add bus: %d\n", ret); + } + /* Create legacy_io and legacy_mem files for this bus */ pci_create_legacy_files(child); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 8a280e9c2ad1..d35c7fcde3af 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -54,6 +54,10 @@ void pci_remove_bus(struct pci_bus *bus) pci_bus_release_busn_res(bus); up_write(&pci_bus_sem); pci_remove_legacy_files(bus); + + if (bus->ops->remove_bus) + bus->ops->remove_bus(bus); + pcibios_remove_bus(bus); device_unregister(&bus->dev); } diff --git a/include/linux/pci.h b/include/linux/pci.h index 27df4a6585da..0f7ef14c4d10 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -578,6 +578,8 @@ static inline int pcibios_err_to_errno(int err) /* Low-level architecture-dependent routines */ struct pci_ops { + int (*add_bus)(struct pci_bus *bus); + void (*remove_bus)(struct pci_bus *bus); void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where); int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val); int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); -- cgit v1.2.3-59-g8ed1b From b4d18d71ec12f9f0ad3190b65e1a33ef1e6f8561 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 9 Feb 2016 15:30:48 +0100 Subject: PCI: tegra: Implement ->{add,remove}_bus() callbacks The configuration space mapping on Tegra is somewhat special, and in order to avoid wasting virtual address space the configuration space for each bus needs to be stitched together from several blocks which form a single continuous virtual address range for accessors. Currently the configuration space is mapped upon the first access to one of its registers. However, the mapping operation may sleep under certain circumstances, so doing it from the configuration space accessors (they are protected by a spin lock) will trigger a warning. To avoid the warning, use the ->add_bus() callback to perform the mapping at enumeration time when the operation is allowed to sleep. Also add an implementation of ->remove_bus() that undoes the mapping established by the ->add_bus() callback. While it isn't currently possible to unload the module, there is work underway to remedy this, and this code will come in handy when that happens. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-tegra.c | 54 ++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 30323114c53c..75c55265ca73 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -426,31 +426,38 @@ free: return ERR_PTR(err); } -/* - * Look up a virtual address mapping for the specified bus number. If no such - * mapping exists, try to create one. - */ -static void __iomem *tegra_pcie_bus_map(struct tegra_pcie *pcie, - unsigned int busnr) +static int tegra_pcie_add_bus(struct pci_bus *bus) { - struct tegra_pcie_bus *bus; + struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata); + struct tegra_pcie_bus *b; - list_for_each_entry(bus, &pcie->buses, list) - if (bus->nr == busnr) - return (void __iomem *)bus->area->addr; + b = tegra_pcie_bus_alloc(pcie, bus->number); + if (IS_ERR(b)) + return PTR_ERR(b); - bus = tegra_pcie_bus_alloc(pcie, busnr); - if (IS_ERR(bus)) - return NULL; + list_add_tail(&b->list, &pcie->buses); - list_add_tail(&bus->list, &pcie->buses); + return 0; +} - return (void __iomem *)bus->area->addr; +static void tegra_pcie_remove_bus(struct pci_bus *child) +{ + struct tegra_pcie *pcie = sys_to_pcie(child->sysdata); + struct tegra_pcie_bus *bus, *tmp; + + list_for_each_entry_safe(bus, tmp, &pcie->buses, list) { + if (bus->nr == child->number) { + vunmap(bus->area->addr); + list_del(&bus->list); + kfree(bus); + break; + } + } } -static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, - unsigned int devfn, - int where) +static void __iomem *tegra_pcie_map_bus(struct pci_bus *bus, + unsigned int devfn, + int where) { struct tegra_pcie *pcie = sys_to_pcie(bus->sysdata); void __iomem *addr = NULL; @@ -466,7 +473,12 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, } } } else { - addr = tegra_pcie_bus_map(pcie, bus->number); + struct tegra_pcie_bus *b; + + list_for_each_entry(b, &pcie->buses, list) + if (b->nr == bus->number) + addr = (void __iomem *)b->area->addr; + if (!addr) { dev_err(pcie->dev, "failed to map cfg. space for bus %u\n", @@ -481,7 +493,9 @@ static void __iomem *tegra_pcie_conf_address(struct pci_bus *bus, } static struct pci_ops tegra_pcie_ops = { - .map_bus = tegra_pcie_conf_address, + .add_bus = tegra_pcie_add_bus, + .remove_bus = tegra_pcie_remove_bus, + .map_bus = tegra_pcie_map_bus, .read = pci_generic_config_read32, .write = pci_generic_config_write32, }; -- cgit v1.2.3-59-g8ed1b From 8493a952432bf00138b3d3c53a9300e353c18a6d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 9 Feb 2016 15:52:31 +0100 Subject: PCI: tegra: Remove unused struct tegra_pcie.num_ports field The num_ports field of the tegra_pcie structure is never used so remove it. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-tegra.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 75c55265ca73..0ca1dae16603 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -295,7 +295,6 @@ struct tegra_pcie { struct tegra_msi msi; struct list_head ports; - unsigned int num_ports; u32 xbar_config; struct regulator_bulk_data *supplies; -- cgit v1.2.3-59-g8ed1b From 56e75e2a15d0b28261503d415eb56bb4c2b92be5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 9 Feb 2016 15:52:32 +0100 Subject: PCI: tegra: Track bus -> CPU mapping Track the offsets of the bus -> CPU mapping for I/O and memory. This is cosmetic for current Tegra chips because the offset is always 0. But to properly support legacy use-cases, like VGA, this would be needed so that PCI bus addresses can be relocated. While at it, also request the I/O resource both in physical memory and I/O space to make /proc/iomem consistent, as well as add the I/O region to the list of host bridge resources. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-tegra.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 0ca1dae16603..7bda73bf7c5e 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -281,6 +281,11 @@ struct tegra_pcie { struct resource prefetch; struct resource busn; + struct { + resource_size_t mem; + resource_size_t io; + } offset; + struct clk *pex_clk; struct clk *afi_clk; struct clk *pll_e; @@ -611,6 +616,17 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) struct tegra_pcie *pcie = sys_to_pcie(sys); int err; + sys->mem_offset = pcie->offset.mem; + sys->io_offset = pcie->offset.io; + + err = devm_request_resource(pcie->dev, &pcie->all, &pcie->io); + if (err < 0) + return err; + + err = devm_request_resource(pcie->dev, &ioport_resource, &pcie->pio); + if (err < 0) + return err; + err = devm_request_resource(pcie->dev, &pcie->all, &pcie->mem); if (err < 0) return err; @@ -619,6 +635,7 @@ static int tegra_pcie_setup(int nr, struct pci_sys_data *sys) if (err) return 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); @@ -1614,6 +1631,9 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie) switch (res.flags & IORESOURCE_TYPE_BITS) { case IORESOURCE_IO: + /* Track the bus -> CPU I/O mapping offset. */ + pcie->offset.io = res.start - range.pci_addr; + memcpy(&pcie->pio, &res, sizeof(res)); pcie->pio.name = np->full_name; @@ -1634,6 +1654,14 @@ static int tegra_pcie_parse_dt(struct tegra_pcie *pcie) break; case IORESOURCE_MEM: + /* + * Track the bus -> CPU memory mapping offset. This + * assumes that the prefetchable and non-prefetchable + * regions will be the last of type IORESOURCE_MEM in + * the ranges property. + * */ + pcie->offset.mem = res.start - range.pci_addr; + if (res.flags & IORESOURCE_PREFETCH) { memcpy(&pcie->prefetch, &res, sizeof(res)); pcie->prefetch.name = "prefetchable"; -- cgit v1.2.3-59-g8ed1b From e32faa303f7f63bad8f9f04267878d61e0f7e0b5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 9 Feb 2016 15:52:33 +0100 Subject: PCI: tegra: Remove misleading PHYS_OFFSET BARs are disabled when the size register is 0, so it's misleading to write a base address into the start register. Signed-off-by: Thierry Reding Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-tegra.c b/drivers/pci/host/pci-tegra.c index 7bda73bf7c5e..68d1f41b3cbf 100644 --- a/drivers/pci/host/pci-tegra.c +++ b/drivers/pci/host/pci-tegra.c @@ -771,7 +771,7 @@ static void tegra_pcie_setup_translations(struct tegra_pcie *pcie) afi_writel(pcie, 0, AFI_FPCI_BAR5); /* map all upstream transactions as uncached */ - afi_writel(pcie, PHYS_OFFSET, AFI_CACHE_BAR0_ST); + afi_writel(pcie, 0, AFI_CACHE_BAR0_ST); afi_writel(pcie, 0, AFI_CACHE_BAR0_SZ); afi_writel(pcie, 0, AFI_CACHE_BAR1_ST); afi_writel(pcie, 0, AFI_CACHE_BAR1_SZ); -- cgit v1.2.3-59-g8ed1b From 20ac75e563d09f56fb00caa25a3afdb88573472d Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 18 Feb 2016 13:50:45 +0100 Subject: PCI/AER: Fix aer_inject error codes EPERM means "Operation not permitted", which doesn't reflect the lack of support for AER. EPROTONOSUPPORT (Protocol not supported) is a better choice of error code if the device or its root port lack support for AER. Likewise, EINVAL means "Invalid argument", which is not suitable for cases where the AER error device is missing or unusable. ENODEV and EPROTONOSUPPORT, respectively, fit better. Suggested-by: Borislav Petkov Signed-off-by: Jean Delvare Signed-off-by: Bjorn Helgaas CC: Borislav Petkov CC: Prarit Bhargava --- drivers/pci/pcie/aer/aer_inject.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index e2760a39a98a..90bc3a6848a1 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -363,7 +363,7 @@ static int aer_inject(struct aer_error_inj *einj) pos_cap_err = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); if (!pos_cap_err) { - ret = -EPERM; + ret = -EPROTONOSUPPORT; goto out_put; } pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever); @@ -373,7 +373,7 @@ static int aer_inject(struct aer_error_inj *einj) rp_pos_cap_err = pci_find_ext_capability(rpdev, PCI_EXT_CAP_ID_ERR); if (!rp_pos_cap_err) { - ret = -EPERM; + ret = -EPROTONOSUPPORT; goto out_put; } @@ -481,12 +481,12 @@ static int aer_inject(struct aer_error_inj *einj) if (find_aer_device(rpdev, &edev)) { if (!get_service_data(edev)) { printk(KERN_WARNING "AER service is not initialized\n"); - ret = -EINVAL; + ret = -EPROTONOSUPPORT; goto out_put; } aer_irq(-1, edev); } else - ret = -EINVAL; + ret = -ENODEV; out_put: kfree(err_alloc); kfree(rperr_alloc); -- cgit v1.2.3-59-g8ed1b From 3bc11851413fcf58f6a2d8f69346609c9db339ed Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 18 Feb 2016 13:52:01 +0100 Subject: PCI/AER: Use dev_warn() in aer_inject dev_warn() is better than printk(LOG_WARNING...) as it records which device the message relates to. Also add a prefix "aer_inject:" to help differentiate real errors from injected errors. Signed-off-by: Jean Delvare Signed-off-by: Bjorn Helgaas CC: Borislav Petkov --- drivers/pci/pcie/aer/aer_inject.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 90bc3a6848a1..3e9fdd5f9330 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "aerdrv.h" /* Override the existing corrected and uncorrected error masks */ @@ -420,14 +421,16 @@ static int aer_inject(struct aer_error_inj *einj) if (!aer_mask_override && einj->cor_status && !(einj->cor_status & ~cor_mask)) { ret = -EINVAL; - printk(KERN_WARNING "The correctable error(s) is masked by device\n"); + dev_warn(&dev->dev, + "aer_inject: The correctable error(s) is masked by device\n"); spin_unlock_irqrestore(&inject_lock, flags); goto out_put; } if (!aer_mask_override && einj->uncor_status && !(einj->uncor_status & ~uncor_mask)) { ret = -EINVAL; - printk(KERN_WARNING "The uncorrectable error(s) is masked by device\n"); + dev_warn(&dev->dev, + "aer_inject: The uncorrectable error(s) is masked by device\n"); spin_unlock_irqrestore(&inject_lock, flags); goto out_put; } @@ -480,7 +483,8 @@ static int aer_inject(struct aer_error_inj *einj) if (find_aer_device(rpdev, &edev)) { if (!get_service_data(edev)) { - printk(KERN_WARNING "AER service is not initialized\n"); + dev_warn(&edev->device, + "aer_inject: AER service is not initialized\n"); ret = -EPROTONOSUPPORT; goto out_put; } -- cgit v1.2.3-59-g8ed1b From 96b45ea5dc19425ca04b53ab2b56af9e2bcc28c6 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 18 Feb 2016 13:52:46 +0100 Subject: PCI/AER: Log actual error causes in aer_inject The aer_inject driver is very quiet. In most cases, it merely returns an error code to user-space, leaving the user with little clue about the actual reason for the failure. So, log error messages for 4 of the most frequent causes of failure: * Can't find the root port of the specified device. * Device doesn't support AER. * Root port doesn't support AER. * AER device not found. This gives the user a chance to understand why aer-inject failed. Based on a preliminary patch by Thomas Renninger. Signed-off-by: Jean Delvare Signed-off-by: Bjorn Helgaas CC: Borislav Petkov CC: Thomas Renninger --- drivers/pci/pcie/aer/aer_inject.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 3e9fdd5f9330..44ed4ded3846 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -358,12 +358,14 @@ static int aer_inject(struct aer_error_inj *einj) return -ENODEV; rpdev = pcie_find_root_port(dev); if (!rpdev) { + dev_err(&dev->dev, "aer_inject: Root port not found\n"); ret = -ENODEV; goto out_put; } pos_cap_err = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); if (!pos_cap_err) { + dev_err(&dev->dev, "aer_inject: Device doesn't support AER\n"); ret = -EPROTONOSUPPORT; goto out_put; } @@ -374,6 +376,8 @@ static int aer_inject(struct aer_error_inj *einj) rp_pos_cap_err = pci_find_ext_capability(rpdev, PCI_EXT_CAP_ID_ERR); if (!rp_pos_cap_err) { + dev_err(&rpdev->dev, + "aer_inject: Root port doesn't support AER\n"); ret = -EPROTONOSUPPORT; goto out_put; } @@ -489,8 +493,10 @@ static int aer_inject(struct aer_error_inj *einj) goto out_put; } aer_irq(-1, edev); - } else + } else { + dev_err(&rpdev->dev, "aer_inject: AER device not found\n"); ret = -ENODEV; + } out_put: kfree(err_alloc); kfree(rperr_alloc); -- cgit v1.2.3-59-g8ed1b From 8e47e15e917fca36945c2f03de28cfda0c8499f0 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Thu, 18 Feb 2016 13:54:02 +0100 Subject: PCI/AER: Log aer_inject error injections Log successful error injections so that injected errors can be differentiated from real errors. Suggested-by: Bjorn Helgaas Signed-off-by: Jean Delvare Signed-off-by: Bjorn Helgaas CC: Borislav Petkov --- drivers/pci/pcie/aer/aer_inject.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer/aer_inject.c b/drivers/pci/pcie/aer/aer_inject.c index 44ed4ded3846..db553dc22c8e 100644 --- a/drivers/pci/pcie/aer/aer_inject.c +++ b/drivers/pci/pcie/aer/aer_inject.c @@ -492,6 +492,9 @@ static int aer_inject(struct aer_error_inj *einj) ret = -EPROTONOSUPPORT; goto out_put; } + dev_info(&edev->device, + "aer_inject: Injecting errors %08x/%08x into device %s\n", + einj->cor_status, einj->uncor_status, pci_name(dev)); aer_irq(-1, edev); } else { dev_err(&rpdev->dev, "aer_inject: AER device not found\n"); -- cgit v1.2.3-59-g8ed1b From caf02abf9bd00b4c23745a055c4f4c243eecd392 Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson" Date: Sun, 6 Mar 2016 22:02:30 +0000 Subject: PCI: Add QEMU top-level IDs for (sub)vendor & device Introduce PCI_VENDOR/PCI_SUBVENDOR/PCI_SUBDEVICE defines to replace the constants scattered in the kernel already used to detect QEMU. They are defined in the QEMU codebase per docs/specs/pci-ids.txt. Signed-off-by: Robin H. Johnson Signed-off-by: Bjorn Helgaas Reviewed-by: Takashi Iwai Reviewed-by: Gerd Hoffmann Acked-by: Michael S. Tsirkin Acked-by: Daniel Vetter --- drivers/gpu/drm/bochs/bochs_drv.c | 4 ++-- drivers/gpu/drm/cirrus/cirrus_drv.c | 5 +++-- drivers/virtio/virtio_pci_common.c | 2 +- include/linux/pci_ids.h | 4 ++++ sound/pci/intel8x0.c | 4 ++-- 5 files changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/bochs/bochs_drv.c b/drivers/gpu/drm/bochs/bochs_drv.c index 7f1a3604b19f..b332b4d3b0e2 100644 --- a/drivers/gpu/drm/bochs/bochs_drv.c +++ b/drivers/gpu/drm/bochs/bochs_drv.c @@ -182,8 +182,8 @@ static const struct pci_device_id bochs_pci_tbl[] = { { .vendor = 0x1234, .device = 0x1111, - .subvendor = 0x1af4, - .subdevice = 0x1100, + .subvendor = PCI_SUBVENDOR_ID_REDHAT_QUMRANET, + .subdevice = PCI_SUBDEVICE_ID_QEMU, .driver_data = BOCHS_QEMU_STDVGA, }, { diff --git a/drivers/gpu/drm/cirrus/cirrus_drv.c b/drivers/gpu/drm/cirrus/cirrus_drv.c index b1619e29a564..7bc394ec9fb3 100644 --- a/drivers/gpu/drm/cirrus/cirrus_drv.c +++ b/drivers/gpu/drm/cirrus/cirrus_drv.c @@ -33,8 +33,9 @@ static struct drm_driver driver; /* only bind to the cirrus chip in qemu */ static const struct pci_device_id pciidlist[] = { - { PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_5446, 0x1af4, 0x1100, 0, - 0, 0 }, + { PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_5446, + PCI_SUBVENDOR_ID_REDHAT_QUMRANET, PCI_SUBDEVICE_ID_QEMU, + 0, 0, 0 }, { PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_5446, PCI_VENDOR_ID_XEN, 0x0001, 0, 0, 0 }, {0,} diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c index 36205c27c4d0..127dfe4e8694 100644 --- a/drivers/virtio/virtio_pci_common.c +++ b/drivers/virtio/virtio_pci_common.c @@ -467,7 +467,7 @@ static const struct dev_pm_ops virtio_pci_pm_ops = { /* Qumranet donated their vendor ID for devices 0x1000 thru 0x10FF. */ static const struct pci_device_id virtio_pci_id_table[] = { - { PCI_DEVICE(0x1af4, PCI_ANY_ID) }, + { PCI_DEVICE(PCI_VENDOR_ID_REDHAT_QUMRANET, PCI_ANY_ID) }, { 0 } }; diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 37f05cb1dfd6..6d249d32c8b8 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2506,6 +2506,10 @@ #define PCI_VENDOR_ID_AZWAVE 0x1a3b +#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBDEVICE_ID_QEMU 0x1100 + #define PCI_VENDOR_ID_ASMEDIA 0x1b21 #define PCI_VENDOR_ID_CIRCUITCO 0x1cc8 diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index 42bcbac801a3..12c2c180e407 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -2980,8 +2980,8 @@ static int snd_intel8x0_inside_vm(struct pci_dev *pci) goto fini; /* check for known (emulated) devices */ - if (pci->subsystem_vendor == 0x1af4 && - pci->subsystem_device == 0x1100) { + if (pci->subsystem_vendor == PCI_SUBVENDOR_ID_REDHAT_QUMRANET && + pci->subsystem_device == PCI_SUBDEVICE_ID_QEMU) { /* KVM emulated sound, PCI SSID: 1af4:1100 */ msg = "enable KVM"; } else if (pci->subsystem_vendor == 0x1ab8) { -- cgit v1.2.3-59-g8ed1b From c521b014cd51da94ae16c3decf1c407c3e0518f6 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 22 Feb 2016 14:58:18 -0600 Subject: PCI: Sleep rather than busy-wait for VPD access completion Use usleep_range() instead of udelay() while waiting for a VPD access to complete. This is not a performance path, so no need to hog the CPU. Rationale for usleep_range() parameters: We clear PCI_VPD_ADDR_F for a read (or set it for a write), then wait for the device to change it. For a device that updates PCI_VPD_ADDR between our config write and subsequent config read, we won't sleep at all and can get the device's maximum rate. Sleeping a minimum of 10 usec per 4-byte access limits throughput to about 400Kbytes/second. VPD is small (32K bytes at most), and most devices use only a fraction of that. We back off exponentially up to 1024 usec per iteration. If we reach 1024, we've already waited up to 1008 usec (16 + 32 + ... + 512), so if we miss an update and wait an extra 1024 usec, we can still get about 1/2 of the device's maximum rate. Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Bjorn Helgaas Reviewed-by: Hannes Reinecke --- drivers/pci/access.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/access.c b/drivers/pci/access.c index 8449d6b58178..01b9d0a00abc 100644 --- a/drivers/pci/access.c +++ b/drivers/pci/access.c @@ -342,14 +342,15 @@ static size_t pci_vpd_size(struct pci_dev *dev, size_t old_size) static int pci_vpd_wait(struct pci_dev *dev) { struct pci_vpd *vpd = dev->vpd; - unsigned long timeout = jiffies + HZ/20 + 2; + unsigned long timeout = jiffies + msecs_to_jiffies(50); + unsigned long max_sleep = 16; u16 status; int ret; if (!vpd->busy) return 0; - for (;;) { + while (time_before(jiffies, timeout)) { ret = pci_user_read_config_word(dev, vpd->cap + PCI_VPD_ADDR, &status); if (ret < 0) @@ -360,15 +361,16 @@ static int pci_vpd_wait(struct pci_dev *dev) return 0; } - if (time_after(jiffies, timeout)) { - dev_printk(KERN_DEBUG, &dev->dev, "vpd r/w failed. This is likely a firmware bug on this device. Contact the card vendor for a firmware update\n"); - return -ETIMEDOUT; - } if (fatal_signal_pending(current)) return -EINTR; - if (!cond_resched()) - udelay(10); + + usleep_range(10, max_sleep); + if (max_sleep < 1024) + max_sleep *= 2; } + + dev_warn(&dev->dev, "VPD access failed. This is likely a firmware bug on this device. Contact the card vendor for a firmware update\n"); + return -ETIMEDOUT; } static ssize_t pci_vpd_read(struct pci_dev *dev, loff_t pos, size_t count, -- cgit v1.2.3-59-g8ed1b From 7c20078a8197389eead62399419fdc4f8ac4a8a3 Mon Sep 17 00:00:00 2001 From: Babu Moger Date: Mon, 15 Feb 2016 09:42:02 +0100 Subject: PCI: Prevent VPD access for buggy devices On some devices, reading or writing VPD causes a system panic. This can be easily reproduced by running "lspci -vvv" or "cat /sys/bus/devices/XX../vpd". Blacklist these devices so we don't access VPD data at all. [bhelgaas: changelog, comment, drop pci/access.c changes] Link: https://bugzilla.kernel.org/show_bug.cgi?id=110681 Tested-by: Shane Seymour Tested-by: Babu Moger Signed-off-by: Babu Moger Signed-off-by: Hannes Reinecke Signed-off-by: Bjorn Helgaas Cc: Alexander Duyck --- drivers/pci/quirks.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 0575a1e026b4..626c3b22c053 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2134,6 +2134,35 @@ static void quirk_via_cx700_pci_parking_caching(struct pci_dev *dev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_VIA, 0x324e, quirk_via_cx700_pci_parking_caching); +/* + * If a device follows the VPD format spec, the PCI core will not read or + * write past the VPD End Tag. But some vendors do not follow the VPD + * format spec, so we can't tell how much data is safe to access. Devices + * may behave unpredictably if we access too much. Blacklist these devices + * so we don't touch VPD at all. + */ +static void quirk_blacklist_vpd(struct pci_dev *dev) +{ + if (dev->vpd) { + dev->vpd->len = 0; + dev_warn(&dev->dev, FW_BUG "VPD access disabled\n"); + } +} + +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0060, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x007c, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0413, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0078, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0079, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0073, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x0071, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005b, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x002f, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005d, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LSI_LOGIC, 0x005f, quirk_blacklist_vpd); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATTANSIC, PCI_ANY_ID, + quirk_blacklist_vpd); + /* * For Broadcom 5706, 5708, 5709 rev. A nics, any read beyond the * VPD end tag will hang the device. This problem was initially -- cgit v1.2.3-59-g8ed1b From c1678ffcdea25afe4fbbebfab13d65a7db5458fb Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Thu, 10 Mar 2016 14:44:13 -0600 Subject: ARC: Add PCI support Add PCI support to ARC and update drivers/pci Makefile enabling the ARC arch to use the generic PCI setup functions. [bhelgaas: fold in Joao's pci-dma-compat.h & pci-bridge.h build fix (I should have caught this myself, sorry] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas Acked-by: Vineet Gupta --- arch/arc/Kconfig | 26 ++++++++++++++++++++++++++ arch/arc/include/asm/dma.h | 5 +++++ arch/arc/include/asm/io.h | 9 +++++++++ arch/arc/include/asm/pci.h | 28 ++++++++++++++++++++++++++++ arch/arc/kernel/Makefile | 1 + arch/arc/kernel/pcibios.c | 22 ++++++++++++++++++++++ arch/arc/plat-axs10x/Kconfig | 1 + drivers/pci/Makefile | 1 + 8 files changed, 93 insertions(+) create mode 100644 arch/arc/include/asm/pci.h create mode 100644 arch/arc/kernel/pcibios.c (limited to 'drivers') diff --git a/arch/arc/Kconfig b/arch/arc/Kconfig index 76dde9db7934..f9e3b357af79 100644 --- a/arch/arc/Kconfig +++ b/arch/arc/Kconfig @@ -19,6 +19,7 @@ config ARC select GENERIC_FIND_FIRST_BIT # for now, we don't need GENERIC_IRQ_PROBE, CONFIG_GENERIC_IRQ_CHIP select GENERIC_IRQ_SHOW + select GENERIC_PCI_IOMAP select GENERIC_PENDING_IRQ if SMP select GENERIC_SMP_IDLE_THREAD select HAVE_ARCH_KGDB @@ -39,6 +40,9 @@ config ARC select PERF_USE_VMALLOC select HAVE_DEBUG_STACKOVERFLOW +config MIGHT_HAVE_PCI + bool + config TRACE_IRQFLAGS_SUPPORT def_bool y @@ -568,6 +572,28 @@ endmenu # "ARC Architecture Configuration" source "mm/Kconfig" source "net/Kconfig" source "drivers/Kconfig" + +menu "Bus Support" + +config PCI + bool "PCI support" if MIGHT_HAVE_PCI + help + PCI is the name of a bus system, i.e., the way the CPU talks to + the other stuff inside your box. Find out if your board/platform + has PCI. + + Note: PCIe support for Synopsys Device will be available only + when HAPS DX is configured with PCIe RC bitmap. If you have PCI, + say Y, otherwise N. + +config PCI_SYSCALL + def_bool PCI + +source "drivers/pci/Kconfig" +source "drivers/pci/pcie/Kconfig" + +endmenu + source "fs/Kconfig" source "arch/arc/Kconfig.debug" source "security/Kconfig" diff --git a/arch/arc/include/asm/dma.h b/arch/arc/include/asm/dma.h index ca7c45181de9..01e47a69b034 100644 --- a/arch/arc/include/asm/dma.h +++ b/arch/arc/include/asm/dma.h @@ -10,5 +10,10 @@ #define ASM_ARC_DMA_H #define MAX_DMA_ADDRESS 0xC0000000 +#ifdef CONFIG_PCI +extern int isa_dma_bridge_buggy; +#else +#define isa_dma_bridge_buggy 0 +#endif #endif diff --git a/arch/arc/include/asm/io.h b/arch/arc/include/asm/io.h index 694ece8a0243..947bf0cfdec0 100644 --- a/arch/arc/include/asm/io.h +++ b/arch/arc/include/asm/io.h @@ -16,6 +16,15 @@ extern void __iomem *ioremap(unsigned long physaddr, unsigned long size); extern void __iomem *ioremap_prot(phys_addr_t offset, unsigned long size, unsigned long flags); +static inline void __iomem *ioport_map(unsigned long port, unsigned int nr) +{ + return (void __iomem *)port; +} + +static inline void ioport_unmap(void __iomem *addr) +{ +} + extern void iounmap(const void __iomem *addr); #define ioremap_nocache(phy, sz) ioremap(phy, sz) diff --git a/arch/arc/include/asm/pci.h b/arch/arc/include/asm/pci.h new file mode 100644 index 000000000000..ba56c23c1b20 --- /dev/null +++ b/arch/arc/include/asm/pci.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * 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. + */ + +#ifndef _ASM_ARC_PCI_H +#define _ASM_ARC_PCI_H + +#ifdef __KERNEL__ +#include + +#define PCIBIOS_MIN_IO 0x100 +#define PCIBIOS_MIN_MEM 0x100000 + +#define pcibios_assign_all_busses() 1 +/* + * The PCI address space does equal the physical memory address space. + * The networking and block device layers use this boolean for bounce + * buffer decisions. + */ +#define PCI_DMA_BUS_IS_PHYS 1 + +#endif /* __KERNEL__ */ + +#endif /* _ASM_ARC_PCI_H */ diff --git a/arch/arc/kernel/Makefile b/arch/arc/kernel/Makefile index e7f3625a19b5..1bc2036b19d7 100644 --- a/arch/arc/kernel/Makefile +++ b/arch/arc/kernel/Makefile @@ -12,6 +12,7 @@ obj-y := arcksyms.o setup.o irq.o time.o reset.o ptrace.o process.o devtree.o obj-y += signal.o traps.o sys.o troubleshoot.o stacktrace.o disasm.o clk.o obj-$(CONFIG_ISA_ARCOMPACT) += entry-compact.o intc-compact.o obj-$(CONFIG_ISA_ARCV2) += entry-arcv2.o intc-arcv2.o +obj-$(CONFIG_PCI) += pcibios.o obj-$(CONFIG_MODULES) += arcksyms.o module.o obj-$(CONFIG_SMP) += smp.o diff --git a/arch/arc/kernel/pcibios.c b/arch/arc/kernel/pcibios.c new file mode 100644 index 000000000000..72e1d73d0bd6 --- /dev/null +++ b/arch/arc/kernel/pcibios.c @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014-2015 Synopsys, Inc. (www.synopsys.com) + * + * 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. + */ + +#include + +/* + * We don't have to worry about legacy ISA devices, so nothing to do here + */ +resource_size_t pcibios_align_resource(void *data, const struct resource *res, + resource_size_t size, resource_size_t align) +{ + return res->start; +} + +void pcibios_fixup_bus(struct pci_bus *bus) +{ +} diff --git a/arch/arc/plat-axs10x/Kconfig b/arch/arc/plat-axs10x/Kconfig index d475f9d4847c..426ac4b8bb39 100644 --- a/arch/arc/plat-axs10x/Kconfig +++ b/arch/arc/plat-axs10x/Kconfig @@ -11,6 +11,7 @@ menuconfig ARC_PLAT_AXS10X select DW_APB_ICTL select GPIO_DWAPB select OF_GPIO + select MIGHT_HAVE_PCI select GENERIC_IRQ_CHIP select ARCH_REQUIRE_GPIOLIB help diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index be3f631c3f75..2154092ddee8 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_PCI_IOV) += iov.o # Some architectures use the generic PCI setup functions # obj-$(CONFIG_ALPHA) += setup-irq.o +obj-$(CONFIG_ARC) += setup-irq.o obj-$(CONFIG_ARM) += setup-irq.o obj-$(CONFIG_ARM64) += setup-irq.o obj-$(CONFIG_UNICORE32) += setup-irq.o -- cgit v1.2.3-59-g8ed1b From 5adecf817dd630529d6565a242141db2df3239f7 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Mon, 22 Feb 2016 13:05:48 -0700 Subject: PCI: Wait for up to 1000ms after FLR reset Some devices take longer than the spec indicates to return from FLR reset, a notable case of this is Intel integrated graphics (IGD), which can often take an additional 300ms powering down an attached LCD panel as part of the FLR. Allow devices up to 1000ms, testing every 100ms whether the second dword of config space is read as -1. Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 602eb4223510..123408e5a1d6 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3414,6 +3414,29 @@ int pci_wait_for_pending_transaction(struct pci_dev *dev) } EXPORT_SYMBOL(pci_wait_for_pending_transaction); +/* + * We should only need to wait 100ms after FLR, but some devices take longer. + * Wait for up to 1000ms for config space to return something other than -1. + * Intel IGD requires this when an LCD panel is attached. We read the 2nd + * dword because VFs don't implement the 1st dword. + */ +static void pci_flr_wait(struct pci_dev *dev) +{ + int i = 0; + u32 id; + + do { + msleep(100); + pci_read_config_dword(dev, PCI_COMMAND, &id); + } while (i++ < 10 && id == ~0); + + if (id == ~0) + dev_warn(&dev->dev, "Failed to return from FLR\n"); + else if (i > 1) + dev_info(&dev->dev, "Required additional %dms to return from FLR\n", + (i - 1) * 100); +} + static int pcie_flr(struct pci_dev *dev, int probe) { u32 cap; @@ -3429,7 +3452,7 @@ static int pcie_flr(struct pci_dev *dev, int probe) dev_err(&dev->dev, "timed out waiting for pending transaction; performing function level reset anyway\n"); pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_BCR_FLR); - msleep(100); + pci_flr_wait(dev); return 0; } @@ -3459,7 +3482,7 @@ static int pci_af_flr(struct pci_dev *dev, int probe) dev_err(&dev->dev, "timed out waiting for pending transaction; performing AF function level reset anyway\n"); pci_write_config_byte(dev, pos + PCI_AF_CTRL, PCI_AF_CTRL_FLR); - msleep(100); + pci_flr_wait(dev); return 0; } -- cgit v1.2.3-59-g8ed1b From 304e6d572bf2dc810b806254123f7412dba334fa Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Thu, 25 Feb 2016 09:45:56 +0900 Subject: PCI: rcar: Depend on ARCH_RENESAS, not ARCH_SHMOBILE Make the R-Car drivers depend on ARCH_RENESAS instead of ARCH_SHMOBILE. This is part of an ongoing process to migrate from ARCH_SHMOBILE to ARCH_RENESAS. The motivation is that RENESAS seems to be a more appropriate name than SHMOBILE for the majority of Renesas ARM-based SoCs. Signed-off-by: Simon Horman Signed-off-by: Bjorn Helgaas Acked-by: Geert Uytterhoeven --- drivers/pci/host/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a605426538..dcf4d5a349ca 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -41,7 +41,7 @@ config PCI_TEGRA config PCI_RCAR_GEN2 bool "Renesas R-Car Gen2 Internal PCI controller" depends on ARM - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST help Say Y here if you want internal PCI support on R-Car Gen2 SoC. There are 3 internal PCI controllers available with a single @@ -49,7 +49,7 @@ config PCI_RCAR_GEN2 config PCI_RCAR_GEN2_PCIE bool "Renesas R-Car PCIe controller" - depends on ARCH_SHMOBILE || (ARM && COMPILE_TEST) + depends on ARCH_RENESAS || (ARM && COMPILE_TEST) help Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. -- cgit v1.2.3-59-g8ed1b From eff31f4002c4e25b9b8c39d0a3a551c6c64c77e8 Mon Sep 17 00:00:00 2001 From: Ley Foon Tan Date: Wed, 2 Mar 2016 17:43:07 +0800 Subject: PCI: altera: Fix altera_pcie_link_is_up() Originally altera_pcie_link_is_up() decided the link was up if any of the low four bits of the LTSSM register were set. But the link is only up if the LTSSM state is L0, so check for that exact value. [bhelgaas: changelog] Signed-off-by: Ley Foon Tan Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pcie-altera.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c index 99da549d5d06..dbac6fb3f0bd 100644 --- a/drivers/pci/host/pcie-altera.c +++ b/drivers/pci/host/pcie-altera.c @@ -40,6 +40,7 @@ #define P2A_INT_ENABLE 0x3070 #define P2A_INT_ENA_ALL 0xf #define RP_LTSSM 0x3c64 +#define RP_LTSSM_MASK 0x1f #define LTSSM_L0 0xf /* TLP configuration type 0 and 1 */ @@ -140,7 +141,7 @@ static void tlp_write_tx(struct altera_pcie *pcie, static bool altera_pcie_link_is_up(struct altera_pcie *pcie) { - return !!(cra_readl(pcie, RP_LTSSM) & LTSSM_L0); + return !!((cra_readl(pcie, RP_LTSSM) & RP_LTSSM_MASK) == LTSSM_L0); } static bool altera_pcie_valid_config(struct altera_pcie *pcie, -- cgit v1.2.3-59-g8ed1b From 25de15c958e98747c7dada3983474bd4b8ae381b Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 7 Mar 2016 12:32:21 +0800 Subject: PCI: keystone: Defer probing if devm_phy_get() returns -EPROBE_DEFER A SerDes PHY is optional, so if devm_phy_get() doesn't find one at all, that's fine. But if devm_phy_get() finds a PHY that doesn't have a driver yet, it returns -EPROBE_DEFER. In that case, defer probing the Keystone driver. We may be able to load it later after a PHY driver is loaded. [bhelgaas: changelog, check for -EPROBE_DEFER first] Signed-off-by: Shawn Lin Signed-off-by: Bjorn Helgaas --- drivers/pci/host/pci-keystone.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index 0aa81bd3de12..cd7034523f52 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -359,6 +359,9 @@ static int __init ks_pcie_probe(struct platform_device *pdev) /* initialize SerDes Phy if present */ phy = devm_phy_get(dev, "pcie-phy"); + if (PTR_ERR_OR_ZERO(phy) == -EPROBE_DEFER) + return PTR_ERR(phy); + if (!IS_ERR_OR_NULL(phy)) { ret = phy_init(phy); if (ret < 0) -- cgit v1.2.3-59-g8ed1b From ab597d35ef11d2a921e0ec507a9b7861bcb44cbd Mon Sep 17 00:00:00 2001 From: Bharat Kumar Gogada Date: Sun, 6 Mar 2016 22:02:14 +0530 Subject: PCI: xilinx-nwl: Add support for Xilinx NWL PCIe Host Controller Add PCIe Root Port driver for Xilinx PCIe NWL bridge IP. [bhelgaas: wait for link like dw_pcie_wait_for_link(), simplify bitmap error path, typos, whitespace, fold in Dan Carpenter's PTR_ERR() fix] Signed-off-by: Bharat Kumar Gogada Signed-off-by: Ravi Kiran Gummaluri Signed-off-by: Bjorn Helgaas Reviewed-by: Marc Zyngier Acked-by: Rob Herring --- .../devicetree/bindings/pci/xilinx-nwl-pcie.txt | 68 ++ drivers/pci/host/Kconfig | 10 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-xilinx-nwl.c | 881 +++++++++++++++++++++ 4 files changed, 960 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt create mode 100644 drivers/pci/host/pcie-xilinx-nwl.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt b/Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt new file mode 100644 index 000000000000..337fc97d18c9 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/xilinx-nwl-pcie.txt @@ -0,0 +1,68 @@ +* Xilinx NWL PCIe Root Port Bridge DT description + +Required properties: +- compatible: Should contain "xlnx,nwl-pcie-2.11" +- #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. +- reg: Should contain Bridge, PCIe Controller registers location, + configuration space, and length +- reg-names: Must include the following entries: + "breg": bridge registers + "pcireg": PCIe controller registers + "cfg": configuration space region +- device_type: must be "pci" +- interrupts: Should contain NWL PCIe interrupt +- interrupt-names: Must include the following entries: + "msi1, msi0": interrupt asserted when MSI is received + "intx": interrupt asserted when a legacy interrupt is received + "misc": interrupt asserted when miscellaneous is received +- interrupt-map-mask and interrupt-map: standard PCI properties to define the + mapping of the PCI interface to interrupt numbers. +- ranges: ranges for the PCI memory regions (I/O space region is not + supported by hardware) + Please refer to the standard PCI bus binding document for a more + detailed explanation +- msi-controller: indicates that this is MSI controller node +- msi-parent: MSI parent of the root complex itself +- legacy-interrupt-controller: Interrupt controller device node for Legacy interrupts + - interrupt-controller: identifies the node as an interrupt controller + - #interrupt-cells: should be set to 1 + - #address-cells: specifies the number of cells needed to encode an + address. The value must be 0. + + +Example: +++++++++ + +nwl_pcie: pcie@fd0e0000 { + #address-cells = <3>; + #size-cells = <2>; + compatible = "xlnx,nwl-pcie-2.11"; + #interrupt-cells = <1>; + msi-controller; + device_type = "pci"; + interrupt-parent = <&gic>; + interrupts = <0 114 4>, <0 115 4>, <0 116 4>, <0 117 4>, <0 118 4>; + interrupt-names = "msi0", "msi1", "intx", "dummy", "misc"; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc 0x1>, + <0x0 0x0 0x0 0x2 &pcie_intc 0x2>, + <0x0 0x0 0x0 0x3 &pcie_intc 0x3>, + <0x0 0x0 0x0 0x4 &pcie_intc 0x4>; + + msi-parent = <&nwl_pcie>; + reg = <0x0 0xfd0e0000 0x0 0x1000>, + <0x0 0xfd480000 0x0 0x1000>, + <0x0 0xe0000000 0x0 0x1000000>; + reg-names = "breg", "pcireg", "cfg"; + ranges = <0x02000000 0x00000000 0xe1000000 0x00000000 0xe1000000 0 0x0f000000>; + + pcie_intc: legacy-interrupt-controller { + interrupt-controller; + #address-cells = <0>; + #interrupt-cells = <1>; + }; + +}; diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a605426538..c39989a9884d 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -16,6 +16,16 @@ config PCI_MVEBU depends on ARCH_MVEBU || ARCH_DOVE depends on OF +config PCIE_XILINX_NWL + bool "NWL PCIe Core" + depends on ARCH_ZYNQMP + select PCI_MSI_IRQ_DOMAIN if PCI_MSI + help + Say 'Y' here if you want kernel support for Xilinx + NWL PCIe controller. The controller can act as Root Port + or End Point. The current option selection will only + support root port enabling. + config PCIE_DW bool diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 7b2f20c6ccc6..d0b7d8585f57 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o +obj-$(CONFIG_PCIE_XILINX_NWL) += pcie-xilinx-nwl.o obj-$(CONFIG_PCI_XGENE) += pci-xgene.o obj-$(CONFIG_PCI_XGENE_MSI) += pci-xgene-msi.o obj-$(CONFIG_PCI_LAYERSCAPE) += pci-layerscape.o diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c new file mode 100644 index 000000000000..5139e6443bbd --- /dev/null +++ b/drivers/pci/host/pcie-xilinx-nwl.c @@ -0,0 +1,881 @@ +/* + * PCIe host controller driver for NWL PCIe Bridge + * Based on pcie-xilinx.c, pci-tegra.c + * + * (C) Copyright 2014 - 2015, Xilinx, Inc. + * + * 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 + +/* Bridge core config registers */ +#define BRCFG_PCIE_RX0 0x00000000 +#define BRCFG_INTERRUPT 0x00000010 +#define BRCFG_PCIE_RX_MSG_FILTER 0x00000020 + +/* Egress - Bridge translation registers */ +#define E_BREG_CAPABILITIES 0x00000200 +#define E_BREG_CONTROL 0x00000208 +#define E_BREG_BASE_LO 0x00000210 +#define E_BREG_BASE_HI 0x00000214 +#define E_ECAM_CAPABILITIES 0x00000220 +#define E_ECAM_CONTROL 0x00000228 +#define E_ECAM_BASE_LO 0x00000230 +#define E_ECAM_BASE_HI 0x00000234 + +/* Ingress - address translations */ +#define I_MSII_CAPABILITIES 0x00000300 +#define I_MSII_CONTROL 0x00000308 +#define I_MSII_BASE_LO 0x00000310 +#define I_MSII_BASE_HI 0x00000314 + +#define I_ISUB_CONTROL 0x000003E8 +#define SET_ISUB_CONTROL BIT(0) +/* Rxed msg fifo - Interrupt status registers */ +#define MSGF_MISC_STATUS 0x00000400 +#define MSGF_MISC_MASK 0x00000404 +#define MSGF_LEG_STATUS 0x00000420 +#define MSGF_LEG_MASK 0x00000424 +#define MSGF_MSI_STATUS_LO 0x00000440 +#define MSGF_MSI_STATUS_HI 0x00000444 +#define MSGF_MSI_MASK_LO 0x00000448 +#define MSGF_MSI_MASK_HI 0x0000044C + +/* Msg filter mask bits */ +#define CFG_ENABLE_PM_MSG_FWD BIT(1) +#define CFG_ENABLE_INT_MSG_FWD BIT(2) +#define CFG_ENABLE_ERR_MSG_FWD BIT(3) +#define CFG_ENABLE_SLT_MSG_FWD BIT(5) +#define CFG_ENABLE_VEN_MSG_FWD BIT(7) +#define CFG_ENABLE_OTH_MSG_FWD BIT(13) +#define CFG_ENABLE_VEN_MSG_EN BIT(14) +#define CFG_ENABLE_VEN_MSG_VEN_INV BIT(15) +#define CFG_ENABLE_VEN_MSG_VEN_ID GENMASK(31, 16) +#define CFG_ENABLE_MSG_FILTER_MASK (CFG_ENABLE_PM_MSG_FWD | \ + CFG_ENABLE_INT_MSG_FWD | \ + CFG_ENABLE_ERR_MSG_FWD | \ + CFG_ENABLE_SLT_MSG_FWD | \ + CFG_ENABLE_VEN_MSG_FWD | \ + CFG_ENABLE_OTH_MSG_FWD | \ + CFG_ENABLE_VEN_MSG_EN | \ + CFG_ENABLE_VEN_MSG_VEN_INV | \ + CFG_ENABLE_VEN_MSG_VEN_ID) + +/* Misc interrupt status mask bits */ +#define MSGF_MISC_SR_RXMSG_AVAIL BIT(0) +#define MSGF_MISC_SR_RXMSG_OVER BIT(1) +#define MSGF_MISC_SR_SLAVE_ERR BIT(4) +#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_MASKALL (MSGF_MISC_SR_RXMSG_AVAIL | \ + MSGF_MISC_SR_RXMSG_OVER | \ + MSGF_MISC_SR_SLAVE_ERR | \ + MSGF_MISC_SR_MASTER_ERR | \ + MSGF_MISC_SR_I_ADDR_ERR | \ + MSGF_MISC_SR_E_ADDR_ERR | \ + MSGF_MISC_SR_UR_DETECT | \ + MSGF_MISC_SR_PCIE_CORE | \ + MSGF_MISC_SR_PCIE_CORE_ERR) + +/* Legacy interrupt status mask bits */ +#define MSGF_LEG_SR_INTA BIT(0) +#define MSGF_LEG_SR_INTB BIT(1) +#define MSGF_LEG_SR_INTC BIT(2) +#define MSGF_LEG_SR_INTD BIT(3) +#define MSGF_LEG_SR_MASKALL (MSGF_LEG_SR_INTA | MSGF_LEG_SR_INTB | \ + 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 MSII_PRESENT BIT(0) +#define MSII_ENABLE BIT(0) +#define MSII_STATUS_ENABLE BIT(15) + +/* Bridge config interrupt mask */ +#define BRCFG_INTERRUPT_MASK BIT(0) +#define BREG_PRESENT BIT(0) +#define BREG_ENABLE BIT(0) +#define BREG_ENABLE_FORCE BIT(1) + +/* E_ECAM status mask bits */ +#define E_ECAM_PRESENT BIT(0) +#define E_ECAM_CR_ENABLE BIT(0) +#define E_ECAM_SIZE_LOC GENMASK(20, 16) +#define E_ECAM_SIZE_SHIFT 16 +#define ECAM_BUS_LOC_SHIFT 20 +#define ECAM_DEV_LOC_SHIFT 12 +#define NWL_ECAM_VALUE_DEFAULT 12 + +#define CFG_DMA_REG_BAR GENMASK(2, 0) + +#define INT_PCI_MSI_NR (2 * 32) +#define INTX_NUM 4 + +/* Readin the PS_LINKUP */ +#define PS_LINKUP_OFFSET 0x00000238 +#define PCIE_PHY_LINKUP_BIT BIT(0) +#define PHY_RDY_LINKUP_BIT BIT(1) + +/* 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 nwl_msi { /* MSI information */ + struct irq_domain *msi_domain; + unsigned long *bitmap; + struct irq_domain *dev_domain; + struct mutex lock; /* protect bitmap variable */ + int irq_msi0; + int irq_msi1; +}; + +struct nwl_pcie { + struct device *dev; + void __iomem *breg_base; + void __iomem *pcireg_base; + void __iomem *ecam_base; + phys_addr_t phys_breg_base; /* Physical Bridge Register Base */ + phys_addr_t phys_pcie_reg_base; /* Physical PCIe Controller Base */ + phys_addr_t phys_ecam_base; /* Physical Configuration Base */ + u32 breg_size; + u32 pcie_reg_size; + u32 ecam_size; + int irq_intx; + int irq_misc; + u32 ecam_value; + u8 last_busno; + u8 root_busno; + struct nwl_msi msi; + struct irq_domain *legacy_irq_domain; +}; + +static inline u32 nwl_bridge_readl(struct nwl_pcie *pcie, u32 off) +{ + return readl(pcie->breg_base + off); +} + +static inline void nwl_bridge_writel(struct nwl_pcie *pcie, u32 val, u32 off) +{ + writel(val, pcie->breg_base + off); +} + +static bool nwl_pcie_link_up(struct nwl_pcie *pcie) +{ + if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PCIE_PHY_LINKUP_BIT) + return true; + return false; +} + +static bool nwl_phy_link_up(struct nwl_pcie *pcie) +{ + if (readl(pcie->pcireg_base + PS_LINKUP_OFFSET) & PHY_RDY_LINKUP_BIT) + return true; + return false; +} + +static int nwl_wait_for_link(struct nwl_pcie *pcie) +{ + int retries; + + /* check if the link is up or not */ + for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { + if (nwl_phy_link_up(pcie)) + return 0; + usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); + } + + dev_err(pcie->dev, "PHY link never came up\n"); + return -ETIMEDOUT; +} + +static bool nwl_pcie_valid_device(struct pci_bus *bus, unsigned int devfn) +{ + struct nwl_pcie *pcie = bus->sysdata; + + /* Check link before accessing downstream ports */ + if (bus->number != pcie->root_busno) { + if (!nwl_pcie_link_up(pcie)) + return false; + } + + /* Only one device down on each root port */ + if (bus->number == pcie->root_busno && devfn > 0) + return false; + + return true; +} + +/** + * nwl_pcie_map_bus - Get configuration base + * + * @bus: Bus structure of current bus + * @devfn: Device/function + * @where: Offset from base + * + * Return: Base address of the configuration space needed to be + * accessed. + */ +static void __iomem *nwl_pcie_map_bus(struct pci_bus *bus, unsigned int devfn, + int where) +{ + struct nwl_pcie *pcie = bus->sysdata; + int relbus; + + if (!nwl_pcie_valid_device(bus, devfn)) + return NULL; + + relbus = (bus->number << ECAM_BUS_LOC_SHIFT) | + (devfn << ECAM_DEV_LOC_SHIFT); + + return pcie->ecam_base + relbus + where; +} + +/* PCIe operations */ +static struct pci_ops nwl_pcie_ops = { + .map_bus = nwl_pcie_map_bus, + .read = pci_generic_config_read, + .write = pci_generic_config_write, +}; + +static irqreturn_t nwl_pcie_misc_handler(int irq, void *data) +{ + struct nwl_pcie *pcie = data; + u32 misc_stat; + + /* Checking for misc interrupts */ + misc_stat = nwl_bridge_readl(pcie, MSGF_MISC_STATUS) & + MSGF_MISC_SR_MASKALL; + if (!misc_stat) + return IRQ_NONE; + + if (misc_stat & MSGF_MISC_SR_RXMSG_OVER) + dev_err(pcie->dev, "Received Message FIFO Overflow\n"); + + if (misc_stat & MSGF_MISC_SR_SLAVE_ERR) + dev_err(pcie->dev, "Slave error\n"); + + if (misc_stat & MSGF_MISC_SR_MASTER_ERR) + dev_err(pcie->dev, "Master error\n"); + + if (misc_stat & MSGF_MISC_SR_I_ADDR_ERR) + dev_err(pcie->dev, + "In Misc Ingress address translation error\n"); + + if (misc_stat & MSGF_MISC_SR_E_ADDR_ERR) + 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"); + + /* Clear misc interrupt status */ + nwl_bridge_writel(pcie, misc_stat, MSGF_MISC_STATUS); + + return IRQ_HANDLED; +} + +static void nwl_pcie_leg_handler(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie; + unsigned long status; + u32 bit; + u32 virq; + + chained_irq_enter(chip, desc); + pcie = irq_desc_get_handler_data(desc); + + while ((status = nwl_bridge_readl(pcie, MSGF_LEG_STATUS) & + MSGF_LEG_SR_MASKALL) != 0) { + for_each_set_bit(bit, &status, INTX_NUM) { + virq = irq_find_mapping(pcie->legacy_irq_domain, + bit + 1); + if (virq) + generic_handle_irq(virq); + } + } + + chained_irq_exit(chip, desc); +} + +static void nwl_pcie_handle_msi_irq(struct nwl_pcie *pcie, u32 status_reg) +{ + struct nwl_msi *msi; + unsigned long status; + u32 bit; + u32 virq; + + msi = &pcie->msi; + + while ((status = nwl_bridge_readl(pcie, status_reg)) != 0) { + for_each_set_bit(bit, &status, 32) { + nwl_bridge_writel(pcie, 1 << bit, status_reg); + virq = irq_find_mapping(msi->dev_domain, bit); + if (virq) + generic_handle_irq(virq); + } + } +} + +static void nwl_pcie_msi_handler_high(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie = irq_desc_get_handler_data(desc); + + chained_irq_enter(chip, desc); + nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_HI); + chained_irq_exit(chip, desc); +} + +static void nwl_pcie_msi_handler_low(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + struct nwl_pcie *pcie = irq_desc_get_handler_data(desc); + + chained_irq_enter(chip, desc); + nwl_pcie_handle_msi_irq(pcie, MSGF_MSI_STATUS_LO); + chained_irq_exit(chip, desc); +} + +static int nwl_legacy_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 legacy_domain_ops = { + .map = nwl_legacy_map, +}; + +#ifdef CONFIG_PCI_MSI +static struct irq_chip nwl_msi_irq_chip = { + .name = "nwl_pcie:msi", + .irq_enable = unmask_msi_irq, + .irq_disable = mask_msi_irq, + .irq_mask = mask_msi_irq, + .irq_unmask = unmask_msi_irq, + +}; + +static struct msi_domain_info nwl_msi_domain_info = { + .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_MULTI_PCI_MSI), + .chip = &nwl_msi_irq_chip, +}; +#endif + +static void nwl_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) +{ + struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data); + phys_addr_t msi_addr = pcie->phys_pcie_reg_base; + + msg->address_lo = lower_32_bits(msi_addr); + msg->address_hi = upper_32_bits(msi_addr); + msg->data = data->hwirq; +} + +static int nwl_msi_set_affinity(struct irq_data *irq_data, + const struct cpumask *mask, bool force) +{ + return -EINVAL; +} + +static struct irq_chip nwl_irq_chip = { + .name = "Xilinx MSI", + .irq_compose_msi_msg = nwl_compose_msi_msg, + .irq_set_affinity = nwl_msi_set_affinity, +}; + +static int nwl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *args) +{ + struct nwl_pcie *pcie = domain->host_data; + struct nwl_msi *msi = &pcie->msi; + int bit; + int i; + + mutex_lock(&msi->lock); + bit = bitmap_find_next_zero_area(msi->bitmap, INT_PCI_MSI_NR, 0, + nr_irqs, 0); + if (bit >= INT_PCI_MSI_NR) { + mutex_unlock(&msi->lock); + return -ENOSPC; + } + + bitmap_set(msi->bitmap, bit, nr_irqs); + + for (i = 0; i < nr_irqs; i++) { + irq_domain_set_info(domain, virq + i, bit + i, &nwl_irq_chip, + domain->host_data, handle_simple_irq, + NULL, NULL); + } + mutex_unlock(&msi->lock); + return 0; +} + +static void nwl_irq_domain_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct irq_data *data = irq_domain_get_irq_data(domain, virq); + struct nwl_pcie *pcie = irq_data_get_irq_chip_data(data); + struct nwl_msi *msi = &pcie->msi; + + mutex_lock(&msi->lock); + bitmap_clear(msi->bitmap, data->hwirq, nr_irqs); + mutex_unlock(&msi->lock); +} + +static const struct irq_domain_ops dev_msi_domain_ops = { + .alloc = nwl_irq_domain_alloc, + .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 + struct fwnode_handle *fwnode = of_node_to_fwnode(pcie->dev->of_node); + struct nwl_msi *msi = &pcie->msi; + + msi->dev_domain = irq_domain_add_linear(NULL, INT_PCI_MSI_NR, + &dev_msi_domain_ops, pcie); + if (!msi->dev_domain) { + dev_err(pcie->dev, "failed to create dev IRQ domain\n"); + return -ENOMEM; + } + msi->msi_domain = pci_msi_create_irq_domain(fwnode, + &nwl_msi_domain_info, + msi->dev_domain); + if (!msi->msi_domain) { + dev_err(pcie->dev, "failed to create msi IRQ domain\n"); + irq_domain_remove(msi->dev_domain); + return -ENOMEM; + } +#endif + return 0; +} + +static int nwl_pcie_init_irq_domain(struct nwl_pcie *pcie) +{ + struct device_node *node = pcie->dev->of_node; + struct device_node *legacy_intc_node; + + legacy_intc_node = of_get_next_child(node, NULL); + if (!legacy_intc_node) { + dev_err(pcie->dev, "No legacy intc node found\n"); + return -EINVAL; + } + + pcie->legacy_irq_domain = irq_domain_add_linear(legacy_intc_node, + INTX_NUM, + &legacy_domain_ops, + pcie); + + if (!pcie->legacy_irq_domain) { + dev_err(pcie->dev, "failed to create IRQ domain\n"); + return -ENOMEM; + } + + nwl_pcie_init_msi_irq_domain(pcie); + return 0; +} + +static int nwl_pcie_enable_msi(struct nwl_pcie *pcie, struct pci_bus *bus) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + struct nwl_msi *msi = &pcie->msi; + unsigned long base; + int ret; + int size = BITS_TO_LONGS(INT_PCI_MSI_NR) * sizeof(long); + + mutex_init(&msi->lock); + + msi->bitmap = kzalloc(size, GFP_KERNEL); + if (!msi->bitmap) + return -ENOMEM; + + /* Get msi_1 IRQ number */ + msi->irq_msi1 = platform_get_irq_byname(pdev, "msi1"); + if (msi->irq_msi1 < 0) { + dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi1); + ret = -EINVAL; + goto err; + } + + irq_set_chained_handler_and_data(msi->irq_msi1, + nwl_pcie_msi_handler_high, pcie); + + /* Get msi_0 IRQ number */ + msi->irq_msi0 = platform_get_irq_byname(pdev, "msi0"); + if (msi->irq_msi0 < 0) { + dev_err(&pdev->dev, "failed to get IRQ#%d\n", msi->irq_msi0); + ret = -EINVAL; + goto err; + } + + irq_set_chained_handler_and_data(msi->irq_msi0, + nwl_pcie_msi_handler_low, pcie); + + /* Check for msii_present bit */ + ret = nwl_bridge_readl(pcie, I_MSII_CAPABILITIES) & MSII_PRESENT; + if (!ret) { + dev_err(pcie->dev, "MSI not present\n"); + ret = -EIO; + goto err; + } + + /* Enable MSII */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) | + MSII_ENABLE, I_MSII_CONTROL); + + /* Enable MSII status */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, I_MSII_CONTROL) | + MSII_STATUS_ENABLE, I_MSII_CONTROL); + + /* setup AFI/FPCI range */ + base = pcie->phys_pcie_reg_base; + nwl_bridge_writel(pcie, lower_32_bits(base), I_MSII_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(base), I_MSII_BASE_HI); + + /* + * For high range MSI interrupts: disable, clear any pending, + * and enable + */ + nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_HI) & + MSGF_MSI_SR_HI_MASK, MSGF_MSI_STATUS_HI); + + nwl_bridge_writel(pcie, MSGF_MSI_SR_HI_MASK, MSGF_MSI_MASK_HI); + + /* + * For low range MSI interrupts: disable, clear any pending, + * and enable + */ + nwl_bridge_writel(pcie, (u32)~MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MSI_STATUS_LO) & + MSGF_MSI_SR_LO_MASK, MSGF_MSI_STATUS_LO); + + nwl_bridge_writel(pcie, MSGF_MSI_SR_LO_MASK, MSGF_MSI_MASK_LO); + + return 0; +err: + kfree(msi->bitmap); + msi->bitmap = NULL; + return ret; +} + +static int nwl_pcie_bridge_init(struct nwl_pcie *pcie) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + u32 breg_val, ecam_val, first_busno = 0; + int err; + + breg_val = nwl_bridge_readl(pcie, E_BREG_CAPABILITIES) & BREG_PRESENT; + if (!breg_val) { + dev_err(pcie->dev, "BREG is not present\n"); + return breg_val; + } + + /* Write bridge_off to breg base */ + nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_breg_base), + E_BREG_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_breg_base), + E_BREG_BASE_HI); + + /* Enable BREG */ + nwl_bridge_writel(pcie, ~BREG_ENABLE_FORCE & BREG_ENABLE, + E_BREG_CONTROL); + + /* Disable DMA channel registers */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_PCIE_RX0) | + CFG_DMA_REG_BAR, BRCFG_PCIE_RX0); + + /* Enable Ingress subtractive decode translation */ + nwl_bridge_writel(pcie, SET_ISUB_CONTROL, I_ISUB_CONTROL); + + /* Enable msg filtering details */ + nwl_bridge_writel(pcie, CFG_ENABLE_MSG_FILTER_MASK, + BRCFG_PCIE_RX_MSG_FILTER); + + err = nwl_wait_for_link(pcie); + if (err) + return err; + + ecam_val = nwl_bridge_readl(pcie, E_ECAM_CAPABILITIES) & E_ECAM_PRESENT; + if (!ecam_val) { + dev_err(pcie->dev, "ECAM is not present\n"); + return ecam_val; + } + + /* Enable ECAM */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) | + E_ECAM_CR_ENABLE, E_ECAM_CONTROL); + + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, E_ECAM_CONTROL) | + (pcie->ecam_value << E_ECAM_SIZE_SHIFT), + E_ECAM_CONTROL); + + nwl_bridge_writel(pcie, lower_32_bits(pcie->phys_ecam_base), + E_ECAM_BASE_LO); + nwl_bridge_writel(pcie, upper_32_bits(pcie->phys_ecam_base), + E_ECAM_BASE_HI); + + /* Get bus range */ + ecam_val = nwl_bridge_readl(pcie, E_ECAM_CONTROL); + pcie->last_busno = (ecam_val & E_ECAM_SIZE_LOC) >> E_ECAM_SIZE_SHIFT; + /* Write primary, secondary and subordinate bus numbers */ + ecam_val = first_busno; + ecam_val |= (first_busno + 1) << 8; + ecam_val |= (pcie->last_busno << E_ECAM_SIZE_SHIFT); + writel(ecam_val, (pcie->ecam_base + PCI_PRIMARY_BUS)); + + if (nwl_pcie_link_up(pcie)) + dev_info(pcie->dev, "Link is UP\n"); + else + dev_info(pcie->dev, "Link is DOWN\n"); + + /* Get misc IRQ number */ + pcie->irq_misc = platform_get_irq_byname(pdev, "misc"); + if (pcie->irq_misc < 0) { + dev_err(&pdev->dev, "failed to get misc IRQ %d\n", + pcie->irq_misc); + return -EINVAL; + } + + err = devm_request_irq(pcie->dev, pcie->irq_misc, + nwl_pcie_misc_handler, IRQF_SHARED, + "nwl_pcie:misc", pcie); + if (err) { + dev_err(pcie->dev, "fail to register misc IRQ#%d\n", + pcie->irq_misc); + return err; + } + + /* Disable all misc interrupts */ + nwl_bridge_writel(pcie, (u32)~MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK); + + /* Clear pending misc interrupts */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_MISC_STATUS) & + MSGF_MISC_SR_MASKALL, MSGF_MISC_STATUS); + + /* Enable all misc interrupts */ + nwl_bridge_writel(pcie, MSGF_MISC_SR_MASKALL, MSGF_MISC_MASK); + + + /* Disable all legacy interrupts */ + nwl_bridge_writel(pcie, (u32)~MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK); + + /* Clear pending legacy interrupts */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, MSGF_LEG_STATUS) & + MSGF_LEG_SR_MASKALL, MSGF_LEG_STATUS); + + /* Enable all legacy interrupts */ + nwl_bridge_writel(pcie, MSGF_LEG_SR_MASKALL, MSGF_LEG_MASK); + + /* Enable the bridge config interrupt */ + nwl_bridge_writel(pcie, nwl_bridge_readl(pcie, BRCFG_INTERRUPT) | + BRCFG_INTERRUPT_MASK, BRCFG_INTERRUPT); + + return 0; +} + +static int nwl_pcie_parse_dt(struct nwl_pcie *pcie, + struct platform_device *pdev) +{ + struct device_node *node = pcie->dev->of_node; + struct resource *res; + const char *type; + + /* Check for device type */ + type = of_get_property(node, "device_type", NULL); + if (!type || strcmp(type, "pci")) { + dev_err(pcie->dev, "invalid \"device_type\" %s\n", type); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "breg"); + pcie->breg_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->breg_base)) + return PTR_ERR(pcie->breg_base); + pcie->phys_breg_base = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcireg"); + pcie->pcireg_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->pcireg_base)) + return PTR_ERR(pcie->pcireg_base); + pcie->phys_pcie_reg_base = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cfg"); + pcie->ecam_base = devm_ioremap_resource(pcie->dev, res); + if (IS_ERR(pcie->ecam_base)) + return PTR_ERR(pcie->ecam_base); + pcie->phys_ecam_base = res->start; + + /* Get intx IRQ number */ + pcie->irq_intx = platform_get_irq_byname(pdev, "intx"); + if (pcie->irq_intx < 0) { + dev_err(&pdev->dev, "failed to get intx IRQ %d\n", + pcie->irq_intx); + return -EINVAL; + } + + irq_set_chained_handler_and_data(pcie->irq_intx, + nwl_pcie_leg_handler, pcie); + + return 0; +} + +static const struct of_device_id nwl_pcie_of_match[] = { + { .compatible = "xlnx,nwl-pcie-2.11", }, + {} +}; + +static int nwl_pcie_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct nwl_pcie *pcie; + struct pci_bus *bus; + struct pci_bus *child; + int err; + resource_size_t iobase = 0; + LIST_HEAD(res); + + pcie = devm_kzalloc(&pdev->dev, sizeof(*pcie), GFP_KERNEL); + if (!pcie) + return -ENOMEM; + + pcie->dev = &pdev->dev; + pcie->ecam_value = NWL_ECAM_VALUE_DEFAULT; + + err = nwl_pcie_parse_dt(pcie, pdev); + if (err) { + dev_err(pcie->dev, "Parsing DT failed\n"); + return err; + } + + err = nwl_pcie_bridge_init(pcie); + if (err) { + dev_err(pcie->dev, "HW Initalization failed\n"); + return err; + } + + err = of_pci_get_host_bridge_resources(node, 0, 0xff, &res, &iobase); + if (err) { + pr_err("Getting bridge resources failed\n"); + return err; + } + + err = nwl_pcie_init_irq_domain(pcie); + if (err) { + dev_err(pcie->dev, "Failed creating IRQ Domain\n"); + return err; + } + + bus = pci_create_root_bus(&pdev->dev, pcie->root_busno, + &nwl_pcie_ops, pcie, &res); + if (!bus) + return -ENOMEM; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + err = nwl_pcie_enable_msi(pcie, bus); + if (err < 0) { + dev_err(&pdev->dev, + "failed to enable MSI support: %d\n", err); + return err; + } + } + pci_scan_child_bus(bus); + pci_assign_unassigned_bus_resources(bus); + list_for_each_entry(child, &bus->children, node) + pcie_bus_configure_settings(child); + pci_bus_add_devices(bus); + platform_set_drvdata(pdev, pcie); + return 0; +} + +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", + .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"); -- cgit v1.2.3-59-g8ed1b From 7149b9fdaa5e160a7ee2af9014d9af77e87d9689 Mon Sep 17 00:00:00 2001 From: David Daney Date: Fri, 11 Mar 2016 15:18:38 -0600 Subject: PCI: generic: Move structure definitions to separate header file Move definitions for generic PCI host controller driver structures to a separate header file so we can share them with other drivers. No functional change intended. [bhelgaas: split into separate patch, changelog] Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas Acked-by: Arnd Bergmann Acked-by: Will Deacon --- drivers/pci/host/pci-host-common.h | 44 +++++++++++++++++++++++++++++++++++++ drivers/pci/host/pci-host-generic.c | 19 +--------------- 2 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 drivers/pci/host/pci-host-common.h (limited to 'drivers') diff --git a/drivers/pci/host/pci-host-common.h b/drivers/pci/host/pci-host-common.h new file mode 100644 index 000000000000..8c3c1491eaae --- /dev/null +++ b/drivers/pci/host/pci-host-common.h @@ -0,0 +1,44 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon + */ + +#ifndef _PCI_HOST_COMMON_H +#define _PCI_HOST_COMMON_H + +#include +#include + +struct gen_pci_cfg_bus_ops { + u32 bus_shift; + struct pci_ops ops; +}; + +struct gen_pci_cfg_windows { + struct resource res; + struct resource *bus_range; + void __iomem **win; + + struct gen_pci_cfg_bus_ops *ops; +}; + +struct gen_pci { + struct pci_host_bridge host; + struct gen_pci_cfg_windows cfg; + struct list_head resources; +}; + +#endif /* _PCI_HOST_COMMON_H */ diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c index 1652bc70b145..89e25b2a0bf9 100644 --- a/drivers/pci/host/pci-host-generic.c +++ b/drivers/pci/host/pci-host-generic.c @@ -25,24 +25,7 @@ #include #include -struct gen_pci_cfg_bus_ops { - u32 bus_shift; - struct pci_ops ops; -}; - -struct gen_pci_cfg_windows { - struct resource res; - struct resource *bus_range; - void __iomem **win; - - struct gen_pci_cfg_bus_ops *ops; -}; - -struct gen_pci { - struct pci_host_bridge host; - struct gen_pci_cfg_windows cfg; - struct list_head resources; -}; +#include "pci-host-common.h" static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus, unsigned int devfn, -- cgit v1.2.3-59-g8ed1b From d51b371087d7198c733d2ef3c4db8165aee4de4e Mon Sep 17 00:00:00 2001 From: David Daney Date: Fri, 11 Mar 2016 15:25:13 -0600 Subject: PCI: generic: Add pci_host_common_probe(), based on gen_pci_probe() Factor gen_pci_probe(), moving most of it into pci_host_common_probe() where it can be shared with other drivers that have slightly different config accessors. No functional change intended. [bhelgaas: split into separate patch, changelog] Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas Acked-by: Arnd Bergmann Acked-by: Will Deacon --- drivers/pci/host/pci-host-generic.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c index 89e25b2a0bf9..d392b1e42912 100644 --- a/drivers/pci/host/pci-host-generic.c +++ b/drivers/pci/host/pci-host-generic.c @@ -182,19 +182,15 @@ static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) return 0; } -static int gen_pci_probe(struct platform_device *pdev) +static int pci_host_common_probe(struct platform_device *pdev, + struct gen_pci *pci) { int err; const char *type; - const struct of_device_id *of_id; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); struct pci_bus *bus, *child; - if (!pci) - return -ENOMEM; - type = of_get_property(np, "device_type", NULL); if (!type || strcmp(type, "pci")) { dev_err(dev, "invalid \"device_type\" %s\n", type); @@ -203,8 +199,6 @@ static int gen_pci_probe(struct platform_device *pdev) of_pci_check_probe_only(); - of_id = of_match_node(gen_pci_of_match, np); - pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; pci->host.dev.parent = dev; INIT_LIST_HEAD(&pci->host.windows); INIT_LIST_HEAD(&pci->resources); @@ -247,6 +241,21 @@ static int gen_pci_probe(struct platform_device *pdev) return 0; } +static int gen_pci_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + + if (!pci) + return -ENOMEM; + + of_id = of_match_node(gen_pci_of_match, dev->of_node); + pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; + + return pci_host_common_probe(pdev, pci); +} + static struct platform_driver gen_pci_driver = { .driver = { .name = "pci-host-generic", -- cgit v1.2.3-59-g8ed1b From 4e64dbe226e707b442d45c2096dde2090f34f84d Mon Sep 17 00:00:00 2001 From: David Daney Date: Fri, 11 Mar 2016 15:35:55 -0600 Subject: PCI: generic: Expose pci_host_common_probe() for use by other drivers Move pci_host_common_probe() and associated functions to pci-host-common.c, where it can be shared with other drivers. Make it public (not static) and update Kconfig and Makefile to build it. No functional change intended. [bhelgaas: split into separate patch, changelog] Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas Acked-by: Arnd Bergmann Acked-by: Will Deacon --- MAINTAINERS | 1 + drivers/pci/host/Kconfig | 4 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-host-common.c | 194 ++++++++++++++++++++++++++++++++++++ drivers/pci/host/pci-host-common.h | 3 + drivers/pci/host/pci-host-generic.c | 165 ------------------------------ 6 files changed, 203 insertions(+), 165 deletions(-) create mode 100644 drivers/pci/host/pci-host-common.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 30aca4aa5467..73c5bde6d167 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8373,6 +8373,7 @@ L: linux-pci@vger.kernel.org L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/devicetree/bindings/pci/host-generic-pci.txt +F: drivers/pci/host/pci-host-common.c F: drivers/pci/host/pci-host-generic.c PCI DRIVER FOR INTEL VOLUME MANAGEMENT DEVICE (VMD) diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a605426538..65709b4bb3a4 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -53,9 +53,13 @@ config PCI_RCAR_GEN2_PCIE help Say Y here if you want PCIe controller support on R-Car Gen2 SoCs. +config PCI_HOST_COMMON + bool + config PCI_HOST_GENERIC bool "Generic PCI host controller" depends on (ARM || ARM64) && OF + select PCI_HOST_COMMON help Say Y here if you want to support a simple generic PCI host controller, such as the one emulated by kvmtool. diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 7b2f20c6ccc6..3b24af860284 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o obj-$(CONFIG_PCI_RCAR_GEN2_PCIE) += pcie-rcar.o +obj-$(CONFIG_PCI_HOST_COMMON) += pci-host-common.o obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o diff --git a/drivers/pci/host/pci-host-common.c b/drivers/pci/host/pci-host-common.c new file mode 100644 index 000000000000..e9f850f07968 --- /dev/null +++ b/drivers/pci/host/pci-host-common.c @@ -0,0 +1,194 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2014 ARM Limited + * + * Author: Will Deacon + */ + +#include +#include +#include +#include +#include + +#include "pci-host-common.h" + +static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) +{ + pci_free_resource_list(&pci->resources); +} + +static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) +{ + int err, res_valid = 0; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + resource_size_t iobase; + struct resource_entry *win; + + err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, + &iobase); + if (err) + return err; + + resource_list_for_each_entry(win, &pci->resources) { + struct resource *parent, *res = win->res; + + switch (resource_type(res)) { + case IORESOURCE_IO: + parent = &ioport_resource; + err = pci_remap_iospace(res, iobase); + if (err) { + dev_warn(dev, "error %d: failed to map resource %pR\n", + err, res); + continue; + } + break; + case IORESOURCE_MEM: + parent = &iomem_resource; + res_valid |= !(res->flags & IORESOURCE_PREFETCH); + break; + case IORESOURCE_BUS: + pci->cfg.bus_range = res; + default: + continue; + } + + err = devm_request_resource(dev, parent, res); + if (err) + goto out_release_res; + } + + if (!res_valid) { + dev_err(dev, "non-prefetchable memory resource required\n"); + err = -EINVAL; + goto out_release_res; + } + + return 0; + +out_release_res: + gen_pci_release_of_pci_ranges(pci); + return err; +} + +static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) +{ + int err; + u8 bus_max; + resource_size_t busn; + struct resource *bus_range; + struct device *dev = pci->host.dev.parent; + struct device_node *np = dev->of_node; + u32 sz = 1 << pci->cfg.ops->bus_shift; + + err = of_address_to_resource(np, 0, &pci->cfg.res); + if (err) { + dev_err(dev, "missing \"reg\" property\n"); + return err; + } + + /* Limit the bus-range to fit within reg */ + bus_max = pci->cfg.bus_range->start + + (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; + pci->cfg.bus_range->end = min_t(resource_size_t, + pci->cfg.bus_range->end, bus_max); + + pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range), + sizeof(*pci->cfg.win), GFP_KERNEL); + if (!pci->cfg.win) + return -ENOMEM; + + /* Map our Configuration Space windows */ + if (!devm_request_mem_region(dev, pci->cfg.res.start, + resource_size(&pci->cfg.res), + "Configuration Space")) + return -ENOMEM; + + bus_range = pci->cfg.bus_range; + for (busn = bus_range->start; busn <= bus_range->end; ++busn) { + u32 idx = busn - bus_range->start; + + pci->cfg.win[idx] = devm_ioremap(dev, + pci->cfg.res.start + idx * sz, + sz); + if (!pci->cfg.win[idx]) + return -ENOMEM; + } + + return 0; +} + +int pci_host_common_probe(struct platform_device *pdev, + struct gen_pci *pci) +{ + int err; + const char *type; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct pci_bus *bus, *child; + + type = of_get_property(np, "device_type", NULL); + if (!type || strcmp(type, "pci")) { + dev_err(dev, "invalid \"device_type\" %s\n", type); + return -EINVAL; + } + + of_pci_check_probe_only(); + + pci->host.dev.parent = dev; + INIT_LIST_HEAD(&pci->host.windows); + INIT_LIST_HEAD(&pci->resources); + + /* Parse our PCI ranges and request their resources */ + err = gen_pci_parse_request_of_pci_ranges(pci); + if (err) + return err; + + /* Parse and map our Configuration Space windows */ + err = gen_pci_parse_map_cfg_windows(pci); + if (err) { + gen_pci_release_of_pci_ranges(pci); + return err; + } + + /* Do not reassign resources if probe only */ + if (!pci_has_flag(PCI_PROBE_ONLY)) + pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); + + + bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start, + &pci->cfg.ops->ops, pci, &pci->resources); + if (!bus) { + dev_err(dev, "Scanning rootbus failed"); + return -ENODEV; + } + + pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); + + if (!pci_has_flag(PCI_PROBE_ONLY)) { + 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); + return 0; +} + +MODULE_DESCRIPTION("Generic PCI host driver common code"); +MODULE_AUTHOR("Will Deacon "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-host-common.h b/drivers/pci/host/pci-host-common.h index 8c3c1491eaae..09f3fa0a55d7 100644 --- a/drivers/pci/host/pci-host-common.h +++ b/drivers/pci/host/pci-host-common.h @@ -41,4 +41,7 @@ struct gen_pci { struct list_head resources; }; +int pci_host_common_probe(struct platform_device *pdev, + struct gen_pci *pci); + #endif /* _PCI_HOST_COMMON_H */ diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c index d392b1e42912..e8aa78faa16d 100644 --- a/drivers/pci/host/pci-host-generic.c +++ b/drivers/pci/host/pci-host-generic.c @@ -76,171 +76,6 @@ static const struct of_device_id gen_pci_of_match[] = { }; MODULE_DEVICE_TABLE(of, gen_pci_of_match); -static void gen_pci_release_of_pci_ranges(struct gen_pci *pci) -{ - pci_free_resource_list(&pci->resources); -} - -static int gen_pci_parse_request_of_pci_ranges(struct gen_pci *pci) -{ - int err, res_valid = 0; - struct device *dev = pci->host.dev.parent; - struct device_node *np = dev->of_node; - resource_size_t iobase; - struct resource_entry *win; - - err = of_pci_get_host_bridge_resources(np, 0, 0xff, &pci->resources, - &iobase); - if (err) - return err; - - resource_list_for_each_entry(win, &pci->resources) { - struct resource *parent, *res = win->res; - - switch (resource_type(res)) { - case IORESOURCE_IO: - parent = &ioport_resource; - err = pci_remap_iospace(res, iobase); - if (err) { - dev_warn(dev, "error %d: failed to map resource %pR\n", - err, res); - continue; - } - break; - case IORESOURCE_MEM: - parent = &iomem_resource; - res_valid |= !(res->flags & IORESOURCE_PREFETCH); - break; - case IORESOURCE_BUS: - pci->cfg.bus_range = res; - default: - continue; - } - - err = devm_request_resource(dev, parent, res); - if (err) - goto out_release_res; - } - - if (!res_valid) { - dev_err(dev, "non-prefetchable memory resource required\n"); - err = -EINVAL; - goto out_release_res; - } - - return 0; - -out_release_res: - gen_pci_release_of_pci_ranges(pci); - return err; -} - -static int gen_pci_parse_map_cfg_windows(struct gen_pci *pci) -{ - int err; - u8 bus_max; - resource_size_t busn; - struct resource *bus_range; - struct device *dev = pci->host.dev.parent; - struct device_node *np = dev->of_node; - u32 sz = 1 << pci->cfg.ops->bus_shift; - - err = of_address_to_resource(np, 0, &pci->cfg.res); - if (err) { - dev_err(dev, "missing \"reg\" property\n"); - return err; - } - - /* Limit the bus-range to fit within reg */ - bus_max = pci->cfg.bus_range->start + - (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1; - pci->cfg.bus_range->end = min_t(resource_size_t, - pci->cfg.bus_range->end, bus_max); - - pci->cfg.win = devm_kcalloc(dev, resource_size(pci->cfg.bus_range), - sizeof(*pci->cfg.win), GFP_KERNEL); - if (!pci->cfg.win) - return -ENOMEM; - - /* Map our Configuration Space windows */ - if (!devm_request_mem_region(dev, pci->cfg.res.start, - resource_size(&pci->cfg.res), - "Configuration Space")) - return -ENOMEM; - - bus_range = pci->cfg.bus_range; - for (busn = bus_range->start; busn <= bus_range->end; ++busn) { - u32 idx = busn - bus_range->start; - - pci->cfg.win[idx] = devm_ioremap(dev, - pci->cfg.res.start + idx * sz, - sz); - if (!pci->cfg.win[idx]) - return -ENOMEM; - } - - return 0; -} - -static int pci_host_common_probe(struct platform_device *pdev, - struct gen_pci *pci) -{ - int err; - const char *type; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct pci_bus *bus, *child; - - type = of_get_property(np, "device_type", NULL); - if (!type || strcmp(type, "pci")) { - dev_err(dev, "invalid \"device_type\" %s\n", type); - return -EINVAL; - } - - of_pci_check_probe_only(); - - pci->host.dev.parent = dev; - INIT_LIST_HEAD(&pci->host.windows); - INIT_LIST_HEAD(&pci->resources); - - /* Parse our PCI ranges and request their resources */ - err = gen_pci_parse_request_of_pci_ranges(pci); - if (err) - return err; - - /* Parse and map our Configuration Space windows */ - err = gen_pci_parse_map_cfg_windows(pci); - if (err) { - gen_pci_release_of_pci_ranges(pci); - return err; - } - - /* Do not reassign resources if probe only */ - if (!pci_has_flag(PCI_PROBE_ONLY)) - pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS); - - - bus = pci_scan_root_bus(dev, pci->cfg.bus_range->start, - &pci->cfg.ops->ops, pci, &pci->resources); - if (!bus) { - dev_err(dev, "Scanning rootbus failed"); - return -ENODEV; - } - - pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci); - - if (!pci_has_flag(PCI_PROBE_ONLY)) { - 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); - return 0; -} - static int gen_pci_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; -- cgit v1.2.3-59-g8ed1b From f12b76e56ada6e276a3d45b60c4e26e2dda7e547 Mon Sep 17 00:00:00 2001 From: David Daney Date: Fri, 4 Mar 2016 14:31:47 -0800 Subject: PCI: thunder: Add PCIe host driver for ThunderX processors The root complexes used to access off-chip PCIe devices (called PEM units in the hardware manuals) on some Cavium ThunderX processors require quirky access methods for the config space of the PCIe bridge. Add a driver to provide these config space accessor functions. Use the pci-host-common code to configure the PCI machinery. Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring Acked-by: Arnd Bergmann --- .../devicetree/bindings/pci/pci-thunder-pem.txt | 43 +++ MAINTAINERS | 8 + drivers/pci/host/Kconfig | 7 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-thunder-pem.c | 346 +++++++++++++++++++++ 5 files changed, 405 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pci-thunder-pem.txt create mode 100644 drivers/pci/host/pci-thunder-pem.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/pci/pci-thunder-pem.txt b/Documentation/devicetree/bindings/pci/pci-thunder-pem.txt new file mode 100644 index 000000000000..f131faea3b7c --- /dev/null +++ b/Documentation/devicetree/bindings/pci/pci-thunder-pem.txt @@ -0,0 +1,43 @@ +* ThunderX PEM PCIe host controller + +Firmware-initialized PCI host controller found on some Cavium +ThunderX processors. + +The properties and their meanings are identical to those described in +host-generic-pci.txt except as listed below. + +Properties of the host controller node that differ from +host-generic-pci.txt: + +- compatible : Must be "cavium,pci-host-thunder-pem" + +- reg : Two entries: First the configuration space for down + stream devices base address and size, as accessed + from the parent bus. Second, the register bank of + the PEM device PCIe bridge. + +Example: + + pci@87e0,c2000000 { + compatible = "cavium,pci-host-thunder-pem"; + device_type = "pci"; + msi-parent = <&its>; + msi-map = <0 &its 0x10000 0x10000>; + bus-range = <0x8f 0xc7>; + #size-cells = <2>; + #address-cells = <3>; + + reg = <0x8880 0x8f000000 0x0 0x39000000>, /* Configuration space */ + <0x87e0 0xc2000000 0x0 0x00010000>; /* PEM space */ + ranges = <0x01000000 0x00 0x00020000 0x88b0 0x00020000 0x00 0x00010000>, /* I/O */ + <0x03000000 0x00 0x10000000 0x8890 0x10000000 0x0f 0xf0000000>, /* mem64 */ + <0x43000000 0x10 0x00000000 0x88a0 0x00000000 0x10 0x00000000>, /* mem64-pref */ + <0x03000000 0x87e0 0xc2f00000 0x87e0 0xc2000000 0x00 0x00100000>; /* mem64 PEM BAR4 */ + + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &gic0 0 0 0 24 4>, /* INTA */ + <0 0 0 2 &gic0 0 0 0 25 4>, /* INTB */ + <0 0 0 3 &gic0 0 0 0 26 4>, /* INTC */ + <0 0 0 4 &gic0 0 0 0 27 4>; /* INTD */ + }; diff --git a/MAINTAINERS b/MAINTAINERS index 73c5bde6d167..1aa8f82837ea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8419,6 +8419,14 @@ L: linux-arm-msm@vger.kernel.org S: Maintained F: drivers/pci/host/*qcom* +PCIE DRIVER FOR CAVIUM THUNDERX +M: David Daney +L: linux-pci@vger.kernel.org +L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) +S: Supported +F: Documentation/devicetree/bindings/pci/pci-thunder-* +F: drivers/pci/host/pci-thunder-* + PCMCIA SUBSYSTEM P: Linux PCMCIA Team L: linux-pcmcia@lists.infradead.org diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 65709b4bb3a4..184df22e4a69 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -195,4 +195,11 @@ config PCIE_QCOM PCIe controller uses the Designware core plus Qualcomm-specific hardware wrappers. +config PCI_HOST_THUNDER_PEM + bool "Cavium Thunder PCIe controller to off-chip devices" + depends on OF && ARM64 + select PCI_HOST_COMMON + help + Say Y here if you want PCIe support for CN88XX Cavium Thunder SoCs. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 3b24af860284..890317234dae 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCI_HISI) += pcie-hisi.o obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o +obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o diff --git a/drivers/pci/host/pci-thunder-pem.c b/drivers/pci/host/pci-thunder-pem.c new file mode 100644 index 000000000000..cabb92a514ac --- /dev/null +++ b/drivers/pci/host/pci-thunder-pem.c @@ -0,0 +1,346 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Copyright (C) 2015 - 2016 Cavium, Inc. + */ + +#include +#include +#include +#include +#include + +#include "pci-host-common.h" + +#define PEM_CFG_WR 0x28 +#define PEM_CFG_RD 0x30 + +struct thunder_pem_pci { + struct gen_pci gen_pci; + u32 ea_entry[3]; + void __iomem *pem_reg_base; +}; + +static void __iomem *thunder_pem_map_bus(struct pci_bus *bus, + unsigned int devfn, int where) +{ + struct gen_pci *pci = bus->sysdata; + resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 16) | where); +} + +static int thunder_pem_bridge_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + u64 read_val; + struct thunder_pem_pci *pem_pci; + struct gen_pci *pci = bus->sysdata; + + pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci); + + if (devfn != 0 || where >= 2048) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + /* + * 32-bit accesses only. Write the address to the low order + * bits of PEM_CFG_RD, then trigger the read by reading back. + * The config data lands in the upper 32-bits of PEM_CFG_RD. + */ + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + + /* + * The config space contains some garbage, fix it up. Also + * synthesize an EA capability for the BAR used by MSI-X. + */ + switch (where & ~3) { + case 0x40: + read_val &= 0xffff00ff; + read_val |= 0x00007000; /* Skip MSI CAP */ + break; + case 0x70: /* Express Cap */ + /* PME interrupt on vector 2*/ + read_val |= (2u << 25); + break; + case 0xb0: /* MSI-X Cap */ + /* TableSize=4, Next Cap is EA */ + read_val &= 0xc00000ff; + read_val |= 0x0003bc00; + break; + case 0xb4: + /* Table offset=0, BIR=0 */ + read_val = 0x00000000; + break; + case 0xb8: + /* BPA offset=0xf0000, BIR=0 */ + read_val = 0x000f0000; + break; + case 0xbc: + /* EA, 1 entry, no next Cap */ + read_val = 0x00010014; + break; + case 0xc0: + /* DW2 for type-1 */ + read_val = 0x00000000; + break; + case 0xc4: + /* Entry BEI=0, PP=0x00, SP=0xff, ES=3 */ + read_val = 0x80ff0003; + break; + case 0xc8: + read_val = pem_pci->ea_entry[0]; + break; + case 0xcc: + read_val = pem_pci->ea_entry[1]; + break; + case 0xd0: + read_val = pem_pci->ea_entry[2]; + break; + default: + break; + } + read_val >>= (8 * (where & 3)); + switch (size) { + case 1: + read_val &= 0xff; + break; + case 2: + read_val &= 0xffff; + break; + default: + break; + } + *val = read_val; + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pem_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct gen_pci *pci = bus->sysdata; + + if (bus->number < pci->cfg.bus_range->start || + bus->number > pci->cfg.bus_range->end) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* + * The first device on the bus is the PEM PCIe bridge. + * Special case its config access. + */ + if (bus->number == pci->cfg.bus_range->start) + return thunder_pem_bridge_read(bus, devfn, where, size, val); + + return pci_generic_config_read(bus, devfn, where, size, val); +} + +/* + * Some of the w1c_bits below also include read-only or non-writable + * reserved bits, this makes the code simpler and is OK as the bits + * are not affected by writing zeros to them. + */ +static u32 thunder_pem_bridge_w1c_bits(int where) +{ + u32 w1c_bits = 0; + + switch (where & ~3) { + case 0x04: /* Command/Status */ + case 0x1c: /* Base and I/O Limit/Secondary Status */ + w1c_bits = 0xff000000; + break; + case 0x44: /* Power Management Control and Status */ + w1c_bits = 0xfffffe00; + break; + case 0x78: /* Device Control/Device Status */ + case 0x80: /* Link Control/Link Status */ + case 0x88: /* Slot Control/Slot Status */ + case 0x90: /* Root Status */ + case 0xa0: /* Link Control 2 Registers/Link Status 2 */ + w1c_bits = 0xffff0000; + break; + case 0x104: /* Uncorrectable Error Status */ + case 0x110: /* Correctable Error Status */ + case 0x130: /* Error Status */ + case 0x160: /* Link Control 4 */ + w1c_bits = 0xffffffff; + break; + default: + break; + } + return w1c_bits; +} + +static int thunder_pem_bridge_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct gen_pci *pci = bus->sysdata; + struct thunder_pem_pci *pem_pci; + u64 write_val, read_val; + u32 mask = 0; + + pem_pci = container_of(pci, struct thunder_pem_pci, gen_pci); + + if (devfn != 0 || where >= 2048) + return PCIBIOS_DEVICE_NOT_FOUND; + + /* + * 32-bit accesses only. If the write is for a size smaller + * than 32-bits, we must first read the 32-bit value and merge + * in the desired bits and then write the whole 32-bits back + * out. + */ + switch (size) { + case 1: + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + mask = ~(0xff << (8 * (where & 3))); + read_val &= mask; + val = (val & 0xff) << (8 * (where & 3)); + val |= (u32)read_val; + break; + case 2: + read_val = where & ~3ull; + writeq(read_val, pem_pci->pem_reg_base + PEM_CFG_RD); + read_val = readq(pem_pci->pem_reg_base + PEM_CFG_RD); + read_val >>= 32; + mask = ~(0xffff << (8 * (where & 3))); + read_val &= mask; + val = (val & 0xffff) << (8 * (where & 3)); + val |= (u32)read_val; + break; + default: + break; + } + + /* + * By expanding the write width to 32 bits, we may + * inadvertently hit some W1C bits that were not intended to + * be written. Calculate the mask that must be applied to the + * data to be written to avoid these cases. + */ + if (mask) { + u32 w1c_bits = thunder_pem_bridge_w1c_bits(where); + + if (w1c_bits) { + mask &= w1c_bits; + val &= ~mask; + } + } + + /* + * Low order bits are the config address, the high order 32 + * bits are the data to be written. + */ + write_val = where & ~3ull; + write_val |= (((u64)val) << 32); + writeq(write_val, pem_pci->pem_reg_base + PEM_CFG_WR); + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_pem_config_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + struct gen_pci *pci = bus->sysdata; + + if (bus->number < pci->cfg.bus_range->start || + bus->number > pci->cfg.bus_range->end) + return PCIBIOS_DEVICE_NOT_FOUND; + /* + * The first device on the bus is the PEM PCIe bridge. + * Special case its config access. + */ + if (bus->number == pci->cfg.bus_range->start) + return thunder_pem_bridge_write(bus, devfn, where, size, val); + + + return pci_generic_config_write(bus, devfn, where, size, val); +} + +static struct gen_pci_cfg_bus_ops thunder_pem_bus_ops = { + .bus_shift = 24, + .ops = { + .map_bus = thunder_pem_map_bus, + .read = thunder_pem_config_read, + .write = thunder_pem_config_write, + } +}; + +static const struct of_device_id thunder_pem_of_match[] = { + { .compatible = "cavium,pci-host-thunder-pem", + .data = &thunder_pem_bus_ops }, + + { }, +}; +MODULE_DEVICE_TABLE(of, thunder_pem_of_match); + +static int thunder_pem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + resource_size_t bar4_start; + struct resource *res_pem; + struct thunder_pem_pci *pem_pci; + + pem_pci = devm_kzalloc(dev, sizeof(*pem_pci), GFP_KERNEL); + if (!pem_pci) + return -ENOMEM; + + of_id = of_match_node(thunder_pem_of_match, dev->of_node); + pem_pci->gen_pci.cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; + + /* + * The second register range is the PEM bridge to the PCIe + * bus. It has a different config access method than those + * devices behind the bridge. + */ + res_pem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res_pem) { + dev_err(dev, "missing \"reg[1]\"property\n"); + return -EINVAL; + } + + pem_pci->pem_reg_base = devm_ioremap(dev, res_pem->start, 0x10000); + if (!pem_pci->pem_reg_base) + return -ENOMEM; + + /* + * The MSI-X BAR for the PEM and AER interrupts is located at + * a fixed offset from the PEM register base. Generate a + * fragment of the synthesized Enhanced Allocation capability + * structure here for the BAR. + */ + bar4_start = res_pem->start + 0xf00000; + pem_pci->ea_entry[0] = (u32)bar4_start | 2; + pem_pci->ea_entry[1] = (u32)(res_pem->end - bar4_start) & ~3u; + pem_pci->ea_entry[2] = (u32)(bar4_start >> 32); + + return pci_host_common_probe(pdev, &pem_pci->gen_pci); +} + +static struct platform_driver thunder_pem_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = thunder_pem_of_match, + }, + .probe = thunder_pem_probe, +}; +module_platform_driver(thunder_pem_driver); + +MODULE_DESCRIPTION("Thunder PEM PCIe host driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 7b6e7ba8e81a862e20d213d90aa5ba1e5a02aba6 Mon Sep 17 00:00:00 2001 From: David Daney Date: Fri, 4 Mar 2016 14:31:48 -0800 Subject: PCI: thunder: Add driver for ThunderX-pass{1,2} on-chip devices The cavium,pci-thunder-ecam devices are exactly ECAM-based PCI root complexes. These root complexes (loosely referred to as ECAM units in the hardware manuals) are used to access the Thunder on-chip devices. They are special in that all the BARs on devices behind these root complexes are at fixed addresses. Add a driver for these devices that synthesizes Enhanced Allocation (EA) capability entries for each BAR. Since this EA synthesis is needed for exactly two chip models, we can hard- code some assumptions about the device topology and the layout of the config space of specific DEVFNs in the driver. [bhelgaas: changelog, whitespace] Signed-off-by: David Daney Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring --- .../devicetree/bindings/pci/pci-thunder-ecam.txt | 30 ++ drivers/pci/host/Kconfig | 7 + drivers/pci/host/Makefile | 1 + drivers/pci/host/pci-thunder-ecam.c | 403 +++++++++++++++++++++ 4 files changed, 441 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt create mode 100644 drivers/pci/host/pci-thunder-ecam.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt b/Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt new file mode 100644 index 000000000000..f478874b79ce --- /dev/null +++ b/Documentation/devicetree/bindings/pci/pci-thunder-ecam.txt @@ -0,0 +1,30 @@ +* ThunderX PCI host controller for pass-1.x silicon + +Firmware-initialized PCI host controller to on-chip devices found on +some Cavium ThunderX processors. These devices have ECAM-based config +access, but the BARs are all at fixed addresses. We handle the fixed +addresses by synthesizing Enhanced Allocation (EA) capabilities for +these devices. + +The properties and their meanings are identical to those described in +host-generic-pci.txt except as listed below. + +Properties of the host controller node that differ from +host-generic-pci.txt: + +- compatible : Must be "cavium,pci-host-thunder-ecam" + +Example: + + pcie@84b000000000 { + compatible = "cavium,pci-host-thunder-ecam"; + device_type = "pci"; + msi-parent = <&its>; + msi-map = <0 &its 0x30000 0x10000>; + bus-range = <0 31>; + #size-cells = <2>; + #address-cells = <3>; + #stream-id-cells = <1>; + reg = <0x84b0 0x00000000 0 0x02000000>; /* Configuration space */ + ranges = <0x03000000 0x8180 0x00000000 0x8180 0x00000000 0x80 0x00000000>; /* mem ranges */ + }; diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 184df22e4a69..ad833a03afbc 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -202,4 +202,11 @@ config PCI_HOST_THUNDER_PEM help Say Y here if you want PCIe support for CN88XX Cavium Thunder SoCs. +config PCI_HOST_THUNDER_ECAM + bool "Cavium Thunder ECAM controller to on-chip devices on pass-1.x silicon" + depends on OF && ARM64 + select PCI_HOST_COMMON + help + Say Y here if you want ECAM support for CN88XX-Pass-1.x Cavium Thunder SoCs. + endmenu diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 890317234dae..d6af3ba870af 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -23,4 +23,5 @@ obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o obj-$(CONFIG_PCI_HISI) += pcie-hisi.o obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o +obj-$(CONFIG_PCI_HOST_THUNDER_ECAM) += pci-thunder-ecam.o obj-$(CONFIG_PCI_HOST_THUNDER_PEM) += pci-thunder-pem.o diff --git a/drivers/pci/host/pci-thunder-ecam.c b/drivers/pci/host/pci-thunder-ecam.c new file mode 100644 index 000000000000..d71935cb2678 --- /dev/null +++ b/drivers/pci/host/pci-thunder-ecam.c @@ -0,0 +1,403 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2015, 2016 Cavium, Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include "pci-host-common.h" + +/* Mapping is standard ECAM */ +static void __iomem *thunder_ecam_map_bus(struct pci_bus *bus, + unsigned int devfn, + int where) +{ + struct gen_pci *pci = bus->sysdata; + resource_size_t idx = bus->number - pci->cfg.bus_range->start; + + return pci->cfg.win[idx] + ((devfn << 12) | where); +} + +static void set_val(u32 v, int where, int size, u32 *val) +{ + int shift = (where & 3) * 8; + + pr_debug("set_val %04x: %08x\n", (unsigned)(where & ~3), v); + v >>= shift; + if (size == 1) + v &= 0xff; + else if (size == 2) + v &= 0xffff; + *val = v; +} + +static int handle_ea_bar(u32 e0, int bar, struct pci_bus *bus, + unsigned int devfn, int where, int size, u32 *val) +{ + void __iomem *addr; + u32 v; + + /* Entries are 16-byte aligned; bits[2,3] select word in entry */ + int where_a = where & 0xc; + + if (where_a == 0) { + set_val(e0, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0x4) { + addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + v &= ~0xf; + v |= 2; /* EA entry-1. Base-L */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0x8) { + u32 barl_orig; + u32 barl_rb; + + addr = bus->ops->map_bus(bus, devfn, bar); /* BAR 0 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + barl_orig = readl(addr + 0); + writel(0xffffffff, addr + 0); + barl_rb = readl(addr + 0); + writel(barl_orig, addr + 0); + /* zeros in unsettable bits */ + v = ~barl_rb & ~3; + v |= 0xc; /* EA entry-2. Offset-L */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc) { + addr = bus->ops->map_bus(bus, devfn, bar + 4); /* BAR 1 */ + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); /* EA entry-3. Base-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + return PCIBIOS_DEVICE_NOT_FOUND; +} + +static int thunder_ecam_p2_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + struct gen_pci *pci = bus->sysdata; + int where_a = where & ~3; + void __iomem *addr; + u32 node_bits; + u32 v; + + /* EA Base[63:32] may be missing some bits ... */ + switch (where_a) { + case 0xa8: + case 0xbc: + case 0xd0: + case 0xe4: + break; + default: + return pci_generic_config_read(bus, devfn, where, size, val); + } + + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + v = readl(addr); + + /* + * Bit 44 of the 64-bit Base must match the same bit in + * the config space access window. Since we are working with + * the high-order 32 bits, shift everything down by 32 bits. + */ + node_bits = (pci->cfg.res.start >> 32) & (1 << 12); + + v |= node_bits; + set_val(v, where, size, val); + + return PCIBIOS_SUCCESSFUL; +} + +static int thunder_ecam_config_read(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 *val) +{ + u32 v; + u32 vendor_device; + u32 class_rev; + void __iomem *addr; + int cfg_type; + int where_a = where & ~3; + + addr = bus->ops->map_bus(bus, devfn, 0xc); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + v = readl(addr); + + /* Check for non type-00 header */ + cfg_type = (v >> 16) & 0x7f; + + addr = bus->ops->map_bus(bus, devfn, 8); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + class_rev = readl(addr); + if (class_rev == 0xffffffff) + goto no_emulation; + + if ((class_rev & 0xff) >= 8) { + /* Pass-2 handling */ + if (cfg_type) + goto no_emulation; + return thunder_ecam_p2_config_read(bus, devfn, where, + size, val); + } + + /* + * All BARs have fixed addresses specified by the EA + * capability; they must return zero on read. + */ + if (cfg_type == 0 && + ((where >= 0x10 && where < 0x2c) || + (where >= 0x1a4 && where < 0x1bc))) { + /* BAR or SR-IOV BAR */ + *val = 0; + return PCIBIOS_SUCCESSFUL; + } + + addr = bus->ops->map_bus(bus, devfn, 0); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + + vendor_device = readl(addr); + if (vendor_device == 0xffffffff) + goto no_emulation; + + pr_debug("%04x:%04x - Fix pass#: %08x, where: %03x, devfn: %03x\n", + vendor_device & 0xffff, vendor_device >> 16, class_rev, + (unsigned) where, devfn); + + /* Check for non type-00 header */ + if (cfg_type == 0) { + bool has_msix; + bool is_nic = (vendor_device == 0xa01e177d); + bool is_tns = (vendor_device == 0xa01f177d); + + addr = bus->ops->map_bus(bus, devfn, 0x70); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + /* E_CAP */ + v = readl(addr); + has_msix = (v & 0xff00) != 0; + + if (!has_msix && where_a == 0x70) { + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xb0) { + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + if (v & 0xff00) + pr_err("Bad MSIX cap header: %08x\n", v); + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xbc) { + if (is_nic) + v = 0x40014; /* EA last in chain, 4 entries */ + else if (is_tns) + v = 0x30014; /* EA last in chain, 3 entries */ + else if (has_msix) + v = 0x20014; /* EA last in chain, 2 entries */ + else + v = 0x10014; /* EA last in chain, 1 entry */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a >= 0xc0 && where_a < 0xd0) + /* EA entry-0. PP=0, BAR0 Size:3 */ + return handle_ea_bar(0x80ff0003, + 0x10, bus, devfn, where, + size, val); + if (where_a >= 0xd0 && where_a < 0xe0 && has_msix) + /* EA entry-1. PP=0, BAR4 Size:3 */ + return handle_ea_bar(0x80ff0043, + 0x20, bus, devfn, where, + size, val); + if (where_a >= 0xe0 && where_a < 0xf0 && is_tns) + /* EA entry-2. PP=0, BAR2, Size:3 */ + return handle_ea_bar(0x80ff0023, + 0x18, bus, devfn, where, + size, val); + if (where_a >= 0xe0 && where_a < 0xf0 && is_nic) + /* EA entry-2. PP=4, VF_BAR0 (9), Size:3 */ + return handle_ea_bar(0x80ff0493, + 0x1a4, bus, devfn, where, + size, val); + if (where_a >= 0xf0 && where_a < 0x100 && is_nic) + /* EA entry-3. PP=4, VF_BAR4 (d), Size:3 */ + return handle_ea_bar(0x80ff04d3, + 0x1b4, bus, devfn, where, + size, val); + } else if (cfg_type == 1) { + bool is_rsl_bridge = devfn == 0x08; + bool is_rad_bridge = devfn == 0xa0; + bool is_zip_bridge = devfn == 0xa8; + bool is_dfa_bridge = devfn == 0xb0; + bool is_nic_bridge = devfn == 0x10; + + if (where_a == 0x70) { + addr = bus->ops->map_bus(bus, devfn, where_a); + if (!addr) { + *val = ~0; + return PCIBIOS_DEVICE_NOT_FOUND; + } + v = readl(addr); + if (v & 0xff00) + pr_err("Bad PCIe cap header: %08x\n", v); + v |= 0xbc00; /* next capability is EA at 0xbc */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xbc) { + if (is_nic_bridge) + v = 0x10014; /* EA last in chain, 1 entry */ + else + v = 0x00014; /* EA last in chain, no entries */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc0) { + if (is_rsl_bridge || is_nic_bridge) + v = 0x0101; /* subordinate:secondary = 1:1 */ + else if (is_rad_bridge) + v = 0x0202; /* subordinate:secondary = 2:2 */ + else if (is_zip_bridge) + v = 0x0303; /* subordinate:secondary = 3:3 */ + else if (is_dfa_bridge) + v = 0x0404; /* subordinate:secondary = 4:4 */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc4 && is_nic_bridge) { + /* Enabled, not-Write, SP=ff, PP=05, BEI=6, ES=4 */ + v = 0x80ff0564; + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xc8 && is_nic_bridge) { + v = 0x00000002; /* Base-L 64-bit */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xcc && is_nic_bridge) { + v = 0xfffffffe; /* MaxOffset-L 64-bit */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xd0 && is_nic_bridge) { + v = 0x00008430; /* NIC Base-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + if (where_a == 0xd4 && is_nic_bridge) { + v = 0x0000000f; /* MaxOffset-H */ + set_val(v, where, size, val); + return PCIBIOS_SUCCESSFUL; + } + } +no_emulation: + return pci_generic_config_read(bus, devfn, where, size, val); +} + +static int thunder_ecam_config_write(struct pci_bus *bus, unsigned int devfn, + int where, int size, u32 val) +{ + /* + * All BARs have fixed addresses; ignore BAR writes so they + * don't get corrupted. + */ + if ((where >= 0x10 && where < 0x2c) || + (where >= 0x1a4 && where < 0x1bc)) + /* BAR or SR-IOV BAR */ + return PCIBIOS_SUCCESSFUL; + + return pci_generic_config_write(bus, devfn, where, size, val); +} + +static struct gen_pci_cfg_bus_ops thunder_ecam_bus_ops = { + .bus_shift = 20, + .ops = { + .map_bus = thunder_ecam_map_bus, + .read = thunder_ecam_config_read, + .write = thunder_ecam_config_write, + } +}; + +static const struct of_device_id thunder_ecam_of_match[] = { + { .compatible = "cavium,pci-host-thunder-ecam", + .data = &thunder_ecam_bus_ops }, + + { }, +}; +MODULE_DEVICE_TABLE(of, thunder_ecam_of_match); + +static int thunder_ecam_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct gen_pci *pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + + if (!pci) + return -ENOMEM; + + of_id = of_match_node(thunder_ecam_of_match, dev->of_node); + pci->cfg.ops = (struct gen_pci_cfg_bus_ops *)of_id->data; + + return pci_host_common_probe(pdev, pci); +} + +static struct platform_driver thunder_ecam_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = thunder_ecam_of_match, + }, + .probe = thunder_ecam_probe, +}; +module_platform_driver(thunder_ecam_driver); + +MODULE_DESCRIPTION("Thunder ECAM PCI host driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From ac0c302a919ba7b68dbf274babdc08c83df6f532 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Sat, 12 Mar 2016 05:48:08 -0600 Subject: PCI: Remove arch-specific IORESOURCE_ROM_SHADOW size from sysfs When pci_create_sysfs_dev_files() created the "rom" sysfs file, it set the sysfs file size to the actual size of a ROM BAR, or if there was no ROM BAR but the platform provided a shadow copy in RAM, to 0x20000. 0x20000 is an arch-specific length that should not be baked into the PCI core. Every place that sets IORESOURCE_ROM_SHADOW also sets the size of the PCI_ROM_RESOURCE, so use the resource length always. Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 95d9e7bd933b..51d4dadcf552 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1356,7 +1356,7 @@ error: int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) { int retval; - int rom_size = 0; + int rom_size; struct bin_attribute *attr; if (!sysfs_initialized) @@ -1373,12 +1373,8 @@ int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) if (retval) goto err_config_file; - if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) - rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - else if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) - rom_size = 0x20000; - /* If the device has a ROM, try to expose it in sysfs. */ + rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); if (rom_size) { attr = kzalloc(sizeof(*attr), GFP_ATOMIC); if (!attr) { -- cgit v1.2.3-59-g8ed1b From f50dd8c3dae5703704441ab2255dff99c021a340 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 1 Mar 2016 11:13:30 -0600 Subject: PCI: Clean up pci_map_rom() whitespace Remove unnecessary indentation in pci_map_rom(). This is logically part of the previous patch; I split it out to make the critical changes in that patch more obvious. No functional change intended. Signed-off-by: Bjorn Helgaas --- drivers/pci/rom.c | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index 80e82b13292f..2a07f344616e 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -128,25 +128,24 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) loff_t start; void __iomem *rom; - if (res->flags & - (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) { - *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - return (void __iomem *)(unsigned long) - pci_resource_start(pdev, PCI_ROM_RESOURCE); - } else { - /* assign the ROM an address if it doesn't have one */ - if (res->parent == NULL && - pci_assign_resource(pdev, PCI_ROM_RESOURCE)) - return NULL; - start = pci_resource_start(pdev, PCI_ROM_RESOURCE); - *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - if (*size == 0) - return NULL; - - /* Enable ROM space decodes */ - if (pci_enable_rom(pdev)) - return NULL; - } + if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) { + *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + return (void __iomem *)(unsigned long) + pci_resource_start(pdev, PCI_ROM_RESOURCE); + } + + /* assign the ROM an address if it doesn't have one */ + if (res->parent == NULL && pci_assign_resource(pdev, PCI_ROM_RESOURCE)) + return NULL; + + start = pci_resource_start(pdev, PCI_ROM_RESOURCE); + *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); + if (*size == 0) + return NULL; + + /* Enable ROM space decodes */ + if (pci_enable_rom(pdev)) + return NULL; rom = ioremap(start, *size); if (!rom) { -- cgit v1.2.3-59-g8ed1b From d9c8bea179a6906a74ea42a2a162c4d1c6d9a16b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 2 Mar 2016 21:46:50 -0600 Subject: PCI: Remove unused IORESOURCE_ROM_COPY and IORESOURCE_ROM_BIOS_COPY The IORESOURCE_ROM_COPY and IORESOURCE_ROM_BIOS_COPY bits are unused. Remove them and code that depends on them. Signed-off-by: Bjorn Helgaas --- drivers/pci/remove.c | 1 - drivers/pci/rom.c | 31 +------------------------------ include/linux/ioport.h | 2 -- 3 files changed, 1 insertion(+), 33 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 8a280e9c2ad1..6b66329a6bff 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -7,7 +7,6 @@ static void pci_free_resources(struct pci_dev *dev) { int i; - pci_cleanup_rom(dev); for (i = 0; i < PCI_NUM_RESOURCES; i++) { struct resource *res = dev->resource + i; if (res->parent) diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index 2a07f344616e..06663d391b39 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -128,12 +128,6 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) loff_t start; void __iomem *rom; - if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) { - *size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - return (void __iomem *)(unsigned long) - pci_resource_start(pdev, PCI_ROM_RESOURCE); - } - /* assign the ROM an address if it doesn't have one */ if (res->parent == NULL && pci_assign_resource(pdev, PCI_ROM_RESOURCE)) return NULL; @@ -150,8 +144,7 @@ void __iomem *pci_map_rom(struct pci_dev *pdev, size_t *size) rom = ioremap(start, *size); if (!rom) { /* restore enable if ioremap fails */ - if (!(res->flags & (IORESOURCE_ROM_ENABLE | - IORESOURCE_ROM_COPY))) + if (!(res->flags & IORESOURCE_ROM_ENABLE)) pci_disable_rom(pdev); return NULL; } @@ -177,9 +170,6 @@ void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom) { struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; - if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_BIOS_COPY)) - return; - iounmap(rom); /* Disable again before continuing */ @@ -188,25 +178,6 @@ void pci_unmap_rom(struct pci_dev *pdev, void __iomem *rom) } EXPORT_SYMBOL(pci_unmap_rom); -/** - * pci_cleanup_rom - free the ROM copy created by pci_map_rom_copy - * @pdev: pointer to pci device struct - * - * Free the copied ROM if we allocated one. - */ -void pci_cleanup_rom(struct pci_dev *pdev) -{ - struct resource *res = &pdev->resource[PCI_ROM_RESOURCE]; - - if (res->flags & IORESOURCE_ROM_COPY) { - kfree((void *)(unsigned long)res->start); - res->flags |= IORESOURCE_UNSET; - res->flags &= ~IORESOURCE_ROM_COPY; - res->start = 0; - res->end = 0; - } -} - /** * pci_platform_rom - provides a pointer to any ROM image provided by the * platform diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 2cf16673f17a..29a6deb9f054 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -99,8 +99,6 @@ struct resource { /* PCI ROM control bits (IORESOURCE_BITS) */ #define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */ #define IORESOURCE_ROM_SHADOW (1<<1) /* Use RAM image, not ROM BAR */ -#define IORESOURCE_ROM_COPY (1<<2) /* ROM is alloc'd copy, resource field overlaid */ -#define IORESOURCE_ROM_BIOS_COPY (1<<3) /* ROM is BIOS copy, resource field overlaid */ /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */ #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */ -- cgit v1.2.3-59-g8ed1b From 9d88b93bea5937c0c5c5fc30089d4e6e3c9ca508 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 1 Mar 2016 09:37:10 -0600 Subject: PCI: Simplify sysfs ROM cleanup The value of pdev->rom_attr is the definitive indicator of the fact that we're created a sysfs attribute. Check that rather than rom_size, which is only used incidentally when deciding whether to create a sysfs attribute. Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 51d4dadcf552..4ca8af3d5eda 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1405,7 +1405,7 @@ int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) return 0; err_rom_file: - if (rom_size) { + if (pdev->rom_attr) { sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); kfree(pdev->rom_attr); pdev->rom_attr = NULL; @@ -1443,8 +1443,6 @@ static void pci_remove_capabilities_sysfs(struct pci_dev *dev) */ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { - int rom_size = 0; - if (!sysfs_initialized) return; @@ -1457,18 +1455,13 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) pci_remove_resource_files(pdev); - if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) - rom_size = pci_resource_len(pdev, PCI_ROM_RESOURCE); - else if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) - rom_size = 0x20000; - - if (rom_size && pdev->rom_attr) { + if (pdev->rom_attr) { sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr); kfree(pdev->rom_attr); + pdev->rom_attr = NULL; } pci_remove_firmware_label_files(pdev); - } static int __init pci_sysfs_init(void) -- cgit v1.2.3-59-g8ed1b From b562ec8f74e4edb532a53d26f0002d6fb4a6c79b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 10 Mar 2016 14:36:28 -0600 Subject: PCI: Don't leak memory if sysfs_create_bin_file() fails If sysfs_create_bin_file() fails, pci_create_attr() leaks the struct bin_attribute it allocated previously. Free the struct bin_attribute if pci_create_attr() fails. Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 4ca8af3d5eda..847a97351030 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1159,6 +1159,8 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) res_attr->size = pci_resource_len(pdev, num); res_attr->private = &pdev->resource[num]; retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); + if (retval) + kfree(res_attr); } else retval = -ENOMEM; -- cgit v1.2.3-59-g8ed1b From bd5174dfb6f171fa06e638664ec6ee54692cf7b2 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 10 Mar 2016 14:36:34 -0600 Subject: PCI: Simplify pci_create_attr() control flow Return error immediately to simplify the control flow in pci_create_attr(). No functional change intended. Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 53 +++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 847a97351030..684f27bb68ba 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1134,35 +1134,36 @@ static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) /* allocate attribute structure, piggyback attribute name */ int name_len = write_combine ? 13 : 10; struct bin_attribute *res_attr; + char *res_attr_name; int retval; res_attr = kzalloc(sizeof(*res_attr) + name_len, GFP_ATOMIC); - if (res_attr) { - char *res_attr_name = (char *)(res_attr + 1); - - sysfs_bin_attr_init(res_attr); - if (write_combine) { - pdev->res_attr_wc[num] = res_attr; - sprintf(res_attr_name, "resource%d_wc", num); - res_attr->mmap = pci_mmap_resource_wc; - } else { - pdev->res_attr[num] = res_attr; - sprintf(res_attr_name, "resource%d", num); - res_attr->mmap = pci_mmap_resource_uc; - } - if (pci_resource_flags(pdev, num) & IORESOURCE_IO) { - res_attr->read = pci_read_resource_io; - res_attr->write = pci_write_resource_io; - } - res_attr->attr.name = res_attr_name; - res_attr->attr.mode = S_IRUSR | S_IWUSR; - res_attr->size = pci_resource_len(pdev, num); - res_attr->private = &pdev->resource[num]; - retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); - if (retval) - kfree(res_attr); - } else - retval = -ENOMEM; + if (!res_attr) + return -ENOMEM; + + res_attr_name = (char *)(res_attr + 1); + + sysfs_bin_attr_init(res_attr); + if (write_combine) { + pdev->res_attr_wc[num] = res_attr; + sprintf(res_attr_name, "resource%d_wc", num); + res_attr->mmap = pci_mmap_resource_wc; + } else { + pdev->res_attr[num] = res_attr; + sprintf(res_attr_name, "resource%d", num); + res_attr->mmap = pci_mmap_resource_uc; + } + if (pci_resource_flags(pdev, num) & IORESOURCE_IO) { + res_attr->read = pci_read_resource_io; + res_attr->write = pci_write_resource_io; + } + res_attr->attr.name = res_attr_name; + res_attr->attr.mode = S_IRUSR | S_IWUSR; + res_attr->size = pci_resource_len(pdev, num); + res_attr->private = &pdev->resource[num]; + retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); + if (retval) + kfree(res_attr); return retval; } -- cgit v1.2.3-59-g8ed1b From cc73176cc91b67f4f07385d0dcfde954e54f42c3 Mon Sep 17 00:00:00 2001 From: Andreas Ziegler Date: Tue, 15 Mar 2016 12:28:32 +0100 Subject: PCI: Cleanup pci/pcie/Kconfig whitespace Clean up style issues in drivers/pci/pcie/Kconfig, in particular all indentation is now done using tabs, not spaces, and the definition of PCIEASPM_DEBUG is now separated from the definition of PCIEASPM with a newline. Signed-off-by: Andreas Ziegler Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index e294713c8143..72db7f4209ca 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -44,6 +44,7 @@ config PCIEASPM /sys/module/pcie_aspm/parameters/policy When in doubt, say Y. + config PCIEASPM_DEBUG bool "Debug PCI Express ASPM" depends on PCIEASPM @@ -58,20 +59,20 @@ choice depends on PCIEASPM config PCIEASPM_DEFAULT - bool "BIOS default" + bool "BIOS default" depends on PCIEASPM help Use the BIOS defaults for PCI Express ASPM. config PCIEASPM_POWERSAVE - bool "Powersave" + bool "Powersave" depends on PCIEASPM help Enable PCI Express ASPM L0s and L1 where possible, even if the BIOS did not. config PCIEASPM_PERFORMANCE - bool "Performance" + bool "Performance" depends on PCIEASPM help Disable PCI Express ASPM L0s and L1, even if the BIOS enabled them. -- cgit v1.2.3-59-g8ed1b From 886bc5ceb5cc3ad4b219502d72b277e3c3255a32 Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Thu, 10 Mar 2016 14:44:35 -0600 Subject: PCI: designware: Add generic dw_pcie_wait_for_link() Several DesignWare-based drivers (dra7xx, exynos, imx6, keystone, qcom, and spear13xx) had similar loops waiting for the link to come up. Add a generic dw_pcie_wait_for_link() for use by all these drivers so the waiting is done consistently, e.g., always using usleep_range() rather than mdelay() and using similar timeouts and retry counts. Note that this changes the Keystone link training/wait for link strategy, so we initiate link training, then wait longer for the link to come up before re-initiating link training. [bhelgaas: changelog, split into its own patch, update pci-keystone.c, pcie-qcom.c] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand --- drivers/pci/host/pci-dra7xx.c | 11 +---------- drivers/pci/host/pci-exynos.c | 13 +++---------- drivers/pci/host/pci-imx6.c | 13 ++++--------- drivers/pci/host/pci-keystone.c | 10 ++++------ drivers/pci/host/pcie-designware.c | 19 +++++++++++++++++++ drivers/pci/host/pcie-designware.h | 6 ++++++ drivers/pci/host/pcie-qcom.c | 12 +----------- drivers/pci/host/pcie-spear13xx.c | 14 +------------- 8 files changed, 39 insertions(+), 59 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/host/pci-dra7xx.c b/drivers/pci/host/pci-dra7xx.c index 923607bdabc5..2ca3a1f30ebf 100644 --- a/drivers/pci/host/pci-dra7xx.c +++ b/drivers/pci/host/pci-dra7xx.c @@ -10,7 +10,6 @@ * published by the Free Software Foundation. */ -#include #include #include #include @@ -108,7 +107,6 @@ static int dra7xx_pcie_establish_link(struct pcie_port *pp) { struct dra7xx_pcie *dra7xx = to_dra7xx_pcie(pp); u32 reg; - unsigned int retries; if (dw_pcie_link_up(pp)) { dev_err(pp->dev, "link is already up\n"); @@ -119,14 +117,7 @@ static int dra7xx_pcie_establish_link(struct pcie_port *pp) reg |= LTSSM_EN; dra7xx_pcie_writel(dra7xx, PCIECTRL_DRA7XX_CONF_DEVICE_CMD, reg); - for (retries = 0; retries < 1000; retries++) { - if (dw_pcie_link_up(pp)) - return 0; - usleep_range(10, 20); - } - - dev_err(pp->dev, "link is not up\n"); - return -EINVAL; + return dw_pcie_wait_for_link(pp); } static void dra7xx_pcie_enable_interrupts(struct pcie_port *pp) diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c index d997d22d4231..219976103efc 100644 --- a/drivers/pci/host/pci-exynos.c +++ b/drivers/pci/host/pci-exynos.c @@ -318,7 +318,6 @@ static int exynos_pcie_establish_link(struct pcie_port *pp) { struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp); u32 val; - unsigned int retries; if (dw_pcie_link_up(pp)) { dev_err(pp->dev, "Link already up\n"); @@ -357,13 +356,8 @@ static int exynos_pcie_establish_link(struct pcie_port *pp) PCIE_APP_LTSSM_ENABLE); /* check if the link is up or not */ - for (retries = 0; retries < 10; retries++) { - if (dw_pcie_link_up(pp)) { - dev_info(pp->dev, "Link up\n"); - return 0; - } - mdelay(100); - } + if (!dw_pcie_wait_for_link(pp)) + return 0; while (exynos_phy_readl(exynos_pcie, PCIE_PHY_PLL_LOCKED) == 0) { val = exynos_blk_readl(exynos_pcie, PCIE_PHY_PLL_LOCKED); @@ -372,8 +366,7 @@ static int exynos_pcie_establish_link(struct pcie_port *pp) /* power off phy */ exynos_pcie_power_off_phy(pp); - dev_err(pp->dev, "PCIe Link Fail\n"); - return -EINVAL; + return -ETIMEDOUT; } static void exynos_pcie_clear_irq_pulse(struct pcie_port *pp) diff --git a/drivers/pci/host/pci-imx6.c b/drivers/pci/host/pci-imx6.c index fe600964fa50..6c35d867399f 100644 --- a/drivers/pci/host/pci-imx6.c +++ b/drivers/pci/host/pci-imx6.c @@ -330,19 +330,14 @@ static void imx6_pcie_init_phy(struct pcie_port *pp) static int imx6_pcie_wait_for_link(struct pcie_port *pp) { - unsigned int retries; - - for (retries = 0; retries < 200; retries++) { - if (dw_pcie_link_up(pp)) - return 0; - usleep_range(100, 1000); - } + /* check if the link is up or not */ + if (!dw_pcie_wait_for_link(pp)) + return 0; - dev_err(pp->dev, "phy link never came up\n"); dev_dbg(pp->dev, "DEBUG_R0: 0x%08x, DEBUG_R1: 0x%08x\n", readl(pp->dbi_base + PCIE_PHY_DEBUG_R0), readl(pp->dbi_base + PCIE_PHY_DEBUG_R1)); - return -EINVAL; + return -ETIMEDOUT; } static int imx6_pcie_wait_for_speed_change(struct pcie_port *pp) diff --git a/drivers/pci/host/pci-keystone.c b/drivers/pci/host/pci-keystone.c index 0aa81bd3de12..9a91487d6ecb 100644 --- a/drivers/pci/host/pci-keystone.c +++ b/drivers/pci/host/pci-keystone.c @@ -97,17 +97,15 @@ static int ks_pcie_establish_link(struct keystone_pcie *ks_pcie) return 0; } - ks_dw_pcie_initiate_link_train(ks_pcie); /* check if the link is up or not */ - for (retries = 0; retries < 200; retries++) { - if (dw_pcie_link_up(pp)) - return 0; - usleep_range(100, 1000); + for (retries = 0; retries < 5; retries++) { ks_dw_pcie_initiate_link_train(ks_pcie); + if (!dw_pcie_wait_for_link(pp)) + return 0; } dev_err(pp->dev, "phy link never came up\n"); - return -EINVAL; + return -ETIMEDOUT; } static void ks_pcie_msi_irq_handler(struct irq_desc *desc) diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index f85f10d22049..e4fb44295d61 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "pcie-designware.h" @@ -380,6 +381,24 @@ static struct msi_controller dw_pcie_msi_chip = { .teardown_irq = dw_msi_teardown_irq, }; +int dw_pcie_wait_for_link(struct pcie_port *pp) +{ + int retries; + + /* check if the link is up or not */ + for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { + if (dw_pcie_link_up(pp)) { + dev_info(pp->dev, "link up\n"); + return 0; + } + usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); + } + + dev_err(pp->dev, "phy link never came up\n"); + + return -ETIMEDOUT; +} + int dw_pcie_link_up(struct pcie_port *pp) { if (pp->ops->link_up) diff --git a/drivers/pci/host/pcie-designware.h b/drivers/pci/host/pcie-designware.h index 2356d29e8527..f437f9b5be04 100644 --- a/drivers/pci/host/pcie-designware.h +++ b/drivers/pci/host/pcie-designware.h @@ -22,6 +22,11 @@ #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; @@ -76,6 +81,7 @@ int dw_pcie_cfg_read(void __iomem *addr, int size, u32 *val); int dw_pcie_cfg_write(void __iomem *addr, int size, u32 val); irqreturn_t dw_handle_msi_irq(struct pcie_port *pp); void dw_pcie_msi_init(struct pcie_port *pp); +int dw_pcie_wait_for_link(struct pcie_port *pp); int dw_pcie_link_up(struct pcie_port *pp); void dw_pcie_setup_rc(struct pcie_port *pp); int dw_pcie_host_init(struct pcie_port *pp); diff --git a/drivers/pci/host/pcie-qcom.c b/drivers/pci/host/pcie-qcom.c index e845fba19632..f2f90c50f75d 100644 --- a/drivers/pci/host/pcie-qcom.c +++ b/drivers/pci/host/pcie-qcom.c @@ -116,8 +116,6 @@ static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg) static int qcom_pcie_establish_link(struct qcom_pcie *pcie) { - struct device *dev = pcie->dev; - unsigned int retries = 0; u32 val; if (dw_pcie_link_up(&pcie->pp)) @@ -128,15 +126,7 @@ static int qcom_pcie_establish_link(struct qcom_pcie *pcie) val |= PCIE20_ELBI_SYS_CTRL_LT_ENABLE; writel(val, pcie->elbi + PCIE20_ELBI_SYS_CTRL); - do { - if (dw_pcie_link_up(&pcie->pp)) - return 0; - usleep_range(250, 1000); - } while (retries < 200); - - dev_warn(dev, "phy link never came up\n"); - - return -ETIMEDOUT; + return dw_pcie_wait_for_link(&pcie->pp); } static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie) diff --git a/drivers/pci/host/pcie-spear13xx.c b/drivers/pci/host/pcie-spear13xx.c index a6cd8233e8c0..a4060b85ab23 100644 --- a/drivers/pci/host/pcie-spear13xx.c +++ b/drivers/pci/host/pcie-spear13xx.c @@ -13,7 +13,6 @@ */ #include -#include #include #include #include @@ -149,7 +148,6 @@ static int spear13xx_pcie_establish_link(struct pcie_port *pp) struct spear13xx_pcie *spear13xx_pcie = to_spear13xx_pcie(pp); struct pcie_app_reg *app_reg = spear13xx_pcie->app_base; u32 exp_cap_off = EXP_CAP_ID_OFFSET; - unsigned int retries; if (dw_pcie_link_up(pp)) { dev_err(pp->dev, "link already up\n"); @@ -200,17 +198,7 @@ static int spear13xx_pcie_establish_link(struct pcie_port *pp) | ((u32)1 << REG_TRANSLATION_ENABLE), &app_reg->app_ctrl_0); - /* check if the link is up or not */ - for (retries = 0; retries < 10; retries++) { - if (dw_pcie_link_up(pp)) { - dev_info(pp->dev, "link up\n"); - return 0; - } - mdelay(100); - } - - dev_err(pp->dev, "link Fail\n"); - return -EINVAL; + return dw_pcie_wait_for_link(pp); } static irqreturn_t spear13xx_pcie_irq_handler(int irq, void *arg) -- cgit v1.2.3-59-g8ed1b From dac29e6c5460d05774e3e8c4fdf4d6e7bd481fab Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Thu, 10 Mar 2016 14:44:44 -0600 Subject: PCI: designware: Add default link up check if sub-driver doesn't override Add a default DesignWare "link_up" test for use when a sub-driver doesn't supply its own pcie_host_ops.link_up() method. [bhelgaas: changelog, split into its own patch] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand --- drivers/pci/host/pcie-designware.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/host/pcie-designware.c b/drivers/pci/host/pcie-designware.c index e4fb44295d61..a4cccd356304 100644 --- a/drivers/pci/host/pcie-designware.c +++ b/drivers/pci/host/pcie-designware.c @@ -70,6 +70,11 @@ #define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) #define PCIE_ATU_UPPER_TARGET 0x91C +/* PCIe Port Logic registers */ +#define PLR_OFFSET 0x700 +#define PCIE_PHY_DEBUG_R1 (PLR_OFFSET + 0x2c) +#define PCIE_PHY_DEBUG_R1_LINK_UP 0x00000010 + static struct pci_ops dw_pcie_ops; int dw_pcie_cfg_read(void __iomem *addr, int size, u32 *val) @@ -401,10 +406,13 @@ int dw_pcie_wait_for_link(struct pcie_port *pp) int dw_pcie_link_up(struct pcie_port *pp) { + u32 val; + if (pp->ops->link_up) return pp->ops->link_up(pp); - return 0; + val = readl(pp->dbi_base + PCIE_PHY_DEBUG_R1); + return val & PCIE_PHY_DEBUG_R1_LINK_UP; } static int dw_pcie_msi_map(struct irq_domain *domain, unsigned int irq, -- cgit v1.2.3-59-g8ed1b From 5a3aa2a8fae4ce1a3ad786d212b8fffca8ee72f5 Mon Sep 17 00:00:00 2001 From: Joao Pinto Date: Thu, 10 Mar 2016 14:44:52 -0600 Subject: PCI: designware: Add driver for prototyping kits based on ARC SDP Add a reference platform driver for PCI RC IP Protoyping Kits based on the ARC SDP. [bhelgaas: changelog, split patch up, MAINTAINERS update] Signed-off-by: Joao Pinto Signed-off-by: Bjorn Helgaas Acked-by: Pratyush Anand --- .../devicetree/bindings/pci/designware-pcie.txt | 17 +++ MAINTAINERS | 7 ++ drivers/pci/host/Kconfig | 11 ++ drivers/pci/host/Makefile | 1 + drivers/pci/host/pcie-designware-plat.c | 138 +++++++++++++++++++++ 5 files changed, 174 insertions(+) create mode 100644 drivers/pci/host/pcie-designware-plat.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/pci/designware-pcie.txt b/Documentation/devicetree/bindings/pci/designware-pcie.txt index 5b0853df9d5a..64f2fff12128 100644 --- a/Documentation/devicetree/bindings/pci/designware-pcie.txt +++ b/Documentation/devicetree/bindings/pci/designware-pcie.txt @@ -28,3 +28,20 @@ Optional properties: - clock-names: Must include the following entries: - "pcie" - "pcie_bus" + +Example configuration: + + pcie: pcie@0xdffff000 { + compatible = "snps,dw-pcie"; + reg = <0xdffff000 0x1000>, /* Controller registers */ + <0xd0000000 0x2000>; /* PCI config space */ + reg-names = "ctrlreg", "config"; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + ranges = <0x81000000 0 0x00000000 0xde000000 0 0x00010000 + 0x82000000 0 0xd0400000 0xd0400000 0 0x0d000000>; + interrupts = <25>, <24>; + #interrupt-cells = <1>; + num-lanes = <1>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 30aca4aa5467..e6c327b4ed8c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8367,6 +8367,13 @@ L: linux-pci@vger.kernel.org S: Maintained F: drivers/pci/host/*designware* +PCI DRIVER FOR SYNOPSYS PROTOTYPING DEVICE +M: Joao Pinto +L: linux-pci@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/pci/designware-pcie.txt +F: drivers/pci/host/pcie-designware-plat.c + PCI DRIVER FOR GENERIC OF HOSTS M: Will Deacon L: linux-pci@vger.kernel.org diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index 75a605426538..7c47d69a2664 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -16,6 +16,17 @@ config PCI_MVEBU depends on ARCH_MVEBU || ARCH_DOVE depends on OF +config PCIE_DW_PLAT + bool "Platform bus based DesignWare PCIe Controller" + select PCIE_DW + ---help--- + This selects the DesignWare PCIe controller support. Select this if + you have a PCIe controller on Platform bus. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config PCIE_DW bool diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 7b2f20c6ccc6..7136a1ede59f 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PCIE_DW) += pcie-designware.o +obj-$(CONFIG_PCIE_DW_PLAT) += pcie-designware-plat.o obj-$(CONFIG_PCI_DRA7XX) += pci-dra7xx.o obj-$(CONFIG_PCI_EXYNOS) += pci-exynos.o obj-$(CONFIG_PCI_IMX6) += pci-imx6.o diff --git a/drivers/pci/host/pcie-designware-plat.c b/drivers/pci/host/pcie-designware-plat.c new file mode 100644 index 000000000000..b3500994d08a --- /dev/null +++ b/drivers/pci/host/pcie-designware-plat.c @@ -0,0 +1,138 @@ +/* + * PCIe RC driver for Synopsys DesignWare Core + * + * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com) + * + * Authors: Joao Pinto + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" + +struct dw_plat_pcie { + void __iomem *mem_base; + struct pcie_port pp; +}; + +static irqreturn_t dw_plat_pcie_msi_irq_handler(int irq, void *arg) +{ + struct pcie_port *pp = arg; + + return dw_handle_msi_irq(pp); +} + +static void dw_plat_pcie_host_init(struct pcie_port *pp) +{ + dw_pcie_setup_rc(pp); + dw_pcie_wait_for_link(pp); + + if (IS_ENABLED(CONFIG_PCI_MSI)) + dw_pcie_msi_init(pp); +} + +static struct pcie_host_ops dw_plat_pcie_host_ops = { + .host_init = dw_plat_pcie_host_init, +}; + +static int dw_plat_add_pcie_port(struct pcie_port *pp, + struct platform_device *pdev) +{ + int ret; + + pp->irq = platform_get_irq(pdev, 1); + if (pp->irq < 0) + return pp->irq; + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + pp->msi_irq = platform_get_irq(pdev, 0); + if (pp->msi_irq < 0) + return pp->msi_irq; + + ret = devm_request_irq(&pdev->dev, pp->msi_irq, + dw_plat_pcie_msi_irq_handler, + IRQF_SHARED, "dw-plat-pcie-msi", pp); + if (ret) { + dev_err(&pdev->dev, "failed to request MSI IRQ\n"); + return ret; + } + } + + pp->root_bus_nr = -1; + pp->ops = &dw_plat_pcie_host_ops; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(&pdev->dev, "failed to initialize host\n"); + return ret; + } + + return 0; +} + +static int dw_plat_pcie_probe(struct platform_device *pdev) +{ + struct dw_plat_pcie *dw_plat_pcie; + struct pcie_port *pp; + struct resource *res; /* Resource from DT */ + int ret; + + dw_plat_pcie = devm_kzalloc(&pdev->dev, sizeof(*dw_plat_pcie), + GFP_KERNEL); + if (!dw_plat_pcie) + return -ENOMEM; + + pp = &dw_plat_pcie->pp; + 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); + + pp->dbi_base = dw_plat_pcie->mem_base; + + ret = dw_plat_add_pcie_port(pp, pdev); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, dw_plat_pcie); + return 0; +} + +static const struct of_device_id dw_plat_pcie_of_match[] = { + { .compatible = "snps,dw-pcie", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_plat_pcie_of_match); + +static struct platform_driver dw_plat_pcie_driver = { + .driver = { + .name = "dw-pcie", + .of_match_table = dw_plat_pcie_of_match, + }, + .probe = dw_plat_pcie_probe, +}; + +module_platform_driver(dw_plat_pcie_driver); + +MODULE_AUTHOR("Joao Pinto "); +MODULE_DESCRIPTION("Synopsys PCIe host controller glue platform driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-59-g8ed1b From 7b78f48a0443eceae435870b14e86d586f8c2a3e Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 15 Mar 2016 14:06:00 +0200 Subject: PCI: Add PCI_CLASS_SERIAL_USB_DEVICE definition PCI-SIG has defined Interface FEh for Base Class 0Ch, Sub-Class 03h as "USB Device (not host controller)". It is already being used in various USB device controller drivers for matching, so add PCI_CLASS_SERIAL_USB_DEVICE and use it. Signed-off-by: Heikki Krogerus Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 2 +- drivers/usb/gadget/udc/amd5536udc.c | 2 +- drivers/usb/gadget/udc/goku_udc.c | 2 +- drivers/usb/gadget/udc/net2280.c | 8 ++++---- drivers/usb/gadget/udc/pch_udc.c | 8 ++++---- include/linux/pci_ids.h | 1 + 6 files changed, 12 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 0575a1e026b4..5714fcfde302 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -438,7 +438,7 @@ static void quirk_amd_nl_class(struct pci_dev *pdev) u32 class = pdev->class; /* Use "USB Device (not host controller)" class */ - pdev->class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe; + pdev->class = PCI_CLASS_SERIAL_USB_DEVICE; dev_info(&pdev->dev, "PCI class overridden (%#08x -> %#08x) so dwc3 driver can claim this instead of xhci\n", class, pdev->class); } diff --git a/drivers/usb/gadget/udc/amd5536udc.c b/drivers/usb/gadget/udc/amd5536udc.c index cd8764150861..39d70b4a8958 100644 --- a/drivers/usb/gadget/udc/amd5536udc.c +++ b/drivers/usb/gadget/udc/amd5536udc.c @@ -3397,7 +3397,7 @@ err_pcidev: static const struct pci_device_id pci_id[] = { { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x2096), - .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = 0xffffffff, }, {}, diff --git a/drivers/usb/gadget/udc/goku_udc.c b/drivers/usb/gadget/udc/goku_udc.c index 1fdfec14a3ba..d2205d9e0c8b 100644 --- a/drivers/usb/gadget/udc/goku_udc.c +++ b/drivers/usb/gadget/udc/goku_udc.c @@ -1846,7 +1846,7 @@ err: /*-------------------------------------------------------------------------*/ static const struct pci_device_id pci_ids[] = { { - .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = ~0, .vendor = 0x102f, /* Toshiba */ .device = 0x0107, /* this UDC */ diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 6706aef907f4..c894b94b234b 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -3735,7 +3735,7 @@ static void net2280_shutdown(struct pci_dev *pdev) /*-------------------------------------------------------------------------*/ static const struct pci_device_id pci_ids[] = { { - .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = ~0, .vendor = PCI_VENDOR_ID_PLX_LEGACY, .device = 0x2280, @@ -3743,7 +3743,7 @@ static const struct pci_device_id pci_ids[] = { { .subdevice = PCI_ANY_ID, .driver_data = PLX_LEGACY | PLX_2280, }, { - .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = ~0, .vendor = PCI_VENDOR_ID_PLX_LEGACY, .device = 0x2282, @@ -3752,7 +3752,7 @@ static const struct pci_device_id pci_ids[] = { { .driver_data = PLX_LEGACY, }, { - .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = ~0, .vendor = PCI_VENDOR_ID_PLX, .device = 0x3380, @@ -3761,7 +3761,7 @@ static const struct pci_device_id pci_ids[] = { { .driver_data = PLX_SUPERSPEED, }, { - .class = ((PCI_CLASS_SERIAL_USB << 8) | 0xfe), + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = ~0, .vendor = PCI_VENDOR_ID_PLX, .device = 0x3382, diff --git a/drivers/usb/gadget/udc/pch_udc.c b/drivers/usb/gadget/udc/pch_udc.c index 7a04157ff579..9571ef54b86b 100644 --- a/drivers/usb/gadget/udc/pch_udc.c +++ b/drivers/usb/gadget/udc/pch_udc.c @@ -3234,22 +3234,22 @@ static const struct pci_device_id pch_udc_pcidev_id[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_UDC), - .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = 0xffffffff, }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_EG20T_UDC), - .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = 0xffffffff, }, { PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7213_IOH_UDC), - .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = 0xffffffff, }, { PCI_DEVICE(PCI_VENDOR_ID_ROHM, PCI_DEVICE_ID_ML7831_IOH_UDC), - .class = (PCI_CLASS_SERIAL_USB << 8) | 0xfe, + .class = PCI_CLASS_SERIAL_USB_DEVICE, .class_mask = 0xffffffff, }, { 0 }, diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 6d249d32c8b8..247da8c95860 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -110,6 +110,7 @@ #define PCI_CLASS_SERIAL_USB_OHCI 0x0c0310 #define PCI_CLASS_SERIAL_USB_EHCI 0x0c0320 #define PCI_CLASS_SERIAL_USB_XHCI 0x0c0330 +#define PCI_CLASS_SERIAL_USB_DEVICE 0x0c03fe #define PCI_CLASS_SERIAL_FIBER 0x0c04 #define PCI_CLASS_SERIAL_SMBUS 0x0c05 -- cgit v1.2.3-59-g8ed1b