From: Rafael J. Wysocki <rafael.j.wyso...@intel.com>

Modify the generic ACPI hotplug code to be able to check if devices
scheduled for hot-removal may be gracefully removed from the system
using the device offline/online mechanism introduced previously.

Namely, make acpi_scan_hot_remove() which handles device hot-removal
call device_offline() for all physical companions of the ACPI device
nodes involved in the operation and check the results.  If any of
the device_offline() calls fails, the function will not progress to
the removal phase (which cannot be aborted), unless its (new) force
argument is set (in case of a failing offline it will put the devices
offlined by it back online).

In support of the 'forced' hot-removal, add a new sysfs attribute
'force_remove' that will reside in every ACPI hotplug profile
present under /sys/firmware/acpi/hotplug/.

Signed-off-by: Rafael J. Wysocki <rafael.j.wyso...@intel.com>
---
 Documentation/ABI/testing/sysfs-firmware-acpi |    9 +-
 drivers/acpi/internal.h                       |    2 
 drivers/acpi/scan.c                           |   97 ++++++++++++++++++++++++--
 drivers/acpi/sysfs.c                          |   27 +++++++
 include/acpi/acpi_bus.h                       |    3 
 5 files changed, 131 insertions(+), 7 deletions(-)

Index: linux-pm/drivers/acpi/sysfs.c
===================================================================
--- linux-pm.orig/drivers/acpi/sysfs.c
+++ linux-pm/drivers/acpi/sysfs.c
@@ -745,8 +745,35 @@ static struct kobj_attribute hotplug_ena
        __ATTR(enabled, S_IRUGO | S_IWUSR, hotplug_enabled_show,
                hotplug_enabled_store);
 
+static ssize_t hotplug_force_remove_show(struct kobject *kobj,
+                                        struct kobj_attribute *attr, char *buf)
+{
+       struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj);
+
+       return sprintf(buf, "%d\n", hotplug->force_remove);
+}
+
+static ssize_t hotplug_force_remove_store(struct kobject *kobj,
+                                         struct kobj_attribute *attr,
+                                         const char *buf, size_t size)
+{
+       struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj);
+       unsigned int val;
+
+       if (kstrtouint(buf, 10, &val) || val > 1)
+               return -EINVAL;
+
+       acpi_scan_hotplug_force_remove(hotplug, val);
+       return size;
+}
+
+static struct kobj_attribute hotplug_force_remove_attr =
+       __ATTR(force_remove, S_IRUGO | S_IWUSR, hotplug_force_remove_show,
+               hotplug_force_remove_store);
+
 static struct attribute *hotplug_profile_attrs[] = {
        &hotplug_enabled_attr.attr,
+       &hotplug_force_remove_attr.attr,
        NULL
 };
 
