Skip to contentSkip to conversation

mutex deadlock of dpm_resume() in low memory situation

BUG In review Youngmin Nam 6 participants 25 messages Dec 27, 2023 – Jan 3, 2024 1 message not in the archive

Subsystems (4)
lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): CGME20231227084252epcas2p3b063f7852f81f82cd0a31afd7f404db4@epcas2p3.samsung.com

#1 lore raw

Hi all.

I'm reporting a issue which looks like a upstream kernel bug. We are using 6.1 but I think all of version can cause this issue.

A mutex deadlock issue occured on low mem situation when the device runs dpm_resume(). Here's the problematic situation.

#1. Currently, the device is on low mem situation as below.
[4: binder:569_5:27109] SLUB: Unable to allocate memory on node -1, gfp=0xb20(GFP_ATOMIC|__GFP_ZERO)
[4: binder:569_5:27109] cache: kmalloc-128, object size: 128, buffer size: 128, default order: 0, min order: 0 [4: binder:569_5:27109] node 0: slabs: 1865, objs: 59680, free: 0
[4: binder:569_5:27109] binder:569_5: page allocation failure: order:0, mode:0xa20(GFP_ATOMIC), nodemask=(null),cpuset=/,mems_allowed=0 [4: binder:569_5:27109] CPU: 4 PID: 27109 Comm: binder:569_5 Tainted: G S C E 6.1.43-android14-11-abS921BXXU1AWLB #1 [4: binder:569_5:27109] Hardware name: Samsung E1S EUR OPENX board based on S5E9945 (DT)
[4: binder:569_5:27109] Call trace:
[4: binder:569_5:27109] dump_backtrace+0xf4/0x118
[4: binder:569_5:27109] show_stack+0x18/0x24
[4: binder:569_5:27109] dump_stack_lvl+0x60/0x7c
[4: binder:569_5:27109] dump_stack+0x18/0x38
[4: binder:569_5:27109] warn_alloc+0xf4/0x190
[4: binder:569_5:27109] __alloc_pages_slowpath+0x10ec/0x12ac
[4: binder:569_5:27109] __alloc_pages+0x27c/0x2fc
[4: binder:569_5:27109] new_slab+0x17c/0x4e0
[4: binder:569_5:27109] ___slab_alloc+0x4e4/0x8a8
[4: binder:569_5:27109] __slab_alloc+0x34/0x6c
[4: binder:569_5:27109] __kmem_cache_alloc_node+0x1f4/0x260
[4: binder:569_5:27109] kmalloc_trace+0x4c/0x144
[4: binder:569_5:27109] async_schedule_node_domain+0x40/0x1ec
[4: binder:569_5:27109] async_schedule_node+0x18/0x28
[4: binder:569_5:27109] dpm_suspend+0xfc/0x48c

#2. The process A runs dpm_resume() and acquired "dpm_list_mtx" lock as below. 1000 void dpm_resume(pm_message_t state)
1001 {
1002 struct device *dev;
1003 ktime_t starttime = ktime_get();
1004
1005 trace_suspend_resume(TPS("dpm_resume"), state.event, true);
1006 might_sleep();
1007

1008         mutex_lock(&dpm_list_mtx);  <-------- process A acquired the lock
1009         pm_transition = state;
1010         async_error = 0;

1011
1012 list_for_each_entry(dev, &dpm_suspended_list, power.entry)
1013 dpm_async_fn(dev, async_resume);

#3. The process A continues to run below functions as below. dpm_async_fn()

      --> async_schedule_dev()
          --> async_schedule_node()
              --> async_schedule_node_domain()

#4. The kzalloc() will be failed because of lowmen situation. 165 async_cookie_t async_schedule_node_domain(async_func_t func, void *data,
166 int node, struct async_domain *domain)
167 {

168         struct async_entry *entry;
169         unsigned long flags;
170         async_cookie_t newcookie;

171
172 /* allow irq-off callers */
173 entry = kzalloc(sizeof(struct async_entry), GFP_ATOMIC); <--- Will be failied
174
175 /*

176          * If we're out of memory or if there's too much work
177          * pending already, we execute synchronously.
178          */
179         if (!entry || atomic_read(&entry_count) > MAX_WORK) {
180                 kfree(entry);
181                 spin_lock_irqsave(&async_lock, flags);
182                 newcookie = next_cookie++;
183                 spin_unlock_irqrestore(&async_lock, flags);
184
185                 /* low on memory.. run synchronously */
186                 func(data, newcookie); <--- the process A will run func() that is async_resume()
187                 return newcookie;

5. The process A continues to run below functions as below. async_resume()

    --> device_resume()
        --> dpm_wait_for_superior()

6. The process A is trying to get the lock which was acquired by A. 278 static bool dpm_wait_for_superior(struct device *dev, bool async) 279 {
280 struct device *parent;
281
282 /*

283          * If the device is resumed asynchronously and the parent's callback
284          * deletes both the device and the parent itself, the parent object may
285          * be freed while this function is running, so avoid that by reference
286          * counting the parent once more unless the device has been deleted
287          * already (in which case return right away).
288          */

289 mutex_lock(&dpm_list_mtx);

So we think this situation can make mutex dead lock issue in suspend/resume sequence.

Here's the process A's callstack in kernel log. (binder:569:5)

I[4:      swapper/4:    0]      pid            uTime            sTime     last_arrival      last_queued   stat   cpu  task_struct           comm [wait channel]
I[4:      swapper/4:    0]    27109                0         1758074533    2396019774802             0    D(  2)  5   ffffff8044bc92c0      binder:569_5 [dpm_wait_for_superior]
I[4:      swapper/4:    0] Mutex: dpm_list_mtx+0x0/0x30: owner[0xffffff8044bc92c0 binder:569_5 :27109]
I[4:      swapper/4:    0] Call trace:
I[4:      swapper/4:    0]  __switch_to+0x174/0x338
I[4:      swapper/4:    0]  __schedule+0x5ec/0x9cc
I[4:      swapper/4:    0]  schedule+0x7c/0xe8
I[4:      swapper/4:    0]  schedule_preempt_disabled+0x24/0x40
I[4:      swapper/4:    0]  __mutex_lock+0x408/0xdac
I[4:      swapper/4:    0]  __mutex_lock_slowpath+0x14/0x24
I[4:      swapper/4:    0]  mutex_lock+0x40/0xec  <-------- trying to acquire dpm_list_mtx again
I[4:      swapper/4:    0]  dpm_wait_for_superior+0x30/0x148
I[4:      swapper/4:    0]  device_resume+0x38/0x1e4
I[4:      swapper/4:    0]  async_resume+0x24/0xf4
I[4:      swapper/4:    0]  async_schedule_node_domain+0xb0/0x1ec
I[4:      swapper/4:    0]  async_schedule_node+0x18/0x28
I[4:      swapper/4:    0]  dpm_resume+0xbc/0x578  <------- acquired dpm_list_mtx
I[4:      swapper/4:    0]  dpm_resume_end+0x1c/0x38
I[4:      swapper/4:    0]  suspend_devices_and_enter+0x83c/0xb2c
I[4:      swapper/4:    0]  pm_suspend+0x34c/0x618
I[4:      swapper/4:    0]  state_store+0x104/0x144

Could you look into this issue ?

maintainer
#2 lore raw
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

thanks,

greg k-h

#3 lore raw
Quoted 7 lines · Greg KH
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

This is a real problem, unfortunately, and the fix would require some infra changes AFAICS.

To address it, we would need a variant of async_schedule_node_domain() that would bail out on low memory instead of attempting to run the stuff synchronously which is harmful (not just for the deadlock reason) in the suspend-resume paths.

I'll try to cut a test patch shortly.

#4 lore raw
In reply to Greg KH
Quoted 6 lines · CET Greg KH
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

Something like the appended patch should be sufficient to address this AFAICS.

I haven't tested it yet (will do so shortly), so all of the usual disclaimers apply.

I think that it can be split into 2 patches, but for easier testing here it goes in one piece.

Fixes f2a424f6c613 ("PM / core: Introduce dpm_async_fn() helper")
---
 drivers/base/power/main.c |   12 ++++--
 include/linux/async.h     |    2 +
 kernel/async.c            |   85 ++++++++++++++++++++++++++++++++++------------
 3 files changed, 73 insertions(+), 26 deletions(-)
kernel/async.c +63 -22
Index: linux-pm/kernel/async.c
===================================================================
@@ -145,6 +145,39 @@ static void async_run_entry_fn(struct wo
 	wake_up(&async_done);
 }
 
+static async_cookie_t __async_schedule_node_domain(async_func_t func,
+						   void *data, int node,
+						   struct async_domain *domain,
+						   struct async_entry *entry)
+{
+	async_cookie_t newcookie;
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&entry->domain_list);
+	INIT_LIST_HEAD(&entry->global_list);
+	INIT_WORK(&entry->work, async_run_entry_fn);
+	entry->func = func;
+	entry->data = data;
+	entry->domain = domain;
+
+	spin_lock_irqsave(&async_lock, flags);
+
+	/* allocate cookie and queue */
+	newcookie = entry->cookie = next_cookie++;
+
+	list_add_tail(&entry->domain_list, &domain->pending);
+	if (domain->registered)
+		list_add_tail(&entry->global_list, &async_global_pending);
+
+	atomic_inc(&entry_count);
+	spin_unlock_irqrestore(&async_lock, flags);
+
+	/* schedule for execution */
+	queue_work_node(node, system_unbound_wq, &entry->work);
+
+	return newcookie;
+}
+
 /**
  * async_schedule_node_domain - NUMA specific version of async_schedule_domain
  * @func: function to execute asynchronously
@@ -186,29 +219,8 @@ async_cookie_t async_schedule_node_domai
 		func(data, newcookie);
 		return newcookie;
 	}
-	INIT_LIST_HEAD(&entry->domain_list);
-	INIT_LIST_HEAD(&entry->global_list);
-	INIT_WORK(&entry->work, async_run_entry_fn);
-	entry->func = func;
-	entry->data = data;
-	entry->domain = domain;
-
-	spin_lock_irqsave(&async_lock, flags);
-
-	/* allocate cookie and queue */
-	newcookie = entry->cookie = next_cookie++;
-
-	list_add_tail(&entry->domain_list, &domain->pending);
-	if (domain->registered)
-		list_add_tail(&entry->global_list, &async_global_pending);
-
-	atomic_inc(&entry_count);
-	spin_unlock_irqrestore(&async_lock, flags);
-
-	/* schedule for execution */
-	queue_work_node(node, system_unbound_wq, &entry->work);
 
-	return newcookie;
+	return __async_schedule_node_domain(func, data, node, domain, entry);
 }
 EXPORT_SYMBOL_GPL(async_schedule_node_domain);
 
@@ -232,6 +244,35 @@ async_cookie_t async_schedule_node(async
 EXPORT_SYMBOL_GPL(async_schedule_node);
 
 /**
+ * async_schedule_dev_nocall - A simplified variant of async_schedule_dev()
+ * @func: function to execute asynchronously
+ * @dev: device argument to be passed to function
+ *
+ * @dev is used as both the argument for the function and to provide NUMA
+ * context for where to run the function.
+ *
+ * If the asynchronous execution of @func is scheduled successfully, return
+ * true. Otherwise, do nothing and return false, unlike async_schedule_dev()
+ * that will run the function synchronously then.
+ */
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+	struct async_entry *entry;
+
+	entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);
+
+	/* Give up if there is no memory or too much work. */
+	if (!entry || atomic_read(&entry_count) > MAX_WORK) {
+		kfree(entry);
+		return false;
+	}
+
+	__async_schedule_node_domain(func, dev, dev_to_node(dev),
+				     &async_dfl_domain, entry);
+	return true;
+}
+
+/**
  * async_synchronize_full - synchronize all asynchronous function calls
  *
  * This function waits until all asynchronous function calls have been done.
include/linux/async.h +2 -0
Index: linux-pm/include/linux/async.h
===================================================================
@@ -90,6 +90,8 @@ async_schedule_dev(async_func_t func, st
 	return async_schedule_node(func, dev, dev_to_node(dev));
 }
 
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev);
+
 /**
  * async_schedule_dev_domain - A device specific version of async_schedule_domain
  * @func: function to execute asynchronously
drivers/base/power/main.c +8 -4
Index: linux-pm/drivers/base/power/main.c
===================================================================
@@ -668,11 +668,15 @@ static bool dpm_async_fn(struct device *
 {
 	reinit_completion(&dev->power.completion);
 
-	if (is_async(dev)) {
-		get_device(dev);
-		async_schedule_dev(func, dev);
+	if (!is_async(dev))
+		return false;
+
+	get_device(dev);
+
+	if (async_schedule_dev_nocall(func, dev))
 		return true;
-	}
+
+	put_device(dev);
 
 	return false;
 }
#5 lore raw
Quoted 8 lines · CET Rafael J. Wysocki
Quoted 6 lines · CET Greg KH
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

Quoted 7 lines

Something like the appended patch should be sufficient to address this AFAICS.

I haven't tested it yet (will do so shortly), so all of the usual disclaimers apply.

I think that it can be split into 2 patches, but for easier testing here it goes in one piece.

Well, please scratch this, it will not handle "async" devices properly.

#6 lore raw
Quoted 12 lines · Rafael J. Wysocki
Quoted 10 lines · CET Rafael J. Wysocki
Quoted 6 lines · CET Greg KH
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

Something like the appended patch should be sufficient to address this AFAICS.

Quoted 7 lines
Quoted 5 lines

I haven't tested it yet (will do so shortly), so all of the usual disclaimers apply.

I think that it can be split into 2 patches, but for easier testing here it goes in one piece.

Well, please scratch this, it will not handle "async" devices properly.

This

https://lore.kernel.org/linux-pm/6019796.lOV4Wx5bFT@kreacher/

should address the issue properly (it has been lightly tested).

Please give it a go and let me know if it works for you (on top of 6.7-rc7).

#7 lore raw
Quoted 21 lines · Rafael J. Wysocki
Quoted 12 lines · Rafael J. Wysocki
Quoted 10 lines · CET Rafael J. Wysocki
Quoted 6 lines · CET Greg KH
Quoted 1 line · Youngmin Nam

Could you look into this issue ?

Can you submit a patch that resolves the issue for you, as you have a way to actually test this out? That would be the quickest way to get it resolved, and to help confirm that this is even an issue at all.

Something like the appended patch should be sufficient to address this AFAICS.

Quoted 7 lines
Quoted 5 lines

I haven't tested it yet (will do so shortly), so all of the usual disclaimers apply.

I think that it can be split into 2 patches, but for easier testing here it goes in one piece.

Well, please scratch this, it will not handle "async" devices properly.

Quoted 8 lines

This

https://lore.kernel.org/linux-pm/6019796.lOV4Wx5bFT@kreacher/

should address the issue properly (it has been lightly tested).

Please give it a go and let me know if it works for you (on top of 6.7-rc7).

Thanks for your help.
Actually, this is very rare case on our side and it is the first time for us to see this issue. Anyway, let me test and let you know.

#8 lore raw

cover PM: sleep: Fix possible device suspend-resume deadlocks

In reply to Rafael J. Wysocki

Hi Everyone,

As reported here

https://lore.kernel.org/linux-pm/ZYvjiqX6EsL15moe@perf/

the device suspend-resume code running during system-wide PM transitions deadlock on low memory, because it attempts to acquire a mutex that's already held by it in those cases.

This series addresses the issue by changing the resume code behavior to directly run the device PM functions synchronously if they cannot be scheduled for asynchronous executions (patch [3/3]).

For this purpose, the async code is rearranged (patch [1/3]) and a new variant of async_schedule_dev() is introduced (patch [2/3]).

Thanks!

#9 lore raw

PATCH 1/3 async: Split async_schedule_node_domain()

From: Rafael J. Wysocki <[email protected]>

In preparation for subsequent changes, split async_schedule_node_domain() in two pieces so as to allow the bottom part of it to be called from a somewhat different code path.

No functional impact.

---
 kernel/async.c |   56 ++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 34 insertions(+), 22 deletions(-)
kernel/async.c +34 -22
Index: linux-pm/kernel/async.c
===================================================================
@@ -145,6 +145,39 @@ static void async_run_entry_fn(struct wo
 	wake_up(&async_done);
 }
 
+static async_cookie_t __async_schedule_node_domain(async_func_t func,
+						   void *data, int node,
+						   struct async_domain *domain,
+						   struct async_entry *entry)
+{
+	async_cookie_t newcookie;
+	unsigned long flags;
+
+	INIT_LIST_HEAD(&entry->domain_list);
+	INIT_LIST_HEAD(&entry->global_list);
+	INIT_WORK(&entry->work, async_run_entry_fn);
+	entry->func = func;
+	entry->data = data;
+	entry->domain = domain;
+
+	spin_lock_irqsave(&async_lock, flags);
+
+	/* allocate cookie and queue */
+	newcookie = entry->cookie = next_cookie++;
+
+	list_add_tail(&entry->domain_list, &domain->pending);
+	if (domain->registered)
+		list_add_tail(&entry->global_list, &async_global_pending);
+
+	atomic_inc(&entry_count);
+	spin_unlock_irqrestore(&async_lock, flags);
+
+	/* schedule for execution */
+	queue_work_node(node, system_unbound_wq, &entry->work);
+
+	return newcookie;
+}
+
 /**
  * async_schedule_node_domain - NUMA specific version of async_schedule_domain
  * @func: function to execute asynchronously
@@ -186,29 +219,8 @@ async_cookie_t async_schedule_node_domai
 		func(data, newcookie);
 		return newcookie;
 	}
-	INIT_LIST_HEAD(&entry->domain_list);
-	INIT_LIST_HEAD(&entry->global_list);
-	INIT_WORK(&entry->work, async_run_entry_fn);
-	entry->func = func;
-	entry->data = data;
-	entry->domain = domain;
-
-	spin_lock_irqsave(&async_lock, flags);
 
-	/* allocate cookie and queue */
-	newcookie = entry->cookie = next_cookie++;
-
-	list_add_tail(&entry->domain_list, &domain->pending);
-	if (domain->registered)
-		list_add_tail(&entry->global_list, &async_global_pending);
-
-	atomic_inc(&entry_count);
-	spin_unlock_irqrestore(&async_lock, flags);
-
-	/* schedule for execution */
-	queue_work_node(node, system_unbound_wq, &entry->work);
-
-	return newcookie;
+	return __async_schedule_node_domain(func, data, node, domain, entry);
 }
 EXPORT_SYMBOL_GPL(async_schedule_node_domain);
 
#10 lore raw

PATCH 2/3 async: Introduce async_schedule_dev_nocall()

In reply to Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

In preparation for subsequent changes, introduce a specialized variant of async_schedule_dev() that will not invoke the argument function synchronously when it cannot be scheduled for asynchronous execution.

The new function, async_schedule_dev_nocall(), will be used for fixing possible deadlocks in the system-wide power management core code.

---
 drivers/base/power/main.c |   12 ++++++++----
 include/linux/async.h     |    2 ++
 kernel/async.c            |   29 +++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 4 deletions(-)
kernel/async.c +29 -0
Index: linux-pm/kernel/async.c
===================================================================
@@ -244,6 +244,35 @@ async_cookie_t async_schedule_node(async
 EXPORT_SYMBOL_GPL(async_schedule_node);
 
 /**
+ * async_schedule_dev_nocall - A simplified variant of async_schedule_dev()
+ * @func: function to execute asynchronously
+ * @dev: device argument to be passed to function
+ *
+ * @dev is used as both the argument for the function and to provide NUMA
+ * context for where to run the function.
+ *
+ * If the asynchronous execution of @func is scheduled successfully, return
+ * true. Otherwise, do nothing and return false, unlike async_schedule_dev()
+ * that will run the function synchronously then.
+ */
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+	struct async_entry *entry;
+
+	entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);
+
+	/* Give up if there is no memory or too much work. */
+	if (!entry || atomic_read(&entry_count) > MAX_WORK) {
+		kfree(entry);
+		return false;
+	}
+
+	__async_schedule_node_domain(func, dev, dev_to_node(dev),
+				     &async_dfl_domain, entry);
+	return true;
+}
+
+/**
  * async_synchronize_full - synchronize all asynchronous function calls
  *
  * This function waits until all asynchronous function calls have been done.
include/linux/async.h +2 -0
Index: linux-pm/include/linux/async.h
===================================================================
@@ -90,6 +90,8 @@ async_schedule_dev(async_func_t func, st
 	return async_schedule_node(func, dev, dev_to_node(dev));
 }
 
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev);
+
 /**
  * async_schedule_dev_domain - A device specific version of async_schedule_domain
  * @func: function to execute asynchronously
#11 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 30 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

In preparation for subsequent changes, introduce a specialized variant of async_schedule_dev() that will not invoke the argument function synchronously when it cannot be scheduled for asynchronous execution.

The new function, async_schedule_dev_nocall(), will be used for fixing possible deadlocks in the system-wide power management core code.

---
 drivers/base/power/main.c |   12 ++++++++----
 include/linux/async.h     |    2 ++
 kernel/async.c            |   29 +++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 4 deletions(-)

Index: linux-pm/kernel/async.c
===================================================================

kernel/async.c +6 -0
@@ -244,6 +244,35 @@ async_cookie_t async_schedule_node(async
 EXPORT_SYMBOL_GPL(async_schedule_node);
 
 /**
+ * async_schedule_dev_nocall - A simplified variant of async_schedule_dev()
+ * @func: function to execute asynchronously
+ * @dev: device argument to be passed to function
+ *
+ * @dev is used as both the argument for the function and to provide NUMA
+ * context for where to run the function.

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 10 lines
kernel/async.c +10 -0
+ *
+ * If the asynchronous execution of @func is scheduled successfully, return
+ * true. Otherwise, do nothing and return false, unlike async_schedule_dev()
+ * that will run the function synchronously then.
+ */
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+	struct async_entry *entry;
+
+	entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ? I think it's not safe since will be called from device_resume_noirq() .

Regards
Stanislaw

#12 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 36 lines · Stanislaw Gruszka
Quoted 34 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

In preparation for subsequent changes, introduce a specialized variant of async_schedule_dev() that will not invoke the argument function synchronously when it cannot be scheduled for asynchronous execution.

The new function, async_schedule_dev_nocall(), will be used for fixing possible deadlocks in the system-wide power management core code.

---
 drivers/base/power/main.c |   12 ++++++++----
 include/linux/async.h     |    2 ++
 kernel/async.c            |   29 +++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 4 deletions(-)

Index: linux-pm/kernel/async.c
===================================================================

kernel/async.c +10 -0
@@ -244,6 +244,35 @@ async_cookie_t async_schedule_node(async
 EXPORT_SYMBOL_GPL(async_schedule_node);
 
 /**
+ * async_schedule_dev_nocall - A simplified variant of async_schedule_dev()
+ * @func: function to execute asynchronously
+ * @dev: device argument to be passed to function
+ *
+ * @dev is used as both the argument for the function and to provide NUMA
+ * context for where to run the function.
+ *
+ * If the asynchronous execution of @func is scheduled successfully, return
+ * true. Otherwise, do nothing and return false, unlike async_schedule_dev()
+ * that will run the function synchronously then.
Quoted 8 lines

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 6 lines
kernel/async.c +6 -0
+ */
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+     struct async_entry *entry;
+
+     entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ?

Yes, it is.

PM will be the only user of this, at least for now, and it all runs in process context.

Quoted 2 lines

I think it's not safe since will
be called from device_resume_noirq() .

device_resume_noirq() runs in process context too.

The name is somewhat confusing (sorry about that) and it means that hardirq handlers (for the majority of IRQs) don't run in that resume phase, but interrupts are enabled locally on all CPUs (this is required for wakeup handling, among other things).

#13 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 16 lines · Rafael J. Wysocki
Quoted 7 lines

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 5 lines
kernel/async.c +5 -0
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+     struct async_entry *entry;
+
+     entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ?

Yes, it is.

PM will be the only user of this, at least for now, and it all runs in process context.

Quoted 2 lines

I think it's not safe since will
be called from device_resume_noirq() .

Quoted 6 lines

device_resume_noirq() runs in process context too.

The name is somewhat confusing (sorry about that) and it means that hardirq handlers (for the majority of IRQs) don't run in that resume phase, but interrupts are enabled locally on all CPUs (this is required for wakeup handling, among other things).

Then my concern would be: if among devices with disabled IRQs are disk devices? Seems there are disk devices as well, and because GFP_KERNEL can start reclaiming memory by doing disk IO (write dirty pages for example), with disk driver interrupts disabled reclaiming process can not finish.

I do not see how such possible infinite waiting for disk IO scenario is prevented here, did I miss something?

Regards
Stanislaw

#14 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 25 lines · Stanislaw Gruszka
Quoted 16 lines · Rafael J. Wysocki
Quoted 7 lines

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 5 lines
kernel/async.c +5 -0
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+     struct async_entry *entry;
+
+     entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ?

Yes, it is.

PM will be the only user of this, at least for now, and it all runs in process context.

Quoted 2 lines

I think it's not safe since will
be called from device_resume_noirq() .

Quoted 6 lines

device_resume_noirq() runs in process context too.

The name is somewhat confusing (sorry about that) and it means that hardirq handlers (for the majority of IRQs) don't run in that resume phase, but interrupts are enabled locally on all CPUs (this is required for wakeup handling, among other things).

Quoted 8 lines

Then my concern would be: if among devices with disabled IRQs are disk devices? Seems there are disk devices as well, and because GFP_KERNEL can start reclaiming memory by doing disk IO (write dirty pages for example), with disk driver interrupts disabled reclaiming process can not finish.

I do not see how such possible infinite waiting for disk IO scenario is prevented here, did I miss something?

Well, it is not a concern, because the suspend code already prevents the mm subsystem from trying too hard to find free memory. See the pm_restrict_gfp_mask() call in enter_state().

Otherwise, it would have been a problem for any GFP_KERNEL allocations made during system-wide suspend-resume, not just in the _noirq phases.

Reviewed-by
#15 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 33 lines · Rafael J. Wysocki
Quoted 31 lines · Stanislaw Gruszka
Quoted 16 lines · Rafael J. Wysocki
Quoted 7 lines

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 5 lines
kernel/async.c +5 -0
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+     struct async_entry *entry;
+
+     entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ?

Yes, it is.

PM will be the only user of this, at least for now, and it all runs in process context.

Quoted 2 lines

I think it's not safe since will
be called from device_resume_noirq() .

Quoted 6 lines

device_resume_noirq() runs in process context too.

The name is somewhat confusing (sorry about that) and it means that hardirq handlers (for the majority of IRQs) don't run in that resume phase, but interrupts are enabled locally on all CPUs (this is required for wakeup handling, among other things).

Then my concern would be: if among devices with disabled IRQs are disk devices? Seems there are disk devices as well, and because GFP_KERNEL can start reclaiming memory by doing disk IO (write dirty pages for example), with disk driver interrupts disabled reclaiming process can not finish.

Quoted 6 lines
Quoted 2 lines

I do not see how such possible infinite waiting for disk IO scenario is prevented here, did I miss something?

Well, it is not a concern, because the suspend code already prevents the mm subsystem from trying too hard to find free memory. See the pm_restrict_gfp_mask() call in enter_state().

So that I missed :-) Thanks for explanations.

