lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20200327112247.17691-3-clin@suse.com>
Date:   Fri, 27 Mar 2020 19:22:46 +0800
From:   Chester Lin <clin@...e.com>
To:     <rjw@...ysocki.net>, <lenb@...nel.org>, <robert.moore@...el.com>,
        <erik.kaneda@...el.com>, <gregkh@...uxfoundation.org>
CC:     <linux-acpi@...r.kernel.org>, <linux-kernel@...r.kernel.org>,
        <devel@...ica.org>, <jlee@...e.com>, <mhocko@...e.com>,
        Chester Lin <clin@...e.com>
Subject: [RFC PATCH 2/3] ACPI: scan: add cancel_eject and auto_eject attributes

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@...e.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@...e.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@...e.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, &not_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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