Framework 13 (Ryzen AI 300): intermittent corrupt battery readings from the EC trigger false "battery critical" / critical-action events

Framework 13 (Ryzen AI 300): intermittent corrupt battery readings from the EC trigger false “battery critical” / critical-action events

Summary

On battery, the ACPI battery status (_BST) intermittently returns corrupt data for one or more consecutive reads: charge fields missing or negative, capacity values like 468376%, discharge current of −65 A. Linux reports these as-is, and UPower turns them into 0% with WarningLevel “action”, the level at which the configured critical power action (hibernate / hybrid sleep / power off) is taken. Most events last 1–2 s, short enough that the action has not fired yet, but one lasted at least 30 s. Eight events have been captured since 2026-09-29, and the rate is rising (four or five on 2026-10-02 alone).

System

Model Framework Laptop 13 (AMD Ryzen AI 300 Series), board A9
CPU AMD Ryzen AI 9 HX 370
BIOS 03.05 (2025-10-30), the latest stable
EC lilac-3.0.5-9010bdf (RO and RW), built 2025-10-30
Battery NVT FRANGWA (61 Wh design), 137 cycles, full charge 3.57 Ah (≈91% of design)
OS Pop!_OS 24.04, kernel 7.1.5, UPower via the ACPI battery driver (BAT1, PNP0C0A)

How it was captured

A logging service records every kernel power_supply uevent for BAT1 (about 1 per second while discharging, otherwise a direct sysfs read every 60 s), every UPower change, and kernel messages from the EC drivers. Values below are exactly what the kernel exposed (µAh, µA, µV); ? means the field was absent from the uevent, i.e. the firmware reported it as unknown.

Captured events (all while discharging, no charger connected)

Time (local) Actual charge Corrupt reading UPower result
2026-09-29 13:14:27 74% capacity=-1376499853 charge_now=-170000, voltage absent, level Low 4.7%, WarningLevel 4 (critical); notification shown
2026-09-29 20:46:53 36% capacity=468376 charge_now=-402654184 current=2000 100% for 30 s
2026-10-01 18:26:01 38% capacity and charge_now absent, level Low 0%, WarningLevel 5 (action)
2026-10-02 11:50:02 96% capacity/charge_now absent, current_now=-65023000, level Low 0%, action
2026-10-02 11:51:19 96% level Critical with current=1000 voltage=17000000 (suspiciously round) —
2026-10-02 12:31:58 95% capacity/charge_now absent, level Low 0%, action
2026-10-02 12:41:19 92% level Critical, current_now absent —
2026-10-02 14:59:02 95% capacity/charge_now absent, current_now=-64663000, level Low 0%, action
2026-10-02 19:59:11 96% charge_now = charge_full (100%) for one sample 100%

Representative raw samples (one-second spacing):

2026-10-02 11:50:01.600  cap=96  lvl=Normal  now=3440000  full=3569000  cur=516000     volt=16991000
2026-10-02 11:50:02.860  cap=?   lvl=Low     now=?        full=3569000  cur=-65023000  volt=16975000
2026-10-02 11:50:04      back to cap=96

2026-09-29 20:46:23.382  cap=36      now=1292000     full=3582000  cur=458000  volt=14977000
2026-09-29 20:46:53.492  cap=468376  now=-402654184  full=3582000  cur=2000    volt=14958000
2026-09-29 20:47:23      back to cap=36

Note that charge_full is never affected, and the voltage is usually sane: the corruption appears to hit some fields of the _BST package and not others.

