summaryrefslogtreecommitdiff
path: root/drivers/pci/hotplug
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/hotplug')
-rw-r--r--drivers/pci/hotplug/pci_hotplug_core.c73
-rw-r--r--drivers/pci/hotplug/pciehp.h1
-rw-r--r--drivers/pci/hotplug/pciehp_core.c29
-rw-r--r--drivers/pci/hotplug/pciehp_ctrl.c2
-rw-r--r--drivers/pci/hotplug/pciehp_hpc.c78
-rw-r--r--drivers/pci/hotplug/s390_pci_hpc.c3
6 files changed, 123 insertions, 63 deletions
diff --git a/drivers/pci/hotplug/pci_hotplug_core.c b/drivers/pci/hotplug/pci_hotplug_core.c
index d30f1316c98e..fadcf98a8a66 100644
--- a/drivers/pci/hotplug/pci_hotplug_core.c
+++ b/drivers/pci/hotplug/pci_hotplug_core.c
@@ -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;
diff --git a/drivers/pci/hotplug/pciehp.h b/drivers/pci/hotplug/pciehp.h
index 273dd8c66f4e..debc79b0adfb 100644
--- a/drivers/pci/hotplug/pciehp.h
+++ b/drivers/pci/hotplug/pciehp.h
@@ -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);
diff --git a/drivers/pci/hotplug/pciehp_core.c b/drivers/pci/hotplug/pciehp_core.c
index 997841c69893..f59baa912970 100644
--- a/drivers/pci/hotplug/pciehp_core.c
+++ b/drivers/pci/hotplug/pciehp_core.c
@@ -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);
diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c
index d603a7aa7483..bcc938d4420f 100644
--- a/drivers/pci/hotplug/pciehp_ctrl.c
+++ b/drivers/pci/hotplug/pciehp_ctrl.c
@@ -131,7 +131,7 @@ static void remove_board(struct controller *ctrl, bool safe_removal)
INDICATOR_NOOP);
/* Don't carry LBMS indications across */
- pcie_reset_lbms_count(ctrl->pcie->port);
+ pcie_reset_lbms(ctrl->pcie->port);
}
static int pciehp_enable_slot(struct controller *ctrl);
diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c
index 8a09fb6083e2..ebd342bda235 100644
--- a/drivers/pci/hotplug/pciehp_hpc.c
+++ b/drivers/pci/hotplug/pciehp_hpc.c
@@ -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;
diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c
index 055518ee354d..d9996516f49e 100644
--- a/drivers/pci/hotplug/s390_pci_hpc.c
+++ b/drivers/pci/hotplug/s390_pci_hpc.c
@@ -59,16 +59,15 @@ static int disable_slot(struct hotplug_slot *hotplug_slot)
pdev = pci_get_slot(zdev->zbus->bus, zdev->devfn);
if (pdev && pci_num_vf(pdev)) {
- pci_dev_put(pdev);
rc = -EBUSY;
goto out;
}
rc = zpci_deconfigure_device(zdev);
out:
- mutex_unlock(&zdev->state_lock);
if (pdev)
pci_dev_put(pdev);
+ mutex_unlock(&zdev->state_lock);
return rc;
}