Battery cycle count: wrong readings on Linux when cycle count >= 256?

Today I just noticed that the battery cycle count went from ~200-300 (can’t remember exactly) to just 10.

How is that possible ?

The two possible actions that I performed in the past weeks are:

  • Update from Fedora 42 to Fedora 43
  • Update to the latest BIOS (03.17)

It does not prevent me from working, but I found this behavior counter-intuitive…

Other people in the same situation ? Any idea ?

$ sudo dmidecode -s bios-version
03.17

$ upower -i /org/freedesktop/UPower/devices/battery_BAT1
  native-path:          BAT1
  vendor:               NVT
  model:                FRANGWA
  serial:               015E
  power supply:         yes
  updated:              mar. 06 janv. 2026 10:10:32 (7 seconds ago)
  has history:          yes
  has statistics:       yes
  battery
    present:             yes
    rechargeable:        yes
    state:               charging
    warning-level:       none
    energy:              49,18 Wh
    energy-empty:        0 Wh
    energy-full:         55,728 Wh
    energy-full-design:  60,6042 Wh
    voltage-min-design:  15,48 V
    capacity-level:      Normal
    energy-rate:         11,2849 W
    voltage:             17,522 V
    charge-cycles:       10
    time to full:        34,8 minutes
    percentage:          88%
    capacity:            91,954%
    technology:          lithium-ion
    icon-name:          'battery-full-charging-symbolic'
  History (rate):
    1767690632  11,285  charging
    1767690602  11,533  charging
    1767690572  11,765  charging
    1767690542  12,043  charging

$ uname -a
Linux fedora 6.17.12-300.fc43.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Dec 13 05:06:24 UTC 2025 x86_64 GNU/Linux

That might be a linux driver or ec “cros” protocol issue, it’s not consistent with info directly from ectool:

$ upower -b | grep -i cycle
charge-cycles:       65

$ cat /sys/class/power_supply/BAT1/cycle_count
65

$ sudo ectool battery | grep -i cycle
Cycle count             321
1 Like

Yes, this is correct.

Thanks for pointing that out !

$ cat /sys/class/power_supply/BAT1/cycle_count
10

$ sudo ~/local/bin/ectool battery 
Battery 0 info:
  OEM name:               NVT
  Model number:           FRANGWAT01
  Chemistry   :           LION
  Serial number:          015E
  Design capacity:        3915 mAh
  Last full charge:       3600 mAh
  Design output voltage   15480 mV
  Cycle count             266
  Present voltage         16426 mV
  Present current         -477 mA
  Remaining capacity      3090 mAh
  Desired voltage         17600 mV
  Desired current         3915 mA
  Flags                   0x83 AC_PRESENT BATT_PRESENT

In fact, there is a pattern:

$ bc -l
bc 1.08.2
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2018, 2024 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 

321-65
256

266-10
256

It looks like an integer overflow.

haha, nice observation, uint8 overflow indeed

EDIT: To be a bit more precise, probably truncation of little-endian uint16 or uint32 to uint8

1 Like

I think linux gets this battery info in sysfs from acpi:

[    0.338572] ACPI: battery: Slot [BAT1] (battery present)

and it’s copied from ACPI as a 32-bit int in drivers/acpi/battery.c

struct acpi_battery {
	...
	int cycle_count;
static const struct acpi_offsets extended_info_offsets[] = {
	...
	{offsetof(struct acpi_battery, cycle_count), 0},
static int extract_package(struct acpi_battery *battery,
			   union acpi_object *package,
			   const struct acpi_offsets *offsets, int num)
{
	...
			int *x = (int *)((u8 *)battery + offsets[i].offset);
			*x = (element->type == ACPI_TYPE_INTEGER) ?
				element->integer.value : -1;

So I think the bug is in the firmware ACPI tables/scripts. The “battery extended info” object is “_BIX” and I can find that in my dumped ACPI tables … but I’m really not an expert in this stuff :grimacing:

            Method (_BIX, 0, NotSerialized)  // _BIX: Battery Information Extended
            {
                Return (BIFX (One))
            }

            Method (BIFX, 1, NotSerialized)
            {
                Name (STAX, Package (0x14)
                {
                    Zero, 
                    Zero, 
                    0x0DF4, 
                    0x0DB7, 
                    One, 
                    0x3C28, 
                    0x015E, 
                    0x69, 
                    Zero, 
                    Zero, 
                    Zero, 
                    Zero, 
                    Zero, 
                    Zero, 
                    0x0108, 
                    0x0EC4, 
                    0xFFFFFFFF, 
                    0xFFFFFFFF, 
                    "Li-Ion  ", 
                    0xFFFFFFFF
                })
                ...
                STAX [One] = BSMD /* \_SB_.BSMD */                                                  
                Local0 = BTDC /* \_SB_.BTDC */                                                      
                STAX [0x02] = (Local0 * BASC)                                                       
                STAX [0x05] = BTDV /* \_SB_.BTDV */                                                 
                Local2 = BTLC /* \_SB_.BTLC */                                                      
                Local2 = (Local2 * BASC)                                                            
                STAX [0x03] = Local2                                                                
                Divide (Local2, 0x64, Local0, Local1)                                               
                Local1 *= 0x0A                                                                      
                STAX [0x06] = Local1                                                                
                Divide (Local2, 0x64, Local0, Local1)                                               
                Local1 *= 0x03                                                                      
                STAX [0x07] = Local1                                                                
                STAX [0x11] = ToString (Concatenate (BTSN, Zero), Ones)                             
                STAX [0x08] = BTCC /* \_SB_.BTCC */
                ...

Hey, I think that STAX [0x08] = BTCC /* \_SB_.BTCC */ is it, the cycle count is the 9th item in the BIX acpi_offsets, so at index 8. Where else can we find it …

        OperationRegion (NVRM, SystemIO, 0x0E00, 0x0200)
        Field (NVRM, ByteAcc, Lock, Preserve)
        {
            TMPL,   8, 
            TMPD,   8, 
            TMPC,   8, 
            TMPA,   8, 
            TDGV,   8, 
            TDVR,   8, 
            TAMB,   8, 
            TMPG,   8, 
            Offset (0x30), 
            ECSW,   8, 
            Offset (0x40), 
            BTVO,   32, 
            BTAC,   32, 
            BTRC,   32, 
            BTFG,   8, 
            BTCT,   8, 
            Offset (0x50), 
            BTDC,   32, 
            BTDV,   32, 
            BTLC,   32, 
            BTCC,   8, 
            ...

Does that say it’s 8 bits? Have we found it?! now just to figure out where to report this to get some actual firmware dev attention on it …

1 Like

Well, so.

When I requested that the ACPI implementation of _BIX report the cycle count (Feature Request: ACPI `_BIX` to report battery cycle count), I used the 32-bit memory map read method M005. This was for the 11th gen, but the principle holds.

Yes, it should be 32 bits. Yes, it is currently marked as being 8.

2 Likes

I created an issue: FW13 AMD 7040 - ACPI battery charge cycles count wrong · Issue #152 · FrameworkComputer/SoftwareFirmwareIssueTracker · GitHub

1 Like

Thanks a lot ! :+1: