All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-11 17:12 [PATCH 1/5] driver core: do not always lock parent in shutdown David Jeffery
@ 2026-03-11 17:12 ` David Jeffery
  2026-03-11 19:40   ` Randy Dunlap
  2026-03-11 23:05   ` Bjorn Helgaas
  0 siblings, 2 replies; 26+ messages in thread
From: David Jeffery @ 2026-03-11 17:12 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	David Jeffery, Stuart Hayes, Laurence Oberman

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 104 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  13 ++++++
 3 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 79d031d2d845..ea2a039e7907 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -113,6 +113,7 @@ struct driver_type {
  * @device - pointer back to the struct device that this structure is
  * associated with.
  * @driver_type - The type of the bound Rust driver.
+ * @complete - completion for device shutdown ordering
  * @dead - This device is currently either in the process of or has been
  *	removed from the system. Any asynchronous events scheduled for this
  *	device should exit without taking any action.
@@ -132,6 +133,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index c2c35f95f751..07f564eb5823 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3538,6 +3543,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4782,6 +4788,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void shutdown_one_device(struct device *dev)
 {
 	/* hold lock to avoid race with probe/release */
@@ -4808,6 +4845,8 @@ static void shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 	if (dev->parent && dev->bus && dev->bus->need_parent_lock)
 		device_unlock(dev->parent);
@@ -4816,6 +4855,58 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev->parent);
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put) {
+		put_device(needs_put->parent);
+		put_device(needs_put);
+	}
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4828,6 +4919,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * paralellism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4852,11 +4949,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index 0be95294b6e6..da1db7d235c9 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -551,6 +551,8 @@ struct device_physical_location {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  *
  * At the lowest level, every device in a Linux system is represented by an
  * instance of struct device. The device structure contains the information
@@ -669,6 +671,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
+	bool			async_shutdown:1;
 };
 
 /**
@@ -824,6 +827,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline bool device_enable_async_shutdown(struct device *dev)
+{
+	return dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return !!dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-11 17:12 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
@ 2026-03-11 19:40   ` Randy Dunlap
  2026-03-11 23:05   ` Bjorn Helgaas
  1 sibling, 0 replies; 26+ messages in thread
From: Randy Dunlap @ 2026-03-11 19:40 UTC (permalink / raw)
  To: David Jeffery, linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman

Hi,

On 3/11/26 10:12 AM, David Jeffery wrote:
> Patterned after async suspend, allow devices to mark themselves as wanting
> to perform async shutdown. Devices using async shutdown wait only for their
> dependencies to shutdown before executing their shutdown routine.
> 
> Sync shutdown devices are shut down one at a time and will only wait for an
> async shutdown device if the async device is a dependency.
> 
> Signed-off-by: David Jeffery <djeffery@redhat.com>
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> Tested-by: Laurence Oberman <loberman@redhat.com>
> ---
>  drivers/base/base.h    |   2 +
>  drivers/base/core.c    | 104 ++++++++++++++++++++++++++++++++++++++++-
>  include/linux/device.h |  13 ++++++
>  3 files changed, 118 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index 79d031d2d845..ea2a039e7907 100644
> --- a/drivers/base/base.h
> +++ b/drivers/base/base.h

I know this isn't directly about this patch, but would you mind adding
descriptions that are currently missing for a couple of struct member fields?

Warning: drivers/base/base.h:59 struct member 'drivers_autoprobe' not described in 'subsys_private'
Warning: drivers/base/base.h:135 struct member 'deferred_probe_reason' not described in 'device_private'

thanks.
-- 
~Randy


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-11 17:12 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
  2026-03-11 19:40   ` Randy Dunlap
@ 2026-03-11 23:05   ` Bjorn Helgaas
  2026-03-12 14:01     ` David Jeffery
  1 sibling, 1 reply; 26+ messages in thread
From: Bjorn Helgaas @ 2026-03-11 23:05 UTC (permalink / raw)
  To: David Jeffery
  Cc: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman

On Wed, Mar 11, 2026 at 01:12:07PM -0400, David Jeffery wrote:
> Patterned after async suspend, allow devices to mark themselves as wanting
> to perform async shutdown. Devices using async shutdown wait only for their
> dependencies to shutdown before executing their shutdown routine.

I'm not an expert on dependencies.  Is it obvious to everybody else
how these dependencies are expressed?  What would I look at to verify
that, for example, PCI devices are dependencies of the PCI bridges
leading to them?  I suppose it's the same dependencies used for
suspend?

From wait_for_shutdown_dependencies() below, it looks like we'll wait
for each child of dev and then wait for each consumer of dev before
shutting down dev itself.

> Sync shutdown devices are shut down one at a time and will only wait for an
> async shutdown device if the async device is a dependency.

> @@ -132,6 +133,7 @@ struct device_private {
>  #ifdef CONFIG_RUST
>  	struct driver_type driver_type;
>  #endif
> +	struct completion complete;

I thought "complete" might be a little too generic, but I guess async
suspend uses "dev->power.completion" :)

> +static void wait_for_shutdown_dependencies(struct device *dev, bool async)
> +{
> +	struct device_link *link;
> +	int idx;
> +
> +	device_for_each_child(dev, &async, wait_for_device_shutdown);
> +
> +	idx = device_links_read_lock();
> +
> +	dev_for_each_link_to_consumer(link, dev)
> +		if (!device_link_flag_is_sync_state_only(link->flags))
> +			wait_for_device_shutdown(link->consumer, &async);
> +
> +	device_links_read_unlock(idx);
> +}

