1
0

Merge branch 'pci/hotplug'

- Ignore Presence Detect Changed caused by DPC.  pciehp already ignores
  Link Down/Up events caused by DPC, but on slots using in-band presence
  detect, DPC causes a spurious Presence Detect Changed event (Lukas
  Wunner)

- Ignore Link Down/Up caused by Secondary Bus Reset.  On hotplug ports
  using in-band presence detect, the reset causes a Presence Detect Changed
  event, which mistakenly caused teardown and re-enumeration of the device.
  Drivers may need to annotate code that resets their device (Lukas Wunner)

* pci/hotplug:
  PCI: hotplug: Drop superfluous #include directives
  PCI: pciehp: Ignore Link Down/Up caused by Secondary Bus Reset
  PCI: pciehp: Ignore Presence Detect Changed caused by DPC

# Conflicts:
#	drivers/pci/pci.h
This commit is contained in:
Bjorn Helgaas
2025-06-04 10:49:59 -05:00
6 changed files with 132 additions and 60 deletions

View File

@@ -20,13 +20,9 @@
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/pagemap.h>
#include <linux/init.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/uaccess.h>
#include "../pci.h"
#include "cpci_hotplug.h"
@@ -492,6 +488,75 @@ void pci_hp_destroy(struct hotplug_slot *slot)
}
EXPORT_SYMBOL_GPL(pci_hp_destroy);
static DECLARE_WAIT_QUEUE_HEAD(pci_hp_link_change_wq);
/**
* pci_hp_ignore_link_change - begin code section causing spurious link changes
* @pdev: PCI hotplug bridge
*
* Mark the beginning of a code section causing spurious link changes on the
* Secondary Bus of @pdev, e.g. as a side effect of a Secondary Bus Reset,
* D3cold transition, firmware update or FPGA reconfiguration.
*
* Hotplug drivers can thus check whether such a code section is executing
* concurrently, await it with pci_hp_spurious_link_change() and ignore the
* resulting link change events.
*
* Must be paired with pci_hp_unignore_link_change(). May be called both
* from the PCI core and from Endpoint drivers. May be called for bridges
* which are not hotplug-capable, in which case it has no effect because
* no hotplug driver is bound to the bridge.
*/
void pci_hp_ignore_link_change(struct pci_dev *pdev)
{
set_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
smp_mb__after_atomic(); /* pairs with implied barrier of wait_event() */
}
/**
* pci_hp_unignore_link_change - end code section causing spurious link changes
* @pdev: PCI hotplug bridge
*
* Mark the end of a code section causing spurious link changes on the
* Secondary Bus of @pdev. Must be paired with pci_hp_ignore_link_change().
*/
void pci_hp_unignore_link_change(struct pci_dev *pdev)
{
set_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
mb(); /* ensure pci_hp_spurious_link_change() sees either bit set */
clear_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
wake_up_all(&pci_hp_link_change_wq);
}
/**
* pci_hp_spurious_link_change - check for spurious link changes
* @pdev: PCI hotplug bridge
*
* Check whether a code section is executing concurrently which is causing
* spurious link changes on the Secondary Bus of @pdev. Await the end of the
* code section if so.
*
* May be called by hotplug drivers to check whether a link change is spurious
* and can be ignored.
*
* Because a genuine link change may have occurred in-between a spurious link
* change and the invocation of this function, hotplug drivers should perform
* sanity checks such as retrieving the current link state and bringing down
* the slot if the link is down.
*
* Return: %true if such a code section has been executing concurrently,
* otherwise %false. Also return %true if such a code section has not been
* executing concurrently, but at least once since the last invocation of this
* function.
*/
bool pci_hp_spurious_link_change(struct pci_dev *pdev)
{
wait_event(pci_hp_link_change_wq,
!test_bit(PCI_LINK_CHANGING, &pdev->priv_flags));
return test_and_clear_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
}
static int __init pci_hotplug_init(void)
{
int result;

View File

@@ -187,6 +187,7 @@ int pciehp_card_present(struct controller *ctrl);
int pciehp_card_present_or_link_active(struct controller *ctrl);
int pciehp_check_link_status(struct controller *ctrl);
int pciehp_check_link_active(struct controller *ctrl);
bool pciehp_device_replaced(struct controller *ctrl);
void pciehp_release_ctrl(struct controller *ctrl);
int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);

View File

