Skip to content

Commit bb5c909

Browse files
committed
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
2 parents 68d0370 + d46b391 commit bb5c909

File tree

6 files changed

+132
-60
lines changed

6 files changed

+132
-60
lines changed

drivers/pci/hotplug/pci_hotplug_core.c

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,9 @@
2020
#include <linux/types.h>
2121
#include <linux/kobject.h>
2222
#include <linux/sysfs.h>
23-
#include <linux/pagemap.h>
2423
#include <linux/init.h>
25-
#include <linux/mount.h>
26-
#include <linux/namei.h>
2724
#include <linux/pci.h>
2825
#include <linux/pci_hotplug.h>
29-
#include <linux/uaccess.h>
3026
#include "../pci.h"
3127
#include "cpci_hotplug.h"
3228

@@ -492,6 +488,75 @@ void pci_hp_destroy(struct hotplug_slot *slot)
492488
}
493489
EXPORT_SYMBOL_GPL(pci_hp_destroy);
494490

491+
static DECLARE_WAIT_QUEUE_HEAD(pci_hp_link_change_wq);
492+
493+
/**
494+
* pci_hp_ignore_link_change - begin code section causing spurious link changes
495+
* @pdev: PCI hotplug bridge
496+
*
497+
* Mark the beginning of a code section causing spurious link changes on the
498+
* Secondary Bus of @pdev, e.g. as a side effect of a Secondary Bus Reset,
499+
* D3cold transition, firmware update or FPGA reconfiguration.
500+
*
501+
* Hotplug drivers can thus check whether such a code section is executing
502+
* concurrently, await it with pci_hp_spurious_link_change() and ignore the
503+
* resulting link change events.
504+
*
505+
* Must be paired with pci_hp_unignore_link_change(). May be called both
506+
* from the PCI core and from Endpoint drivers. May be called for bridges
507+
* which are not hotplug-capable, in which case it has no effect because
508+
* no hotplug driver is bound to the bridge.
509+
*/
510+
void pci_hp_ignore_link_change(struct pci_dev *pdev)
511+
{
512+
set_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
513+
smp_mb__after_atomic(); /* pairs with implied barrier of wait_event() */
514+
}
515+
516+
/**
517+
* pci_hp_unignore_link_change - end code section causing spurious link changes
518+
* @pdev: PCI hotplug bridge
519+
*
520+
* Mark the end of a code section causing spurious link changes on the
521+
* Secondary Bus of @pdev. Must be paired with pci_hp_ignore_link_change().
522+
*/
523+
void pci_hp_unignore_link_change(struct pci_dev *pdev)
524+
{
525+
set_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
526+
mb(); /* ensure pci_hp_spurious_link_change() sees either bit set */
527+
clear_bit(PCI_LINK_CHANGING, &pdev->priv_flags);
528+
wake_up_all(&pci_hp_link_change_wq);
529+
}
530+
531+
/**
532+
* pci_hp_spurious_link_change - check for spurious link changes
533+
* @pdev: PCI hotplug bridge
534+
*
535+
* Check whether a code section is executing concurrently which is causing
536+
* spurious link changes on the Secondary Bus of @pdev. Await the end of the
537+
* code section if so.
538+
*
539+
* May be called by hotplug drivers to check whether a link change is spurious
540+
* and can be ignored.
541+
*
542+
* Because a genuine link change may have occurred in-between a spurious link
543+
* change and the invocation of this function, hotplug drivers should perform
544+
* sanity checks such as retrieving the current link state and bringing down
545+
* the slot if the link is down.
546+
*
547+
* Return: %true if such a code section has been executing concurrently,
548+
* otherwise %false. Also return %true if such a code section has not been
549+
* executing concurrently, but at least once since the last invocation of this
550+
* function.
551+
*/
552+
bool pci_hp_spurious_link_change(struct pci_dev *pdev)
553+
{
554+
wait_event(pci_hp_link_change_wq,
555+
!test_bit(PCI_LINK_CHANGING, &pdev->priv_flags));
556+
557+
return test_and_clear_bit(PCI_LINK_CHANGED, &pdev->priv_flags);
558+
}
559+
495560
static int __init pci_hotplug_init(void)
496561
{
497562
int result;

drivers/pci/hotplug/pciehp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ int pciehp_card_present(struct controller *ctrl);
187187
int pciehp_card_present_or_link_active(struct controller *ctrl);
188188
int pciehp_check_link_status(struct controller *ctrl);
189189
int pciehp_check_link_active(struct controller *ctrl);
190+
bool pciehp_device_replaced(struct controller *ctrl);
190191
void pciehp_release_ctrl(struct controller *ctrl);
191192

192193
int pciehp_sysfs_enable_slot(struct hotplug_slot *hotplug_slot);

drivers/pci/hotplug/pciehp_core.c

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -284,35 +284,6 @@ static int pciehp_suspend(struct pcie_device *dev)
284284
return 0;
285285
}
286286