> @@ -4828,6 +4919,12 @@ void device_shutdown(void)
>  
>  	cpufreq_suspend();
>  
> +	/*
> +	 * Start async device threads where possible to maximize potential
> +	 * paralellism and minimize false dependency on unrelated sync devices

s/paralellism/parallelism/

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-11 23:05   ` Bjorn Helgaas
@ 2026-03-12 14:01     ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-03-12 14:01 UTC (permalink / raw)
  To: Bjorn Helgaas
  Cc: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman

On Wed, Mar 11, 2026 at 7:05 PM Bjorn Helgaas <helgaas@kernel.org> wrote:
>
> On Wed, Mar 11, 2026 at 01:12:07PM -0400, David Jeffery wrote:
> > Patterned after async suspend, allow devices to mark themselves as wanting
> > to perform async shutdown. Devices using async shutdown wait only for their
> > dependencies to shutdown before executing their shutdown routine.
>
> I'm not an expert on dependencies.  Is it obvious to everybody else
> how these dependencies are expressed?  What would I look at to verify
> that, for example, PCI devices are dependencies of the PCI bridges
> leading to them?  I suppose it's the same dependencies used for
> suspend?

Yes, it uses the same dependencies as async suspend does.

> From wait_for_shutdown_dependencies() below, it looks like we'll wait
> for each child of dev and then wait for each consumer of dev before
> shutting down dev itself.

Right, just like async suspend, all children and consumers need to
shut down first for async shutdown.

>
> > Sync shutdown devices are shut down one at a time and will only wait for an
> > async shutdown device if the async device is a dependency.
>
> > @@ -132,6 +133,7 @@ struct device_private {
> >  #ifdef CONFIG_RUST
> >       struct driver_type driver_type;
> >  #endif
> > +     struct completion complete;
>
> I thought "complete" might be a little too generic, but I guess async
> suspend uses "dev->power.completion" :)

I am open to a better name, but anything I came up with ended up
excessively long in my opinion, so I settled on the current and boring
"complete".

>
> > +static void wait_for_shutdown_dependencies(struct device *dev, bool async)
> > +{
> > +     struct device_link *link;
> > +     int idx;
> > +
> > +     device_for_each_child(dev, &async, wait_for_device_shutdown);
> > +
> > +     idx = device_links_read_lock();
> > +
> > +     dev_for_each_link_to_consumer(link, dev)
> > +             if (!device_link_flag_is_sync_state_only(link->flags))
> > +                     wait_for_device_shutdown(link->consumer, &async);
> > +
> > +     device_links_read_unlock(idx);
> > +}
>
> > @@ -4828,6 +4919,12 @@ void device_shutdown(void)
> >
> >       cpufreq_suspend();
> >
> > +     /*
> > +      * Start async device threads where possible to maximize potential
> > +      * paralellism and minimize false dependency on unrelated sync devices
>
> s/paralellism/parallelism/
>

Gah, I will correct it.

David Jeffery


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-19 14:11 [PATCH v12 0/5] shut down devices asynchronously David Jeffery
@ 2026-03-19 14:11 ` David Jeffery
  2026-03-23  9:43   ` Maurizio Lombardi
  0 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-03-19 14:11 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	David Jeffery

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 104 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  13 ++++++
 3 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 79d031d2d845..ea2a039e7907 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -113,6 +113,7 @@ struct driver_type {
  * @device - pointer back to the struct device that this structure is
  * associated with.
  * @driver_type - The type of the bound Rust driver.
+ * @complete - completion for device shutdown ordering
  * @dead - This device is currently either in the process of or has been
  *	removed from the system. Any asynchronous events scheduled for this
  *	device should exit without taking any action.
@@ -132,6 +133,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 2e9094f5c5aa..53568b820a13 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3538,6 +3543,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4782,6 +4788,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4805,6 +4842,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4823,6 +4862,58 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev->parent);
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put) {
+		put_device(needs_put->parent);
+		put_device(needs_put);
+	}
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4835,6 +4926,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4859,11 +4956,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index 0be95294b6e6..da1db7d235c9 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -551,6 +551,8 @@ struct device_physical_location {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  *
  * At the lowest level, every device in a Linux system is represented by an
  * instance of struct device. The device structure contains the information
@@ -669,6 +671,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
+	bool			async_shutdown:1;
 };
 
 /**
@@ -824,6 +827,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline bool device_enable_async_shutdown(struct device *dev)
+{
+	return dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return !!dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-19 14:11 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
@ 2026-03-23  9:43   ` Maurizio Lombardi
  2026-03-23 14:07     ` David Jeffery
  0 siblings, 1 reply; 26+ messages in thread
From: Maurizio Lombardi @ 2026-03-23  9:43 UTC (permalink / raw)
  To: David Jeffery, linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas

On Thu Mar 19, 2026 at 3:11 PM CET, David Jeffery wrote:
> Patterned after async suspend, allow devices to mark themselves as wanting
> to perform async shutdown. Devices using async shutdown wait only for their
> dependencies to shutdown before executing their shutdown routine.
>
> Sync shutdown devices are shut down one at a time and will only wait for an
> async shutdown device if the async device is a dependency.
>
> Signed-off-by: David Jeffery <djeffery@redhat.com>
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> Tested-by: Laurence Oberman <loberman@redhat.com>
> ---
>  drivers/base/base.h    |   2 +
>  drivers/base/core.c    | 104 ++++++++++++++++++++++++++++++++++++++++-
>  include/linux/device.h |  13 ++++++
>  3 files changed, 118 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index 79d031d2d845..ea2a039e7907 100644
> --- a/drivers/base/base.h
> +++ b/drivers/base/base.h
> @@ -113,6 +113,7 @@ struct driver_type {
>   * @device - pointer back to the struct device that this structure is
>   * associated with.
>   * @driver_type - The type of the bound Rust driver.
> + * @complete - completion for device shutdown ordering
>   * @dead - This device is currently either in the process of or has been
>   *	removed from the system. Any asynchronous events scheduled for this
>   *	device should exit without taking any action.
> @@ -132,6 +133,7 @@ struct device_private {
>  #ifdef CONFIG_RUST
>  	struct driver_type driver_type;
>  #endif
> +	struct completion complete;
>  	u8 dead:1;
>  };
>  #define to_device_private_parent(obj)	\
> diff --git a/drivers/base/core.c b/drivers/base/core.c
> index 2e9094f5c5aa..53568b820a13 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>
> @@ -37,6 +38,10 @@
>  #include "physical_location.h"
>  #include "power/power.h"
>  
> +static bool async_shutdown = true;
> +module_param(async_shutdown, bool, 0644);
> +MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
> +
>  /* Device links support. */
>  static LIST_HEAD(deferred_sync);
>  static unsigned int defer_sync_state_count = 1;
> @@ -3538,6 +3543,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);
> +	init_completion(&dev->p->complete);
>  	return 0;
>  }
>  
> @@ -4782,6 +4788,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
>  	return error;
>  }
>  
> +static bool wants_async_shutdown(struct device *dev)
> +{
> +	return async_shutdown && dev->async_shutdown;
> +}
> +
> +static int wait_for_device_shutdown(struct device *dev, void *data)
> +{
> +	bool async = *(bool *)data;
> +
> +	if (async || wants_async_shutdown(dev))
> +		wait_for_completion(&dev->p->complete);
> +
> +	return 0;
> +}
> +
> +static void wait_for_shutdown_dependencies(struct device *dev, bool async)
> +{
> +	struct device_link *link;
> +	int idx;
> +
> +	device_for_each_child(dev, &async, wait_for_device_shutdown);
> +
> +	idx = device_links_read_lock();
> +
> +	dev_for_each_link_to_consumer(link, dev)
> +		if (!device_link_flag_is_sync_state_only(link->flags))
> +			wait_for_device_shutdown(link->consumer, &async);
> +
> +	device_links_read_unlock(idx);
> +}
> +
>  static void __shutdown_one_device(struct device *dev)
>  {
>  	device_lock(dev);
> @@ -4805,6 +4842,8 @@ static void __shutdown_one_device(struct device *dev)
>  		dev->driver->shutdown(dev);
>  	}
>  
> +	complete_all(&dev->p->complete);
> +
>  	device_unlock(dev);
>  }
>  
> @@ -4823,6 +4862,58 @@ static void shutdown_one_device(struct device *dev)
>  	put_device(dev);
>  }
>  
> +static void async_shutdown_handler(void *data, async_cookie_t cookie)
> +{
> +	struct device *dev = data;
> +
> +	wait_for_shutdown_dependencies(dev, true);
> +	shutdown_one_device(dev);
> +}
> +
> +static bool shutdown_device_async(struct device *dev)
> +{
> +	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
> +		return true;
> +	return false;
> +}
> +
> +
> +static void early_async_shutdown_devices(void)
> +{
> +	struct device *dev, *next, *needs_put = NULL;
> +
> +	if (!async_shutdown)
> +		return;
> +
> +	spin_lock(&devices_kset->list_lock);
> +
> +	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
> +					 kobj.entry) {
> +		if (wants_async_shutdown(dev)) {
> +			get_device(dev->parent);
> +			get_device(dev);
> +
> +			if (shutdown_device_async(dev)) {
> +				list_del_init(&dev->kobj.entry);
> +			} else {
> +				/*
> +				 * async failed, clean up extra references
> +				 * and run from the standard shutdown loop
> +				 */
> +				needs_put = dev;
> +				break;
> +			}
> +		}
> +	}
> +
> +	spin_unlock(&devices_kset->list_lock);
> +
> +	if (needs_put) {
> +		put_device(needs_put->parent);
> +		put_device(needs_put);
> +	}
> +}
> +
>  /**
>   * device_shutdown - call ->shutdown() on each device to shutdown.
>   */
> @@ -4835,6 +4926,12 @@ void device_shutdown(void)
>  
>  	cpufreq_suspend();
>  
> +	/*
> +	 * Start async device threads where possible to maximize potential
> +	 * parallelism and minimize false dependency on unrelated sync devices
> +	 */
> +	early_async_shutdown_devices();
> +
>  	spin_lock(&devices_kset->list_lock);
>  	/*
>  	 * Walk the devices list backward, shutting down each in turn.
> @@ -4859,11 +4956,16 @@ void device_shutdown(void)
>  		list_del_init(&dev->kobj.entry);
>  		spin_unlock(&devices_kset->list_lock);
>  
> -		shutdown_one_device(dev);
> +		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
> +			wait_for_shutdown_dependencies(dev, false);
> +			shutdown_one_device(dev);
> +		}
>  
>  		spin_lock(&devices_kset->list_lock);
>  	}
>  	spin_unlock(&devices_kset->list_lock);
> +
> +	async_synchronize_full();
>  }
>  
>  /*
> diff --git a/include/linux/device.h b/include/linux/device.h
> index 0be95294b6e6..da1db7d235c9 100644
> --- a/include/linux/device.h
> +++ b/include/linux/device.h
> @@ -551,6 +551,8 @@ struct device_physical_location {
>   * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
>   * @dma_iommu: Device is using default IOMMU implementation for DMA and
>   *		doesn't rely on dma_ops structure.
> + * @async_shutdown: Device shutdown may be run asynchronously and in parallel
> + *		to the shutdown of unrelated devices
>   *
>   * At the lowest level, every device in a Linux system is represented by an
>   * instance of struct device. The device structure contains the information
> @@ -669,6 +671,7 @@ struct device {
>  #ifdef CONFIG_IOMMU_DMA
>  	bool			dma_iommu:1;
>  #endif
> +	bool			async_shutdown:1;
>  };
>  
>  /**
> @@ -824,6 +827,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
>  	return !!dev->power.async_suspend;
>  }
>  
> +static inline bool device_enable_async_shutdown(struct device *dev)
> +{
> +	return dev->async_shutdown = true;
> +}

Shouldn't this function just return void?

Maurizio

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-03-23  9:43   ` Maurizio Lombardi
@ 2026-03-23 14:07     ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-03-23 14:07 UTC (permalink / raw)
  To: Maurizio Lombardi
  Cc: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
	Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas

On Mon, Mar 23, 2026 at 5:50 AM Maurizio Lombardi <mlombard@arkamax.eu> wrote:
>
> On Thu Mar 19, 2026 at 3:11 PM CET, David Jeffery wrote:
> > Patterned after async suspend, allow devices to mark themselves as wanting
> > to perform async shutdown. Devices using async shutdown wait only for their
> > dependencies to shutdown before executing their shutdown routine.
> >
> > Sync shutdown devices are shut down one at a time and will only wait for an
> > async shutdown device if the async device is a dependency.
> >
> > Signed-off-by: David Jeffery <djeffery@redhat.com>
> > Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> > Tested-by: Laurence Oberman <loberman@redhat.com>
> > ---
> >  drivers/base/base.h    |   2 +
> >  drivers/base/core.c    | 104 ++++++++++++++++++++++++++++++++++++++++-
> >  include/linux/device.h |  13 ++++++
> >  3 files changed, 118 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/base/base.h b/drivers/base/base.h
> > index 79d031d2d845..ea2a039e7907 100644
> > --- a/drivers/base/base.h
> > +++ b/drivers/base/base.h
> > @@ -113,6 +113,7 @@ struct driver_type {
> >   * @device - pointer back to the struct device that this structure is
> >   * associated with.
> >   * @driver_type - The type of the bound Rust driver.
> > + * @complete - completion for device shutdown ordering
> >   * @dead - This device is currently either in the process of or has been
> >   *   removed from the system. Any asynchronous events scheduled for this
> >   *   device should exit without taking any action.
> > @@ -132,6 +133,7 @@ struct device_private {
> >  #ifdef CONFIG_RUST
> >       struct driver_type driver_type;
> >  #endif
> > +     struct completion complete;
> >       u8 dead:1;
> >  };
> >  #define to_device_private_parent(obj)        \
> > diff --git a/drivers/base/core.c b/drivers/base/core.c
> > index 2e9094f5c5aa..53568b820a13 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>
> > @@ -37,6 +38,10 @@
> >  #include "physical_location.h"
> >  #include "power/power.h"
> >
> > +static bool async_shutdown = true;
> > +module_param(async_shutdown, bool, 0644);
> > +MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
> > +
> >  /* Device links support. */
> >  static LIST_HEAD(deferred_sync);
> >  static unsigned int defer_sync_state_count = 1;
> > @@ -3538,6 +3543,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);
> > +     init_completion(&dev->p->complete);
> >       return 0;
> >  }
> >
> > @@ -4782,6 +4788,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
> >       return error;
> >  }
> >
> > +static bool wants_async_shutdown(struct device *dev)
> > +{
> > +     return async_shutdown && dev->async_shutdown;
> > +}
> > +
> > +static int wait_for_device_shutdown(struct device *dev, void *data)
> > +{
> > +     bool async = *(bool *)data;
> > +
> > +     if (async || wants_async_shutdown(dev))
> > +             wait_for_completion(&dev->p->complete);
> > +
> > +     return 0;
> > +}
> > +
> > +static void wait_for_shutdown_dependencies(struct device *dev, bool async)
> > +{
> > +     struct device_link *link;
> > +     int idx;
> > +
> > +     device_for_each_child(dev, &async, wait_for_device_shutdown);
> > +
> > +     idx = device_links_read_lock();
> > +
> > +     dev_for_each_link_to_consumer(link, dev)
> > +             if (!device_link_flag_is_sync_state_only(link->flags))
> > +                     wait_for_device_shutdown(link->consumer, &async);
> > +
> > +     device_links_read_unlock(idx);
> > +}
> > +
> >  static void __shutdown_one_device(struct device *dev)
> >  {
> >       device_lock(dev);
> > @@ -4805,6 +4842,8 @@ static void __shutdown_one_device(struct device *dev)
> >               dev->driver->shutdown(dev);
> >       }
> >
> > +     complete_all(&dev->p->complete);
> > +
> >       device_unlock(dev);
> >  }
> >
> > @@ -4823,6 +4862,58 @@ static void shutdown_one_device(struct device *dev)
> >       put_device(dev);
> >  }
> >
> > +static void async_shutdown_handler(void *data, async_cookie_t cookie)
> > +{
> > +     struct device *dev = data;
> > +
> > +     wait_for_shutdown_dependencies(dev, true);
> > +     shutdown_one_device(dev);
> > +}
> > +
> > +static bool shutdown_device_async(struct device *dev)
> > +{
> > +     if (async_schedule_dev_nocall(async_shutdown_handler, dev))
> > +             return true;
> > +     return false;
> > +}
> > +
> > +
> > +static void early_async_shutdown_devices(void)
> > +{
> > +     struct device *dev, *next, *needs_put = NULL;
> > +
> > +     if (!async_shutdown)
> > +             return;
> > +
> > +     spin_lock(&devices_kset->list_lock);
> > +
> > +     list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
> > +                                      kobj.entry) {
> > +             if (wants_async_shutdown(dev)) {
> > +                     get_device(dev->parent);
> > +                     get_device(dev);
> > +
> > +                     if (shutdown_device_async(dev)) {
> > +                             list_del_init(&dev->kobj.entry);
> > +                     } else {
> > +                             /*
> > +                              * async failed, clean up extra references
> > +                              * and run from the standard shutdown loop
> > +                              */
> > +                             needs_put = dev;
> > +                             break;
> > +                     }
> > +             }
> > +     }
> > +
> > +     spin_unlock(&devices_kset->list_lock);
> > +
> > +     if (needs_put) {
> > +             put_device(needs_put->parent);
> > +             put_device(needs_put);
> > +     }
> > +}
> > +
> >  /**
> >   * device_shutdown - call ->shutdown() on each device to shutdown.
> >   */
> > @@ -4835,6 +4926,12 @@ void device_shutdown(void)
> >
> >       cpufreq_suspend();
> >
> > +     /*
> > +      * Start async device threads where possible to maximize potential
> > +      * parallelism and minimize false dependency on unrelated sync devices
> > +      */
> > +     early_async_shutdown_devices();
> > +
> >       spin_lock(&devices_kset->list_lock);
> >       /*
> >        * Walk the devices list backward, shutting down each in turn.
> > @@ -4859,11 +4956,16 @@ void device_shutdown(void)
> >               list_del_init(&dev->kobj.entry);
> >               spin_unlock(&devices_kset->list_lock);
> >
> > -             shutdown_one_device(dev);
> > +             if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
> > +                     wait_for_shutdown_dependencies(dev, false);
> > +                     shutdown_one_device(dev);
> > +             }
> >
> >               spin_lock(&devices_kset->list_lock);
> >       }
> >       spin_unlock(&devices_kset->list_lock);
> > +
> > +     async_synchronize_full();
> >  }
> >
> >  /*
> > diff --git a/include/linux/device.h b/include/linux/device.h
> > index 0be95294b6e6..da1db7d235c9 100644
> > --- a/include/linux/device.h
> > +++ b/include/linux/device.h
> > @@ -551,6 +551,8 @@ struct device_physical_location {
> >   * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
> >   * @dma_iommu: Device is using default IOMMU implementation for DMA and
> >   *           doesn't rely on dma_ops structure.
> > + * @async_shutdown: Device shutdown may be run asynchronously and in parallel
> > + *           to the shutdown of unrelated devices
> >   *
> >   * At the lowest level, every device in a Linux system is represented by an
> >   * instance of struct device. The device structure contains the information
> > @@ -669,6 +671,7 @@ struct device {
> >  #ifdef CONFIG_IOMMU_DMA
> >       bool                    dma_iommu:1;
> >  #endif
> > +     bool                    async_shutdown:1;
> >  };
> >
> >  /**
> > @@ -824,6 +827,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
> >       return !!dev->power.async_suspend;
> >  }
> >
> > +static inline bool device_enable_async_shutdown(struct device *dev)
> > +{
> > +     return dev->async_shutdown = true;
> > +}
>
> Shouldn't this function just return void?

Yes, it should just be changed to a void.

David Jeffery


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-04-07 15:35 [PATCH v13 0/5] shut down devices asynchronously David Jeffery
@ 2026-04-07 15:35 ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-04-07 15:35 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, David Jeffery

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 101 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  13 ++++++
 3 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 1af95ac68b77..cd435adeeac5 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -113,6 +113,7 @@ struct driver_type {
  * @device - pointer back to the struct device that this structure is
  * associated with.
  * @driver_type - The type of the bound Rust driver.
+ * @complete - completion for device shutdown ordering
  * @dead - This device is currently either in the process of or has been
  *	removed from the system. Any asynchronous events scheduled for this
  *	device should exit without taking any action.
@@ -132,6 +133,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 0fb2f3ccc3bd..89b482435e85 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3540,6 +3545,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4784,6 +4790,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4807,6 +4844,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4828,6 +4867,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4840,6 +4928,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4858,11 +4952,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index e65d564f01cd..cdc890e5d6ae 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -553,6 +553,8 @@ struct device_physical_location {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  *
  * At the lowest level, every device in a Linux system is represented by an
  * instance of struct device. The device structure contains the information
@@ -675,6 +677,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
+	bool			async_shutdown:1;
 };
 
 /**
@@ -878,6 +881,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline void device_enable_async_shutdown(struct device *dev)
+{
+	dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return !!dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-04-20 15:26 [PATCH v14 0/5] shut down devices asynchronously David Jeffery
@ 2026-04-20 15:26 ` David Jeffery
  2026-04-21  7:49   ` John Garry
  0 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-04-20 15:26 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, David Jeffery

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 101 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  13 ++++++
 3 files changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 1af95ac68b77..cd435adeeac5 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -113,6 +113,7 @@ struct driver_type {
  * @device - pointer back to the struct device that this structure is
  * associated with.
  * @driver_type - The type of the bound Rust driver.
+ * @complete - completion for device shutdown ordering
  * @dead - This device is currently either in the process of or has been
  *	removed from the system. Any asynchronous events scheduled for this
  *	device should exit without taking any action.
@@ -132,6 +133,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index a32fc7141957..2319c643b11f 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3539,6 +3544,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4783,6 +4789,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4806,6 +4843,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4827,6 +4866,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4839,6 +4927,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4857,11 +4951,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index e65d564f01cd..cdc890e5d6ae 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -553,6 +553,8 @@ struct device_physical_location {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  *
  * At the lowest level, every device in a Linux system is represented by an
  * instance of struct device. The device structure contains the information
@@ -675,6 +677,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
+	bool			async_shutdown:1;
 };
 
 /**
@@ -878,6 +881,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline void device_enable_async_shutdown(struct device *dev)
+{
+	dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return !!dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-04-20 15:26 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
@ 2026-04-21  7:49   ` John Garry
  2026-04-21 17:31     ` David Jeffery
  0 siblings, 1 reply; 26+ messages in thread
From: John Garry @ 2026-04-21  7:49 UTC (permalink / raw)
  To: David Jeffery, linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen

On 20/04/2026 16:26, David Jeffery wrote:
> +static inline bool device_async_shutdown_enabled(struct device *dev)
> +{
> +	return !!dev->async_shutdown;
> +}

nit: async_shutdown is already a bool, so no need for !!. Furthermore, 
since this function returns a bool, that conversion is automatic.

^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-04-21  7:49   ` John Garry
@ 2026-04-21 17:31     ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-04-21 17:31 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, David Jeffery

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---

Updated to resolve conflicts with latest Linus tree


 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 101 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  14 +++++-
 3 files changed, 115 insertions(+), 2 deletions(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 30b416588617..8b155a91ac3a 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -116,6 +116,7 @@ struct driver_type {
  * @device: pointer back to the struct device that this structure is
  *	    associated with.
  * @driver_type: The type of the bound Rust driver.
+ * @complete: completion for device shutdown ordering
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
@@ -135,6 +136,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 00979555995f..92f788fd813d 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3540,6 +3545,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4799,6 +4805,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4822,6 +4859,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4843,6 +4882,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4855,6 +4943,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4873,11 +4967,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index 9c8fde6a3d86..424a229d69df 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -615,6 +615,8 @@ enum struct_device_flags {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  * @flags:	DEV_FLAG_XXX flags. Use atomic bitfield operations to modify.
  *
  * At the lowest level, every device in a Linux system is represented by an
@@ -738,7 +740,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
-
+	bool			async_shutdown:1;
 	DECLARE_BITMAP(flags, DEV_FLAG_COUNT);
 };
 
@@ -969,6 +971,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline void device_enable_async_shutdown(struct device *dev)
+{
+	dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return !!dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-04-29 17:50 [PATCH v15 0/5] shut down devices asynchronously David Jeffery
@ 2026-04-29 17:50 ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-04-29 17:50 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery,
	Pasha Tatashin

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 drivers/base/base.h    |   2 +
 drivers/base/core.c    | 101 ++++++++++++++++++++++++++++++++++++++++-
 include/linux/device.h |  14 +++++-
 3 files changed, 115 insertions(+), 2 deletions(-)

diff --git a/drivers/base/base.h b/drivers/base/base.h
index 30b416588617..8b155a91ac3a 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -116,6 +116,7 @@ struct driver_type {
  * @device: pointer back to the struct device that this structure is
  *	    associated with.
  * @driver_type: The type of the bound Rust driver.
+ * @complete: completion for device shutdown ordering
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
@@ -135,6 +136,7 @@ struct device_private {
 #ifdef CONFIG_RUST
 	struct driver_type driver_type;
 #endif
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 00979555995f..92f788fd813d 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3540,6 +3545,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4799,6 +4805,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev->async_shutdown;
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4822,6 +4859,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4843,6 +4882,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4855,6 +4943,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4873,11 +4967,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index 9c8fde6a3d86..4b04cf62912b 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -615,6 +615,8 @@ enum struct_device_flags {
  * @dma_skip_sync: DMA sync operations can be skipped for coherent buffers.
  * @dma_iommu: Device is using default IOMMU implementation for DMA and
  *		doesn't rely on dma_ops structure.
+ * @async_shutdown: Device shutdown may be run asynchronously and in parallel
+ *		to the shutdown of unrelated devices
  * @flags:	DEV_FLAG_XXX flags. Use atomic bitfield operations to modify.
  *
  * At the lowest level, every device in a Linux system is represented by an
@@ -738,7 +740,7 @@ struct device {
 #ifdef CONFIG_IOMMU_DMA
 	bool			dma_iommu:1;
 #endif
-
+	bool			async_shutdown:1;
 	DECLARE_BITMAP(flags, DEV_FLAG_COUNT);
 };
 
@@ -969,6 +971,16 @@ static inline bool device_async_suspend_enabled(struct device *dev)
 	return !!dev->power.async_suspend;
 }
 
+static inline void device_enable_async_shutdown(struct device *dev)
+{
+	dev->async_shutdown = true;
+}
+
+static inline bool device_async_shutdown_enabled(struct device *dev)
+{
+	return dev->async_shutdown;
+}
+
 static inline bool device_pm_not_required(struct device *dev)
 {
 	return dev->power.no_pm;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-05-18 19:31 [PATCH v16 0/5] shut down devices asynchronously David Jeffery
@ 2026-05-18 19:32 ` David Jeffery
  0 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-05-18 19:32 UTC (permalink / raw)
  To: linux-kernel, driver-core, linux-pci, linux-scsi,
	Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich
  Cc: Tarun Sahu, Pasha Tatashin, Michał Cłapiński,
	Jordan Richards, Ewan Milne, John Meneghini, Lombardi, Maurizio,
	Stuart Hayes, Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery,
	Pasha Tatashin

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Enabled by default, async shutdown can be explicitly enabled or disabled
by using the kernel parameter "core.async_shutdown=<bool>"

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Tested-by: Tarun Sahu <tarunsahu@google.com>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 .../admin-guide/kernel-parameters.txt         |  10 ++
 drivers/base/base.h                           |   2 +
 drivers/base/core.c                           | 101 +++++++++++++++++-
 include/linux/device.h                        |   2 +
 4 files changed, 114 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5a51a36a048..dd912f47ace4 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1019,6 +1019,16 @@ Kernel parameters
 			seconds. A value of 0 disables the blank timer.
 			Defaults to 0.
 
+	core.async_shutdown=
+			[KNL]
+			Format: <bool>
+			Enable or disable asynchronous shutdown support. When
+			enabled, on system shutdown unrelated devices flagged
+			as async shutdown compatible may be shut down in
+			parallel and asynchronously. When disabled, device
+			shutdown is performed in a serially and synchronously.
+			Enabled by default.
+
 	coredump_filter=
 			[KNL] Change the default value for
 			/proc/<pid>/coredump_filter.
diff --git a/drivers/base/base.h b/drivers/base/base.h
index 483b99b4fa3d..aefa4f256d59 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -103,6 +103,7 @@ struct driver_private {
  *			   dev_err_probe() for later retrieval via debugfs
  * @device: pointer back to the struct device that this structure is
  *	    associated with.
+ * @complete: completion for device shutdown ordering
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
@@ -119,6 +120,7 @@ struct device_private {
 	const struct device_driver *async_driver;
 	char *deferred_probe_reason;
 	struct device *device;
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 6e2c37115bc1..cab10b0e70db 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3536,6 +3541,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -4795,6 +4801,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev_async_shutdown(dev);
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	device_lock(dev);
@@ -4818,6 +4855,8 @@ static void __shutdown_one_device(struct device *dev)
 		dev->driver->shutdown(dev);
 	}
 
+	complete_all(&dev->p->complete);
+
 	device_unlock(dev);
 }
 
@@ -4839,6 +4878,55 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+	return false;
+}
+
+
+static void early_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *needs_put = NULL;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			get_device(dev);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra references
+				 * and run from the standard shutdown loop
+				 */
+				needs_put = dev;
+				break;
+			}
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4851,6 +4939,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	early_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4869,11 +4963,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index d54c86d77764..0f2aeba34483 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -546,6 +546,7 @@ enum struct_device_flags {
 	DEV_FLAG_OF_NODE_REUSED = 7,
 	DEV_FLAG_OFFLINE_DISABLED = 8,
 	DEV_FLAG_OFFLINE = 9,
+	DEV_FLAG_ASYNC_SHUTDOWN = 10,
 
 	DEV_FLAG_COUNT
 };
