From nobody Sat Jul 25 04:56:15 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 640633E3C48; Fri, 17 Jul 2026 23:11:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329880; cv=pass; b=kIB2Uh+ju0c/C3Zx2MZmDwausF0EiBTjLs2xKwUHV1tACmtNzJwzGGLiyMJB4ic1IXQHr+c7VOMPJ4N5otTSbwsITGNTddfTnY5G2J4VuzvI1wM8yqC0lFCLF562Bh1bLCA7nBvJr95qyGDcB6iuUsPeMxPXHcLg0pm2yYVxWjM= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329880; c=relaxed/simple; bh=ynOxoz3WVjYFWCPhkfa9TJr5/ZQQVNSQU/3KGb0z1NU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=a7yWJE5IGWGklseBNVusuXZfHiNjOlVO8WSQ1R1NBv4z1pfx8kBafWXMjh6GNF6l4P2gH69c+eu9hMrVtksJOOfJkkj8bMZQBAL5tCV8A43TKBJ2PEhA1IIjsbUN5ikczWXweKbzD07XXGV3HTP9lJsWoI3+5o8jCOJMJ3+clxQ= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=StDHzHJU; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="StDHzHJU" ARC-Seal: i=1; a=rsa-sha256; t=1784329854; cv=none; d=zohomail.com; s=zohoarc; b=dc0vxVwikRCsmnPlZR8nxyj4jQ04DntxZFEDJqS6AWBfNyVCuo7cEt6QRjDWFqge+DbQQmMaD7qB7MIM1oE6GOM4DKgffL4lo374x97EN9ftqrx6MzXXzjq8mTOmmXQklQEoztdih06ZPH9EBomFgcGMgN3H/grQT0XGHmu99XY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1784329854; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=cgtPPUNk0/oeGD2QhbJSBWvA4wFULY7bdt+FW7ztdt0=; b=Vv13ev9lRRF5urdXRJgpP1lwtzJhefCrsWvOLgUIoB1dxhfHy/bqcNaI4LWOqGZQyeE8LBZs6wjXXPrPbNsYg2pnOZTQe7IsCJPhVJ1+OjhrcpIG9uHQmL8y8H+ivpgnrUbveP5SqXoK2+jR/gR92VyH7Re9OrDBlwj+cb7BNKE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1784329854; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=cgtPPUNk0/oeGD2QhbJSBWvA4wFULY7bdt+FW7ztdt0=; b=StDHzHJU9tFrJEoVI6Psz2ZHcVBthOCTuGohMP9u1jERSCIBt4uvr1u2KLVpHUUl ZdKfCmXOo4FbziMCdHVDZxMhda9QZZ+muwbL2y3LiUksZyOpOXNXnUi2DjbZHpmaTUJ oSd4jlEVoqjb/pCVfSIjL/QcO86ZtgoGF1h9mM+hJz+9xEOsaCJsq3vy59E6b2gIhDA 0gWIpsmDgoqTqG6PYv3vIw6U7C8hgq4uXQN6WjyP4ncZtqqRSkskd8DjvRxxqx2c/vm lt24d4fM+74YLtkUqFEvsuFY4J4I+vHvM7czHD0B5cIyRJZst78diG114FpqUMwaeRS 3w0c5hOzZQ== Received: by mx.zohomail.com with SMTPS id 1784329848244512.1159116820515; Fri, 17 Jul 2026 16:10:48 -0700 (PDT) From: Rong Zhang Date: Sat, 18 Jul 2026 07:10:19 +0800 Subject: [PATCH v4 1/3] ACPI: battery: Merge consecutive battery notifications Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260718-b4-acpi-battery-notification-v4-1-599c8ed1072f@rong.moe> References: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> In-Reply-To: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> To: "Rafael J. Wysocki" , Len Brown Cc: "Rafael J. Wysocki" , Avraham Hollander , =?utf-8?q?Jeffrey_W=C3=A4lti?= , Rick , Mark Pearson , linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-4217c X-ZohoMailClient: External It's a very common pattern to emit consecutive battery notifications, for example: Method (_Qxx, 0, NotSerialized) { Notify (BAT0, 0x80) // Status Change Notify (BAT0, 0x81) // Information Change } In this case, the current code path will update battery state twice within a short period, which is not optimal, as the same data are fetched twice. Moreover, both notifications are likely to call power_supply_changed(), causing power_supply_uevent() to read all battery properties in order to assemble uevents. Even worse, after the first uevent reaches userspace, some userspace processes start to read all battery properties in order to refresh their internal states, which competes with the second notification's handling and uevent assembling. This generates significant pressure on _STA, _BST and _BIX/_BIF methods. Not only that, power_supply_ext properties may also rely on some other ACPI methods, so both uevent assembling and userspace processes call them. It becomes a nightmare when all these methods share the same ACPI mutex protecting EC accesses and hence vulnerable to lock starvation. This is exactly the case of some Lenovo devices, where the mentioned EC query pattern eventually leads to a catastrophic situation that a bunch of ACPI methods (including but not limited to the mentioned ones) fail to acquire the same mutex due to timeout. These devices don't handle mutex acquisition failure gracefully and return garbage data, causing even more chaos. Improve battery notification handling by merging at most 16 consecutive battery notifications within 10ms using a delayed work, so that they only refresh and/or update battery state once. ACPI netlink event and notifier call chain are still triggered multiple times in order not to break other components. Finally, call power_supply_changed() once and lead to a single uevent instead of a bunch, preventing userspace programs from causing too much pressure on power supply properties and underlying ACPI methods. If more than 16 battery notifications are queued within 10ms, the firmware/hardware is anyway buggy, and extra notifications will be dropped. Tested-by: Jeffrey W=C3=A4lti Tested-by: Avraham Hollander Reported-by: Rick Closes: https://bugzilla.kernel.org/show_bug.cgi?id=3D221065 Signed-off-by: Rong Zhang --- 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) 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 - https://sashiko.dev/#/patchset/20260527-b4-acpi-battery-notification-v1= -0-2303bed8ec0b%40rong.moe - Minimalize the critical section of acpi_battery_notify() --- drivers/acpi/battery.c | 89 +++++++++++++++++++++++++++++++++++++++++++++-= ---- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f5e0eb299610..8b26962745bd 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include =20 #include =20 @@ -43,6 +45,9 @@ =20 #define MAX_STRING_LENGTH 64 =20 +#define MAX_QUEUED_EVENTS 16 +#define NOTIF_MERGING_MS 10 + MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_AUTHOR("Alexey Starikovskiy "); MODULE_DESCRIPTION("ACPI Battery Driver"); @@ -95,6 +100,8 @@ struct acpi_battery { struct power_supply_desc bat_desc; struct acpi_device *device; struct device *phys_dev; + struct kfifo acpi_notif_fifo; + struct delayed_work acpi_notif_dwork; struct notifier_block pm_nb; struct list_head list; unsigned long update_time; @@ -1059,14 +1066,24 @@ static void acpi_battery_refresh(struct acpi_batter= y *battery) } =20 /* Driver Interface */ -static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +static void acpi_battery_notification_worker(struct work_struct *work) { - struct acpi_battery *battery =3D data; + struct acpi_battery *battery =3D container_of(work, struct acpi_battery, + acpi_notif_dwork.work); struct acpi_device *device =3D battery->device; + u32 events[MAX_QUEUED_EVENTS]; struct power_supply *old; + unsigned int count, i; =20 guard(mutex)(&battery->update_lock); =20 + count =3D kfifo_out(&battery->acpi_notif_fifo, events, sizeof(events)); + count /=3D sizeof(events[0]); + if (!count) + return; + + pr_debug("merged %u battery notifications within %dms\n", count, NOTIF_ME= RGING_MS); + old =3D battery->bat; /* * On Acer Aspire V5-573G notifications are sometimes triggered too @@ -1076,19 +1093,46 @@ static void acpi_battery_notify(acpi_handle handle,= u32 event, void *data) */ if (battery_notification_delay_ms > 0) msleep(battery_notification_delay_ms); - if (event =3D=3D ACPI_BATTERY_NOTIFY_INFO) - acpi_battery_refresh(battery); + + for (i =3D 0; i < count; i++) { + if (events[i] =3D=3D ACPI_BATTERY_NOTIFY_INFO) { + acpi_battery_refresh(battery); + break; + } + } + acpi_battery_update(battery, false); - acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, - dev_name(&device->dev), event, - acpi_battery_present(battery)); - acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), - event, acpi_battery_present(battery)); + + for (i =3D 0; i < count; i++) { + acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, + dev_name(&device->dev), events[i], + acpi_battery_present(battery)); + acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), + events[i], acpi_battery_present(battery)); + } + /* acpi_battery_update could remove power_supply object */ if (old && battery->bat) power_supply_changed(battery->bat); } =20 +static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +{ + struct acpi_battery *battery =3D data; + + guard(mutex)(&battery->update_lock); + + if (kfifo_avail(&battery->acpi_notif_fifo) >=3D sizeof(event)) { + kfifo_in(&battery->acpi_notif_fifo, &event, sizeof(event)); + schedule_delayed_work(&battery->acpi_notif_dwork, + msecs_to_jiffies(NOTIF_MERGING_MS)); + + return; + } + + 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) { @@ -1231,6 +1275,29 @@ static int devm_acpi_battery_update_retry(struct dev= ice *dev, return ret; } =20 +static void acpi_battery_notify_dwork_cleanup(void *data) +{ + struct acpi_battery *battery =3D data; + + cancel_delayed_work_sync(&battery->acpi_notif_dwork); + kfifo_free(&battery->acpi_notif_fifo); +} + +static int devm_acpi_battery_init_notify_dwork(struct device *dev, + struct acpi_battery *battery) +{ + int ret; + + INIT_DELAYED_WORK(&battery->acpi_notif_dwork, acpi_battery_notification_w= orker); + + ret =3D kfifo_alloc(&battery->acpi_notif_fifo, + MAX_QUEUED_EVENTS * sizeof(u32), GFP_KERNEL); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, acpi_battery_notify_dwork_cleanup, b= attery); +} + static int acpi_battery_probe(struct platform_device *pdev) { struct device *dev =3D &pdev->dev; @@ -1272,6 +1339,10 @@ static int acpi_battery_probe(struct platform_device= *pdev) if (result) return result; =20 + result =3D devm_acpi_battery_init_notify_dwork(dev, battery); + if (result) + return result; + result =3D devm_acpi_install_notify_handler(dev, ACPI_ALL_NOTIFY, acpi_battery_notify, battery); if (result) --=20 2.53.0 From nobody Sat Jul 25 04:56:15 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 732C23AF64F; Fri, 17 Jul 2026 23:11:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329891; cv=pass; b=Y4QiScHf5U9u68OaMtGDcWb+H+HYagPhnpX7y3lBV/HazIXqwMNBEfgjChFn9oaFAs1lFr+tKuau/nxHZITFIbdV6fquXt9qMjjoTGgRlu3dTT/00xtsj9qxP8PBE4RpKQEkBzVgx2XcZjY9zrI/94hAbulU7PqekbyA7EXzXrk= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329891; c=relaxed/simple; bh=flynSxm0F9vG14eV7QFBlYm5P6X39dVJxCUZVPEmb7M=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=PDd0LccDfCGkYJQo9ACu577+aK/lSqlJ3GUXA5cGiyQ6KfT4MGJ9xwn3cFEbB/u0oLozJR8oaXuPGgpAStixnvASDgHVd9vSxOzzKzou399q+gSJkNgmzGr5jLRWaVA4c3HTEye+FAlTnsDYUSWD3KvLM0fBKfYwOFzRm8lwta8= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=MXW8BHkY; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="MXW8BHkY" ARC-Seal: i=1; a=rsa-sha256; t=1784329855; cv=none; d=zohomail.com; s=zohoarc; b=irbs9ZJBzyDNsSPfqhv+W50+7kqaEs0FB8OWsQNWFWSK3AW3wUFJCP1SoREIGxcd1qverHxZS53NW5JwmXm3iaHd5En3YsY2cNkTQ7yoFX/ZFslHwQOWZM/qoN8uIcZ69dKFeWjiGPQunbbiwx4wo3yZBVc/UfjDeum6NHFHTv8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1784329855; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=xpF/qpi/SnA6F3Ft/nufv4BuYqGks84R6qq9ciJTc48=; b=Z4uPxdjfpczLQyVdjbXSxVoJ1bPmcZ1K7LbhQfmUNvzMxkPY62fxcdoJhKksJ1znlkqFALVfFYfA1v7HTjbgqQb1ol0PmFdbUe+h71EzGAilxuBjzouoOYK4io2VtDNaU3qOb6aki1HaPeucKuXzWkMwfBIuqsnc6MqZfvlJTks= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1784329855; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=xpF/qpi/SnA6F3Ft/nufv4BuYqGks84R6qq9ciJTc48=; b=MXW8BHkYvrJPiFuL2QIXm/GXZpcuQh5Acvgf834wgt4vc1U4/NIsF2Z11W8Cu9ff is5t4deBMnLOgVcZZOWZZsHlQlfzUdUWWZlzqp9OKdX74c4/JA5zplQE7UtiafVx4jk FhsFEgXkfgqI3C9TdQqz+94d6v7edEAII/IVKBejOXQug1eo9PmbTa54GSVuE8wIzFp fdu2KhAK2hwBZooVt/5atOzX9owwNT/p9IYns2A8RLBV28CHFxaBccCZ6I1e50mr7dD +6FD2wa50z/rUxxMmFHL9iBkJ/n6bxJJ4E2yfxzz5p2vv4dDIde+2Jfon05jBm2d7wG stw2ypkSaQ== Received: by mx.zohomail.com with SMTPS id 1784329851681899.8125394572486; Fri, 17 Jul 2026 16:10:51 -0700 (PDT) From: Rong Zhang Date: Sat, 18 Jul 2026 07:10:20 +0800 Subject: [PATCH v4 2/3] ACPI: battery: Use kstrtoul() over sscanf("%lu\n") Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260718-b4-acpi-battery-notification-v4-2-599c8ed1072f@rong.moe> References: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> In-Reply-To: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> To: "Rafael J. Wysocki" , Len Brown Cc: "Rafael J. Wysocki" , Avraham Hollander , =?utf-8?q?Jeffrey_W=C3=A4lti?= , Rick , Mark Pearson , linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-4217c X-ZohoMailClient: External It is more preferred to use kstrto*() to parse a single number. The function family properly returns an errno on error and is the correct mechanism to parse data from sysfs. The number base is set to 10 in order not to break the ABI. Tested-by: Avraham Hollander Signed-off-by: Rong Zhang Reported-by: Rick --- 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 - https://sashiko.dev/#/patchset/20260611-b4-acpi-battery-notification-v2= -0-4e8ed651a151%40rong.moe Changes in v2: - New patch in series - Since the series touches acpi_battery_alarm_store(), also convert the use of sscanf("%lu\n") into the more preferred kstrtoul() beforehand --- drivers/acpi/battery.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 8b26962745bd..bd7fa93ff16f 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -675,9 +675,13 @@ static ssize_t acpi_battery_alarm_store(struct device = *dev, { unsigned long x; struct acpi_battery *battery =3D to_acpi_battery(dev_get_drvdata(dev)); + int err; =20 - if (sscanf(buf, "%lu\n", &x) =3D=3D 1) - battery->alarm =3D x/1000; + err =3D kstrtoul(buf, 10, &x); + if (err) + return err; + + battery->alarm =3D x / 1000; if (acpi_battery_present(battery)) acpi_battery_set_alarm(battery); return count; --=20 2.53.0 From nobody Sat Jul 25 04:56:15 2026 Received: from sender4-op-o15.zoho.com (sender4-op-o15.zoho.com [136.143.188.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1330D3AF64F; Fri, 17 Jul 2026 23:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=pass smtp.client-ip=136.143.188.15 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329904; cv=pass; b=j59VZb5EHAs/eRmp47dNE4Gfvj4tbs+rNlDdXuc7QgnkhUpdz8rPxpSygqyDFK/HmGs/DxontS+bE12hN3A63jDQjARy8XauGCvJ7Z92mZLLenVW6BudvuGlt5g/T4j4OgoLpDyayB9pAPsT9/4eficBR1SobPi66lrMDcsNaAI= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784329904; c=relaxed/simple; bh=PnzJtAhDdzKNYBwJLj+RiCWoGtHxMGOj+NkSllUQAnc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=jWaYcihyZVKHeCSmUT+Ll8OqZB3juIHk7bpH6+BlTJAUNJouXiVzXFx3x+VwpTAORYVx0trOfYGrJgKqVww1ELpBlNRQku/aqgLsXxcJ5jwIA6oxnA2ZeQFB9kR7Y7GNYpkRuIMCZu1aMNwd4ex1ndINwXj0oNYLsNb5WTZLJvw= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe; spf=pass smtp.mailfrom=rong.moe; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b=mGSbTdJL; arc=pass smtp.client-ip=136.143.188.15 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=rong.moe Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=rong.moe Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=rong.moe header.i=i@rong.moe header.b="mGSbTdJL" ARC-Seal: i=1; a=rsa-sha256; t=1784329859; cv=none; d=zohomail.com; s=zohoarc; b=EVefwslzrcaIXt6JWszB0bVDHzvvI579NWrKyL+BIQdkSLquSaoQQYTSPSSAH0vmJnFkeYiHxmUymlSi7sPp25Rg5efpfkinVhGlc6yeVRTpXzbKgWCVPCHjn9A1MhsljZ8YEDtB8gzQdSYPBVkgcRCDDi/GOvHwcE7cGu6IBj0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1784329859; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=F55ovocLE6+UjldktEfBoe4z0RCR2uJwgCDHUTTEYD4=; b=n6ZRNkmG4++z53txwF9XWNMYEcrn7TQuZSKZqJjKeW+E5eYFB4AfxANgA6sT8PT1iIFw9crtIp2KbWVNOYk3IElml4nrWxKRs2cPeBcf7wljWuB4+8/gSRoIF5+jTMTHtviXDRbrr0e9ak/V7c5x6ZhM9jq32Itd1dUcbEPDj4M= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=rong.moe; spf=pass smtp.mailfrom=i@rong.moe; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1784329859; s=zmail2048; d=rong.moe; i=i@rong.moe; h=From:From:Date:Date:Subject:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Message-Id:In-Reply-To:To:To:Cc:Cc:Reply-To; bh=F55ovocLE6+UjldktEfBoe4z0RCR2uJwgCDHUTTEYD4=; b=mGSbTdJLKJwe8QZLMyFYjHpwmazSLbGptLGuE2csQEVcw02B+NrT3CPza16Rog+b Rm+vxaQxJ0OV+0JuP5HkEgZzj6cpP0ZfZWJVroMSzcKc0QmpIYyS6/kheHPAsWBX7CL iuhNdH3gWqY8G5y0bXlubB4Q0Uk/2gcAJdql6SKFX8PFxbHLwGtQLXCJ2iDMMvacrBa AxjtBVx9yYrw7HblRal2VECkMeV9bgM5txrtUEYEKFwI+xqUBkCKTOWCdQyxSZZVLAu LesXpUgiLpmp3qIqiZfGeYkBGzIX49Il2v1Qwj8dbL0bFOrzf5UANIhayRMrVaWaW3U F4OHVvKc/Q== Received: by mx.zohomail.com with SMTPS id 1784329855640560.6243003999492; Fri, 17 Jul 2026 16:10:55 -0700 (PDT) From: Rong Zhang Date: Sat, 18 Jul 2026 07:10:21 +0800 Subject: [PATCH v4 3/3] ACPI: battery: Protect all properties with a separated mutex Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260718-b4-acpi-battery-notification-v4-3-599c8ed1072f@rong.moe> References: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> In-Reply-To: <20260718-b4-acpi-battery-notification-v4-0-599c8ed1072f@rong.moe> To: "Rafael J. Wysocki" , Len Brown Cc: "Rafael J. Wysocki" , Avraham Hollander , =?utf-8?q?Jeffrey_W=C3=A4lti?= , Rick , Mark Pearson , linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Rong Zhang X-Mailer: b4 0.16-dev-4217c X-ZohoMailClient: External The acpi_battery_get_property() callback calls acpi_battery_get_state() without any lock held, which could lead to race conditions, e.g., when multiple tasks read power supply properties simultaneously, or when other callbacks are called during its execution. Moreover, some devices' _BST method relies on a heavily shared ACPI mutex which protects EC accesses, so it cannot tolerate too much pressure or else other methods will time out. The lack of synchronization sometimes nullifies the cache mechanism of acpi_battery_get_state() when multiple processes read power supply properties simultaneously, which usually happens after a uevent. Normally, emitting a uevent implies that the cache must have been refreshed due to power_supply_uevent() reading all properties, so the mentioned processes should have seen cache hits. Unfortunately, these fragile devices' power_supply_ext properties are somehow slow to read after battery events, resulting in cache expiration before power_supply_uevent() finishes. Hence, once the uevent reaches userspace, the _BST method will be executed multiple times within a short period due to userspace processes reading all properties again. The coincidence causes lock starvation, resulting in a catastrophic situation that a lot of ACPI methods fail to acquire the shared ACPI mutex due to timeout and return garbage data thanks to the firmware's poorly designed error paths. The said "other synchronized methods" are protected by update_lock, leaving acpi_battery_get_property() to be the last desynchronized code path. Unfortunately, update_lock is not applicapable for acpi_battery_get_property(), as it protects too many fields, far more than necessary. What's worse, some code path could call or wait for acpi_battery_get_property() while an outer functions holding update_lock. Therefore, introduce a mutex to protect all accesses to battery properties, so that acpi_battery_get_property() can take the advantage of the mutex and synchronize itself. 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. Tested-by: Avraham Hollander Reported-by: Rick Closes: https://bugzilla.kernel.org/show_bug.cgi?id=3D221065 Signed-off-by: Rong Zhang --- Changes in v3: - Address Sashiko's concerns on my last-minute changes: - 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 Changes in v2: - Address Sashiko's concerns: - 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 - Dropped Tested-by due to massive rewrite --- 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 bd7fa93ff16f..3ad4acf3d44d 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,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; @@ -130,7 +134,6 @@ struct acpi_battery { char oem_info[MAX_STRING_LENGTH]; int state; int power_unit; - unsigned long flags; }; =20 #define to_acpi_battery(x) power_supply_get_drvdata(x) @@ -187,20 +190,6 @@ static bool acpi_battery_is_degraded(struct acpi_batte= ry *battery) battery->full_charge_capacity < battery->design_capacity; } =20 -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 =3D=3D 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) @@ -208,15 +197,41 @@ static int acpi_battery_get_property(struct power_sup= ply *psy, int full_capacity =3D ACPI_BATTERY_VALUE_UNKNOWN, ret =3D 0; struct acpi_battery *battery =3D to_acpi_battery(psy); =20 - if (acpi_battery_present(battery)) { - /* run battery update only if it is present */ - acpi_battery_get_state(battery); - } else if (psp !=3D 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 =3D 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 =3D acpi_battery_handle_discharging(battery); + if (battery->rate_now !=3D 0) { + val->intval =3D POWER_SUPPLY_STATUS_DISCHARGING; + } else if (battery_ac_is_broken) { + val->intval =3D POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + mutex_unlock(&battery->property_lock); + + val->intval =3D power_supply_is_system_supplied() + ? POWER_SUPPLY_STATUS_NOT_CHARGING + : POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } else if (battery->state & ACPI_BATTERY_STATE_CHARGING) /* Validate the status by checking the current. */ if (battery->rate_now !=3D ACPI_BATTERY_VALUE_UNKNOWN && @@ -318,6 +333,8 @@ static int acpi_battery_get_property(struct power_suppl= y *psy, default: ret =3D -EINVAL; } + + mutex_unlock(&battery->property_lock); return ret; } =20 @@ -540,6 +557,8 @@ static int acpi_battery_get_info(struct acpi_battery *b= attery) int use_bix; int result =3D -ENODEV; =20 + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery)) return 0; =20 @@ -579,6 +598,8 @@ static int acpi_battery_get_state(struct acpi_battery *= battery) acpi_status status =3D 0; struct acpi_buffer buffer =3D { ACPI_ALLOCATE_BUFFER, NULL }; =20 + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery)) return 0; =20 @@ -632,6 +653,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *= battery) { acpi_status status =3D 0; =20 + lockdep_assert_held(&battery->property_lock); + if (!acpi_battery_present(battery) || !test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags)) return -ENODEV; @@ -649,6 +672,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *= battery) =20 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); @@ -666,6 +691,8 @@ static ssize_t acpi_battery_alarm_show(struct device *d= ev, { struct acpi_battery *battery =3D to_acpi_battery(dev_get_drvdata(dev)); =20 + guard(mutex)(&battery->property_lock); + return sysfs_emit(buf, "%d\n", battery->alarm * 1000); } =20 @@ -681,6 +708,8 @@ static ssize_t acpi_battery_alarm_store(struct device *= dev, if (err) return err; =20 + guard(mutex)(&battery->property_lock); + battery->alarm =3D x / 1000; if (acpi_battery_present(battery)) acpi_battery_set_alarm(battery); @@ -865,12 +894,17 @@ static int sysfs_add_battery(struct acpi_battery *bat= tery) .no_wakeup_source =3D true, }; bool full_cap_broken =3D false; + int power_unit; =20 - if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) && - !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity)) - full_cap_broken =3D true; + scoped_guard(mutex, &battery->property_lock) { + power_unit =3D battery->power_unit; =20 - if (battery->power_unit =3D=3D ACPI_BATTERY_POWER_UNIT_MA) { + if (!ACPI_BATTERY_CAPACITY_VALID(battery->full_charge_capacity) && + !ACPI_BATTERY_CAPACITY_VALID(battery->design_capacity)) + full_cap_broken =3D true; + } + + if (power_unit =3D=3D ACPI_BATTERY_POWER_UNIT_MA) { if (full_cap_broken) { battery->bat_desc.properties =3D charge_battery_full_cap_broken_props; @@ -924,6 +958,9 @@ static void sysfs_remove_battery(struct acpi_battery *b= attery) static void find_battery(const struct dmi_header *dm, void *private) { struct acpi_battery *battery =3D (struct acpi_battery *)private; + + lockdep_assert_held(&battery->property_lock); + /* Note: the hardcoded offsets below have been extracted from * the source code of dmidecode. */ @@ -955,6 +992,8 @@ static void find_battery(const struct dmi_header *dm, v= oid *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; =20 @@ -1007,30 +1046,38 @@ static void acpi_battery_quirks(struct acpi_battery= *battery) static int acpi_battery_update(struct acpi_battery *battery, bool resume) { int result =3D acpi_battery_get_status(battery); + bool wakeup; =20 if (result) return result; =20 if (!acpi_battery_present(battery)) { sysfs_remove_battery(battery); - battery->update_time =3D 0; + scoped_guard(mutex, &battery->property_lock) + battery->update_time =3D 0; return 0; } =20 if (resume) return 0; =20 - if (!battery->update_time) { - result =3D acpi_battery_get_info(battery); + scoped_guard(mutex, &battery->property_lock) { + if (!battery->update_time) { + result =3D acpi_battery_get_info(battery); + if (result) + return result; + acpi_battery_init_alarm(battery); + } + + result =3D acpi_battery_get_state(battery); if (result) return result; - acpi_battery_init_alarm(battery); - } + acpi_battery_quirks(battery); =20 - result =3D acpi_battery_get_state(battery); - if (result) - return result; - acpi_battery_quirks(battery); + wakeup =3D ((battery->state & ACPI_BATTERY_STATE_CRITICAL) || + (test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags) && + (battery->capacity_now <=3D battery->alarm))); + } =20 if (!battery->bat) { result =3D sysfs_add_battery(battery); @@ -1042,9 +1089,7 @@ static int acpi_battery_update(struct acpi_battery *b= attery, 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 <=3D battery->alarm))) + if (wakeup) acpi_pm_wakeup_event(battery->phys_dev); =20 return result; @@ -1057,12 +1102,14 @@ static void acpi_battery_refresh(struct acpi_batter= y *battery) if (!battery->bat) return; =20 - power_unit =3D battery->power_unit; + scoped_guard(mutex, &battery->property_lock) { + power_unit =3D battery->power_unit; =20 - acpi_battery_get_info(battery); + acpi_battery_get_info(battery); =20 - if (power_unit =3D=3D battery->power_unit) - return; + if (power_unit =3D=3D battery->power_unit) + return; + } =20 /* The battery has changed its reporting units. */ sysfs_remove_battery(battery); @@ -1154,17 +1201,21 @@ static int battery_notify(struct notifier_block *nb, } else { int result; =20 - result =3D acpi_battery_get_info(battery); - if (result) - return result; + scoped_guard(mutex, &battery->property_lock) { + result =3D acpi_battery_get_info(battery); + if (result) + return result; + } =20 result =3D sysfs_add_battery(battery); if (result) return result; } =20 - 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); + } } =20 return 0; @@ -1329,6 +1380,10 @@ static int acpi_battery_probe(struct platform_device= *pdev) if (result) return result; =20 + result =3D 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); =20 --=20 2.53.0