ACPI / hotplug / PCI: Use global PCI rescan-remove locking
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Fri, 10 Jan 2014 14:24:41 +0000 (15:24 +0100)
committerBjorn Helgaas <bhelgaas@google.com>
Tue, 14 Jan 2014 19:14:25 +0000 (12:14 -0700)
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 <rafael.j.wysocki@intel.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/hotplug/acpiphp.h
drivers/pci/hotplug/acpiphp_core.c
drivers/pci/hotplug/acpiphp_glue.c

index 1592dbe4f90461dbfa82be205f334eb0e8ecc6f6..b6162be4df40a2f6a614d983491037dd865b2c8d 100644 (file)
@@ -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);
index dca66bc44578e661987e2356043e092e5097f14e..728c31f4c2c50399593bee71ee6a4c277df849b6 100644 (file)
@@ -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);
 }
 
 
index 1cf605f6767357947e9a097981caa7d7869ca2f0..641ba6761bd756ca9aa24471fdc53abfc1aaf041 100644 (file)
@@ -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