[PATCH 3/3 RFC] ACPI / hotplug: Use device offline/online for graceful hot-removal
From: Rafael J. Wysocki
Date: Mon Apr 29 2013 - 08:21:52 EST
From: Rafael J. Wysocki <rafael.j.wysocki@xxxxxxxxx>
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.wysocki@xxxxxxxxx>
---
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/