From 6568d82512b0a64809acff3d7a747362fa4288c8 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Thu, 18 Jan 2024 13:08:15 +0200 Subject: PCI/DPC: Print all TLP Prefixes, not just the first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TLP Prefix Log Register consists of multiple DWORDs (PCIe r6.1 sec 7.9.14.13) but the loop in dpc_process_rp_pio_error() keeps reading from the first DWORD, so we print only the first PIO TLP Prefix (duplicated several times), and we never print the second, third, etc., Prefixes. Add the iteration count based offset calculation into the config read. Fixes: f20c4ea49ec4 ("PCI/DPC: Add eDPC support") Link: https://lore.kernel.org/r/20240118110815.3867-1-ilpo.jarvinen@linux.intel.com Signed-off-by: Ilpo Järvinen [bhelgaas: add user-visible details to commit log] Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/dpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index 94111e438241..e5d7c12854fa 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -234,7 +234,7 @@ static void dpc_process_rp_pio_error(struct pci_dev *pdev) for (i = 0; i < pdev->dpc_rp_log_size - 5; i++) { pci_read_config_dword(pdev, - cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG, &prefix); + cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG + i * 4, &prefix); pci_err(pdev, "TLP Prefix Header: dw%d, %#010x\n", i, prefix); } clear_status: -- cgit v1.2.3 From 1e8cc8e6bd85d7b25e0ed3759aedde804c91ba97 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Mon, 29 Jan 2024 13:36:54 +0200 Subject: PCI: Place interrupt related code into irq.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Interrupt related code is spread into irq.c, pci.c, and setup-irq.c. Group them into pre-existing irq.c. Link: https://lore.kernel.org/r/20240129113655.3368-1-ilpo.jarvinen@linux.intel.com Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas --- drivers/pci/Makefile | 2 +- drivers/pci/irq.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci-driver.c | 9 --- drivers/pci/pci.c | 144 --------------------------------- drivers/pci/setup-irq.c | 64 --------------- 5 files changed, 205 insertions(+), 218 deletions(-) delete mode 100644 drivers/pci/setup-irq.c (limited to 'drivers') diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index cc8b4e01e29d..54a7adf0bb88 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ remove.o pci.o pci-driver.o search.o \ pci-sysfs.o rom.o setup-res.o irq.o vpd.o \ - setup-bus.o vc.o mmap.o setup-irq.o + setup-bus.o vc.o mmap.o obj-$(CONFIG_PCI) += msi/ obj-$(CONFIG_PCI) += pcie/ diff --git a/drivers/pci/irq.c b/drivers/pci/irq.c index 0050e8f6814e..4555630be9ec 100644 --- a/drivers/pci/irq.c +++ b/drivers/pci/irq.c @@ -8,9 +8,13 @@ #include #include +#include #include +#include #include +#include "pci.h" + /** * pci_request_irq - allocate an interrupt line for a PCI device * @dev: PCI device to operate on @@ -74,3 +78,203 @@ void pci_free_irq(struct pci_dev *dev, unsigned int nr, void *dev_id) kfree(free_irq(pci_irq_vector(dev, nr), dev_id)); } EXPORT_SYMBOL(pci_free_irq); + +/** + * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge + * @dev: the PCI device + * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) + * + * Perform INTx swizzling for a device behind one level of bridge. This is + * required by section 9.1 of the PCI-to-PCI bridge specification for devices + * behind bridges on add-in cards. For devices with ARI enabled, the slot + * number is always 0 (see the Implementation Note in section 2.2.8.1 of + * the PCI Express Base Specification, Revision 2.1) + */ +u8 pci_swizzle_interrupt_pin(const struct pci_dev *dev, u8 pin) +{ + int slot; + + if (pci_ari_enabled(dev->bus)) + slot = 0; + else + slot = PCI_SLOT(dev->devfn); + + return (((pin - 1) + slot) % 4) + 1; +} + +int pci_get_interrupt_pin(struct pci_dev *dev, struct pci_dev **bridge) +{ + u8 pin; + + pin = dev->pin; + if (!pin) + return -1; + + while (!pci_is_root_bus(dev->bus)) { + pin = pci_swizzle_interrupt_pin(dev, pin); + dev = dev->bus->self; + } + *bridge = dev; + return pin; +} + +/** + * pci_common_swizzle - swizzle INTx all the way to root bridge + * @dev: the PCI device + * @pinp: pointer to the INTx pin value (1=INTA, 2=INTB, 3=INTD, 4=INTD) + * + * Perform INTx swizzling for a device. This traverses through all PCI-to-PCI + * bridges all the way up to a PCI root bus. + */ +u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp) +{ + u8 pin = *pinp; + + while (!pci_is_root_bus(dev->bus)) { + pin = pci_swizzle_interrupt_pin(dev, pin); + dev = dev->bus->self; + } + *pinp = pin; + return PCI_SLOT(dev->devfn); +} +EXPORT_SYMBOL_GPL(pci_common_swizzle); + +void pci_assign_irq(struct pci_dev *dev) +{ + u8 pin; + u8 slot = -1; + int irq = 0; + struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus); + + if (!(hbrg->map_irq)) { + pci_dbg(dev, "runtime IRQ mapping not provided by arch\n"); + return; + } + + /* + * If this device is not on the primary bus, we need to figure out + * which interrupt pin it will come in on. We know which slot it + * will come in on because that slot is where the bridge is. Each + * time the interrupt line passes through a PCI-PCI bridge we must + * apply the swizzle function. + */ + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); + /* Cope with illegal. */ + if (pin > 4) + pin = 1; + + if (pin) { + /* Follow the chain of bridges, swizzling as we go. */ + if (hbrg->swizzle_irq) + slot = (*(hbrg->swizzle_irq))(dev, &pin); + + /* + * If a swizzling function is not used, map_irq() must + * ignore slot. + */ + irq = (*(hbrg->map_irq))(dev, slot, pin); + if (irq == -1) + irq = 0; + } + dev->irq = irq; + + pci_dbg(dev, "assign IRQ: got %d\n", dev->irq); + + /* + * Always tell the device, so the driver knows what is the real IRQ + * to use; the device does not use it. + */ + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); +} + +static bool pci_check_and_set_intx_mask(struct pci_dev *dev, bool mask) +{ + struct pci_bus *bus = dev->bus; + bool mask_updated = true; + u32 cmd_status_dword; + u16 origcmd, newcmd; + unsigned long flags; + bool irq_pending; + + /* + * We do a single dword read to retrieve both command and status. + * Document assumptions that make this possible. + */ + BUILD_BUG_ON(PCI_COMMAND % 4); + BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS); + + raw_spin_lock_irqsave(&pci_lock, flags); + + bus->ops->read(bus, dev->devfn, PCI_COMMAND, 4, &cmd_status_dword); + + irq_pending = (cmd_status_dword >> 16) & PCI_STATUS_INTERRUPT; + + /* + * Check interrupt status register to see whether our device + * triggered the interrupt (when masking) or the next IRQ is + * already pending (when unmasking). + */ + if (mask != irq_pending) { + mask_updated = false; + goto done; + } + + origcmd = cmd_status_dword; + newcmd = origcmd & ~PCI_COMMAND_INTX_DISABLE; + if (mask) + newcmd |= PCI_COMMAND_INTX_DISABLE; + if (newcmd != origcmd) + bus->ops->write(bus, dev->devfn, PCI_COMMAND, 2, newcmd); + +done: + raw_spin_unlock_irqrestore(&pci_lock, flags); + + return mask_updated; +} + +/** + * pci_check_and_mask_intx - mask INTx on pending interrupt + * @dev: the PCI device to operate on + * + * Check if the device dev has its INTx line asserted, mask it and return + * true in that case. False is returned if no interrupt was pending. + */ +bool pci_check_and_mask_intx(struct pci_dev *dev) +{ + return pci_check_and_set_intx_mask(dev, true); +} +EXPORT_SYMBOL_GPL(pci_check_and_mask_intx); + +/** + * pci_check_and_unmask_intx - unmask INTx if no interrupt is pending + * @dev: the PCI device to operate on + * + * Check if the device dev has its INTx line asserted, unmask it if not and + * return true. False is returned and the mask remains active if there was + * still an interrupt pending. + */ +bool pci_check_and_unmask_intx(struct pci_dev *dev) +{ + return pci_check_and_set_intx_mask(dev, false); +} +EXPORT_SYMBOL_GPL(pci_check_and_unmask_intx); + +/** + * pcibios_penalize_isa_irq - penalize an ISA IRQ + * @irq: ISA IRQ to penalize + * @active: IRQ active or not + * + * Permits the platform to provide architecture-specific functionality when + * penalizing ISA IRQs. This is the default implementation. Architecture + * implementations can override this. + */ +void __weak pcibios_penalize_isa_irq(int irq, int active) {} + +int __weak pcibios_alloc_irq(struct pci_dev *dev) +{ + return 0; +} + +void __weak pcibios_free_irq(struct pci_dev *dev) +{ +} diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 51ec9e7e784f..ec838f2e892e 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -419,15 +419,6 @@ static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev) return error; } -int __weak pcibios_alloc_irq(struct pci_dev *dev) -{ - return 0; -} - -void __weak pcibios_free_irq(struct pci_dev *dev) -{ -} - #ifdef CONFIG_PCI_IOV static inline bool pci_device_can_probe(struct pci_dev *pdev) { diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d8f11a078924..75388584e60d 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -2292,17 +2291,6 @@ void __weak pcibios_release_device(struct pci_dev *dev) {} */ void __weak pcibios_disable_device(struct pci_dev *dev) {} -/** - * pcibios_penalize_isa_irq - penalize an ISA IRQ - * @irq: ISA IRQ to penalize - * @active: IRQ active or not - * - * Permits the platform to provide architecture-specific functionality when - * penalizing ISA IRQs. This is the default implementation. Architecture - * implementations can override this. - */ -void __weak pcibios_penalize_isa_irq(int irq, int active) {} - static void do_pci_disable_device(struct pci_dev *dev) { u16 pci_command; @@ -3964,66 +3952,6 @@ int pci_enable_atomic_ops_to_root(struct pci_dev *dev, u32 cap_mask) } EXPORT_SYMBOL(pci_enable_atomic_ops_to_root); -/** - * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge - * @dev: the PCI device - * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) - * - * Perform INTx swizzling for a device behind one level of bridge. This is - * required by section 9.1 of the PCI-to-PCI bridge specification for devices - * behind bridges on add-in cards. For devices with ARI enabled, the slot - * number is always 0 (see the Implementation Note in section 2.2.8.1 of - * the PCI Express Base Specification, Revision 2.1) - */ -u8 pci_swizzle_interrupt_pin(const struct pci_dev *dev, u8 pin) -{ - int slot; - - if (pci_ari_enabled(dev->bus)) - slot = 0; - else - slot = PCI_SLOT(dev->devfn); - - return (((pin - 1) + slot) % 4) + 1; -} - -int pci_get_interrupt_pin(struct pci_dev *dev, struct pci_dev **bridge) -{ - u8 pin; - - pin = dev->pin; - if (!pin) - return -1; - - while (!pci_is_root_bus(dev->bus)) { - pin = pci_swizzle_interrupt_pin(dev, pin); - dev = dev->bus->self; - } - *bridge = dev; - return pin; -} - -/** - * pci_common_swizzle - swizzle INTx all the way to root bridge - * @dev: the PCI device - * @pinp: pointer to the INTx pin value (1=INTA, 2=INTB, 3=INTD, 4=INTD) - * - * Perform INTx swizzling for a device. This traverses through all PCI-to-PCI - * bridges all the way up to a PCI root bus. - */ -u8 pci_common_swizzle(struct pci_dev *dev, u8 *pinp) -{ - u8 pin = *pinp; - - while (!pci_is_root_bus(dev->bus)) { - pin = pci_swizzle_interrupt_pin(dev, pin); - dev = dev->bus->self; - } - *pinp = pin; - return PCI_SLOT(dev->devfn); -} -EXPORT_SYMBOL_GPL(pci_common_swizzle); - /** * pci_release_region - Release a PCI bar * @pdev: PCI device whose resources were previously reserved by @@ -4737,78 +4665,6 @@ void pci_intx(struct pci_dev *pdev, int enable) } EXPORT_SYMBOL_GPL(pci_intx); -static bool pci_check_and_set_intx_mask(struct pci_dev *dev, bool mask) -{ - struct pci_bus *bus = dev->bus; - bool mask_updated = true; - u32 cmd_status_dword; - u16 origcmd, newcmd; - unsigned long flags; - bool irq_pending; - - /* - * We do a single dword read to retrieve both command and status. - * Document assumptions that make this possible. - */ - BUILD_BUG_ON(PCI_COMMAND % 4); - BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS); - - raw_spin_lock_irqsave(&pci_lock, flags); - - bus->ops->read(bus, dev->devfn, PCI_COMMAND, 4, &cmd_status_dword); - - irq_pending = (cmd_status_dword >> 16) & PCI_STATUS_INTERRUPT; - - /* - * Check interrupt status register to see whether our device - * triggered the interrupt (when masking) or the next IRQ is - * already pending (when unmasking). - */ - if (mask != irq_pending) { - mask_updated = false; - goto done; - } - - origcmd = cmd_status_dword; - newcmd = origcmd & ~PCI_COMMAND_INTX_DISABLE; - if (mask) - newcmd |= PCI_COMMAND_INTX_DISABLE; - if (newcmd != origcmd) - bus->ops->write(bus, dev->devfn, PCI_COMMAND, 2, newcmd); - -done: - raw_spin_unlock_irqrestore(&pci_lock, flags); - - return mask_updated; -} - -/** - * pci_check_and_mask_intx - mask INTx on pending interrupt - * @dev: the PCI device to operate on - * - * Check if the device dev has its INTx line asserted, mask it and return - * true in that case. False is returned if no interrupt was pending. - */ -bool pci_check_and_mask_intx(struct pci_dev *dev) -{ - return pci_check_and_set_intx_mask(dev, true); -} -EXPORT_SYMBOL_GPL(pci_check_and_mask_intx); - -/** - * pci_check_and_unmask_intx - unmask INTx if no interrupt is pending - * @dev: the PCI device to operate on - * - * Check if the device dev has its INTx line asserted, unmask it if not and - * return true. False is returned and the mask remains active if there was - * still an interrupt pending. - */ -bool pci_check_and_unmask_intx(struct pci_dev *dev) -{ - return pci_check_and_set_intx_mask(dev, false); -} -EXPORT_SYMBOL_GPL(pci_check_and_unmask_intx); - /** * pci_wait_for_pending_transaction - wait for pending transaction * @dev: the PCI device to operate on diff --git a/drivers/pci/setup-irq.c b/drivers/pci/setup-irq.c deleted file mode 100644 index cc7d26b015f3..000000000000 --- a/drivers/pci/setup-irq.c +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Support routines for initializing a PCI subsystem - * - * Extruded from code written by - * Dave Rusling (david.rusling@reo.mts.dec.com) - * David Mosberger (davidm@cs.arizona.edu) - * David Miller (davem@redhat.com) - */ - -#include -#include -#include -#include -#include -#include "pci.h" - -void pci_assign_irq(struct pci_dev *dev) -{ - u8 pin; - u8 slot = -1; - int irq = 0; - struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus); - - if (!(hbrg->map_irq)) { - pci_dbg(dev, "runtime IRQ mapping not provided by arch\n"); - return; - } - - /* - * If this device is not on the primary bus, we need to figure out - * which interrupt pin it will come in on. We know which slot it - * will come in on because that slot is where the bridge is. Each - * time the interrupt line passes through a PCI-PCI bridge we must - * apply the swizzle function. - */ - pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin); - /* Cope with illegal. */ - if (pin > 4) - pin = 1; - - if (pin) { - /* Follow the chain of bridges, swizzling as we go. */ - if (hbrg->swizzle_irq) - slot = (*(hbrg->swizzle_irq))(dev, &pin); - - /* - * If a swizzling function is not used, map_irq() must - * ignore slot. - */ - irq = (*(hbrg->map_irq))(dev, slot, pin); - if (irq == -1) - irq = 0; - } - dev->irq = irq; - - pci_dbg(dev, "assign IRQ: got %d\n", dev->irq); - - /* - * Always tell the device, so the driver knows what is the real IRQ - * to use; the device does not use it. - */ - pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq); -} -- cgit v1.2.3 From b91da7308171ac4ad2623d371f37288dafbb3bdc Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Sun, 4 Feb 2024 17:28:58 -0300 Subject: PCI: endpoint: Make pci_epf_bus_type const Now that the driver core can properly handle constant struct bus_type, move the pci_epf_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Cc: Greg Kroah-Hartman Suggested-by: Greg Kroah-Hartman Signed-off-by: Ricardo B. Marliere Reviewed-by: Greg Kroah-Hartman Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20240204-bus_cleanup-pci-v1-1-300267a1e99e@marliere.net [mani: modified subject to reflect subsys prefix] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/pci-epf-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index 2c32de667937..bf655383e5ed 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -17,7 +17,7 @@ static DEFINE_MUTEX(pci_epf_mutex); -static struct bus_type pci_epf_bus_type; +static const struct bus_type pci_epf_bus_type; static const struct device_type pci_epf_type; /** @@ -507,7 +507,7 @@ static void pci_epf_device_remove(struct device *dev) epf->driver = NULL; } -static struct bus_type pci_epf_bus_type = { +static const struct bus_type pci_epf_bus_type = { .name = "pci-epf", .match = pci_epf_device_match, .probe = pci_epf_device_probe, -- cgit v1.2.3 From 7adf6ac8521e2102d1d0f970c532e1bb91e1d096 Mon Sep 17 00:00:00 2001 From: "Ricardo B. Marliere" Date: Thu, 8 Feb 2024 16:41:46 -0300 Subject: PCI: Make pcie_port_bus_type const Now that the driver core can properly handle constant struct bus_type, move the pcie_port_bus_type variable to be a constant structure as well, placing it into read-only memory which can not be modified at runtime. Link: https://lore.kernel.org/r/20240208-bus_cleanup-pci2-v1-1-5e578210b6f2@marliere.net Suggested-by: Greg Kroah-Hartman Signed-off-by: Ricardo B. Marliere Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-driver.c | 2 +- drivers/pci/pcie/portdrv.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 51ec9e7e784f..37cfec341fce 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -1714,7 +1714,7 @@ static int pcie_port_bus_match(struct device *dev, struct device_driver *drv) return 1; } -struct bus_type pcie_port_bus_type = { +const struct bus_type pcie_port_bus_type = { .name = "pci_express", .match = pcie_port_bus_match, }; diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index 1f3803bde7ee..12c89ea0313b 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -96,7 +96,7 @@ struct pcie_port_service_driver { int pcie_port_service_register(struct pcie_port_service_driver *new); void pcie_port_service_unregister(struct pcie_port_service_driver *new); -extern struct bus_type pcie_port_bus_type; +extern const struct bus_type pcie_port_bus_type; struct pci_dev; -- cgit v1.2.3 From 1e5c66afd4a40bb7be17cb33cbb1a1085f727730 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 4 Jan 2024 20:52:35 +0100 Subject: PCI/P2PDMA: Fix a sleeping issue in a RCU read section It is not allowed to sleep within a RCU read section, so use GFP_ATOMIC instead of GFP_KERNEL here. Link: https://lore.kernel.org/r/02d9ec4a10235def0e764ff1f5be881ba12e16e8.1704397858.git.christophe.jaillet@wanadoo.fr Fixes: ae21f835a5bd ("PCI/P2PDMA: Finish RCU conversion of pdev->p2pdma") Signed-off-by: Christophe JAILLET Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/p2pdma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 0c361561b855..4f47a13cb500 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -661,7 +661,7 @@ done: p2pdma = rcu_dereference(provider->p2pdma); if (p2pdma) xa_store(&p2pdma->map_types, map_types_idx(client), - xa_mk_value(map_type), GFP_KERNEL); + xa_mk_value(map_type), GFP_ATOMIC); rcu_read_unlock(); return map_type; } -- cgit v1.2.3 From dec529b0b0572b32f9eb91c882dd1f08ca657efb Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 24 Dec 2023 15:30:01 +0100 Subject: PCI: switchtec: Fix an error handling path in switchtec_pci_probe() The commit in Fixes changed the logic on how resources are released and introduced a new switchtec_exit_pci() that need to be called explicitly in order to undo a corresponding switchtec_init_pci(). This was done in the remove function, but not in the probe. Fix the probe now. Fixes: df25461119d9 ("PCI: switchtec: Fix stdev_release() crash after surprise hot remove") Link: https://lore.kernel.org/r/01446d2ccb91a578239915812f2b7dfbeb2882af.1703428183.git.christophe.jaillet@wanadoo.fr Signed-off-by: Christophe JAILLET Signed-off-by: Bjorn Helgaas --- drivers/pci/switch/switchtec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 1804794d0e68..5a4adf6c04cf 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -1672,7 +1672,7 @@ static int switchtec_pci_probe(struct pci_dev *pdev, rc = switchtec_init_isr(stdev); if (rc) { dev_err(&stdev->dev, "failed to init isr.\n"); - goto err_put; + goto err_exit_pci; } iowrite32(SWITCHTEC_EVENT_CLEAR | @@ -1693,6 +1693,8 @@ static int switchtec_pci_probe(struct pci_dev *pdev, err_devadd: stdev_kill(stdev); +err_exit_pci: + switchtec_exit_pci(stdev); err_put: ida_free(&switchtec_minor_ida, MINOR(stdev->dev.devt)); put_device(&stdev->dev); -- cgit v1.2.3 From e891becdccaa9048b1ab91c08ad5722edd571806 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 7 Feb 2024 22:39:14 +0100 Subject: PCI: endpoint: Refactor pci_epf_alloc_space() API Refactor pci_epf_alloc_space() API to accept "epc_features" as a parameter. This is a preparatory work to make the API more robust. Reviewed-by: Frank Li Reviewed-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20240207213922.1796533-2-cassel@kernel.org [mani: reworded commit message] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-ntb.c | 2 +- drivers/pci/endpoint/functions/pci-epf-test.c | 5 ++--- drivers/pci/endpoint/functions/pci-epf-vntb.c | 4 ++-- drivers/pci/endpoint/pci-epf-core.c | 6 ++++-- include/linux/pci-epf.h | 4 +++- 5 files changed, 12 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-ntb.c b/drivers/pci/endpoint/functions/pci-epf-ntb.c index 0553946005c4..43cd309ce22f 100644 --- a/drivers/pci/endpoint/functions/pci-epf-ntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-ntb.c @@ -1067,7 +1067,7 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb, else if (size < ctrl_size + spad_size) return -EINVAL; - base = pci_epf_alloc_space(epf, size, barno, align, type); + base = pci_epf_alloc_space(epf, size, barno, epc_features, type); if (!base) { dev_err(dev, "%s intf: Config/Status/SPAD alloc region fail\n", pci_epc_interface_string(type)); diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 18c80002d3bd..15bfa7d83489 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -848,7 +848,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf) } base = pci_epf_alloc_space(epf, test_reg_size, test_reg_bar, - epc_features->align, PRIMARY_INTERFACE); + epc_features, PRIMARY_INTERFACE); if (!base) { dev_err(dev, "Failed to allocated register space\n"); return -ENOMEM; @@ -866,8 +866,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf) continue; base = pci_epf_alloc_space(epf, bar_size[bar], bar, - epc_features->align, - PRIMARY_INTERFACE); + epc_features, PRIMARY_INTERFACE); if (!base) dev_err(dev, "Failed to allocate space for BAR%d\n", bar); diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index e75a2af77328..ba509d67188b 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -446,7 +446,7 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb) else if (size < ctrl_size + spad_size) return -EINVAL; - base = pci_epf_alloc_space(epf, size, barno, align, 0); + base = pci_epf_alloc_space(epf, size, barno, epc_features, 0); if (!base) { dev_err(dev, "Config/Status/SPAD alloc region fail\n"); return -ENOMEM; @@ -550,7 +550,7 @@ static int epf_ntb_db_bar_init(struct epf_ntb *ntb) barno = ntb->epf_ntb_bar[BAR_DB]; - mw_addr = pci_epf_alloc_space(ntb->epf, size, barno, align, 0); + mw_addr = pci_epf_alloc_space(ntb->epf, size, barno, epc_features, 0); if (!mw_addr) { dev_err(dev, "Failed to allocate OB address\n"); return -ENOMEM; diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index bf655383e5ed..9da9a1e9c086 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -251,14 +251,16 @@ EXPORT_SYMBOL_GPL(pci_epf_free_space); * @epf: the EPF device to whom allocate the memory * @size: the size of the memory that has to be allocated * @bar: the BAR number corresponding to the allocated register space - * @align: alignment size for the allocation region + * @epc_features: the features provided by the EPC specific to this EPF * @type: Identifies if the allocation is for primary EPC or secondary EPC * * Invoke to allocate memory for the PCI EPF register space. */ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, - size_t align, enum pci_epc_interface_type type) + const struct pci_epc_features *epc_features, + enum pci_epc_interface_type type) { + size_t align = epc_features->align; struct pci_epf_bar *epf_bar; dma_addr_t phys_addr; struct pci_epc *epc; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 77b146e0f672..adee6a1b35db 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -15,6 +15,7 @@ #include struct pci_epf; +struct pci_epc_features; enum pci_epc_interface_type; enum pci_barno { @@ -216,7 +217,8 @@ int __pci_epf_register_driver(struct pci_epf_driver *driver, struct module *owner); void pci_epf_unregister_driver(struct pci_epf_driver *driver); void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, - size_t align, enum pci_epc_interface_type type); + const struct pci_epc_features *epc_features, + enum pci_epc_interface_type type); void pci_epf_free_space(struct pci_epf *epf, void *addr, enum pci_barno bar, enum pci_epc_interface_type type); int pci_epf_bind(struct pci_epf *epf); -- cgit v1.2.3 From 84b51a6baeaf1ba85e024a5bf145382c02179013 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 7 Feb 2024 22:39:15 +0100 Subject: PCI: endpoint: Improve pci_epf_alloc_space() API pci_epf_alloc_space() already performs checks on the requested BAR size, and will allocate and set epf_bar->size to a size higher than the requested BAR size if some constraint deems it necessary. However, there are additional checks done in the function drivers like pci-epf-test.c, other than the existing checks in this API. And similar checks are proposed to other endpoint function drivers, see: https://lore.kernel.org/linux-pci/20240108151015.2030469-1-Frank.Li@nxp.com Having these checks scattered over different locations in multiple EPF drivers is not maintainable and makes the code hard to follow. Since pci_epf_alloc_space() already performs roundups and some checks, let's move the additional checks from pci-epf-test.c to pci_epf_alloc_space(). This makes the API more robust and also offloads the checks from the function drivers. Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20240207213922.1796533-3-cassel@kernel.org [mani: reworded commit message and fixed uninitialized 'dev' pointer issue] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/pci-epf-core.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index 9da9a1e9c086..f2b4d34454c4 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -260,6 +260,7 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, const struct pci_epc_features *epc_features, enum pci_epc_interface_type type) { + u64 bar_fixed_size = epc_features->bar_fixed_size[bar]; size_t align = epc_features->align; struct pci_epf_bar *epf_bar; dma_addr_t phys_addr; @@ -270,6 +271,14 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, if (size < 128) size = 128; + if (bar_fixed_size && size > bar_fixed_size) { + dev_err(&epf->dev, "requested BAR size is larger than fixed size\n"); + return NULL; + } + + if (bar_fixed_size) + size = bar_fixed_size; + if (align) size = ALIGN(size, align); else -- cgit v1.2.3 From fda826b15c782b6b7d3bd3a9a089c928512ebd3d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 7 Feb 2024 22:39:16 +0100 Subject: PCI: endpoint: pci-epf-test: Remove superfluous checks for pci_epf_alloc_space() API Now that the checks are performed by the pci_epf_alloc_space() API, let's remove the superfluous checks in this driver. Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20240207213922.1796533-4-cassel@kernel.org [mani: reworded commit message] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-test.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 15bfa7d83489..981894e40681 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -841,12 +841,6 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf) } test_reg_size = test_reg_bar_size + msix_table_size + pba_size; - if (epc_features->bar_fixed_size[test_reg_bar]) { - if (test_reg_size > bar_size[test_reg_bar]) - return -ENOMEM; - test_reg_size = bar_size[test_reg_bar]; - } - base = pci_epf_alloc_space(epf, test_reg_size, test_reg_bar, epc_features, PRIMARY_INTERFACE); if (!base) { @@ -888,8 +882,6 @@ static void pci_epf_configure_bar(struct pci_epf *epf, bar_fixed_64bit = !!(epc_features->bar_fixed_64bit & (1 << i)); if (bar_fixed_64bit) epf_bar->flags |= PCI_BASE_ADDRESS_MEM_TYPE_64; - if (epc_features->bar_fixed_size[i]) - bar_size[i] = epc_features->bar_fixed_size[i]; } } -- cgit v1.2.3 From c795fd3f3622d276f3cdb7b64e6e6c4042585734 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 7 Feb 2024 22:39:17 +0100 Subject: PCI: endpoint: pci-epf-vntb: Remove superfluous checks for pci_epf_alloc_space() API Now that the checks are performed by the pci_epf_alloc_space() API, let's remove the superfluous checks in this driver. Signed-off-by: Niklas Cassel Reviewed-by: Frank Li Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20240207213922.1796533-5-cassel@kernel.org [mani: reworded the commit message] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index ba509d67188b..eda4b906868b 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -527,7 +527,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) static int epf_ntb_db_bar_init(struct epf_ntb *ntb) { const struct pci_epc_features *epc_features; - u32 align; struct device *dev = &ntb->epf->dev; int ret; struct pci_epf_bar *epf_bar; @@ -538,16 +537,6 @@ static int epf_ntb_db_bar_init(struct epf_ntb *ntb) epc_features = pci_epc_get_features(ntb->epf->epc, ntb->epf->func_no, ntb->epf->vfunc_no); - align = epc_features->align; - - if (size < 128) - size = 128; - - if (align) - size = ALIGN(size, align); - else - size = roundup_pow_of_two(size); - barno = ntb->epf_ntb_bar[BAR_DB]; mw_addr = pci_epf_alloc_space(ntb->epf, size, barno, epc_features, 0); -- cgit v1.2.3 From ae874027524c537a15e8d6f14ff69b855bc13ca8 Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Wed, 31 Jan 2024 10:00:21 +0100 Subject: PCI: Move pci_iomap.c to drivers/pci/ The entirety of pci_iomap.c is guarded by an #ifdef CONFIG_PCI. It, consequently, does not belong to lib/ because it is not generic infrastructure. Move pci_iomap.c to drivers/pci/ and implement the necessary changes to Makefiles and Kconfigs. Update MAINTAINERS file. Update Documentation. Link: https://lore.kernel.org/r/20240131090023.12331-3-pstanner@redhat.com [bhelgaas: squash in https://lore.kernel.org/r/20240212150934.24559-1-pstanner@redhat.com] Suggested-by: Danilo Krummrich Signed-off-by: Philipp Stanner Signed-off-by: Bjorn Helgaas Reviewed-by: Arnd Bergmann --- Documentation/driver-api/device-io.rst | 3 - Documentation/driver-api/pci/pci.rst | 3 + MAINTAINERS | 1 - drivers/pci/Kconfig | 5 + drivers/pci/Makefile | 1 + drivers/pci/iomap.c | 177 ++++++++++++++++++++++++++++++++ lib/Kconfig | 3 - lib/Makefile | 1 - lib/pci_iomap.c | 180 --------------------------------- 9 files changed, 186 insertions(+), 188 deletions(-) create mode 100644 drivers/pci/iomap.c delete mode 100644 lib/pci_iomap.c (limited to 'drivers') diff --git a/Documentation/driver-api/device-io.rst b/Documentation/driver-api/device-io.rst index d55384b106bd..5c7e8194bef9 100644 --- a/Documentation/driver-api/device-io.rst +++ b/Documentation/driver-api/device-io.rst @@ -517,6 +517,3 @@ Public Functions Provided .. kernel-doc:: arch/x86/include/asm/io.h :internal: - -.. kernel-doc:: lib/pci_iomap.c - :export: diff --git a/Documentation/driver-api/pci/pci.rst b/Documentation/driver-api/pci/pci.rst index 4843cfad4f60..bacf23bf1343 100644 --- a/Documentation/driver-api/pci/pci.rst +++ b/Documentation/driver-api/pci/pci.rst @@ -4,6 +4,9 @@ PCI Support Library .. kernel-doc:: drivers/pci/pci.c :export: +.. kernel-doc:: drivers/pci/iomap.c + :export: + .. kernel-doc:: drivers/pci/pci-driver.c :export: diff --git a/MAINTAINERS b/MAINTAINERS index 8d1052fa6a69..395fcaad63e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16954,7 +16954,6 @@ F: include/asm-generic/pci* F: include/linux/of_pci.h F: include/linux/pci* F: include/uapi/linux/pci* -F: lib/pci* PCIE DRIVER FOR AMAZON ANNAPURNA LABS M: Jonathan Chocron diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 74147262625b..d35001589d88 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -13,6 +13,11 @@ config FORCE_PCI select HAVE_PCI select PCI +# select this to provide a generic PCI iomap, +# without PCI itself having to be defined +config GENERIC_PCI_IOMAP + bool + menuconfig PCI bool "PCI support" depends on HAVE_PCI diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index cc8b4e01e29d..64dcedccfc87 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -14,6 +14,7 @@ ifdef CONFIG_PCI obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSFS) += slot.o obj-$(CONFIG_ACPI) += pci-acpi.o +obj-$(CONFIG_GENERIC_PCI_IOMAP) += iomap.o endif obj-$(CONFIG_OF) += of.o diff --git a/drivers/pci/iomap.c b/drivers/pci/iomap.c new file mode 100644 index 000000000000..c9725428e387 --- /dev/null +++ b/drivers/pci/iomap.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implement the default iomap interfaces + * + * (C) Copyright 2004 Linus Torvalds + */ +#include +#include + +#include + +/** + * pci_iomap_range - create a virtual mapping cookie for a PCI BAR + * @dev: PCI device that owns the BAR + * @bar: BAR number + * @offset: map memory at the given offset in BAR + * @maxlen: max length of the memory to map + * + * Using this function you will get a __iomem address to your device BAR. + * You can access it using ioread*() and iowrite*(). These functions hide + * the details if this is a MMIO or PIO address space and will just do what + * you expect from them in the correct way. + * + * @maxlen specifies the maximum length to map. If you want to get access to + * the complete BAR from offset to the end, pass %0 here. + * */ +void __iomem *pci_iomap_range(struct pci_dev *dev, + int bar, + unsigned long offset, + unsigned long maxlen) +{ + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + unsigned long flags = pci_resource_flags(dev, bar); + + if (len <= offset || !start) + return NULL; + len -= offset; + start += offset; + if (maxlen && len > maxlen) + len = maxlen; + if (flags & IORESOURCE_IO) + return __pci_ioport_map(dev, start, len); + if (flags & IORESOURCE_MEM) + return ioremap(start, len); + /* What? */ + return NULL; +} +EXPORT_SYMBOL(pci_iomap_range); + +/** + * pci_iomap_wc_range - create a virtual WC mapping cookie for a PCI BAR + * @dev: PCI device that owns the BAR + * @bar: BAR number + * @offset: map memory at the given offset in BAR + * @maxlen: max length of the memory to map + * + * Using this function you will get a __iomem address to your device BAR. + * You can access it using ioread*() and iowrite*(). These functions hide + * the details if this is a MMIO or PIO address space and will just do what + * you expect from them in the correct way. When possible write combining + * is used. + * + * @maxlen specifies the maximum length to map. If you want to get access to + * the complete BAR from offset to the end, pass %0 here. + * */ +void __iomem *pci_iomap_wc_range(struct pci_dev *dev, + int bar, + unsigned long offset, + unsigned long maxlen) +{ + resource_size_t start = pci_resource_start(dev, bar); + resource_size_t len = pci_resource_len(dev, bar); + unsigned long flags = pci_resource_flags(dev, bar); + + + if (flags & IORESOURCE_IO) + return NULL; + + if (len <= offset || !start) + return NULL; + + len -= offset; + start += offset; + if (maxlen && len > maxlen) + len = maxlen; + + if (flags & IORESOURCE_MEM) + return ioremap_wc(start, len); + + /* What? */ + return NULL; +} +EXPORT_SYMBOL_GPL(pci_iomap_wc_range); + +/** + * pci_iomap - create a virtual mapping cookie for a PCI BAR + * @dev: PCI device that owns the BAR + * @bar: BAR number + * @maxlen: length of the memory to map + * + * Using this function you will get a __iomem address to your device BAR. + * You can access it using ioread*() and iowrite*(). These functions hide + * the details if this is a MMIO or PIO address space and will just do what + * you expect from them in the correct way. + * + * @maxlen specifies the maximum length to map. If you want to get access to + * the complete BAR without checking for its length first, pass %0 here. + * */ +void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen) +{ + return pci_iomap_range(dev, bar, 0, maxlen); +} +EXPORT_SYMBOL(pci_iomap); + +/** + * pci_iomap_wc - create a virtual WC mapping cookie for a PCI BAR + * @dev: PCI device that owns the BAR + * @bar: BAR number + * @maxlen: length of the memory to map + * + * Using this function you will get a __iomem address to your device BAR. + * You can access it using ioread*() and iowrite*(). These functions hide + * the details if this is a MMIO or PIO address space and will just do what + * you expect from them in the correct way. When possible write combining + * is used. + * + * @maxlen specifies the maximum length to map. If you want to get access to + * the complete BAR without checking for its length first, pass %0 here. + * */ +void __iomem *pci_iomap_wc(struct pci_dev *dev, int bar, unsigned long maxlen) +{ + return pci_iomap_wc_range(dev, bar, 0, maxlen); +} +EXPORT_SYMBOL_GPL(pci_iomap_wc); + +/* + * pci_iounmap() somewhat illogically comes from lib/iomap.c for the + * CONFIG_GENERIC_IOMAP case, because that's the code that knows about + * the different IOMAP ranges. + * + * But if the architecture does not use the generic iomap code, and if + * it has _not_ defined it's own private pci_iounmap function, we define + * it here. + * + * NOTE! This default implementation assumes that if the architecture + * support ioport mapping (HAS_IOPORT_MAP), the ioport mapping will + * be fixed to the range [ PCI_IOBASE, PCI_IOBASE+IO_SPACE_LIMIT [, + * and does not need unmapping with 'ioport_unmap()'. + * + * If you have different rules for your architecture, you need to + * implement your own pci_iounmap() that knows the rules for where + * and how IO vs MEM get mapped. + * + * This code is odd, and the ARCH_HAS/ARCH_WANTS #define logic comes + * from legacy header file behavior. In particular, + * it would seem to make sense to do the iounmap(p) for the non-IO-space + * case here regardless, but that's not what the old header file code + * did. Probably incorrectly, but this is meant to be bug-for-bug + * compatible. + */ +#if defined(ARCH_WANTS_GENERIC_PCI_IOUNMAP) + +void pci_iounmap(struct pci_dev *dev, void __iomem *p) +{ +#ifdef ARCH_HAS_GENERIC_IOPORT_MAP + uintptr_t start = (uintptr_t) PCI_IOBASE; + uintptr_t addr = (uintptr_t) p; + + if (addr >= start && addr < start + IO_SPACE_LIMIT) + return; +#endif + iounmap(p); +} +EXPORT_SYMBOL(pci_iounmap); + +#endif /* ARCH_WANTS_GENERIC_PCI_IOUNMAP */ diff --git a/lib/Kconfig b/lib/Kconfig index 5ddda7c2ed9b..4557bb8a5256 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -70,9 +70,6 @@ source "lib/math/Kconfig" config NO_GENERIC_PCI_IOPORT_MAP bool -config GENERIC_PCI_IOMAP - bool - config GENERIC_IOMAP bool select GENERIC_PCI_IOMAP diff --git a/lib/Makefile b/lib/Makefile index 6b09731d8e61..0800289ec6c5 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -153,7 +153,6 @@ CFLAGS_debug_info.o += $(call cc-option, -femit-struct-debug-detailed=any) obj-y += math/ crypto/ obj-$(CONFIG_GENERIC_IOMAP) += iomap.o -obj-$(CONFIG_GENERIC_PCI_IOMAP) += pci_iomap.o obj-$(CONFIG_HAS_IOMEM) += iomap_copy.o devres.o obj-$(CONFIG_CHECK_SIGNATURE) += check_signature.o obj-$(CONFIG_DEBUG_LOCKING_API_SELFTESTS) += locking-selftest.o diff --git a/lib/pci_iomap.c b/lib/pci_iomap.c deleted file mode 100644 index 2829ddb0e316..000000000000 --- a/lib/pci_iomap.c +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Implement the default iomap interfaces - * - * (C) Copyright 2004 Linus Torvalds - */ -#include -#include - -#include - -#ifdef CONFIG_PCI -/** - * pci_iomap_range - create a virtual mapping cookie for a PCI BAR - * @dev: PCI device that owns the BAR - * @bar: BAR number - * @offset: map memory at the given offset in BAR - * @maxlen: max length of the memory to map - * - * Using this function you will get a __iomem address to your device BAR. - * You can access it using ioread*() and iowrite*(). These functions hide - * the details if this is a MMIO or PIO address space and will just do what - * you expect from them in the correct way. - * - * @maxlen specifies the maximum length to map. If you want to get access to - * the complete BAR from offset to the end, pass %0 here. - * */ -void __iomem *pci_iomap_range(struct pci_dev *dev, - int bar, - unsigned long offset, - unsigned long maxlen) -{ - resource_size_t start = pci_resource_start(dev, bar); - resource_size_t len = pci_resource_len(dev, bar); - unsigned long flags = pci_resource_flags(dev, bar); - - if (len <= offset || !start) - return NULL; - len -= offset; - start += offset; - if (maxlen && len > maxlen) - len = maxlen; - if (flags & IORESOURCE_IO) - return __pci_ioport_map(dev, start, len); - if (flags & IORESOURCE_MEM) - return ioremap(start, len); - /* What? */ - return NULL; -} -EXPORT_SYMBOL(pci_iomap_range); - -/** - * pci_iomap_wc_range - create a virtual WC mapping cookie for a PCI BAR - * @dev: PCI device that owns the BAR - * @bar: BAR number - * @offset: map memory at the given offset in BAR - * @maxlen: max length of the memory to map - * - * Using this function you will get a __iomem address to your device BAR. - * You can access it using ioread*() and iowrite*(). These functions hide - * the details if this is a MMIO or PIO address space and will just do what - * you expect from them in the correct way. When possible write combining - * is used. - * - * @maxlen specifies the maximum length to map. If you want to get access to - * the complete BAR from offset to the end, pass %0 here. - * */ -void __iomem *pci_iomap_wc_range(struct pci_dev *dev, - int bar, - unsigned long offset, - unsigned long maxlen) -{ - resource_size_t start = pci_resource_start(dev, bar); - resource_size_t len = pci_resource_len(dev, bar); - unsigned long flags = pci_resource_flags(dev, bar); - - - if (flags & IORESOURCE_IO) - return NULL; - - if (len <= offset || !start) - return NULL; - - len -= offset; - start += offset; - if (maxlen && len > maxlen) - len = maxlen; - - if (flags & IORESOURCE_MEM) - return ioremap_wc(start, len); - - /* What? */ - return NULL; -} -EXPORT_SYMBOL_GPL(pci_iomap_wc_range); - -/** - * pci_iomap - create a virtual mapping cookie for a PCI BAR - * @dev: PCI device that owns the BAR - * @bar: BAR number - * @maxlen: length of the memory to map - * - * Using this function you will get a __iomem address to your device BAR. - * You can access it using ioread*() and iowrite*(). These functions hide - * the details if this is a MMIO or PIO address space and will just do what - * you expect from them in the correct way. - * - * @maxlen specifies the maximum length to map. If you want to get access to - * the complete BAR without checking for its length first, pass %0 here. - * */ -void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen) -{ - return pci_iomap_range(dev, bar, 0, maxlen); -} -EXPORT_SYMBOL(pci_iomap); - -/** - * pci_iomap_wc - create a virtual WC mapping cookie for a PCI BAR - * @dev: PCI device that owns the BAR - * @bar: BAR number - * @maxlen: length of the memory to map - * - * Using this function you will get a __iomem address to your device BAR. - * You can access it using ioread*() and iowrite*(). These functions hide - * the details if this is a MMIO or PIO address space and will just do what - * you expect from them in the correct way. When possible write combining - * is used. - * - * @maxlen specifies the maximum length to map. If you want to get access to - * the complete BAR without checking for its length first, pass %0 here. - * */ -void __iomem *pci_iomap_wc(struct pci_dev *dev, int bar, unsigned long maxlen) -{ - return pci_iomap_wc_range(dev, bar, 0, maxlen); -} -EXPORT_SYMBOL_GPL(pci_iomap_wc); - -/* - * pci_iounmap() somewhat illogically comes from lib/iomap.c for the - * CONFIG_GENERIC_IOMAP case, because that's the code that knows about - * the different IOMAP ranges. - * - * But if the architecture does not use the generic iomap code, and if - * it has _not_ defined it's own private pci_iounmap function, we define - * it here. - * - * NOTE! This default implementation assumes that if the architecture - * support ioport mapping (HAS_IOPORT_MAP), the ioport mapping will - * be fixed to the range [ PCI_IOBASE, PCI_IOBASE+IO_SPACE_LIMIT [, - * and does not need unmapping with 'ioport_unmap()'. - * - * If you have different rules for your architecture, you need to - * implement your own pci_iounmap() that knows the rules for where - * and how IO vs MEM get mapped. - * - * This code is odd, and the ARCH_HAS/ARCH_WANTS #define logic comes - * from legacy header file behavior. In particular, - * it would seem to make sense to do the iounmap(p) for the non-IO-space - * case here regardless, but that's not what the old header file code - * did. Probably incorrectly, but this is meant to be bug-for-bug - * compatible. - */ -#if defined(ARCH_WANTS_GENERIC_PCI_IOUNMAP) - -void pci_iounmap(struct pci_dev *dev, void __iomem *p) -{ -#ifdef ARCH_HAS_GENERIC_IOPORT_MAP - uintptr_t start = (uintptr_t) PCI_IOBASE; - uintptr_t addr = (uintptr_t) p; - - if (addr >= start && addr < start + IO_SPACE_LIMIT) - return; -#endif - iounmap(p); -} -EXPORT_SYMBOL(pci_iounmap); - -#endif /* ARCH_WANTS_GENERIC_PCI_IOUNMAP */ - -#endif /* CONFIG_PCI */ -- cgit v1.2.3 From acc2364fe661069637c60ed5bbd32ea2a2c5ef61 Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Wed, 31 Jan 2024 10:00:22 +0100 Subject: PCI: Move PCI-specific devres code to drivers/pci/ The pcim_*() functions in lib/devres.c are guarded by an #ifdef CONFIG_PCI and, thus, don't belong to this file. They are only ever used for PCI and are not generic infrastructure. Move all pcim_*() functions in lib/devres.c to drivers/pci/devres.c. Adjust the Makefile. Add drivers/pci/devres.c to Documentation. Link: https://lore.kernel.org/r/20240131090023.12331-4-pstanner@redhat.com Suggested-by: Danilo Krummrich Signed-off-by: Philipp Stanner Signed-off-by: Bjorn Helgaas --- Documentation/driver-api/pci/pci.rst | 3 + drivers/pci/Makefile | 2 +- drivers/pci/devres.c | 207 ++++++++++++++++++++++++++++++++++ lib/devres.c | 208 +---------------------------------- 4 files changed, 212 insertions(+), 208 deletions(-) create mode 100644 drivers/pci/devres.c (limited to 'drivers') diff --git a/Documentation/driver-api/pci/pci.rst b/Documentation/driver-api/pci/pci.rst index bacf23bf1343..aa40b1cc243b 100644 --- a/Documentation/driver-api/pci/pci.rst +++ b/Documentation/driver-api/pci/pci.rst @@ -7,6 +7,9 @@ PCI Support Library .. kernel-doc:: drivers/pci/iomap.c :export: +.. kernel-doc:: drivers/pci/devres.c + :export: + .. kernel-doc:: drivers/pci/pci-driver.c :export: diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 64dcedccfc87..ed65299b42b5 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ remove.o pci.o pci-driver.o search.o \ pci-sysfs.o rom.o setup-res.o irq.o vpd.o \ - setup-bus.o vc.o mmap.o setup-irq.o + setup-bus.o vc.o mmap.o setup-irq.o devres.o obj-$(CONFIG_PCI) += msi/ obj-$(CONFIG_PCI) += pcie/ diff --git a/drivers/pci/devres.c b/drivers/pci/devres.c new file mode 100644 index 000000000000..a3fd0d65cef1 --- /dev/null +++ b/drivers/pci/devres.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include "pci.h" + +/* + * PCI iomap devres + */ +#define PCIM_IOMAP_MAX PCI_STD_NUM_BARS + +struct pcim_iomap_devres { + void __iomem *table[PCIM_IOMAP_MAX]; +}; + +static void pcim_iomap_release(struct device *gendev, void *res) +{ + struct pci_dev *dev = to_pci_dev(gendev); + struct pcim_iomap_devres *this = res; + int i; + + for (i = 0; i < PCIM_IOMAP_MAX; i++) + if (this->table[i]) + pci_iounmap(dev, this->table[i]); +} + +/** + * pcim_iomap_table - access iomap allocation table + * @pdev: PCI device to access iomap table for + * + * Access iomap allocation table for @dev. If iomap table doesn't + * exist and @pdev is managed, it will be allocated. All iomaps + * recorded in the iomap table are automatically unmapped on driver + * detach. + * + * This function might sleep when the table is first allocated but can + * be safely called without context and guaranteed to succeed once + * allocated. + */ +void __iomem * const *pcim_iomap_table(struct pci_dev *pdev) +{ + struct pcim_iomap_devres *dr, *new_dr; + + dr = devres_find(&pdev->dev, pcim_iomap_release, NULL, NULL); + if (dr) + return dr->table; + + new_dr = devres_alloc_node(pcim_iomap_release, sizeof(*new_dr), GFP_KERNEL, + dev_to_node(&pdev->dev)); + if (!new_dr) + return NULL; + dr = devres_get(&pdev->dev, new_dr, NULL, NULL); + return dr->table; +} +EXPORT_SYMBOL(pcim_iomap_table); + +/** + * pcim_iomap - Managed pcim_iomap() + * @pdev: PCI device to iomap for + * @bar: BAR to iomap + * @maxlen: Maximum length of iomap + * + * Managed pci_iomap(). Map is automatically unmapped on driver + * detach. + */ +void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen) +{ + void __iomem **tbl; + + BUG_ON(bar >= PCIM_IOMAP_MAX); + + tbl = (void __iomem **)pcim_iomap_table(pdev); + if (!tbl || tbl[bar]) /* duplicate mappings not allowed */ + return NULL; + + tbl[bar] = pci_iomap(pdev, bar, maxlen); + return tbl[bar]; +} +EXPORT_SYMBOL(pcim_iomap); + +/** + * pcim_iounmap - Managed pci_iounmap() + * @pdev: PCI device to iounmap for + * @addr: Address to unmap + * + * Managed pci_iounmap(). @addr must have been mapped using pcim_iomap(). + */ +void pcim_iounmap(struct pci_dev *pdev, void __iomem *addr) +{ + void __iomem **tbl; + int i; + + pci_iounmap(pdev, addr); + + tbl = (void __iomem **)pcim_iomap_table(pdev); + BUG_ON(!tbl); + + for (i = 0; i < PCIM_IOMAP_MAX; i++) + if (tbl[i] == addr) { + tbl[i] = NULL; + return; + } + WARN_ON(1); +} +EXPORT_SYMBOL(pcim_iounmap); + +/** + * pcim_iomap_regions - Request and iomap PCI BARs + * @pdev: PCI device to map IO resources for + * @mask: Mask of BARs to request and iomap + * @name: Name used when requesting regions + * + * Request and iomap regions specified by @mask. + */ +int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name) +{ + void __iomem * const *iomap; + int i, rc; + + iomap = pcim_iomap_table(pdev); + if (!iomap) + return -ENOMEM; + + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + unsigned long len; + + if (!(mask & (1 << i))) + continue; + + rc = -EINVAL; + len = pci_resource_len(pdev, i); + if (!len) + goto err_inval; + + rc = pci_request_region(pdev, i, name); + if (rc) + goto err_inval; + + rc = -ENOMEM; + if (!pcim_iomap(pdev, i, 0)) + goto err_region; + } + + return 0; + + err_region: + pci_release_region(pdev, i); + err_inval: + while (--i >= 0) { + if (!(mask & (1 << i))) + continue; + pcim_iounmap(pdev, iomap[i]); + pci_release_region(pdev, i); + } + + return rc; +} +EXPORT_SYMBOL(pcim_iomap_regions); + +/** + * pcim_iomap_regions_request_all - Request all BARs and iomap specified ones + * @pdev: PCI device to map IO resources for + * @mask: Mask of BARs to iomap + * @name: Name used when requesting regions + * + * Request all PCI BARs and iomap regions specified by @mask. + */ +int pcim_iomap_regions_request_all(struct pci_dev *pdev, int mask, + const char *name) +{ + int request_mask = ((1 << 6) - 1) & ~mask; + int rc; + + rc = pci_request_selected_regions(pdev, request_mask, name); + if (rc) + return rc; + + rc = pcim_iomap_regions(pdev, mask, name); + if (rc) + pci_release_selected_regions(pdev, request_mask); + return rc; +} +EXPORT_SYMBOL(pcim_iomap_regions_request_all); + +/** + * pcim_iounmap_regions - Unmap and release PCI BARs + * @pdev: PCI device to map IO resources for + * @mask: Mask of BARs to unmap and release + * + * Unmap and release regions specified by @mask. + */ +void pcim_iounmap_regions(struct pci_dev *pdev, int mask) +{ + void __iomem * const *iomap; + int i; + + iomap = pcim_iomap_table(pdev); + if (!iomap) + return; + + for (i = 0; i < PCIM_IOMAP_MAX; i++) { + if (!(mask & (1 << i))) + continue; + + pcim_iounmap(pdev, iomap[i]); + pci_release_region(pdev, i); + } +} +EXPORT_SYMBOL(pcim_iounmap_regions); diff --git a/lib/devres.c b/lib/devres.c index c44f104b58d5..fe0c63caeb68 100644 --- a/lib/devres.c +++ b/lib/devres.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include -#include #include #include #include @@ -311,212 +311,6 @@ void devm_ioport_unmap(struct device *dev, void __iomem *addr) EXPORT_SYMBOL(devm_ioport_unmap); #endif /* CONFIG_HAS_IOPORT_MAP */ -#ifdef CONFIG_PCI -/* - * PCI iomap devres - */ -#define PCIM_IOMAP_MAX PCI_STD_NUM_BARS - -struct pcim_iomap_devres { - void __iomem *table[PCIM_IOMAP_MAX]; -}; - -static void pcim_iomap_release(struct device *gendev, void *res) -{ - struct pci_dev *dev = to_pci_dev(gendev); - struct pcim_iomap_devres *this = res; - int i; - - for (i = 0; i < PCIM_IOMAP_MAX; i++) - if (this->table[i]) - pci_iounmap(dev, this->table[i]); -} - -/** - * pcim_iomap_table - access iomap allocation table - * @pdev: PCI device to access iomap table for - * - * Access iomap allocation table for @dev. If iomap table doesn't - * exist and @pdev is managed, it will be allocated. All iomaps - * recorded in the iomap table are automatically unmapped on driver - * detach. - * - * This function might sleep when the table is first allocated but can - * be safely called without context and guaranteed to succeed once - * allocated. - */ -void __iomem * const *pcim_iomap_table(struct pci_dev *pdev) -{ - struct pcim_iomap_devres *dr, *new_dr; - - dr = devres_find(&pdev->dev, pcim_iomap_release, NULL, NULL); - if (dr) - return dr->table; - - new_dr = devres_alloc_node(pcim_iomap_release, sizeof(*new_dr), GFP_KERNEL, - dev_to_node(&pdev->dev)); - if (!new_dr) - return NULL; - dr = devres_get(&pdev->dev, new_dr, NULL, NULL); - return dr->table; -} -EXPORT_SYMBOL(pcim_iomap_table); - -/** - * pcim_iomap - Managed pcim_iomap() - * @pdev: PCI device to iomap for - * @bar: BAR to iomap - * @maxlen: Maximum length of iomap - * - * Managed pci_iomap(). Map is automatically unmapped on driver - * detach. - */ -void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen) -{ - void __iomem **tbl; - - BUG_ON(bar >= PCIM_IOMAP_MAX); - - tbl = (void __iomem **)pcim_iomap_table(pdev); - if (!tbl || tbl[bar]) /* duplicate mappings not allowed */ - return NULL; - - tbl[bar] = pci_iomap(pdev, bar, maxlen); - return tbl[bar]; -} -EXPORT_SYMBOL(pcim_iomap); - -/** - * pcim_iounmap - Managed pci_iounmap() - * @pdev: PCI device to iounmap for - * @addr: Address to unmap - * - * Managed pci_iounmap(). @addr must have been mapped using pcim_iomap(). - */ -void pcim_iounmap(struct pci_dev *pdev, void __iomem *addr) -{ - void __iomem **tbl; - int i; - - pci_iounmap(pdev, addr); - - tbl = (void __iomem **)pcim_iomap_table(pdev); - BUG_ON(!tbl); - - for (i = 0; i < PCIM_IOMAP_MAX; i++) - if (tbl[i] == addr) { - tbl[i] = NULL; - return; - } - WARN_ON(1); -} -EXPORT_SYMBOL(pcim_iounmap); - -/** - * pcim_iomap_regions - Request and iomap PCI BARs - * @pdev: PCI device to map IO resources for - * @mask: Mask of BARs to request and iomap - * @name: Name used when requesting regions - * - * Request and iomap regions specified by @mask. - */ -int pcim_iomap_regions(struct pci_dev *pdev, int mask, const char *name) -{ - void __iomem * const *iomap; - int i, rc; - - iomap = pcim_iomap_table(pdev); - if (!iomap) - return -ENOMEM; - - for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { - unsigned long len; - - if (!(mask & (1 << i))) - continue; - - rc = -EINVAL; - len = pci_resource_len(pdev, i); - if (!len) - goto err_inval; - - rc = pci_request_region(pdev, i, name); - if (rc) - goto err_inval; - - rc = -ENOMEM; - if (!pcim_iomap(pdev, i, 0)) - goto err_region; - } - - return 0; - - err_region: - pci_release_region(pdev, i); - err_inval: - while (--i >= 0) { - if (!(mask & (1 << i))) - continue; - pcim_iounmap(pdev, iomap[i]); - pci_release_region(pdev, i); - } - - return rc; -} -EXPORT_SYMBOL(pcim_iomap_regions); - -/** - * pcim_iomap_regions_request_all - Request all BARs and iomap specified ones - * @pdev: PCI device to map IO resources for - * @mask: Mask of BARs to iomap - * @name: Name used when requesting regions - * - * Request all PCI BARs and iomap regions specified by @mask. - */ -int pcim_iomap_regions_request_all(struct pci_dev *pdev, int mask, - const char *name) -{ - int request_mask = ((1 << 6) - 1) & ~mask; - int rc; - - rc = pci_request_selected_regions(pdev, request_mask, name); - if (rc) - return rc; - - rc = pcim_iomap_regions(pdev, mask, name); - if (rc) - pci_release_selected_regions(pdev, request_mask); - return rc; -} -EXPORT_SYMBOL(pcim_iomap_regions_request_all); - -/** - * pcim_iounmap_regions - Unmap and release PCI BARs - * @pdev: PCI device to map IO resources for - * @mask: Mask of BARs to unmap and release - * - * Unmap and release regions specified by @mask. - */ -void pcim_iounmap_regions(struct pci_dev *pdev, int mask) -{ - void __iomem * const *iomap; - int i; - - iomap = pcim_iomap_table(pdev); - if (!iomap) - return; - - for (i = 0; i < PCIM_IOMAP_MAX; i++) { - if (!(mask & (1 << i))) - continue; - - pcim_iounmap(pdev, iomap[i]); - pci_release_region(pdev, i); - } -} -EXPORT_SYMBOL(pcim_iounmap_regions); -#endif /* CONFIG_PCI */ - static void devm_arch_phys_ac_add_release(struct device *dev, void *res) { arch_phys_wc_del(*((int *)res)); -- cgit v1.2.3 From 815a3909ead7440e2827042e5ec618f4396f022c Mon Sep 17 00:00:00 2001 From: Philipp Stanner Date: Wed, 31 Jan 2024 10:00:23 +0100 Subject: PCI: Move devres code from pci.c to devres.c The file pci.c is very large and contains a number of devres functions. These functions should now reside in devres.c. Move as much devres-specific code from pci.c to devres.c as possible. There are a few callers left in pci.c that do devres operations. These should be ported in the future. Add corresponding TODOs. The reason they are not moved right now in this commit is that PCI's devres currently implements a sort of "hybrid-mode": pci_request_region(), for instance, does not have a corresponding pcim_ equivalent, yet. Instead, the function can be made managed by previously calling pcim_enable_device() (instead of pci_enable_device()). This makes it unreasonable to move pci_request_region() to devres.c. Moving the functions would require changes to PCI's API and is, therefore, left for future work. In summary, this commit serves as a preparation step for a following patch series that will cleanly separate the PCI's managed and unmanaged API. Link: https://lore.kernel.org/r/20240131090023.12331-5-pstanner@redhat.com Suggested-by: Danilo Krummrich Signed-off-by: Philipp Stanner Signed-off-by: Bjorn Helgaas --- drivers/pci/devres.c | 241 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.c | 249 --------------------------------------------------- drivers/pci/pci.h | 21 +++++ 3 files changed, 262 insertions(+), 249 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/devres.c b/drivers/pci/devres.c index a3fd0d65cef1..2c562b9eaf80 100644 --- a/drivers/pci/devres.c +++ b/drivers/pci/devres.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include "pci.h" @@ -11,6 +12,246 @@ struct pcim_iomap_devres { void __iomem *table[PCIM_IOMAP_MAX]; }; + +static void devm_pci_unmap_iospace(struct device *dev, void *ptr) +{ + struct resource **res = ptr; + + pci_unmap_iospace(*res); +} + +/** + * devm_pci_remap_iospace - Managed pci_remap_iospace() + * @dev: Generic device to remap IO address for + * @res: Resource describing the I/O space + * @phys_addr: physical address of range to be mapped + * + * Managed pci_remap_iospace(). Map is automatically unmapped on driver + * detach. + */ +int devm_pci_remap_iospace(struct device *dev, const struct resource *res, + phys_addr_t phys_addr) +{ + const struct resource **ptr; + int error; + + ptr = devres_alloc(devm_pci_unmap_iospace, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + error = pci_remap_iospace(res, phys_addr); + if (error) { + devres_free(ptr); + } else { + *ptr = res; + devres_add(dev, ptr); + } + + return error; +} +EXPORT_SYMBOL(devm_pci_remap_iospace); + +/** + * devm_pci_remap_cfgspace - Managed pci_remap_cfgspace() + * @dev: Generic device to remap IO address for + * @offset: Resource address to map + * @size: Size of map + * + * Managed pci_remap_cfgspace(). Map is automatically unmapped on driver + * detach. + */ +void __iomem *devm_pci_remap_cfgspace(struct device *dev, + resource_size_t offset, + resource_size_t size) +{ + void __iomem **ptr, *addr; + + ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return NULL; + + addr = pci_remap_cfgspace(offset, size); + if (addr) { + *ptr = addr; + devres_add(dev, ptr); + } else + devres_free(ptr); + + return addr; +} +EXPORT_SYMBOL(devm_pci_remap_cfgspace); + +/** + * devm_pci_remap_cfg_resource - check, request region and ioremap cfg resource + * @dev: generic device to handle the resource for + * @res: configuration space resource to be handled + * + * Checks that a resource is a valid memory region, requests the memory + * region and ioremaps with pci_remap_cfgspace() API that ensures the + * proper PCI configuration space memory attributes are guaranteed. + * + * All operations are managed and will be undone on driver detach. + * + * Returns a pointer to the remapped memory or an ERR_PTR() encoded error code + * on failure. Usage example:: + * + * res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + * base = devm_pci_remap_cfg_resource(&pdev->dev, res); + * if (IS_ERR(base)) + * return PTR_ERR(base); + */ +void __iomem *devm_pci_remap_cfg_resource(struct device *dev, + struct resource *res) +{ + resource_size_t size; + const char *name; + void __iomem *dest_ptr; + + BUG_ON(!dev); + + if (!res || resource_type(res) != IORESOURCE_MEM) { + dev_err(dev, "invalid resource\n"); + return IOMEM_ERR_PTR(-EINVAL); + } + + size = resource_size(res); + + if (res->name) + name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", dev_name(dev), + res->name); + else + name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); + if (!name) + return IOMEM_ERR_PTR(-ENOMEM); + + if (!devm_request_mem_region(dev, res->start, size, name)) { + dev_err(dev, "can't request region for resource %pR\n", res); + return IOMEM_ERR_PTR(-EBUSY); + } + + dest_ptr = devm_pci_remap_cfgspace(dev, res->start, size); + if (!dest_ptr) { + dev_err(dev, "ioremap failed for resource %pR\n", res); + devm_release_mem_region(dev, res->start, size); + dest_ptr = IOMEM_ERR_PTR(-ENOMEM); + } + + return dest_ptr; +} +EXPORT_SYMBOL(devm_pci_remap_cfg_resource); + +/** + * pcim_set_mwi - a device-managed pci_set_mwi() + * @dev: the PCI device for which MWI is enabled + * + * Managed pci_set_mwi(). + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int pcim_set_mwi(struct pci_dev *dev) +{ + struct pci_devres *dr; + + dr = find_pci_dr(dev); + if (!dr) + return -ENOMEM; + + dr->mwi = 1; + return pci_set_mwi(dev); +} +EXPORT_SYMBOL(pcim_set_mwi); + + +static void pcim_release(struct device *gendev, void *res) +{ + struct pci_dev *dev = to_pci_dev(gendev); + struct pci_devres *this = res; + int i; + + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) + if (this->region_mask & (1 << i)) + pci_release_region(dev, i); + + if (this->mwi) + pci_clear_mwi(dev); + + if (this->restore_intx) + pci_intx(dev, this->orig_intx); + + if (this->enabled && !this->pinned) + pci_disable_device(dev); +} + +/* + * TODO: After the last four callers in pci.c are ported, find_pci_dr() + * needs to be made static again. + */ +struct pci_devres *find_pci_dr(struct pci_dev *pdev) +{ + if (pci_is_managed(pdev)) + return devres_find(&pdev->dev, pcim_release, NULL, NULL); + return NULL; +} + +static struct pci_devres *get_pci_dr(struct pci_dev *pdev) +{ + struct pci_devres *dr, *new_dr; + + dr = devres_find(&pdev->dev, pcim_release, NULL, NULL); + if (dr) + return dr; + + new_dr = devres_alloc(pcim_release, sizeof(*new_dr), GFP_KERNEL); + if (!new_dr) + return NULL; + return devres_get(&pdev->dev, new_dr, NULL, NULL); +} + +/** + * pcim_enable_device - Managed pci_enable_device() + * @pdev: PCI device to be initialized + * + * Managed pci_enable_device(). + */ +int pcim_enable_device(struct pci_dev *pdev) +{ + struct pci_devres *dr; + int rc; + + dr = get_pci_dr(pdev); + if (unlikely(!dr)) + return -ENOMEM; + if (dr->enabled) + return 0; + + rc = pci_enable_device(pdev); + if (!rc) { + pdev->is_managed = 1; + dr->enabled = 1; + } + return rc; +} +EXPORT_SYMBOL(pcim_enable_device); + +/** + * pcim_pin_device - Pin managed PCI device + * @pdev: PCI device to pin + * + * Pin managed PCI device @pdev. Pinned device won't be disabled on + * driver detach. @pdev must have been enabled with + * pcim_enable_device(). + */ +void pcim_pin_device(struct pci_dev *pdev) +{ + struct pci_devres *dr; + + dr = find_pci_dr(pdev); + WARN_ON(!dr || !dr->enabled); + if (dr) + dr->pinned = 1; +} +EXPORT_SYMBOL(pcim_pin_device); + static void pcim_iomap_release(struct device *gendev, void *res) { struct pci_dev *dev = to_pci_dev(gendev); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d8f11a078924..19f18c3856e8 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2157,107 +2157,6 @@ int pci_enable_device(struct pci_dev *dev) } EXPORT_SYMBOL(pci_enable_device); -/* - * Managed PCI resources. This manages device on/off, INTx/MSI/MSI-X - * on/off and BAR regions. pci_dev itself records MSI/MSI-X status, so - * there's no need to track it separately. pci_devres is initialized - * when a device is enabled using managed PCI device enable interface. - */ -struct pci_devres { - unsigned int enabled:1; - unsigned int pinned:1; - unsigned int orig_intx:1; - unsigned int restore_intx:1; - unsigned int mwi:1; - u32 region_mask; -}; - -static void pcim_release(struct device *gendev, void *res) -{ - struct pci_dev *dev = to_pci_dev(gendev); - struct pci_devres *this = res; - int i; - - for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) - if (this->region_mask & (1 << i)) - pci_release_region(dev, i); - - if (this->mwi) - pci_clear_mwi(dev); - - if (this->restore_intx) - pci_intx(dev, this->orig_intx); - - if (this->enabled && !this->pinned) - pci_disable_device(dev); -} - -static struct pci_devres *get_pci_dr(struct pci_dev *pdev) -{ - struct pci_devres *dr, *new_dr; - - dr = devres_find(&pdev->dev, pcim_release, NULL, NULL); - if (dr) - return dr; - - new_dr = devres_alloc(pcim_release, sizeof(*new_dr), GFP_KERNEL); - if (!new_dr) - return NULL; - return devres_get(&pdev->dev, new_dr, NULL, NULL); -} - -static struct pci_devres *find_pci_dr(struct pci_dev *pdev) -{ - if (pci_is_managed(pdev)) - return devres_find(&pdev->dev, pcim_release, NULL, NULL); - return NULL; -} - -/** - * pcim_enable_device - Managed pci_enable_device() - * @pdev: PCI device to be initialized - * - * Managed pci_enable_device(). - */ -int pcim_enable_device(struct pci_dev *pdev) -{ - struct pci_devres *dr; - int rc; - - dr = get_pci_dr(pdev); - if (unlikely(!dr)) - return -ENOMEM; - if (dr->enabled) - return 0; - - rc = pci_enable_device(pdev); - if (!rc) { - pdev->is_managed = 1; - dr->enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pcim_enable_device); - -/** - * pcim_pin_device - Pin managed PCI device - * @pdev: PCI device to pin - * - * Pin managed PCI device @pdev. Pinned device won't be disabled on - * driver detach. @pdev must have been enabled with - * pcim_enable_device(). - */ -void pcim_pin_device(struct pci_dev *pdev) -{ - struct pci_devres *dr; - - dr = find_pci_dr(pdev); - WARN_ON(!dr || !dr->enabled); - if (dr) - dr->pinned = 1; -} -EXPORT_SYMBOL(pcim_pin_device); - /* * pcibios_device_add - provide arch specific hooks when adding device dev * @dev: the PCI device being added @@ -4352,133 +4251,6 @@ void pci_unmap_iospace(struct resource *res) } EXPORT_SYMBOL(pci_unmap_iospace); -static void devm_pci_unmap_iospace(struct device *dev, void *ptr) -{ - struct resource **res = ptr; - - pci_unmap_iospace(*res); -} - -/** - * devm_pci_remap_iospace - Managed pci_remap_iospace() - * @dev: Generic device to remap IO address for - * @res: Resource describing the I/O space - * @phys_addr: physical address of range to be mapped - * - * Managed pci_remap_iospace(). Map is automatically unmapped on driver - * detach. - */ -int devm_pci_remap_iospace(struct device *dev, const struct resource *res, - phys_addr_t phys_addr) -{ - const struct resource **ptr; - int error; - - ptr = devres_alloc(devm_pci_unmap_iospace, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - error = pci_remap_iospace(res, phys_addr); - if (error) { - devres_free(ptr); - } else { - *ptr = res; - devres_add(dev, ptr); - } - - return error; -} -EXPORT_SYMBOL(devm_pci_remap_iospace); - -/** - * devm_pci_remap_cfgspace - Managed pci_remap_cfgspace() - * @dev: Generic device to remap IO address for - * @offset: Resource address to map - * @size: Size of map - * - * Managed pci_remap_cfgspace(). Map is automatically unmapped on driver - * detach. - */ -void __iomem *devm_pci_remap_cfgspace(struct device *dev, - resource_size_t offset, - resource_size_t size) -{ - void __iomem **ptr, *addr; - - ptr = devres_alloc(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - addr = pci_remap_cfgspace(offset, size); - if (addr) { - *ptr = addr; - devres_add(dev, ptr); - } else - devres_free(ptr); - - return addr; -} -EXPORT_SYMBOL(devm_pci_remap_cfgspace); - -/** - * devm_pci_remap_cfg_resource - check, request region and ioremap cfg resource - * @dev: generic device to handle the resource for - * @res: configuration space resource to be handled - * - * Checks that a resource is a valid memory region, requests the memory - * region and ioremaps with pci_remap_cfgspace() API that ensures the - * proper PCI configuration space memory attributes are guaranteed. - * - * All operations are managed and will be undone on driver detach. - * - * Returns a pointer to the remapped memory or an ERR_PTR() encoded error code - * on failure. Usage example:: - * - * res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - * base = devm_pci_remap_cfg_resource(&pdev->dev, res); - * if (IS_ERR(base)) - * return PTR_ERR(base); - */ -void __iomem *devm_pci_remap_cfg_resource(struct device *dev, - struct resource *res) -{ - resource_size_t size; - const char *name; - void __iomem *dest_ptr; - - BUG_ON(!dev); - - if (!res || resource_type(res) != IORESOURCE_MEM) { - dev_err(dev, "invalid resource\n"); - return IOMEM_ERR_PTR(-EINVAL); - } - - size = resource_size(res); - - if (res->name) - name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", dev_name(dev), - res->name); - else - name = devm_kstrdup(dev, dev_name(dev), GFP_KERNEL); - if (!name) - return IOMEM_ERR_PTR(-ENOMEM); - - if (!devm_request_mem_region(dev, res->start, size, name)) { - dev_err(dev, "can't request region for resource %pR\n", res); - return IOMEM_ERR_PTR(-EBUSY); - } - - dest_ptr = devm_pci_remap_cfgspace(dev, res->start, size); - if (!dest_ptr) { - dev_err(dev, "ioremap failed for resource %pR\n", res); - devm_release_mem_region(dev, res->start, size); - dest_ptr = IOMEM_ERR_PTR(-ENOMEM); - } - - return dest_ptr; -} -EXPORT_SYMBOL(devm_pci_remap_cfg_resource); - static void __pci_set_master(struct pci_dev *dev, bool enable) { u16 old_cmd, cmd; @@ -4628,27 +4400,6 @@ int pci_set_mwi(struct pci_dev *dev) } EXPORT_SYMBOL(pci_set_mwi); -/** - * pcim_set_mwi - a device-managed pci_set_mwi() - * @dev: the PCI device for which MWI is enabled - * - * Managed pci_set_mwi(). - * - * RETURNS: An appropriate -ERRNO error value on error, or zero for success. - */ -int pcim_set_mwi(struct pci_dev *dev) -{ - struct pci_devres *dr; - - dr = find_pci_dr(dev); - if (!dr) - return -ENOMEM; - - dr->mwi = 1; - return pci_set_mwi(dev); -} -EXPORT_SYMBOL(pcim_set_mwi); - /** * pci_try_set_mwi - enables memory-write-invalidate PCI transaction * @dev: the PCI device for which MWI is enabled diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 2336a8d1edab..f70066e67d52 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -797,6 +797,27 @@ static inline pci_power_t mid_pci_get_power_state(struct pci_dev *pdev) } #endif +/* + * Managed PCI resources. This manages device on/off, INTx/MSI/MSI-X + * on/off and BAR regions. pci_dev itself records MSI/MSI-X status, so + * there's no need to track it separately. pci_devres is initialized + * when a device is enabled using managed PCI device enable interface. + * + * TODO: Struct pci_devres and find_pci_dr() only need to be here because + * they're used in pci.c. Port or move these functions to devres.c and + * then remove them from here. + */ +struct pci_devres { + unsigned int enabled:1; + unsigned int pinned:1; + unsigned int orig_intx:1; + unsigned int restore_intx:1; + unsigned int mwi:1; + u32 region_mask; +}; + +struct pci_devres *find_pci_dr(struct pci_dev *pdev); + /* * Config Address for PCI Configuration Mechanism #1 * -- cgit v1.2.3 From aebfdfe39b9327a3077d0df8db3beb3160c9bdd0 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Fri, 1 Dec 2023 11:30:56 +0800 Subject: NTB: fix possible name leak in ntb_register_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If device_register() fails in ntb_register_device(), the device name allocated by dev_set_name() should be freed. As per the comment in device_register(), callers should use put_device() to give up the reference in the error path. So fix this by calling put_device() in the error path so that the name can be freed in kobject_cleanup(). As a result of this, put_device() in the error path of ntb_register_device() is removed and the actual error is returned. Fixes: a1bd3baeb2f1 ("NTB: Add NTB hardware abstraction layer") Signed-off-by: Yang Yingliang Reviewed-by: Ilpo Järvinen Reviewed-by: Manivannan Sadhasivam Reviewed-by: Dave Jiang Link: https://lore.kernel.org/r/20231201033057.1399131-1-yangyingliang@huaweicloud.com [mani: reworded commit message] Signed-off-by: Manivannan Sadhasivam --- drivers/ntb/core.c | 8 +++++++- drivers/pci/endpoint/functions/pci-epf-vntb.c | 6 +----- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/ntb/core.c b/drivers/ntb/core.c index 27dd93deff6e..d702bee78082 100644 --- a/drivers/ntb/core.c +++ b/drivers/ntb/core.c @@ -100,6 +100,8 @@ EXPORT_SYMBOL(ntb_unregister_client); int ntb_register_device(struct ntb_dev *ntb) { + int ret; + if (!ntb) return -EINVAL; if (!ntb->pdev) @@ -120,7 +122,11 @@ int ntb_register_device(struct ntb_dev *ntb) ntb->ctx_ops = NULL; spin_lock_init(&ntb->ctx_lock); - return device_register(&ntb->dev); + ret = device_register(&ntb->dev); + if (ret) + put_device(&ntb->dev); + + return ret; } EXPORT_SYMBOL(ntb_register_device); diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index eda4b906868b..d4c25b43da23 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1264,15 +1264,11 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id) ret = ntb_register_device(&ndev->ntb); if (ret) { dev_err(dev, "Failed to register NTB device\n"); - goto err_register_dev; + return ret; } dev_dbg(dev, "PCI Virtual NTB driver loaded\n"); return 0; - -err_register_dev: - put_device(&ndev->ntb.dev); - return -EINVAL; } static struct pci_device_id pci_vntb_table[] = { -- cgit v1.2.3 From 976dc5ff3018552dcfe58018a0cfdf6376318117 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Fri, 1 Dec 2023 11:30:57 +0800 Subject: PCI: epf-vntb: Return actual error code during pci_vntb_probe() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If dma_set_mask_and_coherent() in pci_vntb_probe() fails, return the actual error code instead of -EINVAL. Signed-off-by: Yang Yingliang Reviewed-by: Manivannan Sadhasivam Reviewed-by: Dave Jiang Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20231201033057.1399131-2-yangyingliang@huaweicloud.com [mani: reworded commit message and subject] Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index d4c25b43da23..0675929fc529 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1258,7 +1258,7 @@ static int pci_vntb_probe(struct pci_dev *pdev, const struct pci_device_id *id) ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); if (ret) { dev_err(dev, "Cannot set DMA mask\n"); - return -EINVAL; + return ret; } ret = ntb_register_device(&ndev->ntb); -- cgit v1.2.3 From 2e00fd5487c796db8b8324e80bc5fe8a02c15a15 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 1 Dec 2023 17:36:14 +0530 Subject: PCI: epf-mhi: Add "pci_epf_mhi_" prefix to the function names Without the prefix, the function name would appear as "/sys/kernel/config/functions/{sdx55/sm8450}". This will be a problem if multiple functions are supported for this endpoint device. So let's add the "pci_epf_mhi_" prefix to identify _this_ function uniquely. Even though it is an ABI breakage, this driver is not used anywhere outside Qcom and myself to my knowledge. So it safe to change the function name. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Mrinmay Sarkar Link: https://lore.kernel.org/r/1701432377-16899-4-git-send-email-quic_msarkar@quicinc.com --- drivers/pci/endpoint/functions/pci-epf-mhi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-mhi.c b/drivers/pci/endpoint/functions/pci-epf-mhi.c index 1c3e4ea76bd2..5c0dba5ba602 100644 --- a/drivers/pci/endpoint/functions/pci-epf-mhi.c +++ b/drivers/pci/endpoint/functions/pci-epf-mhi.c @@ -913,8 +913,8 @@ static int pci_epf_mhi_probe(struct pci_epf *epf, } static const struct pci_epf_device_id pci_epf_mhi_ids[] = { - { .name = "sdx55", .driver_data = (kernel_ulong_t)&sdx55_info }, - { .name = "sm8450", .driver_data = (kernel_ulong_t)&sm8450_info }, + { .name = "pci_epf_mhi_sdx55", .driver_data = (kernel_ulong_t)&sdx55_info }, + { .name = "pci_epf_mhi_sm8450", .driver_data = (kernel_ulong_t)&sm8450_info }, {}, }; -- cgit v1.2.3 From c670e29f5bfe6c404a5405a0fa8e235de2f4f0c9 Mon Sep 17 00:00:00 2001 From: Mrinmay Sarkar Date: Fri, 1 Dec 2023 17:36:15 +0530 Subject: PCI: epf-mhi: Add support for SA8775P SoC Add support for Qualcomm Snapdragon SA8775P SoC to the EPF driver. SA8775P is currently reusing the PID 0x0306 (the default one hardcoded in the config space header) as the unique PID is not yet allocated. But the host side stack works fine with the default PID. It will get updated once the PID is finalized. Also, it has no fixed PCI class as of now, so it is being advertised as "PCI_CLASS_OTHERS". Signed-off-by: Mrinmay Sarkar Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/1701432377-16899-5-git-send-email-quic_msarkar@quicinc.com Signed-off-by: Manivannan Sadhasivam --- drivers/pci/endpoint/functions/pci-epf-mhi.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/endpoint/functions/pci-epf-mhi.c b/drivers/pci/endpoint/functions/pci-epf-mhi.c index 5c0dba5ba602..2c54d80107cf 100644 --- a/drivers/pci/endpoint/functions/pci-epf-mhi.c +++ b/drivers/pci/endpoint/functions/pci-epf-mhi.c @@ -123,6 +123,22 @@ static const struct pci_epf_mhi_ep_info sm8450_info = { .flags = MHI_EPF_USE_DMA, }; +static struct pci_epf_header sa8775p_header = { + .vendorid = PCI_VENDOR_ID_QCOM, + .deviceid = 0x0306, /* FIXME: Update deviceid for sa8775p EP */ + .baseclass_code = PCI_CLASS_OTHERS, + .interrupt_pin = PCI_INTERRUPT_INTA, +}; + +static const struct pci_epf_mhi_ep_info sa8775p_info = { + .config = &mhi_v1_config, + .epf_header = &sa8775p_header, + .bar_num = BAR_0, + .epf_flags = PCI_BASE_ADDRESS_MEM_TYPE_32, + .msi_count = 32, + .mru = 0x8000, +}; + struct pci_epf_mhi { const struct pci_epc_features *epc_features; const struct pci_epf_mhi_ep_info *info; @@ -913,6 +929,7 @@ static int pci_epf_mhi_probe(struct pci_epf *epf, } static const struct pci_epf_device_id pci_epf_mhi_ids[] = { + { .name = "pci_epf_mhi_sa8775p", .driver_data = (kernel_ulong_t)&sa8775p_info }, { .name = "pci_epf_mhi_sdx55", .driver_data = (kernel_ulong_t)&sdx55_info }, { .name = "pci_epf_mhi_sm8450", .driver_data = (kernel_ulong_t)&sm8450_info }, {}, -- cgit v1.2.3 From e01c9797c0ebb307c9bb196c677f6e571335773e Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 16 Feb 2024 14:45:14 +0100 Subject: PCI: endpoint: Clean up hardware description for BARs The hardware description for BARs is scattered in many different variables in pci_epc_features. Some of these things are mutually exclusive, so it can create confusion over which variable that has precedence over another. Improve the situation by creating a struct pci_epc_bar_desc, and a new enum pci_epc_bar_type, and convert the endpoint controller drivers to use this more well defined format. Additionally, some endpoint controller drivers mark the BAR succeeding a "64-bit only BAR" as reserved, while some do not. By definition, a 64-bit BAR uses the succeeding BAR for the upper 32-bits, so an EPF driver cannot use a BAR succeeding a 64-bit BAR. Ensure that all endpoint controller drivers are uniform, and actually describe a reserved BAR as reserved. Signed-off-by: Niklas Cassel Reviewed-by: Kishon Vijay Abraham I Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20240216134524.1142149-2-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 3 ++- drivers/pci/controller/dwc/pci-keystone.c | 12 ++++----- drivers/pci/controller/dwc/pci-layerscape-ep.c | 5 +++- drivers/pci/controller/dwc/pcie-keembay.c | 8 ++++-- drivers/pci/controller/dwc/pcie-rcar-gen4.c | 4 ++- drivers/pci/controller/dwc/pcie-tegra194.c | 10 +++++--- drivers/pci/controller/dwc/pcie-uniphier-ep.c | 15 +++++++++--- drivers/pci/controller/pcie-rcar-ep.c | 14 +++++++---- drivers/pci/endpoint/functions/pci-epf-ntb.c | 4 +-- drivers/pci/endpoint/functions/pci-epf-test.c | 8 +++--- drivers/pci/endpoint/functions/pci-epf-vntb.c | 2 +- drivers/pci/endpoint/pci-epc-core.c | 32 ++++++++++++------------ drivers/pci/endpoint/pci-epf-core.c | 15 ++++++------ include/linux/pci-epc.h | 34 +++++++++++++++++++++----- 14 files changed, 108 insertions(+), 58 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index dc2c036ab28c..47a9a96484ed 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1081,7 +1081,8 @@ static const struct pci_epc_features imx8m_pcie_epc_features = { .linkup_notifier = false, .msi_capable = true, .msix_capable = false, - .reserved_bar = 1 << BAR_1 | 1 << BAR_3, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, .align = SZ_64K, }; diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index c0c62533a3f1..b2b93b4fa82d 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -924,12 +924,12 @@ static const struct pci_epc_features ks_pcie_am654_epc_features = { .linkup_notifier = false, .msi_capable = true, .msix_capable = true, - .reserved_bar = 1 << BAR_0 | 1 << BAR_1, - .bar_fixed_64bit = 1 << BAR_0, - .bar_fixed_size[2] = SZ_1M, - .bar_fixed_size[3] = SZ_64K, - .bar_fixed_size[4] = 256, - .bar_fixed_size[5] = SZ_1M, + .bar[BAR_0] = { .type = BAR_RESERVED, .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, + .bar[BAR_4] = { .type = BAR_FIXED, .fixed_size = 256, }, + .bar[BAR_5] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, .align = SZ_1M, }; diff --git a/drivers/pci/controller/dwc/pci-layerscape-ep.c b/drivers/pci/controller/dwc/pci-layerscape-ep.c index 2e398494e7c0..1f6ee1460ec2 100644 --- a/drivers/pci/controller/dwc/pci-layerscape-ep.c +++ b/drivers/pci/controller/dwc/pci-layerscape-ep.c @@ -250,7 +250,10 @@ static int __init ls_pcie_ep_probe(struct platform_device *pdev) pci->dev = dev; pci->ops = pcie->drvdata->dw_pcie_ops; - ls_epc->bar_fixed_64bit = (1 << BAR_2) | (1 << BAR_4); + ls_epc->bar[BAR_2].only_64bit = true; + ls_epc->bar[BAR_3].type = BAR_RESERVED; + ls_epc->bar[BAR_4].only_64bit = true; + ls_epc->bar[BAR_5].type = BAR_RESERVED; ls_epc->linkup_notifier = true; pcie->pci = pci; diff --git a/drivers/pci/controller/dwc/pcie-keembay.c b/drivers/pci/controller/dwc/pcie-keembay.c index 208d3b0ba196..5e8e54f597dd 100644 --- a/drivers/pci/controller/dwc/pcie-keembay.c +++ b/drivers/pci/controller/dwc/pcie-keembay.c @@ -312,8 +312,12 @@ static const struct pci_epc_features keembay_pcie_epc_features = { .linkup_notifier = false, .msi_capable = true, .msix_capable = true, - .reserved_bar = BIT(BAR_1) | BIT(BAR_3) | BIT(BAR_5), - .bar_fixed_64bit = BIT(BAR_0) | BIT(BAR_2) | BIT(BAR_4), + .bar[BAR_0] = { .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .only_64bit = true, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .only_64bit = true, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, .align = SZ_16K, }; diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index e9166619b1f9..0be760ed420b 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -383,7 +383,9 @@ static const struct pci_epc_features rcar_gen4_pcie_epc_features = { .linkup_notifier = false, .msi_capable = true, .msix_capable = false, - .reserved_bar = 1 << BAR_1 | 1 << BAR_3 | 1 << BAR_5, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, .align = SZ_1M, }; diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 7afa9e9aabe2..1f7b662cb8e1 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -2007,9 +2007,13 @@ static const struct pci_epc_features tegra_pcie_epc_features = { .core_init_notifier = true, .msi_capable = false, .msix_capable = false, - .reserved_bar = 1 << BAR_2 | 1 << BAR_3 | 1 << BAR_4 | 1 << BAR_5, - .bar_fixed_64bit = 1 << BAR_0, - .bar_fixed_size[0] = SZ_1M, + .bar[BAR_0] = { .type = BAR_FIXED, .fixed_size = SZ_1M, + .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .type = BAR_RESERVED, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .type = BAR_RESERVED, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, }; static const struct pci_epc_features* diff --git a/drivers/pci/controller/dwc/pcie-uniphier-ep.c b/drivers/pci/controller/dwc/pcie-uniphier-ep.c index 3fced0d3e851..265f65fc673f 100644 --- a/drivers/pci/controller/dwc/pcie-uniphier-ep.c +++ b/drivers/pci/controller/dwc/pcie-uniphier-ep.c @@ -411,8 +411,12 @@ static const struct uniphier_pcie_ep_soc_data uniphier_pro5_data = { .msi_capable = true, .msix_capable = false, .align = 1 << 16, - .bar_fixed_64bit = BIT(BAR_0) | BIT(BAR_2) | BIT(BAR_4), - .reserved_bar = BIT(BAR_4), + .bar[BAR_0] = { .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .only_64bit = true, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .type = BAR_RESERVED, .only_64bit = true, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, }, }; @@ -425,7 +429,12 @@ static const struct uniphier_pcie_ep_soc_data uniphier_nx1_data = { .msi_capable = true, .msix_capable = false, .align = 1 << 12, - .bar_fixed_64bit = BIT(BAR_0) | BIT(BAR_2) | BIT(BAR_4), + .bar[BAR_0] = { .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .only_64bit = true, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .only_64bit = true, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, }, }; diff --git a/drivers/pci/controller/pcie-rcar-ep.c b/drivers/pci/controller/pcie-rcar-ep.c index e6909271def7..05967c6c0b42 100644 --- a/drivers/pci/controller/pcie-rcar-ep.c +++ b/drivers/pci/controller/pcie-rcar-ep.c @@ -440,11 +440,15 @@ static const struct pci_epc_features rcar_pcie_epc_features = { .msi_capable = true, .msix_capable = false, /* use 64-bit BARs so mark BAR[1,3,5] as reserved */ - .reserved_bar = 1 << BAR_1 | 1 << BAR_3 | 1 << BAR_5, - .bar_fixed_64bit = 1 << BAR_0 | 1 << BAR_2 | 1 << BAR_4, - .bar_fixed_size[0] = 128, - .bar_fixed_size[2] = 256, - .bar_fixed_size[4] = 256, + .bar[BAR_0] = { .type = BAR_FIXED, .fixed_size = 128, + .only_64bit = true, }, + .bar[BAR_1] = { .type = BAR_RESERVED, }, + .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = 256, + .only_64bit = true, }, + .bar[BAR_3] = { .type = BAR_RESERVED, }, + .bar[BAR_4] = { .type = BAR_FIXED, .fixed_size = 256, + .only_64bit = true, }, + .bar[BAR_5] = { .type = BAR_RESERVED, }, }; static const struct pci_epc_features* diff --git a/drivers/pci/endpoint/functions/pci-epf-ntb.c b/drivers/pci/endpoint/functions/pci-epf-ntb.c index 43cd309ce22f..e01a98e74d21 100644 --- a/drivers/pci/endpoint/functions/pci-epf-ntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-ntb.c @@ -1012,13 +1012,13 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb, epc_features = ntb_epc->epc_features; barno = ntb_epc->epf_ntb_bar[BAR_CONFIG]; - size = epc_features->bar_fixed_size[barno]; + size = epc_features->bar[barno].fixed_size; align = epc_features->align; peer_ntb_epc = ntb->epc[!type]; peer_epc_features = peer_ntb_epc->epc_features; peer_barno = ntb_epc->epf_ntb_bar[BAR_PEER_SPAD]; - peer_size = peer_epc_features->bar_fixed_size[peer_barno]; + peer_size = peer_epc_features->bar[peer_barno].fixed_size; /* Check if epc_features is populated incorrectly */ if ((!IS_ALIGNED(size, align))) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 981894e40681..cd4ffb39dcdc 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -729,7 +729,7 @@ static int pci_epf_test_set_bar(struct pci_epf *epf) */ add = (epf_bar->flags & PCI_BASE_ADDRESS_MEM_TYPE_64) ? 2 : 1; - if (!!(epc_features->reserved_bar & (1 << bar))) + if (epc_features->bar[bar].type == BAR_RESERVED) continue; ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, @@ -856,7 +856,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf) if (bar == test_reg_bar) continue; - if (!!(epc_features->reserved_bar & (1 << bar))) + if (epc_features->bar[bar].type == BAR_RESERVED) continue; base = pci_epf_alloc_space(epf, bar_size[bar], bar, @@ -874,13 +874,11 @@ static void pci_epf_configure_bar(struct pci_epf *epf, const struct pci_epc_features *epc_features) { struct pci_epf_bar *epf_bar; - bool bar_fixed_64bit; int i; for (i = 0; i < PCI_STD_NUM_BARS; i++) { epf_bar = &epf->bar[i]; - bar_fixed_64bit = !!(epc_features->bar_fixed_64bit & (1 << i)); - if (bar_fixed_64bit) + if (epc_features->bar[i].only_64bit) epf_bar->flags |= PCI_BASE_ADDRESS_MEM_TYPE_64; } } diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 0675929fc529..8e779eecd62d 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -422,7 +422,7 @@ static int epf_ntb_config_spad_bar_alloc(struct epf_ntb *ntb) epf->func_no, epf->vfunc_no); barno = ntb->epf_ntb_bar[BAR_CONFIG]; - size = epc_features->bar_fixed_size[barno]; + size = epc_features->bar[barno].fixed_size; align = epc_features->align; if ((!IS_ALIGNED(size, align))) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index dcd4e66430c1..7fe8f4336765 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -87,7 +87,7 @@ EXPORT_SYMBOL_GPL(pci_epc_get); * @epc_features: pci_epc_features structure that holds the reserved bar bitmap * * Invoke to get the first unreserved BAR that can be used by the endpoint - * function. For any incorrect value in reserved_bar return '0'. + * function. */ enum pci_barno pci_epc_get_first_free_bar(const struct pci_epc_features *epc_features) @@ -102,32 +102,34 @@ EXPORT_SYMBOL_GPL(pci_epc_get_first_free_bar); * @bar: the starting BAR number from where unreserved BAR should be searched * * Invoke to get the next unreserved BAR starting from @bar that can be used - * for endpoint function. For any incorrect value in reserved_bar return '0'. + * for endpoint function. */ enum pci_barno pci_epc_get_next_free_bar(const struct pci_epc_features *epc_features, enum pci_barno bar) { - unsigned long free_bar; + int i; if (!epc_features) return BAR_0; /* If 'bar - 1' is a 64-bit BAR, move to the next BAR */ - if ((epc_features->bar_fixed_64bit << 1) & 1 << bar) + if (bar > 0 && epc_features->bar[bar - 1].only_64bit) bar++; - /* Find if the reserved BAR is also a 64-bit BAR */ - free_bar = epc_features->reserved_bar & epc_features->bar_fixed_64bit; - - /* Set the adjacent bit if the reserved BAR is also a 64-bit BAR */ - free_bar <<= 1; - free_bar |= epc_features->reserved_bar; - - free_bar = find_next_zero_bit(&free_bar, 6, bar); - if (free_bar > 5) - return NO_BAR; + for (i = bar; i < PCI_STD_NUM_BARS; i++) { + /* If the BAR is not reserved, return it. */ + if (epc_features->bar[i].type != BAR_RESERVED) + return i; + + /* + * If the BAR is reserved, and marked as 64-bit only, then the + * succeeding BAR is also reserved. + */ + if (epc_features->bar[i].only_64bit) + i++; + } - return free_bar; + return NO_BAR; } EXPORT_SYMBOL_GPL(pci_epc_get_next_free_bar); diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index f2b4d34454c4..0a28a0b0911b 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -260,7 +260,7 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, const struct pci_epc_features *epc_features, enum pci_epc_interface_type type) { - u64 bar_fixed_size = epc_features->bar_fixed_size[bar]; + u64 bar_fixed_size = epc_features->bar[bar].fixed_size; size_t align = epc_features->align; struct pci_epf_bar *epf_bar; dma_addr_t phys_addr; @@ -271,13 +271,14 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, if (size < 128) size = 128; - if (bar_fixed_size && size > bar_fixed_size) { - dev_err(&epf->dev, "requested BAR size is larger than fixed size\n"); - return NULL; - } - - if (bar_fixed_size) + if (epc_features->bar[bar].type == BAR_FIXED && bar_fixed_size) { + if (size > bar_fixed_size) { + dev_err(&epf->dev, + "requested BAR size is larger than fixed size\n"); + return NULL; + } size = bar_fixed_size; + } if (align) size = ALIGN(size, align); diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 40ea18f5aa02..4ccb4f4f3883 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -145,6 +145,32 @@ struct pci_epc { unsigned long function_num_map; }; +/** + * @BAR_PROGRAMMABLE: The BAR mask can be configured by the EPC. + * @BAR_FIXED: The BAR mask is fixed by the hardware. + * @BAR_RESERVED: The BAR should not be touched by an EPF driver. + */ +enum pci_epc_bar_type { + BAR_PROGRAMMABLE = 0, + BAR_FIXED, + BAR_RESERVED, +}; + +/** + * struct pci_epc_bar_desc - hardware description for a BAR + * @type: the type of the BAR + * @fixed_size: the fixed size, only applicable if type is BAR_FIXED_MASK. + * @only_64bit: if true, an EPF driver is not allowed to choose if this BAR + * should be configured as 32-bit or 64-bit, the EPF driver must + * configure this BAR as 64-bit. Additionally, the BAR succeeding + * this BAR must be set to type BAR_RESERVED. + */ +struct pci_epc_bar_desc { + enum pci_epc_bar_type type; + u64 fixed_size; + bool only_64bit; +}; + /** * struct pci_epc_features - features supported by a EPC device per function * @linkup_notifier: indicate if the EPC device can notify EPF driver on link up @@ -152,9 +178,7 @@ struct pci_epc { * for initialization * @msi_capable: indicate if the endpoint function has MSI capability * @msix_capable: indicate if the endpoint function has MSI-X capability - * @reserved_bar: bitmap to indicate reserved BAR unavailable to function driver - * @bar_fixed_64bit: bitmap to indicate fixed 64bit BARs - * @bar_fixed_size: Array specifying the size supported by each BAR + * @bar: array specifying the hardware description for each BAR * @align: alignment size required for BAR buffer allocation */ struct pci_epc_features { @@ -162,9 +186,7 @@ struct pci_epc_features { unsigned int core_init_notifier : 1; unsigned int msi_capable : 1; unsigned int msix_capable : 1; - u8 reserved_bar; - u8 bar_fixed_64bit; - u64 bar_fixed_size[PCI_STD_NUM_BARS]; + struct pci_epc_bar_desc bar[PCI_STD_NUM_BARS]; size_t align; }; -- cgit v1.2.3 From 9266514689fe6476423209ee40168db53134101d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 16 Feb 2024 14:45:15 +0100 Subject: PCI: endpoint: Drop only_64bit on reserved BARs The definition of a reserved BAR is that EPF drivers should not touch them. The definition of only_64bit is that the EPF driver must configure this BAR as 64-bit. (An EPF driver is not allowed to choose if this BAR should be configured as 32-bit or 64-bit.) Thus, it does not make sense to put only_64bit of a BAR that EPF drivers are not allow to touch. Drop the only_64bit property from hardware descriptions that are of type reserved BAR. Signed-off-by: Niklas Cassel Reviewed-by: Kishon Vijay Abraham I Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20240216134524.1142149-3-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-keystone.c | 2 +- drivers/pci/controller/dwc/pcie-uniphier-ep.c | 2 +- drivers/pci/endpoint/pci-epc-core.c | 7 ------- include/linux/pci-epc.h | 5 +++++ 4 files changed, 7 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index b2b93b4fa82d..844de4418724 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -924,7 +924,7 @@ static const struct pci_epc_features ks_pcie_am654_epc_features = { .linkup_notifier = false, .msi_capable = true, .msix_capable = true, - .bar[BAR_0] = { .type = BAR_RESERVED, .only_64bit = true, }, + .bar[BAR_0] = { .type = BAR_RESERVED, }, .bar[BAR_1] = { .type = BAR_RESERVED, }, .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, diff --git a/drivers/pci/controller/dwc/pcie-uniphier-ep.c b/drivers/pci/controller/dwc/pcie-uniphier-ep.c index 265f65fc673f..639bc2e12476 100644 --- a/drivers/pci/controller/dwc/pcie-uniphier-ep.c +++ b/drivers/pci/controller/dwc/pcie-uniphier-ep.c @@ -415,7 +415,7 @@ static const struct uniphier_pcie_ep_soc_data uniphier_pro5_data = { .bar[BAR_1] = { .type = BAR_RESERVED, }, .bar[BAR_2] = { .only_64bit = true, }, .bar[BAR_3] = { .type = BAR_RESERVED, }, - .bar[BAR_4] = { .type = BAR_RESERVED, .only_64bit = true, }, + .bar[BAR_4] = { .type = BAR_RESERVED, }, .bar[BAR_5] = { .type = BAR_RESERVED, }, }, }; diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 7fe8f4336765..da3fc0795b0b 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -120,13 +120,6 @@ enum pci_barno pci_epc_get_next_free_bar(const struct pci_epc_features /* If the BAR is not reserved, return it. */ if (epc_features->bar[i].type != BAR_RESERVED) return i; - - /* - * If the BAR is reserved, and marked as 64-bit only, then the - * succeeding BAR is also reserved. - */ - if (epc_features->bar[i].only_64bit) - i++; } return NO_BAR; diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 4ccb4f4f3883..cc2f70d061c8 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -164,6 +164,11 @@ enum pci_epc_bar_type { * should be configured as 32-bit or 64-bit, the EPF driver must * configure this BAR as 64-bit. Additionally, the BAR succeeding * this BAR must be set to type BAR_RESERVED. + * + * only_64bit should not be set on a BAR of type BAR_RESERVED. + * (If BARx is a 64-bit BAR that an EPF driver is not allowed to + * touch, then both BARx and BARx+1 must be set to type + * BAR_RESERVED.) */ struct pci_epc_bar_desc { enum pci_epc_bar_type type; -- cgit v1.2.3 From baf67aefbe7d7deafa59ca49612d163f8889934c Mon Sep 17 00:00:00 2001 From: Jörg Wedekind Date: Mon, 19 Feb 2024 14:28:11 +0100 Subject: PCI: Mark 3ware-9650SE Root Port Extended Tags as broken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per PCIe r6.1, sec 2.2.6.2 and 7.5.3.4, a Requester may not use 8-bit Tags unless its Extended Tag Field Enable is set, but all Receivers/Completers must handle 8-bit Tags correctly regardless of their Extended Tag Field Enable. Some devices do not handle 8-bit Tags as Completers, so add a quirk for them. If we find such a device, we disable Extended Tags for the entire hierarchy to make peer-to-peer DMA possible. The 3ware 9650SE seems to have issues with handling 8-bit tags. Mark it as broken. This fixes PCI Parity Errors like : 3w-9xxx: scsi0: ERROR: (0x06:0x000C): PCI Parity Error: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x000D): PCI Abort: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x000E): Controller Queue Error: clearing. 3w-9xxx: scsi0: ERROR: (0x06:0x0010): Microcontroller Error: clearing. Link: https://lore.kernel.org/r/20240219132811.8351-1-joerg@wedekind.de Fixes: 60db3a4d8cc9 ("PCI: Enable PCIe Extended Tags if supported") Closes: https://bugzilla.kernel.org/show_bug.cgi?id=202425 Signed-off-by: Jörg Wedekind Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d797df6e5f3e..2ebbe51a7efe 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5527,6 +5527,7 @@ static void quirk_no_ext_tags(struct pci_dev *pdev) pci_walk_bus(bridge->bus, pci_configure_extended_tags, NULL); } +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_3WARE, 0x1004, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0132, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0140, quirk_no_ext_tags); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_SERVERWORKS, 0x0141, quirk_no_ext_tags); -- cgit v1.2.3 From 6a40185838759cdae728ed79738680d798863ce7 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:11 -0500 Subject: PCI: imx6: Simplify clock handling by using clk_bulk*() function Refactor the clock handling logic. Add 'clk_names' define in drvdata. Use clk_bulk*() API to simplify the code. Link: https://lore.kernel.org/r/20240220161924.3871774-2-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 138 ++++++++++++---------------------- 1 file changed, 50 insertions(+), 88 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 47a9a96484ed..f820823c5f69 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -61,12 +61,16 @@ enum imx6_pcie_variants { #define IMX6_PCIE_FLAG_IMX6_SPEED_CHANGE BIT(1) #define IMX6_PCIE_FLAG_SUPPORTS_SUSPEND BIT(2) +#define IMX6_PCIE_MAX_CLKS 6 + struct imx6_pcie_drvdata { enum imx6_pcie_variants variant; enum dw_pcie_device_mode mode; u32 flags; int dbi_length; const char *gpr; + const char * const *clk_names; + const u32 clks_cnt; }; struct imx6_pcie { @@ -74,11 +78,7 @@ struct imx6_pcie { int reset_gpio; bool gpio_active_high; bool link_is_up; - struct clk *pcie_bus; - struct clk *pcie_phy; - struct clk *pcie_inbound_axi; - struct clk *pcie; - struct clk *pcie_aux; + struct clk_bulk_data clks[IMX6_PCIE_MAX_CLKS]; struct regmap *iomuxc_gpr; u16 msi_ctrl; u32 controller_id; @@ -407,13 +407,18 @@ static void imx7d_pcie_wait_for_phy_pll_lock(struct imx6_pcie *imx6_pcie) static int imx6_setup_phy_mpll(struct imx6_pcie *imx6_pcie) { - unsigned long phy_rate = clk_get_rate(imx6_pcie->pcie_phy); + unsigned long phy_rate = 0; int mult, div; u16 val; + int i; if (!(imx6_pcie->drvdata->flags & IMX6_PCIE_FLAG_IMX6_PHY)) return 0; + for (i = 0; i < imx6_pcie->drvdata->clks_cnt; i++) + if (strncmp(imx6_pcie->clks[i].id, "pcie_phy", 8) == 0) + phy_rate = clk_get_rate(imx6_pcie->clks[i].clk); + switch (phy_rate) { case 125000000: /* @@ -550,19 +555,11 @@ static int imx6_pcie_attach_pd(struct device *dev) static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) { - struct dw_pcie *pci = imx6_pcie->pci; - struct device *dev = pci->dev; unsigned int offset; int ret = 0; switch (imx6_pcie->drvdata->variant) { case IMX6SX: - ret = clk_prepare_enable(imx6_pcie->pcie_inbound_axi); - if (ret) { - dev_err(dev, "unable to enable pcie_axi clock\n"); - break; - } - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN, 0); break; @@ -589,12 +586,6 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) case IMX8MQ_EP: case IMX8MP: case IMX8MP_EP: - ret = clk_prepare_enable(imx6_pcie->pcie_aux); - if (ret) { - dev_err(dev, "unable to enable pcie_aux clock\n"); - break; - } - offset = imx6_pcie_grp_offset(imx6_pcie); /* * Set the over ride low and enabled @@ -615,9 +606,6 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) static void imx6_pcie_disable_ref_clk(struct imx6_pcie *imx6_pcie) { switch (imx6_pcie->drvdata->variant) { - case IMX6SX: - clk_disable_unprepare(imx6_pcie->pcie_inbound_axi); - break; case IMX6QP: case IMX6Q: regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, @@ -631,14 +619,6 @@ static void imx6_pcie_disable_ref_clk(struct imx6_pcie *imx6_pcie) IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, IMX7D_GPR12_PCIE_PHY_REFCLK_SEL); break; - case IMX8MM: - case IMX8MM_EP: - case IMX8MQ: - case IMX8MQ_EP: - case IMX8MP: - case IMX8MP_EP: - clk_disable_unprepare(imx6_pcie->pcie_aux); - break; default: break; } @@ -650,23 +630,9 @@ static int imx6_pcie_clk_enable(struct imx6_pcie *imx6_pcie) struct device *dev = pci->dev; int ret; - ret = clk_prepare_enable(imx6_pcie->pcie_phy); - if (ret) { - dev_err(dev, "unable to enable pcie_phy clock\n"); + ret = clk_bulk_prepare_enable(imx6_pcie->drvdata->clks_cnt, imx6_pcie->clks); + if (ret) return ret; - } - - ret = clk_prepare_enable(imx6_pcie->pcie_bus); - if (ret) { - dev_err(dev, "unable to enable pcie_bus clock\n"); - goto err_pcie_bus; - } - - ret = clk_prepare_enable(imx6_pcie->pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clock\n"); - goto err_pcie; - } ret = imx6_pcie_enable_ref_clk(imx6_pcie); if (ret) { @@ -679,11 +645,7 @@ static int imx6_pcie_clk_enable(struct imx6_pcie *imx6_pcie) return 0; err_ref_clk: - clk_disable_unprepare(imx6_pcie->pcie); -err_pcie: - clk_disable_unprepare(imx6_pcie->pcie_bus); -err_pcie_bus: - clk_disable_unprepare(imx6_pcie->pcie_phy); + clk_bulk_disable_unprepare(imx6_pcie->drvdata->clks_cnt, imx6_pcie->clks); return ret; } @@ -691,9 +653,7 @@ err_pcie_bus: static void imx6_pcie_clk_disable(struct imx6_pcie *imx6_pcie) { imx6_pcie_disable_ref_clk(imx6_pcie); - clk_disable_unprepare(imx6_pcie->pcie); - clk_disable_unprepare(imx6_pcie->pcie_bus); - clk_disable_unprepare(imx6_pcie->pcie_phy); + clk_bulk_disable_unprepare(imx6_pcie->drvdata->clks_cnt, imx6_pcie->clks); } static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) @@ -1252,6 +1212,7 @@ static int imx6_pcie_probe(struct platform_device *pdev) struct device_node *node = dev->of_node; int ret; u16 val; + int i; imx6_pcie = devm_kzalloc(dev, sizeof(*imx6_pcie), GFP_KERNEL); if (!imx6_pcie) @@ -1305,32 +1266,20 @@ static int imx6_pcie_probe(struct platform_device *pdev) return imx6_pcie->reset_gpio; } - /* Fetch clocks */ - imx6_pcie->pcie_bus = devm_clk_get(dev, "pcie_bus"); - if (IS_ERR(imx6_pcie->pcie_bus)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_bus), - "pcie_bus clock source missing or invalid\n"); + if (imx6_pcie->drvdata->clks_cnt >= IMX6_PCIE_MAX_CLKS) + return dev_err_probe(dev, -ENOMEM, "clks_cnt is too big\n"); - imx6_pcie->pcie = devm_clk_get(dev, "pcie"); - if (IS_ERR(imx6_pcie->pcie)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie), - "pcie clock source missing or invalid\n"); + for (i = 0; i < imx6_pcie->drvdata->clks_cnt; i++) + imx6_pcie->clks[i].id = imx6_pcie->drvdata->clk_names[i]; + + /* Fetch clocks */ + ret = devm_clk_bulk_get(dev, imx6_pcie->drvdata->clks_cnt, imx6_pcie->clks); + if (ret) + return ret; switch (imx6_pcie->drvdata->variant) { - case IMX6SX: - imx6_pcie->pcie_inbound_axi = devm_clk_get(dev, - "pcie_inbound_axi"); - if (IS_ERR(imx6_pcie->pcie_inbound_axi)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_inbound_axi), - "pcie_inbound_axi clock missing or invalid\n"); - break; case IMX8MQ: case IMX8MQ_EP: - imx6_pcie->pcie_aux = devm_clk_get(dev, "pcie_aux"); - if (IS_ERR(imx6_pcie->pcie_aux)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_aux), - "pcie_aux clock source missing or invalid\n"); - fallthrough; case IMX7D: if (dbi_base->start == IMX8MQ_PCIE2_BASE_ADDR) imx6_pcie->controller_id = 1; @@ -1353,10 +1302,6 @@ static int imx6_pcie_probe(struct platform_device *pdev) case IMX8MM_EP: case IMX8MP: case IMX8MP_EP: - imx6_pcie->pcie_aux = devm_clk_get(dev, "pcie_aux"); - if (IS_ERR(imx6_pcie->pcie_aux)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_aux), - "pcie_aux clock source missing or invalid\n"); imx6_pcie->apps_reset = devm_reset_control_get_exclusive(dev, "apps"); if (IS_ERR(imx6_pcie->apps_reset)) @@ -1372,14 +1317,6 @@ static int imx6_pcie_probe(struct platform_device *pdev) default: break; } - /* Don't fetch the pcie_phy clock, if it has abstract PHY driver */ - if (imx6_pcie->phy == NULL) { - imx6_pcie->pcie_phy = devm_clk_get(dev, "pcie_phy"); - if (IS_ERR(imx6_pcie->pcie_phy)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_phy), - "pcie_phy clock source missing or invalid\n"); - } - /* Grab turnoff reset */ imx6_pcie->turnoff_reset = devm_reset_control_get_optional_exclusive(dev, "turnoff"); @@ -1470,6 +1407,11 @@ static void imx6_pcie_shutdown(struct platform_device *pdev) imx6_pcie_assert_core_reset(imx6_pcie); } +static const char * const imx6q_clks[] = {"pcie_bus", "pcie", "pcie_phy"}; +static const char * const imx8mm_clks[] = {"pcie_bus", "pcie", "pcie_aux"}; +static const char * const imx8mq_clks[] = {"pcie_bus", "pcie", "pcie_phy", "pcie_aux"}; +static const char * const imx6sx_clks[] = {"pcie_bus", "pcie", "pcie_phy", "pcie_inbound_axi"}; + static const struct imx6_pcie_drvdata drvdata[] = { [IMX6Q] = { .variant = IMX6Q, @@ -1477,6 +1419,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { IMX6_PCIE_FLAG_IMX6_SPEED_CHANGE, .dbi_length = 0x200, .gpr = "fsl,imx6q-iomuxc-gpr", + .clk_names = imx6q_clks, + .clks_cnt = ARRAY_SIZE(imx6q_clks), }, [IMX6SX] = { .variant = IMX6SX, @@ -1484,6 +1428,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { IMX6_PCIE_FLAG_IMX6_SPEED_CHANGE | IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .gpr = "fsl,imx6q-iomuxc-gpr", + .clk_names = imx6sx_clks, + .clks_cnt = ARRAY_SIZE(imx6sx_clks), }, [IMX6QP] = { .variant = IMX6QP, @@ -1492,40 +1438,56 @@ static const struct imx6_pcie_drvdata drvdata[] = { IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .dbi_length = 0x200, .gpr = "fsl,imx6q-iomuxc-gpr", + .clk_names = imx6q_clks, + .clks_cnt = ARRAY_SIZE(imx6q_clks), }, [IMX7D] = { .variant = IMX7D, .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .gpr = "fsl,imx7d-iomuxc-gpr", + .clk_names = imx6q_clks, + .clks_cnt = ARRAY_SIZE(imx6q_clks), }, [IMX8MQ] = { .variant = IMX8MQ, .gpr = "fsl,imx8mq-iomuxc-gpr", + .clk_names = imx8mq_clks, + .clks_cnt = ARRAY_SIZE(imx8mq_clks), }, [IMX8MM] = { .variant = IMX8MM, .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .gpr = "fsl,imx8mm-iomuxc-gpr", + .clk_names = imx8mm_clks, + .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, [IMX8MP] = { .variant = IMX8MP, .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .gpr = "fsl,imx8mp-iomuxc-gpr", + .clk_names = imx8mm_clks, + .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, [IMX8MQ_EP] = { .variant = IMX8MQ_EP, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mq-iomuxc-gpr", + .clk_names = imx8mq_clks, + .clks_cnt = ARRAY_SIZE(imx8mq_clks), }, [IMX8MM_EP] = { .variant = IMX8MM_EP, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mm-iomuxc-gpr", + .clk_names = imx8mm_clks, + .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, [IMX8MP_EP] = { .variant = IMX8MP_EP, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mp-iomuxc-gpr", + .clk_names = imx8mm_clks, + .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, }; -- cgit v1.2.3 From 4e37c2f48712d5874619685819e1cf7307f60840 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:12 -0500 Subject: PCI: imx6: Simplify PHY handling by using IMX6_PCIE_FLAG_HAS_PHYDRV Since some i.MX platforms make use of a separate PHY driver, use IMX6_PCIE_FLAG_HAS_PHYDRV flag to identify them and get the reference to PHY from DT to simplify the code. Link: https://lore.kernel.org/r/20240220161924.3871774-3-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index f820823c5f69..c60eaeeadd38 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -60,6 +60,9 @@ enum imx6_pcie_variants { #define IMX6_PCIE_FLAG_IMX6_PHY BIT(0) #define IMX6_PCIE_FLAG_IMX6_SPEED_CHANGE BIT(1) #define IMX6_PCIE_FLAG_SUPPORTS_SUSPEND BIT(2) +#define IMX6_PCIE_FLAG_HAS_PHYDRV BIT(3) + +#define imx6_check_flag(pci, val) (pci->drvdata->flags & val) #define IMX6_PCIE_MAX_CLKS 6 @@ -1277,6 +1280,13 @@ static int imx6_pcie_probe(struct platform_device *pdev) if (ret) return ret; + if (imx6_check_flag(imx6_pcie, IMX6_PCIE_FLAG_HAS_PHYDRV)) { + imx6_pcie->phy = devm_phy_get(dev, "pcie-phy"); + if (IS_ERR(imx6_pcie->phy)) + return dev_err_probe(dev, PTR_ERR(imx6_pcie->phy), + "failed to get pcie phy\n"); + } + switch (imx6_pcie->drvdata->variant) { case IMX8MQ: case IMX8MQ_EP: @@ -1308,11 +1318,6 @@ static int imx6_pcie_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(imx6_pcie->apps_reset), "failed to get pcie apps reset control\n"); - imx6_pcie->phy = devm_phy_get(dev, "pcie-phy"); - if (IS_ERR(imx6_pcie->phy)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->phy), - "failed to get pcie phy\n"); - break; default: break; @@ -1456,14 +1461,17 @@ static const struct imx6_pcie_drvdata drvdata[] = { }, [IMX8MM] = { .variant = IMX8MM, - .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, + .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND | + IMX6_PCIE_FLAG_HAS_PHYDRV | + IMX6_PCIE_FLAG_HAS_APP_RESET, .gpr = "fsl,imx8mm-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, [IMX8MP] = { .variant = IMX8MP, - .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, + .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND | + IMX6_PCIE_FLAG_HAS_PHYDRV, .gpr = "fsl,imx8mp-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), @@ -1477,6 +1485,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { }, [IMX8MM_EP] = { .variant = IMX8MM_EP, + .flags = IMX6_PCIE_FLAG_HAS_PHYDRV, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mm-iomuxc-gpr", .clk_names = imx8mm_clks, @@ -1484,6 +1493,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { }, [IMX8MP_EP] = { .variant = IMX8MP_EP, + .flags = IMX6_PCIE_FLAG_HAS_PHYDRV, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mp-iomuxc-gpr", .clk_names = imx8mm_clks, -- cgit v1.2.3 From 2ae8fbbe1cd42a6624d345151859982cba89bbd3 Mon Sep 17 00:00:00 2001 From: Smita Koralahalli Date: Wed, 7 Feb 2024 18:18:54 +0000 Subject: PCI/DPC: Ignore Surprise Down error on hot removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to PCIe r6.0 sec 6.7.6 [1], async removal with DPC may result in surprise down error. This error is expected and is just a side-effect of async remove. Ignore surprise down error generated as a side-effect of async remove. Typically, this error is benign as the pciehp handler invoked by PDC or/and DLLSC alongside DPC, de-enumerates and brings down the device appropriately, but the error messages might confuse users. Get rid of these irritating log messages with a 1s delay while pciehp waits for DPC recovery. The implementation is as follows: On an async remove a DPC is triggered along with a Presence Detect State change and/or DLL State Change. Determine it's an async remove by checking for DPC Trigger Status in DPC Status Register and Surprise Down Error Status in AER Uncorrected Error Status to be non-zero. If true, treat the DPC event as a side-effect of async remove, clear the error status registers and continue with hot-plug tear down routines. If not, follow the existing routine to handle AER and DPC errors. Masking Surprise Down Errors was explored as an alternative approach, but discarded due to the odd behavior that masking only avoids the interrupt, but still records an error per PCIe r6.0, sec 6.2.3.2.2. That stale error would be reported the next time some error other than Surprise Down is handled. Dmesg before: pcieport 0000:00:01.4: DPC: containment event, status:0x1f01 source:0x0000 pcieport 0000:00:01.4: DPC: unmasked uncorrectable error detected pcieport 0000:00:01.4: PCIe Bus Error: severity=Uncorrected (Fatal), type=Transaction Layer, (Receiver ID) pcieport 0000:00:01.4: device [1022:14ab] error status/mask=00000020/04004000 pcieport 0000:00:01.4: [ 5] SDES (First) nvme nvme2: frozen state error detected, reset controller pcieport 0000:00:01.4: DPC: Data Link Layer Link Active not set in 1000 msec pcieport 0000:00:01.4: AER: subordinate device reset failed pcieport 0000:00:01.4: AER: device recovery failed pcieport 0000:00:01.4: pciehp: Slot(16): Link Down nvme2n1: detected capacity change from 1953525168 to 0 pci 0000:04:00.0: Removing from iommu group 49 Dmesg after: pcieport 0000:00:01.4: pciehp: Slot(16): Link Down nvme1n1: detected capacity change from 1953525168 to 0 pci 0000:04:00.0: Removing from iommu group 37 [1] PCI Express Base Specification Revision 6.0, Dec 16 2021. https://members.pcisig.com/wg/PCI-SIG/document/16609 Link: https://lore.kernel.org/r/20240207181854.121335-1-Smita.KoralahalliChannabasappa@amd.com Signed-off-by: Smita Koralahalli Signed-off-by: Bjorn Helgaas Reviewed-by: Lukas Wunner Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Ilpo Järvinen --- drivers/pci/pcie/dpc.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index e5d7c12854fa..98b42e425bb9 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -303,10 +303,70 @@ void dpc_process_error(struct pci_dev *pdev) } } +static void pci_clear_surpdn_errors(struct pci_dev *pdev) +{ + if (pdev->dpc_rp_extensions) + pci_write_config_dword(pdev, pdev->dpc_cap + + PCI_EXP_DPC_RP_PIO_STATUS, ~0); + + /* + * In practice, Surprise Down errors have been observed to also set + * error bits in the Status Register as well as the Fatal Error + * Detected bit in the Device Status Register. + */ + pci_write_config_word(pdev, PCI_STATUS, 0xffff); + + pcie_capability_write_word(pdev, PCI_EXP_DEVSTA, PCI_EXP_DEVSTA_FED); +} + +static void dpc_handle_surprise_removal(struct pci_dev *pdev) +{ + if (!pcie_wait_for_link(pdev, false)) { + pci_info(pdev, "Data Link Layer Link Active not cleared in 1000 msec\n"); + goto out; + } + + if (pdev->dpc_rp_extensions && dpc_wait_rp_inactive(pdev)) + goto out; + + pci_aer_raw_clear_status(pdev); + pci_clear_surpdn_errors(pdev); + + pci_write_config_word(pdev, pdev->dpc_cap + PCI_EXP_DPC_STATUS, + PCI_EXP_DPC_STATUS_TRIGGER); + +out: + clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags); + wake_up_all(&dpc_completed_waitqueue); +} + +static bool dpc_is_surprise_removal(struct pci_dev *pdev) +{ + u16 status; + + if (!pdev->is_hotplug_bridge) + return false; + + if (pci_read_config_word(pdev, pdev->aer_cap + PCI_ERR_UNCOR_STATUS, + &status)) + return false; + + return status & PCI_ERR_UNC_SURPDN; +} + static irqreturn_t dpc_handler(int irq, void *context) { struct pci_dev *pdev = context; + /* + * According to PCIe r6.0 sec 6.7.6, errors are an expected side effect + * of async removal and should be ignored by software. + */ + if (dpc_is_surprise_removal(pdev)) { + dpc_handle_surprise_removal(pdev); + return IRQ_HANDLED; + } + dpc_process_error(pdev); /* We configure DPC so it only triggers on ERR_FATAL */ -- cgit v1.2.3 From cb98555fcd8eee98c30165537c7e394f3a66e809 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Wed, 28 Feb 2024 08:53:16 +0100 Subject: Revert "ACPI: PM: Block ASUS B1400CEAE from suspend to idle by default" This reverts commit d52848620de00cde4a3a5df908e231b8c8868250, which was originally put in place to work around a s2idle failure on this platform where the NVMe device was inaccessible upon resume. After extended testing, we found that the firmware's implementation of S3 is buggy and intermittently fails to wake up the system. We need to revert to s2idle mode. The NVMe issue has now been solved more precisely in the commit titled "PCI: Disable D3cold on Asus B1400 PCI-NVMe bridge" Link: https://bugzilla.kernel.org/show_bug.cgi?id=215742 Link: https://lore.kernel.org/r/20240228075316.7404-2-drake@endlessos.org Signed-off-by: Daniel Drake Signed-off-by: Bjorn Helgaas Acked-by: Jian-Hong Pan Acked-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'drivers') diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 808484d11209..728acfeb774d 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -385,18 +385,6 @@ static const struct dmi_system_id acpisleep_dmi_table[] __initconst = { DMI_MATCH(DMI_PRODUCT_NAME, "20GGA00L00"), }, }, - /* - * ASUS B1400CEAE hangs on resume from suspend (see - * https://bugzilla.kernel.org/show_bug.cgi?id=215742). - */ - { - .callback = init_default_s3, - .ident = "ASUS B1400CEAE", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "ASUS EXPERTBOOK B1400CEAE"), - }, - }, {}, }; -- cgit v1.2.3 From fa885b06ec7ee07c2498ad0ce4e0717c3b7dd487 Mon Sep 17 00:00:00 2001 From: Raag Jadav Date: Tue, 27 Feb 2024 11:56:48 +0530 Subject: PCI/PM: Allow runtime PM with no PM callbacks at all Commit c5eb1190074c ("PCI / PM: Allow runtime PM without callback functions") eliminated the need for PM callbacks in pci_pm_runtime_suspend() and pci_pm_runtime_resume(), but didn't do the same for pci_pm_runtime_idle(). Therefore, runtime suspend worked as long as the driver implemented at least one PM callback. But if the driver doesn't implement any PM callbacks at all (driver->pm is NULL), pci_pm_runtime_idle() returned -ENOSYS, which prevented runtime suspend. Modify pci_pm_runtime_idle() to allow PCI device power state transitions without runtime PM callbacks and complete the original intention of commit c5eb1190074c ("PCI / PM: Allow runtime PM without callback functions"). Link: https://lore.kernel.org/r/20240227062648.16579-1-raag.jadav@intel.com Signed-off-by: Raag Jadav [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Tested-by: Jarkko Nikula Reviewed-by: Andy Shevchenko Reviewed-by: Stanislaw Gruszka Acked-by: Rafael J. Wysocki --- drivers/pci/pci-driver.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 51ec9e7e784f..bb7f6775b350 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -1382,10 +1382,7 @@ static int pci_pm_runtime_idle(struct device *dev) if (!pci_dev->driver) return 0; - if (!pm) - return -ENOSYS; - - if (pm->runtime_idle) + if (pm && pm->runtime_idle) return pm->runtime_idle(dev); return 0; -- cgit v1.2.3 From 29a43dc130ce65d365a8ea9e1cc4bc51005a353e Mon Sep 17 00:00:00 2001 From: Edmund Raile Date: Tue, 27 Feb 2024 13:14:18 +0000 Subject: PCI: Mark LSI FW643 to avoid bus reset Apparently the LSI / Agere FW643 can't recover after a Secondary Bus Reset and requires a power-off or suspend/resume and rescan. VFIO resets a device before assigning it to a VM, and the FW643 doesn't support any other reset methods, so this problem prevented assignment of FW643 to VMs. Prevent use of Secondary Bus Reset for this device. With this change, the FW643 can be assigned to VMs with VFIO. Note that it will not be reset, resulting in leaking state between VMs and host. Link: https://lore.kernel.org/r/20240227131401.17913-1-edmund.raile@proton.me Signed-off-by: Edmund Raile [bhelgaas: commit log, comment] Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d797df6e5f3e..6ac58cd098b2 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3765,6 +3765,14 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003e, quirk_no_bus_reset); */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CAVIUM, 0xa100, quirk_no_bus_reset); +/* + * Apparently the LSI / Agere FW643 can't recover after a Secondary Bus + * Reset and requires a power-off or suspend/resume and rescan. Prevent + * use of that reset. + */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATT, 0x5900, quirk_no_bus_reset); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATT, 0x5901, quirk_no_bus_reset); + /* * Some TI KeyStone C667X devices do not support bus/hot reset. The PCIESS * automatically disables LTSSM when Secondary Bus Reset is received and -- cgit v1.2.3 From 0c9651c21f2a09672a983e4c43a74824eca3b174 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:13 -0500 Subject: PCI: imx6: Simplify reset handling by using *_FLAG_HAS_*_RESET Refactor the reset handling logic in the imx6 PCI driver by adding IMX6_PCIE_FLAG_HAS_*_RESET bitmask define for drvdata::flags. The drvdata::flags and bitmask ensure a cleaner and more scalable switch-case structure for handling reset. Link: https://lore.kernel.org/r/20240220161924.3871774-4-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam Reviewed-by: Philipp Zabel --- drivers/pci/controller/dwc/pci-imx6.c | 104 +++++++++++++--------------------- 1 file changed, 39 insertions(+), 65 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index c60eaeeadd38..3a2015a64e9e 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -61,6 +61,8 @@ enum imx6_pcie_variants { #define IMX6_PCIE_FLAG_IMX6_SPEED_CHANGE BIT(1) #define IMX6_PCIE_FLAG_SUPPORTS_SUSPEND BIT(2) #define IMX6_PCIE_FLAG_HAS_PHYDRV BIT(3) +#define IMX6_PCIE_FLAG_HAS_APP_RESET BIT(4) +#define IMX6_PCIE_FLAG_HAS_PHY_RESET BIT(5) #define imx6_check_flag(pci, val) (pci->drvdata->flags & val) @@ -661,18 +663,10 @@ static void imx6_pcie_clk_disable(struct imx6_pcie *imx6_pcie) static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) { + reset_control_assert(imx6_pcie->pciephy_reset); + reset_control_assert(imx6_pcie->apps_reset); + switch (imx6_pcie->drvdata->variant) { - case IMX7D: - case IMX8MQ: - case IMX8MQ_EP: - reset_control_assert(imx6_pcie->pciephy_reset); - fallthrough; - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: - reset_control_assert(imx6_pcie->apps_reset); - break; case IMX6SX: regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN, @@ -693,6 +687,8 @@ static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR1, IMX6Q_GPR1_PCIE_REF_CLK_EN, 0 << 16); break; + default: + break; } /* Some boards don't have PCIe reset GPIO. */ @@ -706,14 +702,10 @@ static int imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie) struct dw_pcie *pci = imx6_pcie->pci; struct device *dev = pci->dev; + reset_control_deassert(imx6_pcie->pciephy_reset); + switch (imx6_pcie->drvdata->variant) { - case IMX8MQ: - case IMX8MQ_EP: - reset_control_deassert(imx6_pcie->pciephy_reset); - break; case IMX7D: - reset_control_deassert(imx6_pcie->pciephy_reset); - /* Workaround for ERR010728, failure of PCI-e PLL VCO to * oscillate, especially when cold. This turns off "Duty-cycle * Corrector" and other mysterious undocumented things. @@ -745,11 +737,7 @@ static int imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie) usleep_range(200, 500); break; - case IMX6Q: /* Nothing to do */ - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: + default: break; } @@ -796,16 +784,11 @@ static void imx6_pcie_ltssm_enable(struct device *dev) IMX6Q_GPR12_PCIE_CTL_2, IMX6Q_GPR12_PCIE_CTL_2); break; - case IMX7D: - case IMX8MQ: - case IMX8MQ_EP: - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: - reset_control_deassert(imx6_pcie->apps_reset); + default: break; } + + reset_control_deassert(imx6_pcie->apps_reset); } static void imx6_pcie_ltssm_disable(struct device *dev) @@ -819,16 +802,11 @@ static void imx6_pcie_ltssm_disable(struct device *dev) regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6Q_GPR12_PCIE_CTL_2, 0); break; - case IMX7D: - case IMX8MQ: - case IMX8MQ_EP: - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: - reset_control_assert(imx6_pcie->apps_reset); + default: break; } + + reset_control_assert(imx6_pcie->apps_reset); } static int imx6_pcie_start_link(struct dw_pcie *pci) @@ -1287,37 +1265,26 @@ static int imx6_pcie_probe(struct platform_device *pdev) "failed to get pcie phy\n"); } + if (imx6_check_flag(imx6_pcie, IMX6_PCIE_FLAG_HAS_APP_RESET)) { + imx6_pcie->apps_reset = devm_reset_control_get_exclusive(dev, "apps"); + if (IS_ERR(imx6_pcie->apps_reset)) + return dev_err_probe(dev, PTR_ERR(imx6_pcie->apps_reset), + "failed to get pcie apps reset control\n"); + } + + if (imx6_check_flag(imx6_pcie, IMX6_PCIE_FLAG_HAS_PHY_RESET)) { + imx6_pcie->pciephy_reset = devm_reset_control_get_exclusive(dev, "pciephy"); + if (IS_ERR(imx6_pcie->pciephy_reset)) + return dev_err_probe(dev, PTR_ERR(imx6_pcie->pciephy_reset), + "Failed to get PCIEPHY reset control\n"); + } + switch (imx6_pcie->drvdata->variant) { case IMX8MQ: case IMX8MQ_EP: case IMX7D: if (dbi_base->start == IMX8MQ_PCIE2_BASE_ADDR) imx6_pcie->controller_id = 1; - - imx6_pcie->pciephy_reset = devm_reset_control_get_exclusive(dev, - "pciephy"); - if (IS_ERR(imx6_pcie->pciephy_reset)) { - dev_err(dev, "Failed to get PCIEPHY reset control\n"); - return PTR_ERR(imx6_pcie->pciephy_reset); - } - - imx6_pcie->apps_reset = devm_reset_control_get_exclusive(dev, - "apps"); - if (IS_ERR(imx6_pcie->apps_reset)) { - dev_err(dev, "Failed to get PCIE APPS reset control\n"); - return PTR_ERR(imx6_pcie->apps_reset); - } - break; - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: - imx6_pcie->apps_reset = devm_reset_control_get_exclusive(dev, - "apps"); - if (IS_ERR(imx6_pcie->apps_reset)) - return dev_err_probe(dev, PTR_ERR(imx6_pcie->apps_reset), - "failed to get pcie apps reset control\n"); - break; default: break; @@ -1448,13 +1415,17 @@ static const struct imx6_pcie_drvdata drvdata[] = { }, [IMX7D] = { .variant = IMX7D, - .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, + .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND | + IMX6_PCIE_FLAG_HAS_APP_RESET | + IMX6_PCIE_FLAG_HAS_PHY_RESET, .gpr = "fsl,imx7d-iomuxc-gpr", .clk_names = imx6q_clks, .clks_cnt = ARRAY_SIZE(imx6q_clks), }, [IMX8MQ] = { .variant = IMX8MQ, + .flags = IMX6_PCIE_FLAG_HAS_APP_RESET | + IMX6_PCIE_FLAG_HAS_PHY_RESET, .gpr = "fsl,imx8mq-iomuxc-gpr", .clk_names = imx8mq_clks, .clks_cnt = ARRAY_SIZE(imx8mq_clks), @@ -1471,13 +1442,16 @@ static const struct imx6_pcie_drvdata drvdata[] = { [IMX8MP] = { .variant = IMX8MP, .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND | - IMX6_PCIE_FLAG_HAS_PHYDRV, + IMX6_PCIE_FLAG_HAS_PHYDRV | + IMX6_PCIE_FLAG_HAS_APP_RESET, .gpr = "fsl,imx8mp-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), }, [IMX8MQ_EP] = { .variant = IMX8MQ_EP, + .flags = IMX6_PCIE_FLAG_HAS_APP_RESET | + IMX6_PCIE_FLAG_HAS_PHY_RESET, .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mq-iomuxc-gpr", .clk_names = imx8mq_clks, -- cgit v1.2.3 From d99aa8d3c4aca24b2d912e546939a5c571ed2acd Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:14 -0500 Subject: PCI: imx6: Simplify ltssm_enable() by using ltssm_off and ltssm_mask Add drvdata::ltssm_off and drvdata::ltssm_mask to simplify imx6_pcie_ltssm_enable(disable)() logic. Link: https://lore.kernel.org/r/20240220161924.3871774-5-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 3a2015a64e9e..376180deeb9c 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -76,6 +76,8 @@ struct imx6_pcie_drvdata { const char *gpr; const char * const *clk_names; const u32 clks_cnt; + const u32 ltssm_off; + const u32 ltssm_mask; }; struct imx6_pcie { @@ -775,18 +777,11 @@ static int imx6_pcie_wait_for_speed_change(struct imx6_pcie *imx6_pcie) static void imx6_pcie_ltssm_enable(struct device *dev) { struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev); + const struct imx6_pcie_drvdata *drvdata = imx6_pcie->drvdata; - switch (imx6_pcie->drvdata->variant) { - case IMX6Q: - case IMX6SX: - case IMX6QP: - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6Q_GPR12_PCIE_CTL_2, - IMX6Q_GPR12_PCIE_CTL_2); - break; - default: - break; - } + if (drvdata->ltssm_mask) + regmap_update_bits(imx6_pcie->iomuxc_gpr, drvdata->ltssm_off, drvdata->ltssm_mask, + drvdata->ltssm_mask); reset_control_deassert(imx6_pcie->apps_reset); } @@ -794,17 +789,11 @@ static void imx6_pcie_ltssm_enable(struct device *dev) static void imx6_pcie_ltssm_disable(struct device *dev) { struct imx6_pcie *imx6_pcie = dev_get_drvdata(dev); + const struct imx6_pcie_drvdata *drvdata = imx6_pcie->drvdata; - switch (imx6_pcie->drvdata->variant) { - case IMX6Q: - case IMX6SX: - case IMX6QP: - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6Q_GPR12_PCIE_CTL_2, 0); - break; - default: - break; - } + if (drvdata->ltssm_mask) + regmap_update_bits(imx6_pcie->iomuxc_gpr, drvdata->ltssm_off, + drvdata->ltssm_mask, 0); reset_control_assert(imx6_pcie->apps_reset); } @@ -1393,6 +1382,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx6q-iomuxc-gpr", .clk_names = imx6q_clks, .clks_cnt = ARRAY_SIZE(imx6q_clks), + .ltssm_off = IOMUXC_GPR12, + .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, }, [IMX6SX] = { .variant = IMX6SX, @@ -1402,6 +1393,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx6q-iomuxc-gpr", .clk_names = imx6sx_clks, .clks_cnt = ARRAY_SIZE(imx6sx_clks), + .ltssm_off = IOMUXC_GPR12, + .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, }, [IMX6QP] = { .variant = IMX6QP, @@ -1412,6 +1405,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx6q-iomuxc-gpr", .clk_names = imx6q_clks, .clks_cnt = ARRAY_SIZE(imx6q_clks), + .ltssm_off = IOMUXC_GPR12, + .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, }, [IMX7D] = { .variant = IMX7D, -- cgit v1.2.3 From f99b121c258918906b119d9333277fa6987a7fb1 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:15 -0500 Subject: PCI: imx6: Simplify configure_type() by using mode_off and mode_mask Add drvdata::mode_off and drvdata::mode_mask to simplify imx6_pcie_configure_type() logic. Link: https://lore.kernel.org/r/20240220161924.3871774-6-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 59 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 20 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 376180deeb9c..cf10ba781bed 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -68,6 +68,7 @@ enum imx6_pcie_variants { #define IMX6_PCIE_MAX_CLKS 6 +#define IMX6_PCIE_MAX_INSTANCES 2 struct imx6_pcie_drvdata { enum imx6_pcie_variants variant; enum dw_pcie_device_mode mode; @@ -78,6 +79,8 @@ struct imx6_pcie_drvdata { const u32 clks_cnt; const u32 ltssm_off; const u32 ltssm_mask; + const u32 mode_off[IMX6_PCIE_MAX_INSTANCES]; + const u32 mode_mask[IMX6_PCIE_MAX_INSTANCES]; }; struct imx6_pcie { @@ -174,32 +177,24 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) static void imx6_pcie_configure_type(struct imx6_pcie *imx6_pcie) { - unsigned int mask, val, mode; + const struct imx6_pcie_drvdata *drvdata = imx6_pcie->drvdata; + unsigned int mask, val, mode, id; - if (imx6_pcie->drvdata->mode == DW_PCIE_EP_TYPE) + if (drvdata->mode == DW_PCIE_EP_TYPE) mode = PCI_EXP_TYPE_ENDPOINT; else mode = PCI_EXP_TYPE_ROOT_PORT; - switch (imx6_pcie->drvdata->variant) { - case IMX8MQ: - case IMX8MQ_EP: - if (imx6_pcie->controller_id == 1) { - mask = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE; - val = FIELD_PREP(IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, - mode); - } else { - mask = IMX6Q_GPR12_DEVICE_TYPE; - val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, mode); - } - break; - default: - mask = IMX6Q_GPR12_DEVICE_TYPE; - val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, mode); - break; - } + id = imx6_pcie->controller_id; + + /* If mode_mask[id] is zero, means each controller have its individual gpr */ + if (!drvdata->mode_mask[id]) + id = 0; + + mask = drvdata->mode_mask[id]; + val = mode << (ffs(mask) - 1); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, mask, val); + regmap_update_bits(imx6_pcie->iomuxc_gpr, drvdata->mode_off[id], mask, val); } static int pcie_phy_poll_ack(struct imx6_pcie *imx6_pcie, bool exp_val) @@ -1384,6 +1379,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx6q_clks), .ltssm_off = IOMUXC_GPR12, .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX6SX] = { .variant = IMX6SX, @@ -1395,6 +1392,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx6sx_clks), .ltssm_off = IOMUXC_GPR12, .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX6QP] = { .variant = IMX6QP, @@ -1407,6 +1406,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx6q_clks), .ltssm_off = IOMUXC_GPR12, .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX7D] = { .variant = IMX7D, @@ -1416,6 +1417,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx7d-iomuxc-gpr", .clk_names = imx6q_clks, .clks_cnt = ARRAY_SIZE(imx6q_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX8MQ] = { .variant = IMX8MQ, @@ -1424,6 +1427,10 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mq-iomuxc-gpr", .clk_names = imx8mq_clks, .clks_cnt = ARRAY_SIZE(imx8mq_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .mode_off[1] = IOMUXC_GPR12, + .mode_mask[1] = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, }, [IMX8MM] = { .variant = IMX8MM, @@ -1433,6 +1440,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mm-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX8MP] = { .variant = IMX8MP, @@ -1442,6 +1451,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mp-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX8MQ_EP] = { .variant = IMX8MQ_EP, @@ -1451,6 +1462,10 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mq-iomuxc-gpr", .clk_names = imx8mq_clks, .clks_cnt = ARRAY_SIZE(imx8mq_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .mode_off[1] = IOMUXC_GPR12, + .mode_mask[1] = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, }, [IMX8MM_EP] = { .variant = IMX8MM_EP, @@ -1459,6 +1474,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mm-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, [IMX8MP_EP] = { .variant = IMX8MP_EP, @@ -1467,6 +1484,8 @@ static const struct imx6_pcie_drvdata drvdata[] = { .gpr = "fsl,imx8mp-iomuxc-gpr", .clk_names = imx8mm_clks, .clks_cnt = ARRAY_SIZE(imx8mm_clks), + .mode_off[0] = IOMUXC_GPR12, + .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, }; -- cgit v1.2.3 From 21ad80b0e0ce5f2b6d17e11bc823e8e0a1527555 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:16 -0500 Subject: PCI: imx6: Simplify switch-case logic by introducing init_phy() callback Instead of using the switch case statement to initialize the PHY handled by this driver itself, let's introduce a new callback init_phy() and define it for platforms that require it. Link: https://lore.kernel.org/r/20240220161924.3871774-7-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 134 +++++++++++++++++----------------- 1 file changed, 69 insertions(+), 65 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index cf10ba781bed..7716cf56fa56 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -69,6 +69,9 @@ enum imx6_pcie_variants { #define IMX6_PCIE_MAX_CLKS 6 #define IMX6_PCIE_MAX_INSTANCES 2 + +struct imx6_pcie; + struct imx6_pcie_drvdata { enum imx6_pcie_variants variant; enum dw_pcie_device_mode mode; @@ -81,6 +84,7 @@ struct imx6_pcie_drvdata { const u32 ltssm_mask; const u32 mode_off[IMX6_PCIE_MAX_INSTANCES]; const u32 mode_mask[IMX6_PCIE_MAX_INSTANCES]; + int (*init_phy)(struct imx6_pcie *pcie); }; struct imx6_pcie { @@ -322,76 +326,66 @@ static int pcie_phy_write(struct imx6_pcie *imx6_pcie, int addr, u16 data) return 0; } -static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie) +static int imx8mq_pcie_init_phy(struct imx6_pcie *imx6_pcie) { - switch (imx6_pcie->drvdata->variant) { - case IMX8MM: - case IMX8MM_EP: - case IMX8MP: - case IMX8MP_EP: - /* - * The PHY initialization had been done in the PHY - * driver, break here directly. - */ - break; - case IMX8MQ: - case IMX8MQ_EP: - /* - * TODO: Currently this code assumes external - * oscillator is being used - */ + /* TODO: Currently this code assumes external oscillator is being used */ + regmap_update_bits(imx6_pcie->iomuxc_gpr, + imx6_pcie_grp_offset(imx6_pcie), + IMX8MQ_GPR_PCIE_REF_USE_PAD, + IMX8MQ_GPR_PCIE_REF_USE_PAD); + /* + * Regarding the datasheet, the PCIE_VPH is suggested to be 1.8V. If the PCIE_VPH is + * supplied by 3.3V, the VREG_BYPASS should be cleared to zero. + */ + if (imx6_pcie->vph && regulator_get_voltage(imx6_pcie->vph) > 3000000) regmap_update_bits(imx6_pcie->iomuxc_gpr, imx6_pcie_grp_offset(imx6_pcie), - IMX8MQ_GPR_PCIE_REF_USE_PAD, - IMX8MQ_GPR_PCIE_REF_USE_PAD); - /* - * Regarding the datasheet, the PCIE_VPH is suggested - * to be 1.8V. If the PCIE_VPH is supplied by 3.3V, the - * VREG_BYPASS should be cleared to zero. - */ - if (imx6_pcie->vph && - regulator_get_voltage(imx6_pcie->vph) > 3000000) - regmap_update_bits(imx6_pcie->iomuxc_gpr, - imx6_pcie_grp_offset(imx6_pcie), - IMX8MQ_GPR_PCIE_VREG_BYPASS, - 0); - break; - case IMX7D: - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, 0); - break; - case IMX6SX: - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6SX_GPR12_PCIE_RX_EQ_MASK, - IMX6SX_GPR12_PCIE_RX_EQ_2); - fallthrough; - default: - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX8MQ_GPR_PCIE_VREG_BYPASS, + 0); + + return 0; +} + +static int imx7d_pcie_init_phy(struct imx6_pcie *imx6_pcie) +{ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX7D_GPR12_PCIE_PHY_REFCLK_SEL, 0); + + return 0; +} + +static int imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie) +{ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6Q_GPR12_PCIE_CTL_2, 0 << 10); - /* configure constant input signal to the pcie ctrl and phy */ - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6Q_GPR12_LOS_LEVEL, 9 << 4); - - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - 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, - imx6_pcie->tx_deemph_gen2_3p5db << 6); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - 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, - imx6_pcie->tx_swing_full << 18); - regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, - IMX6Q_GPR8_TX_SWING_LOW, - imx6_pcie->tx_swing_low << 25); - break; - } + /* configure constant input signal to the pcie ctrl and phy */ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6Q_GPR12_LOS_LEVEL, 9 << 4); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + 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, + imx6_pcie->tx_deemph_gen2_3p5db << 6); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + 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, + imx6_pcie->tx_swing_full << 18); + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR8, + IMX6Q_GPR8_TX_SWING_LOW, + imx6_pcie->tx_swing_low << 25); + return 0; +} - imx6_pcie_configure_type(imx6_pcie); +static int imx6sx_pcie_init_phy(struct imx6_pcie *imx6_pcie) +{ + regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_RX_EQ_MASK, IMX6SX_GPR12_PCIE_RX_EQ_2); + + return imx6_pcie_init_phy(imx6_pcie); } static void imx7d_pcie_wait_for_phy_pll_lock(struct imx6_pcie *imx6_pcie) @@ -902,7 +896,11 @@ static int imx6_pcie_host_init(struct dw_pcie_rp *pp) } imx6_pcie_assert_core_reset(imx6_pcie); - imx6_pcie_init_phy(imx6_pcie); + + if (imx6_pcie->drvdata->init_phy) + imx6_pcie->drvdata->init_phy(imx6_pcie); + + imx6_pcie_configure_type(imx6_pcie); ret = imx6_pcie_clk_enable(imx6_pcie); if (ret) { @@ -1381,6 +1379,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .init_phy = imx6_pcie_init_phy, }, [IMX6SX] = { .variant = IMX6SX, @@ -1394,6 +1393,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .init_phy = imx6sx_pcie_init_phy, }, [IMX6QP] = { .variant = IMX6QP, @@ -1408,6 +1408,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .ltssm_mask = IMX6Q_GPR12_PCIE_CTL_2, .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .init_phy = imx6_pcie_init_phy, }, [IMX7D] = { .variant = IMX7D, @@ -1419,6 +1420,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx6q_clks), .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .init_phy = imx7d_pcie_init_phy, }, [IMX8MQ] = { .variant = IMX8MQ, @@ -1431,6 +1433,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .mode_off[1] = IOMUXC_GPR12, .mode_mask[1] = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, + .init_phy = imx8mq_pcie_init_phy, }, [IMX8MM] = { .variant = IMX8MM, @@ -1466,6 +1469,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .mode_off[1] = IOMUXC_GPR12, .mode_mask[1] = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, + .init_phy = imx8mq_pcie_init_phy, }, [IMX8MM_EP] = { .variant = IMX8MM_EP, -- cgit v1.2.3 From f5c04da3a12b0a69fa2cb5235fe5d1407fb15b51 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:20 -0500 Subject: PCI: imx6: Add iMX95 PCIe Root Complex support Add iMX95 PCIe Root Complex support. Link: https://lore.kernel.org/r/20240220161924.3871774-11-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 76 ++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 7716cf56fa56..b3aaacc88f38 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -42,6 +42,19 @@ #define IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE GENMASK(11, 8) #define IMX8MQ_PCIE2_BASE_ADDR 0x33c00000 +#define IMX95_PCIE_PHY_GEN_CTRL 0x0 +#define IMX95_PCIE_REF_USE_PAD BIT(17) + +#define IMX95_PCIE_SS_RW_REG_0 0xf0 +#define IMX95_PCIE_REF_CLKEN BIT(23) +#define IMX95_PCIE_PHY_CR_PARA_SEL BIT(9) + +#define IMX95_PE0_GEN_CTRL_1 0x1050 +#define IMX95_PCIE_DEVICE_TYPE GENMASK(3, 0) + +#define IMX95_PE0_GEN_CTRL_3 0x1058 +#define IMX95_PCIE_LTSSM_EN BIT(0) + #define to_imx6_pcie(x) dev_get_drvdata((x)->dev) enum imx6_pcie_variants { @@ -52,6 +65,7 @@ enum imx6_pcie_variants { IMX8MQ, IMX8MM, IMX8MP, + IMX95, IMX8MQ_EP, IMX8MM_EP, IMX8MP_EP, @@ -63,6 +77,7 @@ enum imx6_pcie_variants { #define IMX6_PCIE_FLAG_HAS_PHYDRV BIT(3) #define IMX6_PCIE_FLAG_HAS_APP_RESET BIT(4) #define IMX6_PCIE_FLAG_HAS_PHY_RESET BIT(5) +#define IMX6_PCIE_FLAG_HAS_SERDES BIT(6) #define imx6_check_flag(pci, val) (pci->drvdata->flags & val) @@ -179,6 +194,24 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) return imx6_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; } +static int imx95_pcie_init_phy(struct imx6_pcie *imx6_pcie) +{ + regmap_update_bits(imx6_pcie->iomuxc_gpr, + IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_PHY_CR_PARA_SEL, + IMX95_PCIE_PHY_CR_PARA_SEL); + + regmap_update_bits(imx6_pcie->iomuxc_gpr, + IMX95_PCIE_PHY_GEN_CTRL, + IMX95_PCIE_REF_USE_PAD, 0); + regmap_update_bits(imx6_pcie->iomuxc_gpr, + IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_REF_CLKEN, + IMX95_PCIE_REF_CLKEN); + + return 0; +} + static void imx6_pcie_configure_type(struct imx6_pcie *imx6_pcie) { const struct imx6_pcie_drvdata *drvdata = imx6_pcie->drvdata; @@ -575,6 +608,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) IMX6Q_GPR1_PCIE_REF_CLK_EN, 1 << 16); break; case IMX7D: + case IMX95: break; case IMX8MM: case IMX8MM_EP: @@ -1279,12 +1313,32 @@ static int imx6_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx6_pcie->turnoff_reset); } + if (imx6_pcie->drvdata->gpr) { /* Grab GPR config register range */ - imx6_pcie->iomuxc_gpr = - syscon_regmap_lookup_by_compatible(imx6_pcie->drvdata->gpr); - if (IS_ERR(imx6_pcie->iomuxc_gpr)) { - dev_err(dev, "unable to find iomuxc registers\n"); - return PTR_ERR(imx6_pcie->iomuxc_gpr); + imx6_pcie->iomuxc_gpr = + syscon_regmap_lookup_by_compatible(imx6_pcie->drvdata->gpr); + if (IS_ERR(imx6_pcie->iomuxc_gpr)) + return dev_err_probe(dev, PTR_ERR(imx6_pcie->iomuxc_gpr), + "unable to find iomuxc registers\n"); + } + + if (imx6_check_flag(imx6_pcie, IMX6_PCIE_FLAG_HAS_SERDES)) { + void __iomem *off = devm_platform_ioremap_resource_byname(pdev, "app"); + + if (IS_ERR(off)) + return dev_err_probe(dev, PTR_ERR(off), + "unable to find serdes registers\n"); + + static const struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + + imx6_pcie->iomuxc_gpr = devm_regmap_init_mmio(dev, off, ®map_config); + if (IS_ERR(imx6_pcie->iomuxc_gpr)) + return dev_err_probe(dev, PTR_ERR(imx6_pcie->iomuxc_gpr), + "unable to find iomuxc registers\n"); } /* Grab PCIe PHY Tx Settings */ @@ -1457,6 +1511,17 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, }, + [IMX95] = { + .variant = IMX95, + .flags = IMX6_PCIE_FLAG_HAS_SERDES, + .clk_names = imx8mq_clks, + .clks_cnt = ARRAY_SIZE(imx8mq_clks), + .ltssm_off = IMX95_PE0_GEN_CTRL_3, + .ltssm_mask = IMX95_PCIE_LTSSM_EN, + .mode_off[0] = IMX95_PE0_GEN_CTRL_1, + .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, + .init_phy = imx95_pcie_init_phy, + }, [IMX8MQ_EP] = { .variant = IMX8MQ_EP, .flags = IMX6_PCIE_FLAG_HAS_APP_RESET | @@ -1501,6 +1566,7 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx8mq-pcie", .data = &drvdata[IMX8MQ], }, { .compatible = "fsl,imx8mm-pcie", .data = &drvdata[IMX8MM], }, { .compatible = "fsl,imx8mp-pcie", .data = &drvdata[IMX8MP], }, + { .compatible = "fsl,imx95-pcie", .data = &drvdata[IMX95], }, { .compatible = "fsl,imx8mq-pcie-ep", .data = &drvdata[IMX8MQ_EP], }, { .compatible = "fsl,imx8mm-pcie-ep", .data = &drvdata[IMX8MM_EP], }, { .compatible = "fsl,imx8mp-pcie-ep", .data = &drvdata[IMX8MP_EP], }, -- cgit v1.2.3 From 1bd0d43dcf3b4259c51b2e6fd87eb567b622d90d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:21 -0500 Subject: PCI: imx6: Clean up addr_space retrieval code Since the dw_pcie_ep_init() function is already fetching the 'addr_space' region, no need to do the same in this driver. Link: https://lore.kernel.org/r/20240220161924.3871774-12-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index b3aaacc88f38..2907a8f25e33 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1061,7 +1061,6 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, int ret; unsigned int pcie_dbi2_offset; struct dw_pcie_ep *ep; - struct resource *res; struct dw_pcie *pci = imx6_pcie->pci; struct dw_pcie_rp *pp = &pci->pp; struct device *dev = pci->dev; @@ -1080,14 +1079,8 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, pcie_dbi2_offset = SZ_4K; break; } - pci->dbi_base2 = pci->dbi_base + pcie_dbi2_offset; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space"); - if (!res) - return -EINVAL; - ep->phys_base = res->start; - ep->addr_size = resource_size(res); - ep->page_size = SZ_64K; + pci->dbi_base2 = pci->dbi_base + pcie_dbi2_offset; ret = dw_pcie_ep_init(ep); if (ret) { -- cgit v1.2.3 From 0044966cdadf3896389e6075d18468cfc0a54215 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:22 -0500 Subject: PCI: imx6: Add epc_features in imx6_pcie_drvdata The i.MX EP exhibits variations in epc_features among different EP configurations. This introduces the addition of epc_features in imx6_pcie_drvdata to accommodate these differences. It's important to note that there are no functional changes in this commit; instead, it lays the groundwork for supporting i.MX95 EP functions. Link: https://lore.kernel.org/r/20240220161924.3871774-13-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 2907a8f25e33..1b2f40f83f05 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -99,6 +99,7 @@ struct imx6_pcie_drvdata { const u32 ltssm_mask; const u32 mode_off[IMX6_PCIE_MAX_INSTANCES]; const u32 mode_mask[IMX6_PCIE_MAX_INSTANCES]; + const struct pci_epc_features *epc_features; int (*init_phy)(struct imx6_pcie *pcie); }; @@ -1046,7 +1047,10 @@ static const struct pci_epc_features imx8m_pcie_epc_features = { static const struct pci_epc_features* imx6_pcie_ep_get_features(struct dw_pcie_ep *ep) { - return &imx8m_pcie_epc_features; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct imx6_pcie *imx6_pcie = to_imx6_pcie(pci); + + return imx6_pcie->drvdata->epc_features; } static const struct dw_pcie_ep_ops pcie_ep_ops = { @@ -1527,6 +1531,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .mode_off[1] = IOMUXC_GPR12, .mode_mask[1] = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, + .epc_features = &imx8m_pcie_epc_features, .init_phy = imx8mq_pcie_init_phy, }, [IMX8MM_EP] = { @@ -1538,6 +1543,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx8mm_clks), .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .epc_features = &imx8m_pcie_epc_features, }, [IMX8MP_EP] = { .variant = IMX8MP_EP, @@ -1548,6 +1554,7 @@ static const struct imx6_pcie_drvdata drvdata[] = { .clks_cnt = ARRAY_SIZE(imx8mm_clks), .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, + .epc_features = &imx8m_pcie_epc_features, }, }; -- cgit v1.2.3 From b7d67c6130ee0a900dc5c8c37da849138188b182 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 20 Feb 2024 11:19:24 -0500 Subject: PCI: imx6: Add iMX95 Endpoint (EP) support Add iMX95 EP support and add 64bit address support. The internal bus bridge for PCI support 64bit DMA address in iMX95 hence call dma_set_mask_and_coherent() to set 64 bit DMA mask. Link: https://lore.kernel.org/r/20240220161924.3871774-15-Frank.Li@nxp.com Signed-off-by: Frank Li Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pci-imx6.c | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 1b2f40f83f05..99a60270b26c 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -69,6 +69,7 @@ enum imx6_pcie_variants { IMX8MQ_EP, IMX8MM_EP, IMX8MP_EP, + IMX95_EP, }; #define IMX6_PCIE_FLAG_IMX6_PHY BIT(0) @@ -78,6 +79,7 @@ enum imx6_pcie_variants { #define IMX6_PCIE_FLAG_HAS_APP_RESET BIT(4) #define IMX6_PCIE_FLAG_HAS_PHY_RESET BIT(5) #define IMX6_PCIE_FLAG_HAS_SERDES BIT(6) +#define IMX6_PCIE_FLAG_SUPPORT_64BIT BIT(7) #define imx6_check_flag(pci, val) (pci->drvdata->flags & val) @@ -610,6 +612,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) break; case IMX7D: case IMX95: + case IMX95_EP: break; case IMX8MM: case IMX8MM_EP: @@ -1044,6 +1047,23 @@ static const struct pci_epc_features imx8m_pcie_epc_features = { .align = SZ_64K, }; +/* + * BAR# | Default BAR enable | Default BAR Type | Default BAR Size | BAR Sizing Scheme + * ================================================================================================ + * BAR0 | Enable | 64-bit | 1 MB | Programmable Size + * BAR1 | Disable | 32-bit | 64 KB | Fixed Size + * BAR1 should be disabled if BAR0 is 64bit. + * BAR2 | Enable | 32-bit | 1 MB | Programmable Size + * BAR3 | Enable | 32-bit | 64 KB | Programmable Size + * BAR4 | Enable | 32-bit | 1M | Programmable Size + * BAR5 | Enable | 32-bit | 64 KB | Programmable Size + */ +static const struct pci_epc_features imx95_pcie_epc_features = { + .msi_capable = true, + .bar[BAR_1] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, + .align = SZ_4K, +}; + static const struct pci_epc_features* imx6_pcie_ep_get_features(struct dw_pcie_ep *ep) { @@ -1086,6 +1106,18 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, pci->dbi_base2 = pci->dbi_base + pcie_dbi2_offset; + /* + * FIXME: Ideally, dbi2 base address should come from DT. But since only IMX95 is defining + * "dbi2" in DT, "dbi_base2" is set to NULL here for that platform alone so that the DWC + * core code can fetch that from DT. But once all platform DTs were fixed, this and the + * above "dbi_base2" setting should be removed. + */ + if (device_property_match_string(dev, "reg-names", "dbi2") >= 0) + pci->dbi_base2 = NULL; + + if (imx6_check_flag(imx6_pcie, IMX6_PCIE_FLAG_SUPPORT_64BIT)) + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + ret = dw_pcie_ep_init(ep); if (ret) { dev_err(dev, "failed to initialize endpoint\n"); @@ -1556,6 +1588,20 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .epc_features = &imx8m_pcie_epc_features, }, + [IMX95_EP] = { + .variant = IMX95_EP, + .flags = IMX6_PCIE_FLAG_HAS_SERDES | + IMX6_PCIE_FLAG_SUPPORT_64BIT, + .clk_names = imx8mq_clks, + .clks_cnt = ARRAY_SIZE(imx8mq_clks), + .ltssm_off = IMX95_PE0_GEN_CTRL_3, + .ltssm_mask = IMX95_PCIE_LTSSM_EN, + .mode_off[0] = IMX95_PE0_GEN_CTRL_1, + .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, + .init_phy = imx95_pcie_init_phy, + .epc_features = &imx95_pcie_epc_features, + .mode = DW_PCIE_EP_TYPE, + }, }; static const struct of_device_id imx6_pcie_of_match[] = { @@ -1570,6 +1616,7 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx8mq-pcie-ep", .data = &drvdata[IMX8MQ_EP], }, { .compatible = "fsl,imx8mm-pcie-ep", .data = &drvdata[IMX8MM_EP], }, { .compatible = "fsl,imx8mp-pcie-ep", .data = &drvdata[IMX8MP_EP], }, + { .compatible = "fsl,imx95-pcie-ep", .data = &drvdata[IMX95_EP], }, {}, }; -- cgit v1.2.3 From 9d5286d4e7f68beab450deddbb6a32edd5ecf4bf Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 5 Mar 2024 11:45:38 +0100 Subject: PCI/PM: Drain runtime-idle callbacks before driver removal A race condition between the .runtime_idle() callback and the .remove() callback in the rtsx_pcr PCI driver leads to a kernel crash due to an unhandled page fault [1]. The problem is that rtsx_pci_runtime_idle() is not expected to be running after pm_runtime_get_sync() has been called, but the latter doesn't really guarantee that. It only guarantees that the suspend and resume callbacks will not be running when it returns. However, if a .runtime_idle() callback is already running when pm_runtime_get_sync() is called, the latter will notice that the runtime PM status of the device is RPM_ACTIVE and it will return right away without waiting for the former to complete. In fact, it cannot wait for .runtime_idle() to complete because it may be called from that callback (it arguably does not make much sense to do that, but it is not strictly prohibited). Thus in general, whoever is providing a .runtime_idle() callback needs to protect it from running in parallel with whatever code runs after pm_runtime_get_sync(). [Note that .runtime_idle() will not start after pm_runtime_get_sync() has returned, but it may continue running then if it has started earlier.] One way to address that race condition is to call pm_runtime_barrier() after pm_runtime_get_sync() (not before it, because a nonzero value of the runtime PM usage counter is necessary to prevent runtime PM callbacks from being invoked) to wait for the .runtime_idle() callback to complete should it be running at that point. A suitable place for doing that is in pci_device_remove() which calls pm_runtime_get_sync() before removing the driver, so it may as well call pm_runtime_barrier() subsequently, which will prevent the race in question from occurring, not just in the rtsx_pcr driver, but in any PCI drivers providing .runtime_idle() callbacks. Link: https://lore.kernel.org/lkml/20240229062201.49500-1-kai.heng.feng@canonical.com/ # [1] Link: https://lore.kernel.org/r/5761426.DvuYhMxLoT@kreacher Reported-by: Kai-Heng Feng Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas Tested-by: Ricky Wu Acked-by: Kai-Heng Feng Cc: --- drivers/pci/pci-driver.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index bb7f6775b350..652506c54a54 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -473,6 +473,13 @@ static void pci_device_remove(struct device *dev) if (drv->remove) { pm_runtime_get_sync(dev); + /* + * If the driver provides a .runtime_idle() callback and it has + * started to run already, it may continue to run in parallel + * with the code below, so wait until all of the runtime PM + * activity has completed. + */ + pm_runtime_barrier(dev); drv->remove(pci_dev); pm_runtime_put_noidle(dev); } -- cgit v1.2.3 From 627c6db20703b5d18d928464f411d0d4ec327508 Mon Sep 17 00:00:00 2001 From: Paul Menzel Date: Tue, 5 Mar 2024 12:30:56 +0100 Subject: PCI/DPC: Quirk PIO log size for Intel Raptor Lake Root Ports Commit 5459c0b70467 ("PCI/DPC: Quirk PIO log size for certain Intel Root Ports") and commit 3b8803494a06 ("PCI/DPC: Quirk PIO log size for Intel Ice Lake Root Ports") add quirks for Ice, Tiger and Alder Lake Root Ports. System firmware for Raptor Lake still has the bug, so Linux logs the warning below on several Raptor Lake systems like Dell Precision 3581 with Intel Raptor Lake processor (0W18NX) system firmware/BIOS version 1.10.1. pci 0000:00:07.0: [8086:a76e] type 01 class 0x060400 pci 0000:00:07.0: DPC: RP PIO log size 0 is invalid pci 0000:00:07.1: [8086:a73f] type 01 class 0x060400 pci 0000:00:07.1: DPC: RP PIO log size 0 is invalid Apply the quirk for Raptor Lake Root Ports as well. This also enables the DPC driver to dump the RP PIO Log registers when DPC is triggered. Link: https://lore.kernel.org/r/20240305113057.56468-1-pmenzel@molgen.mpg.de Reported-by: Niels van Aert Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218560 Signed-off-by: Paul Menzel Signed-off-by: Bjorn Helgaas Cc: Cc: Mika Westerberg Cc: Niels van Aert --- drivers/pci/quirks.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d797df6e5f3e..663d838fa861 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6225,6 +6225,8 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2b, dpc_log_size); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2d, dpc_log_size); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a2f, dpc_log_size); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a31, dpc_log_size); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xa73f, dpc_log_size); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xa76e, dpc_log_size); #endif /* -- cgit v1.2.3 From be9c3a4c8be13326e434d8817d6dda6c5d2835f5 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Mon, 30 Oct 2023 13:32:12 +0100 Subject: PCI/sysfs: Compile pci-sysfs.c only if CONFIG_SYSFS=y It is possible to enable CONFIG_PCI but disable CONFIG_SYSFS and for space-constrained devices such as routers, such a configuration may actually make sense. However pci-sysfs.c is compiled even if CONFIG_SYSFS is disabled, unnecessarily increasing the kernel's size. To rectify that: * Move pci_mmap_fits() to mmap.c. It is not only needed by pci-sysfs.c, but also proc.c. * Move pci_dev_type to probe.c and make it private. It references pci_dev_attr_groups in pci-sysfs.c. Make that public instead for consistency with pci_dev_groups, pcibus_groups and pci_bus_groups, which are likewise public and referenced by struct definitions in pci-driver.c and probe.c. * Define pci_dev_groups, pci_dev_attr_groups, pcibus_groups and pci_bus_groups to NULL if CONFIG_SYSFS is disabled. Provide empty static inlines for pci_{create,remove}_legacy_files() and pci_{create,remove}_sysfs_dev_files(). Result: vmlinux size is reduced by 122996 bytes in my arm 32-bit test build. Link: https://lore.kernel.org/r/85ca95ae8e4d57ccf082c5c069b8b21eb141846e.1698668982.git.lukas@wunner.de Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Alistair Francis --- drivers/pci/Makefile | 4 ++-- drivers/pci/mmap.c | 29 +++++++++++++++++++++++++++++ drivers/pci/pci-sysfs.c | 29 +---------------------------- drivers/pci/pci.h | 18 ++++++++++++++---- drivers/pci/probe.c | 4 ++++ 5 files changed, 50 insertions(+), 34 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index cc8b4e01e29d..96f4759f2bd3 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_PCI) += access.o bus.o probe.o host-bridge.o \ remove.o pci.o pci-driver.o search.o \ - pci-sysfs.o rom.o setup-res.o irq.o vpd.o \ + rom.o setup-res.o irq.o vpd.o \ setup-bus.o vc.o mmap.o setup-irq.o obj-$(CONFIG_PCI) += msi/ @@ -12,7 +12,7 @@ obj-$(CONFIG_PCI) += pcie/ ifdef CONFIG_PCI obj-$(CONFIG_PROC_FS) += proc.o -obj-$(CONFIG_SYSFS) += slot.o +obj-$(CONFIG_SYSFS) += pci-sysfs.o slot.o obj-$(CONFIG_ACPI) += pci-acpi.o endif diff --git a/drivers/pci/mmap.c b/drivers/pci/mmap.c index 4504039056d1..8da3347a95c4 100644 --- a/drivers/pci/mmap.c +++ b/drivers/pci/mmap.c @@ -11,6 +11,8 @@ #include #include +#include "pci.h" + #ifdef ARCH_GENERIC_PCI_MMAP_RESOURCE static const struct vm_operations_struct pci_phys_vm_ops = { @@ -50,3 +52,30 @@ int pci_mmap_resource_range(struct pci_dev *pdev, int bar, } #endif + +#if (defined(CONFIG_SYSFS) || defined(CONFIG_PROC_FS)) && \ + (defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)) + +int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma, + enum pci_mmap_api mmap_api) +{ + resource_size_t pci_start = 0, pci_end; + unsigned long nr, start, size; + + if (pci_resource_len(pdev, resno) == 0) + return 0; + nr = vma_pages(vma); + start = vma->vm_pgoff; + size = ((pci_resource_len(pdev, resno) - 1) >> PAGE_SHIFT) + 1; + if (mmap_api == PCI_MMAP_PROCFS) { + pci_resource_to_user(pdev, resno, &pdev->resource[resno], + &pci_start, &pci_end); + pci_start >>= PAGE_SHIFT; + } + if (start >= pci_start && start < pci_start + size && + start + nr <= pci_start + size) + return 1; + return 0; +} + +#endif diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 2321fdfefd7d..44ed30df08c3 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1022,29 +1022,6 @@ void pci_remove_legacy_files(struct pci_bus *b) #endif /* HAVE_PCI_LEGACY */ #if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) - -int pci_mmap_fits(struct pci_dev *pdev, int resno, struct vm_area_struct *vma, - enum pci_mmap_api mmap_api) -{ - unsigned long nr, start, size; - resource_size_t pci_start = 0, pci_end; - - if (pci_resource_len(pdev, resno) == 0) - return 0; - nr = vma_pages(vma); - start = vma->vm_pgoff; - size = ((pci_resource_len(pdev, resno) - 1) >> PAGE_SHIFT) + 1; - if (mmap_api == PCI_MMAP_PROCFS) { - pci_resource_to_user(pdev, resno, &pdev->resource[resno], - &pci_start, &pci_end); - pci_start >>= PAGE_SHIFT; - } - if (start >= pci_start && start < pci_start + size && - start + nr <= pci_start + size) - return 1; - return 0; -} - /** * pci_mmap_resource - map a PCI resource into user memory space * @kobj: kobject for mapping @@ -1660,7 +1637,7 @@ static const struct attribute_group pcie_dev_attr_group = { .is_visible = pcie_dev_attrs_are_visible, }; -static const struct attribute_group *pci_dev_attr_groups[] = { +const struct attribute_group *pci_dev_attr_groups[] = { &pci_dev_attr_group, &pci_dev_hp_attr_group, #ifdef CONFIG_PCI_IOV @@ -1677,7 +1654,3 @@ static const struct attribute_group *pci_dev_attr_groups[] = { #endif NULL, }; - -const struct device_type pci_dev_type = { - .groups = pci_dev_attr_groups, -}; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 2336a8d1edab..74d7f66b64a8 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -31,8 +31,6 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); /* Functions internal to the PCI core code */ -int pci_create_sysfs_dev_files(struct pci_dev *pdev); -void pci_remove_sysfs_dev_files(struct pci_dev *pdev); void pci_cleanup_rom(struct pci_dev *dev); #ifdef CONFIG_DMI extern const struct attribute_group pci_dev_smbios_attr_group; @@ -152,7 +150,7 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; } /* Functions for PCI Hotplug drivers to use */ int pci_hp_add_bridge(struct pci_dev *dev); -#ifdef HAVE_PCI_LEGACY +#if defined(CONFIG_SYSFS) && defined(HAVE_PCI_LEGACY) void pci_create_legacy_files(struct pci_bus *bus); void pci_remove_legacy_files(struct pci_bus *bus); #else @@ -185,10 +183,22 @@ static inline int pci_no_d1d2(struct pci_dev *dev) return (dev->no_d1d2 || parent_dstates); } + +#ifdef CONFIG_SYSFS +int pci_create_sysfs_dev_files(struct pci_dev *pdev); +void pci_remove_sysfs_dev_files(struct pci_dev *pdev); extern const struct attribute_group *pci_dev_groups[]; +extern const struct attribute_group *pci_dev_attr_groups[]; extern const struct attribute_group *pcibus_groups[]; -extern const struct device_type pci_dev_type; extern const struct attribute_group *pci_bus_groups[]; +#else +static inline int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; } +static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { } +#define pci_dev_groups NULL +#define pci_dev_attr_groups NULL +#define pcibus_groups NULL +#define pci_bus_groups NULL +#endif extern unsigned long pci_hotplug_io_size; extern unsigned long pci_hotplug_mmio_size; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b7335be56008..c1496e683e70 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2357,6 +2357,10 @@ static void pci_release_dev(struct device *dev) kfree(pci_dev); } +static const struct device_type pci_dev_type = { + .groups = pci_dev_attr_groups, +}; + struct pci_dev *pci_alloc_dev(struct pci_bus *bus) { struct pci_dev *dev; -- cgit v1.2.3 From 2ea548a3c01de873b54390dc07ef0a72d9b4a5f3 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Mon, 30 Oct 2023 13:33:13 +0100 Subject: PCI: Remove obsolete pci_cleanup_rom() declaration Commit d9c8bea179a6 ("PCI: Remove unused IORESOURCE_ROM_COPY and IORESOURCE_ROM_BIOS_COPY") removed pci_cleanup_rom(), but retained its declaration in pci.h. Remove it. Link: https://lore.kernel.org/r/fc30de5276e21d5a3ebcb7e58a8b43e399f7e6e6.1698668982.git.lukas@wunner.de Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Alistair Francis --- drivers/pci/pci.h | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 74d7f66b64a8..9e32227008d5 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -31,7 +31,6 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); /* Functions internal to the PCI core code */ -void pci_cleanup_rom(struct pci_dev *dev); #ifdef CONFIG_DMI extern const struct attribute_group pci_dev_smbios_attr_group; #endif -- cgit v1.2.3 From f6c73999837820f98519bf0146df44e58f20f89c Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Thu, 22 Feb 2024 13:46:06 +0200 Subject: PCI/sysfs: Demacrofy pci_dev_resource_resize_attr(n) functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_dev_resource_resize_attr(n) macro is invoked for six resources, creating a large footprint function for each resource. Rework the macro to only create a function that calls a helper function so the compiler can decide if it warrants to inline the function or not. With x86_64 defconfig, this saves roughly 2.5kB: $ scripts/bloat-o-meter drivers/pci/pci-sysfs.o{.old,.new} add/remove: 1/0 grow/shrink: 0/6 up/down: 512/-2934 (-2422) Function old new delta __resource_resize_store - 512 +512 resource5_resize_store 503 14 -489 resource4_resize_store 503 14 -489 resource3_resize_store 503 14 -489 resource2_resize_store 503 14 -489 resource1_resize_store 503 14 -489 resource0_resize_store 500 11 -489 Total: Before=13399, After=10977, chg -18.08% (The compiler seemingly chose to still inline __resource_resize_show() which is fine, those functions are not very complex/large.) Link: https://lore.kernel.org/r/20240222114607.1837-1-ilpo.jarvinen@linux.intel.com Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 138 ++++++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 64 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 44ed30df08c3..40cfa716392f 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1387,79 +1387,89 @@ static const struct attribute_group pci_dev_reset_attr_group = { .is_visible = pci_dev_reset_attr_is_visible, }; +static ssize_t __resource_resize_show(struct device *dev, int n, char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + ssize_t ret; + + pci_config_pm_runtime_get(pdev); + + ret = sysfs_emit(buf, "%016llx\n", + (u64)pci_rebar_get_possible_sizes(pdev, n)); + + pci_config_pm_runtime_put(pdev); + + return ret; +} + +static ssize_t __resource_resize_store(struct device *dev, int n, + const char *buf, size_t count) +{ + struct pci_dev *pdev = to_pci_dev(dev); + unsigned long size, flags; + int ret, i; + u16 cmd; + + if (kstrtoul(buf, 0, &size) < 0) + return -EINVAL; + + device_lock(dev); + if (dev->driver) { + ret = -EBUSY; + goto unlock; + } + + pci_config_pm_runtime_get(pdev); + + if ((pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) { + ret = aperture_remove_conflicting_pci_devices(pdev, + "resourceN_resize"); + if (ret) + goto pm_put; + } + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + pci_write_config_word(pdev, PCI_COMMAND, + cmd & ~PCI_COMMAND_MEMORY); + + flags = pci_resource_flags(pdev, n); + + pci_remove_resource_files(pdev); + + for (i = 0; i < PCI_STD_NUM_BARS; i++) { + if (pci_resource_len(pdev, i) && + pci_resource_flags(pdev, i) == flags) + pci_release_resource(pdev, i); + } + + ret = pci_resize_resource(pdev, n, size); + + pci_assign_unassigned_bus_resources(pdev->bus); + + if (pci_create_resource_files(pdev)) + pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n"); + + pci_write_config_word(pdev, PCI_COMMAND, cmd); +pm_put: + pci_config_pm_runtime_put(pdev); +unlock: + device_unlock(dev); + + return ret ? ret : count; +} + #define pci_dev_resource_resize_attr(n) \ static ssize_t resource##n##_resize_show(struct device *dev, \ struct device_attribute *attr, \ - char * buf) \ + char *buf) \ { \ - struct pci_dev *pdev = to_pci_dev(dev); \ - ssize_t ret; \ - \ - pci_config_pm_runtime_get(pdev); \ - \ - ret = sysfs_emit(buf, "%016llx\n", \ - (u64)pci_rebar_get_possible_sizes(pdev, n)); \ - \ - pci_config_pm_runtime_put(pdev); \ - \ - return ret; \ + return __resource_resize_show(dev, n, buf); \ } \ - \ static ssize_t resource##n##_resize_store(struct device *dev, \ struct device_attribute *attr,\ const char *buf, size_t count)\ { \ - struct pci_dev *pdev = to_pci_dev(dev); \ - unsigned long size, flags; \ - int ret, i; \ - u16 cmd; \ - \ - if (kstrtoul(buf, 0, &size) < 0) \ - return -EINVAL; \ - \ - device_lock(dev); \ - if (dev->driver) { \ - ret = -EBUSY; \ - goto unlock; \ - } \ - \ - pci_config_pm_runtime_get(pdev); \ - \ - if ((pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA) { \ - ret = aperture_remove_conflicting_pci_devices(pdev, \ - "resourceN_resize"); \ - if (ret) \ - goto pm_put; \ - } \ - \ - pci_read_config_word(pdev, PCI_COMMAND, &cmd); \ - pci_write_config_word(pdev, PCI_COMMAND, \ - cmd & ~PCI_COMMAND_MEMORY); \ - \ - flags = pci_resource_flags(pdev, n); \ - \ - pci_remove_resource_files(pdev); \ - \ - for (i = 0; i < PCI_STD_NUM_BARS; i++) { \ - if (pci_resource_len(pdev, i) && \ - pci_resource_flags(pdev, i) == flags) \ - pci_release_resource(pdev, i); \ - } \ - \ - ret = pci_resize_resource(pdev, n, size); \ - \ - pci_assign_unassigned_bus_resources(pdev->bus); \ - \ - if (pci_create_resource_files(pdev)) \ - pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n");\ - \ - pci_write_config_word(pdev, PCI_COMMAND, cmd); \ -pm_put: \ - pci_config_pm_runtime_put(pdev); \ -unlock: \ - device_unlock(dev); \ - \ - return ret ? ret : count; \ + return __resource_resize_store(dev, n, buf, count); \ } \ static DEVICE_ATTR_RW(resource##n##_resize) -- cgit v1.2.3 From fa84f4435a6202dd90248517f41e54bf3fb85bc5 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 23 Feb 2024 14:58:47 -0600 Subject: PCI/ASPM: Move pci_configure_ltr() to aspm.c The Latency Tolerance Reporting (LTR) mechanism supports the ASPM L1.2 state and is only configured when CONFIG_PCIEASPM is set. Move pci_configure_ltr() and pci_bridge_reconfigure_ltr() into aspm.c since they only build when CONFIG_PCIEASPM is set. No functional change intended. Suggested-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20240128233212.1139663-2-david.e.box@linux.intel.com [bhelgaas: commit log, split build change from function moves] Link: https://lore.kernel.org/r/20240223205851.114931-2-helgaas@kernel.org Signed-off-by: David E. Box Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 18 ------------ drivers/pci/pci.h | 5 +++- drivers/pci/pcie/aspm.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 61 ---------------------------------------- 4 files changed, 79 insertions(+), 80 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d8f11a078924..c783e0f1f2a9 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1626,24 +1626,6 @@ static int pci_save_pcie_state(struct pci_dev *dev) return 0; } -void pci_bridge_reconfigure_ltr(struct pci_dev *dev) -{ -#ifdef CONFIG_PCIEASPM - struct pci_dev *bridge; - u32 ctl; - - bridge = pci_upstream_bridge(dev); - if (bridge && bridge->ltr_path) { - pcie_capability_read_dword(bridge, PCI_EXP_DEVCTL2, &ctl); - if (!(ctl & PCI_EXP_DEVCTL2_LTR_EN)) { - pci_dbg(bridge, "re-enabling LTR\n"); - pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2, - PCI_EXP_DEVCTL2_LTR_EN); - } - } -#endif -} - static void pci_restore_pcie_state(struct pci_dev *dev) { int i = 0; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 2336a8d1edab..9aeba82facc4 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -97,7 +97,6 @@ void pci_msi_init(struct pci_dev *dev); void pci_msix_init(struct pci_dev *dev); bool pci_bridge_d3_possible(struct pci_dev *dev); void pci_bridge_d3_update(struct pci_dev *dev); -void pci_bridge_reconfigure_ltr(struct pci_dev *dev); int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type); static inline void pci_wakeup_event(struct pci_dev *dev) @@ -573,11 +572,15 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev); void pcie_aspm_exit_link_state(struct pci_dev *pdev); void pcie_aspm_pm_state_change(struct pci_dev *pdev); void pcie_aspm_powersave_config_link(struct pci_dev *pdev); +void pci_configure_ltr(struct pci_dev *pdev); +void pci_bridge_reconfigure_ltr(struct pci_dev *pdev); #else static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { } static inline void pcie_aspm_exit_link_state(struct pci_dev *pdev) { } static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev) { } static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { } +static inline void pci_configure_ltr(struct pci_dev *pdev) { } +static inline void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) { } #endif #ifdef CONFIG_PCIE_ECRC diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 5a0066ecc3c5..d1538f73f2f9 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -938,6 +938,81 @@ out: up_read(&pci_bus_sem); } +void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) +{ + struct pci_dev *bridge; + u32 ctl; + + bridge = pci_upstream_bridge(pdev); + if (bridge && bridge->ltr_path) { + pcie_capability_read_dword(bridge, PCI_EXP_DEVCTL2, &ctl); + if (!(ctl & PCI_EXP_DEVCTL2_LTR_EN)) { + pci_dbg(bridge, "re-enabling LTR\n"); + pcie_capability_set_word(bridge, PCI_EXP_DEVCTL2, + PCI_EXP_DEVCTL2_LTR_EN); + } + } +} + +void pci_configure_ltr(struct pci_dev *pdev) +{ + struct pci_host_bridge *host = pci_find_host_bridge(pdev->bus); + struct pci_dev *bridge; + u32 cap, ctl; + + if (!pci_is_pcie(pdev)) + return; + + /* Read L1 PM substate capabilities */ + pdev->l1ss = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS); + + pcie_capability_read_dword(pdev, PCI_EXP_DEVCAP2, &cap); + if (!(cap & PCI_EXP_DEVCAP2_LTR)) + return; + + pcie_capability_read_dword(pdev, PCI_EXP_DEVCTL2, &ctl); + if (ctl & PCI_EXP_DEVCTL2_LTR_EN) { + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) { + pdev->ltr_path = 1; + return; + } + + bridge = pci_upstream_bridge(pdev); + if (bridge && bridge->ltr_path) + pdev->ltr_path = 1; + + return; + } + + if (!host->native_ltr) + return; + + /* + * Software must not enable LTR in an Endpoint unless the Root + * Complex and all intermediate Switches indicate support for LTR. + * PCIe r4.0, sec 6.18. + */ + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) { + pcie_capability_set_word(pdev, PCI_EXP_DEVCTL2, + PCI_EXP_DEVCTL2_LTR_EN); + pdev->ltr_path = 1; + return; + } + + /* + * If we're configuring a hot-added device, LTR was likely + * disabled in the upstream bridge, so re-enable it before enabling + * it in the new device. + */ + bridge = pci_upstream_bridge(pdev); + if (bridge && bridge->ltr_path) { + pci_bridge_reconfigure_ltr(pdev); + pcie_capability_set_word(pdev, PCI_EXP_DEVCTL2, + PCI_EXP_DEVCTL2_LTR_EN); + pdev->ltr_path = 1; + } +} + /* Recheck latencies and update aspm_capable for links under the root */ static void pcie_update_aspm_capable(struct pcie_link_state *root) { diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b7335be56008..b809c0b0e0e5 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2209,67 +2209,6 @@ static void pci_configure_relaxed_ordering(struct pci_dev *dev) } } -static void pci_configure_ltr(struct pci_dev *dev) -{ -#ifdef CONFIG_PCIEASPM - struct pci_host_bridge *host = pci_find_host_bridge(dev->bus); - struct pci_dev *bridge; - u32 cap, ctl; - - if (!pci_is_pcie(dev)) - return; - - /* Read L1 PM substate capabilities */ - dev->l1ss = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_L1SS); - - pcie_capability_read_dword(dev, PCI_EXP_DEVCAP2, &cap); - if (!(cap & PCI_EXP_DEVCAP2_LTR)) - return; - - pcie_capability_read_dword(dev, PCI_EXP_DEVCTL2, &ctl); - if (ctl & PCI_EXP_DEVCTL2_LTR_EN) { - if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT) { - dev->ltr_path = 1; - return; - } - - bridge = pci_upstream_bridge(dev); - if (bridge && bridge->ltr_path) - dev->ltr_path = 1; - - return; - } - - if (!host->native_ltr) - return; - - /* - * Software must not enable LTR in an Endpoint unless the Root - * Complex and all intermediate Switches indicate support for LTR. - * PCIe r4.0, sec 6.18. - */ - if (pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT) { - pcie_capability_set_word(dev, PCI_EXP_DEVCTL2, - PCI_EXP_DEVCTL2_LTR_EN); - dev->ltr_path = 1; - return; - } - - /* - * If we're configuring a hot-added device, LTR was likely - * disabled in the upstream bridge, so re-enable it before enabling - * it in the new device. - */ - bridge = pci_upstream_bridge(dev); - if (bridge && bridge->ltr_path) { - pci_bridge_reconfigure_ltr(dev); - pcie_capability_set_word(dev, PCI_EXP_DEVCTL2, - PCI_EXP_DEVCTL2_LTR_EN); - dev->ltr_path = 1; - } -#endif -} - static void pci_configure_eetlp_prefix(struct pci_dev *dev) { #ifdef CONFIG_PCI_PASID -- cgit v1.2.3 From f3994bba8200b49e3eacbe5914cd13f228e0db37 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 23 Feb 2024 14:58:48 -0600 Subject: PCI/ASPM: Always build aspm.c Some ASPM-related tasks, such as save and restore of LTR and L1SS capabilities, still need to be performed when CONFIG_PCIEASPM is not enabled. To prepare for these changes, wrap the current code in aspm.c with an #ifdef and always build the file. Link: https://lore.kernel.org/r/20240128233212.1139663-2-david.e.box@linux.intel.com [bhelgaas: split build change from function moves] Link: https://lore.kernel.org/r/20240223205851.114931-3-helgaas@kernel.org Signed-off-by: David E. Box Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/Makefile | 2 +- drivers/pci/pcie/aspm.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 8de4ed5f98f1..6461aa93fe76 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -6,7 +6,7 @@ pcieportdrv-y := portdrv.o rcec.o obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o -obj-$(CONFIG_PCIEASPM) += aspm.o +obj-y += aspm.o obj-$(CONFIG_PCIEAER) += aer.o err.o obj-$(CONFIG_PCIEAER_INJECT) += aer_inject.o obj-$(CONFIG_PCIE_PME) += pme.o diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index d1538f73f2f9..d50c0f83430f 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -24,6 +24,8 @@ #include "../pci.h" +#ifdef CONFIG_PCIEASPM + #ifdef MODULE_PARAM_PREFIX #undef MODULE_PARAM_PREFIX #endif @@ -1517,3 +1519,5 @@ bool pcie_aspm_support_enabled(void) { return aspm_support_enabled; } + +#endif /* CONFIG_PCIEASPM */ -- cgit v1.2.3 From 1e11b5494c3dbb1e5fce7e95021c1698799c7288 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 23 Feb 2024 14:58:49 -0600 Subject: PCI/ASPM: Move pci_save_ltr_state() to aspm.c Even when CONFIG_PCIEASPM is not set, we save and restore the LTR Capability so that if ASPM L1.2 and LTR were configured by the platform, ASPM L1.2 will still work after suspend/resume, when that platform configuration may be lost. See dbbfadf23190 ("PCI/ASPM: Save LTR Capability for suspend/resume"). Since ASPM L1.2 depends on the LTR Capability, move the save/restore code to the part of aspm.c that is always compiled regardless of CONFIG_PCIEASPM. No functional change intended. Suggested-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20240128233212.1139663-5-david.e.box@linux.intel.com [bhelgaas: commit log, reorder to make this a pure move] Link: https://lore.kernel.org/r/20240223205851.114931-4-helgaas@kernel.org Signed-off-by: David E. Box Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 40 ---------------------------------------- drivers/pci/pci.h | 5 +++++ drivers/pci/pcie/aspm.c | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 40 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index c783e0f1f2a9..564e2cf2dde5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1689,46 +1689,6 @@ static void pci_restore_pcix_state(struct pci_dev *dev) pci_write_config_word(dev, pos + PCI_X_CMD, cap[i++]); } -static void pci_save_ltr_state(struct pci_dev *dev) -{ - int ltr; - struct pci_cap_saved_state *save_state; - u32 *cap; - - if (!pci_is_pcie(dev)) - return; - - ltr = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR); - if (!ltr) - return; - - save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_LTR); - if (!save_state) { - pci_err(dev, "no suspend buffer for LTR; ASPM issues possible after resume\n"); - return; - } - - /* Some broken devices only support dword access to LTR */ - cap = &save_state->cap.data[0]; - pci_read_config_dword(dev, ltr + PCI_LTR_MAX_SNOOP_LAT, cap); -} - -static void pci_restore_ltr_state(struct pci_dev *dev) -{ - struct pci_cap_saved_state *save_state; - int ltr; - u32 *cap; - - save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_LTR); - ltr = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR); - if (!save_state || !ltr) - return; - - /* Some broken devices only support dword access to LTR */ - cap = &save_state->cap.data[0]; - pci_write_config_dword(dev, ltr + PCI_LTR_MAX_SNOOP_LAT, *cap); -} - /** * pci_save_state - save the PCI configuration space of a device before * suspending diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9aeba82facc4..ad3add45345c 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -567,6 +567,11 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, bool pcie_wait_for_link(struct pci_dev *pdev, bool active); int pcie_retrain_link(struct pci_dev *pdev, bool use_lt); + +/* ASPM-related functionality we need even without CONFIG_PCIEASPM */ +void pci_save_ltr_state(struct pci_dev *dev); +void pci_restore_ltr_state(struct pci_dev *dev); + #ifdef CONFIG_PCIEASPM void pcie_aspm_init_link_state(struct pci_dev *pdev); void pcie_aspm_exit_link_state(struct pci_dev *pdev); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index d50c0f83430f..21731b232fb8 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -24,6 +24,46 @@ #include "../pci.h" +void pci_save_ltr_state(struct pci_dev *dev) +{ + int ltr; + struct pci_cap_saved_state *save_state; + u32 *cap; + + if (!pci_is_pcie(dev)) + return; + + ltr = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR); + if (!ltr) + return; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_LTR); + if (!save_state) { + pci_err(dev, "no suspend buffer for LTR; ASPM issues possible after resume\n"); + return; + } + + /* Some broken devices only support dword access to LTR */ + cap = &save_state->cap.data[0]; + pci_read_config_dword(dev, ltr + PCI_LTR_MAX_SNOOP_LAT, cap); +} + +void pci_restore_ltr_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state; + int ltr; + u32 *cap; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_LTR); + ltr = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_LTR); + if (!save_state || !ltr) + return; + + /* Some broken devices only support dword access to LTR */ + cap = &save_state->cap.data[0]; + pci_write_config_dword(dev, ltr + PCI_LTR_MAX_SNOOP_LAT, *cap); +} + #ifdef CONFIG_PCIEASPM #ifdef MODULE_PARAM_PREFIX -- cgit v1.2.3 From 002bf2fbc00e5c4b95fb167287e2ae7d1973281e Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Mon, 12 Feb 2024 13:01:35 +0100 Subject: PCI/AER: Block runtime suspend when handling errors PM runtime can be done simultaneously with AER error handling. Avoid that by using pm_runtime_get_sync() before and pm_runtime_put() after reset in pcie_do_recovery() for all recovering devices. pm_runtime_get_sync() will increase dev->power.usage_count counter to prevent any possible future request to runtime suspend a device. It will also resume a device, if it was previously in D3hot state. I tested with igc device by doing simultaneous aer_inject and rpm suspend/resume via /sys/bus/pci/devices/PCI_ID/power/control and can reproduce: igc 0000:02:00.0: not ready 65535ms after bus reset; giving up pcieport 0000:00:1c.2: AER: Root Port link has been reset (-25) pcieport 0000:00:1c.2: AER: subordinate device reset failed pcieport 0000:00:1c.2: AER: device recovery failed igc 0000:02:00.0: Unable to change power state from D3hot to D0, device inaccessible The problem disappears when this patch is applied. Link: https://lore.kernel.org/r/20240212120135.146068-1-stanislaw.gruszka@linux.intel.com Signed-off-by: Stanislaw Gruszka Signed-off-by: Bjorn Helgaas Reviewed-by: Kuppuswamy Sathyanarayanan Acked-by: Rafael J. Wysocki Cc: --- drivers/pci/pcie/err.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index 59c90d04a609..705893b5f7b0 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -13,6 +13,7 @@ #define dev_fmt(fmt) "AER: " fmt #include +#include #include #include #include @@ -85,6 +86,18 @@ static int report_error_detected(struct pci_dev *dev, return 0; } +static int pci_pm_runtime_get_sync(struct pci_dev *pdev, void *data) +{ + pm_runtime_get_sync(&pdev->dev); + return 0; +} + +static int pci_pm_runtime_put(struct pci_dev *pdev, void *data) +{ + pm_runtime_put(&pdev->dev); + return 0; +} + static int report_frozen_detected(struct pci_dev *dev, void *data) { return report_error_detected(dev, pci_channel_io_frozen, data); @@ -207,6 +220,8 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, else bridge = pci_upstream_bridge(dev); + pci_walk_bridge(bridge, pci_pm_runtime_get_sync, NULL); + pci_dbg(bridge, "broadcast error_detected message\n"); if (state == pci_channel_io_frozen) { pci_walk_bridge(bridge, report_frozen_detected, &status); @@ -251,10 +266,15 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, pcie_clear_device_status(dev); pci_aer_clear_nonfatal_status(dev); } + + pci_walk_bridge(bridge, pci_pm_runtime_put, NULL); + pci_info(bridge, "device recovery successful\n"); return status; failed: + pci_walk_bridge(bridge, pci_pm_runtime_put, NULL); + pci_uevent_ers(bridge, PCI_ERS_RESULT_DISCONNECT); /* TODO: Should kernel panic here? */ -- cgit v1.2.3 From d1997c98781459f7b6d0bf1858f538f48454a97b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Wed, 6 Mar 2024 10:56:49 +0100 Subject: PCI: qcom: Disable ASPM L0s for sc8280xp, sa8540p and sa8295p Commit 9f4f3dfad8cf ("PCI: qcom: Enable ASPM for platforms supporting 1.9.0 ops") started enabling ASPM unconditionally when the hardware claims to support it. This triggers Correctable Errors for some PCIe devices on machines like the Lenovo ThinkPad X13s when L0s is enabled, which could indicate an incomplete driver ASPM implementation or that the hardware does in fact not support L0s. This has now been confirmed by Qualcomm to be the case for sc8280xp and its derivate platforms (e.g. sa8540p and sa8295p). Specifically, the PHY configuration used on these platforms is not correctly tuned for L0s and there is currently no updated configuration available. Add a new flag to the driver configuration data and use it to disable ASPM L0s on sc8280xp, sa8540p and sa8295p for now. Note that only the 1.9.0 ops enable ASPM currently. Link: https://lore.kernel.org/r/20240306095651.4551-4-johan+linaro@kernel.org Fixes: 9f4f3dfad8cf ("PCI: qcom: Enable ASPM for platforms supporting 1.9.0 ops") Signed-off-by: Johan Hovold Signed-off-by: Lorenzo Pieralisi Reviewed-by: Manivannan Sadhasivam Cc: stable@vger.kernel.org # 6.7 --- drivers/pci/controller/dwc/pcie-qcom.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 10f2d0bb86be..8554482debe8 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -229,6 +229,7 @@ struct qcom_pcie_ops { struct qcom_pcie_cfg { const struct qcom_pcie_ops *ops; + bool no_l0s; }; struct qcom_pcie { @@ -272,6 +273,26 @@ static int qcom_pcie_start_link(struct dw_pcie *pci) return 0; } +static void qcom_pcie_clear_aspm_l0s(struct dw_pcie *pci) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pci); + u16 offset; + u32 val; + + if (!pcie->cfg->no_l0s) + return; + + offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + + dw_pcie_dbi_ro_wr_en(pci); + + val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP); + val &= ~PCI_EXP_LNKCAP_ASPM_L0S; + writel(val, pci->dbi_base + offset + PCI_EXP_LNKCAP); + + dw_pcie_dbi_ro_wr_dis(pci); +} + static void qcom_pcie_clear_hpc(struct dw_pcie *pci) { u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); @@ -961,6 +982,7 @@ err_disable_regulators: static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie) { + qcom_pcie_clear_aspm_l0s(pcie->pci); qcom_pcie_clear_hpc(pcie->pci); return 0; @@ -1358,6 +1380,11 @@ static const struct qcom_pcie_cfg cfg_2_9_0 = { .ops = &ops_2_9_0, }; +static const struct qcom_pcie_cfg cfg_sc8280xp = { + .ops = &ops_1_9_0, + .no_l0s = true, +}; + static const struct dw_pcie_ops dw_pcie_ops = { .link_up = qcom_pcie_link_up, .start_link = qcom_pcie_start_link, @@ -1629,11 +1656,11 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-ipq8074-gen3", .data = &cfg_2_9_0 }, { .compatible = "qcom,pcie-msm8996", .data = &cfg_2_3_2 }, { .compatible = "qcom,pcie-qcs404", .data = &cfg_2_4_0 }, - { .compatible = "qcom,pcie-sa8540p", .data = &cfg_1_9_0 }, + { .compatible = "qcom,pcie-sa8540p", .data = &cfg_sc8280xp }, { .compatible = "qcom,pcie-sa8775p", .data = &cfg_1_9_0}, { .compatible = "qcom,pcie-sc7280", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sc8180x", .data = &cfg_1_9_0 }, - { .compatible = "qcom,pcie-sc8280xp", .data = &cfg_1_9_0 }, + { .compatible = "qcom,pcie-sc8280xp", .data = &cfg_sc8280xp }, { .compatible = "qcom,pcie-sdm845", .data = &cfg_2_7_0 }, { .compatible = "qcom,pcie-sdx55", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8150", .data = &cfg_1_9_0 }, -- cgit v1.2.3 From a37e12bcab22efa05802f87baa0692365ae0ab4d Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Tue, 6 Feb 2024 15:57:14 +0200 Subject: PCI/AER: Use explicit register size for PCI_ERR_CAP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use u32 for PCIe AER Capability register variable and name it "aercc" (Advanced Error Capabilities and Control register, PCIe r6.1 sec 7.8.4.7) instead of "temp". Link: https://lore.kernel.org/r/20240206135717.8565-2-ilpo.jarvinen@linux.intel.com Signed-off-by: Ilpo Järvinen [bhelgaas: make subject more specific and match similar previous patches] Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/aer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index 05fc30bb5134..e31e6a9a7773 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1210,7 +1210,7 @@ int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) { int type = pci_pcie_type(dev); int aer = dev->aer_cap; - int temp; + u32 aercc; /* Must reset in this function */ info->status = 0; @@ -1241,8 +1241,8 @@ int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) return 0; /* Get First Error Pointer */ - pci_read_config_dword(dev, aer + PCI_ERR_CAP, &temp); - info->first_error = PCI_ERR_CAP_FEP(temp); + pci_read_config_dword(dev, aer + PCI_ERR_CAP, &aercc); + info->first_error = PCI_ERR_CAP_FEP(aercc); if (info->status & AER_LOG_TLP_MASKS) { info->tlp_header_valid = 1; -- cgit v1.2.3 From 0a5a46a6a61be7b63c12c18495d427f91f3662a9 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Tue, 6 Feb 2024 15:57:15 +0200 Subject: PCI/AER: Generalize TLP Header Log reading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both AER and DPC RP PIO provide TLP Header Log registers (PCIe r6.1 secs 7.8.4 & 7.9.14) to convey error diagnostics but the struct is named after AER as the struct aer_header_log_regs. Also, not all places that handle TLP Header Log use the struct and the struct members are named individually. Generalize the struct name and members, and use it consistently where TLP Header Log is being handled so that a pcie_read_tlp_log() helper can be easily added. Link: https://lore.kernel.org/r/20240206135717.8565-3-ilpo.jarvinen@linux.intel.com Signed-off-by: Ilpo Järvinen [bhelgaas: drop ixgbe changes for now, tidy whitespace] Signed-off-by: Bjorn Helgaas --- drivers/firmware/efi/cper.c | 4 ++-- drivers/pci/pci.c | 28 ++++++++++++++++++++++++++++ drivers/pci/pci.h | 2 +- drivers/pci/pcie/aer.c | 14 +++----------- drivers/pci/pcie/dpc.c | 14 ++++---------- include/linux/aer.h | 11 +++++------ include/ras/ras_event.h | 10 +++++----- 7 files changed, 48 insertions(+), 35 deletions(-) (limited to 'drivers') diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 35c37f667781..d3f98161171e 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -445,8 +445,8 @@ static void cper_print_pcie(const char *pfx, const struct cper_sec_pcie *pcie, printk("%saer_uncor_severity: 0x%08x\n", pfx, aer->uncor_severity); printk("%sTLP Header: %08x %08x %08x %08x\n", pfx, - aer->header_log.dw0, aer->header_log.dw1, - aer->header_log.dw2, aer->header_log.dw3); + aer->header_log.dw[0], aer->header_log.dw[1], + aer->header_log.dw[2], aer->header_log.dw[3]); } } diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index d8f11a078924..a0af94cfcf7d 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1067,6 +1067,34 @@ disable_acs_redir: pci_disable_acs_redir(dev); } +/** + * pcie_read_tlp_log - read TLP Header Log + * @dev: PCIe device + * @where: PCI Config offset of TLP Header Log + * @tlp_log: TLP Log structure to fill + * + * Fill @tlp_log from TLP Header Log registers, e.g., AER or DPC. + * + * Return: 0 on success and filled TLP Log structure, <0 on error. + */ +int pcie_read_tlp_log(struct pci_dev *dev, int where, + struct pcie_tlp_log *tlp_log) +{ + int i, ret; + + memset(tlp_log, 0, sizeof(*tlp_log)); + + for (i = 0; i < 4; i++) { + ret = pci_read_config_dword(dev, where + i * 4, + &tlp_log->dw[i]); + if (ret) + return pcibios_err_to_errno(ret); + } + + return 0; +} +EXPORT_SYMBOL_GPL(pcie_read_tlp_log); + /** * pci_restore_bars - restore a device's BAR values (e.g. after wake-up) * @dev: PCI device to have its BARs restored diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 2336a8d1edab..a59ba6fde2a0 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -409,7 +409,7 @@ struct aer_err_info { unsigned int status; /* COR/UNCOR Error Status */ unsigned int mask; /* COR/UNCOR Error Mask */ - struct aer_header_log_regs tlp; /* TLP Header */ + struct pcie_tlp_log tlp; /* TLP Header */ }; int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info); diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index e31e6a9a7773..ac6293c24976 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -664,11 +664,10 @@ static void pci_rootport_aer_stats_incr(struct pci_dev *pdev, } } -static void __print_tlp_header(struct pci_dev *dev, - struct aer_header_log_regs *t) +static void __print_tlp_header(struct pci_dev *dev, struct pcie_tlp_log *t) { pci_err(dev, " TLP Header: %08x %08x %08x %08x\n", - t->dw0, t->dw1, t->dw2, t->dw3); + t->dw[0], t->dw[1], t->dw[2], t->dw[3]); } static void __aer_print_error(struct pci_dev *dev, @@ -1246,14 +1245,7 @@ int aer_get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) if (info->status & AER_LOG_TLP_MASKS) { info->tlp_header_valid = 1; - pci_read_config_dword(dev, - aer + PCI_ERR_HEADER_LOG, &info->tlp.dw0); - pci_read_config_dword(dev, - aer + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); - pci_read_config_dword(dev, - aer + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); - pci_read_config_dword(dev, - aer + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); + pcie_read_tlp_log(dev, aer + PCI_ERR_HEADER_LOG, &info->tlp); } } diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index 94111e438241..c197bc7f7f2c 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -190,7 +190,8 @@ out: static void dpc_process_rp_pio_error(struct pci_dev *pdev) { u16 cap = pdev->dpc_cap, dpc_status, first_error; - u32 status, mask, sev, syserr, exc, dw0, dw1, dw2, dw3, log, prefix; + u32 status, mask, sev, syserr, exc, log, prefix; + struct pcie_tlp_log tlp_log; int i; pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, &status); @@ -216,16 +217,9 @@ static void dpc_process_rp_pio_error(struct pci_dev *pdev) if (pdev->dpc_rp_log_size < 4) goto clear_status; - pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG, - &dw0); - pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 4, - &dw1); - pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 8, - &dw2); - pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 12, - &dw3); + pcie_read_tlp_log(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG, &tlp_log); pci_err(pdev, "TLP Header: %#010x %#010x %#010x %#010x\n", - dw0, dw1, dw2, dw3); + tlp_log.dw[0], tlp_log.dw[1], tlp_log.dw[2], tlp_log.dw[3]); if (pdev->dpc_rp_log_size < 5) goto clear_status; diff --git a/include/linux/aer.h b/include/linux/aer.h index ae0fae70d4bd..4b97f38f3fcf 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -18,11 +18,8 @@ struct pci_dev; -struct aer_header_log_regs { - u32 dw0; - u32 dw1; - u32 dw2; - u32 dw3; +struct pcie_tlp_log { + u32 dw[4]; }; struct aer_capability_regs { @@ -33,13 +30,15 @@ struct aer_capability_regs { u32 cor_status; u32 cor_mask; u32 cap_control; - struct aer_header_log_regs header_log; + struct pcie_tlp_log header_log; u32 root_command; u32 root_status; u16 cor_err_source; u16 uncor_err_source; }; +int pcie_read_tlp_log(struct pci_dev *dev, int where, struct pcie_tlp_log *log); + #if defined(CONFIG_PCIEAER) int pci_aer_clear_nonfatal_status(struct pci_dev *dev); int pcie_aer_is_native(struct pci_dev *dev); diff --git a/include/ras/ras_event.h b/include/ras/ras_event.h index cbd3ddd7c33d..c011ea236e9b 100644 --- a/include/ras/ras_event.h +++ b/include/ras/ras_event.h @@ -300,7 +300,7 @@ TRACE_EVENT(aer_event, const u32 status, const u8 severity, const u8 tlp_header_valid, - struct aer_header_log_regs *tlp), + struct pcie_tlp_log *tlp), TP_ARGS(dev_name, status, severity, tlp_header_valid, tlp), @@ -318,10 +318,10 @@ TRACE_EVENT(aer_event, __entry->severity = severity; __entry->tlp_header_valid = tlp_header_valid; if (tlp_header_valid) { - __entry->tlp_header[0] = tlp->dw0; - __entry->tlp_header[1] = tlp->dw1; - __entry->tlp_header[2] = tlp->dw2; - __entry->tlp_header[3] = tlp->dw3; + __entry->tlp_header[0] = tlp->dw[0]; + __entry->tlp_header[1] = tlp->dw[1]; + __entry->tlp_header[2] = tlp->dw[2]; + __entry->tlp_header[3] = tlp->dw[3]; } ), -- cgit v1.2.3 From bf79e33cdd89db498e00a6131e937259de5f2705 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 7 Mar 2024 16:35:15 +0530 Subject: PCI: qcom: Enable BDF to SID translation properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Qcom SoCs making use of ARM SMMU require BDF to SID translation table in the driver to properly map the SID for the PCIe devices based on their BDF identifier. This is currently achieved with the help of qcom_pcie_config_sid_1_9_0() function for SoCs supporting the 1_9_0 config. But With newer Qcom SoCs starting from SM8450, BDF to SID translation is set to bypass mode by default in hardware. Due to this, the translation table that is set in the qcom_pcie_config_sid_1_9_0() is essentially unused and the default SID is used for all endpoints in SoCs starting from SM8450. This is a security concern and also warrants swapping the DeviceID in DT while using the GIC ITS to handle MSIs from endpoints. The swapping is currently done like below in DT when using GIC ITS: /* * MSIs for BDF (1:0.0) only works with Device ID 0x5980. * Hence, the IDs are swapped. */ msi-map = <0x0 &gic_its 0x5981 0x1>, <0x100 &gic_its 0x5980 0x1>; Here, swapping of the DeviceIDs ensure that the endpoint with BDF (1:0.0) gets the DeviceID 0x5980 which is associated with the default SID as per the iommu mapping in DT. So MSIs were delivered with IDs swapped so far. But this also means the Root Port (0:0.0) won't receive any MSIs (for PME, AER etc...) So let's fix these issues by clearing the BDF to SID bypass mode for all SoCs making use of the 1_9_0 config. This allows the PCIe devices to use the correct SID, thus avoiding the DeviceID swapping hack in DT and also achieving the isolation between devices. Fixes: 4c9398822106 ("PCI: qcom: Add support for configuring BDF to SID mapping for SM8250") Link: https://lore.kernel.org/linux-pci/20240307-pci-bdf-sid-fix-v1-1-9423a7e2d63c@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Cc: stable@vger.kernel.org # 5.11 --- drivers/pci/controller/dwc/pcie-qcom.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 8554482debe8..02bfe415c7ac 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -53,6 +53,7 @@ #define PARF_SLV_ADDR_SPACE_SIZE 0x358 #define PARF_DEVICE_TYPE 0x1000 #define PARF_BDF_TO_SID_TABLE_N 0x2000 +#define PARF_BDF_TO_SID_CFG 0x2c00 /* ELBI registers */ #define ELBI_SYS_CTRL 0x04 @@ -120,6 +121,9 @@ /* PARF_DEVICE_TYPE register fields */ #define DEVICE_TYPE_RC 0x4 +/* PARF_BDF_TO_SID_CFG fields */ +#define BDF_TO_SID_BYPASS BIT(0) + /* ELBI_SYS_CTRL register fields */ #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) @@ -1030,11 +1034,17 @@ static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie) u8 qcom_pcie_crc8_table[CRC8_TABLE_SIZE]; int i, nr_map, size = 0; u32 smmu_sid_base; + u32 val; of_get_property(dev->of_node, "iommu-map", &size); if (!size) return 0; + /* Enable BDF to SID translation by disabling bypass mode (default) */ + val = readl(pcie->parf + PARF_BDF_TO_SID_CFG); + val &= ~BDF_TO_SID_BYPASS; + writel(val, pcie->parf + PARF_BDF_TO_SID_CFG); + map = kzalloc(size, GFP_KERNEL); if (!map) return -ENOMEM; -- cgit v1.2.3 From 6d0c39324c5fd8a788a000ab9cead1dbb2fa49a8 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 1 Mar 2024 18:59:02 +0200 Subject: PCI: qcom: Add X1E80100 PCIe support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the compatible and the driver data for X1E80100 PCIe controller. There are 5 controller instances found on this platform, out of which 2 are Gen3 with speeds of up to 8.0GT/s, while the other 3 are Gen4 with speeds of up to 16GT/s. The version of the controller is 1.38.0 for all instances, but they are compatible with 1.9.0 config. The max link width is x8 for one controller, x4 for two of others and x2 for the two left. [kwilczynski: commit log] Link: https://lore.kernel.org/linux-pci/20240301-x1e80100-pci-v4-2-7ab7e281d647@linaro.org Reviewed-by: Manivannan Sadhasivam Signed-off-by: Abel Vesa Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pcie-qcom.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 02bfe415c7ac..8ef33c86f244 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1679,6 +1679,7 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-sm8450-pcie0", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8450-pcie1", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8550", .data = &cfg_1_9_0 }, + { .compatible = "qcom,pcie-x1e80100", .data = &cfg_1_9_0 }, { } }; -- cgit v1.2.3 From 039741a8d7c9a01c1bc84a5ac5aa770a5e138a30 Mon Sep 17 00:00:00 2001 From: Jonathan Bell Date: Sat, 17 Feb 2024 14:37:22 +0100 Subject: PCI: brcmstb: Fix broken brcm_pcie_mdio_write() polling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MDIO_WT_DONE() macro tests bit 31, which is always 0 (== done) as readw_poll_timeout_atomic() does a 16-bit read. Replace with the readl variant. [kwilczynski: commit log] Fixes: ca5dcc76314d ("PCI: brcmstb: Replace status loops with read_poll_timeout_atomic()") Link: https://lore.kernel.org/linux-pci/20240217133722.14391-1-wahrenst@gmx.net Signed-off-by: Jonathan Bell Signed-off-by: Stefan Wahren Signed-off-by: Krzysztof Wilczyński Acked-by: Florian Fainelli --- drivers/pci/controller/pcie-brcmstb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 5b0730c3891b..c08683febdd4 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -336,7 +336,7 @@ static int brcm_pcie_mdio_write(void __iomem *base, u8 port, readl(base + PCIE_RC_DL_MDIO_ADDR); writel(MDIO_DATA_DONE_MASK | wrdata, base + PCIE_RC_DL_MDIO_WR_DATA); - err = readw_poll_timeout_atomic(base + PCIE_RC_DL_MDIO_WR_DATA, data, + err = readl_poll_timeout_atomic(base + PCIE_RC_DL_MDIO_WR_DATA, data, MDIO_WT_DONE(data), 10, 100); return err; } -- cgit v1.2.3 From f3a296405b6e65fe478144c2f85602dc5668700c Mon Sep 17 00:00:00 2001 From: Ajay Agarwal Date: Wed, 21 Feb 2024 21:08:40 +0530 Subject: PCI: dwc: Strengthen the MSI address allocation logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There can be platforms that do not use/have 32-bit DMA addresses. The current implementation of 32-bit IOVA allocation can fail for such platforms, eventually leading to the probe failure. Try to allocate a 32-bit msi_data. If this allocation fails, attempt a 64-bit address allocation. Please note that if the 64-bit MSI address is allocated, then the EPs supporting 32-bit MSI address only will not work. Link: https://lore.kernel.org/linux-pci/20240221153840.1789979-1-ajayagarwal@google.com Tested-by: Will McVicker Signed-off-by: Ajay Agarwal Signed-off-by: Krzysztof Wilczyński Reviewed-by: Manivannan Sadhasivam Reviewed-by: Serge Semin Reviewed-by: Will McVicker --- drivers/pci/controller/dwc/pcie-designware-host.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index d5fc31f8345f..d15a5c2d5b48 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -328,7 +328,7 @@ static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct device *dev = pci->dev; struct platform_device *pdev = to_platform_device(dev); - u64 *msi_vaddr; + u64 *msi_vaddr = NULL; int ret; u32 ctrl, num_ctrls; @@ -379,15 +379,20 @@ static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) * memory. */ ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); - if (ret) - dev_warn(dev, "Failed to set DMA mask to 32-bit. Devices with only 32-bit MSI support may not work properly\n"); + if (!ret) + msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data, + GFP_KERNEL); - msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data, - GFP_KERNEL); if (!msi_vaddr) { - dev_err(dev, "Failed to alloc and map MSI data\n"); - dw_pcie_free_msi(pp); - return -ENOMEM; + dev_warn(dev, "Failed to allocate 32-bit MSI address\n"); + dma_set_coherent_mask(dev, DMA_BIT_MASK(64)); + msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data, + GFP_KERNEL); + if (!msi_vaddr) { + dev_err(dev, "Failed to allocate MSI address\n"); + dw_pcie_free_msi(pp); + return -ENOMEM; + } } return 0; -- cgit v1.2.3 From 667a006d73fb7320fc6f414b6fe11a998fcf0c28 Mon Sep 17 00:00:00 2001 From: Jasko-EXT Wojciech Date: Sat, 2 Dec 2023 14:20:15 +0530 Subject: PCI: cadence: Clear the ARI Capability Next Function Number of the last function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next Function Number field in ARI Capability Register for last function must be zero by default as per the PCIe specification, indicating there is no next higher number function but that's not happening in our case, so this patch clears the Next Function Number field for last function used. [kwilczynski: white spaces update for one define] Link: https://lore.kernel.org/linux-pci/20231202085015.3048516-1-s-vadapalli@ti.com Signed-off-by: Jasko-EXT Wojciech Signed-off-by: Achal Verma Signed-off-by: Siddharth Vadapalli Signed-off-by: Krzysztof Wilczyński Reviewed-by: Vignesh Raghavendra --- drivers/pci/controller/cadence/pcie-cadence-ep.c | 14 +++++++++++++- drivers/pci/controller/cadence/pcie-cadence.h | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/cadence/pcie-cadence-ep.c b/drivers/pci/controller/cadence/pcie-cadence-ep.c index 2d0a8d78bffb..81c50dc64da9 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-ep.c +++ b/drivers/pci/controller/cadence/pcie-cadence-ep.c @@ -565,7 +565,8 @@ static int cdns_pcie_ep_start(struct pci_epc *epc) struct cdns_pcie *pcie = &ep->pcie; struct device *dev = pcie->dev; int max_epfs = sizeof(epc->function_num_map) * 8; - int ret, value, epf; + int ret, epf, last_fn; + u32 reg, value; /* * BIT(0) is hardwired to 1, hence function 0 is always enabled @@ -573,6 +574,17 @@ static int cdns_pcie_ep_start(struct pci_epc *epc) */ cdns_pcie_writel(pcie, CDNS_PCIE_LM_EP_FUNC_CFG, epc->function_num_map); + /* + * Next function field in ARI_CAP_AND_CTR register for last function + * should be 0. + * Clearing Next Function Number field for the last function used. + */ + last_fn = find_last_bit(&epc->function_num_map, BITS_PER_LONG); + reg = CDNS_PCIE_CORE_PF_I_ARI_CAP_AND_CTRL(last_fn); + value = cdns_pcie_readl(pcie, reg); + value &= ~CDNS_PCIE_ARI_CAP_NFN_MASK; + cdns_pcie_writel(pcie, reg, value); + if (ep->quirk_disable_flr) { for (epf = 0; epf < max_epfs; epf++) { if (!(epc->function_num_map & BIT(epf))) diff --git a/drivers/pci/controller/cadence/pcie-cadence.h b/drivers/pci/controller/cadence/pcie-cadence.h index 03b96798f858..7a66a2f815dc 100644 --- a/drivers/pci/controller/cadence/pcie-cadence.h +++ b/drivers/pci/controller/cadence/pcie-cadence.h @@ -130,6 +130,12 @@ #define CDNS_PCIE_EP_FUNC_DEV_CAP_OFFSET 0xc0 #define CDNS_PCIE_EP_FUNC_SRIOV_CAP_OFFSET 0x200 +/* + * Endpoint PF Registers + */ +#define CDNS_PCIE_CORE_PF_I_ARI_CAP_AND_CTRL(fn) (0x144 + (fn) * 0x1000) +#define CDNS_PCIE_ARI_CAP_NFN_MASK GENMASK(15, 8) + /* * Root Port Registers (PCI configuration space for the root port function) */ -- cgit v1.2.3 From 72e34b8593e08a0ee759b7a038e0b178418ea6f8 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Thu, 7 Mar 2024 12:15:20 +0100 Subject: PCI: dwc: endpoint: Fix advertised resizable BAR size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit message in commit fc9a77040b04 ("PCI: designware-ep: Configure Resizable BAR cap to advertise the smallest size") claims that it modifies the Resizable BAR capability to only advertise support for 1 MB size BARs. However, the commit writes all zeroes to PCI_REBAR_CAP (the register which contains the possible BAR sizes that a BAR be resized to). According to the spec, it is illegal to not have a bit set in PCI_REBAR_CAP, and 1 MB is the smallest size allowed. Set bit 4 in PCI_REBAR_CAP, so that we actually advertise support for a 1 MB BAR size. Before: Capabilities: [2e8 v1] Physical Resizable BAR BAR 0: current size: 1MB BAR 1: current size: 1MB BAR 2: current size: 1MB BAR 3: current size: 1MB BAR 4: current size: 1MB BAR 5: current size: 1MB After: Capabilities: [2e8 v1] Physical Resizable BAR BAR 0: current size: 1MB, supported: 1MB BAR 1: current size: 1MB, supported: 1MB BAR 2: current size: 1MB, supported: 1MB BAR 3: current size: 1MB, supported: 1MB BAR 4: current size: 1MB, supported: 1MB BAR 5: current size: 1MB, supported: 1MB Fixes: fc9a77040b04 ("PCI: designware-ep: Configure Resizable BAR cap to advertise the smallest size") Link: https://lore.kernel.org/linux-pci/20240307111520.3303774-1-cassel@kernel.org Signed-off-by: Niklas Cassel Signed-off-by: Krzysztof Wilczyński Reviewed-by: Manivannan Sadhasivam Cc: # 5.2 --- drivers/pci/controller/dwc/pcie-designware-ep.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 5befed2dc02b..389daebc4316 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -627,8 +627,13 @@ int dw_pcie_ep_init_complete(struct dw_pcie_ep *ep) nbars = (reg & PCI_REBAR_CTRL_NBAR_MASK) >> PCI_REBAR_CTRL_NBAR_SHIFT; + /* + * PCIe r6.0, sec 7.8.6.2 require us to support at least one + * size in the range from 1 MB to 512 GB. Advertise support + * for 1 MB BAR size only. + */ for (i = 0; i < nbars; i++, offset += PCI_REBAR_CTRL) - dw_pcie_writel_dbi(pci, offset + PCI_REBAR_CAP, 0x0); + dw_pcie_writel_dbi(pci, offset + PCI_REBAR_CAP, BIT(4)); } /* -- cgit v1.2.3 From b5ff74c1ef50fe08e384026875fec660fadfaedd Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Fri, 16 Feb 2024 12:22:40 -0800 Subject: PCI: hv: Fix ring buffer size calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For a physical PCI device that is passed through to a Hyper-V guest VM, current code specifies the VMBus ring buffer size as 4 pages. But this is an inappropriate dependency, since the amount of ring buffer space needed is unrelated to PAGE_SIZE. For example, on x86 the ring buffer size ends up as 16 Kbytes, while on ARM64 with 64 Kbyte pages, the ring size bloats to 256 Kbytes. The ring buffer for PCI pass-thru devices is used for only a few messages during device setup and removal, so any space above a few Kbytes is wasted. Fix this by declaring the ring buffer size to be a fixed 16 Kbytes. Furthermore, use the VMBUS_RING_SIZE() macro so that the ring buffer header is properly accounted for, and so the size is rounded up to a page boundary, using the page size for which the kernel is built. While w/64 Kbyte pages this results in a 64 Kbyte ring buffer header plus a 64 Kbyte ring buffer, that's the smallest possible with that page size. It's still 128 Kbytes better than the current code. Link: https://lore.kernel.org/linux-pci/20240216202240.251818-1-mhklinux@outlook.com Signed-off-by: Michael Kelley Signed-off-by: Krzysztof Wilczyński Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Ilpo Jarvinen Reviewed-by: Long Li Cc: # 5.15.x --- drivers/pci/controller/pci-hyperv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index 1eaffff40b8d..5992280e8110 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -49,6 +49,7 @@ #include #include #include +#include #include /* @@ -465,7 +466,7 @@ struct pci_eject_response { u32 status; } __packed; -static int pci_ring_size = (4 * PAGE_SIZE); +static int pci_ring_size = VMBUS_RING_SIZE(SZ_16K); /* * Driver specific state. -- cgit v1.2.3 From 17423360a27ae58c1850f588bdd8013bbfcd250b Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 23 Feb 2024 14:58:50 -0600 Subject: PCI/ASPM: Save L1 PM Substates Capability for suspend/resume 4ff116d0d5fd ("PCI/ASPM: Save L1 PM Substates Capability for suspend/resume") restored the L1 PM Substates Capability after resume, which reduced power consumption by making the ASPM L1.x states work after resume. a7152be79b62 ("Revert "PCI/ASPM: Save L1 PM Substates Capability for suspend/resume"") reverted 4ff116d0d5fd because resume failed on some systems, so power consumption after resume increased again. a7152be79b62 mentioned that we restore L1 PM substate configuration even though ASPM L1 may already be enabled. This is due the fact that the pci_restore_aspm_l1ss_state() was called before pci_restore_pcie_state(). Save and restore the L1 PM Substates Capability, following PCIe r6.1, sec 5.5.4 more closely by: 1) Do not restore ASPM configuration in pci_restore_pcie_state() but do that after PCIe capability is restored in pci_restore_aspm_state() following PCIe r6.1, sec 5.5.4. 2) If BIOS reenables L1SS, particularly L1.2, we need to clear the enables in the right order, downstream before upstream. Defer restoring the L1SS config until we are at the downstream component. Then update the config for both ends of the link in the prescribed order. 3) Program ASPM L1 PM substate configuration before L1 enables. 4) Program ASPM L1 PM substate enables last, after rest of the fields in the capability are programmed. [bhelgaas: commit log, squash L1SS-related patches, do both LNKCTL restores in pci_restore_pcie_state()] Link: https://lore.kernel.org/r/20240128233212.1139663-3-david.e.box@linux.intel.com Link: https://lore.kernel.org/r/20240128233212.1139663-4-david.e.box@linux.intel.com Link: https://lore.kernel.org/r/20240223205851.114931-5-helgaas@kernel.org Closes: https://bugzilla.kernel.org/show_bug.cgi?id=217321 Link: https://bugzilla.kernel.org/show_bug.cgi?id=216782 Link: https://bugzilla.kernel.org/show_bug.cgi?id=216877 Co-developed-by: Mika Westerberg Co-developed-by: David E. Box Reported-by: Koba Ko Signed-off-by: Mika Westerberg Signed-off-by: David E. Box Signed-off-by: Bjorn Helgaas Tested-by: Tasev Nikola # Asus UX305FA Cc: Mark Enriquez Cc: Thomas Witt Cc: Werner Sembach Cc: Vidya Sagar --- drivers/pci/pci.c | 17 +++++++- drivers/pci/pci.h | 3 ++ drivers/pci/pcie/aspm.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++-- drivers/pci/probe.c | 1 + include/linux/pci.h | 2 +- 5 files changed, 119 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 564e2cf2dde5..ca6673588bc0 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1623,6 +1623,8 @@ static int pci_save_pcie_state(struct pci_dev *dev) pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &cap[i++]); pcie_capability_read_word(dev, PCI_EXP_SLTCTL2, &cap[i++]); + pci_save_aspm_l1ss_state(dev); + return 0; } @@ -1630,7 +1632,7 @@ static void pci_restore_pcie_state(struct pci_dev *dev) { int i = 0; struct pci_cap_saved_state *save_state; - u16 *cap; + u16 *cap, lnkctl; save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); if (!save_state) @@ -1645,12 +1647,23 @@ static void pci_restore_pcie_state(struct pci_dev *dev) cap = (u16 *)&save_state->cap.data[0]; pcie_capability_write_word(dev, PCI_EXP_DEVCTL, cap[i++]); - pcie_capability_write_word(dev, PCI_EXP_LNKCTL, cap[i++]); + + /* Restore LNKCTL register with ASPM control field clear */ + lnkctl = cap[i++]; + pcie_capability_write_word(dev, PCI_EXP_LNKCTL, + lnkctl & ~PCI_EXP_LNKCTL_ASPMC); + pcie_capability_write_word(dev, PCI_EXP_SLTCTL, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_RTCTL, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_DEVCTL2, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_LNKCTL2, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_SLTCTL2, cap[i++]); + + pci_restore_aspm_l1ss_state(dev); + + /* Restore ASPM control after restoring L1SS state */ + pcie_capability_set_word(dev, PCI_EXP_LNKCTL, + lnkctl & PCI_EXP_LNKCTL_ASPMC); } static int pci_save_pcix_state(struct pci_dev *dev) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ad3add45345c..eca5938deb07 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -571,6 +571,9 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt); /* ASPM-related functionality we need even without CONFIG_PCIEASPM */ void pci_save_ltr_state(struct pci_dev *dev); void pci_restore_ltr_state(struct pci_dev *dev); +void pci_configure_aspm_l1ss(struct pci_dev *dev); +void pci_save_aspm_l1ss_state(struct pci_dev *dev); +void pci_restore_aspm_l1ss_state(struct pci_dev *dev); #ifdef CONFIG_PCIEASPM void pcie_aspm_init_link_state(struct pci_dev *pdev); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 21731b232fb8..977eca893b2a 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -64,6 +64,105 @@ void pci_restore_ltr_state(struct pci_dev *dev) pci_write_config_dword(dev, ltr + PCI_LTR_MAX_SNOOP_LAT, *cap); } +void pci_configure_aspm_l1ss(struct pci_dev *pdev) +{ + int rc; + + pdev->l1ss = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS); + + rc = pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_L1SS, + 2 * sizeof(u32)); + if (rc) + pci_err(pdev, "unable to allocate ASPM L1SS save buffer (%pe)\n", + ERR_PTR(rc)); +} + +void pci_save_aspm_l1ss_state(struct pci_dev *pdev) +{ + struct pci_cap_saved_state *save_state; + u16 l1ss = pdev->l1ss; + u32 *cap; + + /* + * Save L1 substate configuration. The ASPM L0s/L1 configuration + * in PCI_EXP_LNKCTL_ASPMC is saved by pci_save_pcie_state(). + */ + if (!l1ss) + return; + + save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS); + if (!save_state) + return; + + cap = &save_state->cap.data[0]; + pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL2, cap++); + pci_read_config_dword(pdev, l1ss + PCI_L1SS_CTL1, cap++); +} + +void pci_restore_aspm_l1ss_state(struct pci_dev *pdev) +{ + struct pci_cap_saved_state *pl_save_state, *cl_save_state; + struct pci_dev *parent = pdev->bus->self; + u32 *cap, pl_ctl1, pl_ctl2, pl_l1_2_enable; + u32 cl_ctl1, cl_ctl2, cl_l1_2_enable; + + /* + * In case BIOS enabled L1.2 when resuming, we need to disable it first + * on the downstream component before the upstream. So, don't attempt to + * restore either until we are at the downstream component. + */ + if (pcie_downstream_port(pdev) || !parent) + return; + + if (!pdev->l1ss || !parent->l1ss) + return; + + cl_save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_L1SS); + pl_save_state = pci_find_saved_ext_cap(parent, PCI_EXT_CAP_ID_L1SS); + if (!cl_save_state || !pl_save_state) + return; + + cap = &cl_save_state->cap.data[0]; + cl_ctl2 = *cap++; + cl_ctl1 = *cap; + cap = &pl_save_state->cap.data[0]; + pl_ctl2 = *cap++; + pl_ctl1 = *cap; + + /* + * Disable L1.2 on this downstream endpoint device first, followed + * by the upstream + */ + pci_clear_and_set_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1_2_MASK, 0); + pci_clear_and_set_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + PCI_L1SS_CTL1_L1_2_MASK, 0); + + /* + * In addition, Common_Mode_Restore_Time and LTR_L1.2_THRESHOLD + * in PCI_L1SS_CTL1 must be programmed *before* setting the L1.2 + * enable bits, even though they're all in PCI_L1SS_CTL1. + */ + pl_l1_2_enable = pl_ctl1 & PCI_L1SS_CTL1_L1_2_MASK; + pl_ctl1 &= ~PCI_L1SS_CTL1_L1_2_MASK; + cl_l1_2_enable = cl_ctl1 & PCI_L1SS_CTL1_L1_2_MASK; + cl_ctl1 &= ~PCI_L1SS_CTL1_L1_2_MASK; + + /* Write back without enables first (above we cleared them in ctl1) */ + pci_write_config_dword(parent, parent->l1ss + PCI_L1SS_CTL2, pl_ctl2); + pci_write_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL2, cl_ctl2); + pci_write_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, pl_ctl1); + pci_write_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, cl_ctl1); + + /* Then write back the enables */ + if (pl_l1_2_enable || cl_l1_2_enable) { + pci_write_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + pl_ctl1 | pl_l1_2_enable); + pci_write_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, + cl_ctl1 | cl_l1_2_enable); + } +} + #ifdef CONFIG_PCIEASPM #ifdef MODULE_PARAM_PREFIX @@ -1005,9 +1104,6 @@ void pci_configure_ltr(struct pci_dev *pdev) if (!pci_is_pcie(pdev)) return; - /* Read L1 PM substate capabilities */ - pdev->l1ss = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_L1SS); - pcie_capability_read_dword(pdev, PCI_EXP_DEVCAP2, &cap); if (!(cap & PCI_EXP_DEVCAP2_LTR)) return; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b809c0b0e0e5..1434bf495db3 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2259,6 +2259,7 @@ static void pci_configure_device(struct pci_dev *dev) pci_configure_extended_tags(dev, NULL); pci_configure_relaxed_ordering(dev); pci_configure_ltr(dev); + pci_configure_aspm_l1ss(dev); pci_configure_eetlp_prefix(dev); pci_configure_serr(dev); diff --git a/include/linux/pci.h b/include/linux/pci.h index add9368e6314..6967ae7b4115 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -390,9 +390,9 @@ struct pci_dev { unsigned int d3hot_delay; /* D3hot->D0 transition time in ms */ unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ + u16 l1ss; /* L1SS Capability pointer */ #ifdef CONFIG_PCIEASPM struct pcie_link_state *link_state; /* ASPM link state */ - u16 l1ss; /* L1SS Capability pointer */ unsigned int ltr_path:1; /* Latency Tolerance Reporting supported from root to here */ #endif -- cgit v1.2.3 From c198fafa0125e97728d16411aa653602900ab0bc Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 23 Feb 2024 14:58:51 -0600 Subject: PCI/ASPM: Call pci_save_ltr_state() from pci_save_pcie_state() ASPM state is saved and restored from pci_save/restore_pcie_state(). Since the LTR Capability is linked with ASPM, move the LTR save and restore calls there as well. No functional change intended. Suggested-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20240128233212.1139663-6-david.e.box@linux.intel.com Link: https://lore.kernel.org/r/20240223205851.114931-6-helgaas@kernel.org Signed-off-by: David E. Box Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index ca6673588bc0..4ea98665172d 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1624,6 +1624,7 @@ static int pci_save_pcie_state(struct pci_dev *dev) pcie_capability_read_word(dev, PCI_EXP_SLTCTL2, &cap[i++]); pci_save_aspm_l1ss_state(dev); + pci_save_ltr_state(dev); return 0; } @@ -1634,6 +1635,12 @@ static void pci_restore_pcie_state(struct pci_dev *dev) struct pci_cap_saved_state *save_state; u16 *cap, lnkctl; + /* + * Restore max latencies (in the LTR capability) before enabling + * LTR itself in PCI_EXP_DEVCTL2. + */ + pci_restore_ltr_state(dev); + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); if (!save_state) return; @@ -1726,7 +1733,6 @@ int pci_save_state(struct pci_dev *dev) if (i != 0) return i; - pci_save_ltr_state(dev); pci_save_dpc_state(dev); pci_save_aer_state(dev); pci_save_ptm_state(dev); @@ -1827,12 +1833,6 @@ void pci_restore_state(struct pci_dev *dev) if (!dev->state_saved) return; - /* - * Restore max latencies (in the LTR capability) before enabling - * LTR itself (in the PCIe capability). - */ - pci_restore_ltr_state(dev); - pci_restore_pcie_state(dev); pci_restore_pasid_state(dev); pci_restore_pri_state(dev); -- cgit v1.2.3 From 64dbb2d707444f691539fb12aacf81797786c10b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 5 Mar 2024 15:15:25 -0600 Subject: PCI/ASPM: Disable L1 before configuring L1 Substates Per PCIe r6.1, sec 5.5.4, L1 must be disabled while setting ASPM L1 PM Substates enable bits. Previously this was enforced by clearing PCI_EXP_LNKCTL_ASPMC before calling pci_restore_aspm_l1ss_state(). Move the L1 (and L0s, although that doesn't seem required) disable into pci_restore_aspm_l1ss_state() itself so it's closer to the code that depends on it. Link: https://lore.kernel.org/r/20240223213733.GA115410@bhelgaas Signed-off-by: Bjorn Helgaas --- drivers/pci/pci.c | 16 +++------------- drivers/pci/pcie/aspm.c | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 4ea98665172d..5a4b501a3f41 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1633,13 +1633,14 @@ static void pci_restore_pcie_state(struct pci_dev *dev) { int i = 0; struct pci_cap_saved_state *save_state; - u16 *cap, lnkctl; + u16 *cap; /* * Restore max latencies (in the LTR capability) before enabling * LTR itself in PCI_EXP_DEVCTL2. */ pci_restore_ltr_state(dev); + pci_restore_aspm_l1ss_state(dev); save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); if (!save_state) @@ -1654,23 +1655,12 @@ static void pci_restore_pcie_state(struct pci_dev *dev) cap = (u16 *)&save_state->cap.data[0]; pcie_capability_write_word(dev, PCI_EXP_DEVCTL, cap[i++]); - - /* Restore LNKCTL register with ASPM control field clear */ - lnkctl = cap[i++]; - pcie_capability_write_word(dev, PCI_EXP_LNKCTL, - lnkctl & ~PCI_EXP_LNKCTL_ASPMC); - + pcie_capability_write_word(dev, PCI_EXP_LNKCTL, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_SLTCTL, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_RTCTL, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_DEVCTL2, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_LNKCTL2, cap[i++]); pcie_capability_write_word(dev, PCI_EXP_SLTCTL2, cap[i++]); - - pci_restore_aspm_l1ss_state(dev); - - /* Restore ASPM control after restoring L1SS state */ - pcie_capability_set_word(dev, PCI_EXP_LNKCTL, - lnkctl & PCI_EXP_LNKCTL_ASPMC); } static int pci_save_pcix_state(struct pci_dev *dev) diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 977eca893b2a..1379b8decdf1 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -105,6 +105,7 @@ void pci_restore_aspm_l1ss_state(struct pci_dev *pdev) struct pci_dev *parent = pdev->bus->self; u32 *cap, pl_ctl1, pl_ctl2, pl_l1_2_enable; u32 cl_ctl1, cl_ctl2, cl_l1_2_enable; + u16 clnkctl, plnkctl; /* * In case BIOS enabled L1.2 when resuming, we need to disable it first @@ -129,6 +130,17 @@ void pci_restore_aspm_l1ss_state(struct pci_dev *pdev) pl_ctl2 = *cap++; pl_ctl1 = *cap; + /* Make sure L0s/L1 are disabled before updating L1SS config */ + pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &clnkctl); + pcie_capability_read_word(parent, PCI_EXP_LNKCTL, &plnkctl); + if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, clnkctl) || + FIELD_GET(PCI_EXP_LNKCTL_ASPMC, plnkctl)) { + pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, + clnkctl & ~PCI_EXP_LNKCTL_ASPMC); + pcie_capability_write_word(parent, PCI_EXP_LNKCTL, + plnkctl & ~PCI_EXP_LNKCTL_ASPMC); + } + /* * Disable L1.2 on this downstream endpoint device first, followed * by the upstream @@ -161,6 +173,13 @@ void pci_restore_aspm_l1ss_state(struct pci_dev *pdev) pci_write_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, cl_ctl1 | cl_l1_2_enable); } + + /* Restore L0s/L1 if they were enabled */ + if (FIELD_GET(PCI_EXP_LNKCTL_ASPMC, clnkctl) || + FIELD_GET(PCI_EXP_LNKCTL_ASPMC, plnkctl)) { + pcie_capability_write_word(parent, PCI_EXP_LNKCTL, clnkctl); + pcie_capability_write_word(pdev, PCI_EXP_LNKCTL, plnkctl); + } } #ifdef CONFIG_PCIEASPM -- cgit v1.2.3 From 6d4266675279b38c301243f3a4fac4a511b03246 Mon Sep 17 00:00:00 2001 From: Vidya Sagar Date: Fri, 23 Feb 2024 13:36:24 -0600 Subject: PCI/ASPM: Update save_state when configuration changes Many PCIe device drivers save the configuration state of their device during probe and restore it when their .slot_reset() hook is called during PCIe error recovery. If the ASPM configuration is changed after the driver's probe is called and before an error event occurs, .slot_reset() restores the ASPM configuration to what it was at the time of probe, not to what it was just before the occurrence of the error event. This leads to a mismatch in ASPM configuration between the device and its upstream device. Update the saved configuration of the device when the ASPM configuration changes. Link: https://lore.kernel.org/r/20240222174436.3565146-1-vidyas@nvidia.com Signed-off-by: Vidya Sagar [bhelgaas: commit log, rebase to pci/aspm, rename to pci_update_aspm_saved_state() since it updates only LNKCTL, update only ASPMC and CLKREQ_EN in LNKCTL] Signed-off-by: Bjorn Helgaas Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: David E. Box --- drivers/pci/pcie/aspm.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 1379b8decdf1..10160d82c10a 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -301,16 +301,42 @@ static int policy_to_clkpm_state(struct pcie_link_state *link) return 0; } +static void pci_update_aspm_saved_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state; + u16 *cap, lnkctl, aspm_ctl; + + save_state = pci_find_saved_cap(dev, PCI_CAP_ID_EXP); + if (!save_state) + return; + + pcie_capability_read_word(dev, PCI_EXP_LNKCTL, &lnkctl); + + /* + * Update ASPM and CLKREQ bits of LNKCTL in save_state. We only + * write PCI_EXP_LNKCTL_CCC during enumeration, so it shouldn't + * change after being captured in save_state. + */ + aspm_ctl = lnkctl & (PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_CLKREQ_EN); + lnkctl &= ~(PCI_EXP_LNKCTL_ASPMC | PCI_EXP_LNKCTL_CLKREQ_EN); + + /* Depends on pci_save_pcie_state(): cap[1] is LNKCTL */ + cap = (u16 *)&save_state->cap.data[0]; + cap[1] = lnkctl | aspm_ctl; +} + static void pcie_set_clkpm_nocheck(struct pcie_link_state *link, int enable) { struct pci_dev *child; struct pci_bus *linkbus = link->pdev->subordinate; u32 val = enable ? PCI_EXP_LNKCTL_CLKREQ_EN : 0; - list_for_each_entry(child, &linkbus->devices, bus_list) + list_for_each_entry(child, &linkbus->devices, bus_list) { pcie_capability_clear_and_set_word(child, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_CLKREQ_EN, val); + pci_update_aspm_saved_state(child); + } link->clkpm_enabled = !!enable; } @@ -929,6 +955,12 @@ static void pcie_config_aspm_link(struct pcie_link_state *link, u32 state) pcie_config_aspm_dev(parent, upstream); link->aspm_enabled = state; + + /* Update latest ASPM configuration in saved context */ + pci_save_aspm_l1ss_state(link->downstream); + pci_update_aspm_saved_state(link->downstream); + pci_save_aspm_l1ss_state(parent); + pci_update_aspm_saved_state(parent); } static void pcie_config_aspm_path(struct pcie_link_state *link) -- cgit v1.2.3