* [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
@ 2026-02-13 23:14 Kuppuswamy Sathyanarayanan
2026-02-14 6:01 ` Lukas Wunner
2026-02-17 16:54 ` Kuppuswamy Sathyanarayanan
0 siblings, 2 replies; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-13 23:14 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: Lukas Wunner, linux-pci, linux-kernel
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.
Work around this issue by disabling runtime PM for affected ports,
keeping them in D0 during runtime operation. This ensures hotplug
events are handled via direct interrupts rather than relying on
unreliable PME-based wakeup.
During system suspend/resume, PCIe ports are resumed unconditionally
when coming out of system sleep due to DPM_FLAG_SMART_SUSPEND set by
pcie_portdrv_probe(), and pciehp re-enables interrupts and checks slot
occupation status during resume.
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.
Suggested-by: Lukas Wunner <lukas@wunner.de>
Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
---
Changes since v1:
* Removed hack in hotplug driver and disabled runtime PM on affected ports.
* Fixed the commit log and comments accordingly.
drivers/pci/quirks.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 280cd50d693b..779cd65b1a8a 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6340,3 +6340,52 @@ 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
+
+/*
+ * Intel Catlow Lake PCH PCIe root ports have a hardware issue where
+ * PME status registers (PME Status and PME Requester_ID in Root Status)
+ * are not reliably updated during D3hot to D0 transitions, even though
+ * PME interrupts are delivered correctly.
+ *
+ * When a hotplug event occurs while the port is in D3hot, the PME
+ * interrupt fires but the status registers remain empty. This prevents
+ * the PME handler from identifying the event source, leaving the port
+ * in D3hot and causing the hotplug driver to miss the event.
+ *
+ * Disable runtime PM to keep these ports in D0, ensuring hotplug events
+ * are handled via direct interrupts.
+ */
+static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
+{
+ pm_runtime_disable(&dev->dev);
+ pci_info(dev, "Catlow PCH port: PME status unreliable, disabling runtime PM\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);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-13 23:14 [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status Kuppuswamy Sathyanarayanan
@ 2026-02-14 6:01 ` Lukas Wunner
2026-02-14 15:11 ` Lukas Wunner
2026-02-17 16:54 ` Kuppuswamy Sathyanarayanan
1 sibling, 1 reply; 14+ messages in thread
From: Lukas Wunner @ 2026-02-14 6:01 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
On Fri, Feb 13, 2026 at 03:14:28PM -0800, Kuppuswamy Sathyanarayanan wrote:
> 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.
Hm, so in theory we could amend the PME driver to walk the bus below the
Root Port and see if anything has PME_Status set in the PMCSR register.
But the PME interrupt is shared with hotplug, bandwidth control etc,
so we'd end up gratuitouly (and frequently) runtime resuming switches
below the Root Port to see if there's anything below which is requesting
wakeup.
So just keeping the Root Port runtime resumed all the time, as this
patch does, is still a better approach IMO.
I'm wondering though if this causes a power regression. Does keeping
the Root Port in D0 prevent the Package from entering a lower power
state? Or is this irrelevant because the PCH is a different chip
or tile?
If you respin, please cc Rafael and linux-pm@vger.kernel.org to see
if PM maintainers have any objections.
> +++ b/drivers/pci/quirks.c
Since this is x86-specific, it could live in arch/x86/kernel/quirks.c
or arch/x86/pci/fixup.c to prevent being compiled in on other arches.
(I don't know why x86 quirks are spread out across two different files
and there doesn't seem to be a consistent rule which one to pick.)
Otherwise this is
Reviewed-by: Lukas Wunner <lukas@wunner.de>
Thanks,
Lukas
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-14 6:01 ` Lukas Wunner
@ 2026-02-14 15:11 ` Lukas Wunner
2026-02-17 17:01 ` Kuppuswamy Sathyanarayanan
0 siblings, 1 reply; 14+ messages in thread
From: Lukas Wunner @ 2026-02-14 15:11 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
On Sat, Feb 14, 2026 at 07:01:13AM +0100, Lukas Wunner wrote:
> On Fri, Feb 13, 2026 at 03:14:28PM -0800, Kuppuswamy Sathyanarayanan wrote:
> > 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.
>
> Hm, so in theory we could amend the PME driver to walk the bus below the
> Root Port and see if anything has PME_Status set in the PMCSR register.
>
> But the PME interrupt is shared with hotplug, bandwidth control etc,
> so we'd end up gratuitouly (and frequently) runtime resuming switches
> below the Root Port to see if there's anything below which is requesting
> wakeup.
>
> So just keeping the Root Port runtime resumed all the time, as this
> patch does, is still a better approach IMO.
>
> I'm wondering though if this causes a power regression. Does keeping
> the Root Port in D0 prevent the Package from entering a lower power
> state? Or is this irrelevant because the PCH is a different chip
> or tile?
I've just realized that pcie_disable_interrupt() isn't called from
pciehp_suspend() if pme_is_native() is true. Should disabling
runtime PM cause a power regression, an alternative solution may be
to make pcie_disable_interrupt() conditional on a new pme_is_broken()
which checks for affected Catlow Lake PCH Root Ports.
The pm_runtime_disable() approach is slightly preferred because
it keeps pciehp code clean.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-13 23:14 [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status Kuppuswamy Sathyanarayanan
2026-02-14 6:01 ` Lukas Wunner
@ 2026-02-17 16:54 ` Kuppuswamy Sathyanarayanan
2026-02-17 18:08 ` Rafael J. Wysocki
1 sibling, 1 reply; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-17 16:54 UTC (permalink / raw)
To: Bjorn Helgaas, Rafael J . Wysocki
Cc: Lukas Wunner, linux-pci, linux-kernel, linux-pm
Hi Rafael,
On 2/13/2026 3:14 PM, Kuppuswamy Sathyanarayanan wrote:
> 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.
>
> Work around this issue by disabling runtime PM for affected ports,
> keeping them in D0 during runtime operation. This ensures hotplug
> events are handled via direct interrupts rather than relying on
> unreliable PME-based wakeup.
>
> During system suspend/resume, PCIe ports are resumed unconditionally
> when coming out of system sleep due to DPM_FLAG_SMART_SUSPEND set by
> pcie_portdrv_probe(), and pciehp re-enables interrupts and checks slot
> occupation status during resume.
>
> 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.
>
> Suggested-by: Lukas Wunner <lukas@wunner.de>
> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> ---
Could you please review this patch and let us know if calling
pm_runtime_disable() from a PCI quirk is acceptable?
The quirk keeps specific Catlow Lake PCH PCIe root ports in D0 to
work around a hardware bug where PME status registers are not reliably
updated during D3hot to D0 transitions, causing hotplug events to be
missed.
System suspend/resume is unaffected as DPM_FLAG_SMART_SUSPEND ensures
ports are resumed unconditionally and pciehp checks slot occupation
on resume.
>
> Changes since v1:
> * Removed hack in hotplug driver and disabled runtime PM on affected ports.
> * Fixed the commit log and comments accordingly.
>
> drivers/pci/quirks.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 49 insertions(+)
>
> diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
> index 280cd50d693b..779cd65b1a8a 100644
> --- a/drivers/pci/quirks.c
> +++ b/drivers/pci/quirks.c
> @@ -6340,3 +6340,52 @@ 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
> +
> +/*
> + * Intel Catlow Lake PCH PCIe root ports have a hardware issue where
> + * PME status registers (PME Status and PME Requester_ID in Root Status)
> + * are not reliably updated during D3hot to D0 transitions, even though
> + * PME interrupts are delivered correctly.
> + *
> + * When a hotplug event occurs while the port is in D3hot, the PME
> + * interrupt fires but the status registers remain empty. This prevents
> + * the PME handler from identifying the event source, leaving the port
> + * in D3hot and causing the hotplug driver to miss the event.
> + *
> + * Disable runtime PM to keep these ports in D0, ensuring hotplug events
> + * are handled via direct interrupts.
> + */
> +static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
> +{
> + pm_runtime_disable(&dev->dev);
> + pci_info(dev, "Catlow PCH port: PME status unreliable, disabling runtime PM\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);
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-14 15:11 ` Lukas Wunner
@ 2026-02-17 17:01 ` Kuppuswamy Sathyanarayanan
2026-02-17 18:22 ` Lukas Wunner
0 siblings, 1 reply; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-17 17:01 UTC (permalink / raw)
To: Lukas Wunner; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
On 2/14/2026 7:11 AM, Lukas Wunner wrote:
> On Sat, Feb 14, 2026 at 07:01:13AM +0100, Lukas Wunner wrote:
>> On Fri, Feb 13, 2026 at 03:14:28PM -0800, Kuppuswamy Sathyanarayanan wrote:
>>> 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.
>>
>> Hm, so in theory we could amend the PME driver to walk the bus below the
>> Root Port and see if anything has PME_Status set in the PMCSR register.
>>
>> But the PME interrupt is shared with hotplug, bandwidth control etc,
>> so we'd end up gratuitouly (and frequently) runtime resuming switches
>> below the Root Port to see if there's anything below which is requesting
>> wakeup.
>>
>> So just keeping the Root Port runtime resumed all the time, as this
>> patch does, is still a better approach IMO.
>>
>> I'm wondering though if this causes a power regression. Does keeping
>> the Root Port in D0 prevent the Package from entering a lower power
>> state? Or is this irrelevant because the PCH is a different chip
>> or tile?
>
> I've just realized that pcie_disable_interrupt() isn't called from
> pciehp_suspend() if pme_is_native() is true. Should disabling
> runtime PM cause a power regression, an alternative solution may be
> to make pcie_disable_interrupt() conditional on a new pme_is_broken()
> which checks for affected Catlow Lake PCH Root Ports.
>
> The pm_runtime_disable() approach is slightly preferred because
> it keeps pciehp code clean.
I think pcie_disable_interrupt() is called from pciehp_suspend() when
pme_is_native() is true. Looking at the code:
static void pciehp_disable_interrupt(struct pcie_device *dev)
{
/*
* Disable hotplug interrupt so that it does not trigger
* immediately when the downstream link goes down.
*/
if (pme_is_native(dev))
pcie_disable_interrupt(get_service_data(dev));
}
#ifdef CONFIG_PM_SLEEP
static int pciehp_suspend(struct pcie_device *dev)
{
/*
* If the port is already runtime suspended we can keep it that
* way.
*/
if (dev_pm_skip_suspend(&dev->port->dev))
return 0;
pciehp_disable_interrupt(dev);
return 0;
}
pciehp_suspend() calls pciehp_disable_interrupt(), which in turn calls
pcie_disable_interrupt() only if pme_is_native() is true. So the interrupt
is disabled during suspend specifically when PME is native, not the other
way around. Did I misread your statement?
>
> Thanks,
>
> Lukas
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-17 16:54 ` Kuppuswamy Sathyanarayanan
@ 2026-02-17 18:08 ` Rafael J. Wysocki
2026-02-18 16:27 ` Kuppuswamy Sathyanarayanan
0 siblings, 1 reply; 14+ messages in thread
From: Rafael J. Wysocki @ 2026-02-17 18:08 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan
Cc: Bjorn Helgaas, Rafael J . Wysocki, Lukas Wunner, linux-pci,
linux-kernel, linux-pm
On Tue, Feb 17, 2026 at 5:54 PM Kuppuswamy Sathyanarayanan
<sathyanarayanan.kuppuswamy@linux.intel.com> wrote:
>
> Hi Rafael,
>
> On 2/13/2026 3:14 PM, Kuppuswamy Sathyanarayanan wrote:
> > 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.
I think that you mean the
if (PCI_POSSIBLE_ERROR(rtsta) || !(rtsta & PCI_EXP_RTSTA_PME))
check in pcie_pme_irq(). Or do you mean something else?
An alternative workaround might be to add a (new) "always poll PME"
flag for the port in question that will cause it to go to pci_pme_list
in pci_pme_active() every time wakeup is enabled (essentially, an
override for pme_poll clearing).
> > 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.
> >
> > Work around this issue by disabling runtime PM for affected ports,
> > keeping them in D0 during runtime operation. This ensures hotplug
> > events are handled via direct interrupts rather than relying on
> > unreliable PME-based wakeup.
> >
> > During system suspend/resume, PCIe ports are resumed unconditionally
> > when coming out of system sleep due to DPM_FLAG_SMART_SUSPEND set by
> > pcie_portdrv_probe(), and pciehp re-enables interrupts and checks slot
> > occupation status during resume.
> >
> > 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.
> >
> > Suggested-by: Lukas Wunner <lukas@wunner.de>
> > Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> > ---
>
> Could you please review this patch and let us know if calling
> pm_runtime_disable() from a PCI quirk is acceptable?
>
> The quirk keeps specific Catlow Lake PCH PCIe root ports in D0 to
> work around a hardware bug where PME status registers are not reliably
> updated during D3hot to D0 transitions, causing hotplug events to be
> missed.
>
> System suspend/resume is unaffected as DPM_FLAG_SMART_SUSPEND ensures
> ports are resumed unconditionally and pciehp checks slot occupation
> on resume.
>
>
> >
> > Changes since v1:
> > * Removed hack in hotplug driver and disabled runtime PM on affected ports.
> > * Fixed the commit log and comments accordingly.
> >
> > drivers/pci/quirks.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 49 insertions(+)
> >
> > diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
> > index 280cd50d693b..779cd65b1a8a 100644
> > --- a/drivers/pci/quirks.c
> > +++ b/drivers/pci/quirks.c
> > @@ -6340,3 +6340,52 @@ 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
> > +
> > +/*
> > + * Intel Catlow Lake PCH PCIe root ports have a hardware issue where
> > + * PME status registers (PME Status and PME Requester_ID in Root Status)
> > + * are not reliably updated during D3hot to D0 transitions, even though
> > + * PME interrupts are delivered correctly.
> > + *
> > + * When a hotplug event occurs while the port is in D3hot, the PME
> > + * interrupt fires but the status registers remain empty. This prevents
> > + * the PME handler from identifying the event source, leaving the port
> > + * in D3hot and causing the hotplug driver to miss the event.
> > + *
> > + * Disable runtime PM to keep these ports in D0, ensuring hotplug events
> > + * are handled via direct interrupts.
> > + */
> > +static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
> > +{
> > + pm_runtime_disable(&dev->dev);
Personally, I would use pm_runtime_get_sync() here instead which would
really mean "never suspend".
> > + pci_info(dev, "Catlow PCH port: PME status unreliable, disabling runtime PM\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);
>
> --
> Sathyanarayanan Kuppuswamy
> Linux Kernel Developer
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-17 17:01 ` Kuppuswamy Sathyanarayanan
@ 2026-02-17 18:22 ` Lukas Wunner
2026-02-18 16:28 ` Kuppuswamy Sathyanarayanan
0 siblings, 1 reply; 14+ messages in thread
From: Lukas Wunner @ 2026-02-17 18:22 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
On Tue, Feb 17, 2026 at 09:01:25AM -0800, Kuppuswamy Sathyanarayanan wrote:
> On 2/14/2026 7:11 AM, Lukas Wunner wrote:
> > I've just realized that pcie_disable_interrupt() isn't called from
> > pciehp_suspend() if pme_is_native() is true. Should disabling
^^^^
Sorry, I meant "if pme_is_native() is *false*".
My brain was apparently half asleep when I wrote this.
> > runtime PM cause a power regression, an alternative solution may be
> > to make pcie_disable_interrupt() conditional on a new pme_is_broken()
> > which checks for affected Catlow Lake PCH Root Ports.
> >
> > The pm_runtime_disable() approach is slightly preferred because
> > it keeps pciehp code clean.
>
> I think pcie_disable_interrupt() is called from pciehp_suspend() when
> pme_is_native() is true. Looking at the code:
>
> static void pciehp_disable_interrupt(struct pcie_device *dev)
> {
> /*
> * Disable hotplug interrupt so that it does not trigger
> * immediately when the downstream link goes down.
> */
> if (pme_is_native(dev->port))
> pcie_disable_interrupt(get_service_data(dev));
> }
What I had in mind is something like:
- if (pme_is_native(dev))
+ if (pme_is_native(dev) && !pme_is_broken(dev))
pcie_disable_interrupt(get_service_data(dev));
Again, the pm_runtime_disable() (or pm_runtime_get_sync()) approach
is slightly preferred because it keeps pciehp code clean and confines
the issue to a quirk that only needs to be compiled in on x86.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-17 18:08 ` Rafael J. Wysocki
@ 2026-02-18 16:27 ` Kuppuswamy Sathyanarayanan
2026-02-18 17:33 ` Rafael J. Wysocki
0 siblings, 1 reply; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-18 16:27 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Bjorn Helgaas, Lukas Wunner, linux-pci, linux-kernel, linux-pm
On 2/17/2026 10:08 AM, Rafael J. Wysocki wrote:
> On Tue, Feb 17, 2026 at 5:54 PM Kuppuswamy Sathyanarayanan
> <sathyanarayanan.kuppuswamy@linux.intel.com> wrote:
>>
>> Hi Rafael,
>>
>> On 2/13/2026 3:14 PM, Kuppuswamy Sathyanarayanan wrote:
>>> 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.
>
> I think that you mean the
>
> if (PCI_POSSIBLE_ERROR(rtsta) || !(rtsta & PCI_EXP_RTSTA_PME))
>
> check in pcie_pme_irq(). Or do you mean something else?
Yes, I was referring to the above check.
>
> An alternative workaround might be to add a (new) "always poll PME"
> flag for the port in question that will cause it to go to pci_pme_list
> in pci_pme_active() every time wakeup is enabled (essentially, an
> override for pme_poll clearing).
I will check whether this approach works. I want to make sure the poll
logic eventually triggers the hotplug handler to detect slot state
changes.
But if you think there is no power-related issue with keeping these ports
in D0, then we can adopt the pm_runtime_disable() approach. I think this
approach looks clean and simple.
What's your preference?
>
>>> 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.
>>>
>>> Work around this issue by disabling runtime PM for affected ports,
>>> keeping them in D0 during runtime operation. This ensures hotplug
>>> events are handled via direct interrupts rather than relying on
>>> unreliable PME-based wakeup.
>>>
>>> During system suspend/resume, PCIe ports are resumed unconditionally
>>> when coming out of system sleep due to DPM_FLAG_SMART_SUSPEND set by
>>> pcie_portdrv_probe(), and pciehp re-enables interrupts and checks slot
>>> occupation status during resume.
>>>
>>> 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.
>>>
>>> Suggested-by: Lukas Wunner <lukas@wunner.de>
>>> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
>>> ---
>>
>> Could you please review this patch and let us know if calling
>> pm_runtime_disable() from a PCI quirk is acceptable?
>>
>> The quirk keeps specific Catlow Lake PCH PCIe root ports in D0 to
>> work around a hardware bug where PME status registers are not reliably
>> updated during D3hot to D0 transitions, causing hotplug events to be
>> missed.
>>
>> System suspend/resume is unaffected as DPM_FLAG_SMART_SUSPEND ensures
>> ports are resumed unconditionally and pciehp checks slot occupation
>> on resume.
>>
>>
>>>
>>> Changes since v1:
>>> * Removed hack in hotplug driver and disabled runtime PM on affected ports.
>>> * Fixed the commit log and comments accordingly.
>>>
>>> drivers/pci/quirks.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
>>> 1 file changed, 49 insertions(+)
>>>
>>> diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
>>> index 280cd50d693b..779cd65b1a8a 100644
>>> --- a/drivers/pci/quirks.c
>>> +++ b/drivers/pci/quirks.c
>>> @@ -6340,3 +6340,52 @@ 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
>>> +
>>> +/*
>>> + * Intel Catlow Lake PCH PCIe root ports have a hardware issue where
>>> + * PME status registers (PME Status and PME Requester_ID in Root Status)
>>> + * are not reliably updated during D3hot to D0 transitions, even though
>>> + * PME interrupts are delivered correctly.
>>> + *
>>> + * When a hotplug event occurs while the port is in D3hot, the PME
>>> + * interrupt fires but the status registers remain empty. This prevents
>>> + * the PME handler from identifying the event source, leaving the port
>>> + * in D3hot and causing the hotplug driver to miss the event.
>>> + *
>>> + * Disable runtime PM to keep these ports in D0, ensuring hotplug events
>>> + * are handled via direct interrupts.
>>> + */
>>> +static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
>>> +{
>>> + pm_runtime_disable(&dev->dev);
>
> Personally, I would use pm_runtime_get_sync() here instead which would
> really mean "never suspend".
>
>>> + pci_info(dev, "Catlow PCH port: PME status unreliable, disabling runtime PM\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);
>>
>> --
>> Sathyanarayanan Kuppuswamy
>> Linux Kernel Developer
>>
>
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-17 18:22 ` Lukas Wunner
@ 2026-02-18 16:28 ` Kuppuswamy Sathyanarayanan
0 siblings, 0 replies; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-18 16:28 UTC (permalink / raw)
To: Lukas Wunner; +Cc: Bjorn Helgaas, linux-pci, linux-kernel
Hi Lukas,
On 2/17/2026 10:22 AM, Lukas Wunner wrote:
> On Tue, Feb 17, 2026 at 09:01:25AM -0800, Kuppuswamy Sathyanarayanan wrote:
>> On 2/14/2026 7:11 AM, Lukas Wunner wrote:
>>> I've just realized that pcie_disable_interrupt() isn't called from
>>> pciehp_suspend() if pme_is_native() is true. Should disabling
> ^^^^
> Sorry, I meant "if pme_is_native() is *false*".
> My brain was apparently half asleep when I wrote this.
>
>>> runtime PM cause a power regression, an alternative solution may be
>>> to make pcie_disable_interrupt() conditional on a new pme_is_broken()
>>> which checks for affected Catlow Lake PCH Root Ports.
>>>
>>> The pm_runtime_disable() approach is slightly preferred because
>>> it keeps pciehp code clean.
>>
>> I think pcie_disable_interrupt() is called from pciehp_suspend() when
>> pme_is_native() is true. Looking at the code:
>>
>> static void pciehp_disable_interrupt(struct pcie_device *dev)
>> {
>> /*
>> * Disable hotplug interrupt so that it does not trigger
>> * immediately when the downstream link goes down.
>> */
>> if (pme_is_native(dev->port))
>> pcie_disable_interrupt(get_service_data(dev));
>> }
>
> What I had in mind is something like:
>
> - if (pme_is_native(dev))
> + if (pme_is_native(dev) && !pme_is_broken(dev))
> pcie_disable_interrupt(get_service_data(dev));
>
> Again, the pm_runtime_disable() (or pm_runtime_get_sync()) approach
> is slightly preferred because it keeps pciehp code clean and confines
> the issue to a quirk that only needs to be compiled in on x86.
Got it. Thanks for clarifying.
>
> Thanks,
>
> Lukas
>
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-18 16:27 ` Kuppuswamy Sathyanarayanan
@ 2026-02-18 17:33 ` Rafael J. Wysocki
2026-02-19 8:04 ` Lukas Wunner
0 siblings, 1 reply; 14+ messages in thread
From: Rafael J. Wysocki @ 2026-02-18 17:33 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan
Cc: Rafael J. Wysocki, Bjorn Helgaas, Lukas Wunner, linux-pci,
linux-kernel, linux-pm
On Wed, Feb 18, 2026 at 5:27 PM Kuppuswamy Sathyanarayanan
<sathyanarayanan.kuppuswamy@linux.intel.com> wrote:
>
>
>
> On 2/17/2026 10:08 AM, Rafael J. Wysocki wrote:
> > On Tue, Feb 17, 2026 at 5:54 PM Kuppuswamy Sathyanarayanan
> > <sathyanarayanan.kuppuswamy@linux.intel.com> wrote:
> >>
> >> Hi Rafael,
> >>
> >> On 2/13/2026 3:14 PM, Kuppuswamy Sathyanarayanan wrote:
> >>> 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.
> >
> > I think that you mean the
> >
> > if (PCI_POSSIBLE_ERROR(rtsta) || !(rtsta & PCI_EXP_RTSTA_PME))
> >
> > check in pcie_pme_irq(). Or do you mean something else?
>
> Yes, I was referring to the above check.
>
> >
> > An alternative workaround might be to add a (new) "always poll PME"
> > flag for the port in question that will cause it to go to pci_pme_list
> > in pci_pme_active() every time wakeup is enabled (essentially, an
> > override for pme_poll clearing).
>
> I will check whether this approach works. I want to make sure the poll
> logic eventually triggers the hotplug handler to detect slot state
> changes.
>
> But if you think there is no power-related issue with keeping these ports
> in D0, then we can adopt the pm_runtime_disable() approach. I think this
> approach looks clean and simple.
>
> What's your preference?
First, keeping the ports in D0 may gate runtime PC10. Does it not?
Second, I'd use pm_runtime_get_sync() in the quirk as I said because
pm_runtime_disable() generally breaks runtime PM dependency chains
between devices and may cause subtle side-effects to appear.
> >
> >>> 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.
> >>>
> >>> Work around this issue by disabling runtime PM for affected ports,
> >>> keeping them in D0 during runtime operation. This ensures hotplug
> >>> events are handled via direct interrupts rather than relying on
> >>> unreliable PME-based wakeup.
> >>>
> >>> During system suspend/resume, PCIe ports are resumed unconditionally
> >>> when coming out of system sleep due to DPM_FLAG_SMART_SUSPEND set by
> >>> pcie_portdrv_probe(), and pciehp re-enables interrupts and checks slot
> >>> occupation status during resume.
> >>>
> >>> 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.
> >>>
> >>> Suggested-by: Lukas Wunner <lukas@wunner.de>
> >>> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> >>> ---
> >>
> >> Could you please review this patch and let us know if calling
> >> pm_runtime_disable() from a PCI quirk is acceptable?
> >>
> >> The quirk keeps specific Catlow Lake PCH PCIe root ports in D0 to
> >> work around a hardware bug where PME status registers are not reliably
> >> updated during D3hot to D0 transitions, causing hotplug events to be
> >> missed.
> >>
> >> System suspend/resume is unaffected as DPM_FLAG_SMART_SUSPEND ensures
> >> ports are resumed unconditionally and pciehp checks slot occupation
> >> on resume.
> >>
> >>
> >>>
> >>> Changes since v1:
> >>> * Removed hack in hotplug driver and disabled runtime PM on affected ports.
> >>> * Fixed the commit log and comments accordingly.
> >>>
> >>> drivers/pci/quirks.c | 49 ++++++++++++++++++++++++++++++++++++++++++++
> >>> 1 file changed, 49 insertions(+)
> >>>
> >>> diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
> >>> index 280cd50d693b..779cd65b1a8a 100644
> >>> --- a/drivers/pci/quirks.c
> >>> +++ b/drivers/pci/quirks.c
> >>> @@ -6340,3 +6340,52 @@ 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
> >>> +
> >>> +/*
> >>> + * Intel Catlow Lake PCH PCIe root ports have a hardware issue where
> >>> + * PME status registers (PME Status and PME Requester_ID in Root Status)
> >>> + * are not reliably updated during D3hot to D0 transitions, even though
> >>> + * PME interrupts are delivered correctly.
> >>> + *
> >>> + * When a hotplug event occurs while the port is in D3hot, the PME
> >>> + * interrupt fires but the status registers remain empty. This prevents
> >>> + * the PME handler from identifying the event source, leaving the port
> >>> + * in D3hot and causing the hotplug driver to miss the event.
> >>> + *
> >>> + * Disable runtime PM to keep these ports in D0, ensuring hotplug events
> >>> + * are handled via direct interrupts.
> >>> + */
> >>> +static void quirk_intel_catlow_pcie_no_pme_wakeup(struct pci_dev *dev)
> >>> +{
> >>> + pm_runtime_disable(&dev->dev);
> >
> > Personally, I would use pm_runtime_get_sync() here instead which would
> > really mean "never suspend".
> >
> >>> + pci_info(dev, "Catlow PCH port: PME status unreliable, disabling runtime PM\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);
> >>
> >> --
> >> Sathyanarayanan Kuppuswamy
> >> Linux Kernel Developer
> >>
> >
>
> --
> Sathyanarayanan Kuppuswamy
> Linux Kernel Developer
>
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-18 17:33 ` Rafael J. Wysocki
@ 2026-02-19 8:04 ` Lukas Wunner
2026-02-19 11:09 ` Rafael J. Wysocki
0 siblings, 1 reply; 14+ messages in thread
From: Lukas Wunner @ 2026-02-19 8:04 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Kuppuswamy Sathyanarayanan, Bjorn Helgaas, linux-pci,
linux-kernel, linux-pm
On Wed, Feb 18, 2026 at 06:33:15PM +0100, Rafael J. Wysocki wrote:
> First, keeping the ports in D0 may gate runtime PC10. Does it not?
The Root Port in question is on the PCH. I'm not sure, does keeping a
PCH Root Port in D0 also prevent PC10 entry or is that only the case
for Root Ports on the CPU die/tile?
If this does cause a power regression, the pme_is_broken() approach
suggested upthread might be a viable alternative. It'll allow the
Root Port to go to D3hot but will keep interrupts enabled in the
Slot Control register.
Thanks,
Lukas
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-19 8:04 ` Lukas Wunner
@ 2026-02-19 11:09 ` Rafael J. Wysocki
2026-02-19 21:54 ` Kuppuswamy Sathyanarayanan
0 siblings, 1 reply; 14+ messages in thread
From: Rafael J. Wysocki @ 2026-02-19 11:09 UTC (permalink / raw)
To: Lukas Wunner
Cc: Rafael J. Wysocki, Kuppuswamy Sathyanarayanan, Bjorn Helgaas,
linux-pci, linux-kernel, linux-pm
On Thu, Feb 19, 2026 at 9:04 AM Lukas Wunner <lukas@wunner.de> wrote:
>
> On Wed, Feb 18, 2026 at 06:33:15PM +0100, Rafael J. Wysocki wrote:
> > First, keeping the ports in D0 may gate runtime PC10. Does it not?
>
> The Root Port in question is on the PCH. I'm not sure, does keeping a
> PCH Root Port in D0 also prevent PC10 entry or is that only the case
> for Root Ports on the CPU die/tile?
If it is located in the PCH, it should not gate PC10 if in D0 at least
in theory, but it would be good to verify that.
Of course, it will still gate S0ix entry through runtime idle, but
that's a bit moot if the platform is unable to enter S0ix through
runtime idle anyway for other reasons (which is quite likely), or if
the power difference between S0ix and PC10 is small.
> If this does cause a power regression, the pme_is_broken() approach
> suggested upthread might be a viable alternative. It'll allow the
> Root Port to go to D3hot but will keep interrupts enabled in the
> Slot Control register.
Sounds reasonable to me.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-19 11:09 ` Rafael J. Wysocki
@ 2026-02-19 21:54 ` Kuppuswamy Sathyanarayanan
2026-03-09 18:04 ` Kuppuswamy Sathyanarayanan
0 siblings, 1 reply; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-02-19 21:54 UTC (permalink / raw)
To: Rafael J. Wysocki, Lukas Wunner
Cc: Bjorn Helgaas, linux-pci, linux-kernel, linux-pm
Hi,
On 2/19/2026 3:09 AM, Rafael J. Wysocki wrote:
> On Thu, Feb 19, 2026 at 9:04 AM Lukas Wunner <lukas@wunner.de> wrote:
>>
>> On Wed, Feb 18, 2026 at 06:33:15PM +0100, Rafael J. Wysocki wrote:
>>> First, keeping the ports in D0 may gate runtime PC10. Does it not?
>>
>> The Root Port in question is on the PCH. I'm not sure, does keeping a
>> PCH Root Port in D0 also prevent PC10 entry or is that only the case
>> for Root Ports on the CPU die/tile?
>
> If it is located in the PCH, it should not gate PC10 if in D0 at least
> in theory, but it would be good to verify that.
>
> Of course, it will still gate S0ix entry through runtime idle, but
> that's a bit moot if the platform is unable to enter S0ix through
> runtime idle anyway for other reasons (which is quite likely), or if
> the power difference between S0ix and PC10 is small.
I will gather current PC10 and S0ix numbers. If there is a significant
difference between the two power savings, I will implement the
pme_is_broken() approach.
>
>> If this does cause a power regression, the pme_is_broken() approach
>> suggested upthread might be a viable alternative. It'll allow the
>> Root Port to go to D3hot but will keep interrupts enabled in the
>> Slot Control register.
>
> Sounds reasonable to me.
Sounds good.
>
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status
2026-02-19 21:54 ` Kuppuswamy Sathyanarayanan
@ 2026-03-09 18:04 ` Kuppuswamy Sathyanarayanan
0 siblings, 0 replies; 14+ messages in thread
From: Kuppuswamy Sathyanarayanan @ 2026-03-09 18:04 UTC (permalink / raw)
To: Rafael J. Wysocki, Lukas Wunner
Cc: Bjorn Helgaas, linux-pci, linux-kernel, linux-pm
Hi Lukas/Rafael,
On 2/19/2026 1:54 PM, Kuppuswamy Sathyanarayanan wrote:
> Hi,
>
> On 2/19/2026 3:09 AM, Rafael J. Wysocki wrote:
>> On Thu, Feb 19, 2026 at 9:04 AM Lukas Wunner <lukas@wunner.de> wrote:
>>>
>>> On Wed, Feb 18, 2026 at 06:33:15PM +0100, Rafael J. Wysocki wrote:
>>>> First, keeping the ports in D0 may gate runtime PC10. Does it not?
>>>
>>> The Root Port in question is on the PCH. I'm not sure, does keeping a
>>> PCH Root Port in D0 also prevent PC10 entry or is that only the case
>>> for Root Ports on the CPU die/tile?
>>
>> If it is located in the PCH, it should not gate PC10 if in D0 at least
>> in theory, but it would be good to verify that.
>>
>> Of course, it will still gate S0ix entry through runtime idle, but
>> that's a bit moot if the platform is unable to enter S0ix through
>> runtime idle anyway for other reasons (which is quite likely), or if
>> the power difference between S0ix and PC10 is small.
>
> I will gather current PC10 and S0ix numbers. If there is a significant
> difference between the two power savings, I will implement the
> pme_is_broken() approach.
I have gathered the test results. S0ix is not a POR for this platform, so
I was unable to collect those numbers. The deepest package C-state supported
is PC6.
With pm_runtime_disable() keeping the port in D0, I observe approximately
10% reduction in PC6 residency. Given this power impact, I will proceed with
the pme_is_broken() approach suggested by Lukas, which allows the port to
enter D3hot while keeping hotplug interrupts enabled.
I will submit v3 with this implementation. Change log looks like below:
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index 1e9158d7bac7..f854ef9551c3 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -260,13 +260,20 @@ static bool pme_is_native(struct pcie_device *dev)
return pcie_ports_native || host->native_pme;
}
+static bool pme_is_broken(struct pcie_device *pcie)
+{
+ struct pci_dev *pdev = pcie->port;
+
+ return !!(pdev->dev_flags & PCI_DEV_FLAGS_PME_UNRELIABLE);
+}
+
static void pciehp_disable_interrupt(struct pcie_device *dev)
{
/*
* Disable hotplug interrupt so that it does not trigger
* immediately when the downstream link goes down.
*/
- if (pme_is_native(dev))
+ if (pme_is_native(dev) && !pme_is_broken(dev))
pcie_disable_interrupt(get_service_data(dev));
}
@@ -318,7 +325,7 @@ static int pciehp_resume(struct pcie_device *dev)
{
struct controller *ctrl = get_service_data(dev);
- if (pme_is_native(dev))
+ if (pme_is_native(dev) && !pme_is_broken(dev))
pcie_enable_interrupt(ctrl);
pciehp_check_presence(ctrl);
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 48946cca4be7..acaa328dff51 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6380,3 +6380,51 @@ 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.
+ *
+ * Mark affected ports with PCI_DEV_FLAGS_PME_UNRELIABLE to keep
+ * hotplug interrupts (HPIE) enabled during D3hot instead of relying on
+ * PME-based wakeup. This allows hotplug events to be delivered via
+ * direct interrupts while still permitting the port to enter D3hot for
@@ -6380,3 +6380,51 @@ 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.
+ *
+ * Mark affected ports with PCI_DEV_FLAGS_PME_UNRELIABLE to keep
+ * hotplug interrupts (HPIE) enabled during D3hot instead of relying on
+ * PME-based wakeup. This allows hotplug events to be delivered via
+ * direct interrupts while still permitting the port to enter D3hot for
+ * power savings.
+ *
+ */
+static void quirk_intel_catlow_pcie_pme_unreliable(struct pci_dev *dev)
+{
+ dev->dev_flags |= PCI_DEV_FLAGS_PME_UNRELIABLE;
+ pci_info(dev, "Catlow PCH port: PME unreliable\n");
+}
+/* Apply quirk to Catlow Lake PCH root ports (0x7a30 - 0x7a4b) */
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a30, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a31, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a32, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a33, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a34, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a35, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a36, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a37, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a38, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a39, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3a, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3b, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3c, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3d, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3e, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a3f, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a40, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a41, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a42, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a43, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a44, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a45, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a46, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a47, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a48, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a49, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a4a, quirk_intel_catlow_pcie_pme_unreliable);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x7a4b, quirk_intel_catlow_pcie_pme_unreliable);
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 1c270f1d5123..9761351c5d70 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -253,6 +253,8 @@ enum pci_dev_flags {
* 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),
+ /* Device PME is broken or unreliable */
+ PCI_DEV_FLAGS_PME_UNRELIABLE = (__force pci_dev_flags_t) (1 << 15),
};
>
>
>>
>>> If this does cause a power regression, the pme_is_broken() approach
>>> suggested upthread might be a viable alternative. It'll allow the
>>> Root Port to go to D3hot but will keep interrupts enabled in the
>>> Slot Control register.
>>
>> Sounds reasonable to me.
>
> Sounds good.
>
>>
>
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-03-09 18:04 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-13 23:14 [PATCH v2] PCI: pciehp: Fix hotplug on Catlow Lake with unreliable PME status Kuppuswamy Sathyanarayanan
2026-02-14 6:01 ` Lukas Wunner
2026-02-14 15:11 ` Lukas Wunner
2026-02-17 17:01 ` Kuppuswamy Sathyanarayanan
2026-02-17 18:22 ` Lukas Wunner
2026-02-18 16:28 ` Kuppuswamy Sathyanarayanan
2026-02-17 16:54 ` Kuppuswamy Sathyanarayanan
2026-02-17 18:08 ` Rafael J. Wysocki
2026-02-18 16:27 ` Kuppuswamy Sathyanarayanan
2026-02-18 17:33 ` Rafael J. Wysocki
2026-02-19 8:04 ` Lukas Wunner
2026-02-19 11:09 ` Rafael J. Wysocki
2026-02-19 21:54 ` Kuppuswamy Sathyanarayanan
2026-03-09 18:04 ` Kuppuswamy Sathyanarayanan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox