Skip to content

macsmc-battery: charge_now/energy_now reported at inconsistent scale (under & over); capacity intermittently stuck at 100% #522

Description

@Alarisco

macsmc-battery: charge_now/energy_now reported at inconsistent scale (both under and over); capacity intermittently stuck at 100%

Summary

The macsmc-battery power supply driver reports charge_now and energy_now at an inconsistent, time-varying scale relative to charge_full/energy_full. The same *_now pair is, within each sample, scaled by an identical factor, but that factor changes over time and has been observed ranging from ~0.4x to ~10x the physically-correct value. As a result:

  • Any percentage computed as now/full is wrong, in both directions (under- and over-reporting).
  • The dedicated POWER_SUPPLY_CAPACITY attribute (sourced from a separate fuel-gauge register, not computed from now/full) is closer to reality but is intermittently stuck at 100 even when the battery is not full.

The actual battery level is ~95-96% (confirmed by the user; corroborated by time_to_empty_now). Userspace that follows capacity/upower percentage therefore sees either a stuck 100% or a briefly correct ~95%, while anything that computes from charge_now/energy_now sees garbage (e.g. 54%, 69%, 39%, 652%, 1073%). Low-battery alerts never fire while capacity is stuck at 100.

Hardware / Environment

  • Device: Apple MacBook Pro (14-inch, M1 Pro, 2021) — apple,j314s, apple,t6000
  • Battery: bq40z651, serial F8Y13660648Q1LT61, 861 charge cycles, health: Good
  • Design capacity: charge_full_design=6,075,000 µAh / energy_full_design=69,255,000 µWh (~69.3 Wh)
  • Actual full capacity (learned): charge_full≈5,225,000 µAh / energy_full≈59,565,000 µWh (~59.6 Wh) → ~86% health (consistent with 861 cycles). These *_full values are stable and correct.
  • System firmware: unknown v1.5.2 (m1n1 stage2)
  • OS: Arch Linux ARM (asahi-alarm repo)
  • Kernel: 7.0.13.asahi1-1 (linux-asahi package, 7.0.13-1-1-ARCH, aarch64) — this is the latest available in the repo; updating does not fix it.
  • upower: 1.91.2-1
  • Kernel cmdline: BOOT_IMAGE=/boot/vmlinuz-linux-asahi root=UUID=... rw loglevel=3 quiet splash appledrm.show_notch=1

Key observation: capacity is independent of now/full

Across all samples, POWER_SUPPLY_CAPACITY cannot be derived from charge_now/charge_full or energy_now/energy_full by simple division — e.g. charge_now=3,603,000 / charge_full=5,225,000 = 69%, yet capacity=95. This confirms the driver populates capacity from a separate fuel-gauge source. The *_now fields and the capacity field are independently (and differently) broken.

Reproduction 1 — charge_now/energy_now under-reporting while capacity is correct (battery ~95-96%, discharging)

1-second interval, battery discharging, actual level ~95-96%:

cap=95  raw=69.4%  charge_now=3603000  energy_now=41074200  tte=23100s  power_now=-7519000uW
cap=95  raw=69.4%  charge_now=3603000  energy_now=41074200  tte=23280s  power_now=-8699000uW
cap=95  raw=69.4%  charge_now=3603000  energy_now=41074200  tte=23280s  power_now=-9355000uW
cap=95  raw=69.4%  charge_now=3603000  energy_now=41074200  tte=23280s  power_now=-9320000uW
cap=95  raw=64.5%  charge_now=3347000  energy_now=38155800  tte=23280s  power_now=-9321000uW
cap=95  raw=64.5%  charge_now=3347000  energy_now=38155800  tte=23280s  power_now=-9454000uW
cap=95  raw=64.5%  charge_now=3347000  energy_now=38155800  tte=23280s  power_now=-9417000uW
cap=95  raw=64.5%  charge_now=3347000  energy_now=38155800  tte=23280s  power_now=-9530000uW
cap=95  raw=59.5%  charge_now=3091000  energy_now=35237400  tte=23220s  power_now=-9469000uW
cap=95  raw=59.5%  charge_now=3091000  energy_now=35237400  tte=23220s  power_now=-9368000uW
cap=95  raw=59.5%  charge_now=3091000  energy_now=35237400  tte=23160s  power_now=-9505000uW
cap=95  raw=59.5%  charge_now=3091000  energy_now=35237400  tte=23160s  power_now=-9321000uW
cap=95  raw=59.5%  charge_now=3091000  energy_now=35237400  tte=23220s  power_now=-9357000uW
cap=95  raw=54.6%  charge_now=2835000  energy_now=32319000  tte=23220s  power_now=-9367000uW
cap=95  raw=54.6%  charge_now=2835000  energy_now=32319000  tte=23220s  power_now=-9231000uW