Related EC symptoms on the same machine

  • cros_ec_lpcs FRMWC004:00: packet too long (65535 bytes, expected 0) and bad packet checksum ff, intermittently since at least 2026-09-18. One occurred at 11:45:11, five minutes before the 11:50 event; most events have no EC error nearby.
  • workqueue: power_supply_changed_work hogged CPU for >13333us N times, climbing within a boot: 131 by 2026-09-27, 259 by 2026-10-02.
  • ucsi_acpi USBC000:00: unknown error 256 several times a day.
  • 2026-10-02: ACPI: EC: EC stopped / EC started at 08:17 (around a resume) and ACPI: EC: interrupt blocked / unblocked at 19:19.
  • 2026-09-27: the laptop lost power while the gauge reported 2%. The next boot found the RTC reset to 2025-01-01 and spent 36 s in firmware, against 7–9 s normally. A one-off, but it may be relevant to gauge accuracy near empty.

Outside these events the gauge is accurate. A full awake discharge on 2026-09-30 (100% → 11%) integrated to 48.7 Wh from current × voltage, against 49.3 Wh by the gauge, so the problem is transient corrupt reads, not drift.

Impact

  • False “Battery Critical” desktop notifications at 74–96% charge.
  • UPower reaching WarningLevel action at 95–96% charge. Its critical action (here HybridSleep) is only avoided because most events end within a second or two. The 30-second event shows they can last long enough to trigger it.
  • There is no clean OS-side mitigation: UPower cannot be told to distrust a sample, and disabling the critical action removes real low-battery protection.

Questions

  1. Is this a known EC issue on lilac / Ryzen AI 300, and is it fixed or being worked on in a newer EC build?
  2. Is anything in the host’s EC traffic (UCSI / PD polling, the cros_ec host-command channel) known to collide with _BSTreads?
  3. What extra data would help? The logger can capture more around each event, e.g. ectool battery output, if that’s useful.

Full logs available on request.

Going to go out on a limb and say this post was mostly or almost entirely written by AI.

Reading through some of the language that was put together it is clear whomever composed the message did not realize the some of what happens in the real world vs what is reported in logging.

Take for example that a specific point was made somewhere in the middle of it of the actual Wh based on a reported value vs. the calculable total over time from the log of power consumption. Take a closer look at it is I think in the region of a 0.6 difference ? Really want to nitpick over something as silly as this?

Not trying to pick apart this post specifically, anyone posting something written or supplemented with AI should probably read over it with a fine toothed comb to make sure it even makes sense to a regular person.

Blasting a bunch of text and tables/charts with context that doesn’t even the OP does not recognize or fully understand should be a flag to probably not include it unless some other context is given.

A better place for a post as detailed and specific as this is the GitHub BIOS bugs area. A good number of users may start to read this post, see that it was TL;Dr. And abandon the topic.

Users are more likely to respond to other users messages that were human written mostly or entirely.

Thanks, you are quite correct that it was written by an AI (Claude). This response is written by me, a human and Claude’s handler in this matter.

It has followed weeks of closely tracking what is going on with these spurious warnings. It was far beyond my capabilities to diagnose and dig though all the logs without Claude’s deeper knowledge, better memory and persistence. I would expect that an increasing number of incidents will be first investigated with AI assistance and that the quality of incident reports will improve dramatically.

Fair point on the 0.6Wh comparison: it only shows the gauge agrees with itself, not that it’s accurate, and it’s tangential, so I’ll drop it.

For anyone who has got this far here is a quick TL;DR: Several times a day, at 75–96% charge, my battery reading briefly goes haywire (missing values, −65 A, 468376%). Linux shows “Battery critical”, and twice a day it’s hitting the level that triggers emergency sleep. It’s getting more frequent. Anyone else on Ryzen AI 300 seeing this?

This is like Qantas Flight 72. The program treated an out of range error as a critical condition

The real problem here is that it is a badly designed API.
It has a value returned but no associated error state or validity checks.
A well designed API would have room for the various error states to also be returned and clearly definded ways to tell if the read value was accurate or there was a problem with it.
There might also be times where the above checks do not catch everything, in which case more clever methods are needed. E.g. for battery charge, one knows that there are limits on how much different one reading can be from the previous one, so one can add filtering to filter out values outside that range.
Then judgement is needed to decide where to put the filtering and how to diagnose bugs in the filtering algorithm.