drivers/pci/hotplug/pciehp_hpc.c | 4 ++- drivers/pci/quirks.c | 45 ++++++++++++++++++++++++++++++++ include/linux/pci.h | 2 ++ 3 files changed, 50 insertions(+), 1 deletion(-)
On Intel Catlow Lake platforms, PCH PCIe root ports do not reliably
update PME status registers (PME Status and PME Requester_ID in the
Root Status register) during D3hot to D0 transitions, even though PME
interrupts are delivered correctly.
This issue manifests during PCIe hotplug operations as follows:
1. After a hot-remove event, the PCIe port transitions to D3hot and
the hotplug interrupt enable (HPIE) flag is disabled as the port
enters low power state.
2. When a hot-add occurs while the port is in D3hot, a PME interrupt
fires as expected to wake the port.
3. However, the PME interrupt handler finds the PME_Status and
PME_Requester_ID registers unpopulated, preventing identification
of which device triggered the PME. The handler returns IRQ_NONE,
leaving the port in D3hot.
4. Because the port remains in D3hot with HPIE disabled, the hotplug
driver ignores the hot-add event, resulting in the newly inserted
device not being recognized.
The PME interrupt delivery mechanism itself works correctly;
interrupts arrive reliably. The problem is purely the missing status
register updates. Verification via IOSF-SideBand (IOSF-SB) backdoor
reads confirms that these registers remain empty when the PME
interrupt fires. Neither BIOS nor kernel code is clearing these
registers.
This issue is present in all steppings of Catlow Lake PCH and affects
customers in production deployments. A public hardware errata document
is not yet available. While the document is being published, we need a
quirk to address the issue for existing customers.
Alternative approaches were considered to avoid modifying the pciehp
driver. We attempted to keep these ports in D0 state by calling
pm_runtime_disable() in the quirk. Although this approach works for
most cases, it fails to provide wakeup when the system enters suspend
state. Another option of modifying the PME configuration to force
pci_target_state() to select D0 also did not help, as runtime PM
still transitions the port to D3hot.
Add a PCI quirk (PCI_DEV_FLAGS_NO_PME_WAKEUP) for affected Catlow
Lake PCH PCIe root ports that allows the hotplug interrupt handler to
process events regardless of the port power state, bypassing the
broken PME-based wakeup dependency.
The quirk is applied only to Catlow PCH PCIe root ports (device IDs
0x7a30 through 0x7a4b). Catlow CPU PCIe ports are not affected as
they are not hotplug-capable. Systems with reliable PME signaling
continue to operate normally.
Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
---
drivers/pci/hotplug/pciehp_hpc.c | 4 ++-
drivers/pci/quirks.c | 45 ++++++++++++++++++++++++++++++++
include/linux/pci.h | 2 ++
3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index bcc51b26d03d..e5036bb79a08 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -631,7 +631,9 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
* in the Slot Control register (PCIe r4.0, sec 6.7.3.4).
*/
if (pdev->current_state == PCI_D3cold ||
- (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode))
+ (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) &&
+ !pciehp_poll_mode &&
+ !(pdev->dev_flags & PCI_DEV_FLAGS_NO_PME_WAKEUP)))
return IRQ_NONE;
/*
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 280cd50d693b..b22eb8554ab0 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6340,3 +6340,48 @@ static void pci_mask_replay_timer_timeout(struct pci_dev *pdev)
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout);
DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout);
#endif
+
+/*
+ * When a PCIe port is in D3hot, the hotplug driver depends on PME
+ * to wake the port back to D0 and then process any hotplug-related
+ * state changes. On Intel Catlow Lake platforms, PCH PCIe root ports
+ * do not reliably update PME state during D3hot to D0 transitions.
+ *
+ * Apply a quirk to disable PME-based wakeup requirements for these
+ * specific ports, allowing the hotplug driver to handle events
+ * independently of the port power state.
+ */
+static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
+{
+ dev->dev_flags |= PCI_DEV_FLAGS_NO_PME_WAKEUP;
+ pci_info(dev, "Catlow PCH port: PME unreliable, bypassing PME-based wakeup for hotplug\n");
+}
+/* Apply quirk to Catlow Lake PCH root ports (0x7a30 - 0x7a4b) */
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a30, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a31, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a32, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a33, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a34, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a35, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a36, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a37, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a38, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a39, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3a, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3b, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3c, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3d, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3e, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3f, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a40, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a41, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a42, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a43, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a44, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a45, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a46, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a47, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a48, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a49, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a4a, quirk_intel_catlow_pcie_no_pme_wakeup);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a4b, quirk_intel_catlow_pcie_no_pme_wakeup);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index b5cc0c2b9906..e4a95b36e4a0 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -248,6 +248,8 @@ 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),
+ /* Device does not reliably update PME status to wakeup from D3 to D0 */
+ PCI_DEV_FLAGS_NO_PME_WAKEUP = (__force pci_dev_flags_t) (1 << 14),
};
enum pci_irq_reroute_variant {
--
2.43.0
© 2016 - 2026 Red Hat, Inc.