Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/saf/device/evidence/power/HANDOFF-upower-charge-control.md
Fimeg bde961c6f2 saf: one spine — device, state, and work under the index
PAF becomes saf/device (history kept), STATE.md dissolves
into saf/state.md with the dated era archived, the substrate
SAF moves up from souveraine, and every agreement points at
saf/INDEX.md and nowhere else. one map, nothing to remember
2026-08-18 09:47:30 -04:00

158 lines
8 KiB
Markdown

# HANDOFF — pmi8998 charge control (kernel + userspace)
> Historical handoff. Current platform status belongs in `../../platform.md`.
Written 2026-07-24. For a fresh model/reviewer picking this up cold.
## What this is
Full read/write charge control for the Pixel 3 (blueline) charger, so the
phone can report its charge stage and cap the charge for battery longevity —
the capability LineageOS exposed downstream and mainline dropped.
Two layers, in two separate repos:
- **Kernel driver** — `drivers/power/supply/qcom_smbx.c` in the
`linux-blueline` tree. Adds charge_type reporting + charge-cap control.
- **Userspace** — `rootfs-overlay/…` in the `Pixel3Arch` repo. A CLI, a udev
rule, a persistence unit. **Currently stale — see "Known gaps" #1.**
Nothing is flashed. The phone is untouched. The driver builds clean but has
not been packaged since the last fix, and has NOT run on hardware.
## State of the tree — READ THIS FIRST
**The gitea branch is behind local by two commits.**
| commit | what | where |
|---|---|---|
| `f08dc70b3` | original: charge_type + charge control | **pushed to gitea `blueline`** |
| `6b733d3ea` | fix pass after review #1 | **LOCAL ONLY** |
| `588929024` | cell-aware current cap + probe guard | **LOCAL ONLY** |
`gitea.wiuf.net`/`10.20…:4455` `Fimeg/linux-blueline` branch `blueline` tip is
`f08dc70b369a` — the **flawed original**. A reviewer who clones from gitea gets
code with the defects listed under "Review #1 found" still present. To review
the real thing, use the local tree at
`~/Projects/Pixel3Arch/kernel/linux-blueline-current` (branch `mic-race-fix`,
tip `588929024`) or push the two commits first.
`build.sh` clones from gitea and hard-asserts the built package's hash matches
the branch tip, so **a package build will not include the fixes until they are
pushed.**
## Files
### Kernel (linux-blueline-current, branch mic-race-fix)
- `drivers/power/supply/qcom_smbx.c` — the only changed file. +241 lines.
- `smb_get_prop_charge_type` — read-only, maps BATTERY_CHARGER_STATUS_1
bits 0:2 (trickle/pre/fast/fullon/taper/…) to POWER_SUPPLY_CHARGE_TYPE_*.
- `smb_{get,set}_prop_voltage_max` — CV target, FLOAT_VOLTAGE_CFG (0x70).
- `smb_{get,set}_prop_constant_charge_current` — FAST_CHARGE_CURRENT_CFG (0x61).
- `smb_{get,set}_prop_charge_control_end` — percentage form of voltage_max,
on POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD.
- `smb_fast_charge_current_max` — 1C ceiling from charge_full_design_uah.
- `smbx_ov_status` — one-char fix, `reg & mask``val & mask`.
- probe: design-limit guard + constant substitution.
### Userspace (Pixel3Arch repo, committed at 8a35c45 / 194c422)
- `rootfs-overlay/usr/bin/blueline-charge` — CLI. **STALE.**
- `rootfs-overlay/etc/udev/rules.d/60-blueline-charge.rules` — grants the
writable attrs to group wheel. **STALE.**
- `rootfs-overlay/etc/systemd/system/blueline-charge-limit.service`
reapplies the cap on boot (registers are volatile).
- `PAF/platform.md` — "Charging" section documents the register map.
## Property provenance (asked and answered)
Every property name is a **pre-existing mainline `power_supply` core constant**
(`include/linux/power_supply.h`), not invented here and not from any PR of
mine. `charge_control_end_threshold` has a documented ABI contract dated April
2019 (`Documentation/ABI/testing/sysfs-class-power`) — "battery percentage
level, above which charging will stop" — and is already implemented by seven
in-tree drivers, including the sibling `qcom_battmgr.c` on this same SoC family.
The one thing authored here is the charge_type register-state→enum mapping.
## Review history
Three independent cold-start adversarial reviews were run (subagents, each told
to refute safety). Summary of what each found:
**Review #1 (against f08dc70b3) — four real defects, all since fixed in 6b733d3ea:**
1. `smbx_ov_status` masked the register *address* not its *value*
(`reg & mask`), so the overvoltage backstop returned 0 on both SMB2 and
SMB5 and never fired — pre-existing, but the write path leaned on it.
2. `charge_control_limit` is microamps by ABI; it was implemented as a
percentage, inverting a thermal daemon's write. Moved to
`charge_control_end_threshold` (percent, ABI-correct).
3. `voltage_max` floored at the register base (3.4875V), not the cell design
minimum (3.6V). `echo 3487500 > voltage_max` set CV below terminal voltage;
with CHARGER_INHIBIT the charger stalls and the phone drains on a cable.
Now floored at voltage_min_design_uv.
4. Current setter clamped against the DCP *input* limit (a different register
and concept), so the getter returned 1.95A while the setter rejected it.
**Review #2 (against 6b733d3ea) — confirmed the four fixes; flagged the current
ceiling. Fixed in 588929024:**
- The current cap was bounded by register width = 6.375A = **2.15C** into a
2970mAh cell. Relying on "the adapter/AICL gate it" is the same
external-backstop reasoning that hid the dead OV check. Now bounded at **1C**
(2.97A from charge_full_design_uah), register width as fallback.
- Probe didn't validate voltage design limits (latent, not blueline). Guarded.
**Review #3 (against 588929024) — verdict FLASH for blueline.** No overcharge
(clamp-before-encode, encoder rounds down, 8-bit field can't truncate a legal
value unsafe), no stall (3.6V floor on every path, registers volatile →
reboot recovers), 1C cap confirmed, dispatch clean.
## Known gaps — WHAT STILL NEEDS DOING
1. **Userspace is stale.** `blueline-charge` and `60-blueline-charge.rules`
still reference `charge_control_limit`, which the driver no longer exposes.
On the fixed kernel the CLI fails its own `[ -e "$PSY/charge_control_limit" ]`
guard. They must be updated to `charge_control_end_threshold`. NOT done.
2. **Only `end_threshold`, not the `start`/`end` pair.** The ABI defines
`charge_control_start_threshold` and `_end_threshold` as a pair; some
userspace (GNOME/UPower, some tlp configs) expects both. Only `end` is
implemented. `drivers/power/supply/cros_charge-control.c` is the in-tree
reference for doing both.
3. **The SMB2 overvoltage bit is unverified.** No auditor could confirm from
code alone that BIT(5) of BATTERY_CHARGER_STATUS_2 is the pmi8998 OV bit —
there's no datasheet or downstream reference in-tree. It's pre-existing and
read-only (can't overcharge/stall), but worth confirming against a datasheet.
4. **Cosmetic:** the max voltage write (4.4V → reg 122) reads back as 4.395V
and 99% via the threshold getter, because the 7.5mV encoding step doesn't
divide the range evenly. Errs low (safe). Not fixed.
5. **Not packaged, not flashed.** Single-object compiles pass under clang/LLD/
CFI with W=1. Full `build.sh` not re-run since the fixes (and can't include
them until pushed — see "State of the tree"). No hardware test.
## What is NOT verified without hardware
- Whether lowering the float voltage actually *stops* the charge on the
pmi8998, versus the fuel gauge continuing to report rising capacity.
- Whether charge_type reports sane transitions (Fast under load → Standard as
it tapers near full).
## Build & deploy (per PAF/build.md)
- Commit + **push to gitea** (uncommitted/unpushed work does not build).
- `ssh casey@10.10.20.123 'cd ~/Projects/Pixel3Arch/kernel && ./build.sh blueline'`
— self-locks with flock, invoke directly; asserts pkg hash == gitea tip.
- Deploy: modules first, then flash boot_a, then refuse to bless unless
`uname -r` matches the installed tree (`7.1.1-sdm845-g<hash>`).
- Rollback: `staging/deploy-139aa/boot-139aa7748181-pmoschain.img`. Slot B
(LineageOS) is untouched and is the ultimate fallback.
## Single-object compile check (fast, no full build)
scp qcom_smbx.c casey@10.10.20.123:/tmp/
ssh casey@10.10.20.123 'cd ~/Projects/Pixel3Arch/kernel/src/linux-blueline-src && \
cp /tmp/qcom_smbx.c drivers/power/supply/ && \
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- W=1 \
drivers/power/supply/qcom_smbx.o'
# restore the tree afterward — this is makepkg's throwaway clone