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-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html