Here charge_now decreases smoothly (3,603,000 → 2,835,000) but reads ~54-69% of charge_full, while capacity=95 and time_to_empty_now≈23,200s (6.4 h). At ~9.4 W, 6.4 h implies ~56-61 Wh remaining ≈ 95-102% of energy_full — consistent with capacity=95 and the user-reported ~96%, and inconsistent with the charge_now-derived 54-69%. So here charge_now/energy_now are under-reporting by ~0.5-0.7x while capacity is correct.

Reproduction 2 — charge_now/energy_now over-reporting and capacity stuck at 100 (battery actually ~96%, discharging)

1-second interval, same session, a few minutes earlier (still discharging, actual level still ~96%):

t=1  status=Discharging  capacity=100  charge_now=34067000 charge_full=5225000 (raw 652.0%)  energy_now=388363800 energy_full=59565000
t=2  status=Discharging  capacity=100  charge_now=34067000 charge_full=5225000 (raw 652.0%)  energy_now=388363800 energy_full=59565000
t=3  status=Discharging  capacity=100  charge_now=33811000 charge_full=5225000 (raw 647.1%)  energy_now=385445400 energy_full=59565000
t=4  status=Discharging  capacity=100  charge_now=33811000 charge_full=5225000 (raw 647.1%)  energy_now=385445400 energy_full=59565000
t=5  status=Discharging  capacity=100  charge_now=33811000 charge_full=5225000 (raw 647.1%)  energy_now=385445400 energy_full=59565000
t=6  status=Discharging  capacity=100  charge_now=33811000 charge_full=5225000 (raw 647.1%)  energy_now=385445400 energy_full=59565000
t=7  status=Discharging  capacity=100  charge_now=33555000 charge_full=5225000 (raw 642.2%)  energy_now=382527000 energy_full=59565000
t=8  status=Discharging  capacity=100  charge_now=33555000 charge_full=5225000 (raw 642.2%)  energy_now=382527000 energy_full=59565000
t=9  status=Discharging  capacity=100  charge_now=33555000 charge_full=5225000 (raw 642.2%)  energy_now=382527000 energy_full=59565000
t=10 status=Discharging  capacity=100  charge_now=33555000 charge_full=5225000 (raw 642.2%)  energy_now=382527000 energy_full=59565000

Here charge_now/charge_full = 652% (over-reporting by ~6.5x; also seen at ~10x: 55,827,000/5,202,000 = 1073%), and capacity is stuck at 100 despite the battery being at ~96%. upower exposes this as percentage: 100%, energy: 639.346 Wh, time to empty: 3.4 days — all absurd for a 69 Wh-design battery.

Reproduction 3 — charge_now/energy_now briefly under-report at ~0.4x

A one-off reading captured during the same discharge:

charge_now=2,068,000  charge_full=5,234,000  -> 39.5%
energy_now=23,575,200 energy_full=59,667,600 -> 39.5%

charge_now and energy_now are internally consistent with each other (both 39.5%) but inconsistent with reality (~96%) and with time_to_empty_now. This is an under-reporting outlier at ~0.4x, not the true state.

Summary of observed *_now scale factors vs. physically-correct value

Observed reading charge_now/charge_full Inferred scale vs. true (~96%)
2,068,000 / 5,234,000 39.5% ~0.4x (under)
2,835,000 / 5,225,000 54.6% ~0.6x (under)
3,603,000 / 5,225,000 69.4% ~0.7x (under)
34,067,000 / 5,225,000 652% ~6.5x (over)
55,827,000 / 5,202,000 1073% ~10x (over)

Within each sample, charge_now and energy_now are scaled by the same factor; the *_full values are stable and correct.

