drivers/pci/hotplug/pciehp_hpc.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-)
pciehp_isr() dismisses an interrupt as not ours (IRQ_NONE) when the
Hot-Plug Interrupt Enable (HPIE) bit is clear in ctrl->slot_ctrl, the
driver's cached copy of the Slot Control register.
This can drop hotplug events during PCIe hot-add. On an AMD EPYC
platform, sometimes hot-adding an PCIe device delivered only the
Presence Detect Changed (PDC) event while the Data Link Layer State
Changed (DLSC) event was lost. Afterwards lspci still showed the
Link State Changed bit latched in Slot Status, i.e. the DLSC interrupt
was never serviced:
pcieport 0000:c0:03.4: pciehp: pending interrupts 0x0008 from Slot Status
pcieport 0000:c0:03.4: pciehp: Slot(71): Card present
lspci -vvv after hot-add:
SltCtl: Enable: ... PresDet+ HPIrq+ LinkChg+
SltSta: Changed: MRL- PresDet- LinkState+
Only PDC (Slot Status 0x0008) was reported; LinkState+ (DLSC) stayed
latched and unhandled. This will affect the device's next hot-plug
operation, causing the state machine to become inconsistent.
The root cause is a stale cache. Platform firmware transiently clears
HPIE in hardware and restores it shortly afterwards. While handling the
PDC event the driver updates the slot indicators via
pciehp_set_indicators(), which issues a Slot Control read-modify-write
(pcie_do_write_cmd()). If that read occurs while firmware has HPIE
cleared, HPIE=0 is read back and stored into ctrl->slot_ctrl. Firmware
then restores HPIE=1 in hardware, but the cached HPIE=0 remains, so the
following DLSC interrupt is misjudged as not ours and dropped.
The hardware HPIE bit is correct in this case, so consult hardware rather
than the possibly stale cache. To keep the fast path cheap and avoid any
behavioural change for low-power ports, only re-read Slot Control when the
cache says HPIE is disabled and the port is in D0 (accessible and
enabled). In a lower-power state the port may be inaccessible, so the
cached value remains authoritative and the existing early return is
preserved. Surprise removal is handled via PCI_POSSIBLE_ERROR().
This changes behaviour only in the previously broken case (D0, cached
HPIE clear but hardware HPIE set). Poll mode, the shared-INTx early
return while suspended, and the D3hot deferral to the IRQ thread are
unchanged.
Signed-off-by: Zhu Qiyu <qiyuzhu2@amd.com>
---
drivers/pci/hotplug/pciehp_hpc.c | 29 +++++++++++++++++++++++++----
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 4c62140a3cb44..df977d4af4056 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -628,13 +628,34 @@ static irqreturn_t pciehp_isr(int irq, void *dev_id)
u16 status, events = 0;
/*
- * Interrupts only occur in D3hot or shallower and only if enabled
- * in the Slot Control register (PCIe r4.0, sec 6.7.3.4).
+ * Interrupts only occur in D3hot or shallower (PCIe r4.0, sec 6.7.3.4).
*/
- if (pdev->current_state == PCI_D3cold ||
- (!(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE) && !pciehp_poll_mode))
+ if (pdev->current_state == PCI_D3cold)
return IRQ_NONE;
+ /*
+ * Interrupts are only sent if enabled in the Slot Control register
+ * (PCIe r4.0, sec 6.7.3.4). ctrl->slot_ctrl caches that register, but
+ * the cached Hot-Plug Interrupt Enable bit can fall transiently out of
+ * sync with the hardware if platform firmware clears it behind the
+ * driver's back (e.g. concurrently with a Slot Control read-modify-
+ * write). While the port is in D0 it is accessible, so re-read the
+ * register from hardware before dismissing the interrupt as not ours.
+ * In a low-power state the port may be inaccessible and the cached
+ * value is authoritative.
+ */
+ if (!pciehp_poll_mode && !(ctrl->slot_ctrl & PCI_EXP_SLTCTL_HPIE)) {
+ u16 slot_ctrl;
+
+ if (pdev->current_state != PCI_D0)
+ return IRQ_NONE;
+
+ pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &slot_ctrl);
+ if (PCI_POSSIBLE_ERROR(slot_ctrl) ||
+ !(slot_ctrl & PCI_EXP_SLTCTL_HPIE))
+ return IRQ_NONE;
+ }
+
/*
* Keep the port accessible by holding a runtime PM ref on its parent.
* Defer resume of the parent to the IRQ thread if it's suspended.
--
2.43.0
© 2016 - 2026 Red Hat, Inc.