#16 lore raw

Re: [PATCH v1 2/3] async: Introduce async_schedule_dev_nocall()

Quoted 38 lines · Stanislaw Gruszka
Quoted 36 lines · Rafael J. Wysocki
Quoted 33 lines · Stanislaw Gruszka
Quoted 16 lines · Rafael J. Wysocki
Quoted 7 lines

kernel/async.c async_cookie_t async_schedule_node(async · line 262

Quoted 5 lines
kernel/async.c +5 -0
+bool async_schedule_dev_nocall(async_func_t func, struct device *dev)
+{
+     struct async_entry *entry;
+
+     entry = kzalloc(sizeof(struct async_entry), GFP_KERNEL);

Is GFP_KERNEL intended here ?

Yes, it is.

PM will be the only user of this, at least for now, and it all runs in process context.

Quoted 2 lines

I think it's not safe since will
be called from device_resume_noirq() .

Quoted 6 lines

device_resume_noirq() runs in process context too.

The name is somewhat confusing (sorry about that) and it means that hardirq handlers (for the majority of IRQs) don't run in that resume phase, but interrupts are enabled locally on all CPUs (this is required for wakeup handling, among other things).

Then my concern would be: if among devices with disabled IRQs are disk devices? Seems there are disk devices as well, and because GFP_KERNEL can start reclaiming memory by doing disk IO (write dirty pages for example), with disk driver interrupts disabled reclaiming process can not finish.

I do not see how such possible infinite waiting for disk IO scenario is prevented here, did I miss something?

Quoted 7 lines
Quoted 3 lines

Well, it is not a concern, because the suspend code already prevents the mm subsystem from trying too hard to find free memory. See the pm_restrict_gfp_mask() call in enter_state().

So that I missed :-) Thanks for explanations.

Thank you!

#17 lore raw

PATCH 3/3 PM: sleep: Fix possible deadlocks in core system-wide PM code

In reply to Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")

---

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Still, the fix will not apply to the code before that commit.

---
 drivers/base/power/main.c |  148 +++++++++++++++++++++-------------------------
 1 file changed, 68 insertions(+), 80 deletions(-)
drivers/base/power/main.c +68 -80
Index: linux-pm/drivers/base/power/main.c
===================================================================
@@ -579,7 +579,7 @@ bool dev_pm_skip_resume(struct device *d
 }
 
 /**
- * device_resume_noirq - Execute a "noirq resume" callback for given device.
+ * __device_resume_noirq - Execute a "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
@@ -587,7 +587,7 @@ bool dev_pm_skip_resume(struct device *d
  * The driver of @dev will not receive interrupts while this function is being
  * executed.
  */
-static int device_resume_noirq(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
 {
 	pm_callback_t callback = NULL;
 	const char *info = NULL;
@@ -655,7 +655,13 @@ Skip:
 Out:
 	complete_all(&dev->power.completion);
 	TRACE_RESUME(error);
-	return error;
+
+	if (error) {
+		suspend_stats.failed_resume_noirq++;
+		dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
+		dpm_save_failed_dev(dev_name(dev));
+		pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
+	}
 }
 
 static bool is_async(struct device *dev)
@@ -668,11 +674,15 @@ static bool dpm_async_fn(struct device *
 {
 	reinit_completion(&dev->power.completion);
 
-	if (is_async(dev)) {
-		get_device(dev);
-		async_schedule_dev(func, dev);
+	if (!is_async(dev))
+		return false;
+
+	get_device(dev);
+
+	if (async_schedule_dev_nocall(func, dev))
 		return true;
-	}
+
+	put_device(dev);
 
 	return false;
 }
@@ -680,15 +690,19 @@ static bool dpm_async_fn(struct device *
 static void async_resume_noirq(void *data, async_cookie_t cookie)
 {
 	struct device *dev = data;
-	int error;
-
-	error = device_resume_noirq(dev, pm_transition, true);
-	if (error)
-		pm_dev_err(dev, pm_transition, " async", error);
 
+	__device_resume_noirq(dev, pm_transition, true);
 	put_device(dev);
 }
 
+static void device_resume_noirq(struct device *dev)
+{
+	if (dpm_async_fn(dev, async_resume_noirq))
+		return;
+
+	__device_resume_noirq(dev, pm_transition, false);
+}
+
 static void dpm_noirq_resume_devices(pm_message_t state)
 {
 	struct device *dev;
@@ -698,14 +712,6 @@ static void dpm_noirq_resume_devices(pm_
 	mutex_lock(&dpm_list_mtx);
 	pm_transition = state;
 
-	/*
-	 * Advanced the async threads upfront,
-	 * in case the starting of async threads is
-	 * delayed by non-async resuming devices.
-	 */
-	list_for_each_entry(dev, &dpm_noirq_list, power.entry)
-		dpm_async_fn(dev, async_resume_noirq);
-
 	while (!list_empty(&dpm_noirq_list)) {
 		dev = to_device(dpm_noirq_list.next);
 		get_device(dev);
@@ -713,17 +719,7 @@ static void dpm_noirq_resume_devices(pm_
 
 		mutex_unlock(&dpm_list_mtx);
 
-		if (!is_async(dev)) {
-			int error;
-
-			error = device_resume_noirq(dev, state, false);
-			if (error) {
-				suspend_stats.failed_resume_noirq++;
-				dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
-				dpm_save_failed_dev(dev_name(dev));
-				pm_dev_err(dev, state, " noirq", error);
-			}
-		}
+		device_resume_noirq(dev);
 
 		put_device(dev);
 
@@ -751,14 +747,14 @@ void dpm_resume_noirq(pm_message_t state
 }
 
 /**
- * device_resume_early - Execute an "early resume" callback for given device.
+ * __device_resume_early - Execute an "early resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  *
  * Runtime PM is disabled for @dev while this function is being executed.
  */
-static int device_resume_early(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_early(struct device *dev, pm_message_t state, bool async)
 {
 	pm_callback_t callback = NULL;
 	const char *info = NULL;
@@ -811,21 +807,31 @@ Out:
 
 	pm_runtime_enable(dev);
 	complete_all(&dev->power.completion);
-	return error;
+
+	if (error) {
+		suspend_stats.failed_resume_early++;
+		dpm_save_failed_step(SUSPEND_RESUME_EARLY);
+		dpm_save_failed_dev(dev_name(dev));
+		pm_dev_err(dev, state, async ? " async early" : " early", error);
+	}
 }
 
 static void async_resume_early(void *data, async_cookie_t cookie)
 {
 	struct device *dev = data;
-	int error;
-
-	error = device_resume_early(dev, pm_transition, true);
-	if (error)
-		pm_dev_err(dev, pm_transition, " async", error);
 
+	__device_resume_early(dev, pm_transition, true);
 	put_device(dev);
 }
 
+static void device_resume_early(struct device *dev)
+{
+	if (dpm_async_fn(dev, async_resume_early))
+		return;
+
+	__device_resume_early(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume_early - Execute "early resume" callbacks for all devices.
  * @state: PM transition of the system being carried out.
@@ -839,14 +845,6 @@ void dpm_resume_early(pm_message_t state
 	mutex_lock(&dpm_list_mtx);
 	pm_transition = state;
 
-	/*
-	 * Advanced the async threads upfront,
-	 * in case the starting of async threads is
-	 * delayed by non-async resuming devices.
-	 */
-	list_for_each_entry(dev, &dpm_late_early_list, power.entry)
-		dpm_async_fn(dev, async_resume_early);
-
 	while (!list_empty(&dpm_late_early_list)) {
 		dev = to_device(dpm_late_early_list.next);
 		get_device(dev);
@@ -854,17 +852,7 @@ void dpm_resume_early(pm_message_t state
 
 		mutex_unlock(&dpm_list_mtx);
 
-		if (!is_async(dev)) {
-			int error;
-
-			error = device_resume_early(dev, state, false);
-			if (error) {
-				suspend_stats.failed_resume_early++;
-				dpm_save_failed_step(SUSPEND_RESUME_EARLY);
-				dpm_save_failed_dev(dev_name(dev));
-				pm_dev_err(dev, state, " early", error);
-			}
-		}
+		device_resume_early(dev);
 
 		put_device(dev);
 
@@ -888,12 +876,12 @@ void dpm_resume_start(pm_message_t state
 EXPORT_SYMBOL_GPL(dpm_resume_start);
 
 /**
- * device_resume - Execute "resume" callbacks for given device.
+ * __device_resume - Execute "resume" callbacks for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  */
-static int device_resume(struct device *dev, pm_message_t state, bool async)
+static void __device_resume(struct device *dev, pm_message_t state, bool async)
 {
 	pm_callback_t callback = NULL;
 	const char *info = NULL;
@@ -975,20 +963,30 @@ static int device_resume(struct device *
 
 	TRACE_RESUME(error);
 
-	return error;
+	if (error) {
+		suspend_stats.failed_resume++;
+		dpm_save_failed_step(SUSPEND_RESUME);
+		dpm_save_failed_dev(dev_name(dev));
+		pm_dev_err(dev, state, async ? " async" : "", error);
+	}
 }
 
 static void async_resume(void *data, async_cookie_t cookie)
 {
 	struct device *dev = data;
-	int error;
 
-	error = device_resume(dev, pm_transition, true);
-	if (error)
-		pm_dev_err(dev, pm_transition, " async", error);
+	__device_resume(dev, pm_transition, true);
 	put_device(dev);
 }
 
+static void device_resume(struct device *dev)
+{
+	if (dpm_async_fn(dev, async_resume))
+		return;
+
+	__device_resume(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume - Execute "resume" callbacks for non-sysdev devices.
  * @state: PM transition of the system being carried out.
@@ -1008,27 +1006,17 @@ void dpm_resume(pm_message_t state)
 	pm_transition = state;
 	async_error = 0;
 
-	list_for_each_entry(dev, &dpm_suspended_list, power.entry)
-		dpm_async_fn(dev, async_resume);
-
 	while (!list_empty(&dpm_suspended_list)) {
 		dev = to_device(dpm_suspended_list.next);
+
 		get_device(dev);
-		if (!is_async(dev)) {
-			int error;
 
-			mutex_unlock(&dpm_list_mtx);
+		mutex_unlock(&dpm_list_mtx);
 
-			error = device_resume(dev, state, false);
-			if (error) {
-				suspend_stats.failed_resume++;
-				dpm_save_failed_step(SUSPEND_RESUME);
-				dpm_save_failed_dev(dev_name(dev));
-				pm_dev_err(dev, state, "", error);
-			}
+		device_resume(dev);
+
+		mutex_lock(&dpm_list_mtx);
 
-			mutex_lock(&dpm_list_mtx);
-		}
 		if (!list_empty(&dev->power.entry))
 			list_move_tail(&dev->power.entry, &dpm_prepared_list);
 
#18 lore raw

Re: [PATCH v1 3/3] PM: sleep: Fix possible deadlocks in core system-wide PM code

Quoted 17 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")
Quoted 6 lines

---

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Still, the fix will not apply to the code before that commit.

An option could be to just do "Cc: [email protected] # v5.7+" instead of pointing to a commit with a Fixes tag.

Quoted 89 lines
---
 drivers/base/power/main.c |  148 +++++++++++++++++++++-------------------------
 1 file changed, 68 insertions(+), 80 deletions(-)

Index: linux-pm/drivers/base/power/main.c
===================================================================

drivers/base/power/main.c +26 -12
@@ -579,7 +579,7 @@ bool dev_pm_skip_resume(struct device *d
 }
 
 /**
- * device_resume_noirq - Execute a "noirq resume" callback for given device.
+ * __device_resume_noirq - Execute a "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
@@ -587,7 +587,7 @@ bool dev_pm_skip_resume(struct device *d
  * The driver of @dev will not receive interrupts while this function is being
  * executed.
  */
-static int device_resume_noirq(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -655,7 +655,13 @@ Skip:
 Out:
        complete_all(&dev->power.completion);
        TRACE_RESUME(error);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_noirq++;
+               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
+       }
 }
 
 static bool is_async(struct device *dev)
@@ -668,11 +674,15 @@ static bool dpm_async_fn(struct device *
 {
        reinit_completion(&dev->power.completion);
 
-       if (is_async(dev)) {
-               get_device(dev);
-               async_schedule_dev(func, dev);
+       if (!is_async(dev))
+               return false;
+
+       get_device(dev);
+
+       if (async_schedule_dev_nocall(func, dev))
                return true;
-       }
+
+       put_device(dev);
 
        return false;
 }
@@ -680,15 +690,19 @@ static bool dpm_async_fn(struct device *
 static void async_resume_noirq(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_noirq(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_noirq(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_noirq(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_noirq))
+               return;
+
+       __device_resume_noirq(dev, pm_transition, false);
+}
+
 static void dpm_noirq_resume_devices(pm_message_t state)
 {
        struct device *dev;
@@ -698,14 +712,6 @@ static void dpm_noirq_resume_devices(pm_
        mutex_lock(&dpm_list_mtx);

drivers/base/power/main.c static void dpm_noirq_resume_devices(pm_ · line 708

Quoted 10 lines
drivers/base/power/main.c +0 -8
        pm_transition = state;
 
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_noirq_list, power.entry)
-               dpm_async_fn(dev, async_resume_noirq);
-

If I understand correctly, this means that we are no longer going to run the async devices upfront, right?

Depending on how devices get ordered in the dpm_noirq_list, it sounds like the above could have a negative impact on the total resume time!? Of course, if all devices would be async capable this wouldn't be a problem...

Quoted 79 lines
drivers/base/power/main.c +19 -19
        while (!list_empty(&dpm_noirq_list)) {
                dev = to_device(dpm_noirq_list.next);
                get_device(dev);
@@ -713,17 +719,7 @@ static void dpm_noirq_resume_devices(pm_
 
                mutex_unlock(&dpm_list_mtx);
 
-               if (!is_async(dev)) {
-                       int error;
-
-                       error = device_resume_noirq(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume_noirq++;
-                               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, " noirq", error);
-                       }
-               }
+               device_resume_noirq(dev);
 
                put_device(dev);
 
@@ -751,14 +747,14 @@ void dpm_resume_noirq(pm_message_t state
 }
 
 /**
- * device_resume_early - Execute an "early resume" callback for given device.
+ * __device_resume_early - Execute an "early resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  *
  * Runtime PM is disabled for @dev while this function is being executed.
  */
-static int device_resume_early(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_early(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -811,21 +807,31 @@ Out:
 
        pm_runtime_enable(dev);
        complete_all(&dev->power.completion);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_early++;
+               dpm_save_failed_step(SUSPEND_RESUME_EARLY);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async early" : " early", error);
+       }
 }
 
 static void async_resume_early(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_early(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_early(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_early(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_early))
+               return;
+
+       __device_resume_early(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume_early - Execute "early resume" callbacks for all devices.
  * @state: PM transition of the system being carried out.
@@ -839,14 +845,6 @@ void dpm_resume_early(pm_message_t state
        mutex_lock(&dpm_list_mtx);

drivers/base/power/main.c void dpm_resume_early(pm_message_t state · line 849

Quoted 10 lines
drivers/base/power/main.c +0 -8
        pm_transition = state;
 
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_late_early_list, power.entry)
-               dpm_async_fn(dev, async_resume_early);
-

Ditto.

Quoted 74 lines
drivers/base/power/main.c +18 -18
        while (!list_empty(&dpm_late_early_list)) {
                dev = to_device(dpm_late_early_list.next);
                get_device(dev);
@@ -854,17 +852,7 @@ void dpm_resume_early(pm_message_t state
 
                mutex_unlock(&dpm_list_mtx);
 
-               if (!is_async(dev)) {
-                       int error;
-
-                       error = device_resume_early(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume_early++;
-                               dpm_save_failed_step(SUSPEND_RESUME_EARLY);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, " early", error);
-                       }
-               }
+               device_resume_early(dev);
 
                put_device(dev);
 
@@ -888,12 +876,12 @@ void dpm_resume_start(pm_message_t state
 EXPORT_SYMBOL_GPL(dpm_resume_start);
 
 /**
- * device_resume - Execute "resume" callbacks for given device.
+ * __device_resume - Execute "resume" callbacks for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  */
-static int device_resume(struct device *dev, pm_message_t state, bool async)
+static void __device_resume(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -975,20 +963,30 @@ static int device_resume(struct device *
 
        TRACE_RESUME(error);
 
-       return error;
+       if (error) {
+               suspend_stats.failed_resume++;
+               dpm_save_failed_step(SUSPEND_RESUME);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async" : "", error);
+       }
 }
 
 static void async_resume(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
 
-       error = device_resume(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
+       __device_resume(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume))
+               return;
+
+       __device_resume(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume - Execute "resume" callbacks for non-sysdev devices.
  * @state: PM transition of the system being carried out.

drivers/base/power/main.c void dpm_resume(pm_message_t state) · line 1013

Quoted 6 lines
drivers/base/power/main.c +0 -3
@@ -1008,27 +1006,17 @@ void dpm_resume(pm_message_t state)
        pm_transition = state;
        async_error = 0;
 
-       list_for_each_entry(dev, &dpm_suspended_list, power.entry)
-               dpm_async_fn(dev, async_resume);
-

Ditto.

Quoted 16 lines
drivers/base/power/main.c +2 -9
        while (!list_empty(&dpm_suspended_list)) {
                dev = to_device(dpm_suspended_list.next);
+
                get_device(dev);
-               if (!is_async(dev)) {
-                       int error;
 
-                       mutex_unlock(&dpm_list_mtx);
+               mutex_unlock(&dpm_list_mtx);
 
-                       error = device_resume(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume++;
-                               dpm_save_failed_step(SUSPEND_RESUME);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, "", error);

drivers/base/power/main.c void dpm_resume(pm_message_t state) · line 1022

Quoted 10 lines
drivers/base/power/main.c +3 -3
-                       }
+               device_resume(dev);
+
+               mutex_lock(&dpm_list_mtx);
 
-                       mutex_lock(&dpm_list_mtx);
-               }
                if (!list_empty(&dev->power.entry))
                        list_move_tail(&dev->power.entry, &dpm_prepared_list);
 

Other than the potential issue I pointed out, the code as such looks good to me!

Kind regards
Uffe

#19 lore raw

Re: [PATCH v1 3/3] PM: sleep: Fix possible deadlocks in core system-wide PM code

Quoted 21 lines · Ulf Hansson
Quoted 19 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")

---

Quoted 7 lines
Quoted 4 lines

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Still, the fix will not apply to the code before that commit.

An option could be to just do "Cc: [email protected] # v5.7+" instead of pointing to a commit with a Fixes tag.

Right, but one can argue that every commit with a "Cc: stable" tag is a fix, so it should carry a Fixes: tag too anyway.

Quoted 91 lines
Quoted 91 lines
---
 drivers/base/power/main.c |  148 +++++++++++++++++++++-------------------------
 1 file changed, 68 insertions(+), 80 deletions(-)

Index: linux-pm/drivers/base/power/main.c
===================================================================

drivers/base/power/main.c +26 -12
@@ -579,7 +579,7 @@ bool dev_pm_skip_resume(struct device *d
 }
 
 /**
- * device_resume_noirq - Execute a "noirq resume" callback for given device.
+ * __device_resume_noirq - Execute a "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
@@ -587,7 +587,7 @@ bool dev_pm_skip_resume(struct device *d
  * The driver of @dev will not receive interrupts while this function is being
  * executed.
  */
-static int device_resume_noirq(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -655,7 +655,13 @@ Skip:
 Out:
        complete_all(&dev->power.completion);
        TRACE_RESUME(error);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_noirq++;
+               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
+       }
 }
 
 static bool is_async(struct device *dev)
@@ -668,11 +674,15 @@ static bool dpm_async_fn(struct device *
 {
        reinit_completion(&dev->power.completion);
 
-       if (is_async(dev)) {
-               get_device(dev);
-               async_schedule_dev(func, dev);
+       if (!is_async(dev))
+               return false;
+
+       get_device(dev);
+
+       if (async_schedule_dev_nocall(func, dev))
                return true;
-       }
+
+       put_device(dev);
 
        return false;
 }
@@ -680,15 +690,19 @@ static bool dpm_async_fn(struct device *
 static void async_resume_noirq(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_noirq(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_noirq(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_noirq(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_noirq))
+               return;
+
+       __device_resume_noirq(dev, pm_transition, false);
+}
+
 static void dpm_noirq_resume_devices(pm_message_t state)
 {
        struct device *dev;
@@ -698,14 +712,6 @@ static void dpm_noirq_resume_devices(pm_
        mutex_lock(&dpm_list_mtx);
        pm_transition = state;
 
Quoted 11 lines

drivers/base/power/main.c static void dpm_noirq_resume_devices(pm_ · line 708

Quoted 8 lines
drivers/base/power/main.c +0 -8
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_noirq_list, power.entry)
-               dpm_async_fn(dev, async_resume_noirq);
-

If I understand correctly, this means that we are no longer going to run the async devices upfront, right?

Right.

Quoted 2 lines

Depending on how devices get ordered in the dpm_noirq_list, it sounds like the above could have a negative impact on the total resume time!?

It could, but it is unclear at this time whether or not it will.

Quoted 2 lines

Of course, if all devices would be async capable this wouldn't be a problem...

Sure.

So the existing behavior can be restored with the help of an additional device flag, but I didn't decide to add such a flag just yet.

I'll probably do it in 6.9, unless the performance impact is serious enough, in which case it can be added earlier.

I still would prefer to get to a point at which the suspend and resume paths are analogous (from the async POV) and that's what happens after this patch, so I'd say that IMO it is better to address any performance regressions on top of it.

Quoted 196 lines
Quoted 79 lines
drivers/base/power/main.c +19 -19
        while (!list_empty(&dpm_noirq_list)) {
                dev = to_device(dpm_noirq_list.next);
                get_device(dev);
@@ -713,17 +719,7 @@ static void dpm_noirq_resume_devices(pm_
 
                mutex_unlock(&dpm_list_mtx);
 
-               if (!is_async(dev)) {
-                       int error;
-
-                       error = device_resume_noirq(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume_noirq++;
-                               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, " noirq", error);
-                       }
-               }
+               device_resume_noirq(dev);
 
                put_device(dev);
 
@@ -751,14 +747,14 @@ void dpm_resume_noirq(pm_message_t state
 }
 
 /**
- * device_resume_early - Execute an "early resume" callback for given device.
+ * __device_resume_early - Execute an "early resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  *
  * Runtime PM is disabled for @dev while this function is being executed.
  */
-static int device_resume_early(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_early(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -811,21 +807,31 @@ Out:
 
        pm_runtime_enable(dev);
        complete_all(&dev->power.completion);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_early++;
+               dpm_save_failed_step(SUSPEND_RESUME_EARLY);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async early" : " early", error);
+       }
 }
 
 static void async_resume_early(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_early(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_early(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_early(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_early))
+               return;
+
+       __device_resume_early(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume_early - Execute "early resume" callbacks for all devices.
  * @state: PM transition of the system being carried out.
@@ -839,14 +845,6 @@ void dpm_resume_early(pm_message_t state
        mutex_lock(&dpm_list_mtx);

drivers/base/power/main.c void dpm_resume_early(pm_message_t state · line 849

Quoted 10 lines
drivers/base/power/main.c +0 -8
        pm_transition = state;
 
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_late_early_list, power.entry)
-               dpm_async_fn(dev, async_resume_early);
-

Ditto.

Quoted 74 lines
drivers/base/power/main.c +18 -18
        while (!list_empty(&dpm_late_early_list)) {
                dev = to_device(dpm_late_early_list.next);
                get_device(dev);
@@ -854,17 +852,7 @@ void dpm_resume_early(pm_message_t state
 
                mutex_unlock(&dpm_list_mtx);
 
-               if (!is_async(dev)) {
-                       int error;
-
-                       error = device_resume_early(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume_early++;
-                               dpm_save_failed_step(SUSPEND_RESUME_EARLY);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, " early", error);
-                       }
-               }
+               device_resume_early(dev);
 
                put_device(dev);
 
@@ -888,12 +876,12 @@ void dpm_resume_start(pm_message_t state
 EXPORT_SYMBOL_GPL(dpm_resume_start);
 
 /**
- * device_resume - Execute "resume" callbacks for given device.
+ * __device_resume - Execute "resume" callbacks for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
  */
-static int device_resume(struct device *dev, pm_message_t state, bool async)
+static void __device_resume(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -975,20 +963,30 @@ static int device_resume(struct device *
 
        TRACE_RESUME(error);
 
-       return error;
+       if (error) {
+               suspend_stats.failed_resume++;
+               dpm_save_failed_step(SUSPEND_RESUME);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async" : "", error);
+       }
 }
 
 static void async_resume(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
 
-       error = device_resume(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
+       __device_resume(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume))
+               return;
+
+       __device_resume(dev, pm_transition, false);
+}
+
 /**
  * dpm_resume - Execute "resume" callbacks for non-sysdev devices.
  * @state: PM transition of the system being carried out.

drivers/base/power/main.c void dpm_resume(pm_message_t state) · line 1013

Quoted 6 lines
drivers/base/power/main.c +0 -3
@@ -1008,27 +1006,17 @@ void dpm_resume(pm_message_t state)
        pm_transition = state;
        async_error = 0;
 
-       list_for_each_entry(dev, &dpm_suspended_list, power.entry)
-               dpm_async_fn(dev, async_resume);
-

Ditto.

Quoted 21 lines
drivers/base/power/main.c +5 -10
        while (!list_empty(&dpm_suspended_list)) {
                dev = to_device(dpm_suspended_list.next);
+
                get_device(dev);
-               if (!is_async(dev)) {
-                       int error;
 
-                       mutex_unlock(&dpm_list_mtx);
+               mutex_unlock(&dpm_list_mtx);
 
-                       error = device_resume(dev, state, false);
-                       if (error) {
-                               suspend_stats.failed_resume++;
-                               dpm_save_failed_step(SUSPEND_RESUME);
-                               dpm_save_failed_dev(dev_name(dev));
-                               pm_dev_err(dev, state, "", error);
-                       }
+               device_resume(dev);
+
+               mutex_lock(&dpm_list_mtx);
 
Quoted 7 lines

drivers/base/power/main.c void dpm_resume(pm_message_t state) · line 1022

Quoted 5 lines
drivers/base/power/main.c +0 -2
-                       mutex_lock(&dpm_list_mtx);
-               }
                if (!list_empty(&dev->power.entry))
                        list_move_tail(&dev->power.entry, &dpm_prepared_list);
 

Other than the potential issue I pointed out, the code as such looks good to me!

Thank you!

#20 lore raw

Re: [PATCH v1 3/3] PM: sleep: Fix possible deadlocks in core system-wide PM code

Quoted 26 lines · Rafael J. Wysocki
Quoted 24 lines · Ulf Hansson
Quoted 22 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")

---

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

Still, the fix will not apply to the code before that commit.

An option could be to just do "Cc: [email protected] # v5.7+" instead of pointing to a commit with a Fixes tag.

Right, but one can argue that every commit with a "Cc: stable" tag is a fix, so it should carry a Fixes: tag too anyway.

Yes, certainly. But in this case it's more questionable as it's not really fixing the commit it points out.

Note that, I have no strong opinion here, but maybe Greg has a preferred way?

Quoted 120 lines
Quoted 92 lines
Quoted 92 lines

---
 drivers/base/power/main.c |  148 +++++++++++++++++++++-------------------------
 1 file changed, 68 insertions(+), 80 deletions(-)

Index: linux-pm/drivers/base/power/main.c
===================================================================

drivers/base/power/main.c +26 -12
@@ -579,7 +579,7 @@ bool dev_pm_skip_resume(struct device *d
 }
 
 /**
- * device_resume_noirq - Execute a "noirq resume" callback for given device.
+ * __device_resume_noirq - Execute a "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
@@ -587,7 +587,7 @@ bool dev_pm_skip_resume(struct device *d
  * The driver of @dev will not receive interrupts while this function is being
  * executed.
  */
-static int device_resume_noirq(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -655,7 +655,13 @@ Skip:
 Out:
        complete_all(&dev->power.completion);
        TRACE_RESUME(error);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_noirq++;
+               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
+       }
 }
 
 static bool is_async(struct device *dev)
@@ -668,11 +674,15 @@ static bool dpm_async_fn(struct device *
 {
        reinit_completion(&dev->power.completion);
 
-       if (is_async(dev)) {
-               get_device(dev);
-               async_schedule_dev(func, dev);
+       if (!is_async(dev))
+               return false;
+
+       get_device(dev);
+
+       if (async_schedule_dev_nocall(func, dev))
                return true;
-       }
+
+       put_device(dev);
 
        return false;
 }
@@ -680,15 +690,19 @@ static bool dpm_async_fn(struct device *
 static void async_resume_noirq(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_noirq(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_noirq(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_noirq(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_noirq))
+               return;
+
+       __device_resume_noirq(dev, pm_transition, false);
+}
+
 static void dpm_noirq_resume_devices(pm_message_t state)
 {
        struct device *dev;
@@ -698,14 +712,6 @@ static void dpm_noirq_resume_devices(pm_
        mutex_lock(&dpm_list_mtx);
        pm_transition = state;
 
Quoted 11 lines

drivers/base/power/main.c static void dpm_noirq_resume_devices(pm_ · line 708

Quoted 8 lines
drivers/base/power/main.c +0 -8
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_noirq_list, power.entry)
-               dpm_async_fn(dev, async_resume_noirq);
-

If I understand correctly, this means that we are no longer going to run the async devices upfront, right?

Right.

Quoted 2 lines

Depending on how devices get ordered in the dpm_noirq_list, it sounds like the above could have a negative impact on the total resume time!?

It could, but it is unclear at this time whether or not it will.

Quoted 2 lines

Of course, if all devices would be async capable this wouldn't be a problem...

Sure.

So the existing behavior can be restored with the help of an additional device flag, but I didn't decide to add such a flag just yet.

Quoted 7 lines

I'll probably do it in 6.9, unless the performance impact is serious enough, in which case it can be added earlier.

I still would prefer to get to a point at which the suspend and resume paths are analogous (from the async POV) and that's what happens after this patch, so I'd say that IMO it is better to address any performance regressions on top of it.

Fair enough!

[...]

Feel free to add my Reviewed-by for the series!

Kind regards
Uffe

#21 lore raw

Re: [PATCH v1 3/3] PM: sleep: Fix possible deadlocks in core system-wide PM code

Quoted 169 lines · Ulf Hansson
Quoted 26 lines · Rafael J. Wysocki
Quoted 24 lines · Ulf Hansson
Quoted 22 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")

---

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Quoted 7 lines
Quoted 4 lines
Quoted 1 line

Still, the fix will not apply to the code before that commit.

An option could be to just do "Cc: [email protected] # v5.7+" instead of pointing to a commit with a Fixes tag.

Right, but one can argue that every commit with a "Cc: stable" tag is a fix, so it should carry a Fixes: tag too anyway.

Yes, certainly. But in this case it's more questionable as it's not really fixing the commit it points out.

Note that, I have no strong opinion here, but maybe Greg has a preferred way?

Quoted 120 lines
Quoted 92 lines
Quoted 92 lines

---
 drivers/base/power/main.c |  148 +++++++++++++++++++++-------------------------
 1 file changed, 68 insertions(+), 80 deletions(-)

Index: linux-pm/drivers/base/power/main.c
===================================================================

drivers/base/power/main.c +26 -12
@@ -579,7 +579,7 @@ bool dev_pm_skip_resume(struct device *d
 }
 
 /**
- * device_resume_noirq - Execute a "noirq resume" callback for given device.
+ * __device_resume_noirq - Execute a "noirq resume" callback for given device.
  * @dev: Device to handle.
  * @state: PM transition of the system being carried out.
  * @async: If true, the device is being resumed asynchronously.
@@ -587,7 +587,7 @@ bool dev_pm_skip_resume(struct device *d
  * The driver of @dev will not receive interrupts while this function is being
  * executed.
  */
-static int device_resume_noirq(struct device *dev, pm_message_t state, bool async)
+static void __device_resume_noirq(struct device *dev, pm_message_t state, bool async)
 {
        pm_callback_t callback = NULL;
        const char *info = NULL;
@@ -655,7 +655,13 @@ Skip:
 Out:
        complete_all(&dev->power.completion);
        TRACE_RESUME(error);
-       return error;
+
+       if (error) {
+               suspend_stats.failed_resume_noirq++;
+               dpm_save_failed_step(SUSPEND_RESUME_NOIRQ);
+               dpm_save_failed_dev(dev_name(dev));
+               pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
+       }
 }
 
 static bool is_async(struct device *dev)
@@ -668,11 +674,15 @@ static bool dpm_async_fn(struct device *
 {
        reinit_completion(&dev->power.completion);
 
-       if (is_async(dev)) {
-               get_device(dev);
-               async_schedule_dev(func, dev);
+       if (!is_async(dev))
+               return false;
+
+       get_device(dev);
+
+       if (async_schedule_dev_nocall(func, dev))
                return true;
-       }
+
+       put_device(dev);
 
        return false;
 }
@@ -680,15 +690,19 @@ static bool dpm_async_fn(struct device *
 static void async_resume_noirq(void *data, async_cookie_t cookie)
 {
        struct device *dev = data;
-       int error;
-
-       error = device_resume_noirq(dev, pm_transition, true);
-       if (error)
-               pm_dev_err(dev, pm_transition, " async", error);
 
+       __device_resume_noirq(dev, pm_transition, true);
        put_device(dev);
 }
 
+static void device_resume_noirq(struct device *dev)
+{
+       if (dpm_async_fn(dev, async_resume_noirq))
+               return;
+
+       __device_resume_noirq(dev, pm_transition, false);
+}
+
 static void dpm_noirq_resume_devices(pm_message_t state)
 {
        struct device *dev;
@@ -698,14 +712,6 @@ static void dpm_noirq_resume_devices(pm_
        mutex_lock(&dpm_list_mtx);
        pm_transition = state;
 
Quoted 11 lines

drivers/base/power/main.c static void dpm_noirq_resume_devices(pm_ · line 708

Quoted 8 lines
drivers/base/power/main.c +0 -8
-       /*
-        * Advanced the async threads upfront,
-        * in case the starting of async threads is
-        * delayed by non-async resuming devices.
-        */
-       list_for_each_entry(dev, &dpm_noirq_list, power.entry)
-               dpm_async_fn(dev, async_resume_noirq);
-

If I understand correctly, this means that we are no longer going to run the async devices upfront, right?

Right.

Quoted 2 lines

Depending on how devices get ordered in the dpm_noirq_list, it sounds like the above could have a negative impact on the total resume time!?

It could, but it is unclear at this time whether or not it will.

Quoted 2 lines

Of course, if all devices would be async capable this wouldn't be a problem...

Sure.

So the existing behavior can be restored with the help of an additional device flag, but I didn't decide to add such a flag just yet.

Quoted 7 lines

I'll probably do it in 6.9, unless the performance impact is serious enough, in which case it can be added earlier.

I still would prefer to get to a point at which the suspend and resume paths are analogous (from the async POV) and that's what happens after this patch, so I'd say that IMO it is better to address any performance regressions on top of it.

Quoted 5 lines

Fair enough!

[...]

Feel free to add my Reviewed-by for the series!

Thanks!

maintainer
#22 lore raw

Re: [PATCH v1 3/3] PM: sleep: Fix possible deadlocks in core system-wide PM code

In reply to Ulf Hansson
Quoted 32 lines · Ulf Hansson
Quoted 31 lines · Rafael J. Wysocki
Quoted 28 lines · Ulf Hansson
Quoted 17 lines · Rafael J. Wysocki

From: Rafael J. Wysocki <[email protected]>

It is reported that in low-memory situations the system-wide resume core code deadlocks, because async_schedule_dev() executes its argument function synchronously if it cannot allocate memory (an not only then) and that function attempts to acquire a mutex that is already held.

Address this by changing the code in question to use async_schedule_dev_nocall() for scheduling the asynchronous execution of device suspend and resume functions and to directly run them synchronously if async_schedule_dev_nocall() returns false.

Fixes 09beebd8f93b ("PM: sleep: core: Switch back to async_schedule_dev()")
Quoted 6 lines

---

The commit pointed to by the Fixes: tag is the last one that modified the code in question, even though the bug had been there already before.

Still, the fix will not apply to the code before that commit.

An option could be to just do "Cc: [email protected] # v5.7+" instead of pointing to a commit with a Fixes tag.

Quoted 7 lines
Quoted 2 lines

Right, but one can argue that every commit with a "Cc: stable" tag is a fix, so it should carry a Fixes: tag too anyway.

Yes, certainly. But in this case it's more questionable as it's not really fixing the commit it points out.

Note that, I have no strong opinion here, but maybe Greg has a preferred way?

Either is fine with me, just to give me a good hint on how far back a commit should be backported to.

thanks,

greg k-h

#23 lore raw

Re: [PATCH v1 0/3] PM: sleep: Fix possible device suspend-resume deadlocks

In reply to Rafael J. Wysocki
Quoted 11 lines · Rafael J. Wysocki

Hi Everyone,

As reported here

https://lore.kernel.org/linux-pm/ZYvjiqX6EsL15moe@perf/

the device suspend-resume code running during system-wide PM transitions deadlock on low memory, because it attempts to acquire a mutex that's already held by it in those cases.

Quoted 6 lines

This series addresses the issue by changing the resume code behavior to directly run the device PM functions synchronously if they cannot be scheduled for asynchronous executions (patch [3/3]).

For this purpose, the async code is rearranged (patch [1/3]) and a new variant of async_schedule_dev() is introduced (patch [2/3]).

Given the lack of negative feedback, I've queued up this series for 6.8-rc1.

Please let me know if there are any issues with that.

Thanks!

author Tested-by
#24 lore raw

Re: [PATCH v1 0/3] PM: sleep: Fix possible device suspend-resume deadlocks

Quoted 16 lines · Rafael J. Wysocki
Quoted 15 lines · Rafael J. Wysocki

Hi Everyone,

As reported here

https://lore.kernel.org/linux-pm/ZYvjiqX6EsL15moe@perf/

the device suspend-resume code running during system-wide PM transitions deadlock on low memory, because it attempts to acquire a mutex that's already held by it in those cases.

This series addresses the issue by changing the resume code behavior to directly run the device PM functions synchronously if they cannot be scheduled for asynchronous executions (patch [3/3]).

Quoted 9 lines
Quoted 2 lines

For this purpose, the async code is rearranged (patch [1/3]) and a new variant of async_schedule_dev() is introduced (patch [2/3]).

Given the lack of negative feedback, I've queued up this series for 6.8-rc1.

Please let me know if there are any issues with that.

Thanks!

Hi Rafael

We haven't seen any regression issue under our stress test.

So, feel free to add

Thanks.

#25 lore raw

Re: [PATCH v1 0/3] PM: sleep: Fix possible device suspend-resume deadlocks

Quoted 27 lines · Youngmin Nam
Quoted 16 lines · Rafael J. Wysocki
Quoted 15 lines · Rafael J. Wysocki

Hi Everyone,

As reported here

https://lore.kernel.org/linux-pm/ZYvjiqX6EsL15moe@perf/

the device suspend-resume code running during system-wide PM transitions deadlock on low memory, because it attempts to acquire a mutex that's already held by it in those cases.

This series addresses the issue by changing the resume code behavior to directly run the device PM functions synchronously if they cannot be scheduled for asynchronous executions (patch [3/3]).

Quoted 9 lines
Quoted 2 lines

For this purpose, the async code is rearranged (patch [1/3]) and a new variant of async_schedule_dev() is introduced (patch [2/3]).

Given the lack of negative feedback, I've queued up this series for 6.8-rc1.

Please let me know if there are any issues with that.

Thanks!

Quoted 7 lines

Hi Rafael

We haven't seen any regression issue under our stress test.

So, feel free to add

Thank you!