287-
static bool pciehp_device_replaced(struct controller *ctrl)
288-
{
289-
struct pci_dev *pdev __free(pci_dev_put) = NULL;
290-
u32 reg;
291-
292-
if (pci_dev_is_disconnected(ctrl->pcie->port))
293-
return false;
294-
295-
pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0));
296-
if (!pdev)
297-
return true;
298-
299-
if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) ||
300-
reg != (pdev->vendor | (pdev->device << 16)) ||
301-
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) ||
302-
reg != (pdev->revision | (pdev->class << 8)))
303-
return true;
304-
305-
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL &&
306-
(pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) ||
307-
reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16))))
308-
return true;
309-
310-
if (pci_get_dsn(pdev) != ctrl->dsn)
311-
return true;
312-
313-
return false;
314-
}
315-
316287
static int pciehp_resume_noirq(struct pcie_device *dev)
317288
{
318289
struct controller *ctrl = get_service_data(dev);

drivers/pci/hotplug/pciehp_hpc.c

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -563,29 +563,59 @@ void pciehp_power_off_slot(struct controller *ctrl)
563563
PCI_EXP_SLTCTL_PWR_OFF);
564564
}
565565

566-
static void pciehp_ignore_dpc_link_change(struct controller *ctrl,
567-
struct pci_dev *pdev, int irq)
566+
bool pciehp_device_replaced(struct controller *ctrl)
567+
{
568+
struct pci_dev *pdev __free(pci_dev_put) = NULL;
569+
u32 reg;
570+
571+
if (pci_dev_is_disconnected(ctrl->pcie->port))
572+
return false;
573+
574+
pdev = pci_get_slot(ctrl->pcie->port->subordinate, PCI_DEVFN(0, 0));
575+
if (!pdev)
576+
return true;
577+
578+
if (pci_read_config_dword(pdev, PCI_VENDOR_ID, &reg) ||
579+
reg != (pdev->vendor | (pdev->device << 16)) ||
580+
pci_read_config_dword(pdev, PCI_CLASS_REVISION, &reg) ||
581+
reg != (pdev->revision | (pdev->class << 8)))
582+
return true;
583+
584+
if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL &&
585+
(pci_read_config_dword(pdev, PCI_SUBSYSTEM_VENDOR_ID, &reg) ||
586+
reg != (pdev->subsystem_vendor | (pdev->subsystem_device << 16))))
587+
return true;
588+
589+
if (pci_get_dsn(pdev) != ctrl->dsn)
590+
return true;
591+
592+
return false;
593+
}
594+
595+
static void pciehp_ignore_link_change(struct controller *ctrl,
596+
struct pci_dev *pdev, int irq,
597+
u16 ignored_events)
568598
{
569599
/*
570600
* Ignore link changes which occurred while waiting for DPC recovery.
571601
* Could be several if DPC triggered multiple times consecutively.
602+
* Also ignore link changes caused by Secondary Bus Reset, etc.
572603
*/
573604
synchronize_hardirq(irq);
574-
atomic_and(~PCI_EXP_SLTSTA_DLLSC, &ctrl->pending_events);
605+
atomic_and(~ignored_events, &ctrl->pending_events);
575606
if (pciehp_poll_mode)
576607
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA,
577-
PCI_EXP_SLTSTA_DLLSC);
578-
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored (recovered by DPC)\n",
579-
slot_name(ctrl));
608+
ignored_events);
609+
ctrl_info(ctrl, "Slot(%s): Link Down/Up ignored\n", slot_name(ctrl));
580610

581611
/*
582612
* If the link is unexpectedly down after successful recovery,
583613
* the corresponding link change may have been ignored above.
584614
* Synthesize it to ensure that it is acted on.
585615
*/
586616
down_read_nested(&ctrl->reset_lock, ctrl->depth);
587-
if (!pciehp_check_link_active(ctrl))
588-
pciehp_request(ctrl, PCI_EXP_SLTSTA_DLLSC);
617+
if (!pciehp_check_link_active(ctrl) || pciehp_device_replaced(ctrl))
618+
pciehp_request(ctrl, ignored_events);
589619
up_read(&ctrl->reset_lock);
590620
}
591621

