[PATCH 1/4] driver core: Support two-pass driver shutdown
Jeremy Allison
jallison at ciq.com
Thu Dec 21 09:22:54 PST 2023
From: Tanjore Suresh <tansuresh at google.com>
This changes the bus driver interface with an additional entry point
to enable devices to implement two-pass shutdown. The existing
synchronous interface to shutdown is called, and if a shutdown_wait
method is defined the device is moved to an alternate list.
Once the shutdown method is called for all devices, the
shutdown_wait method is then called synchronously for
all devices on the alternate list.
Signed-off-by: Tanjore Suresh <tansuresh at google.com>
Signed-off-by: Jeremy Allison <jallison at ciq.com>
---
drivers/base/core.c | 35 +++++++++++++++++++++++++++++++++++
include/linux/device/bus.h | 6 +++++-
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 67ba592afc77..a66988cbaad1 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4725,6 +4725,7 @@ EXPORT_SYMBOL_GPL(device_change_owner);
void device_shutdown(void)
{
struct device *dev, *parent;
+ LIST_HEAD(shutdown_wait_list);
wait_for_device_probe();
device_block_probing();
@@ -4769,10 +4770,15 @@ void device_shutdown(void)
dev_info(dev, "shutdown_pre\n");
dev->class->shutdown_pre(dev);
}
+
if (dev->bus && dev->bus->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->bus->shutdown(dev);
+ /* Only put the device on the shutdown_wait_list
+ * if a shutdown_wait() method is also defined. */
+ if (dev->bus->shutdown_wait)
+ list_add(&dev->kobj.entry, &shutdown_wait_list);
} else if (dev->driver && dev->driver->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
@@ -4789,6 +4795,35 @@ void device_shutdown(void)
spin_lock(&devices_kset->list_lock);
}
spin_unlock(&devices_kset->list_lock);
+
+ /*
+ * Second pass only for devices that have configured
+ * a shutdown_wait() method.
+ */
+ while (!list_empty(&shutdown_wait_list)) {
+ dev = list_entry(shutdown_wait_list.next, struct device,
+ kobj.entry);
+ parent = get_device(dev->parent);
+ get_device(dev);
+ /*
+ * Make sure the device is off the list
+ */
+ list_del_init(&dev->kobj.entry);
+ if (parent)
+ device_lock(parent);
+ device_lock(dev);
+ if (dev->bus && dev->bus->shutdown_wait) {
+ if (initcall_debug)
+ dev_info(dev,
+ "shutdown_wait called\n");
+ dev->bus->shutdown_wait(dev);
+ }
+ device_unlock(dev);
+ if (parent)
+ device_unlock(parent);
+ put_device(dev);
+ put_device(parent);
+ }
}
/*
diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h
index ae10c4322754..6f192221b551 100644
--- a/include/linux/device/bus.h
+++ b/include/linux/device/bus.h
@@ -48,7 +48,10 @@ struct fwnode_handle;
* will never get called until they do.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
- *
+ * @shutdown_wait: If this method exists, devices are stored on a separate
+ * list after shutdown() has been called and
+ * shutdown_wait() is called synchronously on each device
+ * in this list in turn.
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
@@ -90,6 +93,7 @@ struct bus_type {
void (*sync_state)(struct device *dev);
void (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
+ void (*shutdown_wait)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
--
2.39.3
More information about the Linux-nvme
mailing list