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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions