From: Chester Lin <clin@suse.com>
To: <rjw@rjwysocki.net>, <lenb@kernel.org>, <robert.moore@intel.com>,
<erik.kaneda@intel.com>, <gregkh@linuxfoundation.org>
Cc: <linux-acpi@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<devel@acpica.org>, <jlee@suse.com>, <mhocko@suse.com>,
Chester Lin <clin@suse.com>
Subject: [RFC PATCH 2/3] ACPI: scan: add cancel_eject and auto_eject attributes
Date: Fri, 27 Mar 2020 19:22:46 +0800 [thread overview]
Message-ID: <20200327112247.17691-3-clin@suse.com> (raw)
In-Reply-To: <20200327112247.17691-1-clin@suse.com>
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@suse.com>
---
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@suse.com>
+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@suse.com>
+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
next prev parent reply other threads:[~2020-03-27 11:28 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-03-27 11:22 [RFC PATCH 0/3] ACPI: Flexible eject options to remove devices cautiously Chester Lin
2020-03-27 11:22 ` [RFC PATCH 1/3] ACPI: scan: add userland notification while handling eject events Chester Lin
2020-03-27 11:38 ` Greg KH
2020-03-30 8:50 ` Chester Lin
2020-03-30 9:11 ` Chester Lin
2020-03-27 11:22 ` Chester Lin [this message]
2020-03-27 11:22 ` [RFC PATCH 3/3] ACPI: scan: add a request_offline_recursive attribute Chester Lin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200327112247.17691-3-clin@suse.com \
--to=clin@suse.com \
--cc=devel@acpica.org \
--cc=erik.kaneda@intel.com \
--cc=gregkh@linuxfoundation.org \
--cc=jlee@suse.com \
--cc=lenb@kernel.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mhocko@suse.com \
--cc=rjw@rjwysocki.net \
--cc=robert.moore@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.