@@ -763,6 +764,7 @@ __create_dev_flag_accessors(dma_coherent, DEV_FLAG_DMA_COHERENT);
 __create_dev_flag_accessors(of_node_reused, DEV_FLAG_OF_NODE_REUSED);
 __create_dev_flag_accessors(offline_disabled, DEV_FLAG_OFFLINE_DISABLED);
 __create_dev_flag_accessors(offline, DEV_FLAG_OFFLINE);
+__create_dev_flag_accessors(async_shutdown, DEV_FLAG_ASYNC_SHUTDOWN);
 
 #undef __create_dev_flag_accessors
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH v17 0/5] shut down devices asynchronously
@ 2026-06-16 15:22 David Jeffery
  2026-06-16 15:22 ` [PATCH 1/5] driver core: separate function to shutdown one device David Jeffery
                   ` (4 more replies)
  0 siblings, 5 replies; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery

These patches are rebased against the driver-core tree's driver-core-next
branch and should also apply against recent linux-next. Changes for v17 are
contained in patches 2 and 3 which are most in need of reviewing.


This patchset allows the kernel to shutdown devices asynchronously and
unrelated async devices to be shut down in parallel to each other.

Only devices which explicitly enable it are shut down asynchronously. The
default is for a device to be shut down from the synchronous shutdown loop.

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. And on another system from
80 seconds to 11.

And thank you to everyone who has spent some of their valuable time
providing reviews, suggestions, criticisms, or tests on the various
iterations of this patchset.

Changes from V16:

Drop spinlock before async subsystem call which uses GFP_KERNEL
Handle that async shutdown can widen races between device shutdown and deletion
  * __shutdown_one_device will immediately return if a device is dead
  * Set shutdown device completion to complete when marking a device dead to
      prevent waiting on a dead device
  * Only late-access a parent pointer if device is in a non-dead state to
      ensure the pointer is still valid

Changes from V15:

The async_shutdown bit field is converted to a device flags bit
Convert all patches to use the flag bit accessor macros to set or check if
  async shutdown should be used
Added documentation on the kernel parameter to control use of async shutdown

Changes from V14:

Remove unneeded use of '!!' with boolean type

Changes from V13:

Remove duplicate flagging of async shutdown on scsi hosts/targets/devices

Changes from V12:

Only acquire a parent reference if acquiring the parent's lock
device_enable_async_shutdown should return void
Minor comment and description cleanups

Changes from V11:

  * Swap the order of the first two patches
  * Rework conditional parent locking so that lock and unlock no longer use
    separate conditional checks
  * Remove an used variable
  * Comment and description text cleanups

Changes from V10:

Reworked to more closely match the design used for async suspend
  * No longer uses async subsystem cookies for synchronization
  * Minimized changes to struct device
  * Enable async shutdown for pci and scsi devices which support async suspend

Changes from V9:

Address resource and timing issues when spawning a unique async thread
for every device during shutdown:
  * Make the asynchronous threads able to shut down multiple devices,
    instead of spawning a unique thread for every device.
  * Modify core kernel async code with a custom wake function so it
    doesn't wake up a thread waiting to synchronize on a cookie until
    the cookie has reached the desired value, instead of waking up
    every waiting thread to check the cookie every time an async thread
    ends.

Changes from V8:

Deal with shutdown hangs resulting when a parent/supplier device is
  later in the devices_kset list than its children/consumers:
  * Ignore sync_state_only devlinks for shutdown dependencies
  * Ignore shutdown_after for devices that don't want async shutdown
  * Add a sanity check to revert to sync shutdown for any device that
    would otherwise wait for a child/consumer shutdown that hasn't
    already been scheduled

Changes from V7:

Do not expose driver async_shutdown_enable in sysfs.
Wrapped a long line.

Changes from V6:

Removed a sysfs attribute that allowed the async device shutdown to be
"on" (with driver opt-out), "safe" (driver opt-in), or "off"... what was
previously "safe" is now the only behavior, so drivers now only need to
have the option to enable or disable async shutdown.

Changes from V5:

Separated into multiple patches to make review easier.
Reworked some code to make it more readable
Made devices wait for consumers to shut down, not just children
  (suggested by David Jeffery)

Changes from V4:

Change code to use cookies for synchronization rather than async domains
Allow async shutdown to be disabled via sysfs, and allow driver opt-in or
  opt-out of async shutdown (when not disabled), with ability to control
  driver opt-in/opt-out via sysfs
  
Changes from V3:

Bug fix (used "parent" not "dev->parent" in device_shutdown)
 
Changes from V2:
 
Removed recursive functions to schedule children to be shutdown before
  parents, since existing device_shutdown loop will already do this
 
Changes from V1:

Rewritten using kernel async code (suggested by Lukas Wunner)


Stuart Hayes (2):
  driver core: separate function to shutdown one device
  driver core: do not always lock parent in shutdown

David Jeffery (3):
  driver core: async device shutdown infrastructure
  PCI: Enable async shutdown support
  scsi: Enable async shutdown support

 .../admin-guide/kernel-parameters.txt         |  10 +
 drivers/base/base.h                           |   2 +
 drivers/base/core.c                           | 214 +++++++++++++++---
 drivers/pci/probe.c                           |   2 +
 drivers/scsi/hosts.c                          |   2 +
 drivers/scsi/scsi_sysfs.c                     |   3 +
 include/linux/device.h                        |   2 +
 7 files changed, 199 insertions(+), 36 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 26+ messages in thread

* [PATCH 1/5] driver core: separate function to shutdown one device
  2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
@ 2026-06-16 15:22 ` David Jeffery
  2026-06-16 15:34   ` sashiko-bot
  2026-06-16 15:22 ` [PATCH 2/5] driver core: do not always lock parent in shutdown David Jeffery
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery,
	Pasha Tatashin

Make a separate function for the part of device_shutdown() that does the
shutown for a single device.  This is in preparation for making device
shutdown asynchronous.

Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Signed-off-by: David Jeffery <djeffery@redhat.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
 drivers/base/core.c | 71 +++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 32 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4d026682944f..74c693cd19cf 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4865,12 +4865,48 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static void shutdown_one_device(struct device *dev)
+{
+	struct device *parent = dev->parent;
+
+	/* hold lock to avoid race with probe/release */
+	if (parent)
+		device_lock(parent);
+	device_lock(dev);
+
+	/* Don't allow any more runtime suspends */
+	pm_runtime_get_noresume(dev);
+	pm_runtime_barrier(dev);
+
+	if (dev->class && dev->class->shutdown_pre) {
+		if (initcall_debug)
+			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);
+	} else if (dev->driver && dev->driver->shutdown) {
+		if (initcall_debug)
+			dev_info(dev, "shutdown\n");
+		dev->driver->shutdown(dev);
+	}
+
+	device_unlock(dev);
+	if (parent)
+		device_unlock(parent);
+
+	put_device(parent);
+	put_device(dev);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
 void device_shutdown(void)
 {
-	struct device *dev, *parent;
+	struct device *dev;
 
 	wait_for_device_probe();
 	device_block_probing();
@@ -4892,7 +4928,7 @@ void device_shutdown(void)
 		 * prevent it from being freed because parent's
 		 * lock is to be held
 		 */
-		parent = get_device(dev->parent);
+		get_device(dev->parent);
 		get_device(dev);
 		/*
 		 * Make sure the device is off the kset list, in the
@@ -4901,36 +4937,7 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		/* hold lock to avoid race with probe/release */
-		if (parent)
-			device_lock(parent);
-		device_lock(dev);
-
-		/* Don't allow any more runtime suspends */
-		pm_runtime_get_noresume(dev);
-		pm_runtime_barrier(dev);
-
-		if (dev->class && dev->class->shutdown_pre) {
-			if (initcall_debug)
-				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);
-		} else if (dev->driver && dev->driver->shutdown) {
-			if (initcall_debug)
-				dev_info(dev, "shutdown\n");
-			dev->driver->shutdown(dev);
-		}
-
-		device_unlock(dev);
-		if (parent)
-			device_unlock(parent);
-
-		put_device(dev);
-		put_device(parent);
+		shutdown_one_device(dev);
 
 		spin_lock(&devices_kset->list_lock);
 	}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 2/5] driver core: do not always lock parent in shutdown
  2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
  2026-06-16 15:22 ` [PATCH 1/5] driver core: separate function to shutdown one device David Jeffery
@ 2026-06-16 15:22 ` David Jeffery
  2026-06-16 15:38   ` sashiko-bot
  2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery

Don't lock a parent device unless it is needed in device_shutdown. This
is in preparation for making device shutdown asynchronous, when it will
be needed to allow children of a common parent to shut down
simultaneously.

And only acquire a reference to the parent device if the parent is to be
locked.

Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Signed-off-by: David Jeffery <djeffery@redhat.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 drivers/base/core.c | 42 ++++++++++++++++++++++++++----------------
 1 file changed, 26 insertions(+), 16 deletions(-)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 74c693cd19cf..3b3d983b1747 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -4865,14 +4865,10 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
-static void shutdown_one_device(struct device *dev)
+static void __shutdown_one_device(struct device *dev)
 {
-	struct device *parent = dev->parent;
-
-	/* hold lock to avoid race with probe/release */
-	if (parent)
-		device_lock(parent);
-	device_lock(dev);
+	if (dev->p->dead)
+		return;
 
 	/* Don't allow any more runtime suspends */
 	pm_runtime_get_noresume(dev);
@@ -4892,12 +4888,32 @@ static void shutdown_one_device(struct device *dev)
 			dev_info(dev, "shutdown\n");
 		dev->driver->shutdown(dev);
 	}
+}
 
-	device_unlock(dev);
-	if (parent)
+static void shutdown_one_device(struct device *dev)
+{
+	struct device *parent;
+
+	device_lock(dev);
+
+	/* use parent lock if needed to avoid race with probe/release */
+	if (dev->bus && dev->bus->need_parent_lock && !dev->p->dead &&
+	    (parent = get_device(dev->parent))) {
+		/* the parent lock needs to be acquired first, so re-lock */
+		device_unlock(dev);
+
+		device_lock(parent);
+		device_lock(dev);
+
+		__shutdown_one_device(dev);
+		device_unlock(dev);
 		device_unlock(parent);
+		put_device(parent);
+	} else {
+		__shutdown_one_device(dev);
+		device_unlock(dev);
+	}
 
-	put_device(parent);
 	put_device(dev);
 }
 
@@ -4923,12 +4939,6 @@ void device_shutdown(void)
 		dev = list_entry(devices_kset->list.prev, struct device,
 				kobj.entry);
 
-		/*
-		 * hold reference count of device's parent to
-		 * prevent it from being freed because parent's
-		 * lock is to be held
-		 */
-		get_device(dev->parent);
 		get_device(dev);
 		/*
 		 * Make sure the device is off the kset list, in the
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
  2026-06-16 15:22 ` [PATCH 1/5] driver core: separate function to shutdown one device David Jeffery
  2026-06-16 15:22 ` [PATCH 2/5] driver core: do not always lock parent in shutdown David Jeffery
@ 2026-06-16 15:22 ` David Jeffery
  2026-06-16 15:39   ` sashiko-bot
                     ` (2 more replies)
  2026-06-16 15:22 ` [PATCH 4/5] PCI: Enable async shutdown support David Jeffery
  2026-06-16 15:22 ` [PATCH 5/5] scsi: " David Jeffery
  4 siblings, 3 replies; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery

Patterned after async suspend, allow devices to mark themselves as wanting
to perform async shutdown. Devices using async shutdown wait only for their
dependencies to shutdown before executing their shutdown routine.

Sync shutdown devices are shut down one at a time and will only wait for an
async shutdown device if the async device is a dependency.

Enabled by default, async shutdown can be explicitly enabled or disabled
by using the kernel parameter "core.async_shutdown=<bool>"

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
---
 .../admin-guide/kernel-parameters.txt         |  10 ++
 drivers/base/base.h                           |   2 +
 drivers/base/core.c                           | 127 +++++++++++++++++-
 include/linux/device.h                        |   2 +
 4 files changed, 140 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index b5a51a36a048..dd912f47ace4 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1019,6 +1019,16 @@ Kernel parameters
 			seconds. A value of 0 disables the blank timer.
 			Defaults to 0.
 
+	core.async_shutdown=
+			[KNL]
+			Format: <bool>
+			Enable or disable asynchronous shutdown support. When
+			enabled, on system shutdown unrelated devices flagged
+			as async shutdown compatible may be shut down in
+			parallel and asynchronously. When disabled, device
+			shutdown is performed in a serially and synchronously.
+			Enabled by default.
+
 	coredump_filter=
 			[KNL] Change the default value for
 			/proc/<pid>/coredump_filter.
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff0..40dbf588a5d6 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -103,6 +103,7 @@ struct driver_private {
  *			   dev_err_probe() for later retrieval via debugfs
  * @device: pointer back to the struct device that this structure is
  *	    associated with.
+ * @complete: completion for device shutdown ordering
  * @dead: This device is currently either in the process of or has been
  *	  removed from the system. Any asynchronous events scheduled for this
  *	  device should exit without taking any action.
@@ -119,6 +120,7 @@ struct device_private {
 	const struct device_driver *async_driver;
 	char *deferred_probe_reason;
 	struct device *device;
+	struct completion complete;
 	u8 dead:1;
 };
 #define to_device_private_parent(obj)	\
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 3b3d983b1747..751fe2e13b3a 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>
@@ -37,6 +38,10 @@
 #include "physical_location.h"
 #include "power/power.h"
 
+static bool async_shutdown = true;
+module_param(async_shutdown, bool, 0644);
+MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
+
 /* Device links support. */
 static LIST_HEAD(deferred_sync);
 static unsigned int defer_sync_state_count = 1;
@@ -3606,6 +3611,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);
+	init_completion(&dev->p->complete);
 	return 0;
 }
 
@@ -3895,6 +3901,7 @@ bool kill_device(struct device *dev)
 	if (dev->p->dead)
 		return false;
 	dev->p->dead = true;
+	complete_all(&dev->p->complete);
 	return true;
 }
 EXPORT_SYMBOL_GPL(kill_device);
@@ -4865,6 +4872,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
 	return error;
 }
 
+static bool wants_async_shutdown(struct device *dev)
+{
+	return async_shutdown && dev_async_shutdown(dev);
+}
+
+static int wait_for_device_shutdown(struct device *dev, void *data)
+{
+	bool async = *(bool *)data;
+
+	if (async || wants_async_shutdown(dev))
+		wait_for_completion(&dev->p->complete);
+
+	return 0;
+}
+
+static void wait_for_shutdown_dependencies(struct device *dev, bool async)
+{
+	struct device_link *link;
+	int idx;
+
+	device_for_each_child(dev, &async, wait_for_device_shutdown);
+
+	idx = device_links_read_lock();
+
+	dev_for_each_link_to_consumer(link, dev)
+		if (!device_link_flag_is_sync_state_only(link->flags))
+			wait_for_device_shutdown(link->consumer, &async);
+
+	device_links_read_unlock(idx);
+}
+
 static void __shutdown_one_device(struct device *dev)
 {
 	if (dev->p->dead)
@@ -4888,6 +4926,8 @@ static void __shutdown_one_device(struct device *dev)
 			dev_info(dev, "shutdown\n");
 		dev->driver->shutdown(dev);
 	}
+
+	complete_all(&dev->p->complete);
 }
 
 static void shutdown_one_device(struct device *dev)
@@ -4917,6 +4957,80 @@ static void shutdown_one_device(struct device *dev)
 	put_device(dev);
 }
 
+static void async_shutdown_handler(void *data, async_cookie_t cookie)
+{
+	struct device *dev = data;
+
+	wait_for_shutdown_dependencies(dev, true);
+	shutdown_one_device(dev);
+}
+
+static bool shutdown_device_async(struct device *dev)
+{
+	if (async_schedule_dev_nocall(async_shutdown_handler, dev))
+		return true;
+
+	dev_clear_async_shutdown(dev);
+	return false;
+}
+
+
+static void start_async_shutdown_devices(void)
+{
+	struct device *dev, *next, *ndev, *needs_put = NULL;
+	bool clear_async = false;
+
+	if (!async_shutdown)
+		return;
+
+	spin_lock(&devices_kset->list_lock);
+restart:
+	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
+					 kobj.entry) {
+		if (wants_async_shutdown(dev)) {
+			if (clear_async) {
+				dev_clear_async_shutdown(dev);
+				continue;
+			}
+			get_device(dev);
+
+			if (!list_entry_is_head(next, &devices_kset->list,
+						kobj.entry))
+				ndev = get_device(next);
+			else
+				ndev = NULL;
+			spin_unlock(&devices_kset->list_lock);
+
+			if (shutdown_device_async(dev)) {
+				list_del_init(&dev->kobj.entry);
+			} else {
+				/*
+				 * async failed, clean up extra reference
+				 * and run shutdown from the sync shutdown loop
+				 */
+				clear_async = true;
+				put_device(dev);
+			}
+			if (needs_put)
+				put_device(needs_put);
+			needs_put = ndev;
+			spin_lock(&devices_kset->list_lock);
+			/*
+			 * If the next device has been marked dead while the
+			 * spinlock was released, it may no longer be on the
+			 * devices_kset list. Restart the list walk to be safe
+			 */
+			if (ndev && ndev->p->dead)
+				goto restart;
+		}
+	}
+
+	spin_unlock(&devices_kset->list_lock);
+
+	if (needs_put)
+		put_device(needs_put);
+}
+
 /**
  * device_shutdown - call ->shutdown() on each device to shutdown.
  */
@@ -4929,6 +5043,12 @@ void device_shutdown(void)
 
 	cpufreq_suspend();
 
+	/*
+	 * Start async device threads where possible to maximize potential
+	 * parallelism and minimize false dependency on unrelated sync devices
+	 */
+	start_async_shutdown_devices();
+
 	spin_lock(&devices_kset->list_lock);
 	/*
 	 * Walk the devices list backward, shutting down each in turn.
@@ -4947,11 +5067,16 @@ void device_shutdown(void)
 		list_del_init(&dev->kobj.entry);
 		spin_unlock(&devices_kset->list_lock);
 
-		shutdown_one_device(dev);
+		if (!wants_async_shutdown(dev) || !shutdown_device_async(dev)) {
+			wait_for_shutdown_dependencies(dev, false);
+			shutdown_one_device(dev);
+		}
 
 		spin_lock(&devices_kset->list_lock);
 	}
 	spin_unlock(&devices_kset->list_lock);
+
+	async_synchronize_full();
 }
 
 /*
diff --git a/include/linux/device.h b/include/linux/device.h
index 7b2baffdd2f5..f913d72218f8 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -610,6 +610,7 @@ enum struct_device_flags {
 	DEV_FLAG_OF_NODE_REUSED = 7,
 	DEV_FLAG_OFFLINE_DISABLED = 8,
 	DEV_FLAG_OFFLINE = 9,
+	DEV_FLAG_ASYNC_SHUTDOWN = 10,
 
 	DEV_FLAG_COUNT
 };
@@ -827,6 +828,7 @@ __create_dev_flag_accessors(dma_coherent, DEV_FLAG_DMA_COHERENT);
 __create_dev_flag_accessors(of_node_reused, DEV_FLAG_OF_NODE_REUSED);
 __create_dev_flag_accessors(offline_disabled, DEV_FLAG_OFFLINE_DISABLED);
 __create_dev_flag_accessors(offline, DEV_FLAG_OFFLINE);
+__create_dev_flag_accessors(async_shutdown, DEV_FLAG_ASYNC_SHUTDOWN);
 
 #undef __create_dev_flag_accessors
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 4/5] PCI: Enable async shutdown support
  2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
                   ` (2 preceding siblings ...)
  2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
@ 2026-06-16 15:22 ` David Jeffery
  2026-06-16 15:38   ` sashiko-bot
  2026-06-16 15:22 ` [PATCH 5/5] scsi: " David Jeffery
  4 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery,
	Pasha Tatashin, Bjorn Helgaas

Like its async suspend support, allow PCI device shutdown to be performed
asynchronously to reduce shutdown time.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Bjorn Helgaas <bhelgaas@google.com>
---
 drivers/pci/probe.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index b63cd0c310bc..7cf3dabc885e 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -1045,6 +1045,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 
 	bus->bridge = get_device(&bridge->dev);
 	device_enable_async_suspend(bus->bridge);
+	dev_set_async_shutdown(bus->bridge);
 	pci_set_bus_of_node(bus);
 	pci_set_bus_msi_domain(bus);
 	if (bridge->msi_domain && !dev_get_msi_domain(&bus->dev) &&
@@ -2753,6 +2754,7 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
 	pci_reassigndev_resource_alignment(dev);
 
 	pci_init_capabilities(dev);
+	dev_set_async_shutdown(&dev->dev);
 
 	/*
 	 * Add the device to our list of discovered devices
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* [PATCH 5/5] scsi: Enable async shutdown support
  2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
                   ` (3 preceding siblings ...)
  2026-06-16 15:22 ` [PATCH 4/5] PCI: Enable async shutdown support David Jeffery
@ 2026-06-16 15:22 ` David Jeffery
  2026-06-16 15:44   ` sashiko-bot
  4 siblings, 1 reply; 26+ messages in thread
From: David Jeffery @ 2026-06-16 15:22 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec, David Jeffery,
	Pasha Tatashin

Like scsi's async suspend support, allow scsi devices to be shut down
asynchronously to reduce system shutdown time.

Signed-off-by: David Jeffery <djeffery@redhat.com>
Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: John Garry <john.g.garry@oracle.com>
---
 drivers/scsi/hosts.c      | 2 ++
 drivers/scsi/scsi_sysfs.c | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index e047747d4ecf..bf691acc7a67 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -273,6 +273,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
 	pm_runtime_set_active(&shost->shost_gendev);
 	pm_runtime_enable(&shost->shost_gendev);
 	device_enable_async_suspend(&shost->shost_gendev);
+	dev_set_async_shutdown(&shost->shost_gendev);
 
 	error = device_add(&shost->shost_gendev);
 	if (error)
@@ -282,6 +283,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev,
 	get_device(shost->shost_gendev.parent);
 
 	device_enable_async_suspend(&shost->shost_dev);
+	dev_set_async_shutdown(&shost->shost_dev);
 
 	get_device(&shost->shost_gendev);
 	error = device_add(&shost->shost_dev);
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index dfc3559e7e04..8fd317aef37b 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1386,6 +1386,7 @@ static int scsi_target_add(struct scsi_target *starget)
 	pm_runtime_set_active(&starget->dev);
 	pm_runtime_enable(&starget->dev);
 	device_enable_async_suspend(&starget->dev);
+	dev_set_async_shutdown(&starget->dev);
 
 	return 0;
 }
@@ -1412,6 +1413,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 	transport_configure_device(&starget->dev);
 
 	device_enable_async_suspend(&sdev->sdev_gendev);
+	dev_set_async_shutdown(&sdev->sdev_gendev);
 	scsi_autopm_get_target(starget);
 	pm_runtime_set_active(&sdev->sdev_gendev);
 	if (!sdev->rpm_autosuspend)
@@ -1431,6 +1433,7 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
 	}
 
 	device_enable_async_suspend(&sdev->sdev_dev);
+	dev_set_async_shutdown(&sdev->sdev_dev);
 	error = device_add(&sdev->sdev_dev);
 	if (error) {
 		sdev_printk(KERN_INFO, sdev,
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 26+ messages in thread

* Re: [PATCH 1/5] driver core: separate function to shutdown one device
  2026-06-16 15:22 ` [PATCH 1/5] driver core: separate function to shutdown one device David Jeffery
@ 2026-06-16 15:34   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-06-16 15:34 UTC (permalink / raw)
  To: David Jeffery; +Cc: linux-scsi, linux-pci

> Make a separate function for the part of device_shutdown() that does the
> shutown for a single device.  This is in preparation for making device
> shutdown asynchronous.
> 
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> Signed-off-by: David Jeffery <djeffery@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616152219.6268-1-djeffery@redhat.com?part=1


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 2/5] driver core: do not always lock parent in shutdown
  2026-06-16 15:22 ` [PATCH 2/5] driver core: do not always lock parent in shutdown David Jeffery
@ 2026-06-16 15:38   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-06-16 15:38 UTC (permalink / raw)
  To: David Jeffery; +Cc: linux-scsi, linux-pci

> Don't lock a parent device unless it is needed in device_shutdown. This
> is in preparation for making device shutdown asynchronous, when it will
> be needed to allow children of a common parent to shut down
> simultaneously.
> 
> And only acquire a reference to the parent device if the parent is to be
> locked.
> 
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> Signed-off-by: David Jeffery <djeffery@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616152219.6268-1-djeffery@redhat.com?part=2


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 4/5] PCI: Enable async shutdown support
  2026-06-16 15:22 ` [PATCH 4/5] PCI: Enable async shutdown support David Jeffery
@ 2026-06-16 15:38   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-06-16 15:38 UTC (permalink / raw)
  To: David Jeffery; +Cc: linux-pci, linux-scsi

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Double `put_device()` on `bridge->dev` in the `pci_register_host_bridge()` error path leads to use-after-free.
- [High] Unlocked read-modify-write of C bitfields in `dev_pm_info` via sysfs can clobber PM core states like `in_dpm_list`.
--

commit 5968194913e4b5e2b73a45e8bbc2ab20340b2cfe
Author: David Jeffery <djeffery@redhat.com>

PCI: Enable async shutdown support

This commit allows PCI device shutdown to be performed asynchronously.
It introduces calls to dev_set_async_shutdown() during host bridge
registration and device addition to reduce overall shutdown time.

> diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
> index b3cefec25f518..4782b4d5306b6 100644
> --- a/drivers/pci/probe.c
> +++ b/drivers/pci/probe.c
> @@ -1046,6 +1046,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
>  
>  	bus->bridge = get_device(&bridge->dev);
>  	device_enable_async_suspend(bus->bridge);

[Severity: High]
This is a pre-existing issue, but does writing to the power/async sysfs
file trigger an unlocked read-modify-write that can clobber PM core states?

struct dev_pm_info declares adjacent bitfields async_suspend:1 and
in_dpm_list:1 that share a memory word.

device_pm_add() performs a locked read-modify-write under dpm_list_mtx to set
dev->power.in_dpm_list = true.

Because dpm_sysfs_add(dev) executes before device_pm_add(dev) during
initialization, userspace can immediately trigger a concurrent lockless
read-modify-write by writing to the power/async sysfs file. This calls
async_store() -> device_enable_async_suspend() and sets
dev->power.async_suspend = true without holding any locks.

If this lockless RMW races with device_pm_add(), can it overwrite in_dpm_list
back to false after it was set, breaking PM dependency chains and aborting
device linking?

[Severity: Critical]
This is a pre-existing issue, but does the error path in
pci_register_host_bridge() trigger a double put_device() on bridge->dev?

If device_register(&bus->dev) fails, execution jumps to the unregister label
which explicitly drops the bridge->dev reference:

unregister:
	put_device(&bridge->dev);
	device_del(&bridge->dev);
free:
	...
	if (bus_registered)
		put_device(&bus->dev);

However, the release function for the bus, release_pcibus_dev(), also
unconditionally drops the bridge reference:

static void release_pcibus_dev(struct device *dev)
{
	struct pci_bus *pci_bus = to_pci_bus(dev);

	put_device(pci_bus->bridge);
	...
}

When the execution falls through to the free label and calls
put_device(&bus->dev), will release_pcibus_dev() drop the bridge->dev
reference a second time, potentially causing a use-after-free when the caller
handles the registration error?

> +	dev_set_async_shutdown(bus->bridge);
>  	pci_set_bus_of_node(bus);
>  	pci_set_bus_msi_domain(bus);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616152219.6268-1-djeffery@redhat.com?part=4

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
@ 2026-06-16 15:39   ` sashiko-bot
  2026-06-16 16:10   ` David Jeffery
  2026-06-16 17:26   ` Randy Dunlap
  2 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-06-16 15:39 UTC (permalink / raw)
  To: David Jeffery; +Cc: linux-scsi, linux-pci

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unprotected lockless modification of `devices_kset->list` leading to list corruption.
--

commit 3c22e8d639c6f39d1755272000b5af82fc85ebc8
Author: David Jeffery <djeffery@redhat.com>

driver core: async device shutdown infrastructure

This commit introduces an asynchronous device shutdown infrastructure. Devices
marked for async shutdown can be processed in parallel, waiting only for
their dependencies. This helps minimize false dependencies on unrelated
synchronous devices.

> diff --git a/drivers/base/core.c b/drivers/base/core.c
> index 3b3d983b17470..751fe2e13b3aa 100644
> --- a/drivers/base/core.c
> +++ b/drivers/base/core.c

[ ... ]

> +static void start_async_shutdown_devices(void)
> +{
> +	struct device *dev, *next, *ndev, *needs_put = NULL;
> +	bool clear_async = false;
> +
> +	if (!async_shutdown)
> +		return;
> +
> +	spin_lock(&devices_kset->list_lock);
> +restart:
> +	list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
> +					 kobj.entry) {
> +		if (wants_async_shutdown(dev)) {
> +			if (clear_async) {
> +				dev_clear_async_shutdown(dev);
> +				continue;
> +			}
> +			get_device(dev);
> +
> +			if (!list_entry_is_head(next, &devices_kset->list,
> +						kobj.entry))
> +				ndev = get_device(next);
> +			else
> +				ndev = NULL;
> +			spin_unlock(&devices_kset->list_lock);
> +
> +			if (shutdown_device_async(dev)) {
> +				list_del_init(&dev->kobj.entry);

[Severity: High]
Could this lead to list corruption?

In start_async_shutdown_devices(), the devices_kset->list_lock is released
before calling shutdown_device_async(). If the async schedule succeeds,
list_del_init() is called to remove the device from devices_kset->list
without reacquiring the lock.

Since this is a global doubly-linked list and concurrent device hotplug or
unplug events can still occur during system shutdown, doesn't modifying
this list without the lock risk corrupting the list pointers and causing
memory corruption or a panic?

> +			} else {
> +				/*
> +				 * async failed, clean up extra reference
> +				 * and run shutdown from the sync shutdown loop
> +				 */
> +				clear_async = true;
> +				put_device(dev);
> +			}

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616152219.6268-1-djeffery@redhat.com?part=3

^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 5/5] scsi: Enable async shutdown support
  2026-06-16 15:22 ` [PATCH 5/5] scsi: " David Jeffery
@ 2026-06-16 15:44   ` sashiko-bot
  0 siblings, 0 replies; 26+ messages in thread
From: sashiko-bot @ 2026-06-16 15:44 UTC (permalink / raw)
  To: David Jeffery; +Cc: linux-scsi, linux-pci

> Like scsi's async suspend support, allow scsi devices to be shut down
> asynchronously to reduce system shutdown time.
> 
> Signed-off-by: David Jeffery <djeffery@redhat.com>
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616152219.6268-1-djeffery@redhat.com?part=5


^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
  2026-06-16 15:39   ` sashiko-bot
@ 2026-06-16 16:10   ` David Jeffery
  2026-06-16 17:26   ` Randy Dunlap
  2 siblings, 0 replies; 26+ messages in thread
From: David Jeffery @ 2026-06-16 16:10 UTC (permalink / raw)
  To: driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec

On Tue, Jun 16, 2026 at 11:23 AM David Jeffery <djeffery@redhat.com> wrote:
>
> Patterned after async suspend, allow devices to mark themselves as wanting
> to perform async shutdown. Devices using async shutdown wait only for their
> dependencies to shutdown before executing their shutdown routine.
>
> Sync shutdown devices are shut down one at a time and will only wait for an
> async shutdown device if the async device is a dependency.
>
> Enabled by default, async shutdown can be explicitly enabled or disabled
> by using the kernel parameter "core.async_shutdown=<bool>"
>
> Signed-off-by: David Jeffery <djeffery@redhat.com>
> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
> Tested-by: Laurence Oberman <loberman@redhat.com>
> ---
>  .../admin-guide/kernel-parameters.txt         |  10 ++
>  drivers/base/base.h                           |   2 +
>  drivers/base/core.c                           | 127 +++++++++++++++++-
>  include/linux/device.h                        |   2 +
>  4 files changed, 140 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index b5a51a36a048..dd912f47ace4 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -1019,6 +1019,16 @@ Kernel parameters
>                         seconds. A value of 0 disables the blank timer.
>                         Defaults to 0.
>
> +       core.async_shutdown=
> +                       [KNL]
> +                       Format: <bool>
> +                       Enable or disable asynchronous shutdown support. When
> +                       enabled, on system shutdown unrelated devices flagged
> +                       as async shutdown compatible may be shut down in
> +                       parallel and asynchronously. When disabled, device
> +                       shutdown is performed in a serially and synchronously.
> +                       Enabled by default.
> +
>         coredump_filter=
>                         [KNL] Change the default value for
>                         /proc/<pid>/coredump_filter.
> diff --git a/drivers/base/base.h b/drivers/base/base.h
> index a5b7abc10ff0..40dbf588a5d6 100644
> --- a/drivers/base/base.h
> +++ b/drivers/base/base.h
> @@ -103,6 +103,7 @@ struct driver_private {
>   *                        dev_err_probe() for later retrieval via debugfs
>   * @device: pointer back to the struct device that this structure is
>   *         associated with.
> + * @complete: completion for device shutdown ordering
>   * @dead: This device is currently either in the process of or has been
>   *       removed from the system. Any asynchronous events scheduled for this
>   *       device should exit without taking any action.
> @@ -119,6 +120,7 @@ struct device_private {
>         const struct device_driver *async_driver;
>         char *deferred_probe_reason;
>         struct device *device;
> +       struct completion complete;
>         u8 dead:1;
>  };
>  #define to_device_private_parent(obj)  \
> diff --git a/drivers/base/core.c b/drivers/base/core.c
> index 3b3d983b1747..751fe2e13b3a 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>
> @@ -37,6 +38,10 @@
>  #include "physical_location.h"
>  #include "power/power.h"
>
> +static bool async_shutdown = true;
> +module_param(async_shutdown, bool, 0644);
> +MODULE_PARM_DESC(async_shutdown, "Enable asynchronous device shutdown support");
> +
>  /* Device links support. */
>  static LIST_HEAD(deferred_sync);
>  static unsigned int defer_sync_state_count = 1;
> @@ -3606,6 +3611,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);
> +       init_completion(&dev->p->complete);
>         return 0;
>  }
>
> @@ -3895,6 +3901,7 @@ bool kill_device(struct device *dev)
>         if (dev->p->dead)
>                 return false;
>         dev->p->dead = true;
> +       complete_all(&dev->p->complete);
>         return true;
>  }
>  EXPORT_SYMBOL_GPL(kill_device);
> @@ -4865,6 +4872,37 @@ int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
>         return error;
>  }
>
> +static bool wants_async_shutdown(struct device *dev)
> +{
> +       return async_shutdown && dev_async_shutdown(dev);
> +}
> +
> +static int wait_for_device_shutdown(struct device *dev, void *data)
> +{
> +       bool async = *(bool *)data;
> +
> +       if (async || wants_async_shutdown(dev))
> +               wait_for_completion(&dev->p->complete);
> +
> +       return 0;
> +}
> +
> +static void wait_for_shutdown_dependencies(struct device *dev, bool async)
> +{
> +       struct device_link *link;
> +       int idx;
> +
> +       device_for_each_child(dev, &async, wait_for_device_shutdown);
> +
> +       idx = device_links_read_lock();
> +
> +       dev_for_each_link_to_consumer(link, dev)
> +               if (!device_link_flag_is_sync_state_only(link->flags))
> +                       wait_for_device_shutdown(link->consumer, &async);
> +
> +       device_links_read_unlock(idx);
> +}
> +
>  static void __shutdown_one_device(struct device *dev)
>  {
>         if (dev->p->dead)
> @@ -4888,6 +4926,8 @@ static void __shutdown_one_device(struct device *dev)
>                         dev_info(dev, "shutdown\n");
>                 dev->driver->shutdown(dev);
>         }
> +
> +       complete_all(&dev->p->complete);
>  }
>
>  static void shutdown_one_device(struct device *dev)
> @@ -4917,6 +4957,80 @@ static void shutdown_one_device(struct device *dev)
>         put_device(dev);
>  }
>
> +static void async_shutdown_handler(void *data, async_cookie_t cookie)
> +{
> +       struct device *dev = data;
> +
> +       wait_for_shutdown_dependencies(dev, true);
> +       shutdown_one_device(dev);
> +}
> +
> +static bool shutdown_device_async(struct device *dev)
> +{
> +       if (async_schedule_dev_nocall(async_shutdown_handler, dev))
> +               return true;
> +
> +       dev_clear_async_shutdown(dev);
> +       return false;
> +}
> +
> +
> +static void start_async_shutdown_devices(void)
> +{
> +       struct device *dev, *next, *ndev, *needs_put = NULL;
> +       bool clear_async = false;
> +
> +       if (!async_shutdown)
> +               return;
> +
> +       spin_lock(&devices_kset->list_lock);
> +restart:
> +       list_for_each_entry_safe_reverse(dev, next, &devices_kset->list,
> +                                        kobj.entry) {
> +               if (wants_async_shutdown(dev)) {
> +                       if (clear_async) {
> +                               dev_clear_async_shutdown(dev);
> +                               continue;
> +                       }
> +                       get_device(dev);
> +
> +                       if (!list_entry_is_head(next, &devices_kset->list,
> +                                               kobj.entry))
> +                               ndev = get_device(next);
> +                       else
> +                               ndev = NULL;
> +                       spin_unlock(&devices_kset->list_lock);
> +
> +                       if (shutdown_device_async(dev)) {
> +                               list_del_init(&dev->kobj.entry);


Sashiko detected a locking error here. The lock rework made
list_del_init occur while not holding the spinlock, which is a
potential list corruption issue. This will be corrected in the next
iteration.

David Jeffery



^ permalink raw reply	[flat|nested] 26+ messages in thread

* Re: [PATCH 3/5] driver core: async device shutdown infrastructure
  2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
  2026-06-16 15:39   ` sashiko-bot
  2026-06-16 16:10   ` David Jeffery
@ 2026-06-16 17:26   ` Randy Dunlap
  2 siblings, 0 replies; 26+ messages in thread
From: Randy Dunlap @ 2026-06-16 17:26 UTC (permalink / raw)
  To: David Jeffery, driver-core, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich
  Cc: linux-kernel, linux-pci, linux-scsi, Tarun Sahu, Pasha Tatashin,
	Michał Cłapiński, Jordan Richards, Ewan Milne,
	John Meneghini, Lombardi, Maurizio, Stuart Hayes,
	Laurence Oberman, Bart Van Assche, Bjorn Helgaas,
	Martin K . Petersen, John Garry, kexec



On 6/16/26 8:22 AM, David Jeffery wrote:
> +	core.async_shutdown=
> +			[KNL]
> +			Format: <bool>
> +			Enable or disable asynchronous shutdown support. When
> +			enabled, on system shutdown unrelated devices flagged
> +			as async shutdown compatible may be shut down in
> +			parallel and asynchronously. When disabled, device
> +			shutdown is performed in a serially and synchronously.

			            performed serially and synchronously.

> +			Enabled by default.

-- 
~Randy



^ permalink raw reply	[flat|nested] 26+ messages in thread

end of thread, other threads:[~2026-06-16 17:26 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-16 15:22 [PATCH v17 0/5] shut down devices asynchronously David Jeffery
2026-06-16 15:22 ` [PATCH 1/5] driver core: separate function to shutdown one device David Jeffery
2026-06-16 15:34   ` sashiko-bot
2026-06-16 15:22 ` [PATCH 2/5] driver core: do not always lock parent in shutdown David Jeffery
2026-06-16 15:38   ` sashiko-bot
2026-06-16 15:22 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-06-16 15:39   ` sashiko-bot
2026-06-16 16:10   ` David Jeffery
2026-06-16 17:26   ` Randy Dunlap
2026-06-16 15:22 ` [PATCH 4/5] PCI: Enable async shutdown support David Jeffery
2026-06-16 15:38   ` sashiko-bot
2026-06-16 15:22 ` [PATCH 5/5] scsi: " David Jeffery
2026-06-16 15:44   ` sashiko-bot
  -- strict thread matches above, loose matches on Subject: below --
2026-05-18 19:31 [PATCH v16 0/5] shut down devices asynchronously David Jeffery
2026-05-18 19:32 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-04-29 17:50 [PATCH v15 0/5] shut down devices asynchronously David Jeffery
2026-04-29 17:50 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-04-20 15:26 [PATCH v14 0/5] shut down devices asynchronously David Jeffery
2026-04-20 15:26 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-04-21  7:49   ` John Garry
2026-04-21 17:31     ` David Jeffery
2026-04-07 15:35 [PATCH v13 0/5] shut down devices asynchronously David Jeffery
2026-04-07 15:35 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-03-19 14:11 [PATCH v12 0/5] shut down devices asynchronously David Jeffery
2026-03-19 14:11 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-03-23  9:43   ` Maurizio Lombardi
2026-03-23 14:07     ` David Jeffery
2026-03-11 17:12 [PATCH 1/5] driver core: do not always lock parent in shutdown David Jeffery
2026-03-11 17:12 ` [PATCH 3/5] driver core: async device shutdown infrastructure David Jeffery
2026-03-11 19:40   ` Randy Dunlap
2026-03-11 23:05   ` Bjorn Helgaas
2026-03-12 14:01     ` David Jeffery

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.