From: Paul Menzel <pmenzel@molgen.mpg.de>
To: "Rafael J. Wysocki" <rafael@kernel.org>, Len Brown <lenb@kernel.org>
Cc: Paul Menzel <pmenzel@molgen.mpg.de>,
linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] ACPI: battery: Refresh the battery asynchronously after system resume
Date: Sat, 14 Nov 2026 01:26:12 +0100 [thread overview]
Message-ID: <20261114002612.219894-2-pmenzel@molgen.mpg.de> (raw)
The PM_POST_SUSPEND notifier evaluates _BIX, _BTP and _BST synchronously,
and the notifier chain is the last step of the system resume: the write to
/sys/power/state, and so systemd-sleep and everything waiting for it, only
returns after it.
On a Dell XPS 13 9310 nearly all of the PM_POST_SUSPEND notifier chain is
battery_notify() (143 ms with the function_graph tracer):
@ 143043.5 us | } /* battery_notify [battery] */
@ 143513.1 us | } /* notifier_call_chain */
@ 143536.0 us | } /* pm_notifier_call_chain */
Queue the refresh on system_freezable_wq instead.
Time from "Restarting tasks: Done" (end of thaw_processes) to "PM: suspend
exit" for suspend-to-idle cycles of 8 s with sleepgraph, in ms, on Linux
7.4-rc1 with other resume changes, which do not run in this phase:
panel lit at suspend panel off at suspend
before 24.5 26.7 27.3 29.4 58.8 60.4 85.2
after 10.7 10.8 15.9 17.7 5.7 8.3 9.3
median 27.0 -> 13.4 60.4 -> 8.3
After the change, pm_notifier_call_chain(PM_POST_SUSPEND) takes
0.7-0.9 ms (function_graph, three cycles, hwrng_pm_notifier() being the
largest), so the rest of the phase is outside the notifier chain. The
refresh still runs after each resume (kprobes, three cycles):
battery_notify() returns after 4-8 us, acpi_battery_pm_worker() starts
0.02-0.2 ms later and takes 17.8-18.6 ms, and it ends after rtcwake has
already returned.
Signed-off-by: Paul Menzel <pmenzel@molgen.mpg.de>
---
Notes:
Assisted-by: claude-opus-5.5 (Claude Code), analysis and patch
draft; I reviewed it.
drivers/acpi/battery.c | 63 +++++++++++++++++++++++++-----------------
1 file changed, 37 insertions(+), 26 deletions(-)
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 6edea559a5bce..4e2a79d356ef9 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -105,6 +105,7 @@ struct acpi_battery {
struct kfifo acpi_notif_fifo;
struct delayed_work acpi_notif_dwork;
struct notifier_block pm_nb;
+ struct work_struct pm_work;
struct list_head list;
unsigned long flags;
@@ -1293,40 +1294,48 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data)
pr_err_ratelimited("too many battery notifications within %dms\n", NOTIF_MERGING_MS);
}
-static int battery_notify(struct notifier_block *nb,
- unsigned long mode, void *_unused)
+static void acpi_battery_pm_worker(struct work_struct *work)
{
- struct acpi_battery *battery = container_of(nb, struct acpi_battery,
- pm_nb);
-
- if (mode == PM_POST_SUSPEND || mode == PM_POST_HIBERNATION) {
- guard(mutex)(&battery->update_lock);
-
- if (!acpi_battery_present(battery))
- return 0;
-
- if (battery->bat) {
- acpi_battery_refresh(battery);
- } else {
- int result;
+ struct acpi_battery *battery = container_of(work, struct acpi_battery,
+ pm_work);
- scoped_guard(mutex, &battery->property_lock) {
- result = acpi_battery_get_info(battery);
- if (result)
- return result;
- }
+ guard(mutex)(&battery->update_lock);
- result = sysfs_add_battery(battery);
- if (result)
- return result;
- }
+ if (!acpi_battery_present(battery))
+ return;
+ if (battery->bat) {
+ acpi_battery_refresh(battery);
+ } else {
scoped_guard(mutex, &battery->property_lock) {
- acpi_battery_init_alarm(battery);
- acpi_battery_get_state(battery);
+ if (acpi_battery_get_info(battery))
+ return;
}
+
+ if (sysfs_add_battery(battery))
+ return;
}
+ scoped_guard(mutex, &battery->property_lock) {
+ acpi_battery_init_alarm(battery);
+ acpi_battery_get_state(battery);
+ }
+}
+
+static int battery_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
+ struct acpi_battery *battery = container_of(nb, struct acpi_battery,
+ pm_nb);
+
+ /*
+ * Evaluating _BIX, _BTP and _BST takes tens of milliseconds on some
+ * systems, and the PM notifier chain delays the end of the system
+ * resume, so do it asynchronously.
+ */
+ if (mode == PM_POST_SUSPEND || mode == PM_POST_HIBERNATION)
+ queue_work(system_freezable_wq, &battery->pm_work);
+
return 0;
}
@@ -1516,6 +1525,7 @@ static int acpi_battery_probe(struct platform_device *pdev)
if (result)
return result;
+ INIT_WORK(&battery->pm_work, acpi_battery_pm_worker);
battery->pm_nb.notifier_call = battery_notify;
return register_pm_notifier(&battery->pm_nb);
}
@@ -1525,6 +1535,7 @@ static void acpi_battery_remove(struct platform_device *pdev)
struct acpi_battery *battery = platform_get_drvdata(pdev);
unregister_pm_notifier(&battery->pm_nb);
+ cancel_work_sync(&battery->pm_work);
}
/* this is needed to learn about changes made in suspended state */
--
2.54.0
reply other threads:[~2026-11-14 0:26 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261114002612.219894-2-pmenzel@molgen.mpg.de \
--to=pmenzel@molgen.mpg.de \
--cc=lenb@kernel.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=rafael@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®