From 9d16947b75831acd317ab9a53e0e94d160731d33 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:22:18 +0100 Subject: [PATCH 01/10] PCI: Add global pci_lock_rescan_remove() There are multiple PCI device addition and removal code paths that may be run concurrently with the generic PCI bus rescan and device removal that can be triggered via sysfs. If that happens, it may lead to multiple different, potentially dangerous race conditions. The most straightforward way to address those problems is to run the code in question under the same lock that is used by the generic rescan/remove code in pci-sysfs.c. To prepare for those changes, move the definition of the global PCI remove/rescan lock to probe.c and provide global wrappers, pci_lock_rescan_remove() and pci_unlock_rescan_remove(), allowing drivers to manipulate that lock. Also provide pci_stop_and_remove_bus_device_locked() for the callers of pci_stop_and_remove_bus_device() who only need to hold the rescan/remove lock around it. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-sysfs.c | 19 +++++++------------ drivers/pci/probe.c | 18 ++++++++++++++++++ drivers/pci/remove.c | 8 ++++++++ include/linux/pci.h | 3 +++ 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index c91e6c18debc..276ef9c18802 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -297,7 +297,6 @@ msi_bus_store(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(msi_bus); -static DEFINE_MUTEX(pci_remove_rescan_mutex); static ssize_t bus_rescan_store(struct bus_type *bus, const char *buf, size_t count) { @@ -308,10 +307,10 @@ static ssize_t bus_rescan_store(struct bus_type *bus, const char *buf, return -EINVAL; if (val) { - mutex_lock(&pci_remove_rescan_mutex); + pci_lock_rescan_remove(); while ((b = pci_find_next_bus(b)) != NULL) pci_rescan_bus(b); - mutex_unlock(&pci_remove_rescan_mutex); + pci_unlock_rescan_remove(); } return count; } @@ -342,9 +341,9 @@ dev_rescan_store(struct device *dev, struct device_attribute *attr, return -EINVAL; if (val) { - mutex_lock(&pci_remove_rescan_mutex); + pci_lock_rescan_remove(); pci_rescan_bus(pdev->bus); - mutex_unlock(&pci_remove_rescan_mutex); + pci_unlock_rescan_remove(); } return count; } @@ -354,11 +353,7 @@ static struct device_attribute dev_rescan_attr = __ATTR(rescan, static void remove_callback(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - - mutex_lock(&pci_remove_rescan_mutex); - pci_stop_and_remove_bus_device(pdev); - mutex_unlock(&pci_remove_rescan_mutex); + pci_stop_and_remove_bus_device_locked(to_pci_dev(dev)); } static ssize_t @@ -395,12 +390,12 @@ dev_bus_rescan_store(struct device *dev, struct device_attribute *attr, return -EINVAL; if (val) { - mutex_lock(&pci_remove_rescan_mutex); + pci_lock_rescan_remove(); if (!pci_is_root_bus(bus) && list_empty(&bus->devices)) pci_rescan_bus_bridge_resize(bus->self); else pci_rescan_bus(bus); - mutex_unlock(&pci_remove_rescan_mutex); + pci_unlock_rescan_remove(); } return count; } diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c86c8638d3c4..04796c056d12 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2024,6 +2024,24 @@ EXPORT_SYMBOL(pci_scan_slot); EXPORT_SYMBOL(pci_scan_bridge); EXPORT_SYMBOL_GPL(pci_scan_child_bus); +/* + * pci_rescan_bus(), pci_rescan_bus_bridge_resize() and PCI device removal + * routines should always be executed under this mutex. + */ +static DEFINE_MUTEX(pci_rescan_remove_lock); + +void pci_lock_rescan_remove(void) +{ + mutex_lock(&pci_rescan_remove_lock); +} +EXPORT_SYMBOL_GPL(pci_lock_rescan_remove); + +void pci_unlock_rescan_remove(void) +{ + mutex_unlock(&pci_rescan_remove_lock); +} +EXPORT_SYMBOL_GPL(pci_unlock_rescan_remove); + static int __init pci_sort_bf_cmp(const struct device *d_a, const struct device *d_b) { const struct pci_dev *a = to_pci_dev(d_a); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index f452148e6d55..10fa13f9e309 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -95,6 +95,14 @@ void pci_stop_and_remove_bus_device(struct pci_dev *dev) } EXPORT_SYMBOL(pci_stop_and_remove_bus_device); +void pci_stop_and_remove_bus_device_locked(struct pci_dev *dev) +{ + pci_lock_rescan_remove(); + pci_stop_and_remove_bus_device(dev); + pci_unlock_rescan_remove(); +} +EXPORT_SYMBOL_GPL(pci_stop_and_remove_bus_device_locked); + void pci_stop_root_bus(struct pci_bus *bus) { struct pci_dev *child, *tmp; diff --git a/include/linux/pci.h b/include/linux/pci.h index 9e3ec8b951b7..2087e6b35545 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -775,6 +775,7 @@ struct pci_dev *pci_dev_get(struct pci_dev *dev); void pci_dev_put(struct pci_dev *dev); void pci_remove_bus(struct pci_bus *b); void pci_stop_and_remove_bus_device(struct pci_dev *dev); +void pci_stop_and_remove_bus_device_locked(struct pci_dev *dev); void pci_stop_root_bus(struct pci_bus *bus); void pci_remove_root_bus(struct pci_bus *bus); void pci_setup_cardbus(struct pci_bus *bus); @@ -1011,6 +1012,8 @@ void set_pcie_hotplug_bridge(struct pci_dev *pdev); int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap); unsigned int pci_rescan_bus_bridge_resize(struct pci_dev *bridge); unsigned int pci_rescan_bus(struct pci_bus *bus); +void pci_lock_rescan_remove(void); +void pci_unlock_rescan_remove(void); /* Vital product data routines */ ssize_t pci_read_vpd(struct pci_dev *dev, loff_t pos, size_t count, void *buf); From 7a3bb55ebd1cb04f08dd68cf60af24b434f8cc2a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:23:14 +0100 Subject: [PATCH 02/10] ACPI / PCI: Use global PCI rescan-remove locking in PCI root hotplug Multiple race conditions are possible between the addition and removal of PCI devices during ACPI PCI host bridge hotplug and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make the ACPI PCI host bridge addition and removal code use global PCI rescan-remove locking. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/acpi/pci_root.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 0703bff5e60e..07ee02aa3c51 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -596,7 +596,9 @@ static int acpi_pci_root_add(struct acpi_device *device, pci_assign_unassigned_root_bus_resources(root->bus); } + pci_lock_rescan_remove(); pci_bus_add_devices(root->bus); + pci_unlock_rescan_remove(); return 1; end: @@ -608,6 +610,8 @@ static void acpi_pci_root_remove(struct acpi_device *device) { struct acpi_pci_root *root = acpi_driver_data(device); + pci_lock_rescan_remove(); + pci_stop_root_bus(root->bus); device_set_run_wake(root->bus->bridge, false); @@ -615,6 +619,8 @@ static void acpi_pci_root_remove(struct acpi_device *device) pci_remove_root_bus(root->bus); + pci_unlock_rescan_remove(); + kfree(root); } From 9217a984671e8a7e38f1822eba754898066e2ae1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:24:41 +0100 Subject: [PATCH 03/10] ACPI / hotplug / PCI: Use global PCI rescan-remove locking Multiple race conditions are possible between the ACPI-based PCI hotplug (ACPIPHP) and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make the ACPIPHP code use global PCI rescan-remove locking. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/acpiphp.h | 5 +++- drivers/pci/hotplug/acpiphp_core.c | 2 +- drivers/pci/hotplug/acpiphp_glue.c | 43 ++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/drivers/pci/hotplug/acpiphp.h b/drivers/pci/hotplug/acpiphp.h index 1592dbe4f904..b6162be4df40 100644 --- a/drivers/pci/hotplug/acpiphp.h +++ b/drivers/pci/hotplug/acpiphp.h @@ -77,6 +77,8 @@ struct acpiphp_bridge { /* PCI-to-PCI bridge device */ struct pci_dev *pci_dev; + + bool is_going_away; }; @@ -150,6 +152,7 @@ struct acpiphp_attention_info /* slot flags */ #define SLOT_ENABLED (0x00000001) +#define SLOT_IS_GOING_AWAY (0x00000002) /* function flags */ @@ -169,7 +172,7 @@ void acpiphp_unregister_hotplug_slot(struct acpiphp_slot *slot); typedef int (*acpiphp_callback)(struct acpiphp_slot *slot, void *data); int acpiphp_enable_slot(struct acpiphp_slot *slot); -int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot); +int acpiphp_disable_slot(struct acpiphp_slot *slot); u8 acpiphp_get_power_status(struct acpiphp_slot *slot); u8 acpiphp_get_attention_status(struct acpiphp_slot *slot); u8 acpiphp_get_latch_status(struct acpiphp_slot *slot); diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c index dca66bc44578..728c31f4c2c5 100644 --- a/drivers/pci/hotplug/acpiphp_core.c +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -156,7 +156,7 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) pr_debug("%s - physical_slot = %s\n", __func__, slot_name(slot)); /* disable the specified slot */ - return acpiphp_disable_and_eject_slot(slot->acpi_slot); + return acpiphp_disable_slot(slot->acpi_slot); } diff --git a/drivers/pci/hotplug/acpiphp_glue.c b/drivers/pci/hotplug/acpiphp_glue.c index 1cf605f67673..641ba6761bd7 100644 --- a/drivers/pci/hotplug/acpiphp_glue.c +++ b/drivers/pci/hotplug/acpiphp_glue.c @@ -430,6 +430,7 @@ static void cleanup_bridge(struct acpiphp_bridge *bridge) pr_err("failed to remove notify handler\n"); } } + slot->flags |= SLOT_IS_GOING_AWAY; if (slot->slot) acpiphp_unregister_hotplug_slot(slot); } @@ -437,6 +438,8 @@ static void cleanup_bridge(struct acpiphp_bridge *bridge) mutex_lock(&bridge_mutex); list_del(&bridge->list); mutex_unlock(&bridge_mutex); + + bridge->is_going_away = true; } /** @@ -736,6 +739,10 @@ static void acpiphp_check_bridge(struct acpiphp_bridge *bridge) { struct acpiphp_slot *slot; + /* Bail out if the bridge is going away. */ + if (bridge->is_going_away) + return; + list_for_each_entry(slot, &bridge->slots, node) { struct pci_bus *bus = slot->bus; struct pci_dev *dev, *tmp; @@ -805,6 +812,8 @@ void acpiphp_check_host_bridge(acpi_handle handle) } } +static int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot); + static void hotplug_event(acpi_handle handle, u32 type, void *data) { struct acpiphp_context *context = data; @@ -834,6 +843,9 @@ static void hotplug_event(acpi_handle handle, u32 type, void *data) } else { struct acpiphp_slot *slot = func->slot; + if (slot->flags & SLOT_IS_GOING_AWAY) + break; + mutex_lock(&slot->crit_sect); enable_slot(slot); mutex_unlock(&slot->crit_sect); @@ -849,6 +861,9 @@ static void hotplug_event(acpi_handle handle, u32 type, void *data) struct acpiphp_slot *slot = func->slot; int ret; + if (slot->flags & SLOT_IS_GOING_AWAY) + break; + /* * Check if anything has changed in the slot and rescan * from the parent if that's the case. @@ -878,9 +893,11 @@ static void hotplug_event_work(void *data, u32 type) acpi_handle handle = context->handle; acpi_scan_lock_acquire(); + pci_lock_rescan_remove(); hotplug_event(handle, type, context); + pci_unlock_rescan_remove(); acpi_scan_lock_release(); acpi_evaluate_hotplug_ost(handle, type, ACPI_OST_SC_SUCCESS, NULL); put_bridge(context->func.parent); @@ -1048,12 +1065,19 @@ void acpiphp_remove_slots(struct pci_bus *bus) */ int acpiphp_enable_slot(struct acpiphp_slot *slot) { + pci_lock_rescan_remove(); + + if (slot->flags & SLOT_IS_GOING_AWAY) + return -ENODEV; + mutex_lock(&slot->crit_sect); /* configure all functions */ if (!(slot->flags & SLOT_ENABLED)) enable_slot(slot); mutex_unlock(&slot->crit_sect); + + pci_unlock_rescan_remove(); return 0; } @@ -1061,10 +1085,12 @@ int acpiphp_enable_slot(struct acpiphp_slot *slot) * acpiphp_disable_and_eject_slot - power off and eject slot * @slot: ACPI PHP slot */ -int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot) +static int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot) { struct acpiphp_func *func; - int retval = 0; + + if (slot->flags & SLOT_IS_GOING_AWAY) + return -ENODEV; mutex_lock(&slot->crit_sect); @@ -1082,9 +1108,18 @@ int acpiphp_disable_and_eject_slot(struct acpiphp_slot *slot) } mutex_unlock(&slot->crit_sect); - return retval; + return 0; } +int acpiphp_disable_slot(struct acpiphp_slot *slot) +{ + int ret; + + pci_lock_rescan_remove(); + ret = acpiphp_disable_and_eject_slot(slot); + pci_unlock_rescan_remove(); + return ret; +} /* * slot enabled: 1 @@ -1095,7 +1130,6 @@ u8 acpiphp_get_power_status(struct acpiphp_slot *slot) return (slot->flags & SLOT_ENABLED); } - /* * latch open: 1 * latch closed: 0 @@ -1105,7 +1139,6 @@ u8 acpiphp_get_latch_status(struct acpiphp_slot *slot) return !(get_slot_status(slot) & ACPI_STA_DEVICE_UI); } - /* * adapter presence : 1 * absence : 0 From 5ef68e8867ca9d979644d05c6045b2c79d8989de Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:25:34 +0100 Subject: [PATCH 04/10] pcmcia: Use global PCI rescan-remove locking Multiple race conditions are possible between the cardbus PCI device addition and removal and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make the cardbus code use global PCI rescan-remove locking. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pcmcia/cardbus.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/pcmcia/cardbus.c b/drivers/pcmcia/cardbus.c index b2a98cdbd0d2..8bde61952d20 100644 --- a/drivers/pcmcia/cardbus.c +++ b/drivers/pcmcia/cardbus.c @@ -70,6 +70,8 @@ int __ref cb_alloc(struct pcmcia_socket *s) struct pci_dev *dev; unsigned int max, pass; + pci_lock_rescan_remove(); + s->functions = pci_scan_slot(bus, PCI_DEVFN(0, 0)); pci_fixup_cardbus(bus); @@ -93,6 +95,7 @@ int __ref cb_alloc(struct pcmcia_socket *s) pci_bus_add_devices(bus); + pci_unlock_rescan_remove(); return 0; } @@ -115,6 +118,10 @@ void cb_free(struct pcmcia_socket *s) if (!bus) return; + pci_lock_rescan_remove(); + list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) pci_stop_and_remove_bus_device(dev); + + pci_unlock_rescan_remove(); } From c4ec84c7db0e4b01ed40cc2388f16ae5c6513cc0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 14 Jan 2014 12:03:14 -0700 Subject: [PATCH 05/10] PCI: hotplug: Use global PCI rescan-remove locking Multiple race conditions are possible between PCI hotplug and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make PCI hotplug use global PCI rescan-remove locking. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/cpci_hotplug_pci.c | 14 ++++++++++++-- drivers/pci/hotplug/cpqphp_pci.c | 8 +++++++- drivers/pci/hotplug/ibmphp_core.c | 13 +++++++++++-- drivers/pci/hotplug/pciehp_pci.c | 17 +++++++++++++---- drivers/pci/hotplug/rpadlpar_core.c | 19 ++++++++++++++----- drivers/pci/hotplug/rpaphp_core.c | 4 ++++ drivers/pci/hotplug/s390_pci_hpc.c | 4 +++- drivers/pci/hotplug/sgi_hotplug.c | 5 +++++ drivers/pci/hotplug/shpchp_pci.c | 18 ++++++++++++++---- 9 files changed, 83 insertions(+), 19 deletions(-) diff --git a/drivers/pci/hotplug/cpci_hotplug_pci.c b/drivers/pci/hotplug/cpci_hotplug_pci.c index d3add9819f63..8c1464851768 100644 --- a/drivers/pci/hotplug/cpci_hotplug_pci.c +++ b/drivers/pci/hotplug/cpci_hotplug_pci.c @@ -254,9 +254,12 @@ int __ref cpci_configure_slot(struct slot *slot) { struct pci_dev *dev; struct pci_bus *parent; + int ret = 0; dbg("%s - enter", __func__); + pci_lock_rescan_remove(); + if (slot->dev == NULL) { dbg("pci_dev null, finding %02x:%02x:%x", slot->bus->number, PCI_SLOT(slot->devfn), PCI_FUNC(slot->devfn)); @@ -277,7 +280,8 @@ int __ref cpci_configure_slot(struct slot *slot) slot->dev = pci_get_slot(slot->bus, slot->devfn); if (slot->dev == NULL) { err("Could not find PCI device for slot %02x", slot->number); - return -ENODEV; + ret = -ENODEV; + goto out; } } parent = slot->dev->bus; @@ -294,8 +298,10 @@ int __ref cpci_configure_slot(struct slot *slot) pci_bus_add_devices(parent); + out: + pci_unlock_rescan_remove(); dbg("%s - exit", __func__); - return 0; + return ret; } int cpci_unconfigure_slot(struct slot* slot) @@ -308,6 +314,8 @@ int cpci_unconfigure_slot(struct slot* slot) return -ENODEV; } + pci_lock_rescan_remove(); + list_for_each_entry_safe(dev, temp, &slot->bus->devices, bus_list) { if (PCI_SLOT(dev->devfn) != PCI_SLOT(slot->devfn)) continue; @@ -318,6 +326,8 @@ int cpci_unconfigure_slot(struct slot* slot) pci_dev_put(slot->dev); slot->dev = NULL; + pci_unlock_rescan_remove(); + dbg("%s - exit", __func__); return 0; } diff --git a/drivers/pci/hotplug/cpqphp_pci.c b/drivers/pci/hotplug/cpqphp_pci.c index 6e4a12c91adb..a3e3c2002b58 100644 --- a/drivers/pci/hotplug/cpqphp_pci.c +++ b/drivers/pci/hotplug/cpqphp_pci.c @@ -86,6 +86,8 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func) struct pci_bus *child; int num; + pci_lock_rescan_remove(); + if (func->pci_dev == NULL) func->pci_dev = pci_get_bus_and_slot(func->bus,PCI_DEVFN(func->device, func->function)); @@ -100,7 +102,7 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func) func->pci_dev = pci_get_bus_and_slot(func->bus, PCI_DEVFN(func->device, func->function)); if (func->pci_dev == NULL) { dbg("ERROR: pci_dev still null\n"); - return 0; + goto out; } } @@ -113,6 +115,8 @@ int cpqhp_configure_device (struct controller* ctrl, struct pci_func* func) pci_dev_put(func->pci_dev); + out: + pci_unlock_rescan_remove(); return 0; } @@ -123,6 +127,7 @@ int cpqhp_unconfigure_device(struct pci_func* func) dbg("%s: bus/dev/func = %x/%x/%x\n", __func__, func->bus, func->device, func->function); + pci_lock_rescan_remove(); for (j=0; j<8 ; j++) { struct pci_dev* temp = pci_get_bus_and_slot(func->bus, PCI_DEVFN(func->device, j)); if (temp) { @@ -130,6 +135,7 @@ int cpqhp_unconfigure_device(struct pci_func* func) pci_stop_and_remove_bus_device(temp); } } + pci_unlock_rescan_remove(); return 0; } diff --git a/drivers/pci/hotplug/ibmphp_core.c b/drivers/pci/hotplug/ibmphp_core.c index efdc13adbe41..cf3ac1e4b099 100644 --- a/drivers/pci/hotplug/ibmphp_core.c +++ b/drivers/pci/hotplug/ibmphp_core.c @@ -718,6 +718,8 @@ static void ibm_unconfigure_device(struct pci_func *func) func->device, func->function); debug("func->device << 3 | 0x0 = %x\n", func->device << 3 | 0x0); + pci_lock_rescan_remove(); + for (j = 0; j < 0x08; j++) { temp = pci_get_bus_and_slot(func->busno, (func->device << 3) | j); if (temp) { @@ -725,7 +727,10 @@ static void ibm_unconfigure_device(struct pci_func *func) pci_dev_put(temp); } } + pci_dev_put(func->dev); + + pci_unlock_rescan_remove(); } /* @@ -780,6 +785,8 @@ static int ibm_configure_device(struct pci_func *func) int flag = 0; /* this is to make sure we don't double scan the bus, for bridged devices primarily */ + pci_lock_rescan_remove(); + if (!(bus_structure_fixup(func->busno))) flag = 1; if (func->dev == NULL) @@ -789,7 +796,7 @@ static int ibm_configure_device(struct pci_func *func) if (func->dev == NULL) { struct pci_bus *bus = pci_find_bus(0, func->busno); if (!bus) - return 0; + goto out; num = pci_scan_slot(bus, PCI_DEVFN(func->device, func->function)); @@ -800,7 +807,7 @@ static int ibm_configure_device(struct pci_func *func) PCI_DEVFN(func->device, func->function)); if (func->dev == NULL) { err("ERROR... : pci_dev still NULL\n"); - return 0; + goto out; } } if (!(flag) && (func->dev->hdr_type == PCI_HEADER_TYPE_BRIDGE)) { @@ -810,6 +817,8 @@ static int ibm_configure_device(struct pci_func *func) pci_bus_add_devices(child); } + out: + pci_unlock_rescan_remove(); return 0; } diff --git a/drivers/pci/hotplug/pciehp_pci.c b/drivers/pci/hotplug/pciehp_pci.c index 198355112ee7..b07d7cc2d697 100644 --- a/drivers/pci/hotplug/pciehp_pci.c +++ b/drivers/pci/hotplug/pciehp_pci.c @@ -39,22 +39,26 @@ int pciehp_configure_device(struct slot *p_slot) struct pci_dev *dev; struct pci_dev *bridge = p_slot->ctrl->pcie->port; struct pci_bus *parent = bridge->subordinate; - int num; + int num, ret = 0; struct controller *ctrl = p_slot->ctrl; + pci_lock_rescan_remove(); + dev = pci_get_slot(parent, PCI_DEVFN(0, 0)); if (dev) { ctrl_err(ctrl, "Device %s already exists " "at %04x:%02x:00, cannot hot-add\n", pci_name(dev), pci_domain_nr(parent), parent->number); pci_dev_put(dev); - return -EINVAL; + ret = -EINVAL; + goto out; } num = pci_scan_slot(parent, PCI_DEVFN(0, 0)); if (num == 0) { ctrl_err(ctrl, "No new device found\n"); - return -ENODEV; + ret = -ENODEV; + goto out; } list_for_each_entry(dev, &parent->devices, bus_list) @@ -73,7 +77,9 @@ int pciehp_configure_device(struct slot *p_slot) pci_bus_add_devices(parent); - return 0; + out: + pci_unlock_rescan_remove(); + return ret; } int pciehp_unconfigure_device(struct slot *p_slot) @@ -90,6 +96,8 @@ int pciehp_unconfigure_device(struct slot *p_slot) __func__, pci_domain_nr(parent), parent->number); pciehp_get_adapter_status(p_slot, &presence); + pci_lock_rescan_remove(); + /* * Stopping an SR-IOV PF device removes all the associated VFs, * which will update the bus->devices list and confuse the @@ -124,5 +132,6 @@ int pciehp_unconfigure_device(struct slot *p_slot) pci_dev_put(dev); } + pci_unlock_rescan_remove(); return rc; } diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c index e9c044d15add..4fcdeedda31b 100644 --- a/drivers/pci/hotplug/rpadlpar_core.c +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -354,10 +354,15 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) { struct pci_bus *bus; struct slot *slot; + int ret = 0; + + pci_lock_rescan_remove(); bus = pcibios_find_pci_bus(dn); - if (!bus) - return -EINVAL; + if (!bus) { + ret = -EINVAL; + goto out; + } pr_debug("PCI: Removing PCI slot below EADS bridge %s\n", bus->self ? pci_name(bus->self) : ""); @@ -371,7 +376,8 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) printk(KERN_ERR "%s: unable to remove hotplug slot %s\n", __func__, drc_name); - return -EIO; + ret = -EIO; + goto out; } } @@ -382,7 +388,8 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) if (pcibios_unmap_io_space(bus)) { printk(KERN_ERR "%s: failed to unmap bus range\n", __func__); - return -ERANGE; + ret = -ERANGE; + goto out; } /* Remove the EADS bridge device itself */ @@ -390,7 +397,9 @@ int dlpar_remove_pci_slot(char *drc_name, struct device_node *dn) pr_debug("PCI: Now removing bridge device %s\n", pci_name(bus->self)); pci_stop_and_remove_bus_device(bus->self); - return 0; + out: + pci_unlock_rescan_remove(); + return ret; } /** diff --git a/drivers/pci/hotplug/rpaphp_core.c b/drivers/pci/hotplug/rpaphp_core.c index b7fc5c9255a5..4796c15fba94 100644 --- a/drivers/pci/hotplug/rpaphp_core.c +++ b/drivers/pci/hotplug/rpaphp_core.c @@ -398,7 +398,9 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) return retval; if (state == PRESENT) { + pci_lock_rescan_remove(); pcibios_add_pci_devices(slot->bus); + pci_unlock_rescan_remove(); slot->state = CONFIGURED; } else if (state == EMPTY) { slot->state = EMPTY; @@ -418,7 +420,9 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) if (slot->state == NOT_CONFIGURED) return -EINVAL; + pci_lock_rescan_remove(); pcibios_remove_pci_devices(slot->bus); + pci_unlock_rescan_remove(); vm_unmap_aliases(); slot->state = NOT_CONFIGURED; diff --git a/drivers/pci/hotplug/s390_pci_hpc.c b/drivers/pci/hotplug/s390_pci_hpc.c index 3c7eb5dd91c6..8d2ce22151eb 100644 --- a/drivers/pci/hotplug/s390_pci_hpc.c +++ b/drivers/pci/hotplug/s390_pci_hpc.c @@ -80,7 +80,9 @@ static int enable_slot(struct hotplug_slot *hotplug_slot) goto out_deconfigure; pci_scan_slot(slot->zdev->bus, ZPCI_DEVFN); + pci_lock_rescan_remove(); pci_bus_add_devices(slot->zdev->bus); + pci_unlock_rescan_remove(); return rc; @@ -98,7 +100,7 @@ static int disable_slot(struct hotplug_slot *hotplug_slot) return -EIO; if (slot->zdev->pdev) - pci_stop_and_remove_bus_device(slot->zdev->pdev); + pci_stop_and_remove_bus_device_locked(slot->zdev->pdev); rc = zpci_disable_device(slot->zdev); if (rc) diff --git a/drivers/pci/hotplug/sgi_hotplug.c b/drivers/pci/hotplug/sgi_hotplug.c index 5b05a68cca6c..613043f7576f 100644 --- a/drivers/pci/hotplug/sgi_hotplug.c +++ b/drivers/pci/hotplug/sgi_hotplug.c @@ -459,12 +459,15 @@ static int enable_slot(struct hotplug_slot *bss_hotplug_slot) acpi_scan_lock_release(); } + pci_lock_rescan_remove(); + /* Call the driver for the new device */ pci_bus_add_devices(slot->pci_bus); /* Call the drivers for the new devices subordinate to PPB */ if (new_ppb) pci_bus_add_devices(new_bus); + pci_unlock_rescan_remove(); mutex_unlock(&sn_hotplug_mutex); if (rc == 0) @@ -540,6 +543,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot) acpi_scan_lock_release(); } + pci_lock_rescan_remove(); /* Free the SN resources assigned to the Linux device.*/ list_for_each_entry_safe(dev, temp, &slot->pci_bus->devices, bus_list) { if (PCI_SLOT(dev->devfn) != slot->device_num + 1) @@ -550,6 +554,7 @@ static int disable_slot(struct hotplug_slot *bss_hotplug_slot) pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } + pci_unlock_rescan_remove(); /* Remove the SSDT for the slot from the ACPI namespace */ if (SN_ACPI_BASE_SUPPORT() && ssdt_id) { diff --git a/drivers/pci/hotplug/shpchp_pci.c b/drivers/pci/hotplug/shpchp_pci.c index b0e83132542e..2bf69fe1926c 100644 --- a/drivers/pci/hotplug/shpchp_pci.c +++ b/drivers/pci/hotplug/shpchp_pci.c @@ -40,7 +40,9 @@ int __ref shpchp_configure_device(struct slot *p_slot) struct controller *ctrl = p_slot->ctrl; struct pci_dev *bridge = ctrl->pci_dev; struct pci_bus *parent = bridge->subordinate; - int num; + int num, ret = 0; + + pci_lock_rescan_remove(); dev = pci_get_slot(parent, PCI_DEVFN(p_slot->device, 0)); if (dev) { @@ -48,13 +50,15 @@ int __ref shpchp_configure_device(struct slot *p_slot) "at %04x:%02x:%02x, cannot hot-add\n", pci_name(dev), pci_domain_nr(parent), p_slot->bus, p_slot->device); pci_dev_put(dev); - return -EINVAL; + ret = -EINVAL; + goto out; } num = pci_scan_slot(parent, PCI_DEVFN(p_slot->device, 0)); if (num == 0) { ctrl_err(ctrl, "No new device found\n"); - return -ENODEV; + ret = -ENODEV; + goto out; } list_for_each_entry(dev, &parent->devices, bus_list) { @@ -75,7 +79,9 @@ int __ref shpchp_configure_device(struct slot *p_slot) pci_bus_add_devices(parent); - return 0; + out: + pci_unlock_rescan_remove(); + return ret; } int shpchp_unconfigure_device(struct slot *p_slot) @@ -89,6 +95,8 @@ int shpchp_unconfigure_device(struct slot *p_slot) ctrl_dbg(ctrl, "%s: domain:bus:dev = %04x:%02x:%02x\n", __func__, pci_domain_nr(parent), p_slot->bus, p_slot->device); + pci_lock_rescan_remove(); + list_for_each_entry_safe(dev, temp, &parent->devices, bus_list) { if (PCI_SLOT(dev->devfn) != p_slot->device) continue; @@ -108,6 +116,8 @@ int shpchp_unconfigure_device(struct slot *p_slot) pci_stop_and_remove_bus_device(dev); pci_dev_put(dev); } + + pci_unlock_rescan_remove(); return rc; } From 8b9ec1da6a310b438516468c669a7e1be046b18c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:27:08 +0100 Subject: [PATCH 06/10] platform / x86: Use global PCI rescan-remove locking Multiple race conditions are possible between the rfkill hotplug in the asus-wmi and eeepc-laptop drivers and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make asus-wmi and eeepc-laptop use global PCI rescan-remove locking around the rfkill hotplug. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/platform/x86/asus-wmi.c | 2 ++ drivers/platform/x86/eeepc-laptop.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 19c313b056c3..6fe268f6af91 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -606,6 +606,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus) mutex_unlock(&asus->wmi_lock); mutex_lock(&asus->hotplug_lock); + pci_lock_rescan_remove(); if (asus->wlan.rfkill) rfkill_set_sw_state(asus->wlan.rfkill, blocked); @@ -656,6 +657,7 @@ static void asus_rfkill_hotplug(struct asus_wmi *asus) } out_unlock: + pci_unlock_rescan_remove(); mutex_unlock(&asus->hotplug_lock); } diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index aefcc32e5634..538521b00948 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -592,6 +592,7 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) rfkill_set_sw_state(eeepc->wlan_rfkill, blocked); mutex_lock(&eeepc->hotplug_lock); + pci_lock_rescan_remove(); if (eeepc->hotplug_slot) { port = acpi_get_pci_dev(handle); @@ -649,6 +650,7 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) } out_unlock: + pci_unlock_rescan_remove(); mutex_unlock(&eeepc->hotplug_lock); } From 64cdb4181d87900c6560f8c72d02c7ccf75c9070 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:27:56 +0100 Subject: [PATCH 07/10] MPT / PCI: Use pci_stop_and_remove_bus_device_locked() Race conditions are theoretically possible between the MPT PCI device removal and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make the MPT PCI code use pci_stop_and_remove_bus_device_locked(). Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/message/fusion/mptbase.c | 2 +- drivers/scsi/mpt2sas/mpt2sas_base.c | 2 +- drivers/scsi/mpt3sas/mpt3sas_base.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/message/fusion/mptbase.c b/drivers/message/fusion/mptbase.c index 767ff4d839f4..570b18a113ff 100644 --- a/drivers/message/fusion/mptbase.c +++ b/drivers/message/fusion/mptbase.c @@ -346,7 +346,7 @@ static int mpt_remove_dead_ioc_func(void *arg) if ((pdev == NULL)) return -1; - pci_stop_and_remove_bus_device(pdev); + pci_stop_and_remove_bus_device_locked(pdev); return 0; } diff --git a/drivers/scsi/mpt2sas/mpt2sas_base.c b/drivers/scsi/mpt2sas/mpt2sas_base.c index 3901edc35812..bde63f7452bd 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_base.c +++ b/drivers/scsi/mpt2sas/mpt2sas_base.c @@ -128,7 +128,7 @@ static int mpt2sas_remove_dead_ioc_func(void *arg) pdev = ioc->pdev; if ((pdev == NULL)) return -1; - pci_stop_and_remove_bus_device(pdev); + pci_stop_and_remove_bus_device_locked(pdev); return 0; } diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.c b/drivers/scsi/mpt3sas/mpt3sas_base.c index fa785062e97b..0cf4f7000f94 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.c +++ b/drivers/scsi/mpt3sas/mpt3sas_base.c @@ -131,7 +131,7 @@ static int mpt3sas_remove_dead_ioc_func(void *arg) pdev = ioc->pdev; if ((pdev == NULL)) return -1; - pci_stop_and_remove_bus_device(pdev); + pci_stop_and_remove_bus_device_locked(pdev); return 0; } From 1c2042c83aa7af10579b71a1fe5e22bbae69f08c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 15 Jan 2014 14:33:20 +0100 Subject: [PATCH 08/10] powerpc/eeh: Use global PCI rescan-remove locking Race conditions are theoretically possible between the PCI device addition and removal in the PPC64 PCI error recovery driver and the generic PCI bus rescan and device removal that can be triggered via sysfs. To avoid those race conditions make PPC64 PCI error recovery driver use global PCI rescan-remove locking around PCI device addition and removal. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- arch/powerpc/kernel/eeh_driver.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c index 36bed5a12750..c17f90d0f73c 100644 --- a/arch/powerpc/kernel/eeh_driver.c +++ b/arch/powerpc/kernel/eeh_driver.c @@ -369,7 +369,9 @@ static void *eeh_rmv_device(void *data, void *userdata) edev->mode |= EEH_DEV_DISCONNECTED; (*removed)++; + pci_lock_rescan_remove(); pci_stop_and_remove_bus_device(dev); + pci_unlock_rescan_remove(); return NULL; } @@ -416,10 +418,13 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) * into pcibios_add_pci_devices(). */ eeh_pe_state_mark(pe, EEH_PE_KEEP); - if (bus) + if (bus) { + pci_lock_rescan_remove(); pcibios_remove_pci_devices(bus); - else if (frozen_bus) + pci_unlock_rescan_remove(); + } else if (frozen_bus) { eeh_pe_dev_traverse(pe, eeh_rmv_device, &removed); + } /* Reset the pci controller. (Asserts RST#; resets config space). * Reconfigure bridges and devices. Don't try to bring the system @@ -429,6 +434,8 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) if (rc) return rc; + pci_lock_rescan_remove(); + /* Restore PE */ eeh_ops->configure_bridge(pe); eeh_pe_restore_bars(pe); @@ -462,6 +469,7 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) pe->tstamp = tstamp; pe->freeze_count = cnt; + pci_unlock_rescan_remove(); return 0; } @@ -618,8 +626,11 @@ static void eeh_handle_normal_event(struct eeh_pe *pe) eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); /* Shut down the device drivers for good. */ - if (frozen_bus) + if (frozen_bus) { + pci_lock_rescan_remove(); pcibios_remove_pci_devices(frozen_bus); + pci_unlock_rescan_remove(); + } } static void eeh_handle_special_event(void) @@ -692,6 +703,7 @@ static void eeh_handle_special_event(void) if (rc == 2 || rc == 1) eeh_handle_normal_event(pe); else { + pci_lock_rescan_remove(); list_for_each_entry_safe(hose, tmp, &hose_list, list_node) { phb_pe = eeh_phb_pe_get(hose); @@ -703,6 +715,7 @@ static void eeh_handle_special_event(void) eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); pcibios_remove_pci_devices(bus); } + pci_unlock_rescan_remove(); } } From a83919e0940f6eb8f77ab1d602a063f8a6703117 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 10 Jan 2014 15:29:19 +0100 Subject: [PATCH 09/10] xen/pcifront: Use global PCI rescan-remove locking Multiple race conditions are possible between the Xen pcifront device addition and removal and the generic PCI device addition and removal that can be triggered via sysfs. To avoid those race conditions make the Xen pcifront code use global PCI rescan-remove locking. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/xen-pcifront.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c index f7197a790341..d1cd60f51f87 100644 --- a/drivers/pci/xen-pcifront.c +++ b/drivers/pci/xen-pcifront.c @@ -471,12 +471,15 @@ static int pcifront_scan_root(struct pcifront_device *pdev, } pcifront_init_sd(sd, domain, bus, pdev); + pci_lock_rescan_remove(); + b = pci_scan_bus_parented(&pdev->xdev->dev, bus, &pcifront_bus_ops, sd); if (!b) { dev_err(&pdev->xdev->dev, "Error creating PCI Frontend Bus!\n"); err = -ENOMEM; + pci_unlock_rescan_remove(); goto err_out; } @@ -494,6 +497,7 @@ static int pcifront_scan_root(struct pcifront_device *pdev, /* Create SysFS and notify udev of the devices. Aka: "going live" */ pci_bus_add_devices(b); + pci_unlock_rescan_remove(); return err; err_out: @@ -556,6 +560,7 @@ static void pcifront_free_roots(struct pcifront_device *pdev) dev_dbg(&pdev->xdev->dev, "cleaning up root buses\n"); + pci_lock_rescan_remove(); list_for_each_entry_safe(bus_entry, t, &pdev->root_buses, list) { list_del(&bus_entry->list); @@ -568,6 +573,7 @@ static void pcifront_free_roots(struct pcifront_device *pdev) kfree(bus_entry); } + pci_unlock_rescan_remove(); } static pci_ers_result_t pcifront_common_process(int cmd, @@ -1043,8 +1049,10 @@ static int pcifront_detach_devices(struct pcifront_device *pdev) domain, bus, slot, func); continue; } + pci_lock_rescan_remove(); pci_stop_and_remove_bus_device(pci_dev); pci_dev_put(pci_dev); + pci_unlock_rescan_remove(); dev_dbg(&pdev->xdev->dev, "PCI device %04x:%02x:%02x.%d removed.\n", From 8a4c5c329de716996eea03d93753ccbb5406072b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 14 Jan 2014 12:04:51 -0700 Subject: [PATCH 10/10] PCI: Check parent kobject in pci_destroy_dev() If pci_stop_and_remove_bus_device() is run concurrently for a device and its parent bridge via remove_callback(), both code paths attempt to acquire pci_rescan_remove_lock. If the child device removal acquires it first, there will be no problems. However, if the parent bridge removal acquires it first, it will eventually execute pci_destroy_dev() for the child device, but that device object will not be freed yet due to the reference held by the concurrent child removal. Consequently, both pci_stop_bus_device() and pci_remove_bus_device() will be executed for that device unnecessarily and pci_destroy_dev() will see a corrupted list head in that object. Moreover, an excess put_device() will be executed for that device in that case which may lead to a use-after-free in the final kobject_put() done by sysfs_schedule_callback_work(). To avoid that problem, make pci_destroy_dev() check if the device's parent kobject is NULL, which only happens after device_del() has already run for it. Make pci_destroy_dev() return immediately whithout doing anything in that case. Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/pci/remove.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 10fa13f9e309..4ff36bfa785e 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -20,6 +20,9 @@ static void pci_stop_dev(struct pci_dev *dev) static void pci_destroy_dev(struct pci_dev *dev) { + if (!dev->dev.kobj.parent) + return; + device_del(&dev->dev); put_device(&dev->dev);