Index: linux-pm/drivers/acpi/internal.h
===================================================================
--- linux-pm.orig/drivers/acpi/internal.h
+++ linux-pm/drivers/acpi/internal.h
@@ -52,6 +52,8 @@ void acpi_sysfs_add_hotplug_profile(stru
 int acpi_scan_add_handler_with_hotplug(struct acpi_scan_handler *handler,
                                       const char *hotplug_profile_name);
 void acpi_scan_hotplug_enabled(struct acpi_hotplug_profile *hotplug, bool val);
+void acpi_scan_hotplug_force_remove(struct acpi_hotplug_profile *hotplug,
+                                   bool val);
 
 #ifdef CONFIG_DEBUG_FS
 extern struct dentry *acpi_debugfs_dir;
Index: linux-pm/include/acpi/acpi_bus.h
===================================================================
--- linux-pm.orig/include/acpi/acpi_bus.h
+++ linux-pm/include/acpi/acpi_bus.h
@@ -97,6 +97,7 @@ enum acpi_hotplug_mode {
 struct acpi_hotplug_profile {
        struct kobject kobj;
        bool enabled:1;
+       bool force_remove:1;
        enum acpi_hotplug_mode mode;
 };
 
@@ -286,6 +287,7 @@ struct acpi_device_physical_node {
        u8 node_id;
        struct list_head node;
        struct device *dev;
+       bool put_online:1;
 };
 
 /* set maximum of physical nodes to 32 for expansibility */
@@ -346,6 +348,7 @@ struct acpi_bus_event {
 struct acpi_eject_event {
        struct acpi_device      *device;
        u32             event;
+       bool            force;
 };
 
 struct acpi_hp_work {
Index: linux-pm/drivers/acpi/scan.c
===================================================================
--- linux-pm.orig/drivers/acpi/scan.c
+++ linux-pm/drivers/acpi/scan.c
@@ -120,7 +120,61 @@ acpi_device_modalias_show(struct device
 }
 static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL);
 
-static int acpi_scan_hot_remove(struct acpi_device *device)
+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 force = *((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;
+
+               ret = device_offline(pn->dev);
+               if (force)
+                       continue;
+
+               if (ret < 0) {
+                       status = AE_ERROR;
+                       break;
+               }
+               pn->put_online = !ret;
+       }
+
+       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, bool force)
 {
        acpi_handle handle = device->handle;
        acpi_handle not_used;
@@ -136,10 +190,30 @@ static int acpi_scan_hot_remove(struct a
                return -EINVAL;
        }
 
+       lock_device_offline();
+
+       status = acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX,
+                                    NULL, acpi_bus_offline_companions, &force,
+                                    NULL);
+       if (ACPI_SUCCESS(status) || force)
+               status = acpi_bus_offline_companions(handle, 0, &force, NULL);
+
+       if (ACPI_FAILURE(status) && !force) {
+               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_offline();
+               return -EBUSY;
+       }
+
        ACPI_DEBUG_PRINT((ACPI_DB_INFO,
                "Hot-removing device %s...\n", dev_name(&device->dev)));
 
        acpi_bus_trim(device);
+
+       unlock_device_offline();
+
        /* Device node has been unregistered. */
        put_device(&device->dev);
        device = NULL;
@@ -214,7 +288,8 @@ static void acpi_bus_device_eject(void *
                int error;
 
                get_device(&device->dev);
-               error = acpi_scan_hot_remove(device);
+               error = acpi_scan_hot_remove(device,
+                                            handler->hotplug.force_remove);
                if (error)
                        goto err_out;
        }
@@ -353,7 +428,7 @@ void acpi_bus_hot_remove_device(void *co
 
        mutex_lock(&acpi_scan_lock);
 
-       error = acpi_scan_hot_remove(device);
+       error = acpi_scan_hot_remove(device, ej_event->force);
        if (error && handle)
                acpi_evaluate_hotplug_ost(handle, ej_event->event,
                                          ACPI_OST_SC_NON_SPECIFIC_FAILURE,
@@ -422,7 +497,7 @@ acpi_eject_store(struct device *d, struc
                /* Eject initiated by user space. */
                ost_source = ACPI_OST_EC_OSPM_EJECT;
        }
-       ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL);
+       ej_event = kzalloc(sizeof(*ej_event), GFP_KERNEL);
        if (!ej_event) {
                ret = -ENOMEM;
                goto err_out;
@@ -431,6 +506,9 @@ acpi_eject_store(struct device *d, struc
                                  ACPI_OST_SC_EJECT_IN_PROGRESS, NULL);
        ej_event->device = acpi_device;
        ej_event->event = ost_source;
+       if (acpi_device->handler)
+               ej_event->force = acpi_device->handler->hotplug.force_remove;
+
        get_device(&acpi_device->dev);
        status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device, ej_event);
        if (ACPI_FAILURE(status)) {
@@ -1769,9 +1847,18 @@ void acpi_scan_hotplug_enabled(struct ac
                return;
 
        mutex_lock(&acpi_scan_lock);
-
        hotplug->enabled = val;
+       mutex_unlock(&acpi_scan_lock);
+}
 
+void acpi_scan_hotplug_force_remove(struct acpi_hotplug_profile *hotplug,
+                                   bool val)
+{
+       if (!!hotplug->force_remove == !!val)
+               return;
+
+       mutex_lock(&acpi_scan_lock);
+       hotplug->force_remove = val;
        mutex_unlock(&acpi_scan_lock);
 }
 
Index: linux-pm/Documentation/ABI/testing/sysfs-firmware-acpi
===================================================================
--- linux-pm.orig/Documentation/ABI/testing/sysfs-firmware-acpi
+++ linux-pm/Documentation/ABI/testing/sysfs-firmware-acpi
@@ -40,8 +40,13 @@ Description:
                        effectively disables hotplug for the correspoinding
                        class of devices.
 
-               The value of the above attribute is an integer number: 1 (set)
-               or 0 (unset).  Attempts to write any other values to it will
+               force_remove: If set, the ACPI core will force hot-removal
+                       for the given class of devices regardless of whether or
+                       not they may be gracefully removed from the system
+                       (according to the kernel).
+
+               The values of the above attributes are integer numbers: 1 (set)
+               or 0 (unset).  Attempts to write any other values to them will
                cause -EINVAL to be returned.
 
 What:          /sys/firmware/acpi/interrupts/

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to