]> Pileus Git - ~andy/linux/blobdiff - drivers/pci/hotplug/acpiphp_glue.c
Merge tag 'sunxi-fixes-for-3.14' of https://github.com/mripard/linux into fixes
[~andy/linux] / drivers / pci / hotplug / acpiphp_glue.c
index cd929aed3613314068ed7a4baf2bb6e5c6cb9f1f..7c7a388c85ab3679732f7971552790057abec4f5 100644 (file)
@@ -210,10 +210,29 @@ static void post_dock_fixups(acpi_handle not_used, u32 event, void *data)
        }
 }
 
+static void dock_event(acpi_handle handle, u32 type, void *data)
+{
+       struct acpiphp_context *context;
+
+       mutex_lock(&acpiphp_context_lock);
+       context = acpiphp_get_context(handle);
+       if (!context || WARN_ON(context->handle != handle)
+           || context->func.parent->is_going_away) {
+               mutex_unlock(&acpiphp_context_lock);
+               return;
+       }
+       get_bridge(context->func.parent);
+       acpiphp_put_context(context);
+       mutex_unlock(&acpiphp_context_lock);
+
+       hotplug_event(handle, type, data);
+
+       put_bridge(context->func.parent);
+}
 
 static const struct acpi_dock_ops acpiphp_dock_ops = {
        .fixup = post_dock_fixups,
-       .handler = hotplug_event,
+       .handler = dock_event,
 };
 
 /* Check whether the PCI device is managed by native PCIe hotplug driver */
@@ -441,7 +460,9 @@ static void cleanup_bridge(struct acpiphp_bridge *bridge)
        list_del(&bridge->list);
        mutex_unlock(&bridge_mutex);
 
+       mutex_lock(&acpiphp_context_lock);
        bridge->is_going_away = true;
+       mutex_unlock(&acpiphp_context_lock);
 }
 
 /**
@@ -709,6 +730,17 @@ static unsigned int get_slot_status(struct acpiphp_slot *slot)
        return (unsigned int)sta;
 }
 
+static inline bool device_status_valid(unsigned int sta)
+{
+       /*
+        * ACPI spec says that _STA may return bit 0 clear with bit 3 set
+        * if the device is valid but does not require a device driver to be
+        * loaded (Section 6.3.7 of ACPI 5.0A).
+        */
+       unsigned int mask = ACPI_STA_DEVICE_ENABLED | ACPI_STA_DEVICE_FUNCTIONING;
+       return (sta & mask) == mask;
+}
+
 /**
  * trim_stale_devices - remove PCI devices that are not responding.
  * @dev: PCI device to start walking the hierarchy from.
@@ -724,7 +756,7 @@ static void trim_stale_devices(struct pci_dev *dev)
                unsigned long long sta;
 
                status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
-               alive = (ACPI_SUCCESS(status) && sta == ACPI_STA_ALL)
+               alive = (ACPI_SUCCESS(status) && device_status_valid(sta))
                        || acpiphp_no_hotplug(handle);
        }
        if (!alive) {
@@ -742,7 +774,7 @@ static void trim_stale_devices(struct pci_dev *dev)
 
                /* The device is a bridge. so check the bus below it. */
                pm_runtime_get_sync(&dev->dev);
-               list_for_each_entry_safe(child, tmp, &bus->devices, bus_list)
+               list_for_each_entry_safe_reverse(child, tmp, &bus->devices, bus_list)
                        trim_stale_devices(child);
 
                pm_runtime_put(&dev->dev);
@@ -771,10 +803,10 @@ static void acpiphp_check_bridge(struct acpiphp_bridge *bridge)
                mutex_lock(&slot->crit_sect);
                if (slot_no_hotplug(slot)) {
                        ; /* do nothing */
-               } else if (get_slot_status(slot) == ACPI_STA_ALL) {
+               } else if (device_status_valid(get_slot_status(slot))) {
                        /* remove stale devices if any */
-                       list_for_each_entry_safe(dev, tmp, &bus->devices,
-                                                bus_list)
+                       list_for_each_entry_safe_reverse(dev, tmp,
+                                                        &bus->devices, bus_list)
                                if (PCI_SLOT(dev->devfn) == slot->device)
                                        trim_stale_devices(dev);
 
@@ -805,7 +837,7 @@ static void acpiphp_sanitize_bus(struct pci_bus *bus)
        int i;
        unsigned long type_mask = IORESOURCE_IO | IORESOURCE_MEM;
 
-       list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) {
+       list_for_each_entry_safe_reverse(dev, tmp, &bus->devices, bus_list) {
                for (i=0; i<PCI_BRIDGE_RESOURCES; i++) {
                        struct resource *res = &dev->resource[i];
                        if ((res->flags & type_mask) && !res->start &&
@@ -829,7 +861,11 @@ void acpiphp_check_host_bridge(acpi_handle handle)
 
        bridge = acpiphp_handle_to_bridge(handle);
        if (bridge) {
+               pci_lock_rescan_remove();
+
                acpiphp_check_bridge(bridge);
+
+               pci_unlock_rescan_remove();
                put_bridge(bridge);
        }
 }
@@ -852,6 +888,7 @@ static void hotplug_event(acpi_handle handle, u32 type, void *data)
 
        mutex_unlock(&acpiphp_context_lock);
 
+       pci_lock_rescan_remove();
        acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
 
        switch (type) {
@@ -905,6 +942,7 @@ static void hotplug_event(acpi_handle handle, u32 type, void *data)
                break;
        }
 
+       pci_unlock_rescan_remove();
        if (bridge)
                put_bridge(bridge);
 }
@@ -915,11 +953,9 @@ 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);
@@ -937,6 +973,7 @@ static void handle_hotplug_event(acpi_handle handle, u32 type, void *data)
 {
        struct acpiphp_context *context;
        u32 ost_code = ACPI_OST_SC_SUCCESS;
+       acpi_status status;
 
        switch (type) {
        case ACPI_NOTIFY_BUS_CHECK:
@@ -972,13 +1009,20 @@ static void handle_hotplug_event(acpi_handle handle, u32 type, void *data)
 
        mutex_lock(&acpiphp_context_lock);
        context = acpiphp_get_context(handle);
-       if (context && !WARN_ON(context->handle != handle)) {
-               get_bridge(context->func.parent);
-               acpiphp_put_context(context);
-               acpi_hotplug_execute(hotplug_event_work, context, type);
+       if (!context || WARN_ON(context->handle != handle)
+           || context->func.parent->is_going_away)
+               goto err_out;
+
+       get_bridge(context->func.parent);
+       acpiphp_put_context(context);
+       status = acpi_hotplug_execute(hotplug_event_work, context, type);
+       if (ACPI_SUCCESS(status)) {
                mutex_unlock(&acpiphp_context_lock);
                return;
        }
+       put_bridge(context->func.parent);
+
+ err_out:
        mutex_unlock(&acpiphp_context_lock);
        ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE;