Skip to contentSkip to conversation

ACPI: New eject flow to remove devices cautiously

PATCH In review Chester Lin 2 participants 6 messages Jul 3, 2019 – Aug 5, 2019

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

v2 cover ACPI: New eject flow to remove devices cautiously

Currently there are two ways to handle ACPI device ejection. When an eject event happens on a container, the kernel just sends KOBJ_CHANGE to userland and userland should handle offline operation. For other device types, acpi_scan_try_to_offline() is called and it tries to put target device(s) offline and then removes all nodes once they are all offline.

However we found that sometimes applications could intensively access resources on ejectable devices therefore they could have risk if ejection suddenly happens and removes devices without any notification. In stead of executing the offline callbakcs directly, we want to introduce a new approach, which sends change events to notify all target nodes beforehand and hands over offline handling to userland so that userland can have a chance to schedule an offline task based on current workload. The online function to recover from failure is also changed, it follows the same approach to send change events rather than putting devices online directly , which means userland will also need to take care of online handling.

To ensure that eject function can work properly since normal users might not have their own offline/online handling, we will submit a generic udev rule to systemd upstream as default in order to deal with change events and take [offline/online] action accordingly. But the Hot-Removing part still remains so the hotplug function can run to it once target nodes are all offline.

To easily monitor eject status and start over an eject process, there's a status trace mechanism in this eject flow, which helps to count current online devices under the ejectable target, and it can reschedule an eject event when all nodes within the device tree have been put offline.

v2:
- device_sysfs: Add descriptions in /Document/ABI/testing/sysfs-bus-acpi
- device_sysfs: Replace the declartion with DEVICE_ATTR_RW and add cancel option in eject_store.
- scan: Add a retry mechanism when userspace fail to put device offline.
- scan: Add ready-to-remove state.

Chester Lin (3):
ACPI / hotplug: Send change events for offline/online requests when
eject is triggered
ACPI / hotplug: Eject status trace and auto-remove approach ACPI / device_sysfs: Add eject_show and add a cancel option in
eject_store

 Documentation/ABI/testing/sysfs-bus-acpi |   9 +-
 drivers/acpi/container.c                 |   2 +-
 drivers/acpi/device_sysfs.c              |  94 ++++++-
 drivers/acpi/glue.c                      | 146 +++++++++++
 drivers/acpi/internal.h                  |  34 ++-
 drivers/acpi/scan.c                      | 318 +++++++++++++++++------
 drivers/base/core.c                      |   4 +
 include/acpi/acpi_bus.h                  |   3 +-
 include/linux/acpi.h                     |   6 +
 9 files changed, 523 insertions(+), 93 deletions(-)
Signature · 1 line
2.20.1
#1 lore raw

PATCH v2 1/3 ACPI / hotplug: Send change events for offline/online requests when eject is triggered

Here we change offline/online handling in device hotplug by sending change events to userland as notification so that userland can have control and determine when will be a good time to put them offline/online based on current workload. In this approach the real offline/online opertions are handed over to userland so that userland can have more time to prepare before any device change actually happens.

All child devices under the ejection target are traversed and notified hierarchically based on ACPI namespace in ascending order when an eject event happens.

---
 drivers/acpi/container.c |   2 +-
 drivers/acpi/internal.h  |   2 +-
 drivers/acpi/scan.c      | 140 +++++++++++++++++----------------------
 include/acpi/acpi_bus.h  |   2 +-
 4 files changed, 65 insertions(+), 81 deletions(-)
drivers/acpi/container.c +1 -1
index 9ea5f55d97e3..53ca9b1ae1bf 100644
@@ -33,7 +33,7 @@ static int acpi_container_offline(struct container_dev *cdev)
 
 	/* Check all of the dependent devices' physical companions. */
 	list_for_each_entry(child, &adev->children, node)
-		if (!acpi_scan_is_offline(child, false))
+		if (!acpi_scan_is_offline(child))
 			return -EBUSY;
 
 	return 0;
drivers/acpi/internal.h +1 -1
index f6157d4d637a..656d237b073d 100644
@@ -83,7 +83,7 @@ void acpi_apd_init(void);
 acpi_status acpi_hotplug_schedule(struct acpi_device *adev, u32 src);
 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);
+bool acpi_scan_is_offline(struct acpi_device *adev);
 
 acpi_status acpi_sysfs_table_handler(u32 event, void *table, void *context);
 void acpi_scan_table_handler(u32 event, void *table, void *context);
drivers/acpi/scan.c +62 -78

210 lines not on this page · raw message

include/acpi/acpi_bus.h +1 -1
index 4752ff0a9d9b..57e4ad0483ca 100644
@@ -330,7 +330,7 @@ struct acpi_device_physical_node {
 	unsigned int node_id;
 	struct list_head node;
 	struct device *dev;
-	bool put_online:1;
+	bool changed_offline:1;
 };
 
 struct acpi_device_properties {
Signature · 1 line
2.20.1
#2 lore raw

PATCH v2 2/3 ACPI / hotplug: Eject status trace and auto-remove approach

In reply to Chester Lin

This is an eject-status trace mechanism which helps to count current online devices under the ejection target, and it can automatically reschedules an eject event when all related devices are offline. The number of online nodes can be updated when any node has been put offline successfully. Any online event or offline failure within the whole device tree during ejection will stop the whole process and devices who have been put offline will need be online again as recovery.

---
 drivers/acpi/glue.c     | 146 ++++++++++++++++++++++++++++++++++++++
 drivers/acpi/internal.h |  30 ++++++++
 drivers/acpi/scan.c     | 152 +++++++++++++++++++++++++++++++++++++++-
 drivers/base/core.c     |   4 ++
 include/acpi/acpi_bus.h |   1 +
 include/linux/acpi.h    |   6 ++
 6 files changed, 336 insertions(+), 3 deletions(-)
drivers/acpi/glue.c +146 -0
index 36b24b0658cb..e1c419335128 100644
@@ -360,6 +360,82 @@ static int acpi_device_notify_remove(struct device *dev)
 	return 0;
 }
 
+static void acpi_device_eject_tracer(struct device *dev,
+				enum kobject_action action)
+{
+	struct acpi_device *adev = ACPI_COMPANION(dev);
+	struct acpi_device *adev_root = NULL;
+	struct eject_data *eject_node = NULL;
+	struct eject_data *eject_root = NULL;
+
+	if (!adev)
+		return;
+
+	acpi_scan_lock_acquire();
+
+	eject_node = (struct eject_data *)adev->eject_stat;
+
+	if (!eject_node)
+		goto tracer_end;
+
+	if (eject_node->base.root_handle)
+		adev_root =
+		    acpi_bus_get_acpi_device(eject_node->base.root_handle);
+
+	if (adev_root)
+		eject_root = (struct eject_data *)adev_root->eject_stat;
+
+	if (action == KOBJ_OFFLINE) {
+		eject_node->online_nodes--;
+
+		if (eject_node->online_nodes == 0)
+			eject_node->status = ACPI_EJECT_STATUS_READY_REMOVE;
+
+		/*
+		 * If the offline device is child, update its eject_root's
+		 * number of online nodes.
+		 */
+		if (eject_root) {
+			eject_root->online_nodes--;
+
+			goto tracer_end;
+		}
+		/*
+		 * Adjust number of online nodes when any physical node under
+		 * eject_root is offline. Trigger acpi_eject_try() once all
+		 * nodes are offline.
+		 */
+		if (eject_node->online_nodes == 0)
+			acpi_eject_retry(adev);
+
+	} else if (action == KOBJ_ONLINE) {
+		/* !eject_root means now eject_node is root. */
+		if (!eject_root)
+			eject_root = eject_node;
+
+		if (eject_root->status == ACPI_EJECT_STATUS_GOING_OFFLINE ||
+		    eject_root->status == ACPI_EJECT_STATUS_READY_REMOVE) {
+			eject_root->status = ACPI_EJECT_STATUS_FAIL;
+			acpi_eject_retry(adev_root);
+		}
+	}
+tracer_end:
+	if (adev_root)
+		acpi_bus_put_acpi_device(adev_root);
+
+	acpi_scan_lock_release();
+}
+
+static inline void acpi_device_check_eject_status(struct device *dev)
+{
+	acpi_device_eject_tracer(dev, KOBJ_OFFLINE);
+}
+
+static inline void acpi_device_check_eject_recover(struct device *dev)
+{
+	acpi_device_eject_tracer(dev, KOBJ_ONLINE);
+}
+
 int acpi_platform_notify(struct device *dev, enum kobject_action action)
 {
 	switch (action) {
@@ -369,8 +445,78 @@ int acpi_platform_notify(struct device *dev, enum kobject_action action)
 	case KOBJ_REMOVE:
 		acpi_device_notify_remove(dev);
 		break;
+	case KOBJ_OFFLINE:
+		acpi_device_check_eject_status(dev);
+		break;
+	case KOBJ_ONLINE:
+		acpi_device_check_eject_recover(dev);
+		break;
 	default:
 		break;
 	}
 	return 0;
 }
+
+static void acpi_device_check_offline_fail(struct device *dev)
+{
+	struct acpi_device *adev = ACPI_COMPANION(dev);
+	struct acpi_device *adev_root = NULL;
+	struct acpi_device *adev_child = NULL;
+	struct eject_data *eject_node = NULL;
+	bool has_online_child = false;
+	char *envp[] = { "EVENT=offline", NULL };
+
+	if (!adev)
+		return;
+
+	acpi_scan_lock_acquire();
+
+	eject_node = (struct eject_data *)adev->eject_stat;
+
+	if (!eject_node ||
+	    eject_node->status != ACPI_EJECT_STATUS_GOING_OFFLINE)
+		goto check_end;
+
+	if (!eject_node->retry) {
+		/* Check if the device contains any online child. */
+		list_for_each_entry(adev_child, &adev->children, node)
+			if (!acpi_scan_is_offline(adev_child)) {
+				has_online_child = true;
+				break;
+			}
+
+		if (!has_online_child)
+			eject_node->retry = true;
+
+		/* Sent change event again. */
+		kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+
+	} else {
+		/* Treat as failure if it still fails after retry. */
+		if (eject_node->base.root_handle) {
+			adev_root = acpi_bus_get_acpi_device(
+				eject_node->base.root_handle);
+
+			if (!adev_root || !adev_root->eject_stat)
+				goto check_end;
+
+			adev = adev_root;
+			eject_node = (struct eject_data *)adev_root->eject_stat;
+		}
+
+		eject_node->status = ACPI_EJECT_STATUS_FAIL;
+		acpi_eject_retry(adev);
+	}
+
+check_end:
+	if (adev_root)
+		acpi_bus_put_acpi_device(adev_root);
+
+	acpi_scan_lock_release();
+}
+
+void acpi_action_fail_notify(struct device *dev, enum kobject_action action)
+{
+	if (action == KOBJ_OFFLINE)
+		acpi_device_check_offline_fail(dev);
+}
drivers/acpi/internal.h +30 -0
index 656d237b073d..8154690b872b 100644
@@ -257,4 +257,34 @@ void acpi_init_lpit(void);
 static inline void acpi_init_lpit(void) { }
 #endif
 
+/* --------------------------------------------------------------------------
+ *				Eject Status
+ * --------------------------------------------------------------------------
+ */
+enum acpi_eject_status {
+	ACPI_EJECT_STATUS_NONE = 0,
+	ACPI_EJECT_STATUS_GOING_OFFLINE,
+	ACPI_EJECT_STATUS_READY_REMOVE,
+	ACPI_EJECT_STATUS_FAIL
+};
+
+enum acpi_eject_node_type {
+	ACPI_EJECT_CHILD_NODE = 0,
+	ACPI_EJECT_ROOT_NODE
+};
+
+struct eject_data_base {
+	enum acpi_eject_node_type type;
+	acpi_handle root_handle;
+};
+
+struct eject_data {
+	struct eject_data_base base;
+	enum acpi_eject_status status;
+	u32 online_nodes;
+	bool retry;
+};
+
+void acpi_eject_retry(struct acpi_device *adev);
+
 #endif /* _ACPI_INTERNAL_H_ */
drivers/acpi/scan.c +149 -3

212 lines not on this page · raw message

drivers/base/core.c +4 -0
index b4c64528f13c..2a85fbf0c666 100644
@@ -2570,6 +2570,9 @@ int device_offline(struct device *dev)
 			if (!ret) {
 				kobject_uevent(&dev->kobj, KOBJ_OFFLINE);
 				dev->offline = true;
+				device_platform_notify(dev, KOBJ_OFFLINE);
+			} else {
+				acpi_action_fail_notify(dev, KOBJ_OFFLINE);
 			}
 		}
 	}
@@ -2599,6 +2602,7 @@ int device_online(struct device *dev)
 			if (!ret) {
 				kobject_uevent(&dev->kobj, KOBJ_ONLINE);
 				dev->offline = false;
+				device_platform_notify(dev, KOBJ_ONLINE);
 			}
 		} else {
 			ret = 1;
include/acpi/acpi_bus.h +1 -0
index 57e4ad0483ca..29004b3fad87 100644
@@ -378,6 +378,7 @@ struct acpi_device {
 	struct list_head physical_node_list;
 	struct mutex physical_node_lock;
 	void (*remove)(struct acpi_device *);
+	void *eject_stat;
 };
 
 /* Non-device subnode */
include/linux/acpi.h +6 -0
index d315d86844e4..91ba75914ac7 100644
@@ -1321,12 +1321,18 @@ static inline int find_acpi_cpu_cache_topology(unsigned int cpu, int level)
 
 #ifdef CONFIG_ACPI
 extern int acpi_platform_notify(struct device *dev, enum kobject_action action);
+extern void acpi_action_fail_notify(struct device *dev,
+					enum kobject_action action);
 #else
 static inline int
 acpi_platform_notify(struct device *dev, enum kobject_action action)
 {
 	return 0;
 }
+static inline void
+void acpi_action_fail_notify(struct device *dev, enum kobject_action action)
+{
+}
 #endif
 
 #endif	/*_LINUX_ACPI_H*/
Signature · 1 line
2.20.1
#3 lore raw

PATCH v2 3/3 ACPI / device_sysfs: Add eject_show and add a cancel option in eject_store

In reply to Chester Lin

Add an eject_show attribute for users to monitor current status because sometimes it could take time to finish an ejection so we need to know whether it is still in progress or not. For userspace who might need to cancel an onging ejection, we also offer an option in eject_store.

---
 Documentation/ABI/testing/sysfs-bus-acpi |  9 ++-
 drivers/acpi/device_sysfs.c              | 94 +++++++++++++++++++++---
 drivers/acpi/internal.h                  |  4 +-
 drivers/acpi/scan.c                      | 38 +++++++++-
 4 files changed, 129 insertions(+), 16 deletions(-)
Documentation/ABI/testing/sysfs-bus-acpi +6 -3
index e7898cfe5fb1..32fdf4af962e 100644
@@ -53,9 +53,12 @@ What:		/sys/bus/acpi/devices/.../eject
 Date:		December 2006
 Contact:	Rafael J. Wysocki <[email protected]>
 Description:
-		Writing 1 to this attribute will trigger hot removal of
-		this device object.  This file exists for every device
-		object that has _EJ0 method.
+		(R) Allows users to read eject status of the device object.
+		(W) Writing 1 to this attribute will trigger hot removal of
+		this device object. Writing 2 to this attribute will cancel hot
+		removal work if it's still in offline process and the original
+		state of this device object will be recovered. This file exists
+		for every device object that has _EJ0 method.
 
 What:		/sys/bus/acpi/devices/.../status
 Date:		Jan, 2014
drivers/acpi/device_sysfs.c +84 -10

119 lines not on this page · raw message

drivers/acpi/internal.h +3 -1
index 8154690b872b..e5d526402188 100644
@@ -265,7 +265,8 @@ enum acpi_eject_status {
 	ACPI_EJECT_STATUS_NONE = 0,
 	ACPI_EJECT_STATUS_GOING_OFFLINE,
 	ACPI_EJECT_STATUS_READY_REMOVE,
-	ACPI_EJECT_STATUS_FAIL
+	ACPI_EJECT_STATUS_FAIL,
+	ACPI_EJECT_STATUS_CANCEL
 };
 
 enum acpi_eject_node_type {
@@ -286,5 +287,6 @@ struct eject_data {
 };
 
 void acpi_eject_retry(struct acpi_device *adev);
+char *acpi_eject_status_string(struct acpi_device *adev);
 
 #endif /* _ACPI_INTERNAL_H_ */
drivers/acpi/scan.c +36 -2
index 13f16b6ad7a2..90983c067410 100644
@@ -366,8 +366,9 @@ static int acpi_scan_offline_check(struct acpi_device *device)
 			return -EBUSY;
 		}
 
-		if (eject_obj->status == ACPI_EJECT_STATUS_FAIL) {
-			dev_warn(&device->dev, "Eject failed. Recover all.\n");
+		if (eject_obj->status == ACPI_EJECT_STATUS_FAIL ||
+		    eject_obj->status == ACPI_EJECT_STATUS_CANCEL) {
+			dev_warn(&device->dev, "Eject stopped. Recover all.\n");
 			acpi_scan_notify_online(device);
 			return -EAGAIN;
 		}
@@ -383,6 +384,39 @@ static int acpi_scan_offline_check(struct acpi_device *device)
 	return ret;
 }
 
+char *acpi_eject_status_string(struct acpi_device *adev)
+{
+	struct eject_data *eject_obj;
+	char *status_string = "none";
+
+	mutex_lock(&acpi_scan_lock);
+	eject_obj = (struct eject_data *) adev->eject_stat;
+
+	if (eject_obj) {
+		switch (eject_obj->status) {
+		case ACPI_EJECT_STATUS_NONE:
+			break;
+		case ACPI_EJECT_STATUS_GOING_OFFLINE:
+			status_string = "going offline";
+			break;
+		case ACPI_EJECT_STATUS_READY_REMOVE:
+			status_string = "ready to remove";
+			break;
+		case ACPI_EJECT_STATUS_FAIL:
+			status_string = "failure";
+			break;
+		case ACPI_EJECT_STATUS_CANCEL:
+			status_string = "cancel";
+			break;
+		default:
+			status_string = "(unknown)";
+		}
+	}
+
+	mutex_unlock(&acpi_scan_lock);
+	return status_string;
+}
+
 static int acpi_scan_hot_remove(struct acpi_device *device)
 {
 	acpi_handle handle = device->handle;
Signature · 1 line
2.20.1
#4 lore raw
In reply to Chester Lin
Quoted 17 lines · Chester Lin

Currently there are two ways to handle ACPI device ejection. When an eject event happens on a container, the kernel just sends KOBJ_CHANGE to userland and userland should handle offline operation. For other device types, acpi_scan_try_to_offline() is called and it tries to put target device(s) offline and then removes all nodes once they are all offline.

However we found that sometimes applications could intensively access resources on ejectable devices therefore they could have risk if ejection suddenly happens and removes devices without any notification. In stead of executing the offline callbakcs directly, we want to introduce a new approach, which sends change events to notify all target nodes beforehand and hands over offline handling to userland so that userland can have a chance to schedule an offline task based on current workload. The online function to recover from failure is also changed, it follows the same approach to send change events rather than putting devices online directly , which means userland will also need to take care of online handling.

Quoted 7 lines

To ensure that eject function can work properly since normal users might not have their own offline/online handling, we will submit a generic udev rule to systemd upstream as default in order to deal with change events and take [offline/online] action accordingly. But the Hot-Removing part still remains so the hotplug function can run to it once target nodes are all offline.

Here are default rules we are going to propose:

# 80-acpi-hotplug-eject.rules
# Generic rules for handling ACPI hotplug eject.

SUBSYSTEM=="*", ACTION=="change", ENV{EVENT}=="offline", ATTR{online}=="1", \
DEVPATH=="*", ATTR{online}="0"

SUBSYSTEM=="*", ACTION=="change", ENV{EVENT}=="online", ATTR{online}=="0", \
DEVPATH=="*", ATTR{online}="1"

Quoted 33 lines

To easily monitor eject status and start over an eject process, there's a status trace mechanism in this eject flow, which helps to count current online devices under the ejectable target, and it can reschedule an eject event when all nodes within the device tree have been put offline.

v2:
- device_sysfs: Add descriptions in /Document/ABI/testing/sysfs-bus-acpi
- device_sysfs: Replace the declartion with DEVICE_ATTR_RW and add cancel option in eject_store.
- scan: Add a retry mechanism when userspace fail to put device offline.
- scan: Add ready-to-remove state.

Chester Lin (3):
ACPI / hotplug: Send change events for offline/online requests when
eject is triggered
ACPI / hotplug: Eject status trace and auto-remove approach ACPI / device_sysfs: Add eject_show and add a cancel option in
eject_store

 Documentation/ABI/testing/sysfs-bus-acpi |   9 +-
 drivers/acpi/container.c                 |   2 +-
 drivers/acpi/device_sysfs.c              |  94 ++++++-
 drivers/acpi/glue.c                      | 146 +++++++++++
 drivers/acpi/internal.h                  |  34 ++-
 drivers/acpi/scan.c                      | 318 +++++++++++++++++------
 drivers/base/core.c                      |   4 +
 include/acpi/acpi_bus.h                  |   3 +-
 include/linux/acpi.h                     |   6 +
 9 files changed, 523 insertions(+), 93 deletions(-)
Signature · 1 line
2.20.1
#5 lore raw
In reply to Chester Lin
Quoted 43 lines · Chester Lin

Currently there are two ways to handle ACPI device ejection. When an eject event happens on a container, the kernel just sends KOBJ_CHANGE to userland and userland should handle offline operation. For other device types, acpi_scan_try_to_offline() is called and it tries to put target device(s) offline and then removes all nodes once they are all offline.

However we found that sometimes applications could intensively access resources on ejectable devices therefore they could have risk if ejection suddenly happens and removes devices without any notification. In stead of executing the offline callbakcs directly, we want to introduce a new approach, which sends change events to notify all target nodes beforehand and hands over offline handling to userland so that userland can have a chance to schedule an offline task based on current workload. The online function to recover from failure is also changed, it follows the same approach to send change events rather than putting devices online directly , which means userland will also need to take care of online handling.

To ensure that eject function can work properly since normal users might not have their own offline/online handling, we will submit a generic udev rule to systemd upstream as default in order to deal with change events and take [offline/online] action accordingly. But the Hot-Removing part still remains so the hotplug function can run to it once target nodes are all offline.

To easily monitor eject status and start over an eject process, there's a status trace mechanism in this eject flow, which helps to count current online devices under the ejectable target, and it can reschedule an eject event when all nodes within the device tree have been put offline.

v2:
- device_sysfs: Add descriptions in /Document/ABI/testing/sysfs-bus-acpi
- device_sysfs: Replace the declartion with DEVICE_ATTR_RW and add cancel option in eject_store.
- scan: Add a retry mechanism when userspace fail to put device offline.
- scan: Add ready-to-remove state.

Chester Lin (3):
ACPI / hotplug: Send change events for offline/online requests when
eject is triggered
ACPI / hotplug: Eject status trace and auto-remove approach ACPI / device_sysfs: Add eject_show and add a cancel option in
eject_store

Quoted 11 lines
 Documentation/ABI/testing/sysfs-bus-acpi |   9 +-
 drivers/acpi/container.c                 |   2 +-
 drivers/acpi/device_sysfs.c              |  94 ++++++-
 drivers/acpi/glue.c                      | 146 +++++++++++
 drivers/acpi/internal.h                  |  34 ++-
 drivers/acpi/scan.c                      | 318 +++++++++++++++++------
 drivers/base/core.c                      |   4 +
 include/acpi/acpi_bus.h                  |   3 +-
 include/linux/acpi.h                     |   6 +
 9 files changed, 523 insertions(+), 93 deletions(-)

Gentle ping. I will appreciate any comment on this series.

Thanks,
Chester