@@ -732,12 +762,19 @@ static irqreturn_t pciehp_ist(int irq, void *dev_id)
732762

733763
/*
734764
* Ignore Link Down/Up events caused by Downstream Port Containment
735-
* if recovery from the error succeeded.
765+
* if recovery succeeded, or caused by Secondary Bus Reset,
766+
* suspend to D3cold, firmware update, FPGA reconfiguration, etc.
736767
*/
737-
if ((events & PCI_EXP_SLTSTA_DLLSC) && pci_dpc_recovered(pdev) &&
768+
if ((events & (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC)) &&
769+
(pci_dpc_recovered(pdev) || pci_hp_spurious_link_change(pdev)) &&
738770
ctrl->state == ON_STATE) {
739-
events &= ~PCI_EXP_SLTSTA_DLLSC;
740-
pciehp_ignore_dpc_link_change(ctrl, pdev, irq);
771+
u16 ignored_events = PCI_EXP_SLTSTA_DLLSC;
772+
773+
if (!ctrl->inband_presence_disabled)
774+
ignored_events |= events & PCI_EXP_SLTSTA_PDC;
775+
776+
events &= ~ignored_events;
777+
pciehp_ignore_link_change(ctrl, pdev, irq, ignored_events);
741778
}
742779

743780
/*
@@ -902,31 +939,18 @@ int pciehp_reset_slot(struct hotplug_slot *hotplug_slot, bool probe)
902939
{
903940
struct controller *ctrl = to_ctrl(hotplug_slot);
904941
struct pci_dev *pdev = ctrl_dev(ctrl);
905-
u16 stat_mask = 0, ctrl_mask = 0;
906942
int rc;
907943

908944
if (probe)
909945
return 0;
910946

911947
down_write_nested(&ctrl->reset_lock, ctrl->depth);
912948

913-
if (!ATTN_BUTTN(ctrl)) {
914-
ctrl_mask |= PCI_EXP_SLTCTL_PDCE;
915-
stat_mask |= PCI_EXP_SLTSTA_PDC;
916-
}
917-
ctrl_mask |= PCI_EXP_SLTCTL_DLLSCE;
918-
stat_mask |= PCI_EXP_SLTSTA_DLLSC;
919-
920-
pcie_write_cmd(ctrl, 0, ctrl_mask);
921-
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
922-
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, 0);
949+
pci_hp_ignore_link_change(pdev);
923950

924951
rc = pci_bridge_secondary_bus_reset(ctrl->pcie->port);
925952

926-
pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, stat_mask);
927-
pcie_write_cmd_nowait(ctrl, ctrl_mask, ctrl_mask);
928-
ctrl_dbg(ctrl, "%s: SLOTCTRL %x write cmd %x\n", __func__,
929-
pci_pcie_cap(ctrl->pcie->port) + PCI_EXP_SLTCTL, ctrl_mask);
953+
pci_hp_unignore_link_change(pdev);
930954

931955
up_write(&ctrl->reset_lock);
932956
return rc;

drivers/pci/pci.h

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

228228
/* Functions for PCI Hotplug drivers to use */
229229
int pci_hp_add_bridge(struct pci_dev *dev);
230+
bool pci_hp_spurious_link_change(struct pci_dev *pdev);
230231

231232
#if defined(CONFIG_SYSFS) && defined(HAVE_PCI_LEGACY)
232233
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)
557558
#define PCI_DPC_RECOVERED 1
558559
#define PCI_DPC_RECOVERING 2
559560
#define PCI_DEV_REMOVED 3
561+
#define PCI_LINK_CHANGED 4
562+
#define PCI_LINK_CHANGING 5
560563
#define PCI_LINK_LBMS_SEEN 6
561564
#define PCI_DEV_ALLOW_BINDING 7
562565

include/linux/pci.h

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

1846+
#ifdef CONFIG_HOTPLUG_PCI
1847+
void pci_hp_ignore_link_change(struct pci_dev *pdev);
1848+
void pci_hp_unignore_link_change(struct pci_dev *pdev);
1849+
#else
1850+
static inline void pci_hp_ignore_link_change(struct pci_dev *pdev) { }
1851+
static inline void pci_hp_unignore_link_change(struct pci_dev *pdev) { }
1852+
#endif
1853+
18461854
#ifdef CONFIG_PCIEAER
18471855
bool pci_aer_available(void);
18481856
#else

0 commit comments

Comments
 (0)