summaryrefslogtreecommitdiff
path: root/drivers/base/core.c
diff options
context:
space:
mode:
authorStuart Hayes <stuart.w.hayes@gmail.com>2024-08-22 15:28:04 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2024-09-03 13:06:43 +0200
commit8064952c65045f05ee2671fe437770e50c151776 (patch)
tree2f708cc3ff74bd8dda289d9939ca649cc0b925e9 /drivers/base/core.c
parent95dc7565253a8564911190ebd1e4ffceb4de208a (diff)
downloadlinux-8064952c65045f05ee2671fe437770e50c151776.tar.gz
linux-8064952c65045f05ee2671fe437770e50c151776.tar.bz2
linux-8064952c65045f05ee2671fe437770e50c151776.zip
driver core: shut down devices asynchronously
Add code to allow asynchronous shutdown of devices, ensuring that each device is shut down before its parents & suppliers. Only devices with drivers that have async_shutdown_enable enabled will be shut down asynchronously. This can dramatically reduce system shutdown/reboot time on systems that have multiple devices that take many seconds to shut down (like certain NVMe drives). On one system tested, the shutdown time went from 11 minutes without this patch to 55 seconds with the patch. Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com> Signed-off-by: David Jeffery <djeffery@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Sagi Grimberg <sagi@grimberg.me> Reviewed-by: Keith Busch <kbusch@kernel.org> Tested-by: Keith Busch <kbusch@kernel.org> Link: https://lore.kernel.org/r/20240822202805.6379-4-stuart.w.hayes@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/base/core.c')
-rw-r--r--drivers/base/core.c54
1 files changed, 53 insertions, 1 deletions
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 8598421cbb7f..6113bc1092ae 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -9,6 +9,7 @@
*/
#include <linux/acpi.h>
+#include <linux/async.h>
#include <linux/blkdev.h>
#include <linux/cleanup.h>
#include <linux/cpufreq.h>
@@ -3524,6 +3525,7 @@ static int device_private_init(struct device *dev)
klist_init(&dev->p->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->p->deferred_probe);
+ dev->p->shutdown_after = 0;
return 0;
}
@@ -4779,6 +4781,8 @@ out:
}
EXPORT_SYMBOL_GPL(device_change_owner);
+static ASYNC_DOMAIN(sd_domain);
+
static void shutdown_one_device(struct device *dev)
{
/* hold lock to avoid race with probe/release */
@@ -4815,11 +4819,33 @@ static void shutdown_one_device(struct device *dev)
}
/**
+ * shutdown_one_device_async
+ * @data: the pointer to the struct device to be shutdown
+ * @cookie: not used
+ *
+ * Shuts down one device, after waiting for shutdown_after to complete.
+ * shutdown_after should be set to the cookie of the last child or consumer
+ * of this device to be shutdown (if any), or to the cookie of the previous
+ * device to be shut down for devices that don't enable asynchronous shutdown.
+ */
+static void shutdown_one_device_async(void *data, async_cookie_t cookie)
+{
+ struct device *dev = data;
+
+ async_synchronize_cookie_domain(dev->p->shutdown_after + 1, &sd_domain);
+
+ shutdown_one_device(dev);
+}
+
+/**
* device_shutdown - call ->shutdown() on each device to shutdown.
*/
void device_shutdown(void)
{
struct device *dev, *parent;
+ async_cookie_t cookie = 0;
+ struct device_link *link;
+ int idx;
wait_for_device_probe();
device_block_probing();
@@ -4850,11 +4876,37 @@ void device_shutdown(void)
list_del_init(&dev->kobj.entry);
spin_unlock(&devices_kset->list_lock);
- shutdown_one_device(dev);
+
+ /*
+ * Set cookie for devices that will be shut down synchronously
+ */
+ if (!dev->driver || !dev->driver->async_shutdown_enable)
+ dev->p->shutdown_after = cookie;
+
+ get_device(dev);
+ get_device(parent);
+
+ cookie = async_schedule_domain(shutdown_one_device_async,
+ dev, &sd_domain);
+ /*
+ * Ensure parent & suppliers wait for this device to shut down
+ */
+ if (parent) {
+ parent->p->shutdown_after = cookie;
+ put_device(parent);
+ }
+
+ idx = device_links_read_lock();
+ list_for_each_entry_rcu(link, &dev->links.suppliers, c_node,
+ device_links_read_lock_held())
+ link->supplier->p->shutdown_after = cookie;
+ device_links_read_unlock(idx);
+ put_device(dev);
spin_lock(&devices_kset->list_lock);
}
spin_unlock(&devices_kset->list_lock);
+ async_synchronize_full_domain(&sd_domain);
}
/*