Skip to content
69 changes: 69 additions & 0 deletions drivers/pci/hotplug/pci_hotplug_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,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);
}
Comment on lines +580 to +628

static int __init pci_hotplug_init(void)
{
int result;
Expand Down
5 changes: 5 additions & 0 deletions drivers/pci/hotplug/pciehp.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ extern int pciehp_poll_time;
/**
* struct controller - PCIe hotplug controller
* @pcie: pointer to the controller's PCIe port service device
* @dsn: cached copy of Device Serial Number of Function 0 in the hotplug slot
* (PCIe r6.2 sec 7.9.3); used to determine whether a hotplugged device
* was replaced with a different one during system sleep
* @slot_cap: cached copy of the Slot Capabilities register
* @inband_presence_disabled: In-Band Presence Detect Disable supported by
* controller and disabled per spec recommendation (PCIe r5.0, appendix I
Expand Down Expand Up @@ -87,6 +90,7 @@ extern int pciehp_poll_time;
*/
struct controller {
struct pcie_device *pcie;
u64 dsn;

u32 slot_cap; /* capabilities and quirks */
unsigned int inband_presence_disabled:1;
Expand Down Expand Up @@ -183,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);
Expand Down
16 changes: 15 additions & 1 deletion drivers/pci/hotplug/pciehp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,23 @@ static int pciehp_resume_noirq(struct pcie_device *dev)
ctrl->cmd_busy = true;

/* clear spurious events from rediscovery of inserted card */
if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE)
if (ctrl->state == ON_STATE || ctrl->state == BLINKINGOFF_STATE) {
pcie_clear_hotplug_events(ctrl);

/*
* If hotplugged device was replaced with a different one
* during system sleep, mark the old device disconnected
* (to prevent its driver from accessing the new device)
* and synthesize a Presence Detect Changed event.
*/
if (pciehp_device_replaced(ctrl)) {
ctrl_dbg(ctrl, "device replaced during system sleep\n");
pci_walk_bus(ctrl->pcie->port->subordinate,
pci_dev_set_disconnected, NULL);
pciehp_request(ctrl, PCI_EXP_SLTSTA_PDC);
}
}

return 0;
}
#endif
Expand Down
83 changes: 56 additions & 27 deletions drivers/pci/hotplug/pciehp_hpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -559,29 +559,59 @@ 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,
* the corresponding link change may have been ignored above.
* 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);
}

Expand Down Expand Up @@ -728,12 +758,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 |= PCI_EXP_SLTSTA_PDC;

events &= ~ignored_events;
pciehp_ignore_link_change(ctrl, pdev, irq, ignored_events);
}

/*
Expand Down Expand Up @@ -898,31 +935,18 @@ 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)
return 0;

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;
Expand Down Expand Up @@ -1056,6 +1080,11 @@ struct controller *pcie_init(struct pcie_device *dev)
}
}

pdev = pci_get_slot(subordinate, PCI_DEVFN(0, 0));
if (pdev)
ctrl->dsn = pci_get_dsn(pdev);
pci_dev_put(pdev);

return ctrl;
}

Expand Down
4 changes: 4 additions & 0 deletions drivers/pci/hotplug/pciehp_pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ int pciehp_configure_device(struct controller *ctrl)
pci_bus_add_devices(parent);
down_read_nested(&ctrl->reset_lock, ctrl->depth);

dev = pci_get_slot(parent, PCI_DEVFN(0, 0));
ctrl->dsn = pci_get_dsn(dev);
pci_dev_put(dev);
Comment on lines +76 to +77

out:
pci_unlock_rescan_remove();
return ret;
Expand Down
48 changes: 34 additions & 14 deletions drivers/pci/iov.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,23 +285,16 @@ const struct attribute_group sriov_vf_dev_attr_group = {
.is_visible = sriov_vf_attrs_are_visible,
};

int pci_iov_add_virtfn(struct pci_dev *dev, int id)
static struct pci_dev *pci_iov_scan_device(struct pci_dev *dev, int id,
struct pci_bus *bus)
{
int i;
int rc = -ENOMEM;
u64 size;
struct pci_dev *virtfn;
struct resource *res;
struct pci_sriov *iov = dev->sriov;
struct pci_bus *bus;

bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
if (!bus)
goto failed;
struct pci_dev *virtfn;
int rc;

virtfn = pci_alloc_dev(bus);
if (!virtfn)
goto failed0;
return ERR_PTR(-ENOMEM);

virtfn->devfn = pci_iov_virtfn_devfn(dev, id);
virtfn->vendor = dev->vendor;
Expand All @@ -314,8 +307,35 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id)
pci_read_vf_config_common(virtfn);

rc = pci_setup_device(virtfn);
if (rc)
goto failed1;
if (rc) {
pci_dev_put(dev);
pci_bus_put(virtfn->bus);
kfree(virtfn);
return ERR_PTR(rc);
}

return virtfn;
}

int pci_iov_add_virtfn(struct pci_dev *dev, int id)
{
struct pci_bus *bus;
struct pci_dev *virtfn;
struct resource *res;
int rc, i;
u64 size;

bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
if (!bus) {
rc = -ENOMEM;
goto failed;
}

virtfn = pci_iov_scan_device(dev, id, bus);
if (IS_ERR(virtfn)) {
rc = PTR_ERR(virtfn);
goto failed0;
}

virtfn->dev.parent = dev->dev.parent;
virtfn->multifunction = 0;
Expand Down
8 changes: 8 additions & 0 deletions drivers/pci/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@ static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
#define PCI_DEV_ADDED 0
#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

static inline void pci_dev_assign_added(struct pci_dev *dev, bool added)
{
Expand All @@ -418,6 +421,11 @@ static inline bool pci_dev_is_added(const struct pci_dev *dev)
return test_bit(PCI_DEV_ADDED, &dev->priv_flags);
}

static inline bool pci_dev_test_and_set_removed(struct pci_dev *dev)
{
return test_and_set_bit(PCI_DEV_REMOVED, &dev->priv_flags);
}

#ifdef CONFIG_PCIEAER
#include <linux/aer.h>

Expand Down
4 changes: 2 additions & 2 deletions drivers/pci/pcie/dpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ bool pci_dpc_recovered(struct pci_dev *pdev)
/*
* Need a timeout in case DPC never completes due to failure of
* dpc_wait_rp_inactive(). The spec doesn't mandate a time limit,
* but reports indicate that DPC completes within 4 seconds.
* but reports indicate that DPC completes within 16 seconds.
*/
wait_event_timeout(dpc_completed_waitqueue, dpc_completed(pdev),
msecs_to_jiffies(4000));
msecs_to_jiffies(16000));

return test_and_clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags);
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/pci/remove.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static void pci_stop_dev(struct pci_dev *dev)

static void pci_destroy_dev(struct pci_dev *dev)
{
if (!dev->dev.kobj.parent)
if (pci_dev_test_and_set_removed(dev))
return;

device_del(&dev->dev);
Expand Down
Loading
Loading