Skip to content

Commit 02dda0d

Browse files
l1kopsiff
authored andcommitted
PCI: pciehp: Ignore Link Down/Up caused by Secondary Bus Reset
When a Secondary Bus Reset is issued at a hotplug port, it causes a Data Link Layer State Changed event as a side effect. On hotplug ports using in-band presence detect, it additionally causes a Presence Detect Changed event. These spurious events should not result in teardown and re-enumeration of the device in the slot. Hence commit 2e35afa ("PCI: pciehp: Add reset_slot() method") masked the Presence Detect Changed Enable bit in the Slot Control register during a Secondary Bus Reset. Commit 06a8d89 ("PCI: pciehp: Disable link notification across slot reset") additionally masked the Data Link Layer State Changed Enable bit. However masking those bits only disables interrupt generation (PCIe r6.2 sec 6.7.3.1). The events are still visible in the Slot Status register and picked up by the IRQ handler if it runs during a Secondary Bus Reset. This can happen if the interrupt is shared or if an unmasked hotplug event occurs, e.g. Attention Button Pressed or Power Fault Detected. The likelihood of this happening used to be small, so it wasn't much of a problem in practice. That has changed with the recent introduction of bandwidth control in v6.13-rc1 with commit 665745f ("PCI/bwctrl: Re-add BW notification portdrv as PCIe BW controller"): Bandwidth control shares the interrupt with PCIe hotplug. A Secondary Bus Reset causes a Link Bandwidth Notification, so the hotplug IRQ handler runs, picks up the masked events and tears down the device in the slot. As a result, Joel reports VFIO passthrough failure of a GPU, which Ilpo root-caused to the incorrect handling of masked hotplug events. Clearly, a more reliable way is needed to ignore spurious hotplug events. For Downstream Port Containment, a new ignore mechanism was introduced by commit a97396c ("PCI: pciehp: Ignore Link Down/Up caused by DPC"). It has been working reliably for the past four years. Adapt it for Secondary Bus Resets. Introduce two helpers to annotate code sections which cause spurious link changes: pci_hp_ignore_link_change() and pci_hp_unignore_link_change() Use those helpers in lieu of masking interrupts in the Slot Control register. Introduce a helper to check whether such a code section is executing concurrently and if so, await it: pci_hp_spurious_link_change() Invoke the helper in the hotplug IRQ thread pciehp_ist(). Re-use the IRQ thread's existing code which ignores DPC-induced link changes unless the link is unexpectedly down after reset recovery or the device was replaced during the bus reset. That code block in pciehp_ist() was previously only executed if a Data Link Layer State Changed event has occurred. Additionally execute it for Presence Detect Changed events. That's necessary for compatibility with PCIe r1.0 hotplug ports because Data Link Layer State Changed didn't exist before PCIe r1.1. DPC was added with PCIe r3.1 and thus DPC-capable hotplug ports always support Data Link Layer State Changed events. But the same cannot be assumed for Secondary Bus Reset, which already existed in PCIe r1.0. Secondary Bus Reset is only one of many causes of spurious link changes. Others include runtime suspend to D3cold, firmware updates or FPGA reconfiguration. The new pci_hp_{,un}ignore_link_change() helpers may be used by all kinds of drivers to annotate such code sections, hence their declarations are publicly visible in <linux/pci.h>. A case in point is the Mellanox Ethernet driver which disables a firmware reset feature if the Ethernet card is attached to a hotplug port, see commit 3d7a3f2 ("net/mlx5: Nack sync reset request when HotPlug is enabled"). Going forward, PCIe hotplug will be able to cope gracefully with all such use cases once the code sections are properly annotated. The new helpers internally use two bits in struct pci_dev's priv_flags as well as a wait_queue. This mirrors what was done for DPC by commit a97396c ("PCI: pciehp: Ignore Link Down/Up caused by DPC"). That may be insufficient if spurious link changes are caused by multiple sources simultaneously. An example might be a Secondary Bus Reset issued by AER during FPGA reconfiguration. If this turns out to happen in real life, support for it can easily be added by replacing the PCI_LINK_CHANGING flag with an atomic_t counter incremented by pci_hp_ignore_link_change() and decremented by pci_hp_unignore_link_change(). Instead of awaiting a zero PCI_LINK_CHANGING flag, the pci_hp_spurious_link_change() helper would then simply await a zero counter. Fixes: 665745f ("PCI/bwctrl: Re-add BW notification portdrv as PCIe BW controller") Reported-by: Joel Mathew Thomas <proxy0@tutamail.com> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219765 Signed-off-by: Lukas Wunner <lukas@wunner.de> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Tested-by: Joel Mathew Thomas <proxy0@tutamail.com> Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com> Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com> Link: https://patch.msgid.link/d04deaf49d634a2edf42bf3c06ed81b4ca54d17b.1744298239.git.lukas@wunner.de Signed-off-by: LeoLiu-oc <leoliu-oc@zhaoxin.com> Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
1 parent 4d07c23 commit 02dda0d

