From 9e541b3cee70a3bbe86b176c903c23b29fe033cd Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Wed, 10 Dec 2025 21:29:05 +0800 Subject: PCI: trace: Add generic RAS tracepoint for hotplug event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hotplug events are critical indicators for analyzing hardware health, and surprise link downs can significantly impact system performance and reliability. Define a new TRACING_SYSTEM named "pci", add a generic RAS tracepoint for hotplug event to help health checks. Add enum pci_hotplug_event in include/uapi/linux/pci.h so applications like rasdaemon can register tracepoint event handlers for it. The following output is generated when a device is hotplugged: $ echo 1 > /sys/kernel/debug/tracing/events/pci/pci_hp_event/enable $ cat /sys/kernel/debug/tracing/trace_pipe irq/51-pciehp-88 [001] ..... 1311.177459: pci_hp_event: 0000:00:02.0 slot:10, event:CARD_PRESENT irq/51-pciehp-88 [001] ..... 1311.177566: pci_hp_event: 0000:00:02.0 slot:10, event:LINK_UP Suggested-by: Lukas Wunner Signed-off-by: Shuai Xue Signed-off-by: Bjorn Helgaas Reviewed-by: Lukas Wunner Reviewed-by: Jonathan Cameron Reviewed-by: Steven Rostedt (Google) # for trace event Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251210132907.58799-2-xueshuai@linux.alibaba.com --- include/trace/events/pci.h | 72 ++++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/pci.h | 7 +++++ 2 files changed, 79 insertions(+) create mode 100644 include/trace/events/pci.h (limited to 'include') diff --git a/include/trace/events/pci.h b/include/trace/events/pci.h new file mode 100644 index 000000000000..39e512a167ee --- /dev/null +++ b/include/trace/events/pci.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM pci + +#if !defined(_TRACE_HW_EVENT_PCI_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HW_EVENT_PCI_H + +#include + +#define PCI_HOTPLUG_EVENT \ + EM(PCI_HOTPLUG_LINK_UP, "LINK_UP") \ + EM(PCI_HOTPLUG_LINK_DOWN, "LINK_DOWN") \ + EM(PCI_HOTPLUG_CARD_PRESENT, "CARD_PRESENT") \ + EMe(PCI_HOTPLUG_CARD_NOT_PRESENT, "CARD_NOT_PRESENT") + +/* Enums require being exported to userspace, for user tool parsing */ +#undef EM +#undef EMe +#define EM(a, b) TRACE_DEFINE_ENUM(a); +#define EMe(a, b) TRACE_DEFINE_ENUM(a); + +PCI_HOTPLUG_EVENT + +/* + * Now redefine the EM() and EMe() macros to map the enums to the strings + * that will be printed in the output. + */ +#undef EM +#undef EMe +#define EM(a, b) {a, b}, +#define EMe(a, b) {a, b} + +/* + * Note: For generic PCI hotplug events, we pass already-resolved strings + * (port_name, slot) instead of driver-specific structures like 'struct + * controller'. This is because different PCI hotplug drivers (pciehp, cpqphp, + * ibmphp, shpchp) define their own versions of 'struct controller' with + * different fields and helper functions. Using driver-specific structures would + * make the tracepoint interface non-generic and cause compatibility issues + * across different drivers. + */ +TRACE_EVENT(pci_hp_event, + + TP_PROTO(const char *port_name, + const char *slot, + const int event), + + TP_ARGS(port_name, slot, event), + + TP_STRUCT__entry( + __string( port_name, port_name ) + __string( slot, slot ) + __field( int, event ) + ), + + TP_fast_assign( + __assign_str(port_name); + __assign_str(slot); + __entry->event = event; + ), + + TP_printk("%s slot:%s, event:%s\n", + __get_str(port_name), + __get_str(slot), + __print_symbolic(__entry->event, PCI_HOTPLUG_EVENT) + ) +); + +#endif /* _TRACE_HW_EVENT_PCI_H */ + +/* This part must be outside protection */ +#include diff --git a/include/uapi/linux/pci.h b/include/uapi/linux/pci.h index a769eefc5139..4f150028965d 100644 --- a/include/uapi/linux/pci.h +++ b/include/uapi/linux/pci.h @@ -39,4 +39,11 @@ #define PCIIOC_MMAP_IS_MEM (PCIIOC_BASE | 0x02) /* Set mmap state to MEM space. */ #define PCIIOC_WRITE_COMBINE (PCIIOC_BASE | 0x03) /* Enable/disable write-combining. */ +enum pci_hotplug_event { + PCI_HOTPLUG_LINK_UP, + PCI_HOTPLUG_LINK_DOWN, + PCI_HOTPLUG_CARD_PRESENT, + PCI_HOTPLUG_CARD_NOT_PRESENT, +}; + #endif /* _UAPILINUX_PCI_H */ -- cgit v1.2.3 From d4318c1a79ac49f0726dd23a01d1961757b5f98d Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Wed, 10 Dec 2025 21:29:06 +0800 Subject: PCI: trace: Add RAS tracepoint to monitor link speed changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCIe link speed degradation directly impacts system performance and often indicates hardware issues such as faulty devices, physical layer problems, or configuration errors. To this end, add a RAS tracepoint to monitor link speed changes, enabling proactive health checks and diagnostic analysis. The following output is generated when a device is hotplugged: $ echo 1 > /sys/kernel/debug/tracing/events/pci/pcie_link_event/enable $ cat /sys/kernel/debug/tracing/trace_pipe irq/51-pciehp-88 [001] ..... 381.545386: pcie_link_event: 0000:00:02.0 type:4, reason:4, cur_bus_speed:20, max_bus_speed:23, width:1, flit_mode:0, status:DLLLA Suggested-by: Ilpo Järvinen Suggested-by: Matthew W Carlis Suggested-by: Lukas Wunner Signed-off-by: Shuai Xue Signed-off-by: Bjorn Helgaas Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251210132907.58799-3-xueshuai@linux.alibaba.com --- drivers/pci/hotplug/pciehp_hpc.c | 3 ++- drivers/pci/pci.c | 2 +- drivers/pci/pci.h | 21 +++++++++++++-- drivers/pci/pcie/bwctrl.c | 4 +-- drivers/pci/probe.c | 9 ++++--- include/trace/events/pci.h | 57 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index bcc51b26d03d..ad5f28f6a8b1 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -320,7 +320,8 @@ int pciehp_check_link_status(struct controller *ctrl) } pcie_capability_read_word(pdev, PCI_EXP_LNKSTA2, &linksta2); - __pcie_update_link_speed(ctrl->pcie->port->subordinate, lnk_status, linksta2); + __pcie_update_link_speed(ctrl->pcie->port->subordinate, PCIE_HOTPLUG, + lnk_status, linksta2); if (!found) { ctrl_info(ctrl, "Slot(%s): No device found\n", diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 13dbb405dc31..f034e173819f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4550,7 +4550,7 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt) * Link Speed. */ if (pdev->subordinate) - pcie_update_link_speed(pdev->subordinate); + pcie_update_link_speed(pdev->subordinate, PCIE_LINK_RETRAIN); return rc; } diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 0e67014aa001..c71cfbe78cc3 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -5,6 +5,7 @@ #include #include #include +#include struct pcie_tlp_log; @@ -555,12 +556,28 @@ const char *pci_speed_string(enum pci_bus_speed speed); void __pcie_print_link_status(struct pci_dev *dev, bool verbose); void pcie_report_downtraining(struct pci_dev *dev); -static inline void __pcie_update_link_speed(struct pci_bus *bus, u16 linksta, u16 linksta2) +enum pcie_link_change_reason { + PCIE_LINK_RETRAIN, + PCIE_ADD_BUS, + PCIE_BWCTRL_ENABLE, + PCIE_BWCTRL_IRQ, + PCIE_HOTPLUG, +}; + +static inline void __pcie_update_link_speed(struct pci_bus *bus, + enum pcie_link_change_reason reason, + u16 linksta, u16 linksta2) { bus->cur_bus_speed = pcie_link_speed[linksta & PCI_EXP_LNKSTA_CLS]; bus->flit_mode = (linksta2 & PCI_EXP_LNKSTA2_FLIT) ? 1 : 0; + + trace_pcie_link_event(bus, + reason, + FIELD_GET(PCI_EXP_LNKSTA_NLW, linksta), + linksta & PCI_EXP_LNKSTA_LINK_STATUS_MASK); } -void pcie_update_link_speed(struct pci_bus *bus); + +void pcie_update_link_speed(struct pci_bus *bus, enum pcie_link_change_reason reason); /* Single Root I/O Virtualization */ struct pci_sriov { diff --git a/drivers/pci/pcie/bwctrl.c b/drivers/pci/pcie/bwctrl.c index 36f939f23d34..32f1b30ecb84 100644 --- a/drivers/pci/pcie/bwctrl.c +++ b/drivers/pci/pcie/bwctrl.c @@ -199,7 +199,7 @@ static void pcie_bwnotif_enable(struct pcie_device *srv) * Update after enabling notifications & clearing status bits ensures * link speed is up to date. */ - pcie_update_link_speed(port->subordinate); + pcie_update_link_speed(port->subordinate, PCIE_BWCTRL_ENABLE); } static void pcie_bwnotif_disable(struct pci_dev *port) @@ -234,7 +234,7 @@ static irqreturn_t pcie_bwnotif_irq(int irq, void *context) * speed (inside pcie_update_link_speed()) after LBMS has been * cleared to avoid missing link speed changes. */ - pcie_update_link_speed(port->subordinate); + pcie_update_link_speed(port->subordinate, PCIE_BWCTRL_IRQ); return IRQ_HANDLED; } diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 41183aed8f5d..392b7dc3d391 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "pci.h" #define CARDBUS_LATENCY_TIMER 176 /* secondary latency timer */ @@ -824,14 +825,16 @@ const char *pci_speed_string(enum pci_bus_speed speed) } EXPORT_SYMBOL_GPL(pci_speed_string); -void pcie_update_link_speed(struct pci_bus *bus) +void pcie_update_link_speed(struct pci_bus *bus, + enum pcie_link_change_reason reason) { struct pci_dev *bridge = bus->self; u16 linksta, linksta2; pcie_capability_read_word(bridge, PCI_EXP_LNKSTA, &linksta); pcie_capability_read_word(bridge, PCI_EXP_LNKSTA2, &linksta2); - __pcie_update_link_speed(bus, linksta, linksta2); + + __pcie_update_link_speed(bus, reason, linksta, linksta2); } EXPORT_SYMBOL_GPL(pcie_update_link_speed); @@ -918,7 +921,7 @@ static void pci_set_bus_speed(struct pci_bus *bus) pcie_capability_read_dword(bridge, PCI_EXP_LNKCAP, &linkcap); bus->max_bus_speed = pcie_link_speed[linkcap & PCI_EXP_LNKCAP_SLS]; - pcie_update_link_speed(bus); + pcie_update_link_speed(bus, PCIE_ADD_BUS); } } diff --git a/include/trace/events/pci.h b/include/trace/events/pci.h index 39e512a167ee..9a9122f62fd3 100644 --- a/include/trace/events/pci.h +++ b/include/trace/events/pci.h @@ -5,6 +5,7 @@ #if !defined(_TRACE_HW_EVENT_PCI_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_HW_EVENT_PCI_H +#include #include #define PCI_HOTPLUG_EVENT \ @@ -66,6 +67,62 @@ TRACE_EVENT(pci_hp_event, ) ); +#define PCI_EXP_LNKSTA_LINK_STATUS_MASK (PCI_EXP_LNKSTA_LBMS | \ + PCI_EXP_LNKSTA_LABS | \ + PCI_EXP_LNKSTA_LT | \ + PCI_EXP_LNKSTA_DLLLA) + +#define LNKSTA_FLAGS \ + { PCI_EXP_LNKSTA_LT, "LT"}, \ + { PCI_EXP_LNKSTA_DLLLA, "DLLLA"}, \ + { PCI_EXP_LNKSTA_LBMS, "LBMS"}, \ + { PCI_EXP_LNKSTA_LABS, "LABS"} + +TRACE_EVENT(pcie_link_event, + + TP_PROTO(struct pci_bus *bus, + unsigned int reason, + unsigned int width, + unsigned int status + ), + + TP_ARGS(bus, reason, width, status), + + TP_STRUCT__entry( + __string( port_name, pci_name(bus->self)) + __field( unsigned int, type ) + __field( unsigned int, reason ) + __field( unsigned int, cur_bus_speed ) + __field( unsigned int, max_bus_speed ) + __field( unsigned int, width ) + __field( unsigned int, flit_mode ) + __field( unsigned int, link_status ) + ), + + TP_fast_assign( + __assign_str(port_name); + __entry->type = pci_pcie_type(bus->self); + __entry->reason = reason; + __entry->cur_bus_speed = bus->cur_bus_speed; + __entry->max_bus_speed = bus->max_bus_speed; + __entry->width = width; + __entry->flit_mode = bus->flit_mode; + __entry->link_status = status; + ), + + TP_printk("%s type:%d, reason:%d, cur_bus_speed:%d, max_bus_speed:%d, width:%u, flit_mode:%u, status:%s\n", + __get_str(port_name), + __entry->type, + __entry->reason, + __entry->cur_bus_speed, + __entry->max_bus_speed, + __entry->width, + __entry->flit_mode, + __print_flags((unsigned long)__entry->link_status, "|", + LNKSTA_FLAGS) + ) +); + #endif /* _TRACE_HW_EVENT_PCI_H */ /* This part must be outside protection */ -- cgit v1.2.3 From 80d9411c00e805488b631c91034e9b6c14a6dbdc Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Sun, 4 Jan 2026 14:51:28 +0200 Subject: PCI/P2PDMA: Add missing struct p2pdma_provider documentation Two fields in struct p2pdma_provider were not documented, which resulted in the following kernel-doc warning: Warning: include/linux/pci-p2pdma.h:26 struct member 'owner' not described in 'p2pdma_provider' Warning: include/linux/pci-p2pdma.h:26 struct member 'bus_offset' not described in 'p2pdma_provider' Repro: $ scripts/kernel-doc -none include/linux/pci-p2pdma.h Fixes: f58ef9d1d135 ("PCI/P2PDMA: Separate the mmap() support from the core logic") Reported-by: Bjorn Helgaas Closes: https://lore.kernel.org/all/20260102234033.GA246107@bhelgaas Signed-off-by: Leon Romanovsky Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe Link: https://patch.msgid.link/20260104-fix-p2p-kdoc-v1-1-6d181233f8bc@nvidia.com --- include/linux/pci-p2pdma.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/pci-p2pdma.h b/include/linux/pci-p2pdma.h index 517e121d2598..873de20a2247 100644 --- a/include/linux/pci-p2pdma.h +++ b/include/linux/pci-p2pdma.h @@ -20,6 +20,8 @@ struct scatterlist; * struct p2pdma_provider * * A p2pdma provider is a range of MMIO address space available to the CPU. + * @owner: Device to which this provider belongs. + * @bus_offset: Bus offset for p2p communication. */ struct p2pdma_provider { struct device *owner; -- cgit v1.2.3 From eeb95c07d5fcaafb1829d5307ce4290cf1dc3190 Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Wed, 17 Dec 2025 07:45:28 -0800 Subject: PCI: Add ASPEED vendor ID to pci_ids.h Add PCI_VENDOR_ID_ASPEED to the shared pci_ids.h header and remove the duplicate local definition from ehci-pci.c. This prepares for adding a PCI quirk for ASPEED devices. Signed-off-by: Nirmoy Das Signed-off-by: Bjorn Helgaas Reviewed-by: Jason Gunthorpe Link: https://patch.msgid.link/20251217154529.377586-1-nirmoyd@nvidia.com --- drivers/usb/host/ehci-pci.c | 1 - include/linux/pci_ids.h | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 889dc4426271..bd3a63555594 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -21,7 +21,6 @@ static const char hcd_name[] = "ehci-pci"; /* defined here to avoid adding to pci_ids.h for single instance use */ #define PCI_DEVICE_ID_INTEL_CE4100_USB 0x2e70 -#define PCI_VENDOR_ID_ASPEED 0x1a03 #define PCI_DEVICE_ID_ASPEED_EHCI 0x2603 /*-------------------------------------------------------------------------*/ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index a9a089566b7c..30dd854a9156 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2583,6 +2583,8 @@ #define PCI_DEVICE_ID_NETRONOME_NFP3800_VF 0x3803 #define PCI_DEVICE_ID_NETRONOME_NFP6000_VF 0x6003 +#define PCI_VENDOR_ID_ASPEED 0x1a03 + #define PCI_VENDOR_ID_QMI 0x1a32 #define PCI_VENDOR_ID_AZWAVE 0x1a3b -- cgit v1.2.3 From 550a190494a0d3e933dd6f3b2e9c430f94a30a8c Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Wed, 17 Dec 2025 07:45:29 -0800 Subject: PCI: Add PCI_BRIDGE_NO_ALIAS quirk for ASPEED AST1150 ASPEED BMC controllers have VGA and USB functions behind a PCIe-to-PCI bridge that causes them to share the same StreamID: [e0]---00.0-[e1-e2]----00.0-[e2]--+-00.0 ASPEED Graphics Family \-02.0 ASPEED USB Controller Both devices get StreamID 0x5e200 due to bridge aliasing, causing the USB controller to be rejected with 'Aliasing StreamID unsupported'. Per ASPEED, the AST1150 doesn't use a real PCI bus and always forwards the original Requester ID from downstream devices rather than replacing it with any alias. Add a new PCI_DEV_FLAGS_PCI_BRIDGE_NO_ALIAS flag and apply it to the AST1150. Suggested-by: Jason Gunthorpe Signed-off-by: Nirmoy Das Signed-off-by: Bjorn Helgaas Reviewed-by: Robin Murphy Reviewed-by: Jason Gunthorpe Link: https://patch.msgid.link/20251217154529.377586-2-nirmoyd@nvidia.com --- drivers/pci/quirks.c | 10 ++++++++++ drivers/pci/search.c | 2 ++ include/linux/pci.h | 5 +++++ 3 files changed, 17 insertions(+) (limited to 'include') diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index b9c252aa6fe0..7404fb0ff146 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4453,6 +4453,16 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9000, DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_BROADCOM, 0x9084, quirk_bridge_cavm_thrx2_pcie_root); +/* + * AST1150 doesn't use a real PCI bus and always forwards the requester ID + * from downstream devices. + */ +static void quirk_aspeed_pci_bridge_no_alias(struct pci_dev *pdev) +{ + pdev->dev_flags |= PCI_DEV_FLAGS_PCI_BRIDGE_NO_ALIAS; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASPEED, 0x1150, quirk_aspeed_pci_bridge_no_alias); + /* * Intersil/Techwell TW686[4589]-based video capture cards have an empty (zero) * class code. Fix it. diff --git a/drivers/pci/search.c b/drivers/pci/search.c index e6e84dc62e82..e3d3177fce54 100644 --- a/drivers/pci/search.c +++ b/drivers/pci/search.c @@ -86,6 +86,8 @@ int pci_for_each_dma_alias(struct pci_dev *pdev, case PCI_EXP_TYPE_DOWNSTREAM: continue; case PCI_EXP_TYPE_PCI_BRIDGE: + if (tmp->dev_flags & PCI_DEV_FLAGS_PCI_BRIDGE_NO_ALIAS) + continue; ret = fn(tmp, PCI_DEVID(tmp->subordinate->number, PCI_DEVFN(0, 0)), data); diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..48d5b9dac5f1 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -248,6 +248,11 @@ enum pci_dev_flags { PCI_DEV_FLAGS_HAS_MSI_MASKING = (__force pci_dev_flags_t) (1 << 12), /* Device requires write to PCI_MSIX_ENTRY_DATA before any MSIX reads */ PCI_DEV_FLAGS_MSIX_TOUCH_ENTRY_DATA_FIRST = (__force pci_dev_flags_t) (1 << 13), + /* + * PCIe to PCI bridge does not create RID aliases because the bridge is + * integrated with the downstream devices and doesn't use real PCI. + */ + PCI_DEV_FLAGS_PCI_BRIDGE_NO_ALIAS = (__force pci_dev_flags_t) (1 << 14), }; enum pci_irq_reroute_variant { -- cgit v1.2.3 From 113f44ed50d274447a3b76cf250989a423f179a5 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 15 Jan 2026 12:59:01 +0530 Subject: PCI/pwrctrl: Add 'struct pci_pwrctrl::power_{on/off}' callbacks To allow the pwrctrl core to control the power on/off sequences of the pwrctrl drivers, add the 'struct pci_pwrctrl::power_{on/off}' callbacks and populate them in the respective pwrctrl drivers. The pwrctrl drivers still power on the resources on their own now. So there is no functional change. Co-developed-by: Krishna Chaitanya Chundru Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Chen-Yu Tsai Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260115-pci-pwrctrl-rework-v5-9-9d26da3ce903@oss.qualcomm.com --- drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c | 3 +++ drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c | 22 ++++++++++++++++------ drivers/pci/pwrctrl/slot.c | 3 +++ include/linux/pci-pwrctrl.h | 4 ++++ 4 files changed, 26 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c b/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c index 9b698c5426c5..23ee1a9e7f77 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-pwrseq.c @@ -111,6 +111,9 @@ static int pwrseq_pwrctrl_probe(struct platform_device *pdev) if (ret) return ret; + pwrseq->pwrctrl.power_on = pwrseq_pwrctrl_power_on; + pwrseq->pwrctrl.power_off = pwrseq_pwrctrl_power_off; + pci_pwrctrl_init(&pwrseq->pwrctrl, dev); ret = devm_pci_pwrctrl_device_set_ready(dev, &pwrseq->pwrctrl); diff --git a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c index af3bd1d01ad9..23095d8ecebc 100644 --- a/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c +++ b/drivers/pci/pwrctrl/pci-pwrctrl-tc9563.c @@ -450,15 +450,22 @@ static int tc9563_pwrctrl_parse_device_dt(struct tc9563_pwrctrl *tc9563, return 0; } -static void tc9563_pwrctrl_power_off(struct tc9563_pwrctrl *tc9563) +static int tc9563_pwrctrl_power_off(struct pci_pwrctrl *pwrctrl) { + struct tc9563_pwrctrl *tc9563 = container_of(pwrctrl, + struct tc9563_pwrctrl, pwrctrl); + gpiod_set_value(tc9563->reset_gpio, 1); regulator_bulk_disable(ARRAY_SIZE(tc9563->supplies), tc9563->supplies); + + return 0; } -static int tc9563_pwrctrl_bring_up(struct tc9563_pwrctrl *tc9563) +static int tc9563_pwrctrl_power_on(struct pci_pwrctrl *pwrctrl) { + struct tc9563_pwrctrl *tc9563 = container_of(pwrctrl, + struct tc9563_pwrctrl, pwrctrl); struct device *dev = tc9563->pwrctrl.dev; struct tc9563_pwrctrl_cfg *cfg; int ret, i; @@ -520,7 +527,7 @@ static int tc9563_pwrctrl_bring_up(struct tc9563_pwrctrl *tc9563) return 0; power_off: - tc9563_pwrctrl_power_off(tc9563); + tc9563_pwrctrl_power_off(&tc9563->pwrctrl); return ret; } @@ -613,7 +620,7 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) goto remove_i2c; } - ret = tc9563_pwrctrl_bring_up(tc9563); + ret = tc9563_pwrctrl_power_on(&tc9563->pwrctrl); if (ret) goto remove_i2c; @@ -623,6 +630,9 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) goto power_off; } + tc9563->pwrctrl.power_on = tc9563_pwrctrl_power_on; + tc9563->pwrctrl.power_off = tc9563_pwrctrl_power_off; + ret = devm_pci_pwrctrl_device_set_ready(dev, &tc9563->pwrctrl); if (ret) goto power_off; @@ -632,7 +642,7 @@ static int tc9563_pwrctrl_probe(struct platform_device *pdev) return 0; power_off: - tc9563_pwrctrl_power_off(tc9563); + tc9563_pwrctrl_power_off(&tc9563->pwrctrl); remove_i2c: i2c_unregister_device(tc9563->client); put_device(&tc9563->adapter->dev); @@ -643,7 +653,7 @@ static void tc9563_pwrctrl_remove(struct platform_device *pdev) { struct tc9563_pwrctrl *tc9563 = platform_get_drvdata(pdev); - tc9563_pwrctrl_power_off(tc9563); + tc9563_pwrctrl_power_off(&tc9563->pwrctrl); i2c_unregister_device(tc9563->client); put_device(&tc9563->adapter->dev); } diff --git a/drivers/pci/pwrctrl/slot.c b/drivers/pci/pwrctrl/slot.c index 094c7df454fc..71aaddd24253 100644 --- a/drivers/pci/pwrctrl/slot.c +++ b/drivers/pci/pwrctrl/slot.c @@ -85,6 +85,9 @@ static int slot_pwrctrl_probe(struct platform_device *pdev) slot_pwrctrl_power_on(&slot->pwrctrl); + slot->pwrctrl.power_on = slot_pwrctrl_power_on; + slot->pwrctrl.power_off = slot_pwrctrl_power_off; + pci_pwrctrl_init(&slot->pwrctrl, dev); ret = devm_pci_pwrctrl_device_set_ready(dev, &slot->pwrctrl); diff --git a/include/linux/pci-pwrctrl.h b/include/linux/pci-pwrctrl.h index 4aefc7901cd1..435b822c841e 100644 --- a/include/linux/pci-pwrctrl.h +++ b/include/linux/pci-pwrctrl.h @@ -31,6 +31,8 @@ struct device_link; /** * struct pci_pwrctrl - PCI device power control context. * @dev: Address of the power controlling device. + * @power_on: Callback to power on the power controlling device. + * @power_off: Callback to power off the power controlling device. * * An object of this type must be allocated by the PCI power control device and * passed to the pwrctrl subsystem to trigger a bus rescan and setup a device @@ -38,6 +40,8 @@ struct device_link; */ struct pci_pwrctrl { struct device *dev; + int (*power_on)(struct pci_pwrctrl *pwrctrl); + int (*power_off)(struct pci_pwrctrl *pwrctrl); /* private: internal use only */ struct notifier_block nb; -- cgit v1.2.3 From 4c413248920106393b35b5e014e6ac525405437e Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Thu, 15 Jan 2026 12:59:02 +0530 Subject: PCI/pwrctrl: Add APIs to create, destroy pwrctrl devices Previously, the PCI core created pwrctrl devices during pci_scan_device() on its own and then skipped enumeration of those devices, hoping the pwrctrl driver would power them on and trigger a bus rescan. This approach works for endpoint devices directly connected to Root Ports, but it fails for PCIe switches acting as bus extenders. When the switch requires pwrctrl support and the pwrctrl driver is not available during the pwrctrl device creation, its enumeration will be skipped during the initial PCI bus scan. This premature scan leads the PCI core to allocate resources (bridge windows, bus numbers) for the upstream bridge based on available downstream buses at scan time. For non-hotplug capable bridges, PCI core typically allocates resources based on the number of buses available during the initial bus scan, which happens to be just one if the switch is not powered on and enumerated at that time. When the switch gets enumerated later on, it will fail due to the lack of upstream resources. As a result, a PCIe switch powered on by the pwrctrl driver cannot be reliably enumerated currently. Either the switch has to be enabled in the bootloader or the switch pwrctrl driver has to be loaded during the pwrctrl device creation time to work around these issues. Introduce new APIs to explicitly create and destroy pwrctrl devices from controller drivers by recursively scanning the PCI child nodes of the controller. These APIs allow creating pwrctrl devices based on the original criteria and are intended to be called during controller probe and removal. These APIs, together with the upcoming APIs for power on/off will allow the controller drivers to power on all the devices before starting the initial bus scan, thereby solving the resource allocation issue. Signed-off-by: Krishna Chaitanya Chundru [mani: splitted the patch, cleaned up the code, and rewrote description] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Chen-Yu Tsai Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260115-pci-pwrctrl-rework-v5-10-9d26da3ce903@oss.qualcomm.com --- drivers/pci/of.c | 1 + drivers/pci/pwrctrl/core.c | 114 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/pci-pwrctrl.h | 8 +++- 3 files changed, 122 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/pci/of.c b/drivers/pci/of.c index 3579265f1198..9bb5f258759b 100644 --- a/drivers/pci/of.c +++ b/drivers/pci/of.c @@ -867,6 +867,7 @@ bool of_pci_supply_present(struct device_node *np) return false; } +EXPORT_SYMBOL_GPL(of_pci_supply_present); #endif /* CONFIG_PCI */ diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 6bdbfed584d6..b423768cc477 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -3,14 +3,21 @@ * Copyright (C) 2024 Linaro Ltd. */ +#define dev_fmt(fmt) "pwrctrl: " fmt + #include #include #include +#include +#include #include #include +#include #include #include +#include "../pci.h" + static int pci_pwrctrl_notify(struct notifier_block *nb, unsigned long action, void *data) { @@ -145,6 +152,113 @@ int devm_pci_pwrctrl_device_set_ready(struct device *dev, } EXPORT_SYMBOL_GPL(devm_pci_pwrctrl_device_set_ready); +static int pci_pwrctrl_create_device(struct device_node *np, + struct device *parent) +{ + struct platform_device *pdev; + int ret; + + for_each_available_child_of_node_scoped(np, child) { + ret = pci_pwrctrl_create_device(child, parent); + if (ret) + return ret; + } + + /* Bail out if the platform device is already available for the node */ + pdev = of_find_device_by_node(np); + if (pdev) { + platform_device_put(pdev); + return 0; + } + + /* + * Sanity check to make sure that the node has the compatible property + * to allow driver binding. + */ + if (!of_property_present(np, "compatible")) + return 0; + + /* + * Check whether the pwrctrl device really needs to be created or not. + * This is decided based on at least one of the power supplies being + * defined in the devicetree node of the device. + */ + if (!of_pci_supply_present(np)) { + dev_dbg(parent, "Skipping OF node: %s\n", np->name); + return 0; + } + + /* Now create the pwrctrl device */ + pdev = of_platform_device_create(np, NULL, parent); + if (!pdev) { + dev_err(parent, "Failed to create pwrctrl device for node: %s\n", np->name); + return -EINVAL; + } + + return 0; +} + +/** + * pci_pwrctrl_create_devices - Create pwrctrl devices + * + * @parent: PCI host controller device + * + * Recursively create pwrctrl devices for the devicetree hierarchy below + * the specified PCI host controller in a depth first manner. On error, all + * created devices will be destroyed. + * + * Return: 0 on success, negative error number on error. + */ +int pci_pwrctrl_create_devices(struct device *parent) +{ + int ret; + + for_each_available_child_of_node_scoped(parent->of_node, child) { + ret = pci_pwrctrl_create_device(child, parent); + if (ret) { + pci_pwrctrl_destroy_devices(parent); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(pci_pwrctrl_create_devices); + +static void pci_pwrctrl_destroy_device(struct device_node *np) +{ + struct platform_device *pdev; + + for_each_available_child_of_node_scoped(np, child) + pci_pwrctrl_destroy_device(child); + + pdev = of_find_device_by_node(np); + if (!pdev) + return; + + of_device_unregister(pdev); + platform_device_put(pdev); + + of_node_clear_flag(np, OF_POPULATED); +} + +/** + * pci_pwrctrl_destroy_devices - Destroy pwrctrl devices + * + * @parent: PCI host controller device + * + * Recursively destroy pwrctrl devices for the devicetree hierarchy below + * the specified PCI host controller in a depth first manner. + */ +void pci_pwrctrl_destroy_devices(struct device *parent) +{ + struct device_node *np = parent->of_node; + + for_each_available_child_of_node_scoped(np, child) + pci_pwrctrl_destroy_device(child); +} +EXPORT_SYMBOL_GPL(pci_pwrctrl_destroy_devices); + MODULE_AUTHOR("Bartosz Golaszewski "); MODULE_DESCRIPTION("PCI Device Power Control core driver"); MODULE_LICENSE("GPL"); diff --git a/include/linux/pci-pwrctrl.h b/include/linux/pci-pwrctrl.h index 435b822c841e..44f66872d090 100644 --- a/include/linux/pci-pwrctrl.h +++ b/include/linux/pci-pwrctrl.h @@ -54,5 +54,11 @@ int pci_pwrctrl_device_set_ready(struct pci_pwrctrl *pwrctrl); void pci_pwrctrl_device_unset_ready(struct pci_pwrctrl *pwrctrl); int devm_pci_pwrctrl_device_set_ready(struct device *dev, struct pci_pwrctrl *pwrctrl); - +#if IS_ENABLED(CONFIG_PCI_PWRCTRL) +int pci_pwrctrl_create_devices(struct device *parent); +void pci_pwrctrl_destroy_devices(struct device *parent); +#else +static inline int pci_pwrctrl_create_devices(struct device *parent) { return 0; } +static void pci_pwrctrl_destroy_devices(struct device *parent) { } +#endif #endif /* __PCI_PWRCTRL_H__ */ -- cgit v1.2.3 From b35cf3b6aa1eb08909f58c2d1e2f8ef7d000809a Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 15 Jan 2026 12:59:03 +0530 Subject: PCI/pwrctrl: Add APIs to power on/off pwrctrl devices To fix bridge resource allocation issues when powering PCI bridges with the pwrctrl driver, introduce APIs to explicitly power on and off all related devices simultaneously. Previously, the individual pwrctrl drivers powered on/off the PCI devices autonomously, without any control from the controller drivers. But to enforce ordering with respect to powering on the devices, these APIs will power on/off all the devices at the same time. The pci_pwrctrl_power_on_devices() API recursively scans the PCI child nodes, makes sure that pwrctrl drivers are bound to devices, and calls their power_on() callbacks. If any pwrctrl driver is not bound, it will return -EPROBE_DEFER. Similarly, pci_pwrctrl_power_off_devices() API powers off devices recursively via their power_off() callbacks. These APIs are expected to be called during the controller probe and suspend/resume time to power on/off the devices. But before calling these APIs, the pwrctrl devices should be created using the pci_pwrctrl_{create/destroy}_devices() APIs. Co-developed-by: Krishna Chaitanya Chundru Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Chen-Yu Tsai Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260115-pci-pwrctrl-rework-v5-11-9d26da3ce903@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 130 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/pci-pwrctrl.h | 4 ++ 2 files changed, 134 insertions(+) (limited to 'include') diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index b423768cc477..fef5243d9445 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -65,6 +65,7 @@ void pci_pwrctrl_init(struct pci_pwrctrl *pwrctrl, struct device *dev) { pwrctrl->dev = dev; INIT_WORK(&pwrctrl->work, rescan_work_func); + dev_set_drvdata(dev, pwrctrl); } EXPORT_SYMBOL_GPL(pci_pwrctrl_init); @@ -152,6 +153,135 @@ int devm_pci_pwrctrl_device_set_ready(struct device *dev, } EXPORT_SYMBOL_GPL(devm_pci_pwrctrl_device_set_ready); +static int __pci_pwrctrl_power_off_device(struct device *dev) +{ + struct pci_pwrctrl *pwrctrl = dev_get_drvdata(dev); + + if (!pwrctrl) + return 0; + + return pwrctrl->power_off(pwrctrl); +} + +static void pci_pwrctrl_power_off_device(struct device_node *np) +{ + struct platform_device *pdev; + int ret; + + for_each_available_child_of_node_scoped(np, child) + pci_pwrctrl_power_off_device(child); + + pdev = of_find_device_by_node(np); + if (!pdev) + return; + + if (device_is_bound(&pdev->dev)) { + ret = __pci_pwrctrl_power_off_device(&pdev->dev); + if (ret) + dev_err(&pdev->dev, "Failed to power off device: %d", ret); + } + + platform_device_put(pdev); +} + +/** + * pci_pwrctrl_power_off_devices - Power off pwrctrl devices + * + * @parent: PCI host controller device + * + * Recursively traverse all pwrctrl devices for the devicetree hierarchy + * below the specified PCI host controller and power them off in a depth + * first manner. + */ +void pci_pwrctrl_power_off_devices(struct device *parent) +{ + struct device_node *np = parent->of_node; + + for_each_available_child_of_node_scoped(np, child) + pci_pwrctrl_power_off_device(child); +} +EXPORT_SYMBOL_GPL(pci_pwrctrl_power_off_devices); + +static int __pci_pwrctrl_power_on_device(struct device *dev) +{ + struct pci_pwrctrl *pwrctrl = dev_get_drvdata(dev); + + if (!pwrctrl) + return 0; + + return pwrctrl->power_on(pwrctrl); +} + +/* + * Power on the devices in a depth first manner. Before powering on the device, + * make sure its driver is bound. + */ +static int pci_pwrctrl_power_on_device(struct device_node *np) +{ + struct platform_device *pdev; + int ret; + + for_each_available_child_of_node_scoped(np, child) { + ret = pci_pwrctrl_power_on_device(child); + if (ret) + return ret; + } + + pdev = of_find_device_by_node(np); + if (!pdev) + return 0; + + if (device_is_bound(&pdev->dev)) { + ret = __pci_pwrctrl_power_on_device(&pdev->dev); + } else { + /* FIXME: Use blocking wait instead of probe deferral */ + dev_dbg(&pdev->dev, "driver is not bound\n"); + ret = -EPROBE_DEFER; + } + + platform_device_put(pdev); + + return ret; +} + +/** + * pci_pwrctrl_power_on_devices - Power on pwrctrl devices + * + * @parent: PCI host controller device + * + * Recursively traverse all pwrctrl devices for the devicetree hierarchy + * below the specified PCI host controller and power them on in a depth + * first manner. On error, all powered on devices will be powered off. + * + * Return: 0 on success, -EPROBE_DEFER if any pwrctrl driver is not bound, an + * appropriate error code otherwise. + */ +int pci_pwrctrl_power_on_devices(struct device *parent) +{ + struct device_node *np = parent->of_node; + struct device_node *child = NULL; + int ret; + + for_each_available_child_of_node(np, child) { + ret = pci_pwrctrl_power_on_device(child); + if (ret) + goto err_power_off; + } + + return 0; + +err_power_off: + for_each_available_child_of_node_scoped(np, tmp) { + if (tmp == child) + break; + pci_pwrctrl_power_off_device(tmp); + } + of_node_put(child); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_pwrctrl_power_on_devices); + static int pci_pwrctrl_create_device(struct device_node *np, struct device *parent) { diff --git a/include/linux/pci-pwrctrl.h b/include/linux/pci-pwrctrl.h index 44f66872d090..1192a2527521 100644 --- a/include/linux/pci-pwrctrl.h +++ b/include/linux/pci-pwrctrl.h @@ -57,8 +57,12 @@ int devm_pci_pwrctrl_device_set_ready(struct device *dev, #if IS_ENABLED(CONFIG_PCI_PWRCTRL) int pci_pwrctrl_create_devices(struct device *parent); void pci_pwrctrl_destroy_devices(struct device *parent); +int pci_pwrctrl_power_on_devices(struct device *parent); +void pci_pwrctrl_power_off_devices(struct device *parent); #else static inline int pci_pwrctrl_create_devices(struct device *parent) { return 0; } static void pci_pwrctrl_destroy_devices(struct device *parent) { } +static inline int pci_pwrctrl_power_on_devices(struct device *parent) { return 0; } +static void pci_pwrctrl_power_off_devices(struct device *parent) { } #endif #endif /* __PCI_PWRCTRL_H__ */ -- cgit v1.2.3 From 54786d9806b2720659b4fc64af0ebde148780229 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 15 Jan 2026 12:59:06 +0530 Subject: PCI: Drop the assert_perst() callback Now since all .assert_callback() implementations have been removed from the controller drivers, drop the .assert_callback callback from pci.h. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260115-pci-pwrctrl-rework-v5-14-9d26da3ce903@oss.qualcomm.com --- include/linux/pci.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include') diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..3eb8fd975ad9 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -854,7 +854,6 @@ struct pci_ops { void __iomem *(*map_bus)(struct pci_bus *bus, unsigned int devfn, int where); int (*read)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val); int (*write)(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val); - int (*assert_perst)(struct pci_bus *bus, bool assert); }; /* -- cgit v1.2.3 From 2aa7c47a681f54b5cf4d98e13447be2ea5fb5af3 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 19 Dec 2025 19:40:27 +0200 Subject: resource: Mark res given to resource_assigned() as const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caller may hold a const struct resource which will trigger an unnecessary warning when calling resource_assigned() as it will not modify res in any way. Mark resource_assigned()'s struct resource *res parameter const to avoid the compiler warning. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251219174036.16738-15-ilpo.jarvinen@linux.intel.com --- include/linux/ioport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 9afa30f9346f..60ca6a49839c 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -338,7 +338,7 @@ static inline bool resource_union(const struct resource *r1, const struct resour * Check if this resource is added to a resource tree or detached. Caller is * responsible for not racing assignment. */ -static inline bool resource_assigned(struct resource *res) +static inline bool resource_assigned(const struct resource *res) { return res->parent; } -- cgit v1.2.3 From fd29d4ea09baa54b87f7ec7278768d0db00382a8 Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 19 Dec 2025 19:40:30 +0200 Subject: PCI: Separate CardBus setup & build it only with CONFIG_CARDBUS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCI bridge window setup code includes special code to handle CardBus bridges. CardBus has long since fallen out of favor and modern systems have no use for it. Move CardBus setup code to its own file and use existing CONFIG_CARDBUS to decide whether it should be built or not. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251219174036.16738-18-ilpo.jarvinen@linux.intel.com --- drivers/pci/Makefile | 1 + drivers/pci/pci.h | 23 +++++- drivers/pci/setup-bus.c | 171 ++---------------------------------------- drivers/pci/setup-cardbus.c | 167 +++++++++++++++++++++++++++++++++++++++++ drivers/pcmcia/yenta_socket.c | 2 +- include/linux/pci.h | 6 +- 6 files changed, 202 insertions(+), 168 deletions(-) create mode 100644 drivers/pci/setup-cardbus.c (limited to 'include') diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index e10cfe5a280b..8922f90afecb 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_PCI_TSM) += tsm.o obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o +obj-$(CONFIG_CARDBUS) += setup-cardbus.o # Endpoint library must be initialized before its users obj-$(CONFIG_PCI_ENDPOINT) += endpoint/ diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index c27144af550f..2340e9df05c2 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -379,6 +379,23 @@ extern unsigned long pci_hotplug_bus_size; extern unsigned long pci_cardbus_io_size; extern unsigned long pci_cardbus_mem_size; +#ifdef CONFIG_CARDBUS +unsigned long pci_cardbus_resource_alignment(struct resource *res); +int pci_bus_size_cardbus_bridge(struct pci_bus *bus, + struct list_head *realloc_head); + +#else +static inline unsigned long pci_cardbus_resource_alignment(struct resource *res) +{ + return 0; +} +static inline int pci_bus_size_cardbus_bridge(struct pci_bus *bus, + struct list_head *realloc_head) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_CARDBUS */ + /** * pci_match_one_device - Tell if a PCI device structure has a matching * PCI device id structure @@ -440,6 +457,10 @@ void __pci_size_stdbars(struct pci_dev *dev, int count, int __pci_read_base(struct pci_dev *dev, enum pci_bar_type type, struct resource *res, unsigned int reg, u32 *sizes); void pci_configure_ari(struct pci_dev *dev); + +int pci_dev_res_add_to_list(struct list_head *head, struct pci_dev *dev, + struct resource *res, resource_size_t add_size, + resource_size_t min_align); void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head); void __pci_bus_assign_resources(const struct pci_bus *bus, @@ -929,8 +950,6 @@ static inline void pci_suspend_ptm(struct pci_dev *dev) { } static inline void pci_resume_ptm(struct pci_dev *dev) { } #endif -unsigned long pci_cardbus_resource_alignment(struct resource *); - static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, struct resource *res) { diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 3cc26fede31a..e680f75a5b5e 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -67,10 +67,9 @@ static void pci_dev_res_free_list(struct list_head *head) * @add_size: Additional size to be optionally added to the resource * @min_align: Minimum memory window alignment */ -static int pci_dev_res_add_to_list(struct list_head *head, struct pci_dev *dev, - struct resource *res, - resource_size_t add_size, - resource_size_t min_align) +int pci_dev_res_add_to_list(struct list_head *head, struct pci_dev *dev, + struct resource *res, resource_size_t add_size, + resource_size_t min_align) { struct pci_dev_resource *tmp; @@ -773,61 +772,6 @@ static void pbus_assign_resources_sorted(const struct pci_bus *bus, __assign_resources_sorted(&head, realloc_head, fail_head); } -void pci_setup_cardbus(struct pci_bus *bus) -{ - struct pci_dev *bridge = bus->self; - struct resource *res; - struct pci_bus_region region; - - pci_info(bridge, "CardBus bridge to %pR\n", - &bus->busn_res); - - res = bus->resource[0]; - pcibios_resource_to_bus(bridge->bus, ®ion, res); - if (resource_assigned(res) && res->flags & IORESOURCE_IO) { - /* - * The IO resource is allocated a range twice as large as it - * would normally need. This allows us to set both IO regs. - */ - pci_info(bridge, " bridge window %pR\n", res); - pci_write_config_dword(bridge, PCI_CB_IO_BASE_0, - region.start); - pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_0, - region.end); - } - - res = bus->resource[1]; - pcibios_resource_to_bus(bridge->bus, ®ion, res); - if (resource_assigned(res) && res->flags & IORESOURCE_IO) { - pci_info(bridge, " bridge window %pR\n", res); - pci_write_config_dword(bridge, PCI_CB_IO_BASE_1, - region.start); - pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_1, - region.end); - } - - res = bus->resource[2]; - pcibios_resource_to_bus(bridge->bus, ®ion, res); - if (resource_assigned(res) && res->flags & IORESOURCE_MEM) { - pci_info(bridge, " bridge window %pR\n", res); - pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_0, - region.start); - pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_0, - region.end); - } - - res = bus->resource[3]; - pcibios_resource_to_bus(bridge->bus, ®ion, res); - if (resource_assigned(res) && res->flags & IORESOURCE_MEM) { - pci_info(bridge, " bridge window %pR\n", res); - pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_1, - region.start); - pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_1, - region.end); - } -} -EXPORT_SYMBOL(pci_setup_cardbus); - /* * Initialize bridges with base/limit values we have collected. PCI-to-PCI * Bridge Architecture Specification rev. 1.1 (1998) requires that if there @@ -1424,108 +1368,6 @@ static void pbus_size_mem(struct pci_bus *bus, struct resource *b_res, } } -unsigned long pci_cardbus_resource_alignment(struct resource *res) -{ - if (res->flags & IORESOURCE_IO) - return pci_cardbus_io_size; - if (res->flags & IORESOURCE_MEM) - return pci_cardbus_mem_size; - return 0; -} - -static void pci_bus_size_cardbus(struct pci_bus *bus, - struct list_head *realloc_head) -{ - struct pci_dev *bridge = bus->self; - struct resource *b_res; - resource_size_t b_res_3_size = pci_cardbus_mem_size * 2; - u16 ctrl; - - b_res = &bridge->resource[PCI_CB_BRIDGE_IO_0_WINDOW]; - if (resource_assigned(b_res)) - goto handle_b_res_1; - /* - * Reserve some resources for CardBus. We reserve a fixed amount - * of bus space for CardBus bridges. - */ - resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size); - b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; - if (realloc_head) { - b_res->end -= pci_cardbus_io_size; - pci_dev_res_add_to_list(realloc_head, bridge, b_res, - pci_cardbus_io_size, - pci_cardbus_io_size); - } - -handle_b_res_1: - b_res = &bridge->resource[PCI_CB_BRIDGE_IO_1_WINDOW]; - if (resource_assigned(b_res)) - goto handle_b_res_2; - resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size); - b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; - if (realloc_head) { - b_res->end -= pci_cardbus_io_size; - pci_dev_res_add_to_list(realloc_head, bridge, b_res, - pci_cardbus_io_size, - pci_cardbus_io_size); - } - -handle_b_res_2: - /* MEM1 must not be pref MMIO */ - pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); - if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM1) { - ctrl &= ~PCI_CB_BRIDGE_CTL_PREFETCH_MEM1; - pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); - pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); - } - - /* Check whether prefetchable memory is supported by this bridge. */ - pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); - if (!(ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0)) { - ctrl |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM0; - pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); - pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); - } - - b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_0_WINDOW]; - if (resource_assigned(b_res)) - goto handle_b_res_3; - /* - * If we have prefetchable memory support, allocate two regions. - * Otherwise, allocate one region of twice the size. - */ - if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) { - resource_set_range(b_res, pci_cardbus_mem_size, - pci_cardbus_mem_size); - b_res->flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | - IORESOURCE_STARTALIGN; - if (realloc_head) { - b_res->end -= pci_cardbus_mem_size; - pci_dev_res_add_to_list(realloc_head, bridge, b_res, - pci_cardbus_mem_size, - pci_cardbus_mem_size); - } - - /* Reduce that to half */ - b_res_3_size = pci_cardbus_mem_size; - } - -handle_b_res_3: - b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_1_WINDOW]; - if (resource_assigned(b_res)) - goto handle_done; - resource_set_range(b_res, pci_cardbus_mem_size, b_res_3_size); - b_res->flags |= IORESOURCE_MEM | IORESOURCE_STARTALIGN; - if (realloc_head) { - b_res->end -= b_res_3_size; - pci_dev_res_add_to_list(realloc_head, bridge, b_res, - b_res_3_size, pci_cardbus_mem_size); - } - -handle_done: - ; -} - void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head) { struct pci_dev *dev; @@ -1542,7 +1384,8 @@ void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head) switch (dev->hdr_type) { case PCI_HEADER_TYPE_CARDBUS: - pci_bus_size_cardbus(b, realloc_head); + if (pci_bus_size_cardbus_bridge(b, realloc_head)) + continue; break; case PCI_HEADER_TYPE_BRIDGE: @@ -1666,7 +1509,7 @@ void __pci_bus_assign_resources(const struct pci_bus *bus, break; case PCI_HEADER_TYPE_CARDBUS: - pci_setup_cardbus(b); + pci_setup_cardbus_bridge(b); break; default: @@ -1771,7 +1614,7 @@ static void __pci_bridge_assign_resources(const struct pci_dev *bridge, break; case PCI_CLASS_BRIDGE_CARDBUS: - pci_setup_cardbus(b); + pci_setup_cardbus_bridge(b); break; default: diff --git a/drivers/pci/setup-cardbus.c b/drivers/pci/setup-cardbus.c new file mode 100644 index 000000000000..b017a2039fe1 --- /dev/null +++ b/drivers/pci/setup-cardbus.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Cardbus bridge setup routines. + */ + +#include +#include +#include + +#include "pci.h" + +unsigned long pci_cardbus_resource_alignment(struct resource *res) +{ + if (res->flags & IORESOURCE_IO) + return pci_cardbus_io_size; + if (res->flags & IORESOURCE_MEM) + return pci_cardbus_mem_size; + return 0; +} + +int pci_bus_size_cardbus_bridge(struct pci_bus *bus, + struct list_head *realloc_head) +{ + struct pci_dev *bridge = bus->self; + struct resource *b_res; + resource_size_t b_res_3_size = pci_cardbus_mem_size * 2; + u16 ctrl; + + b_res = &bridge->resource[PCI_CB_BRIDGE_IO_0_WINDOW]; + if (resource_assigned(b_res)) + goto handle_b_res_1; + /* + * Reserve some resources for CardBus. We reserve a fixed amount + * of bus space for CardBus bridges. + */ + resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size); + b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res->end -= pci_cardbus_io_size; + pci_dev_res_add_to_list(realloc_head, bridge, b_res, + pci_cardbus_io_size, + pci_cardbus_io_size); + } + +handle_b_res_1: + b_res = &bridge->resource[PCI_CB_BRIDGE_IO_1_WINDOW]; + if (resource_assigned(b_res)) + goto handle_b_res_2; + resource_set_range(b_res, pci_cardbus_io_size, pci_cardbus_io_size); + b_res->flags |= IORESOURCE_IO | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res->end -= pci_cardbus_io_size; + pci_dev_res_add_to_list(realloc_head, bridge, b_res, + pci_cardbus_io_size, + pci_cardbus_io_size); + } + +handle_b_res_2: + /* MEM1 must not be pref MMIO */ + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM1) { + ctrl &= ~PCI_CB_BRIDGE_CTL_PREFETCH_MEM1; + pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + } + + /* Check whether prefetchable memory is supported by this bridge. */ + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + if (!(ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0)) { + ctrl |= PCI_CB_BRIDGE_CTL_PREFETCH_MEM0; + pci_write_config_word(bridge, PCI_CB_BRIDGE_CONTROL, ctrl); + pci_read_config_word(bridge, PCI_CB_BRIDGE_CONTROL, &ctrl); + } + + b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_0_WINDOW]; + if (resource_assigned(b_res)) + goto handle_b_res_3; + /* + * If we have prefetchable memory support, allocate two regions. + * Otherwise, allocate one region of twice the size. + */ + if (ctrl & PCI_CB_BRIDGE_CTL_PREFETCH_MEM0) { + resource_set_range(b_res, pci_cardbus_mem_size, + pci_cardbus_mem_size); + b_res->flags |= IORESOURCE_MEM | IORESOURCE_PREFETCH | + IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res->end -= pci_cardbus_mem_size; + pci_dev_res_add_to_list(realloc_head, bridge, b_res, + pci_cardbus_mem_size, + pci_cardbus_mem_size); + } + + /* Reduce that to half */ + b_res_3_size = pci_cardbus_mem_size; + } + +handle_b_res_3: + b_res = &bridge->resource[PCI_CB_BRIDGE_MEM_1_WINDOW]; + if (resource_assigned(b_res)) + goto handle_done; + resource_set_range(b_res, pci_cardbus_mem_size, b_res_3_size); + b_res->flags |= IORESOURCE_MEM | IORESOURCE_STARTALIGN; + if (realloc_head) { + b_res->end -= b_res_3_size; + pci_dev_res_add_to_list(realloc_head, bridge, b_res, + b_res_3_size, pci_cardbus_mem_size); + } + +handle_done: + return 0; +} + +void pci_setup_cardbus_bridge(struct pci_bus *bus) +{ + struct pci_dev *bridge = bus->self; + struct resource *res; + struct pci_bus_region region; + + pci_info(bridge, "CardBus bridge to %pR\n", + &bus->busn_res); + + res = bus->resource[0]; + pcibios_resource_to_bus(bridge->bus, ®ion, res); + if (resource_assigned(res) && res->flags & IORESOURCE_IO) { + /* + * The IO resource is allocated a range twice as large as it + * would normally need. This allows us to set both IO regs. + */ + pci_info(bridge, " bridge window %pR\n", res); + pci_write_config_dword(bridge, PCI_CB_IO_BASE_0, + region.start); + pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_0, + region.end); + } + + res = bus->resource[1]; + pcibios_resource_to_bus(bridge->bus, ®ion, res); + if (resource_assigned(res) && res->flags & IORESOURCE_IO) { + pci_info(bridge, " bridge window %pR\n", res); + pci_write_config_dword(bridge, PCI_CB_IO_BASE_1, + region.start); + pci_write_config_dword(bridge, PCI_CB_IO_LIMIT_1, + region.end); + } + + res = bus->resource[2]; + pcibios_resource_to_bus(bridge->bus, ®ion, res); + if (resource_assigned(res) && res->flags & IORESOURCE_MEM) { + pci_info(bridge, " bridge window %pR\n", res); + pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_0, + region.start); + pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_0, + region.end); + } + + res = bus->resource[3]; + pcibios_resource_to_bus(bridge->bus, ®ion, res); + if (resource_assigned(res) && res->flags & IORESOURCE_MEM) { + pci_info(bridge, " bridge window %pR\n", res); + pci_write_config_dword(bridge, PCI_CB_MEMORY_BASE_1, + region.start); + pci_write_config_dword(bridge, PCI_CB_MEMORY_LIMIT_1, + region.end); + } +} +EXPORT_SYMBOL(pci_setup_cardbus_bridge); diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c index 923ed23570a0..34c4eaee7dfc 100644 --- a/drivers/pcmcia/yenta_socket.c +++ b/drivers/pcmcia/yenta_socket.c @@ -779,7 +779,7 @@ static void yenta_allocate_resources(struct yenta_socket *socket) IORESOURCE_MEM, PCI_CB_MEMORY_BASE_1, PCI_CB_MEMORY_LIMIT_1); if (program) - pci_setup_cardbus(socket->dev->subordinate); + pci_setup_cardbus_bridge(socket->dev->subordinate); } diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..ddec80c92816 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1243,7 +1243,11 @@ void pci_stop_and_remove_bus_device(struct pci_dev *dev); void pci_stop_and_remove_bus_device_locked(struct pci_dev *dev); void pci_stop_root_bus(struct pci_bus *bus); void pci_remove_root_bus(struct pci_bus *bus); -void pci_setup_cardbus(struct pci_bus *bus); +#ifdef CONFIG_CARDBUS +void pci_setup_cardbus_bridge(struct pci_bus *bus); +#else +static inline void pci_setup_cardbus_bridge(struct pci_bus *bus) { } +#endif void pcibios_setup_bridge(struct pci_bus *bus, unsigned long type); void pci_sort_breadthfirst(void); #define dev_is_pci(d) ((d)->bus == &pci_bus_type) -- cgit v1.2.3 From cad3337bb6c3a2ba2307d6a9061e752e15681d2b Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 19 Dec 2025 19:40:33 +0200 Subject: PCI: Add dword #defines for Bus Number + Secondary Latency Timer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uapi/linux/pci_regs.h defines Primary/Secondary/Subordinate Bus Numbers and Secondary Latency Timer (PCIe r7.0, sec. 7.5.1.3) as byte register offsets, but in practice the code may read/write the entire dword. In the lack of #defines to handle the dword fields, the code ends up using literals which are not as easy to read. Add dword field masks for the Bus Number and Secondary Latency Timer fields and use them in probe.c. Signed-off-by: Ilpo Järvinen [bhelgaas: squash new #defines and uses together] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251219174036.16738-21-ilpo.jarvinen@linux.intel.com Link: https://patch.msgid.link/20251219174036.16738-22-ilpo.jarvinen@linux.intel.com --- drivers/pci/probe.c | 25 +++++++++++++------------ include/uapi/linux/pci_regs.h | 5 +++++ 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ed4d26833640..53ec1879fb99 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -524,8 +524,8 @@ static void pci_read_bridge_windows(struct pci_dev *bridge) pci_read_config_dword(bridge, PCI_PRIMARY_BUS, &buses); res.flags = IORESOURCE_BUS; - res.start = (buses >> 8) & 0xff; - res.end = (buses >> 16) & 0xff; + res.start = FIELD_GET(PCI_SECONDARY_BUS_MASK, buses); + res.end = FIELD_GET(PCI_SUBORDINATE_BUS_MASK, buses); pci_info(bridge, "PCI bridge to %pR%s\n", &res, bridge->transparent ? " (subtractive decode)" : ""); @@ -1393,9 +1393,9 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, pm_runtime_get_sync(&dev->dev); pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses); - primary = buses & 0xFF; - secondary = (buses >> 8) & 0xFF; - subordinate = (buses >> 16) & 0xFF; + primary = FIELD_GET(PCI_PRIMARY_BUS_MASK, buses); + secondary = FIELD_GET(PCI_SECONDARY_BUS_MASK, buses); + subordinate = FIELD_GET(PCI_SUBORDINATE_BUS_MASK, buses); pci_dbg(dev, "scanning [bus %02x-%02x] behind bridge, pass %d\n", secondary, subordinate, pass); @@ -1476,7 +1476,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, * ranges. */ pci_write_config_dword(dev, PCI_PRIMARY_BUS, - buses & ~0xffffff); + buses & PCI_SEC_LATENCY_TIMER_MASK); goto out; } @@ -1507,18 +1507,19 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, if (available_buses) available_buses--; - buses = (buses & 0xff000000) - | ((unsigned int)(child->primary) << 0) - | ((unsigned int)(child->busn_res.start) << 8) - | ((unsigned int)(child->busn_res.end) << 16); + buses = (buses & PCI_SEC_LATENCY_TIMER_MASK) | + FIELD_PREP(PCI_PRIMARY_BUS_MASK, child->primary) | + FIELD_PREP(PCI_SECONDARY_BUS_MASK, child->busn_res.start) | + FIELD_PREP(PCI_SUBORDINATE_BUS_MASK, child->busn_res.end); /* * yenta.c forces a secondary latency timer of 176. * Copy that behaviour here. */ if (is_cardbus) { - buses &= ~0xff000000; - buses |= CARDBUS_LATENCY_TIMER << 24; + buses &= ~PCI_SEC_LATENCY_TIMER_MASK; + buses |= FIELD_PREP(PCI_SEC_LATENCY_TIMER_MASK, + CARDBUS_LATENCY_TIMER); } /* We need to blast all three values with a single write */ diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 3add74ae2594..8be55ece2a21 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -132,6 +132,11 @@ #define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */ #define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */ #define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */ +/* Masks for dword-sized processing of Bus Number and Sec Latency Timer fields */ +#define PCI_PRIMARY_BUS_MASK 0x000000ff +#define PCI_SECONDARY_BUS_MASK 0x0000ff00 +#define PCI_SUBORDINATE_BUS_MASK 0x00ff0000 +#define PCI_SEC_LATENCY_TIMER_MASK 0xff000000 #define PCI_IO_BASE 0x1c /* I/O range behind the bridge */ #define PCI_IO_LIMIT 0x1d #define PCI_IO_RANGE_TYPE_MASK 0x0fUL /* I/O bridging type */ -- cgit v1.2.3 From 06a81c5940e46cc7bddee28f16bdd29a12a76344 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:05 +0900 Subject: PCI: endpoint: Add dynamic_inbound_mapping EPC feature Introduce a new EPC feature bit (dynamic_inbound_mapping) that indicates whether an Endpoint Controller can update the inbound address translation for a BAR without requiring the EPF driver to clear/reset the BAR first. Endpoint Function drivers (e.g. vNTB) can use this information to decide whether it really is safe to call pci_epc_set_bar() multiple times to update inbound mappings for the BAR. Suggested-by: Niklas Cassel Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260124145012.2794108-2-den@valinux.co.jp --- include/linux/pci-epc.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include') diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 4286bfdbfdfa..4c8516756c56 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -223,6 +223,10 @@ struct pci_epc_bar_desc { /** * 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 + * @dynamic_inbound_mapping: indicate if the EPC device supports updating + * inbound mappings for an already configured BAR + * (i.e. allow calling pci_epc_set_bar() again + * without first calling pci_epc_clear_bar()) * @msi_capable: indicate if the endpoint function has MSI capability * @msix_capable: indicate if the endpoint function has MSI-X capability * @intx_capable: indicate if the endpoint can raise INTx interrupts @@ -231,6 +235,7 @@ struct pci_epc_bar_desc { */ struct pci_epc_features { unsigned int linkup_notifier : 1; + unsigned int dynamic_inbound_mapping : 1; unsigned int msi_capable : 1; unsigned int msix_capable : 1; unsigned int intx_capable : 1; -- cgit v1.2.3 From 31fb95400451040050361e22ff480476964280f0 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:06 +0900 Subject: PCI: endpoint: Add BAR subrange mapping support Some endpoint platforms have only a small number of usable BARs. At the same time, EPF drivers (e.g. vNTB) may need multiple independent inbound regions (control/scratchpad, one or more memory windows, and optionally MSI or other feature-related regions). Subrange mapping allows these to share a single BAR without consuming additional BARs that may not be available, or forcing a fragile layout by aggressively packing into a single contiguous memory range. Extend the PCI endpoint core to support mapping subranges within a BAR. Add an optional 'submap' field in struct pci_epf_bar so an endpoint function driver can request inbound mappings that fully cover the BAR. Introduce a new EPC feature bit, subrange_mapping, and reject submap requests from pci_epc_set_bar() unless the controller advertises both subrange_mapping and dynamic_inbound_mapping features. The submap array describes the complete BAR layout (no overlaps and no gaps are allowed to avoid exposing untranslated address ranges). This provides the generic infrastructure needed to map multiple logical regions into a single BAR at different offsets, without assuming a controller-specific inbound address translation mechanism. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260124145012.2794108-3-den@valinux.co.jp --- drivers/pci/endpoint/pci-epc-core.c | 8 ++++++++ include/linux/pci-epc.h | 4 ++++ include/linux/pci-epf.h | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+) (limited to 'include') diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index ca7f19cc973a..068155819c57 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -596,6 +596,14 @@ int pci_epc_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, if (!epc_features) return -EINVAL; + if (epf_bar->num_submap && !epf_bar->submap) + return -EINVAL; + + if (epf_bar->num_submap && + !(epc_features->dynamic_inbound_mapping && + epc_features->subrange_mapping)) + return -EINVAL; + if (epc_features->bar[bar].type == BAR_RESIZABLE && (epf_bar->size < SZ_1M || (u64)epf_bar->size > (SZ_128G * 1024))) return -EINVAL; diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 4c8516756c56..c021c7af175f 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -227,6 +227,9 @@ struct pci_epc_bar_desc { * inbound mappings for an already configured BAR * (i.e. allow calling pci_epc_set_bar() again * without first calling pci_epc_clear_bar()) + * @subrange_mapping: indicate if the EPC device can map inbound subranges for a + * BAR. This feature depends on @dynamic_inbound_mapping + * feature. * @msi_capable: indicate if the endpoint function has MSI capability * @msix_capable: indicate if the endpoint function has MSI-X capability * @intx_capable: indicate if the endpoint can raise INTx interrupts @@ -236,6 +239,7 @@ struct pci_epc_bar_desc { struct pci_epc_features { unsigned int linkup_notifier : 1; unsigned int dynamic_inbound_mapping : 1; + unsigned int subrange_mapping : 1; unsigned int msi_capable : 1; unsigned int msix_capable : 1; unsigned int intx_capable : 1; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 48f68c4dcfa5..7737a7c03260 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -110,6 +110,22 @@ struct pci_epf_driver { #define to_pci_epf_driver(drv) container_of_const((drv), struct pci_epf_driver, driver) +/** + * struct pci_epf_bar_submap - BAR subrange for inbound mapping + * @phys_addr: target physical/DMA address for this subrange + * @size: the size of the subrange to be mapped + * + * When pci_epf_bar.num_submap is >0, pci_epf_bar.submap describes the + * complete BAR layout. This allows an EPC driver to program multiple + * inbound translation windows for a single BAR when supported by the + * controller. The array order defines the BAR layout (submap[0] at offset + * 0, and each immediately follows the previous one). + */ +struct pci_epf_bar_submap { + dma_addr_t phys_addr; + size_t size; +}; + /** * struct pci_epf_bar - represents the BAR of EPF device * @phys_addr: physical address that should be mapped to the BAR @@ -119,6 +135,9 @@ struct pci_epf_driver { * requirement * @barno: BAR number * @flags: flags that are set for the BAR + * @num_submap: number of entries in @submap + * @submap: array of subrange descriptors allocated by the caller. See + * struct pci_epf_bar_submap for the semantics in detail. */ struct pci_epf_bar { dma_addr_t phys_addr; @@ -127,6 +146,10 @@ struct pci_epf_bar { size_t mem_size; enum pci_barno barno; int flags; + + /* Optional sub-range mapping */ + unsigned int num_submap; + struct pci_epf_bar_submap *submap; }; /** -- cgit v1.2.3 From 8cf82bb558517503a81f8e3c49914c0836360aa6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:11 +0900 Subject: misc: pci_endpoint_test: Add BAR subrange mapping test case Add a new PCITEST_BAR_SUBRANGE ioctl to exercise EPC BAR subrange mapping end-to-end. The test programs a simple 2-subrange layout on the endpoint (via pci-epf-test) and verifies that: - the endpoint-provided per-subrange signature bytes are observed at the expected PCIe BAR offsets, and - writes to each subrange are routed to the correct backing region (i.e. the submap order is applied rather than accidentally working due to an identity mapping). Return -EOPNOTSUPP when the endpoint does not advertise subrange mapping, -ENODATA when the BAR is disabled, and -EBUSY when the BAR is reserved for the test register space. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260124145012.2794108-8-den@valinux.co.jp --- drivers/misc/pci_endpoint_test.c | 203 ++++++++++++++++++++++++++++++++++++++- include/uapi/linux/pcitest.h | 1 + 2 files changed, 203 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 1c0fd185114f..74ab5b5b9011 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -39,6 +39,8 @@ #define COMMAND_COPY BIT(5) #define COMMAND_ENABLE_DOORBELL BIT(6) #define COMMAND_DISABLE_DOORBELL BIT(7) +#define COMMAND_BAR_SUBRANGE_SETUP BIT(8) +#define COMMAND_BAR_SUBRANGE_CLEAR BIT(9) #define PCI_ENDPOINT_TEST_STATUS 0x8 #define STATUS_READ_SUCCESS BIT(0) @@ -55,6 +57,10 @@ #define STATUS_DOORBELL_ENABLE_FAIL BIT(11) #define STATUS_DOORBELL_DISABLE_SUCCESS BIT(12) #define STATUS_DOORBELL_DISABLE_FAIL BIT(13) +#define STATUS_BAR_SUBRANGE_SETUP_SUCCESS BIT(14) +#define STATUS_BAR_SUBRANGE_SETUP_FAIL BIT(15) +#define STATUS_BAR_SUBRANGE_CLEAR_SUCCESS BIT(16) +#define STATUS_BAR_SUBRANGE_CLEAR_FAIL BIT(17) #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR 0x0c #define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR 0x10 @@ -77,6 +83,7 @@ #define CAP_MSI BIT(1) #define CAP_MSIX BIT(2) #define CAP_INTX BIT(3) +#define CAP_SUBRANGE_MAPPING BIT(4) #define PCI_ENDPOINT_TEST_DB_BAR 0x34 #define PCI_ENDPOINT_TEST_DB_OFFSET 0x38 @@ -100,6 +107,8 @@ #define PCI_DEVICE_ID_ROCKCHIP_RK3588 0x3588 +#define PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB 2 + static DEFINE_IDA(pci_endpoint_test_ida); #define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \ @@ -414,6 +423,193 @@ static int pci_endpoint_test_bars(struct pci_endpoint_test *test) return 0; } +static u8 pci_endpoint_test_subrange_sig_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0x50 + (barno * 8) + subno; +} + +static u8 pci_endpoint_test_subrange_test_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0xa0 + (barno * 8) + subno; +} + +static int pci_endpoint_test_bar_subrange_cmd(struct pci_endpoint_test *test, + enum pci_barno barno, u32 command, + u32 ok_bit, u32 fail_bit) +{ + struct pci_dev *pdev = test->pdev; + struct device *dev = &pdev->dev; + int irq_type = test->irq_type; + u32 status; + + if (irq_type < PCITEST_IRQ_TYPE_INTX || + irq_type > PCITEST_IRQ_TYPE_MSIX) { + dev_err(dev, "Invalid IRQ type\n"); + return -EINVAL; + } + + reinit_completion(&test->irq_raised); + + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); + /* Reuse SIZE as a command parameter: bar number. */ + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, barno); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, command); + + if (!wait_for_completion_timeout(&test->irq_raised, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + status = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS); + if (status & fail_bit) + return -EIO; + + if (!(status & ok_bit)) + return -EIO; + + return 0; +} + +static int pci_endpoint_test_bar_subrange_setup(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_SETUP, + STATUS_BAR_SUBRANGE_SETUP_SUCCESS, + STATUS_BAR_SUBRANGE_SETUP_FAIL); +} + +static int pci_endpoint_test_bar_subrange_clear(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_CLEAR, + STATUS_BAR_SUBRANGE_CLEAR_SUCCESS, + STATUS_BAR_SUBRANGE_CLEAR_FAIL); +} + +static int pci_endpoint_test_bar_subrange(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + u32 nsub = PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB; + struct device *dev = &test->pdev->dev; + size_t sub_size, buf_size; + resource_size_t bar_size; + void __iomem *bar_addr; + void *read_buf = NULL; + int ret, clear_ret; + size_t off, chunk; + u32 i, exp, val; + u8 pattern; + + if (!(test->ep_caps & CAP_SUBRANGE_MAPPING)) + return -EOPNOTSUPP; + + /* + * The test register BAR is not safe to reprogram and write/read + * over its full size. BAR_TEST already special-cases it to a tiny + * range. For subrange mapping tests, let's simply skip it. + */ + if (barno == test->test_reg_bar) + return -EBUSY; + + bar_size = pci_resource_len(test->pdev, barno); + if (!bar_size) + return -ENODATA; + + bar_addr = test->bar[barno]; + if (!bar_addr) + return -ENOMEM; + + ret = pci_endpoint_test_bar_subrange_setup(test, barno); + if (ret) + return ret; + + if (bar_size % nsub || bar_size / nsub > SIZE_MAX) { + ret = -EINVAL; + goto out_clear; + } + + sub_size = bar_size / nsub; + if (sub_size < sizeof(u32)) { + ret = -ENOSPC; + goto out_clear; + } + + /* Limit the temporary buffer size */ + buf_size = min_t(size_t, sub_size, SZ_1M); + + read_buf = kmalloc(buf_size, GFP_KERNEL); + if (!read_buf) { + ret = -ENOMEM; + goto out_clear; + } + + /* + * Step 1: verify EP-provided signature per subrange. This detects + * whether the EP actually applied the submap order. + */ + for (i = 0; i < nsub; i++) { + exp = (u32)pci_endpoint_test_subrange_sig_byte(barno, i) * + 0x01010101U; + val = ioread32(bar_addr + (i * sub_size)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, (size_t)i * sub_size, exp, val); + ret = -EIO; + goto out_clear; + } + val = ioread32(bar_addr + (i * sub_size) + sub_size - sizeof(u32)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, + ((size_t)i * sub_size) + sub_size - sizeof(u32), + exp, val); + ret = -EIO; + goto out_clear; + } + } + + /* Step 2: write unique pattern per subrange (write all first). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + memset_io(bar_addr + (i * sub_size), pattern, sub_size); + } + + /* Step 3: read back and verify (read all after all writes). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + for (off = 0; off < sub_size; off += chunk) { + void *bad; + + chunk = min_t(size_t, buf_size, sub_size - off); + memcpy_fromio(read_buf, bar_addr + (i * sub_size) + off, + chunk); + bad = memchr_inv(read_buf, pattern, chunk); + if (bad) { + size_t bad_off = (u8 *)bad - (u8 *)read_buf; + + dev_err(dev, + "BAR%d subrange%u data mismatch @%#zx (pattern %#02x)\n", + barno, i, (size_t)i * sub_size + off + bad_off, + pattern); + ret = -EIO; + goto out_clear; + } + } + } + +out_clear: + kfree(read_buf); + clear_ret = pci_endpoint_test_bar_subrange_clear(test, barno); + return ret ?: clear_ret; +} + static int pci_endpoint_test_intx_irq(struct pci_endpoint_test *test) { u32 val; @@ -936,12 +1132,17 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, switch (cmd) { case PCITEST_BAR: + case PCITEST_BAR_SUBRANGE: bar = arg; if (bar <= NO_BAR || bar > BAR_5) goto ret; if (is_am654_pci_dev(pdev) && bar == BAR_0) goto ret; - ret = pci_endpoint_test_bar(test, bar); + + if (cmd == PCITEST_BAR) + ret = pci_endpoint_test_bar(test, bar); + else + ret = pci_endpoint_test_bar_subrange(test, bar); break; case PCITEST_BARS: ret = pci_endpoint_test_bars(test); diff --git a/include/uapi/linux/pcitest.h b/include/uapi/linux/pcitest.h index d6023a45a9d0..710f8842223f 100644 --- a/include/uapi/linux/pcitest.h +++ b/include/uapi/linux/pcitest.h @@ -22,6 +22,7 @@ #define PCITEST_GET_IRQTYPE _IO('P', 0x9) #define PCITEST_BARS _IO('P', 0xa) #define PCITEST_DOORBELL _IO('P', 0xb) +#define PCITEST_BAR_SUBRANGE _IO('P', 0xc) #define PCITEST_CLEAR_IRQ _IO('P', 0x10) #define PCITEST_IRQ_TYPE_UNDEFINED -1 -- cgit v1.2.3 From 8f05a5f6745ccc9ff784736608c5a38edb09acc8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 2 Jan 2026 21:04:48 +0530 Subject: PCI: Cache ACS Capabilities register The ACS Capability register is read-only. Cache it to allow quirks to override it and to avoid re-reading it. Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Tested-by: Marek Szyprowski Tested-by: Naresh Kamboju Link: https://patch.msgid.link/20260102-pci_acs-v3-2-72280b94d288@oss.qualcomm.com --- drivers/pci/pci.c | 24 ++++++++++++++---------- include/linux/pci.h | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 647c3c4eb482..e4c11efce34c 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -893,7 +893,6 @@ static const char *disable_acs_redir_param; static const char *config_acs_param; struct pci_acs { - u16 cap; u16 ctrl; u16 fw_ctrl; }; @@ -996,20 +995,20 @@ static void __pci_config_acs(struct pci_dev *dev, struct pci_acs *caps, static void pci_std_enable_acs(struct pci_dev *dev, struct pci_acs *caps) { /* Source Validation */ - caps->ctrl |= (caps->cap & PCI_ACS_SV); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_SV); /* P2P Request Redirect */ - caps->ctrl |= (caps->cap & PCI_ACS_RR); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_RR); /* P2P Completion Redirect */ - caps->ctrl |= (caps->cap & PCI_ACS_CR); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_CR); /* Upstream Forwarding */ - caps->ctrl |= (caps->cap & PCI_ACS_UF); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_UF); /* Enable Translation Blocking for external devices and noats */ if (pci_ats_disabled() || dev->external_facing || dev->untrusted) - caps->ctrl |= (caps->cap & PCI_ACS_TB); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_TB); } /** @@ -1032,7 +1031,6 @@ void pci_enable_acs(struct pci_dev *dev) if (!pos) return; - pci_read_config_word(dev, pos + PCI_ACS_CAP, &caps.cap); pci_read_config_word(dev, pos + PCI_ACS_CTRL, &caps.ctrl); caps.fw_ctrl = caps.ctrl; @@ -3515,7 +3513,7 @@ void pci_configure_ari(struct pci_dev *dev) static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) { int pos; - u16 cap, ctrl; + u16 ctrl; pos = pdev->acs_cap; if (!pos) @@ -3526,8 +3524,7 @@ static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) * or only required if controllable. Features missing from the * capability field can therefore be assumed as hard-wired enabled. */ - pci_read_config_word(pdev, pos + PCI_ACS_CAP, &cap); - acs_flags &= (cap | PCI_ACS_EC); + acs_flags &= (pdev->acs_capabilities | PCI_ACS_EC); pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl); return (ctrl & acs_flags) == acs_flags; @@ -3648,7 +3645,14 @@ bool pci_acs_path_enabled(struct pci_dev *start, */ void pci_acs_init(struct pci_dev *dev) { + int pos; + dev->acs_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); + pos = dev->acs_cap; + if (!pos) + return; + + pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities); } /** diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..6195e040b29c 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -558,6 +558,7 @@ struct pci_dev { struct pci_tsm *tsm; /* TSM operation state */ #endif u16 acs_cap; /* ACS Capability offset */ + u16 acs_capabilities; /* ACS Capabilities */ u8 supported_speeds; /* Supported Link Speeds Vector */ phys_addr_t rom; /* Physical address if not from BAR */ size_t romlen; /* Length if not from BAR */ -- cgit v1.2.3 From 46a9f70e93ef73860d1dbbec75ef840031f8f30a Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 16 Jan 2026 15:15:12 +0200 Subject: PCI/bwctrl: Disable BW controller on Intel P45 using a quirk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit 665745f27487 ("PCI/bwctrl: Re-add BW notification portdrv as PCIe BW controller") was found to lead to a boot hang on a Intel P45 system. Testing without setting Link Bandwidth Management Interrupt Enable (LBMIE) and Link Autonomous Bandwidth Interrupt Enable (LABIE) (PCIe r7.0, sec 7.5.3.7) in bwctrl allowed system to come up. P45 is a very old chipset and supports only up to gen2 PCIe, so not having bwctrl does not seem a huge deficiency. Add no_bw_notif in struct pci_dev and quirk Intel P45 Root Port with it. Reported-by: Adam Stylinski Link: https://lore.kernel.org/linux-pci/aUCt1tHhm_-XIVvi@eggsbenedict/ Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Tested-by: Adam Stylinski Link: https://patch.msgid.link/20260116131513.2359-1-ilpo.jarvinen@linux.intel.com --- drivers/pci/pcie/bwctrl.c | 3 +++ drivers/pci/quirks.c | 10 ++++++++++ include/linux/pci.h | 1 + 3 files changed, 14 insertions(+) (limited to 'include') diff --git a/drivers/pci/pcie/bwctrl.c b/drivers/pci/pcie/bwctrl.c index 36f939f23d34..4ae92c9f912a 100644 --- a/drivers/pci/pcie/bwctrl.c +++ b/drivers/pci/pcie/bwctrl.c @@ -250,6 +250,9 @@ static int pcie_bwnotif_probe(struct pcie_device *srv) struct pci_dev *port = srv->port; int ret; + if (port->no_bw_notif) + return -ENODEV; + /* Can happen if we run out of bus numbers during enumeration. */ if (!port->subordinate) return -ENODEV; diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index b9c252aa6fe0..6ef42a2c4831 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -1359,6 +1359,16 @@ static void quirk_transparent_bridge(struct pci_dev *dev) DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82380FB, quirk_transparent_bridge); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TOSHIBA, 0x605, quirk_transparent_bridge); +/* + * Enabling Link Bandwidth Management Interrupts (BW notifications) can cause + * boot hangs on P45. + */ +static void quirk_p45_bw_notifications(struct pci_dev *dev) +{ + dev->no_bw_notif = 1; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e21, quirk_p45_bw_notifications); + /* * Common misconfiguration of the MediaGX/Geode PCI master that will reduce * PCI bandwidth from 70MB/s to 25MB/s. See the GXM/GXLV/GX1 datasheets diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..3a556cd749e3 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -406,6 +406,7 @@ struct pci_dev { user sysfs */ unsigned int clear_retrain_link:1; /* Need to clear Retrain Link bit manually */ + unsigned int no_bw_notif:1; /* BW notifications may cause issues */ unsigned int d3hot_delay; /* D3hot->D0 transition time in ms */ unsigned int d3cold_delay; /* D3cold->D0 transition time in ms */ -- cgit v1.2.3