drivers/acpi/battery.c | 147 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 46 deletions(-)
The acpi_battery_get_property() callback calls acpi_battery_get_state()
without any lock held. On some devices, it happens that the property
cache has expired before a uevent reaches userspace, triggering
simultaneous attempts to evaluate _BST. See [1] for an analysis to sysrq
stacktraces on one of the these devices.
In a few cases, including when the AML is sleeping or acquiring a mutex,
ACPICA drops the namespace and interpreter locks and allows the
evaluation of _BST to start while another task is still evaluating it.
This could somehow confuse the interpreter and lead to chaos in AML
mutexes on some devices, see [2] for an example.
Not holding the lock is also prone to race conditions, for example:
CPU0 | CPU1
acpi_battery_get_property() |
acpi_battery_get_state() |
[update_time expired] |
extract_package() | acpi_battery_get_property()
battery->update_time = jiffies | acpi_battery_get_state()
kfree() | [up to date]
| [read capacity_now]
[fix capacity_now due to quirk] |
where CPU1 gets raw capacity_now before CPU0 fixes it to a meaningful
value.
The existing mutex update_lock is not applicapable for
acpi_battery_get_property(), as some code path could call or wait for
acpi_battery_get_property() while holding update_lock.
Therefore, introduce a mutex called property_lock to protect all
accesses to battery properties, so that acpi_battery_get_property() can
take the advantage of the mutex and synchronize itself. With the mutex,
acpi_battery_get_state() are synchronized in all code paths calling it,
and its cache mechanism can always clamp the frequency of _BST
evaluations according to cache_time.
The helper function acpi_battery_handle_discharging() for quirky devices
has to be inlined due to the change, as the mutex must be unlocked
before calling the expensive power_supply_is_system_supplied() helper
function.
Fixes: 86bfd21a0baf ("ACPI: battery: Drop redundant locking")
Tested-by: Avraham Hollander <anhollander516@gmail.com>
Reported-by: Rick <rickk1166@gmail.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221065#c85 [1]
Reported-by: Avraham Hollander <anhollander516@gmail.com>
Closes: https://lore.kernel.org/linux-acpi/CAP1mzZReJCn6df5DwEPu-JCQUyr=Pu1cg5xKCMttWZkHCQtVmQ@mail.gmail.com [2]
Signed-off-by: Rong Zhang <i@rong.moe>
---
Changes in v5:
- Reword commit message (thanks Rafael J. Wysocki)
- Rebase onto linux-pm after other patches in the series have been
applied
- Link to v4: https://patch.msgid.link/20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe
Changes in v4:
- Rebase and adopt devres-based resource management
- Refactor acpi_battery_notify() to hold the mutex across the entire
function to improve readability and drop unnecessary variables (thanks
Rafael J. Wysocki)
- Link to v3: https://patch.msgid.link/20260611-b4-acpi-battery-notification-v3-0-f9390382c5a4@rong.moe
Changes in v3:
- Address Sashiko's concerns on my last-minute changes:
- Set the number base to 10 in order not to break the ABI
- Do not overwrite the initial value of `ret' in
acpi_battery_get_property()
- https://sashiko.dev/#/patchset/20260611-b4-acpi-battery-notification-v2-0-4e8ed651a151%40rong.moe
- Link to v2: https://patch.msgid.link/20260611-b4-acpi-battery-notification-v2-0-4e8ed651a151@rong.moe
Changes in v2:
- Address Sashiko's concerns:
- Return from acpi_battery_notification_worker() early when the fifo
is empty
- Use pr_err_ratelimited() for potential event storms
- Add missing `\n' in a printk message
- Use a separated mutex to protect all properties instead of reusing
update_lock
- https://sashiko.dev/#/patchset/20260527-b4-acpi-battery-notification-v1-0-2303bed8ec0b%40rong.moe
- Minimalize the critical section of acpi_battery_notify()
- Rearrange the series
- Dropped Tested-by from patch 3 due to massive rewrite
- Link to v1: https://patch.msgid.link/20260527-b4-acpi-battery-notification-v1-0-2303bed8ec0b@rong.moe
---
drivers/acpi/battery.c | 147 +++++++++++++++++++++++++++++++++----------------
1 file changed, 101 insertions(+), 46 deletions(-)
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 0084f308b790..670853ec3a4d 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -17,6 +17,7 @@
#include <linux/kernel.h>
#include <linux/kfifo.h>
#include <linux/list.h>
+#include <linux/lockdep.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
@@ -105,6 +106,9 @@ struct acpi_battery {
struct delayed_work acpi_notif_dwork;
struct notifier_block pm_nb;
struct list_head list;
+ unsigned long flags;
+
+ struct mutex property_lock; /* Protects properties below. */
unsigned long update_time;
int revision;
int rate_now;
@@ -131,7 +135,6 @@ struct acpi_battery {
char oem_info[MAX_STRING_LENGTH];
int state;
int power_unit;
- unsigned long flags;
};
#define to_acpi_battery(x) power_supply_get_drvdata(x)
@@ -189,20 +192,6 @@ static bool acpi_battery_is_degraded(struct acpi_battery *battery)
battery->full_charge_capacity < battery->design_capacity;
}
-static int acpi_battery_handle_discharging(struct acpi_battery *battery)
-{
- /*
- * Some devices wrongly report discharging if the battery's charge level
- * was above the device's start charging threshold atm the AC adapter
- * was plugged in and the device thus did not start a new charge cycle.
- */
- if ((battery_ac_is_broken || power_supply_is_system_supplied()) &&
- battery->rate_now == 0)
- return POWER_SUPPLY_STATUS_NOT_CHARGING;
-
- return POWER_SUPPLY_STATUS_DISCHARGING;
-}
-
static int acpi_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -210,15 +199,41 @@ static int acpi_battery_get_property(struct power_supply *psy,
int full_capacity = ACPI_BATTERY_VALUE_UNKNOWN, ret = 0;
struct acpi_battery *battery = to_acpi_battery(psy);
- if (acpi_battery_present(battery)) {
- /* run battery update only if it is present */
- acpi_battery_get_state(battery);
- } else if (psp != POWER_SUPPLY_PROP_PRESENT)
- return -ENODEV;
+ /* run battery update only if it is present */
+ if (!acpi_battery_present(battery)) {
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 0;
+ return 0;
+ default:
+ return -ENODEV;
+ }
+ }
+
+ mutex_lock(&battery->property_lock);
+
+ acpi_battery_get_state(battery);
+
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
+ /*
+ * Some devices wrongly report discharging if the battery's charge level
+ * was above the device's start charging threshold atm the AC adapter
+ * was plugged in and the device thus did not start a new charge cycle.
+ */
if (battery->state & ACPI_BATTERY_STATE_DISCHARGING)
- val->intval = acpi_battery_handle_discharging(battery);
+ if (battery->rate_now != 0) {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if (battery_ac_is_broken) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ mutex_unlock(&battery->property_lock);
+
+ val->intval = power_supply_is_system_supplied()
+ ? POWER_SUPPLY_STATUS_NOT_CHARGING
+ : POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
else if (battery->state & ACPI_BATTERY_STATE_CHARGING)
/* Check the rate and capacity to validate the status. */
if (!acpi_battery_is_full(battery) ||
@@ -321,6 +336,8 @@ static int acpi_battery_get_property(struct power_supply *psy,
default:
ret = -EINVAL;
}
+
+ mutex_unlock(&battery->property_lock);
return ret;
}
@@ -556,6 +573,8 @@ static int acpi_battery_get_info(struct acpi_battery *battery)
int use_bix;
int result = -ENODEV;
+ lockdep_assert_held(&battery->property_lock);
+
if (!acpi_battery_present(battery))
return 0;
@@ -595,6 +614,8 @@ static int acpi_battery_get_state(struct acpi_battery *battery)
acpi_status status = 0;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ lockdep_assert_held(&battery->property_lock);
+
if (!acpi_battery_present(battery))
return 0;
@@ -648,6 +669,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery)
{
acpi_status status = 0;
+ lockdep_assert_held(&battery->property_lock);
+
if (!acpi_battery_present(battery) ||
!test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags))
return -ENODEV;
@@ -665,6 +688,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery)
static int acpi_battery_init_alarm(struct acpi_battery *battery)
{
+ lockdep_assert_held(&battery->property_lock);
+
/* See if alarms are supported, and if so, set default */
if (!acpi_has_method(battery->device->handle, "_BTP")) {
clear_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags);
@@ -682,6 +707,8 @@ static ssize_t acpi_battery_alarm_show(struct device *dev,
{
struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev));
+ guard(mutex)(&battery->property_lock);
+
return sysfs_emit(buf, "%d\n", battery->alarm * 1000);
}
@@ -697,6 +724,8 @@ static ssize_t acpi_battery_alarm_store(struct device *dev,
if (err)
return err;
+ guard(mutex)(&battery->property_lock);
+
battery->alarm = x / 1000;
if (acpi_battery_present(battery))
acpi_battery_set_alarm(battery);
@@ -881,12 +910,17 @@ static int sysfs_add_battery(struct acpi_battery *battery)
.no_wakeup_source = true,
};
bool full_cap_broken = false;
+ int power_unit;
- if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) &&
- !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity))
- full_cap_broken = true;
+ scoped_guard(mutex, &battery->property_lock) {
+ power_unit = battery->power_unit;
- if (battery->power_unit == ACPI_BATTERY_POWER_UNIT_MA) {
+ if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) &&
+ !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity))
+ full_cap_broken = true;
+ }
+
+ if (power_unit == ACPI_BATTERY_POWER_UNIT_MA) {
if (full_cap_broken) {
battery->bat_desc.properties =
charge_battery_full_cap_broken_props;
@@ -940,6 +974,9 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
static void find_battery(const struct dmi_header *dm, void *private)
{
struct acpi_battery *battery = (struct acpi_battery *)private;
+
+ lockdep_assert_held(&battery->property_lock);
+
/* Note: the hardcoded offsets below have been extracted from
* the source code of dmidecode.
*/
@@ -971,6 +1008,8 @@ static void find_battery(const struct dmi_header *dm, void *private)
*/
static void acpi_battery_quirks(struct acpi_battery *battery)
{
+ lockdep_assert_held(&battery->property_lock);
+
if (test_bit(ACPI_BATTERY_QUIRK_PERCENTAGE_CAPACITY, &battery->flags))
return;
@@ -1023,30 +1062,38 @@ static void acpi_battery_quirks(struct acpi_battery *battery)
static int acpi_battery_update(struct acpi_battery *battery, bool resume)
{
int result = acpi_battery_get_status(battery);
+ bool wakeup;
if (result)
return result;
if (!acpi_battery_present(battery)) {
sysfs_remove_battery(battery);
- battery->update_time = 0;
+ scoped_guard(mutex, &battery->property_lock)
+ battery->update_time = 0;
return 0;
}
if (resume)
return 0;
- if (!battery->update_time) {
- result = acpi_battery_get_info(battery);
+ scoped_guard(mutex, &battery->property_lock) {
+ if (!battery->update_time) {
+ result = acpi_battery_get_info(battery);
+ if (result)
+ return result;
+ acpi_battery_init_alarm(battery);
+ }
+
+ result = acpi_battery_get_state(battery);
if (result)
return result;
- acpi_battery_init_alarm(battery);
- }
+ acpi_battery_quirks(battery);
- result = acpi_battery_get_state(battery);
- if (result)
- return result;
- acpi_battery_quirks(battery);
+ wakeup = ((battery->state & ACPI_BATTERY_STATE_CRITICAL) ||
+ (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) &&
+ (battery->capacity_now <= battery->alarm)));
+ }
if (!battery->bat) {
result = sysfs_add_battery(battery);
@@ -1058,9 +1105,7 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume)
* Wakeup the system if battery is critical low
* or lower than the alarm level
*/
- if ((battery->state & ACPI_BATTERY_STATE_CRITICAL) ||
- (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) &&
- (battery->capacity_now <= battery->alarm)))
+ if (wakeup)
acpi_pm_wakeup_event(battery->phys_dev);
return result;
@@ -1073,12 +1118,14 @@ static void acpi_battery_refresh(struct acpi_battery *battery)
if (!battery->bat)
return;
- power_unit = battery->power_unit;
+ scoped_guard(mutex, &battery->property_lock) {
+ power_unit = battery->power_unit;
- acpi_battery_get_info(battery);
+ acpi_battery_get_info(battery);
- if (power_unit == battery->power_unit)
- return;
+ if (power_unit == battery->power_unit)
+ return;
+ }
/* The battery has changed its reporting units. */
sysfs_remove_battery(battery);
@@ -1170,17 +1217,21 @@ static int battery_notify(struct notifier_block *nb,
} else {
int result;
- result = acpi_battery_get_info(battery);
- if (result)
- return result;
+ scoped_guard(mutex, &battery->property_lock) {
+ result = acpi_battery_get_info(battery);
+ if (result)
+ return result;
+ }
result = sysfs_add_battery(battery);
if (result)
return result;
}
- acpi_battery_init_alarm(battery);
- acpi_battery_get_state(battery);
+ scoped_guard(mutex, &battery->property_lock) {
+ acpi_battery_init_alarm(battery);
+ acpi_battery_get_state(battery);
+ }
}
return 0;
@@ -1345,6 +1396,10 @@ static int acpi_battery_probe(struct platform_device *pdev)
if (result)
return result;
+ result = devm_mutex_init(&pdev->dev, &battery->property_lock);
+ if (result)
+ return result;
+
if (acpi_has_method(battery->device->handle, "_BIX"))
set_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags);
---
base-commit: 92ec461acad4a92722634aafab38cfd3236e884d
change-id: 20260520-b4-acpi-battery-notification-90d781a3f217
Thanks,
Rong
On Sun, Aug 9, 2026 at 1:44 AM Rong Zhang <i@rong.moe> wrote:
>
> The acpi_battery_get_property() callback calls acpi_battery_get_state()
> without any lock held. On some devices, it happens that the property
> cache has expired before a uevent reaches userspace, triggering
> simultaneous attempts to evaluate _BST. See [1] for an analysis to sysrq
> stacktraces on one of the these devices.
>
> In a few cases, including when the AML is sleeping or acquiring a mutex,
> ACPICA drops the namespace and interpreter locks and allows the
> evaluation of _BST to start while another task is still evaluating it.
> This could somehow confuse the interpreter and lead to chaos in AML
> mutexes on some devices, see [2] for an example.
>
> Not holding the lock is also prone to race conditions, for example:
>
> CPU0 | CPU1
> acpi_battery_get_property() |
> acpi_battery_get_state() |
> [update_time expired] |
> extract_package() | acpi_battery_get_property()
> battery->update_time = jiffies | acpi_battery_get_state()
> kfree() | [up to date]
> | [read capacity_now]
> [fix capacity_now due to quirk] |
>
> where CPU1 gets raw capacity_now before CPU0 fixes it to a meaningful
> value.
>
> The existing mutex update_lock is not applicapable for
> acpi_battery_get_property(), as some code path could call or wait for
> acpi_battery_get_property() while holding update_lock.
>
> Therefore, introduce a mutex called property_lock to protect all
> accesses to battery properties, so that acpi_battery_get_property() can
> take the advantage of the mutex and synchronize itself. With the mutex,
> acpi_battery_get_state() are synchronized in all code paths calling it,
> and its cache mechanism can always clamp the frequency of _BST
> evaluations according to cache_time.
>
> The helper function acpi_battery_handle_discharging() for quirky devices
> has to be inlined due to the change, as the mutex must be unlocked
> before calling the expensive power_supply_is_system_supplied() helper
> function.
>
> Fixes: 86bfd21a0baf ("ACPI: battery: Drop redundant locking")
> Tested-by: Avraham Hollander <anhollander516@gmail.com>
> Reported-by: Rick <rickk1166@gmail.com>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221065#c85 [1]
> Reported-by: Avraham Hollander <anhollander516@gmail.com>
> Closes: https://lore.kernel.org/linux-acpi/CAP1mzZReJCn6df5DwEPu-JCQUyr=Pu1cg5xKCMttWZkHCQtVmQ@mail.gmail.com [2]
> Signed-off-by: Rong Zhang <i@rong.moe>
Applied as 7.3 material, thanks!
> ---
> Changes in v5:
> - Reword commit message (thanks Rafael J. Wysocki)
> - Rebase onto linux-pm after other patches in the series have been
> applied
> - Link to v4: https://patch.msgid.link/20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe
>
> Changes in v4:
> - Rebase and adopt devres-based resource management
> - Refactor acpi_battery_notify() to hold the mutex across the entire
> function to improve readability and drop unnecessary variables (thanks
> Rafael J. Wysocki)
> - Link to v3: https://patch.msgid.link/20260611-b4-acpi-battery-notification-v3-0-f9390382c5a4@rong.moe
>
> Changes in v3:
> - Address Sashiko's concerns on my last-minute changes:
> - Set the number base to 10 in order not to break the ABI
> - Do not overwrite the initial value of `ret' in
> acpi_battery_get_property()
> - https://sashiko.dev/#/patchset/20260611-b4-acpi-battery-notification-v2-0-4e8ed651a151%40rong.moe
> - Link to v2: https://patch.msgid.link/20260611-b4-acpi-battery-notification-v2-0-4e8ed651a151@rong.moe
>
> Changes in v2:
> - Address Sashiko's concerns:
> - Return from acpi_battery_notification_worker() early when the fifo
> is empty
> - Use pr_err_ratelimited() for potential event storms
> - Add missing `\n' in a printk message
> - Use a separated mutex to protect all properties instead of reusing
> update_lock
> - https://sashiko.dev/#/patchset/20260527-b4-acpi-battery-notification-v1-0-2303bed8ec0b%40rong.moe
> - Minimalize the critical section of acpi_battery_notify()
> - Rearrange the series
> - Dropped Tested-by from patch 3 due to massive rewrite
> - Link to v1: https://patch.msgid.link/20260527-b4-acpi-battery-notification-v1-0-2303bed8ec0b@rong.moe
> ---
> drivers/acpi/battery.c | 147 +++++++++++++++++++++++++++++++++----------------
> 1 file changed, 101 insertions(+), 46 deletions(-)
>
> diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
> index 0084f308b790..670853ec3a4d 100644
> --- a/drivers/acpi/battery.c
> +++ b/drivers/acpi/battery.c
> @@ -17,6 +17,7 @@
> #include <linux/kernel.h>
> #include <linux/kfifo.h>
> #include <linux/list.h>
> +#include <linux/lockdep.h>
> #include <linux/module.h>
> #include <linux/mutex.h>
> #include <linux/platform_device.h>
> @@ -105,6 +106,9 @@ struct acpi_battery {
> struct delayed_work acpi_notif_dwork;
> struct notifier_block pm_nb;
> struct list_head list;
> + unsigned long flags;
> +
> + struct mutex property_lock; /* Protects properties below. */
> unsigned long update_time;
> int revision;
> int rate_now;
> @@ -131,7 +135,6 @@ struct acpi_battery {
> char oem_info[MAX_STRING_LENGTH];
> int state;
> int power_unit;
> - unsigned long flags;
> };
>
> #define to_acpi_battery(x) power_supply_get_drvdata(x)
> @@ -189,20 +192,6 @@ static bool acpi_battery_is_degraded(struct acpi_battery *battery)
> battery->full_charge_capacity < battery->design_capacity;
> }
>
> -static int acpi_battery_handle_discharging(struct acpi_battery *battery)
> -{
> - /*
> - * Some devices wrongly report discharging if the battery's charge level
> - * was above the device's start charging threshold atm the AC adapter
> - * was plugged in and the device thus did not start a new charge cycle.
> - */
> - if ((battery_ac_is_broken || power_supply_is_system_supplied()) &&
> - battery->rate_now == 0)
> - return POWER_SUPPLY_STATUS_NOT_CHARGING;
> -
> - return POWER_SUPPLY_STATUS_DISCHARGING;
> -}
> -
> static int acpi_battery_get_property(struct power_supply *psy,
> enum power_supply_property psp,
> union power_supply_propval *val)
> @@ -210,15 +199,41 @@ static int acpi_battery_get_property(struct power_supply *psy,
> int full_capacity = ACPI_BATTERY_VALUE_UNKNOWN, ret = 0;
> struct acpi_battery *battery = to_acpi_battery(psy);
>
> - if (acpi_battery_present(battery)) {
> - /* run battery update only if it is present */
> - acpi_battery_get_state(battery);
> - } else if (psp != POWER_SUPPLY_PROP_PRESENT)
> - return -ENODEV;
> + /* run battery update only if it is present */
> + if (!acpi_battery_present(battery)) {
> + switch (psp) {
> + case POWER_SUPPLY_PROP_PRESENT:
> + val->intval = 0;
> + return 0;
> + default:
> + return -ENODEV;
> + }
> + }
> +
> + mutex_lock(&battery->property_lock);
> +
> + acpi_battery_get_state(battery);
> +
> switch (psp) {
> case POWER_SUPPLY_PROP_STATUS:
> + /*
> + * Some devices wrongly report discharging if the battery's charge level
> + * was above the device's start charging threshold atm the AC adapter
> + * was plugged in and the device thus did not start a new charge cycle.
> + */
> if (battery->state & ACPI_BATTERY_STATE_DISCHARGING)
> - val->intval = acpi_battery_handle_discharging(battery);
> + if (battery->rate_now != 0) {
> + val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
> + } else if (battery_ac_is_broken) {
> + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
> + } else {
> + mutex_unlock(&battery->property_lock);
> +
> + val->intval = power_supply_is_system_supplied()
> + ? POWER_SUPPLY_STATUS_NOT_CHARGING
> + : POWER_SUPPLY_STATUS_DISCHARGING;
> + return 0;
> + }
> else if (battery->state & ACPI_BATTERY_STATE_CHARGING)
> /* Check the rate and capacity to validate the status. */
> if (!acpi_battery_is_full(battery) ||
> @@ -321,6 +336,8 @@ static int acpi_battery_get_property(struct power_supply *psy,
> default:
> ret = -EINVAL;
> }
> +
> + mutex_unlock(&battery->property_lock);
> return ret;
> }
>
> @@ -556,6 +573,8 @@ static int acpi_battery_get_info(struct acpi_battery *battery)
> int use_bix;
> int result = -ENODEV;
>
> + lockdep_assert_held(&battery->property_lock);
> +
> if (!acpi_battery_present(battery))
> return 0;
>
> @@ -595,6 +614,8 @@ static int acpi_battery_get_state(struct acpi_battery *battery)
> acpi_status status = 0;
> struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
>
> + lockdep_assert_held(&battery->property_lock);
> +
> if (!acpi_battery_present(battery))
> return 0;
>
> @@ -648,6 +669,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery)
> {
> acpi_status status = 0;
>
> + lockdep_assert_held(&battery->property_lock);
> +
> if (!acpi_battery_present(battery) ||
> !test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags))
> return -ENODEV;
> @@ -665,6 +688,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery)
>
> static int acpi_battery_init_alarm(struct acpi_battery *battery)
> {
> + lockdep_assert_held(&battery->property_lock);
> +
> /* See if alarms are supported, and if so, set default */
> if (!acpi_has_method(battery->device->handle, "_BTP")) {
> clear_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags);
> @@ -682,6 +707,8 @@ static ssize_t acpi_battery_alarm_show(struct device *dev,
> {
> struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev));
>
> + guard(mutex)(&battery->property_lock);
> +
> return sysfs_emit(buf, "%d\n", battery->alarm * 1000);
> }
>
> @@ -697,6 +724,8 @@ static ssize_t acpi_battery_alarm_store(struct device *dev,
> if (err)
> return err;
>
> + guard(mutex)(&battery->property_lock);
> +
> battery->alarm = x / 1000;
> if (acpi_battery_present(battery))
> acpi_battery_set_alarm(battery);
> @@ -881,12 +910,17 @@ static int sysfs_add_battery(struct acpi_battery *battery)
> .no_wakeup_source = true,
> };
> bool full_cap_broken = false;
> + int power_unit;
>
> - if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) &&
> - !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity))
> - full_cap_broken = true;
> + scoped_guard(mutex, &battery->property_lock) {
> + power_unit = battery->power_unit;
>
> - if (battery->power_unit == ACPI_BATTERY_POWER_UNIT_MA) {
> + if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) &&
> + !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity))
> + full_cap_broken = true;
> + }
> +
> + if (power_unit == ACPI_BATTERY_POWER_UNIT_MA) {
> if (full_cap_broken) {
> battery->bat_desc.properties =
> charge_battery_full_cap_broken_props;
> @@ -940,6 +974,9 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
> static void find_battery(const struct dmi_header *dm, void *private)
> {
> struct acpi_battery *battery = (struct acpi_battery *)private;
> +
> + lockdep_assert_held(&battery->property_lock);
> +
> /* Note: the hardcoded offsets below have been extracted from
> * the source code of dmidecode.
> */
> @@ -971,6 +1008,8 @@ static void find_battery(const struct dmi_header *dm, void *private)
> */
> static void acpi_battery_quirks(struct acpi_battery *battery)
> {
> + lockdep_assert_held(&battery->property_lock);
> +
> if (test_bit(ACPI_BATTERY_QUIRK_PERCENTAGE_CAPACITY, &battery->flags))
> return;
>
> @@ -1023,30 +1062,38 @@ static void acpi_battery_quirks(struct acpi_battery *battery)
> static int acpi_battery_update(struct acpi_battery *battery, bool resume)
> {
> int result = acpi_battery_get_status(battery);
> + bool wakeup;
>
> if (result)
> return result;
>
> if (!acpi_battery_present(battery)) {
> sysfs_remove_battery(battery);
> - battery->update_time = 0;
> + scoped_guard(mutex, &battery->property_lock)
> + battery->update_time = 0;
> return 0;
> }
>
> if (resume)
> return 0;
>
> - if (!battery->update_time) {
> - result = acpi_battery_get_info(battery);
> + scoped_guard(mutex, &battery->property_lock) {
> + if (!battery->update_time) {
> + result = acpi_battery_get_info(battery);
> + if (result)
> + return result;
> + acpi_battery_init_alarm(battery);
> + }
> +
> + result = acpi_battery_get_state(battery);
> if (result)
> return result;
> - acpi_battery_init_alarm(battery);
> - }
> + acpi_battery_quirks(battery);
>
> - result = acpi_battery_get_state(battery);
> - if (result)
> - return result;
> - acpi_battery_quirks(battery);
> + wakeup = ((battery->state & ACPI_BATTERY_STATE_CRITICAL) ||
> + (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) &&
> + (battery->capacity_now <= battery->alarm)));
> + }
>
> if (!battery->bat) {
> result = sysfs_add_battery(battery);
> @@ -1058,9 +1105,7 @@ static int acpi_battery_update(struct acpi_battery *battery, bool resume)
> * Wakeup the system if battery is critical low
> * or lower than the alarm level
> */
> - if ((battery->state & ACPI_BATTERY_STATE_CRITICAL) ||
> - (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) &&
> - (battery->capacity_now <= battery->alarm)))
> + if (wakeup)
> acpi_pm_wakeup_event(battery->phys_dev);
>
> return result;
> @@ -1073,12 +1118,14 @@ static void acpi_battery_refresh(struct acpi_battery *battery)
> if (!battery->bat)
> return;
>
> - power_unit = battery->power_unit;
> + scoped_guard(mutex, &battery->property_lock) {
> + power_unit = battery->power_unit;
>
> - acpi_battery_get_info(battery);
> + acpi_battery_get_info(battery);
>
> - if (power_unit == battery->power_unit)
> - return;
> + if (power_unit == battery->power_unit)
> + return;
> + }
>
> /* The battery has changed its reporting units. */
> sysfs_remove_battery(battery);
> @@ -1170,17 +1217,21 @@ static int battery_notify(struct notifier_block *nb,
> } else {
> int result;
>
> - result = acpi_battery_get_info(battery);
> - if (result)
> - return result;
> + scoped_guard(mutex, &battery->property_lock) {
> + result = acpi_battery_get_info(battery);
> + if (result)
> + return result;
> + }
>
> result = sysfs_add_battery(battery);
> if (result)
> return result;
> }
>
> - acpi_battery_init_alarm(battery);
> - acpi_battery_get_state(battery);
> + scoped_guard(mutex, &battery->property_lock) {
> + acpi_battery_init_alarm(battery);
> + acpi_battery_get_state(battery);
> + }
> }
>
> return 0;
> @@ -1345,6 +1396,10 @@ static int acpi_battery_probe(struct platform_device *pdev)
> if (result)
> return result;
>
> + result = devm_mutex_init(&pdev->dev, &battery->property_lock);
> + if (result)
> + return result;
> +
> if (acpi_has_method(battery->device->handle, "_BIX"))
> set_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags);
>
>
> ---
> base-commit: 92ec461acad4a92722634aafab38cfd3236e884d
> change-id: 20260520-b4-acpi-battery-notification-90d781a3f217
>
> Thanks,
> Rong
>
© 2016 - 2026 Red Hat, Inc.