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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 != (pdev->vendor | (pdev->device << 16)) ||
|
||||
pci_read_config_dword(pdev, PCI_CLASS_REVISION, ®) ||
|
||||
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 != (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);
|
||||
|
||||
@@ -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 != (pdev->vendor | (pdev->device << 16)) ||
|
||||
pci_read_config_dword(pdev, PCI_CLASS_REVISION, ®) ||
|
||||
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 != (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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user