Add two attributes 'cancel_eject' and 'auto_eject' as auxiliary features of request_offline, which are only effective when the request_offline is set. Writing 1 to cancel_eject will remove pending the eject event and then put the target back to its original online state if it has been changed. Writing a time interval to auto_eject will periodically schedule an eject event and will trigger real hot-remove once the target is offline. You can still keep auto_eject to 0 if the firmware or userpsace caller who raises the eject request can re-trigger an eject event by itself. Signed-off-by: Chester Lin <clin@xxxxxxxx> --- Documentation/ABI/testing/sysfs-bus-acpi | 20 ++++++ drivers/acpi/device_sysfs.c | 87 +++++++++++++++++++++++- drivers/acpi/internal.h | 2 + drivers/acpi/osl.c | 37 ++++++++-- drivers/acpi/scan.c | 53 +++++++++++++-- include/acpi/acpi_bus.h | 9 ++- 6 files changed, 193 insertions(+), 15 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-acpi b/Documentation/ABI/testing/sysfs-bus-acpi index b9c467704889..be00749f00e6 100644 --- a/Documentation/ABI/testing/sysfs-bus-acpi +++ b/Documentation/ABI/testing/sysfs-bus-acpi @@ -109,3 +109,23 @@ Description: operations before the target can be ejected. This approach provides flexibility while some applications could need more time to release resources. + +What: /sys/bus/acpi/devices/.../cancel_eject +Date: Mar, 2020 +Contact: Chester Lin <clin@xxxxxxxx> +Description: + (WO) Writing 1 to this attribute will cancel the pending + ejection when userland is working on a target's offline + procedure [e.g. req_offline is set]. Then it will try putting + the target device back to its original online state. + +What: /sys/bus/acpi/devices/.../auto_eject +Date: Mar, 2020 +Contact: Chester Lin <clin@xxxxxxxx> +Description: + (RW) Allows the userland to periodically schedule an eject + event on a target until it can be successfully removed. + Userland can write a time interval [unit: second] to this + attribute, and write 0 to disable it. This feature is disabled + when the request_offline is 0 or no initial eject event + is triggered by firmware or an eject attribute in /sys. diff --git a/drivers/acpi/device_sysfs.c b/drivers/acpi/device_sysfs.c index 453bd1b9edf5..e40daafa3f85 100644 --- a/drivers/acpi/device_sysfs.c +++ b/drivers/acpi/device_sysfs.c @@ -511,7 +511,7 @@ static ssize_t request_offline_show(struct device *dev, { struct acpi_device *acpi_dev = to_acpi_device(dev); - return sprintf(buf, "%u\n", acpi_dev->request_offline?1:0); + return sprintf(buf, "%u\n", acpi_dev->eject.request_offline?1:0); } static ssize_t request_offline_store(struct device *dev, @@ -524,10 +524,10 @@ static ssize_t request_offline_store(struct device *dev, switch (buf[0]) { case '0': - acpi_dev->request_offline = false; + acpi_dev->eject.request_offline = false; break; case '1': - acpi_dev->request_offline = true; + acpi_dev->eject.request_offline = true; break; default: return -EINVAL; @@ -537,6 +537,74 @@ static ssize_t request_offline_store(struct device *dev, } static DEVICE_ATTR_RW(request_offline); +static ssize_t auto_eject_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + + return sprintf(buf, "%u\n", acpi_dev->eject.poll_time); +} + +static ssize_t auto_eject_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct acpi_device *acpi_dev = to_acpi_device(dev); + unsigned int time_interval; + + if (!count) + return -EINVAL; + + if (sscanf(buf, "%u\n", &time_interval) == 1) + acpi_dev->eject.poll_time = time_interval; + + return count; +} +static DEVICE_ATTR_RW(auto_eject); + +static ssize_t +cancel_eject_store(struct device *d, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct acpi_device *acpi_device = to_acpi_device(d); + acpi_object_type not_used; + acpi_status status; + + if (!count || buf[0] != '1') + return -EINVAL; + + if ((!acpi_device->handler || !acpi_device->handler->hotplug.enabled) + && !acpi_device->driver) + return -ENODEV; + + status = acpi_get_type(acpi_device->handle, ¬_used); + if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable) + return -ENODEV; + + if (!acpi_device->eject.in_progress) + return count; + + acpi_device->eject.cancel = true; + + if (!acpi_device->eject.poll_time) { + get_device(&acpi_device->dev); + + status = acpi_hotplug_schedule(acpi_device, + ACPI_OST_EC_OSPM_EJECT); + if (ACPI_SUCCESS(status)) + return count; + + put_device(&acpi_device->dev); + + acpi_evaluate_ost(acpi_device->handle, ACPI_OST_EC_OSPM_EJECT, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); + + return status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN; + } + + return count; +} +static DEVICE_ATTR_WO(cancel_eject); + /** * acpi_device_setup_files - Create sysfs attributes of an ACPI device. * @dev: ACPI device object. @@ -616,6 +684,17 @@ int acpi_device_setup_files(struct acpi_device *dev) &dev_attr_request_offline); if (result) return result; + + result = device_create_file(&dev->dev, + &dev_attr_auto_eject); + if (result) + return result; + + result = device_create_file(&dev->dev, + &dev_attr_cancel_eject); + if (result) + return result; + } if (dev->flags.power_manageable) { @@ -662,6 +741,8 @@ void acpi_device_remove_files(struct acpi_device *dev) if (acpi_has_method(dev->handle, "_EJ0")) { device_remove_file(&dev->dev, &dev_attr_eject); device_remove_file(&dev->dev, &dev_attr_request_offline); + device_remove_file(&dev->dev, &dev_attr_auto_eject); + device_remove_file(&dev->dev, &dev_attr_cancel_eject); } if (acpi_has_method(dev->handle, "_SUN")) diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index e387517d3354..45f4ce42a044 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -81,6 +81,8 @@ static inline void acpi_lpss_init(void) {} void acpi_apd_init(void); acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src); +acpi_status acpi_hotplug_delayed_schedule(struct acpi_device *adev, + u32 src, unsigned long delay); bool acpi_queue_hotplug_work(struct work_struct *work); void acpi_device_hotplug(struct acpi_device *adev, u32 src); bool acpi_scan_is_offline(struct acpi_device *adev, bool uevent); diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 762c5d50b8fe..0716c5bbff12 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -1143,33 +1143,44 @@ void acpi_os_wait_events_complete(void) EXPORT_SYMBOL(acpi_os_wait_events_complete); struct acpi_hp_work { - struct work_struct work; + struct delayed_work work; struct acpi_device *adev; u32 src; }; static void acpi_hotplug_work_fn(struct work_struct *work) { - struct acpi_hp_work *hpw = container_of(work, struct acpi_hp_work, work); + struct delayed_work *delay_work; + struct acpi_hp_work *hpw; + + delay_work = container_of(work, struct delayed_work, work); + hpw = container_of(delay_work, struct acpi_hp_work, work); + + if (!hpw) { + pr_debug("Null object of ACPI hotplug work.\n"); + return; + } acpi_os_wait_events_complete(); acpi_device_hotplug(hpw->adev, hpw->src); kfree(hpw); } -acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src) +static acpi_status acpi_hotplug_schedule_work(struct acpi_device *adev, + u32 src, unsigned long delay) { struct acpi_hp_work *hpw; ACPI_DEBUG_PRINT((ACPI_DB_EXEC, - "Scheduling hotplug event (%p, %u) for deferred execution.\n", - adev, src)); + "Scheduling hotplug event (%p, %u, %lu) for deferred execution.\n", + adev, src, delay)); hpw = kmalloc(sizeof(*hpw), GFP_KERNEL); if (!hpw) return AE_NO_MEMORY; - INIT_WORK(&hpw->work, acpi_hotplug_work_fn); + INIT_DELAYED_WORK(&hpw->work, acpi_hotplug_work_fn); + hpw->adev = adev; hpw->src = src; /* @@ -1178,13 +1189,25 @@ acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src) * invoke flush_scheduled_work()/acpi_os_wait_events_complete() to flush * these workqueues. */ - if (!queue_work(kacpi_hotplug_wq, &hpw->work)) { + if (!queue_delayed_work(kacpi_hotplug_wq, &hpw->work, delay)) { kfree(hpw); return AE_ERROR; } + return AE_OK; } +acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src) +{ + return acpi_hotplug_schedule_work(adev, src, 0); +} + +acpi_status acpi_hotplug_delayed_schedule(struct acpi_device *adev, + u32 src, unsigned long delay) +{ + return acpi_hotplug_schedule_work(adev, src, delay); +} + bool acpi_queue_hotplug_work(struct work_struct *work) { return queue_work(kacpi_hotplug_wq, work); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 1cb39c5360cf..b4678ed14eed 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -169,7 +169,7 @@ static acpi_status acpi_bus_offline(acpi_handle handle, u32 lvl, void *data, } /* Don't offline directly but need to notify userland first */ - if (device->request_offline) { + if (device->eject.request_offline) { if (pn->dev->offline) ret = 0; else @@ -209,7 +209,7 @@ static acpi_status acpi_bus_online(acpi_handle handle, u32 lvl, void *data, list_for_each_entry(pn, &device->physical_node_list, node) if (pn->put_online) { - if (device->request_offline) + if (device->eject.request_offline) kobject_uevent_env(&pn->dev->kobj, KOBJ_CHANGE, envp); else @@ -269,6 +269,41 @@ static int acpi_scan_try_to_offline(struct acpi_device *device) return 0; } +static void acpi_scan_cancel_eject(struct acpi_device *device) +{ + acpi_handle handle = device->handle; + + /* Get all nodes online again if necessary */ + acpi_bus_online(handle, 0, NULL, NULL); + acpi_walk_namespace(ACPI_TYPE_ANY, handle, ACPI_UINT32_MAX, + acpi_bus_online, NULL, NULL, NULL); + + device->eject.in_progress = false; + device->eject.cancel = false; +} + +static inline void acpi_set_eject_status(struct acpi_device *device) +{ + unsigned long delay; + acpi_status status; + + device->eject.in_progress = true; + + if (!device->eject.poll_time) + return; + + delay = device->eject.poll_time * HZ; + + get_device(&device->dev); + status = acpi_hotplug_delayed_schedule(device, + ACPI_OST_EC_OSPM_EJECT, delay); + + if (ACPI_FAILURE(status)) { + pr_err("Failed to schedule a delayed work\n"); + put_device(&device->dev); + } +} + static int acpi_scan_hot_remove(struct acpi_device *device) { acpi_handle handle = device->handle; @@ -277,8 +312,13 @@ static int acpi_scan_hot_remove(struct acpi_device *device) bool notify_single = false; int error; + if (device->eject.request_offline && device->eject.cancel) { + acpi_scan_cancel_eject(device); + return -EBUSY; + } + if (device->handler && device->handler->hotplug.demand_offline) - if (!device->request_offline) + if (!device->eject.request_offline) notify_single = true; if (!acpi_scan_is_offline(device, notify_single)) { @@ -289,10 +329,15 @@ static int acpi_scan_hot_remove(struct acpi_device *device) if (error) return error; - if (device->request_offline) + if (device->eject.request_offline) { + acpi_set_eject_status(device); return -EBUSY; + } } + device->eject.in_progress = false; + device->eject.cancel = false; + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Hot-removing device %s...\n", dev_name(&device->dev))); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 7a29ea0a7d0e..1fb72e399e0d 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -346,6 +346,13 @@ struct acpi_device_data { struct acpi_gpio_mapping; +struct acpi_eject_status { + bool request_offline; + bool in_progress; + bool cancel; + unsigned int poll_time; /* unit: second */ +}; + /* Device */ struct acpi_device { int device_type; @@ -375,7 +382,7 @@ struct acpi_device { struct list_head physical_node_list; struct mutex physical_node_lock; void (*remove)(struct acpi_device *); - bool request_offline; + struct acpi_eject_status eject; }; /* Non-device subnode */ -- 2.24.0