4 files changed

Lines changed: 92 additions & 23 deletions

File tree

drivers/pci/hotplug/pci_hotplug_core.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,75 @@ void pci_hp_destroy(struct hotplug_slot *slot)
558558
}
559559
EXPORT_SYMBOL_GPL(pci_hp_destroy);
560560

561+
static DECLARE_WAIT_QUEUE_HEAD(pci_hp_link_change_wq);
562+
563+
/**
564+
* pci_hp_ignore_link_change - begin code section causing spurious link changes
565+
* @pdev: PCI hotplug bridge
566+
*
567+
* Mark the beginning of a code section causing spurious link changes on the
568+
* Secondary Bus of @pdev, e.g. as a side effect of a Secondary Bus Reset,
569+
* D3cold transition, firmware update or FPGA reconfiguration.
570+
*
571+
* Hotplug drivers can thus check whether such a code section is executing
572+
* concurrently, await it with pci_hp_spurious_link_change() and ignore the
573+
* resulting link change events.
574+
*
575+
* Must be paired with pci_hp_unignore_link_change(). May be called both
576+
* from the PCI core and from Endpoint drivers. May be called for bridges
577+
* which are not hotplug-capable, in which case it has no effect because
578+
* no hotplug driver is bound to the bridge.
579+
*/
580+
void pci_hp_ignore_link_change(struct pci_dev *pdev)
581+
{
582+
set_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
583+
smp_mb__after_atomic(); /* pairs with implied barrier of wait_event() */
584+
}
585+
586+
/**
587+
* pci_hp_unignore_link_change - end code section causing spurious link changes
588+
* @pdev: PCI hotplug bridge
589+
*
590+
* Mark the end of a code section causing spurious link changes on the
591+
* Secondary Bus of @pdev. Must be paired with pci_hp_ignore_link_change().
592+
*/
593+
void pci_hp_unignore_link_change(struct pci_dev *pdev)
594+
{
595+
set_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
596+
mb(); /* ensure pci_hp_spurious_link_change() sees either bit set */
597+
clear_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
598+
wake_up_all(&pci_hp_link_change_wq);
599+
}
600+
601+
/**
602+
* pci_hp_spurious_link_change - check for spurious link changes
603+
* @pdev: PCI hotplug bridge
604+
*
605+
* Check whether a code section is executing concurrently which is causing
606+
* spurious link changes on the Secondary Bus of @pdev. Await the end of the
607+
* code section if so.
608+
*
609+
* May be called by hotplug drivers to check whether a link change is spurious
610+
* and can be ignored.
611+
*
612+
* Because a genuine link change may have occurred in-between a spurious link
613+
* change and the invocation of this function, hotplug drivers should perform
614+
* sanity checks such as retrieving the current link state and bringing down
615+
* the slot if the link is down.
616+
*
617+
* Return: %true if such a code section has been executing concurrently,
618+
* otherwise %false. Also return %true if such a code section has not been
619+
* executing concurrently, but at least once since the last invocation of this
620+
* function.
621+
*/
622+
bool pci_hp_spurious_link_change(struct pci_dev *pdev)
623+
{
624+
wait_event(pci_hp_link_change_wq,
625+
!test_bit(PCI_LINK_CHANGING, &pdev->priv_flags));
626+
627+
return test_and_clear_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
628+
}
629+
561630
static int __init pci_hotplug_init(void)
562631
{
563632
int result;

drivers/pci/hotplug/pciehp_hpc.c

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -588,21 +588,21 @@ bool pciehp_device_replaced(struct controller *ctrl)
588588
return false;
589589
}
590590

591-
static void pciehp_ignore_dpc_link_change(struct controller *ctrl,
592-
struct pci_dev *pdev, int irq,
593-
u16 ignored_events)
591+
static void pciehp_ignore_link_change(struct controller *ctrl,
592+
struct pci_dev *pdev, int irq,
593+
u16 ignored_events)
594594
{
595595
/*
596596
* Ignore link changes which occurred while waiting for DPC recovery.
597597
* Could be several if DPC triggered multiple times consecutively.
598+
* Also ignore link changes caused by Secondary Bus Reset, etc.
598599
*/
599600
synchronize_hardirq(irq);
600601
atomic_and(~ignored_events, &ctrl->pending_events);
601602
if (pciehp_poll_mode)
602603
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
603604
ignored_events);
604-
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n",
605-
slot_name(ctrl));
605+
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored\n", slot_name(ctrl));
606606

607607
/*
608608
* If the link is unexpectedly down after successful recovery,
@@ -758,17 +758,19 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
758758

759759
/*
760760
* Ignore Link Down/Up events caused by Downstream Port Containment
761-
* if recovery from the error succeeded.
761+
* if recovery succeeded, or caused by Secondary Bus Reset,
762+
* suspend to D3cold, firmware update, FPGA reconfiguration, etc.
762763
*/
763-
if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) &&
764+
if ((events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) &&
765+
(pci_dpc_recovered(pdev) || pci_hp_spurious_link_change(pdev)) &&
764766
ctrl->state == ON_STATE) {
765767
u16 ignored_events = PCI_EXP_SLTSTA_DLLSC;
766768

767769
if (!ctrl->inband_presence_disabled)
768770
ignored_events |= events & PCI_EXP_SLTSTA_PDC;
769771

770772
events &= ~ignored_events;
771-
pciehp_ignore_dpc_link_change(ctrl, pdev, irq, ignored_events);
773+
pciehp_ignore_link_change(ctrl, pdev, irq, ignored_events);
772774
}
773775

774776
/*
@@ -933,31 +935,18 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
933935
{
934936
struct controller *ctrl = to_ctrl(hotplug_slot);
935937
struct pci_dev *pdev = ctrl_dev(ctrl);
936-
u16 stat_mask = 0, ctrl_mask = 0;
937938
int rc;
938939

939940
if (probe)
940941
return 0;
941942

942943
down_write_nested(&ctrl->reset_lock, ctrl->depth);
943944

944-
if (!ATTN_BUTTN(ctrl)) {
945-
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
946-
stat_mask |= PCI_EXP_SLTSTA_PDC;
947-
}
948-
ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE;
949-
stat_mask |= PCI_EXP_SLTSTA_DLLSC;
950-
951-
pcie_write_cmd(ctrl, 0, ctrl_mask);
952-
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
953-
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
945+
pci_hp_ignore_link_change(pdev);
954946

955947
rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port);
956948

957-
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
958-
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
959-
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
960-
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
949+
pci_hp_unignore_link_change(pdev);
961950

962951
up_write(&ctrl->reset_lock);
963952
return rc;

drivers/pci/pci.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; }
177177

178178
/* Functions for PCI Hotplug drivers to use */
179179
int pci_hp_add_bridge(struct pci_dev *dev);
180+
bool pci_hp_spurious_link_change(struct pci_dev *pdev);
180181

181182
#ifdef HAVE_PCI_LEGACY
182183
void pci_create_legacy_files(struct pci_bus *bus);
@@ -408,6 +409,8 @@ static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
408409
#define PCI_DPC_RECOVERED 1
409410
#define PCI_DPC_RECOVERING 2
410411
#define PCI_DEV_REMOVED 3
412+
#define PCI_LINK_CHANGED 4
413+
#define PCI_LINK_CHANGING 5
411414

412415
static inline void pci_dev_assign_added(struct pci_dev *dev, bool added)
413416
{

include/linux/pci.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,14 @@ static inline bool pcie_aspm_support_enabled(void) { return false; }
18541854
static inline bool pcie_aspm_enabled(struct pci_dev *pdev) { return false; }
18551855
#endif
18561856

1857+
#ifdef CONFIG_HOTPLUG_PCI
1858+
void pci_hp_ignore_link_change(struct pci_dev *pdev);
1859+
void pci_hp_unignore_link_change(struct pci_dev *pdev);
1860+
#else
1861+
static inline void pci_hp_ignore_link_change(struct pci_dev *pdev) { }
1862+
static inline void pci_hp_unignore_link_change(struct pci_dev *pdev) { }
1863+
#endif
1864+
18571865
#ifdef CONFIG_PCIEAER
18581866
bool pci_aer_available(void);
18591867
#else

0 commit comments

Comments
 (0)