@@ -284,35 +284,6 @@ static int pciehp_suspend(struct pcie_device *dev)
return 0;
}
static bool pciehp_device_replaced(struct controller *ctrl)
{
struct pci_dev *pdev __free(pci_dev_put) = NULL;
u32 reg;
if (pci_dev_is_disconnected(ctrl->pcie->port))
return false;
pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0));
if (!pdev)
return true;
if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) ||
reg != (pdev->vendor | (pdev->device << 16)) ||
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) ||
reg != (pdev->revision | (pdev->class << 8)))
return true;
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL &&
(pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) ||
reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16))))
return true;
if (pci_get_dsn(pdev) != ctrl->dsn)
return true;
return false;
}
static int pciehp_resume_noirq(struct pcie_device *dev)
{
struct controller *ctrl = get_service_data(dev);

View File

@@ -563,20 +563,50 @@ void pciehp_power_off_slot(struct controller *ctrl)
PCI_EXP_SLTCTL_PWR_OFF);
}
static void pciehp_ignore_dpc_link_change(struct controller *ctrl,
struct pci_dev *pdev, int irq)
bool pciehp_device_replaced(struct controller *ctrl)
{
struct pci_dev *pdev __free(pci_dev_put) = NULL;
u32 reg;
if (pci_dev_is_disconnected(ctrl->pcie->port))
return false;
pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0));
if (!pdev)
return true;
if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) ||
reg != (pdev->vendor | (pdev->device << 16)) ||
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) ||
reg != (pdev->revision | (pdev->class << 8)))
return true;
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL &&
(pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) ||
reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16))))
return true;
if (pci_get_dsn(pdev) != ctrl->dsn)
return true;
return false;
}
static void pciehp_ignore_link_change(struct controller *ctrl,
struct pci_dev *pdev, int irq,
u16 ignored_events)
{
/*
* Ignore link changes which occurred while waiting for DPC recovery.
* Could be several if DPC triggered multiple times consecutively.
* Also ignore link changes caused by Secondary Bus Reset, etc.
*/
synchronize_hardirq(irq);
atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events);
atomic_and(~ignored_events, &ctrl->pending_events);
if (pciehp_poll_mode)
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
PCI_EXP_SLTSTA_DLLSC);
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n",
slot_name(ctrl));
ignored_events);
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored\n", slot_name(ctrl));
/*
* If the link is unexpectedly down after successful recovery,
@@ -584,8 +614,8 @@ static void pciehp_ignore_dpc_link_change(struct controller *ctrl,
* Synthesize it to ensure that it is acted on.
*/
down_read_nested(&ctrl->reset_lock, ctrl->depth);
if (!pciehp_check_link_active(ctrl))
pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC);
if (!pciehp_check_link_active(ctrl) || pciehp_device_replaced(ctrl))
pciehp_request(ctrl, ignored_events);
up_read(&ctrl->reset_lock);
}
@@ -732,12 +762,19 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
/*
* Ignore Link Down/Up events caused by Downstream Port Containment
* if recovery from the error succeeded.
* if recovery succeeded, or caused by Secondary Bus Reset,
* suspend to D3cold, firmware update, FPGA reconfiguration, etc.
*/
if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) &&
if ((events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) &&
(pci_dpc_recovered(pdev) || pci_hp_spurious_link_change(pdev)) &&
ctrl->state == ON_STATE) {
events &= ~PCI_EXP_SLTSTA_DLLSC;
pciehp_ignore_dpc_link_change(ctrl, pdev, irq);
u16 ignored_events = PCI_EXP_SLTSTA_DLLSC;
if (!ctrl->inband_presence_disabled)
ignored_events |= events & PCI_EXP_SLTSTA_PDC;
events &= ~ignored_events;
pciehp_ignore_link_change(ctrl, pdev, irq, ignored_events);
}
/*
@@ -902,7 +939,6 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
{
struct controller *ctrl = to_ctrl(hotplug_slot);
struct pci_dev *pdev = ctrl_dev(ctrl);
u16 stat_mask = 0, ctrl_mask = 0;
int rc;
if (probe)
@@ -910,23 +946,11 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
down_write_nested(&ctrl->reset_lock, ctrl->depth);
if (!ATTN_BUTTN(ctrl)) {
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
stat_mask |= PCI_EXP_SLTSTA_PDC;
}
ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE;
stat_mask |= PCI_EXP_SLTSTA_DLLSC;
pcie_write_cmd(ctrl, 0, ctrl_mask);
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
pci_hp_ignore_link_change(pdev);
rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port);
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
pci_hp_unignore_link_change(pdev);
up_write(&ctrl->reset_lock);
return rc;

View File

@@ -227,6 +227,7 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; }
/* Functions for PCI Hotplug drivers to use */
int pci_hp_add_bridge(struct pci_dev *dev);
bool pci_hp_spurious_link_change(struct pci_dev *pdev);
#if defined(CONFIG_SYSFS) && defined(HAVE_PCI_LEGACY)
void pci_create_legacy_files(struct pci_bus *bus);
@@ -557,6 +558,8 @@ static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
#define PCI_DPC_RECOVERED 1
#define PCI_DPC_RECOVERING 2
#define PCI_DEV_REMOVED 3
#define PCI_LINK_CHANGED 4
#define PCI_LINK_CHANGING 5
#define PCI_LINK_LBMS_SEEN 6
#define PCI_DEV_ALLOW_BINDING 7

View File

@@ -1843,6 +1843,14 @@ static inline bool pcie_aspm_support_enabled(void) { return false; }
static inline bool pcie_aspm_enabled(struct pci_dev *pdev) { return false; }
#endif
#ifdef CONFIG_HOTPLUG_PCI
void pci_hp_ignore_link_change(struct pci_dev *pdev);
void pci_hp_unignore_link_change(struct pci_dev *pdev);
#else
static inline void pci_hp_ignore_link_change(struct pci_dev *pdev) { }
static inline void pci_hp_unignore_link_change(struct pci_dev *pdev) { }
#endif
#ifdef CONFIG_PCIEAER
bool pci_aer_available(void);
#else