]> rtime.felk.cvut.cz Git - linux-imx.git/blobdiff - drivers/acpi/scan.c
Merge branch 'acpi-hotplug'
[linux-imx.git] / drivers / acpi / scan.c
index 27da63061e11ae88c628242e0eed71fc0e1d7588..db118b1ad3e8cf30791feec2ca568bab20a5795f 100644 (file)
@@ -27,6 +27,12 @@ extern struct acpi_device *acpi_root;
 
 #define ACPI_IS_ROOT_DEVICE(device)    (!(device)->parent)
 
+/*
+ * If set, devices will be hot-removed even if they cannot be put offline
+ * gracefully (from the kernel's standpoint).
+ */
+bool acpi_force_hot_remove;
+
 static const char *dummy_hid = "device";
 
 static LIST_HEAD(acpi_device_list);
@@ -120,12 +126,78 @@ acpi_device_modalias_show(struct device *dev, struct device_attribute *attr, cha
 }
 static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL);
 
+static acpi_status acpi_bus_offline_companions(acpi_handle handle, u32 lvl,
+                                              void *data, void **ret_p)
+{
+       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))
+               return AE_OK;
+
+       mutex_lock(&device->physical_node_lock);
+
+       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) {
+                       pn->put_online = !ret;
+               } else {
+                       *ret_p = pn->dev;
+                       if (second_pass) {
+                               status = AE_ERROR;
+                               break;
+                       }
+               }
+       }
+
+       mutex_unlock(&device->physical_node_lock);
+
+       return status;
+}
+
+static acpi_status acpi_bus_online_companions(acpi_handle handle, u32 lvl,
+                                             void *data, void **ret_p)
+{
+       struct acpi_device *device = NULL;
+       struct acpi_device_physical_node *pn;
+
+       if (acpi_bus_get_device(handle, &device))
+               return AE_OK;
+
+       mutex_lock(&device->physical_node_lock);
+
+       list_for_each_entry(pn, &device->physical_node_list, node)
+               if (pn->put_online) {
+                       device_online(pn->dev);
+                       pn->put_online = false;
+               }
+
+       mutex_unlock(&device->physical_node_lock);
+
+       return AE_OK;
+}
+
 static int acpi_scan_hot_remove(struct acpi_device *device)
 {
        acpi_handle handle = device->handle;
        acpi_handle not_used;
        struct acpi_object_list arg_list;
        union acpi_object arg;
+       struct device *errdev;
        acpi_status status;
        unsigned long long sta;
 
@@ -136,10 +208,53 @@ static int acpi_scan_hot_remove(struct acpi_device *device)
                return -EINVAL;
        }
 
+       lock_device_hotplug();
+
+       /*
+        * 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,
+                                   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,
                "Hot-removing device %s...\n", dev_name(&device->dev)));
 
        acpi_bus_trim(device);
+
+       unlock_device_hotplug();
+
        /* Device node has been unregistered. */
        put_device(&device->dev);
        device = NULL;
@@ -236,6 +351,7 @@ static void acpi_scan_bus_device_check(acpi_handle handle, u32 ost_source)
        int error;
 
        mutex_lock(&acpi_scan_lock);
+       lock_device_hotplug();
 
        acpi_bus_get_device(handle, &device);
        if (device) {
@@ -259,6 +375,7 @@ static void acpi_scan_bus_device_check(acpi_handle handle, u32 ost_source)
                kobject_uevent(&device->dev.kobj, KOBJ_ONLINE);
 
  out:
+       unlock_device_hotplug();
        acpi_evaluate_hotplug_ost(handle, ost_source, ost_code, NULL);
        mutex_unlock(&acpi_scan_lock);
 }
@@ -952,7 +1069,6 @@ int acpi_device_add(struct acpi_device *device,
                printk(KERN_ERR PREFIX "Error creating sysfs interface for device %s\n",
                       dev_name(&device->dev));
 
-       device->removal_type = ACPI_BUS_REMOVAL_NORMAL;
        return 0;
 
  err:
@@ -1939,7 +2055,6 @@ static acpi_status acpi_bus_device_detach(acpi_handle handle, u32 lvl_not_used,
        if (!acpi_bus_get_device(handle, &device)) {
                struct acpi_scan_handler *dev_handler = device->handler;
 
-               device->removal_type = ACPI_BUS_REMOVAL_EJECT;
                if (dev_handler) {
                        if (dev_handler->detach)
                                dev_handler->detach(device);
@@ -2038,6 +2153,7 @@ int __init acpi_scan_init(void)
 
        acpi_pci_root_init();
        acpi_pci_link_init();
+       acpi_processor_init();
        acpi_platform_init();
        acpi_lpss_init();
        acpi_container_init();