capacity behavior

  • Sourced independently from now/full (see "Key observation").
  • Intermittently stuck at 100 even when the battery is at ~96% (Reproduction 2). When stuck, low-battery alerts never fire (safety/usability concern: unexpected shutdowns).
  • At other times reports a value consistent with reality and time_to_empty_now (Reproduction 1: capacity=95).

upower -d excerpt (captured during an over-reporting + stuck-capacity window)

Device: /org/freedesktop/UPower/devices/battery_macsmc_battery
  native-path:          macsmc-battery
  model:                bq40z651
  state:                discharging
  energy:              639.346 Wh          <-- absurd (from inflated *_now)
  energy-full:         639.346 Wh          <-- equals "energy" because now>=full
  energy-full-design:  69.255 Wh
  voltage:             12.535 V
  energy-rate:         7.871 W
  charge-cycles:       861
  percentage:          100%
  capacity:            100%                <-- health also misreported (should be ~86%)
  time to empty:       3.4 days            <-- derived from inflated energy
  charge-start-threshold:        75%
  charge-end-threshold:          80%

Note: upower's capacity (health) is also wrong here (100% instead of ~86% = 59.5/69.3), suggesting energy_full is also occasionally inflated when upower samples it (sysfs polling showed energy_full stable at 59.5 Wh, but upower caught 639 Wh).

Full sysfs uevent (captured during a ~0.4x under-reporting window)

POWER_SUPPLY_NAME=macsmc-battery
POWER_SUPPLY_TYPE=Battery
POWER_SUPPLY_STATUS=Discharging
POWER_SUPPLY_HEALTH=Good
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_CYCLE_COUNT=861
POWER_SUPPLY_VOLTAGE_MAX=12998000
POWER_SUPPLY_VOLTAGE_MIN=8940000
POWER_SUPPLY_VOLTAGE_MAX_DESIGN=12825000
POWER_SUPPLY_VOLTAGE_MIN_DESIGN=9000000
POWER_SUPPLY_VOLTAGE_NOW=12516000
POWER_SUPPLY_CURRENT_NOW=-815000
POWER_SUPPLY_POWER_NOW=-10200000
POWER_SUPPLY_CHARGE_FULL_DESIGN=6075000
POWER_SUPPLY_CHARGE_FULL=5234000
POWER_SUPPLY_CHARGE_NOW=2068000
POWER_SUPPLY_CHARGE_COUNTER=-769574
POWER_SUPPLY_ENERGY_FULL_DESIGN=69255000
POWER_SUPPLY_ENERGY_FULL=59667600
POWER_SUPPLY_ENERGY_NOW=23575200
POWER_SUPPLY_CAPACITY=100            <-- stuck at 100 even though charge_now/charge_full = 39.5%
POWER_SUPPLY_CAPACITY_LEVEL=Normal
POWER_SUPPLY_TEMP=341
POWER_SUPPLY_TIME_TO_EMPTY_NOW=25680

Suggested investigation

  • The macsmc-battery driver appears to source charge_now/energy_now from a register/field whose unit or scaling is unstable across reads (returning anything from ~0.4x to ~10x the correct value), while charge_full/energy_full are stable. Identify which SMC key/register backs *_now and whether it sometimes returns a cumulative counter, a raw ADC value, or a value in a different unit rather than instantaneous remaining charge/energy.
  • POWER_SUPPLY_CAPACITY is populated independently of now/full (good) but gets stuck at 100. Worth checking whether the driver caches/clamps it and only refreshes on certain events (e.g. charge state transitions) rather than on every read.
  • If helpful, I can collect: longer traces, dmesg output, readings while charging vs. discharging, or concurrent time_to_empty_now/power_now samples to triangulate the true state.
  • Separately: upower's health capacity (energy_full/design) was observed as 100% when it should be ~86%; may indicate energy_full is also occasionally inflated at the moments upower samples it.

Workaround in use

None at the kernel level (the latest linux-asahi 7.0.13.asahi1-1 available in asahi-alarm is already installed and exhibits the issue). Userspace workaround under consideration: prefer POWER_SUPPLY_CAPACITY over now/full-derived percentage, and when capacity is stuck at 100 for extended periods while discharging, fall back to time_to_empty_now × power_now / energy_full to estimate the true level.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions