]> rtime.felk.cvut.cz Git - linux-imx.git/blobdiff - drivers/acpi/scan.c
ACPI / scan: Add second pass of companion offlining to hot-remove code
[linux-imx.git] / drivers / acpi / scan.c
index ba8ee6cbf0f17eddcc029237518117d8e2a25b67..2959fe1ce43e71203ea440279d9869a959ffee1c 100644 (file)
@@ -131,6 +131,7 @@ static acpi_status acpi_bus_offline_companions(acpi_handle handle, u32 lvl,
 {
        struct acpi_device *device = NULL;
        struct acpi_device_physical_node *pn;
+       bool second_pass = (bool)data;
        acpi_status status = AE_OK;
 
        if (acpi_bus_get_device(handle, &device))
@@ -141,15 +142,26 @@ static acpi_status acpi_bus_offline_companions(acpi_handle handle, u32 lvl,
        list_for_each_entry(pn, &device->physical_node_list, node) {
                int ret;
 
+               if (second_pass) {
+                       /* Skip devices offlined by the first pass. */
+                       if (pn->put_online)
+                               continue;
+               } else {
+                       pn->put_online = false;
+               }
                ret = device_offline(pn->dev);
                if (acpi_force_hot_remove)
                        continue;
 
-               if (ret < 0) {
-                       status = AE_ERROR;
-                       break;
+               if (ret >= 0) {
+                       pn->put_online = !ret;
+               } else {
+                       *ret_p = pn->dev;
+                       if (second_pass) {
+                               status = AE_ERROR;
+                               break;
+                       }
                }
-               pn->put_online = !ret;
        }
 
        mutex_unlock(&device->physical_node_lock);
@@ -185,6 +197,7 @@ static int acpi_scan_hot_remove(struct acpi_device *device)
        acpi_handle not_used;
        struct acpi_object_list arg_list;
        union acpi_object arg;
+       struct device *errdev;
        acpi_status status;
        unsigned long long sta;
 
@@ -197,22 +210,42 @@ static int acpi_scan_hot_remove(struct acpi_device *device)
 
        lock_device_hotplug();
 
-       status = acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,
-                                    NULL, acpi_bus_offline_companions, NULL,
-                                    NULL);
-       if (ACPI_SUCCESS(status) || acpi_force_hot_remove)
-               status = acpi_bus_offline_companions(handle, 0, NULL, NULL);
-
-       if (ACPI_FAILURE(status) && !acpi_force_hot_remove) {
-               acpi_bus_online_companions(handle, 0, NULL, NULL);
+       /*
+        * Carry out two passes here and ignore errors in the first pass,
+        * because if the devices in question are memory blocks and
+        * CONFIG_MEMCG is set, one of the blocks may hold data structures
+        * that the other blocks depend on, but it is not known in advance which
+        * block holds them.
+        *
+        * If the first pass is successful, the second one isn't needed, though.
+        */
+       errdev = NULL;
+       acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,
+                           NULL, acpi_bus_offline_companions,
+                           (void *)false, (void **)&errdev);
+       acpi_bus_offline_companions(handle, 0, (void *)false, (void **)&errdev);
+       if (errdev) {
+               errdev = NULL;
                acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,
-                                   acpi_bus_online_companions, NULL, NULL,
-                                   NULL);
-
-               unlock_device_hotplug();
-
-               put_device(&device->dev);
-               return -EBUSY;
+                                   NULL, acpi_bus_offline_companions,
+                                   (void *)true , (void **)&errdev);
+               if (!errdev || acpi_force_hot_remove)
+                       acpi_bus_offline_companions(handle, 0, (void *)true,
+                                                   (void **)&errdev);
+
+               if (errdev && !acpi_force_hot_remove) {
+                       dev_warn(errdev, "Offline failed.\n");
+                       acpi_bus_online_companions(handle, 0, NULL, NULL);
+                       acpi_walk_namespace(ACPI_TYPE_ANY, handle,
+                                           ACPI_UINT32_MAX,
+                                           acpi_bus_online_companions, NULL,
+                                           NULL, NULL);
+
+                       unlock_device_hotplug();
+
+                       put_device(&device->dev);
+                       return -EBUSY;
+               }
        }
 
        ACPI_DEBUG_PRINT((ACPI_DB_INFO,