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
This commit is contained in:
parent
341a2fe060
commit
bde961c6f2
90 changed files with 4590 additions and 68 deletions
112
saf/device/README.md
Normal file
112
saf/device/README.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# PAF — Pixel 3 (blueline) architecture
|
||||
|
||||
The living spine for the phone, reached from the SAF index as **device**. It
|
||||
began in the pmOS project, moved to Pixel3Arch in July, was copied to
|
||||
SouveraineOS on 2026-07-24, and became `saf/device/` on 2026-08-18 when the
|
||||
SAF moved up to the umbrella and took the whole spine with it. Device code
|
||||
remains in `../../Pixel3Arch/`.
|
||||
|
||||
**Update the relevant layer in place.** Dated captures are evidence, not a
|
||||
second spine. Git holds the old arrangements: SouveraineOS `72aaa53` is the
|
||||
July import; Pixel3Arch `9b1afd1` is the last full in-repo PAF snapshot.
|
||||
|
||||
## Start here — "I want to work on my phone"
|
||||
|
||||
The six things you need before any of the layer docs below matter.
|
||||
|
||||
**Where it is.** `10.10.20.234` over WiFi (stable since 2026-07-20; the MAC is
|
||||
pinned, so it does not drift the way this doc used to claim). `172.16.42.1`
|
||||
over USB, which is the fallback when WiFi is wedged.
|
||||
|
||||
ssh -i ~/.ssh/ani casey@10.10.20.234
|
||||
|
||||
**What it runs.** Souveraine OS — Arch Linux ARM, systemd, pacman, bash. Our
|
||||
own kernel (`uname -r` → `7.1.1-sdm845-g<hash>`; the hash is ground truth for
|
||||
what is actually booted). Slot A is ours, slot B is LineageOS and is the
|
||||
fallback — never touch it. `casey` has passwordless sudo.
|
||||
|
||||
**Where things get built.** Never here, and never on the laptop:
|
||||
ArchDev, `casey@10.10.20.123`, same key. The phone has 3.5 GB of RAM and is the
|
||||
daily driver; the laptop is for device ops only. This holds for `cargo test`
|
||||
and `cargo clippy` too, not just release builds.
|
||||
|
||||
**How it updates.** `sudo pacman -Syu`. Everything we build ships as a signed
|
||||
package — kernel included. Reaching for fastboot because a kernel changed is a
|
||||
regression, not a fallback; see `../../START-HERE.md` and
|
||||
`../../Pixel3Arch/CLAUDE.md` for the few real reasons to flash. COLD boots only
|
||||
(a warm reboot poisons TZ/remoteproc), and reboots are Casey's, at the device.
|
||||
|
||||
**When it will not answer.** Two failures look identical (ping works, ssh
|
||||
hangs) and both are routing, not the phone:
|
||||
- *USB:* networkd matches `Name=usb*`, so a stale down `usb0` and the live
|
||||
`usb1` both hold 172.16.42.1 and replies leave by the dead one.
|
||||
`sudo ip addr flush dev usb0`, and on the laptop
|
||||
`sudo ip neigh del 172.16.42.1 dev <iface>` if the MAC looks wrong.
|
||||
- *WiFi:* ath10k_snoc wedges with auth timeouts — reload the module, then
|
||||
`systemctl restart wpa_supplicant`.
|
||||
|
||||
**Work that started on the phone.** This is the one with no tooling, so it is
|
||||
the one that bites. Things get hacked on-device and then live only there —
|
||||
`~/stevia-pkg` was four pkgrels deep before any of it came back. To find it:
|
||||
|
||||
ssh -i ~/.ssh/ani casey@10.10.20.234 'ls -lt ~ | head -20'
|
||||
pacman -Qm # foreign packages — built here, owned by nobody upstream
|
||||
pacman -Qo /usr/bin/<x> # "No package owns" = it was hand-copied
|
||||
|
||||
Finishing it means three steps, in order, and the middle one is the one that
|
||||
gets skipped: **merge it back** into `../../Pixel3Arch/pkgs/<name>/` (the repo
|
||||
copy may have moved too — diff both ways, they can both claim the same pkgrel),
|
||||
**build it on ArchDev** (`CARCH=aarch64 makepkg -f --ignorearch --nodeps`;
|
||||
`../../Pixel3Arch/pkgs/stevia` is the worked cross example), then **install the
|
||||
built package** rather than the on-device one. A thing that only exists on the
|
||||
phone is one reflash from gone, and `../../Pixel3Arch/rootfs-overlay/` only
|
||||
reaches a device at provision time — which is TASK-27's whole subject.
|
||||
|
||||
---
|
||||
|
||||
- **[platform.md](platform.md)** — hardware map, live partition layout,
|
||||
reaching the phone, boot chain, recovery.
|
||||
- **[build.md](build.md)** — gitea → ArchDev → deploy pipeline, and the
|
||||
gotchas that have each cost a session.
|
||||
- **[display.md](display.md)** — panel/DSI stack, the warm-cycle doctrine,
|
||||
SWIRE bias rails. SOLVED; read before touching the panel driver.
|
||||
- **[touch.md](touch.md)** — FTS driver, reset polarity truth, GPI-DMA
|
||||
telemetry, the tap-to-wake/SLPI handoff goal.
|
||||
- **[slpi.md](slpi.md)** — sensor island: the five-fix bring-up chain and
|
||||
the `sar.cc:27` wall. Arch status header + pmOS-era record.
|
||||
- **[audio.md](audio.md)** — WCD9340/SLIMbus audio: working HiFi speaker and
|
||||
microphone profiles, working two-way calls, and the lifecycle archaeology.
|
||||
- **[edge-sense.md](edge-sense.md)** — Active Edge force path, calibration,
|
||||
sessiond gating, and the remaining doze gap.
|
||||
- **[modem.md](modem.md)** — WORKING on Arch. Status header + the full
|
||||
52-wall research record (modemsmem root cause).
|
||||
- **[evidence/](evidence/)** — dated capture/research docs the spine cites:
|
||||
audio handoffs under `evidence/audio/`, power handoffs under
|
||||
`evidence/power/`, plus modem/QCRIL/TZ archaeology and pmOS-era snapshots.
|
||||
Read-only history; amend the living layer doc instead.
|
||||
|
||||
Related: `../../START-HERE.md` for triage, `../../docs/tasks/` for live work, and
|
||||
`../../Pixel3Arch/` for kernel, packages, overlays, and device tooling.
|
||||
|
||||
## Status at a glance (reconciled 2026-08-07)
|
||||
|
||||
| Layer | State |
|
||||
|---|---|
|
||||
| Boot chain (our kernel + pmOS-chain ramdisk → Arch_root) | 🟢 stable; qbootctl closes the A/B lockout |
|
||||
| Display (SW43408 panel, DPMS off/on wake) | 🟠 Warm-cycle baseline remains the only working architecture, but current `g56b0a053bef8` glass testing shows a green flash at boot/wake and occasional stale visible frame; no DSI/DPU error was logged — see display.md |
|
||||
| Touch (FTS, multi-touch + gestures) | 🟠 Daytime touch and gesture detection work on current glass. DT2W can half-wake on the first double-tap (prepare immediately followed by unprepare); a second tap wakes — see touch.md |
|
||||
| Modem / RF (LTE data) | 🟢 WORKING on Arch — mmcli connected, LTE, Fido, clat up (verified live 2026-07-10) |
|
||||
| WiFi (ath10k_snoc) | 🟢 True unplugged cold boot verified 2026-07-11: no `rejected:90`, associates, gets DHCP, and reaches gateway + Internet. `failed to install key … -110` still occurs during association/roam but did not break connectivity; `cryptmode=1` remains a fallback only if it does. |
|
||||
| Bluetooth (bluetoothd) | 🟢 WORKING 2026-07-10: controller was invisible because WCN3990 ships no BD address (kernel leaves hci0 unconfigured) — blueline-bt-bdaddr.service sets a persisted one; live scan verified |
|
||||
| USB device / KVM | 🟠 Full GUD + HID + NCM + smoo composite builds on ArchDev with transactional rollback and a sessiond-owned power-sheet verb. Not installed on glass yet: the phone still runs usb-signaller 0.3.1-3 and has no smoo package — see `../../USB/README.md` and TASK-62. |
|
||||
| SLPI sensor island | 🟢 `sar.cc` fatal is gone (regression, not a real limit) — `iio-sensor-proxy` runs always-on via `blueline-sensors-enable.service` (After=blueline-hexagonrpcd-sdsp), no more on-demand start/stop. Proximity (TMD2725) and accelerometer both live — see slpi.md |
|
||||
| Tap-to-wake | 🟠 AP-side gesture mode detects DBLTAP and delivers wake, but the current screen-toggle path can half-wake on the first tap. DT2W now gates on proximity — `blueline-screen-toggle on` checks `ProximityNear` over D-Bus and suppresses the wake if blocked (phone in pocket). The SLPI gesture-nanoapp path is unblocked by the sar.cc fix but not required for AP-side DT2W. |
|
||||
| Audio | 🟢 WORKING. PipeWire/WirePlumber exposes the HiFi speaker and microphone; Casey uses the mic for STT. The Voice Call profile carries real two-way calls, with earpiece/speaker switching handled by `blueline-callaudio-hook` — see audio.md. |
|
||||
| Active Edge (squeeze) | 🟢 WORKING 2026-07-28 — force detected (2502-2990 deflection) after the rail became a DT gpio-hog; producer packaged, enabled, and gated through sessiond's pocket veto. No detection while the AP sleeps — see edge-sense.md |
|
||||
| Fingerprint (FPC1020) | 🟠 driver written and building 2026-08-01 — `drivers/input/misc/fpc1020.c` on branch `fingerprint-fpc1020`, reset gpio7 / IRQ gpio121 verified against this unit's DTBO, reports `KEY_WAKEUP`. **Never run on the device.** Electrical half only; capture/matching are a TrustZone app we do not load — see TASK-44 |
|
||||
| Camera | 🟠 Front IMX355 streams real frames. Rear IMX363 DT exists but remains unbuilt — see TASK-46. |
|
||||
| NFC / haptics | ⚪ Open — see TASK-45 and the live task index. |
|
||||
|
||||
**One-line state:** daily-driver Arch on slot A with LTE, WiFi, Bluetooth,
|
||||
speaker/microphone/call audio, daytime touch, Active Edge, always-on sensors,
|
||||
and a working front camera. Android (LOS) remains intact on slot B.
|
||||
836
saf/device/audio.md
Normal file
836
saf/device/audio.md
Normal file
|
|
@ -0,0 +1,836 @@
|
|||
# Audio — WCD9340 / SLIMbus
|
||||
|
||||
## Current — 2026-08-07
|
||||
|
||||
This is a working phone audio stack, not an active microphone bring-up.
|
||||
|
||||
- **Runtime:** PipeWire + WirePlumber + pipewire-pulse + pipewire-alsa. Native
|
||||
PulseAudio and the ABI-provider shims are historical experiments, not the
|
||||
running authority.
|
||||
- **HiFi:** both CS35L36 speakers play; the WCD9340 microphone captures clean
|
||||
mono audio through DMIC4 → DEC5/TX5. Casey uses it regularly for STT.
|
||||
- **Calls:** the `Voice Call` UCM profile, `souveraine-callaudio`,
|
||||
`souveraine-q6voiced`, and `souveraine-callaudiod` carry real two-way calls
|
||||
and the earpiece/speaker path.
|
||||
- **Persistence:** WirePlumber restores volumes and the selected profile. Do
|
||||
not add another volume writer; role policy remains TASK-39.
|
||||
- **One hardware debt remains:** the kernel permanently holds codec sysclk and
|
||||
MICB2 from probe. Audio works, but the hold costs idle power and blocks a
|
||||
clean suspend story. TASK-07 owns removing it.
|
||||
|
||||
The July **“UN-PIN TEST PASSED”** below removed the userspace always-open mic
|
||||
pin. It did **not** remove kernel commit `cd7758eae`'s sysclk + MICB2 hold; that
|
||||
similar noun is the likely source of the remembered “we resolved it.”
|
||||
|
||||
The implementation lives in Pixel3Arch under `pkgs/souveraine-ucm-blueline`,
|
||||
`pkgs/souveraine-wireplumber-blueline` (which packages the canonical
|
||||
WirePlumber policy from `rootfs-overlay/`), `pkgs/blueline-callaudio` (package
|
||||
name `souveraine-callaudio`), `pkgs/souveraine-callaudiod`, and
|
||||
`pkgs/q6voiced` (package name `souveraine-q6voiced`).
|
||||
|
||||
Everything below is the evidence ledger. Headings such as “still open” describe
|
||||
the date beneath them unless the current block above repeats the defect.
|
||||
|
||||
## UCM PROFILE GONE AT BOOT — 2026-08-16, fixed same day
|
||||
|
||||
Boot-time music died after kernel `7.1.1.r162.g457a667bd3eb` (installed
|
||||
2026-08-13, first boot 08-16 00:46): the card came up with only `off` and
|
||||
`pro-audio`, no UCM profiles, so no HiFi sink existed at boot. In-session
|
||||
audio (calls, STT mic) kept working — the regression only hit the fresh-boot
|
||||
state, which is why it surfaced as “music at boot”.
|
||||
|
||||
Measured chain, all on-device:
|
||||
|
||||
- **Kernel made routed capture strictly mono 48k.** The wcd934x
|
||||
recorder-state work in r162 (`b30e849be` “program all DMIC interface pads”,
|
||||
`9610ef9cf` “replay complete blueline recorder state”) rejects stereo
|
||||
capture hw_params on the routed MultiMedia2 (`CHANNELS: [1 4]` advertised,
|
||||
but `arecord -c 2` → -EINVAL, `-c 1` → fine).
|
||||
- **ACP probes with declared UCM values.** The Mic devices declared no
|
||||
`CaptureChannels`/`CaptureRate`, so ACP assumed stereo at the FE-default
|
||||
rate → capture probe failed -EINVAL → every profile “not supported” → ACP
|
||||
fell back to `pro-audio` (priority 1) at boot.
|
||||
- **Secondary lock, not the cause:** `90-blueline.conf` sets
|
||||
`session.suspend-timeout-seconds = 0` on `~alsa_output.*`, so the pro-audio
|
||||
sink never released hw:0,0; later probes died “Device or resource busy”.
|
||||
The persisted `default-profile` state (wireplumber saves the selected
|
||||
profile, by design) then re-pinned `pro-audio` each boot.
|
||||
- **Fix:** declared `CaptureChannels 1` + `CaptureRate 48000` in both Mic
|
||||
devices (HiFi + VoiceCall). ACP probe now succeeds; all three UCM profiles
|
||||
form; card activates HiFi at boot. Tone verified audible on glass; mic
|
||||
capture nonzero through the UCM source. Shipped as
|
||||
`souveraine-ucm-blueline 0.1.0-2` (commit `9f0f103`, CI run 1696);
|
||||
rootfs-overlay UCM copies kept identical to the package.
|
||||
- **Not shipped, known hardening:** scope the never-suspend output rule to
|
||||
`~alsa_output.platform-sound.*__sink` (the UCM sinks) so a stray pro-audio
|
||||
fallback can never hold hw:0,0 forever.
|
||||
|
||||
Probe mechanics learned (canonical, 2026-08-16):
|
||||
|
||||
- `spa-acp-tool -v -v hw:0` is the ACP probe; “Probing profile X” with no
|
||||
failure lines after it = supported. The trailing “unknown command hw:0” is
|
||||
a harmless arg-parsing quirk.
|
||||
- `alsaucm -c hw:0 set _verb HiFi` is the real-card UCM branch; it runs the
|
||||
verb EnableSequence (and enables devices — TX5 mapping included). `-c G3`
|
||||
and `-c "Google Pixel 3"` hit the *no-hw* branch (conf.virt.d / legacy
|
||||
paths) and fail ENOENT even when UCM is healthy — red herrings.
|
||||
- `alsaucm list profiles` / `get _verbs` fail with ENOENT quirks on this
|
||||
box; they are not valid probes.
|
||||
- `cat /proc/asound/card0/pcm*p/sub0/status` shows `owner_pid` — the way to
|
||||
find who holds a PCM.
|
||||
- Raw `arecord -D hw:0,1` only works while the verb routes are applied;
|
||||
`spa-acp-tool` runs DisableSequence on exit and tears them. Re-apply the
|
||||
verb before raw capture tests.
|
||||
|
||||
## PIPEWIRE RESTORED — 2026-07-19 late evening
|
||||
|
||||
The PA-native arrangement is retired. Live state, all verified on-device:
|
||||
|
||||
- **Stack:** pipewire 1.6.8 + wireplumber 0.5.15 + pipewire-pulse +
|
||||
pipewire-alsa. The shims (`pipewire-abi-provider`, `pulse-native-provider`)
|
||||
and the patched `pulseaudio-q6` are removed (rollback pkg kept at
|
||||
`~/build/pulseaudio-q6-r3/`; `/etc/pulse` backed up as
|
||||
`/etc/pulse.bak-20260719`). The `enable-pipewire` gate drop-ins were
|
||||
deleted — PipeWire is unconditional now.
|
||||
- **UCM discovery works under ACP/wireplumber.** HiFi verb → Speaker sink +
|
||||
Mic source appear named from the UCM comments. The old `alsaucm -c G3`
|
||||
`-2` failure is an id-vs-longname CLI quirk; `-c "Google Pixel 3"` parses
|
||||
both verbs fine. Real clients are unaffected.
|
||||
- **Playback:** `51-blueline-audio-s16.conf` (S16LE + disable-mmap +
|
||||
generous periods, recovered verbatim from `8920c3c`) reinstated; chime
|
||||
confirmed audible on glass.
|
||||
- **Capture:** mic source pinned mono S16LE 48k, suspend disabled,
|
||||
period 4800 / headroom 9600 (`90-blueline.conf`). Nonzero on every test
|
||||
capture.
|
||||
- **UN-PIN TEST PASSED.** With the kernel sysclk hold (`cd7758eae`) and no
|
||||
loopback hold, two consecutive same-map reopens both captured nonzero
|
||||
(451k/470k nonzero samples). The userspace mic pin is DELETED — kernel
|
||||
hold alone is sufficient. Cold-boot confirmation pending.
|
||||
- **New kernel bug found and fixed:** any xrun → userspace
|
||||
`snd_pcm_prepare()` re-enters wcd934x prepare without shutdown →
|
||||
`slim_stream_prepare` -EINVAL "Stream already Prepared" retried ~50/s
|
||||
forever (PipeWire recover loop starved the whole graph). Fix
|
||||
`f56b1ae08` on `mic-race-fix`: per-dai `prepared` flag, re-prepare
|
||||
no-ops, stream stays armed (alternation-law friendly). Wireplumber
|
||||
headroom masks the common case until the kernel is installed.
|
||||
- **VoiceCall.conf `hw:0,5` → `hw:0,2`** fixed on-device + repo.
|
||||
- **Voice-call discovery:** VoiceMMode1 (`hw:0,2`) open fails -EINVAL until
|
||||
the voice BE routes (`SLIMBUS_0_RX Voice Mixer VoiceMMode1`,
|
||||
`VoiceMMode1 Capture Mixer SLIMBUS_0_TX`) are applied — DPCM
|
||||
no-backend rejection. With routes applied the PCM opens. This is why
|
||||
q6voiced logged "Failed to open tx/rx: Invalid argument". ~~Because the
|
||||
PCM is unprobeable, ACP can never synthesize a VoiceCall profile, so
|
||||
callaudiod's profile-switching path is structurally dead on this card.~~
|
||||
**Wrong — see 2026-07-29.** ACP never probes `hw:0,2`; it probes the PCMs
|
||||
the verb *declares*, and the profile was lost for two reasons of our own
|
||||
making. Shipped `blueline-callaudio-hook` (system service): MM `CallAdded`
|
||||
→ UCM voice verb, last call terminating → HiFi verb restore. q6voiced then
|
||||
opens an already-routed PCM.
|
||||
|
||||
## PHONE CALLS WORK — TWO-WAY AUDIO ON A REAL BEARER — 2026-07-29
|
||||
|
||||
Incoming call, answered, audio both directions, confirmed by ear at both ends.
|
||||
Five defects had to fall, every one of them ours:
|
||||
|
||||
- **Verb named `VoiceCall`.** alsa-lib's `SND_USE_CASE_VERB_VOICECALL` is
|
||||
`"Voice Call"`, with the space, and callaudiod accepts only a profile
|
||||
carrying that prefix (`cad-pulse.c:508`). Renamed.
|
||||
- **The voice verb gave MultiMedia1 no backend.** HiFi routes it through
|
||||
`QUAT_TDM_RX_0`; the voice verb routed nothing, so opening `hw:0,0` under
|
||||
the verb returned -EINVAL and ACP dropped the profile — *"Profile 'Voice
|
||||
Call' mapping 'Voice Call: Speaker: sink': output PCM open failed"*,
|
||||
`spa-acp-tool -v -v`. That, not an unprobeable `hw:0,2`, is why no voice
|
||||
profile ever appeared. The card carries one now.
|
||||
- **Downlink went to `SLIMBUS_0_RX`.** blueline's earpiece and loudspeaker are
|
||||
both CS35L36 amps on `QUAT_TDM_RX_0`; the WCD9340 is the uplink and the
|
||||
headset jack. Voice sent to the codec reached nothing — audible as a call
|
||||
the far end heard perfectly while this end heard silence.
|
||||
- **A front-end takes exactly one backend.** These mixers are *not* additive:
|
||||
enabling a second silently clears the first, measured in both directions.
|
||||
Setting `QUAT` and `SLIMBUS` together is not belt-and-braces, it is a no-op.
|
||||
- **q6voiced latches a failed open.** tinyalsa's `pcm_open()` returns a
|
||||
non-NULL handle on failure and `q6voiced_open()` guards on `if (v->tx)`, so
|
||||
one lost race silences the whole call. Reachable on any outgoing call:
|
||||
`mm_state_is_active()` counts DIALING, emitted in the same instant as
|
||||
`CallAdded`. Incoming calls are safe only by accident — RINGING_IN is not
|
||||
counted active, so their open lands well after the routes. Patched in
|
||||
`souveraine-q6voiced` to release and retry.
|
||||
|
||||
Two further findings:
|
||||
|
||||
- **The mic died after every call**, not just occasionally. `alsaucm` holds its
|
||||
UCM context in-process, so a one-shot `set _verb HiFi` has no previous verb
|
||||
to dismantle and never runs its `DisableSequence`. TX5 and TX7 both stayed
|
||||
mapped into `AIF1_CAP`, SLIM_0_TX carried two channels into a mono front-end,
|
||||
and capture read exact zeros. The hook now unmaps the outgoing leg explicitly.
|
||||
- **callaudiod cannot own earpiece/speaker on this card.** It switches a sink
|
||||
*port* (`cad-pulse.c:961`) and needs both port types on one sink, but ACP
|
||||
models UCM devices as one profile per combination — `Voice Call (Earpiece,
|
||||
Mic)` and `Voice Call (Mic, Speaker)`. `ConflictingDevices` does not change
|
||||
it. enchilada sidesteps this by giving each transducer its own PCM; blueline
|
||||
has both amps behind one. The toggle is therefore
|
||||
`blueline-callaudio-hook speaker on|off`, writing the amps directly.
|
||||
|
||||
## ROOT CAUSE FOUND AND FIXED — MICS CAPTURE REAL AUDIO — 2026-07-19
|
||||
|
||||
**The wall was the codec master clock.** Mainline DT fed the WCD9340's `extclk`
|
||||
from `RPMH_LN_BB_CLK2` (inherited from the reference-board dtsi). On blueline,
|
||||
Android's own DT gives **LN_BB_CLK2 to the Pixel Visual Core (`mnh-sm`)** and
|
||||
feeds the codec from **`clock_rpmh` index 4 = LN_BB_CLK3** (verified by
|
||||
decompiling the phone's `dtbo_b`; `audio_ext_clk_lnbb` → `<&clock_rpmh 0x04>`).
|
||||
The RPMh vote on the wrong buffer succeeds, so nothing errors — the codec just
|
||||
never receives 19.2 MHz. That single fault explained the entire residue:
|
||||
|
||||
- eFuse sensing timed out every boot (`-110`, `status=0x0`) — needs MCLK, no
|
||||
ports involved. After the fix: `eFuse sense complete: status=0x01 out14=0xda
|
||||
out15=0x01` — first time ever on mainline.
|
||||
- Analog AND digital capture read zeros (whole audio core unclocked) while
|
||||
every SLIM control write ACKed (SLIMbus clock is separate).
|
||||
- IRQ 213 (codec SLIM data line) never fired in the machine's life; after the
|
||||
fix it fires on every capture.
|
||||
- MBHC read garbage impedance at probe.
|
||||
|
||||
Fix: `&wcd9340 { clocks = <&rpmhcc RPMH_LN_BB_CLK3>; }` in
|
||||
`sdm845-google-common.dtsi` (committed in the kernel tree). Deployed initially
|
||||
as a verified one-byte DTB patch (`/boot/dtbs/sdm845-google-blueline.dtb`,
|
||||
backup `.lnbb2-orig` beside it; kexec chain reads the DTB from `/boot`, NOT
|
||||
from the boot_a image — see below).
|
||||
|
||||
**Live capture results after the fix (kernel `g8cbfbb939327`, same boot):**
|
||||
- AMIC1 → ADC1 → DEC0 → TX0: 121,724 / 144,000 nonzero samples
|
||||
- DMIC0 → DEC7 → TX7 (`hw:0,1`): 94,198 nonzero
|
||||
- DMIC4 → DEC5 → TX5 (Android triple-mic route): 81,545 nonzero
|
||||
- DMIC3 → DEC6 → TX6 (Android triple-mic route): 92,216 nonzero
|
||||
- DMIC4 → DEC7 → TX7 (Android handset-mic route): 124,050 nonzero
|
||||
|
||||
All three physical mics (DMIC0/3/4 per Android's `triple-mic` path) and the
|
||||
analog path produce real ambient audio through plain ALSA `hw:0,1` —
|
||||
**no HAL, no ADM usecase, no ACDB OEM tree in the loop**. This falsifies the
|
||||
"ADM usecase routing is required for samples to flow" hypothesis from the
|
||||
2026-07-19 live-reconciliation doc, and retroactively confirms every "codec
|
||||
registers are bit-perfect but silent" audit: the registers were right; the
|
||||
silicon was unclocked.
|
||||
|
||||
### Boot chain discovery (why kernel deploys "never landed")
|
||||
`boot_a` holds pocketboot (LinuxBoot, a 7.2.0-rc1 first-stage kernel). It
|
||||
kexecs `/boot/vmlinuz-blueline` + `/boot/dtbs/sdm845-google-blueline.dtb` per
|
||||
`/boot/extlinux/extlinux.conf` on Arch_root (`linux,booted-from-kexec` in the
|
||||
live chosen node). Flashing boot_a via fastboot does NOT change the running
|
||||
kernel/DTB — install to `/boot` on the rootfs instead.
|
||||
|
||||
### Then-open capture intermittency — superseded later on 2026-07-19
|
||||
- Ordering effect observed on the first post-fix boot: DMIC3/4 through
|
||||
MUX6/MUX7 read zero until the DEC5/DEC6 (Android-native) routes ran once;
|
||||
after that, the previously-dead MUX7+DMIC4 handset route worked. Some
|
||||
first-use state sticks. Full codec+IFD regmap snapshots of the known-good
|
||||
state saved on-phone (`/tmp/codec-good-state.txt`, `/tmp/ifd-good-state.txt`
|
||||
— copy off before they evaporate; /tmp is tmpfs).
|
||||
- The last test of the session (speaker-tone loopback) read all-zero again
|
||||
right after a string of successes — cause unestablished. Per the
|
||||
live-reconciliation doc's warning, record airplane/radio/mode state before
|
||||
interpreting ANY zero capture from now on.
|
||||
- Android runtime parity notes for the diff: Android recording state has
|
||||
`0xd25 = 0x11` (ADC MUX3/7 mode reg) where ours reads 0x00 even while
|
||||
working; pads `0x8015-0x8018 = 0x00`, `0x8019/1a = 0x0a` — identical to what
|
||||
our kernel programs (my in-session debugfs pad pokes don't persist).
|
||||
- Regmap-cache caveat from the reconciliation doc applies to OUR debugfs reads
|
||||
of IFD/PGD registers too: prefer live-read paths when auditing those.
|
||||
|
||||
### THE ALTERNATION LAW — exact zombie mechanism (2026-07-19, evening)
|
||||
Every capture result from all three boots today reduces to ONE deterministic
|
||||
rule: **a SLIM TX capture produces samples only when its channel map differs
|
||||
from the previous capture session's map. Re-opening with the identical map
|
||||
always reads zeros.** Verified live: TX5→TX5 = work,zero; TX5→TX6→TX5 =
|
||||
work,work,work; boot-first TX7 always zero (ch135 is pre-touched by ADSP
|
||||
boot/cal state, so the first TX7 session counts as a same-map reopen).
|
||||
Everything earlier read as input-specific (DMIC3/4 dead), power-related
|
||||
(PA holding sysclk), or "priming" was test-order artifact of this law.
|
||||
- Codec side is fully correct per-session (DEF_ACT_CHAN wbuf identical
|
||||
between dead and working sessions except TID; teardown calls
|
||||
slim_stream_unprepare which sends DEACTIVATE/REMOVE/RECONFIGURE per
|
||||
drivers/slimbus/stream.c).
|
||||
- Clock gates during a zombie capture are bit-identical to Android's working
|
||||
recording (0x711=0x91 0xd41=0x01 0xd42=0x0d).
|
||||
- Conclusion: the ADSP's SLIM-master/AFE side treats an identical re-DEFINE
|
||||
as a no-op and never re-arms the data path. Android never hits this because
|
||||
its HAL/ADM re-arms per session (the reconciliation doc's ADM hypothesis,
|
||||
refined: not "ADM required for routing" but "ADM masks the stale-restart").
|
||||
- NEXT INSTRUMENTATION: log outgoing MCs in qcom_slim_ngd xfer path and
|
||||
diff same-map vs changed-map session teardown/setup on the wire; then
|
||||
candidate kernel fix = force a map perturbation or explicit port
|
||||
disconnect+reconnect on every SLIM capture start.
|
||||
- Userspace mitigation shipped meanwhile (see 90-blueline-alsa.pa): a
|
||||
null-sink loopback holds blueline_mic open from PA start — the boot-time
|
||||
open is on virgin TX5 (works), and a held-open source never re-opens, so
|
||||
the law is never violated. VALIDATED on true cold boot 2026-07-19 12:09:
|
||||
blueline_mic RUNNING at boot, 262k/263k nonzero samples first client,
|
||||
two back-to-back client sessions both live (peaks 5256/5590/26044).
|
||||
Costs continuous capture power (remove once the kernel fix lands).
|
||||
|
||||
### THE BOOT RACE — pinned boot capture is a coin flip; kernel + config were red herrings (2026-07-19 evening)
|
||||
Journal survey of ALL 9 boots of 2026-07-19 (`journalctl -b -8 .. -b 0`,
|
||||
MICDBG sysclk lines at the pin's boot-time capture open):
|
||||
|
||||
| boots | kernel | at pin open | MICB2 | outcome |
|
||||
|---|---|---|---|---|
|
||||
| -7 | g323b297 | `users=2 skip` (joined a live hold) | 0x50 | 262k WORKING |
|
||||
| -3 | gbdf098937 | `users=2 skip` | 0x50 | never tested (!) |
|
||||
| -8 | g323b297 | `users=1 full bring-up` after `users=0 HW POWER-OFF` | 0x10 | zero |
|
||||
| -6 -5 -4, 0 | gbdf098937 | same losing pattern | 0x10 | zero |
|
||||
| -2 -1 | gfdb22e5 | same losing pattern | 0x10 | zero |
|
||||
|
||||
An early boot-time actor grabs codec sysclk ~3s after boot, holds ~3s, and has
|
||||
MICB2's 0x40 bit up while it holds. If PA's pin lands INSIDE that window the
|
||||
capture joins as user 2 and samples flow; if it lands after the release (codec
|
||||
did a HW POWER-OFF), capture does its own full bring-up and reads zero on every
|
||||
route for the rest of the boot — the same persistent zombie the PA-stop
|
||||
experiment produced. Same kernel both wins and loses (g323b297: won -7, lost
|
||||
-8; gbdf098937: won -3, lost four). Therefore:
|
||||
- **QRTR=y (`fdb22e5`/`f245323`) is exonerated.** Every "dead on fdb22e5"
|
||||
observation was a lost race, sampled. Config stays QRTR=m / PD_MAPPER=m
|
||||
(Kconfig forces PD_MAPPER ≤ QRTR anyway); it is orthogonal to the mic.
|
||||
- **The CHAN_CTRL-remove commit (`bdf098937`) is exonerated for "dead at
|
||||
boot"** — its code path is open-inert (only adds `disable_stream`, verified
|
||||
by diff) and it won the race on boot -3.
|
||||
- **The un-pin question is STILL unanswered.** It needs a race-winning boot:
|
||||
pinned baseline nonzero, `pactl unload-module` the loopback, then same-map
|
||||
reopen; nonzero reopen = pin can be deleted.
|
||||
- The early holder is unidentified; MBHC headset-detect is the prime suspect
|
||||
(boot-time codec user, raises MICB2 for impedance sensing). MICB2
|
||||
0x50-vs-0x10 tracks outcome 8/8 but is NOT yet proven causal — it may just
|
||||
fingerprint the hold. Verify before building on it: read Android's ANA_MICB2
|
||||
during a working slot-B recording, and/or set the 0x40 bit via debugfs during
|
||||
a fresh lost-race boot's capture.
|
||||
- Kernel fix direction (pick after causality check): wire the DMIC supply
|
||||
(micbias) into the capture DAPM path, or hold sysclk from probe until first
|
||||
capture so the boot window cannot close early.
|
||||
|
||||
### Afternoon findings — zombie state + wedge reproduction (2026-07-19)
|
||||
- Cold-boot experiment (radios all on, recorded): first `arecord` after boot
|
||||
opened ONLY the FE — zero kernel-side codec/BE activity, zeros. Second and
|
||||
third captures (any route) worked. The morning "DEC5 unlocks MUX7" story is
|
||||
partly this: two distinct zero-classes exist — (a) BE-never-opened,
|
||||
(b) BE-open-but-silent.
|
||||
- Stopping PulseAudio (releasing the card → last sysclk user drops →
|
||||
`wcd934x_disable_ana_bias_and_syclk` powers the codec down) put capture into
|
||||
a persistent all-zero state on EVERY route, and restarting PA did not
|
||||
recover it. Mixer state is bit-identical PA-up vs PA-down (diff empty), so
|
||||
it is a power-lifecycle effect, not mixer state.
|
||||
- In the zombie state, live volatile reads DURING capture show the analog
|
||||
domain healthy: `ANA_BIAS=0x80 BUCK=0x07 MICB4=0x90`. The bias/buck theory
|
||||
is dead. Codec-internal clock gates (0xd41/0xd42) were not yet live-readable
|
||||
— instrumented build `323b29715` makes them volatile and logs
|
||||
enable/disable/sido lifecycle + per-capture clock-gate reads.
|
||||
- Manually zeroing `CLK_SYS_MCLK_PRG` (0x711) via debugfs wedged the SLIM bus
|
||||
into the historical `-110` NACK loop (`Failed to read IRQ status: -110`,
|
||||
`Error Interrupt 0x82000000`, 1 Hz). One historical "SLIM wedge" class is
|
||||
therefore reproducible as pure codec-clock-state loss — consistent with the
|
||||
2026-07-19 warning that not every wedge was a bus/driver fault. Phone needs
|
||||
a cold boot after this (self-inflicted, cause exactly known).
|
||||
- Upstream comparison: `enable_ana_bias_and_sysclk`/`disable` and the
|
||||
sido-cache skip are upstream-inherited structure, not our divergence; our
|
||||
only body change is the Android-parity buck sequence (captures work with
|
||||
it). No structural smoking gun found by code audit — hence instrumentation.
|
||||
|
||||
## SLIM NACK loop confirmed boot-invariant on a real cold boot — 2026-07-15
|
||||
|
||||
- Fixed three misidentified raw-hex register writes in
|
||||
`wcd934x_apply_blueline_android_tx7_capture_setup()` (kernel `5199244ee0ee`):
|
||||
`0x0b07-0x0b3f` were RX playback compander `CTL6` registers (not TX
|
||||
per-path enables), `0x0c96` is `WCD934X_SWR_AHB_BRIDGE_ACCESS_STATUS` (a
|
||||
status register, not a TX enable — writing it was the suspected cause of a
|
||||
SLIM wedge seen on the prior build `9610ef9cf45a`), and `0x0d1e`/`0x0d25`
|
||||
are `ADC_MUX0_CFG1`/`ADC_MUX3_CFG1` (TX0/TX3, not TX7/TX8 as the old
|
||||
comment claimed). Verified against downstream
|
||||
`techpack/include/asoc/wcd934x_registers.h`. This fix did NOT resolve the
|
||||
SLIM wedge — see below.
|
||||
- The recurring error is a genuine **NACK**, not a bus hang:
|
||||
`qcom,slim-ngd-ctrl: Error Interrupt received 0x82000000` decodes to
|
||||
`NGD_INT_TX_MSG_SENT (BIT 31) | NGD_INT_TX_NACKED_2 (BIT 25)` — the
|
||||
message transmitted and the target actively refused it. The target is
|
||||
`LA:0xcf` (the WCD934x codec's assigned logical address, confirmed not
|
||||
`SLIM_LA_MGR`/`0xff`), message code `0x60` =
|
||||
`SLIM_MSG_MC_REQUEST_VALUE`. The read is `regmap-irq`'s generic
|
||||
`regmap_irq_thread()` polling the codec's IRQ status registers
|
||||
(`drivers/base/regmap/regmap-irq.c`) — the codec's IRQ line is
|
||||
`IRQF_TRIGGER_HIGH` (level), so a failed status read never clears it and
|
||||
it refires every ~1s, forever, from ~1-2min post-boot onward.
|
||||
- Tested with a **user-operated true power-off (`shutdown -h now`, not
|
||||
`fastboot reboot`) followed by manual power-on**, specifically to rule
|
||||
out the in-place-ADSP-restart degradation `b0b813f` warned about. Result:
|
||||
the NACK loop reproduces identically on this real cold boot (confirmed
|
||||
climbing at 110s uptime, same `MC:0x60,LA:0xcf` signature). **This is not
|
||||
a warm-reboot or ADSP-restart artifact** — it is boot-invariant, matching
|
||||
the entry below from earlier the same day.
|
||||
- `blueline-hexagonrpcd-adsp.service` (`b0b813f`, still uncommitted to
|
||||
gitea as of this entry) attaches cleanly on this same cold boot — no
|
||||
fluence/ECNS/AVS module errors, only the unrelated `lowi_lp.conf` miss.
|
||||
So the ADSP audio-PD HexagonFS gap is fixed and not the current blocker;
|
||||
capture still fails at the ALSA `hw_params` layer (`arecord` "Unable to
|
||||
install hw params") before the ADSP module question is even reachable,
|
||||
because the SLIM NACK loop blocks it upstream.
|
||||
- **Current standing blocker, plainly:** the SLIM NACK loop on `LA:0xcf`.
|
||||
Not yet root-caused. Two real, separate bugs were found and fixed
|
||||
tonight (the register-write bug above, and the ADSP HexagonFS gap in
|
||||
`b0b813f`); this is a third, still-open one.
|
||||
|
||||
## Cold-boot SLIM-NGD correction — 2026-07-14
|
||||
|
||||
- A user-operated full power-off while unplugged, followed by power-on, still
|
||||
logs `qcom,slim-ngd-ctrl ... QMI wait timeout` at 12:29:52, immediately
|
||||
after ADSP boot/WCD934x probe, followed by repeated `SLIM SAT: Rcvd master
|
||||
capability`. This happened before any ALSA/Pulse microphone client opened.
|
||||
- Therefore the timeout/capability loop is **not merely a warm-reboot or
|
||||
long-uptime artifact**. ADSP, CDSP, SLPI, and MSS all reach `running`, so
|
||||
the fault is specifically in the ADSP↔NGD SLIM initialization exchange and
|
||||
is now the primary boot-invariant microphone lead. Do not restart ADSP or
|
||||
NGD while investigating; preserve this state for tracing.
|
||||
- The adjacent WCD MBHC threshold/impedance messages concern headset/accessory
|
||||
detection (including the available USB-to-mic adapter), not the target
|
||||
internal DMIC4 → DEC7 → TX7 capture path.
|
||||
|
||||
## Q6 VoiceMMode1 call path — staged 2026-07-14
|
||||
|
||||
- This is deliberately separate from the still-silent raw `MultiMedia1/2`
|
||||
capture path. Android proves the board microphone works; normal PCM remains
|
||||
an ADM/calibration problem, while modem calls use the hostless Q6Voice path.
|
||||
- The missing board wiring was real: `sdm845.dtsi` left Q6 MVM/CVS/CVP
|
||||
disabled and blueline had no `VoiceMMode1` DAI/card link. Kernel commit
|
||||
`551d3bb1b` enables those services and instantiates the link on the tested
|
||||
Type-C baseline. Its matching module tree is installed on the phone at
|
||||
`7.1.1-sdm845-g551d3bb1bd35`; its boot image is staged locally.
|
||||
- The preserved prior device package, found at
|
||||
`archive/pmos-20260709/pmaports/device/testing/device-google-blueline/`,
|
||||
is the authoritative board data: `VoiceMMode1` enumerates as **`hw:0,5`**,
|
||||
not the raw MultiMedia devices. Its exact `VoiceCall.conf` routing
|
||||
(`SLIMBUS_0_RX Voice Mixer VoiceMMode1`, `VoiceMMode1 Capture Mixer
|
||||
SLIMBUS_0_TX`, DMIC4→DEC7→TX7) is installed in the Arch UCM overlay.
|
||||
- `q6voiced-blueline` was built and installed natively, with that exact PCM-5
|
||||
configuration. Its system unit is deliberately **disabled until the new
|
||||
kernel cold-boot confirms `00-05: VoiceMMode1`**; enabling it on the old
|
||||
kernel would create meaningless restart noise. First test must be a clean
|
||||
cold boot and an actual ModemManager call *before any HiFi/raw-capture
|
||||
experiment*, because the historical second call failure was SLIMBUS RX
|
||||
already claimed by earlier HiFi activity.
|
||||
|
||||
## Live state — 2026-07-12 evening (mic)
|
||||
|
||||
**Live Android Recorder ground truth — 2026-07-14:** On the rooted Lineage
|
||||
slot-B reference system (`4.9.337-gb929452c5c49`), an ordinary
|
||||
`org.lineageos.recorder` recording was observed live, not inferred from XML.
|
||||
It opens **MultiMedia1 / PCM device 0** (not MultiMedia2): its userspace input
|
||||
is mono S16_LE at 8 kHz, while the active backend is
|
||||
`SLIMBUS_0_TX`, mono S16_LE at 48 kHz. The downstream HAL enables
|
||||
`DMIC4 -> DEC7 -> TX7`; `SLIM_0_TX Channels=One`; and its active capture
|
||||
app-type control is `[69938, 4, 48000, 3]`: downstream's documented field
|
||||
order makes that `app_type=69938` (the Qualcomm default TX-path app type),
|
||||
`acdb_dev_id=4`, `sample_rate=48000`, `backend_id=3`. The capture track was active and
|
||||
non-silent in AudioFlinger. Crucially, Android's dedicated WCD SLIM-interface
|
||||
debug dump during that recording read `0x032=0x80`, `0x057=0x05`,
|
||||
`0x097=0x21`, and `0x11c=0x80`. (`tavil-slim-pgd` regmap is codec register
|
||||
space, not this interface-device space.) Every earlier Arch microphone
|
||||
negative test targeted `hw:0,1` / MultiMedia2 at 48 kHz. The exact
|
||||
**MultiMedia1 `hw:0,0` 8-kHz FE / 48-kHz SLIMBUS backend** retest then ran on
|
||||
Arch with TX7/DMIC4 and the active speaker RX route preserved: its 8-second
|
||||
raw payload was 128,000 bytes / **zero non-zero samples**. (An earlier
|
||||
apparent 19-sample result was immediately identified as `arecord`'s 44-byte
|
||||
WAV header and corrected with `-t raw`.) This closes the FE/rate mismatch as a
|
||||
microphone fix. The Android HAL's ADM identity and ACDB path is now the
|
||||
leading differential: it programs this FE's app type, topology, and device
|
||||
calibration, whereas mainline q6routing passes zeros.
|
||||
|
||||
**DEF_ACT_CHAN client-field test — 2026-07-13:** The Android downstream NGD
|
||||
driver encodes the first `DEF_ACT_CHAN` payload byte as
|
||||
`(data_fmt << 5) | (laddr & 0x1f)`; mainline had sent the full WCD9340 logical
|
||||
address (`0xcf`) and consequently put its high bits into the data-format field.
|
||||
The one-line correction was built on the current Type-C/G56 test base as
|
||||
`3ceab71eded0`, its matching module tree was installed before flashing, and
|
||||
the resulting `7.1.1-sdm845-g3ceab71eded0` boot was verified on glass. A
|
||||
controlled single-direction `DMIC4 → DEC7 → TX7 → MultiMedia2` 48 kHz mono
|
||||
capture (TX6 disabled) produced `384000` bytes / `192000` samples, with **0
|
||||
non-zero bytes** and signed-16 range `0..0`. ALSA state restored (only the
|
||||
known read-only HPH controls complained); no new SLIM/Q6/NGD timeout or audio
|
||||
error appeared. This fix is a valid protocol correction and must remain, but
|
||||
it is **not sufficient to make blueline microphone capture work**.
|
||||
|
||||
**Android DMIC-pad + analog split test — 2026-07-13:** Android's WCD9340
|
||||
initial register table writes `DMIC3_CLK_PINCFG`/`DMIC3_DATA_PINCFG`
|
||||
(`0x8019`/`0x801a`) to `0x0a`; mainline defined but never programmed those
|
||||
registers. That exact pair was added to WCD934x component initialization,
|
||||
built as `959f0d5e6`, and booted as
|
||||
`7.1.1-sdm845-g959f0d5e65fc`. The normal Android handset route
|
||||
`DMIC4 -> DEC7 -> TX7 -> SLIMBUS_0_TX -> MultiMedia2` remained exactly zero
|
||||
(384000 bytes / 192000 signed-16 samples / zero non-zero bytes), without a
|
||||
capture-time SLIM, Q6, or ADSP error. A separate reversible replay of
|
||||
Android's analog fallback `AMIC1 -> ADC1 -> DEC6 -> TX6` also produced the
|
||||
same exact zero stream. Thus neither the DMIC3 pad configuration nor a
|
||||
bottom-DMIC/flex-only fault can explain the failure: the common remaining
|
||||
boundary is codec TX channel activation and/or the ADM/ADSP capture path.
|
||||
The only ALSA restore errors were the known read-only HPH impedance/type
|
||||
controls.
|
||||
|
||||
**ADM/calibration lead — 2026-07-13 (refined 2026-07-14):** Mainline `q6routing_stream_open()`
|
||||
unconditionally passes `NULL_COPP_TOPOLOGY` and app/acdb values of zero to
|
||||
`q6adm_open()`. Pixel Android's `msm-pcm-routing-v2.c` starts with that
|
||||
fallback but looks up topology, app type, and ACDB device ID by FE/BE/session
|
||||
from audio-calibration tables before opening ADM. Do not replace the topology
|
||||
with a guessed constant: the live Android `MultiMedia1 + SLIMBUS_0_TX`
|
||||
recorder has now identified `app_type=69938`, `acdb_dev_id=4`,
|
||||
`sample_rate=48000` (and backend ID 3). The exact ADM topology was decoded
|
||||
from the extracted board-matched ACDB on 2026-07-14; see the next entry.
|
||||
|
||||
**ADM identity-only test — 2026-07-14 (negative):** Kernel
|
||||
`7.1.1-sdm845-g3b9cf346af3a` passed Android's observed `app_type=69938` and
|
||||
`acdb_dev_id=4` only for live `SLIMBUS_0_TX` capture, leaving the topology
|
||||
`NULL` rather than inventing one. The matching modules were installed before
|
||||
the pmOS-chain `boot_a` image was flashed; the running kernel and modules
|
||||
matched. A controlled `MultiMedia1` / `hw:0,0`, mono S16_LE 8 kHz raw capture
|
||||
with the Android-equivalent `DMIC4 -> DEC7 -> TX7` route yielded 128,000 bytes
|
||||
with **zero non-zero samples**. ALSA state was restored (only the known
|
||||
read-only HPH controls refused writes). This eliminates the two ADM identity
|
||||
numbers alone as a microphone fix. The remaining Android differential is the
|
||||
calibration-selected ADM topology and/or downstream codec-side programming.
|
||||
|
||||
**Board ACDB topology decoded — 2026-07-14:** The local Lineage vendor blob
|
||||
`blobs/los-vendor/etc/acdbdata/OEM/sdm845-tavil-b1-snd-card/Handset_cal.acdb`
|
||||
is not merely a future reference: `acdb-ldp` decodes its `HANDSET_MIC` device
|
||||
4 configuration map as `69938 -> 0x00010315` (at 48 kHz). Downstream's
|
||||
`apr_audio-v2.h` names `0x00010315` as
|
||||
`ADM_CMD_COPP_OPENOPOLOGY_ID_MIC_MONO_AUDIO_COPP`; it is the specific Android
|
||||
mono-microphone COPP, rather than the mainline null passthrough
|
||||
`0x00010312` or a guessed default. This explains why the identity-only test
|
||||
was necessarily incomplete. The present 7.1.1 tree also contains q6core's
|
||||
optional ACDB topology-registration code, but blueline's q6core DT node has
|
||||
no `qcom,acdb-name` and no `Global_cal.acdb` is installed as firmware, so that
|
||||
path is currently dormant.
|
||||
|
||||
**ADM topology + AudProc test — 2026-07-14 (negative):** The current kernel
|
||||
opens `SLIMBUS_0_TX` capture with topology `0x10315`, app type 69938, and ACDB
|
||||
device 4, then parses and sends the board ACDB's complete 524-byte AudProc
|
||||
table. Live dmesg confirms `SLIMBUS_0_TX applied ACDB AudProc calibration:
|
||||
device=4 rate=48000 app=69938 (524 bytes)`, but capture remains exactly zero.
|
||||
This eliminates the selected COPP topology plus AudProc table as a complete
|
||||
fix; Android also sends the separate AudVol table.
|
||||
|
||||
**AFE calibration delivery audit — 2026-07-14:** All three local Pixel
|
||||
Handset ACDB variants agree. Device 4 has no `AFECLUT0` common-calibration
|
||||
row, and its `AFE_CMN_TOPID_V2` device property is `0x000112fb`, Qualcomm's
|
||||
`AFE_TX_TOPOLOGY_ID_NONE`. The only device-4 AFE datum is a 48-kHz AV-sync
|
||||
delay of 578, which cannot account for an all-zero stream. Qualcomm's SDM670
|
||||
ACDB loader passes the same device ID 4 to AFE (there is no hidden remap to
|
||||
the unrelated AFE-common IDs 100/128/130/149), tries topology, common table,
|
||||
then hardware delay. Therefore adding an AFE common/topology sender here
|
||||
would deliver no microphone payload. Do not revisit AFE delivery without new
|
||||
Android evidence.
|
||||
|
||||
**ADM AudVol + explicit SLIM-error test — 2026-07-14 (negative):** Device 4,
|
||||
app type 69938, volume index 0 decodes to a 40-byte table: soft-volume
|
||||
stepping parameters and mono gain `0x2000` (unity). Kernel
|
||||
`7.1.1-sdm845-gfb8f067e12f2` parses `AVOLLUT0`/`AVOLCDFT`/`AVOLCDOT` with the
|
||||
same validated generic table builder and sends AudProc then AudVol, matching
|
||||
Android's order. The same build propagates previously ignored failures from
|
||||
SLIM port connection and WCD934x stream prepare/enable. A controlled 5-second
|
||||
`DMIC4 -> DEC7 -> TX7 -> MultiMedia2` mono 48-kHz capture completed normally
|
||||
and produced 480,000 bytes / 240,000 samples, all exactly zero. Live dmesg
|
||||
confirms `SLIMBUS_0_TX applied ACDB AudProc/AudVol calibration: device=4
|
||||
rate=48000 app=69938 (524/40 bytes)` and contains no capture-time SLIM
|
||||
connect/prepare/enable, WCD934x, Q6, permission, or missing-file error.
|
||||
Therefore neither the missing AudVol table nor a silently discarded SLIM
|
||||
setup failure explains the zero stream.
|
||||
|
||||
Do not stream the ADSP-owned `217:250:0:0` debugfs regmap `registers` file
|
||||
during capture: a filtered read blocked after the capture had completed and
|
||||
had to be killed. This is consistent with the earlier observation that IFD
|
||||
`0x097` is controlled by the ADSP SLIM master; it is not a safe codec-style
|
||||
bulk register snapshot path and yielded no new state evidence.
|
||||
|
||||
**True cold-boot re-validation — 2026-07-13:** Kernel
|
||||
`7.1.1-sdm845-g44d43e6ef336` includes the WCD934x `.prepare`/`.shutdown`
|
||||
stream-lifecycle experiment. On a physical shutdown/power-on with native WCN
|
||||
boot healthy, the controlled DMIC4 → DEC7 → TX7 → MultiMedia2 raw capture was
|
||||
again exactly zero (`192000` bytes, `0` non-zero bytes). The temporary
|
||||
`mic-ngd` trace proves the entire kernel-visible sequence ran successfully:
|
||||
`q6afe_port_start ret=0`, `slim_stream_enable` entered,
|
||||
`qcom_slim_ngd_enable_stream` entered and returned `0`. There was no SLIM/Q6
|
||||
timeout. ALSA was restored immediately; the three HPH-impedance restore errors
|
||||
are documented read-only controls. **This falsifies the prepare-order patch as
|
||||
a microphone fix and makes silent ADSP-side channel activation (not userspace,
|
||||
Q6 return handling, or NGD error propagation) the current boundary.**
|
||||
|
||||
**Stable-SLPI follow-up capture — 2026-07-13:** After the repaired sensor
|
||||
registry cleared CHRE's five-minute watchdog and the SSC desktop sensor clients
|
||||
were exercised successfully, the same safe single-direction TX7/DMIC4 route
|
||||
was captured as 48 kHz mono raw audio for five seconds. It produced 480,000
|
||||
bytes / 240,000 samples, all exactly zero. ALSA state was restored by trap;
|
||||
SLPI, hexagonrpcd, and iio-sensor-proxy stayed active, with no `MC:0x2c`,
|
||||
`-110`, or CHRE fatal. The sensor repair therefore improves platform stability
|
||||
but does **not** open the microphone path.
|
||||
|
||||
**NGD/update audit — 2026-07-13:** The running 7.1.1 source already contains
|
||||
the complete upstream May-2026 NGD probe/SSR race-fix series that landed for
|
||||
7.2-rc: deferred IRQ enable, controller-owned workqueue initialization, safe
|
||||
SSR/PDR cleanup, and the `tx_lock` ABBA deadlock fix. It therefore must not be
|
||||
re-applied as a speculative microphone patch. This boot still logged one
|
||||
`QMI wait timeout`; that is the driver's one-second wait for the remote QMI
|
||||
service, not an `EPERM` or file-access failure. It remains a boot-stability
|
||||
lead, but the subsequent raw capture's core `CONNECT_SOURCE` transaction
|
||||
returned `0` and the audio data was still all zero.
|
||||
|
||||
- The on-device session's mic doc (collected to
|
||||
`docs/tasks/mic-slim-tx-20260712.md`, source
|
||||
phone `~/projects/unfinished/pixel3-microphone/`) found every capture
|
||||
start at ~23h uptime logging 3x `TX timed out:MC:0x2c` /
|
||||
`failed:-110` — the SLIMbus `CONNECT_SRC` user message to the ADSP
|
||||
master never answered — and called that the localized root cause.
|
||||
- **Re-test after a WARM reboot (same day, laptop-driven): the 0x2c
|
||||
timeout did not reproduce — but the boot was NOT clean.** That boot was
|
||||
produced by `sudo reboot` (warm), and its dmesg shows `QMI wait timeout`
|
||||
at 8.1s plus the repeating "Rcvd master capability" retry loop — the
|
||||
documented degraded slim-ngd state that warm reboots carry over (same
|
||||
TZ-poison family as the WiFi MSA issue; only an UNPLUGGED cold power-off
|
||||
clears it). Within that caveat: a `hw:0,1` capture with the doc's exact
|
||||
TX7/DMIC4 arming logged 37 slim transfers — all MC `0x60`/`0x68` value
|
||||
messages, zero errors, zero timeouts — and was still bit-for-bit zero,
|
||||
with IFD `0097/0320/0330/0970` all `00` mid-capture. **Both datapoints
|
||||
(0x2c timeout at 23h uptime; silent no-activation here) need
|
||||
re-validation on a true cold boot before either is trusted.**
|
||||
- No root-cause conclusion is valid yet. The `-110` may be a degraded-NGD
|
||||
artifact of long uptime / remoteproc restarts, while the silent all-zero
|
||||
capture may be a separate normal-state failure; the only attempted contrast
|
||||
was tainted by a warm reboot. Kernel-visible Q6 and NGD success plus Android's
|
||||
`IFD 0x097=21` versus mainline `00` remain observations, **not a proven
|
||||
boot-invariant activation failure**.
|
||||
- Note: `0320/0330` read `00` here vs `c0/01` in the 07-11 canonical 2ch
|
||||
trace — arming differed (single TX7 vs TX7+TX8); observation only.
|
||||
- What the phone doc contributes regardless: OnePlus 6 (enchilada) mic
|
||||
works on pmOS 6.x kernels with this same driver stack (6.x->7.x
|
||||
regression candidate in slimbus/ngd/wcd934x, or blueline-specific ADSP
|
||||
behavior); pmOS's blueline UCM ships with the whole mic section
|
||||
commented out (nobody has solved this on mainline); operational
|
||||
hazards — never restart the adsp remoteproc while debugging slim (NGD
|
||||
stays degraded until reboot, reproducible), never leave MultiMedia2
|
||||
with both `SLIMBUS_0_RX` and `SLIMBUS_0_TX` routes enabled (q6adm/q6asm
|
||||
wedge); QUAT_TDM_TX_0 in DT would give a WCD-free CS35L36 VMON/IMON
|
||||
capture reference.
|
||||
- Refined next steps: trace the `qcom_slim_ngd_enable_stream` user-message
|
||||
exchange on a fresh boot (is CONNECT_SRC sent and ACKed, or skipped?);
|
||||
diff `drivers/slimbus/{qcom-ngd-ctrl.c,stream.c,messaging.c}` +
|
||||
`wcd934x.c` against pmOS's 6.x sdm845 tree; if an enchilada is handy,
|
||||
boot this 7.1.1 kernel on it to split regression vs blueline-specific;
|
||||
ask #sdm845-mainline about silent no-activation with working value
|
||||
messages.
|
||||
|
||||
### Upstream/device-branch archaeology — 2026-07-12 (no phone test)
|
||||
|
||||
- The current `sdm845/7.1-dev` upstream line is active (its tip is two months
|
||||
newer than our 7.1.1 base) but its audio delta is the CS35L36 speaker DTS
|
||||
support already mined for the working speaker route; it contains no WCD934x
|
||||
microphone/SLIM-TX fix. Do not hold a cold-boot test for an imagined pending
|
||||
7.1 microphone update.
|
||||
- **Real candidate to test after a true cold baseline:** historical
|
||||
`joel/op6-audio-fixes-wip` commit `1d5776ca` changes WCD934x SLIM lifecycle
|
||||
from `.trigger` (enable on PCM START) to `.prepare` plus `.shutdown` (enable
|
||||
before START, disable on close). Our current `wcd934x.c` still has the old
|
||||
`.trigger` implementation. This is not upstream and not proof of a fix, but
|
||||
it moves the `slim_stream_enable()` / CONNECT_SRC exchange out of PCM START
|
||||
into the backend prepare phase. Static inspection confirms the Q6 SLIM DAI
|
||||
starts its AFE port in `.prepare`, while DPCM prepares BEs before the FE;
|
||||
that makes this a concrete stream-ordering hypothesis rather than a generic
|
||||
codec tweak. First make an instrumentation-only patch or trace plan that
|
||||
establishes the exact per-DAI callback order; only then consider a
|
||||
rollback-approved kernel test.
|
||||
- Related branch commits were inspected and are **not** default candidates for
|
||||
blueline DMIC capture: `75ad5bb0` adds separate AIF2/AIF3 SLIM DAI links for
|
||||
OnePlus use cases (our AIF1 `SLIMBUS_0_TX` link already matches its AIF1
|
||||
portion); `4a05bee2` and beryllium `07078b5c` repair MBHC/MIC_BIAS2 headset
|
||||
handling, not the DMIC4/MIC_BIAS4 path. Keep them as topology references,
|
||||
not a grab-bag patch series.
|
||||
- **Candidate build/deploy and cold-boot result (2026-07-13):** the lifecycle
|
||||
change was ported exactly onto the running Type-C-bisect base
|
||||
(`56b0a053b`) as `44d43e6ef` / Gitea branch
|
||||
`mic-slim-prepare-order-g56`, built on ArchDev, module tree installed, and
|
||||
boot image flashed to slot A. Casey then performed an unplugged shutdown and
|
||||
power-on; on `7.1.1-sdm845-g44d43e6ef336`, the controlled
|
||||
`DMIC4 → DEC7 → TX7 → MultiMedia2` `hw:0,1` capture still contained only
|
||||
zero samples. No `MC:0x2c` / `-110` occurred during that capture. Thus the
|
||||
prepare-order hypothesis is a valid deployed negative result, not an
|
||||
untested lead; retain the kernel as the current microphone-test baseline.
|
||||
|
||||
## Live state — 2026-07-12 (speakers)
|
||||
|
||||
- BOTH speakers now fire: the earlier "speaker playback works" state was in
|
||||
fact top-speaker-only — both CS35L36 amps had `Channel Mux = RX1`, so the
|
||||
bottom loudspeaker never received a channel. Fixed with the pmOS backup
|
||||
image's routing (Left=RX1 top, Right=RX2 bottom, plus LDM select / PDM
|
||||
ramps / zero-cross / per-amp ASPTX sources) in the overlay UCM
|
||||
(commit 2f39c06), installed on-device and persisted via `alsactl store`.
|
||||
Verified by ear: distinct left/right/stereo tones.
|
||||
- Phone-side handoff doc `~/projects/unfinished/pixel3-speaker-routing/`
|
||||
(README + baseline state + test tones) documents the diagnosis trail;
|
||||
its "next step" (pmOS MRs online) was superseded by reading the local
|
||||
pmos-20260703 rootfs image directly (debugfs, no mount needed).
|
||||
- **Regression and guardrail — 2026-07-13:** the live TDM split was still
|
||||
correct (`Left Channel Mux=RX1`, `Right Channel Mux=RX2`), but both amp and
|
||||
boost switches were off. Re-enabled all four and stored the corrected state
|
||||
on the phone. `docs/tasks/asound-baseline-20260712.state` is a *pre-fix
|
||||
diagnostic snapshot* (`Right Channel Mux=RX1`); it must never be passed to
|
||||
`alsactl restore`, because doing so regresses the bottom speaker. Use the
|
||||
deployed UCM and current `/var/lib/alsa/asound.state` as the restore source.
|
||||
|
||||
## Prior live state — 2026-07-11
|
||||
|
||||
- Speaker playback works end-to-end through PipeWire, UCM, QUAT TDM and the
|
||||
CS35L36 amplifiers (top speaker only — see above).
|
||||
- `arecord -D hw:0,1 -f S16_LE -r 48000 -c 2` (the canonical MultiMedia2
|
||||
front-end, whose `SLIMBUS_0_TX` link is enabled) opens and runs, but capture is
|
||||
bit-for-bit zero. This is a hardware/ASoC transport investigation, not a
|
||||
PipeWire source-selection failure; no PipeWire source can exist until ALSA
|
||||
produces samples.
|
||||
- This work did not start `iio-sensor-proxy`. There is no evidence that the
|
||||
sensor-client HACK or SLPI wake-lockup mechanism gates this audio path.
|
||||
|
||||
## Verified zero-capture path — historical 2026-07-11
|
||||
|
||||
The controlled route is Android's documented handset/endfire pair:
|
||||
|
||||
- DMIC4 -> DEC7 -> SLIM TX7 and DMIC3 -> DEC8 -> SLIM TX8.
|
||||
- Q6 function tracing during `arecord` observed, in order:
|
||||
`q6slim_set_channel_map`, `wcd934x_hw_params`,
|
||||
`q6afe_slim_port_prepare`, `q6afe_port_start`, and `wcd934x_trigger`.
|
||||
- A temporary argument probe observed the two expected shared-channel IDs
|
||||
delivered to Q6 when TX6 is off: `tx_num=2`, `0x87`, `0x88` (135, 136).
|
||||
The resulting 478,078 signed-16 samples on `hw:0,1` were all zero.
|
||||
- The WCD9340 interface device (`217:250:0:0`, `WCD9340-IFC-DEV`) receives
|
||||
the expected driver writes for both ports: TX7 map/config `0x11c=80`,
|
||||
`0x11d=01`, `0x57=05`; TX8 `0x120=80`, `0x121=01`, `0x58=05`.
|
||||
Its live interrupt enables are `0x32=c0` and `0x33=01`, so their absence is
|
||||
**not** the explanation for silence.
|
||||
- On that same canonical front-end, `q6afe_port_start()` returns `0`, then
|
||||
`slim_stream_enable()` invokes `qcom_slim_ngd_enable_stream()`, which returns
|
||||
`0` after the controller's `DEF_ACT_CHAN` and `RECONFIG_NOW` operations.
|
||||
Kernel-visible Q6 and NGD schedule activation therefore succeeded.
|
||||
- **Corrected DAPM observation:** a clean live `hw:0,1` capture powers
|
||||
codec-side `AIF1 CAP` and `AIF1_CAP Mixer`. Its prepare phase calls
|
||||
`wcd934x_codec_enable_slim` and two `wcd934x_codec_enable_dec` events.
|
||||
The earlier report that these callbacks never fired was false: the trace
|
||||
missed the prepare-phase calls. Do not use that earlier result as evidence.
|
||||
- The *persistent* mixer state leaves `CDC_IF TX7 MUX` and `CDC_IF TX8 MUX`
|
||||
at `ZERO`, despite the corresponding AIF mixer switches and DMIC muxes
|
||||
being selected. Consequently the TX7/TX8 branch is off during an ordinary
|
||||
capture and no `wcd934x_codec_enable_dmic` event occurs. This is a real UCM
|
||||
route omission, not an ADSP conclusion.
|
||||
- A controlled, automatically reverted test set those muxes to `DEC7` and
|
||||
`DEC8` and disabled stray TX6. It powered the entire intended branch and
|
||||
invoked `wcd934x_codec_enable_dmic` twice (with matching shutdown calls).
|
||||
The independent two-second PCM capture was nevertheless 384,000 zero bytes.
|
||||
Thus the codec DAPM path and physical DMIC endpoints are viable once the
|
||||
muxes are selected, but that route omission is not the final silence cause.
|
||||
- This is **not** a missing FE-to-BE connection: runtime ASoC state reports
|
||||
`MultiMedia2 Capture: start` with `SLIM Capture 1: start` as its active
|
||||
backend, and the codec's AIF/decimator event callbacks run. With the CDC
|
||||
muxes selected, the DMIC callbacks run too; the unresolved point at that
|
||||
date was after the fully powered codec capture path.
|
||||
- Reducing the route to the downstream-style single TX7/DMIC4 channel did not
|
||||
change the result: a four-second `hw:0,1` mono capture contained 192,000
|
||||
zero samples. Channel count/configuration is therefore not the activation
|
||||
trigger.
|
||||
|
||||
## What the 2026-07-11 evidence did and did not say
|
||||
|
||||
Android's captured recording state has IFD `0x097=21`; mainline reads `00`.
|
||||
During a five-second live TX7/TX8 capture, writing `0097 21` through the
|
||||
debugfs regmap returned success but read back `0097: 00`, and all 478,078
|
||||
samples remained zero. This confirms that a userspace register replay cannot
|
||||
enable that state on this path. It does **not** establish what `0x097` means
|
||||
or prove that it is the root cause. Do not turn it into an unconditional
|
||||
codec write or call it an “ADSP-master fix” without an implementation-level
|
||||
source or Android-side transition capture.
|
||||
|
||||
TX6 is enabled by the current mixer default alongside TX7/TX8. It makes the
|
||||
unconstrained map three channels (`0x86`, `0x87`, `0x88`), while disabling it
|
||||
produces the correct TX7/TX8 pair above. That is a real UCM/machine-routing
|
||||
cleanup item, but it did not restore capture and is not the root cause.
|
||||
|
||||
## Proposed next work on 2026-07-11 — superseded
|
||||
|
||||
1. Capture the working Android interface-device transition, or find the
|
||||
downstream owner/meaning of IFD `0x097`; compare the actual write sequence,
|
||||
not just idle/recording register snapshots.
|
||||
2. Add the TX7/TX8 `CDC_IF` mux selections to the eventual microphone UCM
|
||||
enable sequence (and keep TX6 off for this two-channel route). Do not
|
||||
expose a broken PipeWire source before its capture is non-zero.
|
||||
3. Repeat the WCD9340 interface/ADSP comparison only with that fully powered
|
||||
codec path. The still-zero full-path test strengthens, but does not prove,
|
||||
the relevance of the non-sticking Android IFD `0x097=21` delta. Capture an
|
||||
Android transition or find the downstream owner before making a firmware
|
||||
or register claim. Do not flash a kernel test without an explicit
|
||||
rollback-approved deployment plan.
|
||||
4. Only after that, compare the remaining post-activation state with Android
|
||||
at the firmware boundary. Q6 and NGD success alone do not show that the
|
||||
codec/ADSP hardware emits decimator samples.
|
||||
5. Fix the stray TX6 default separately in the persistent UCM/machine route,
|
||||
then verify it with the channel-map probe. Do not claim mic support until a
|
||||
live capture contains non-zero samples.
|
||||
|
||||
## Instrumentation
|
||||
|
||||
`/usr/local/sbin/blueline-lockup-trace` is a root helper deployed from
|
||||
`rootfs-overlay/`. `mic-start` traces the Q6/WCD stream functions and the
|
||||
three WCD codec DAPM event callbacks; `mic-map-start`, `mic-ifc-start`, and
|
||||
`mic-ngd-start` collect Q6 channel-map, WCD interface-register-write, and
|
||||
Q6/NGD return traces respectively. Trace sessions are temporary and must
|
||||
always be paired with the matching `*-stop` command; the current phone was
|
||||
checked clean afterwards (`tracing_on=0`, tracer `nop`, no kprobes).
|
||||
|
||||
## 2026-07-18 full Android power-state reconciliation
|
||||
|
||||
A clean, single-channel PulseAudio/UCM capture on kernel `4c3a94153` proved
|
||||
that the intended TX7/DMIC4 path is selected and fully ACKed, but every one of
|
||||
55,680 S16 samples is exactly zero. A simultaneous live codec dump exposed
|
||||
two remaining Android/mainline power-state differences which earlier tests had
|
||||
not actually eliminated:
|
||||
|
||||
- `ANA_MICB4` is `0x50` on mainline versus `0x90` during Android recording.
|
||||
The DT opt-in `qcom,blueline-dmic-micbias-pullup` is on the `codec@1,0`
|
||||
node, but `wcd934x_codec_parse_data()` queried `dev->parent->of_node` (the
|
||||
SLIM controller). Therefore `micbias_pullup` was always false and the
|
||||
earlier claim that pull-up mode had been tested is invalid. The code now
|
||||
queries `dev->of_node`, selecting Qualcomm's downstream
|
||||
`MICB_PULLUP_ENABLE` path (`0x90`).
|
||||
- `ANA_BUCK_CTL` is `0x00` on mainline versus `0x07` on Android. Qualcomm's
|
||||
resource manager sets RCO followed by buck `PRE_EN1`, `PRE_EN2`, and
|
||||
`HIGH_ACCURACY_EN`, with 100 us between stages. The mainline driver had
|
||||
dropped this sequence. It is restored as ordered masked writes, including
|
||||
the reverse transition; this is not the harmful unsequenced Android
|
||||
end-state replay removed earlier.
|
||||
|
||||
The same correction (`a75371b49`) also matches Android's board state by
|
||||
programming only DMIC interface 3 pads (the real DMIC3/4 inputs) and avoids
|
||||
leaving TX7 at the transient 150 Hz HPF setting (`CFG0=0xd0`) indefinitely;
|
||||
Android's steady recording state is `CFG0=0x90`. One boot-time log reports
|
||||
whether pull-up mode was parsed, and the capture log reports live BUCK and all
|
||||
MICB registers so the next capture validates the complete state in one pass.
|
||||
|
||||
## 2026-07-18 eFuse initialization regression and cache fix
|
||||
|
||||
On the clean `3b85e688f` boot, the complete downstream AFE codec-control map
|
||||
registered successfully and TX7 again reached `port_start`, CONNECT_SOURCE,
|
||||
DEF_ACT_CHAN, and RECONFIG_NOW with positive ACKs. PulseAudio captured 760,318
|
||||
raw bytes (380,159 S16 samples), all exactly zero. The journal exposed a real
|
||||
codec boot failure: `wcd934x_enable_efuse_sensing` read `EFUSE_STATUS=0` and
|
||||
reported that sensing never completed.
|
||||
|
||||
History showed that `1c095f7a7` had added the downstream-required
|
||||
internal-SIDO start before eFuse sensing, but `c1b6371c6` removed the call while
|
||||
dropping unrelated experiments. The exact downstream SIDO/buck transition
|
||||
helper was later restored by `a75371b49` without reconnecting it to eFuse init.
|
||||
The audit also found that downstream treats `EFUSE_CTL`, all eFuse value
|
||||
outputs, and status as volatile, while mainline cached them in `REGCACHE_MAPLE`.
|
||||
|
||||
Commit `82355eac0` batches the complete correction: internal SIDO before the
|
||||
sense enable, the required 5 ms delay before RCO/BG, downstream-parity volatile
|
||||
register handling for the eFuse/clock/buck sequence, live completion polling,
|
||||
register error propagation, and explicit status/out14/out15 logging. Do not
|
||||
interpret another zero capture until a cold boot proves the codec prints
|
||||
`eFuse sense complete`; otherwise the physical codec trim/version state is
|
||||
still uninitialized even though SLIM management commands ACK normally.
|
||||
64
saf/device/build.md
Normal file
64
saf/device/build.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# PAF · Build & deploy (Arch era)
|
||||
|
||||
The loop that turns a kernel commit into a verified boot on glass. pmOS-era
|
||||
pmbootstrap notes preserved at `evidence/build-pmos-era.md` — dead path now.
|
||||
|
||||
## The three places
|
||||
|
||||
| Where | Role |
|
||||
|---|---|
|
||||
| gitea `10.10.20.120:4455` — `Fimeg/Pixel3Arch` + `Fimeg/linux-blueline` (branch `blueline`) | **Source of truth.** Uncommitted work does not build — commit or it doesn't exist. |
|
||||
| ArchDev LXC `casey@10.10.20.123` (ani key) | **Build host.** Cross toolchain installed, MAKEFLAGS system-wide. |
|
||||
| Laptop | **Device ops only** — flashing, fastboot, USB tether, boot.img assembly. |
|
||||
|
||||
## The loop
|
||||
|
||||
1. Commit + push kernel changes to gitea (`kernel/src/linux-blueline-src`
|
||||
on the laptop is a clone with remote `gitea`).
|
||||
2. Build on ArchDev:
|
||||
`ssh ... casey@10.10.20.123 'cd ~/Projects/Pixel3Arch/kernel && ./build.sh blueline'`
|
||||
- build.sh **self-locks with flock** — invoke it directly, never wrap it
|
||||
in an outer flock on the same lockfile (deadlock, cost a session).
|
||||
- It force-fresh-clones from gitea and **hard-asserts the built package's
|
||||
embedded commit hash matches the gitea branch tip** — a mismatch means
|
||||
stale source; the build fails loud instead of shipping it.
|
||||
3. Pull `kernel/linux-blueline-7.1.1-1-aarch64.pkg.tar.zst` to the laptop.
|
||||
4. Assemble the boot image (pmOS-chain — the currently proven ramdisk path):
|
||||
```
|
||||
bash boot/build-bootimg-pmos-chain.sh <vmlinuz-from-pkg> <dtb-from-pkg> \
|
||||
staging/deploy-139aa/boot-139aa7748181-pmoschain.img <out.img>
|
||||
```
|
||||
Kernel is Image.gz + **appended DTB** (header v0 ignores --dtb); ramdisk
|
||||
and cmdline are lifted from the reference image. Migrating to our own
|
||||
mkinitcpio ramdisk (`boot/build-bootimg.sh`) is a parked one-variable task.
|
||||
5. Deploy: `scripts/deploy-kernel.sh <pkg.tar.zst> <boot.img>`
|
||||
|
||||
## Deploy doctrine — no chimera boots
|
||||
|
||||
`deploy-kernel.sh` exists because a kernel was once booted 28 seconds before
|
||||
its module tree was installed: no evdev, no input, dead services, and a
|
||||
night of void test results. The script enforces the only safe order:
|
||||
|
||||
1. **Modules first** — scp the pkg, `pacman -U`, verify the module dir exists.
|
||||
2. Flash boot_a, reboot.
|
||||
3. **Refuse to bless the boot** unless `uname -r` matches the installed tree.
|
||||
|
||||
Never flash-then-install. If `uname -r` and `/usr/lib/modules/` disagree,
|
||||
every test result on that boot is garbage — reboot properly before
|
||||
concluding anything.
|
||||
|
||||
## Gotchas ledger (each cost a session)
|
||||
|
||||
- makepkg needs `CARCH=aarch64 ... --ignorearch` — CARCH comes from the
|
||||
environment, not the PKGBUILD, or you get x86_64-labeled aarch64 packages.
|
||||
- makepkg builds in its own clone of `source=()`, not your working tree —
|
||||
a dirty tree silently builds without your changes.
|
||||
- The kernel release string embeds the git hash (`-g<hash>`) — it is the
|
||||
ground truth for "what am I actually running" (`uname -r`).
|
||||
- `modules_install` needs CROSS_COMPILE too, or strip fails on aarch64 .ko.
|
||||
- ArchDev is unprivileged LXC: qemu-emulated execution (binfmt) does NOT
|
||||
work there — chroot-based work stays on the laptop or on-device.
|
||||
- Commit messages: history in this repo proved that "evidence" cited in
|
||||
rapid-fire commits can be wrong (see touch.md, display.md post-mortems).
|
||||
Cite the actual file:line / register / boot you verified on, and note
|
||||
whether the boot was clean.
|
||||
109
saf/device/display.md
Normal file
109
saf/device/display.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# PAF · Display — SW43408 panel, DSI, bias rails (SOLVED 2026-07-10)
|
||||
|
||||
## Current regression — verified on glass 2026-07-11
|
||||
|
||||
On current kernel `7.1.1-sdm845-g56b0a053bef8`, a true unplugged cold boot
|
||||
still produces a green flash during boot. Green flashes also remain on wake.
|
||||
During AP-side DT2W testing the first double tap sometimes does a visible
|
||||
green-flash/black half-wake and the second wakes the panel. The kernel trace
|
||||
shows the first gesture reaches panel prepare and is immediately followed by
|
||||
unprepare; there are no DSI/DPU errors. In one successful wake the visible
|
||||
frame remained stale despite a synchronized system clock and live Hyprland +
|
||||
Quickshell processes. This is an observation, **not** proof of a panel-only
|
||||
root cause; preserve it for the display/DPMS investigation.
|
||||
|
||||
**Recovery and clock distinction, 2026-07-11:** after an instrumented
|
||||
libssc-triggered lockup, `blueline-screen-toggle on` re-prepared the panel but
|
||||
it unprepared again seconds later even with `hypridle` stopped. Do not churn
|
||||
panel/FTS resets in that state. A normal reboot of the known-good current slot
|
||||
restored the panel and compositor. The first post-boot `ii` screenshot still
|
||||
showed the Jul-06 20:00 clock floor even though NTP had corrected system time;
|
||||
restarting only Quickshell (`qs -c ii kill`, then `qs -c ii`) refreshed it to
|
||||
the real time. Therefore an old ii clock is not alone evidence of a frozen
|
||||
scanout. The remaining task is to prevent ii from retaining the pre-NTP clock,
|
||||
not to reset the panel again.
|
||||
|
||||
Cold boot → Hyprland AND power-button sleep/wake visually verified on
|
||||
kernel `g766205b60588`. Read this whole file before touching
|
||||
`panel-lg-sw43408.c`, the labibb regulator, or the display DT.
|
||||
|
||||
## The doctrine: WARM-CYCLE. Do not dismantle again.
|
||||
|
||||
The working design in `drivers/gpu/drm/panel/panel-lg-sw43408.c`:
|
||||
|
||||
- `.prepare` = regulators + reset pulse only. No DCS.
|
||||
- `.enable` = the ENTIRE DCS init (gamma, TE, sleep-out, vendor regs, PPS,
|
||||
compression, display-on) — runs over a fully-up DSI link, onto a
|
||||
still-powered DDIC, on every DPMS-on.
|
||||
- `.disable` = display_off + enter_sleep (HS mode). That is ALL DPMS-off does.
|
||||
- **No `.unprepare` power-off.** The panel keeps vddi/vpnl forever after
|
||||
first boot; reset is never re-asserted at runtime.
|
||||
|
||||
Why: **upstream's prepare-time cold-cycle design cannot cold-start this
|
||||
panel.** Full power-off + reset + re-init produces a green flash (display-on
|
||||
latching onto random GRAM) then permanent black, on every transition. That
|
||||
is the original "screen won't wake without the sdm845-mainline hack" bug
|
||||
(David's report). The warm-cycle shape IS the fix — the panel is
|
||||
re-initialized warm, never cold.
|
||||
|
||||
History: the working design was dismantled on 2026-07-09 by ee6611adb
|
||||
("adopt upstream prepare-time init") and a day of ordering experiments
|
||||
failed to make cold-start work (display-on before PPS, after PPS, downstream
|
||||
power sequencing — all green-flash-black). 766205b60 restored the warm-cycle
|
||||
architecture. Any future "align with upstream" refactor must first prove
|
||||
cold-start on real glass.
|
||||
|
||||
## Bias rails: LAB/IBB under SWIRE (hardware) control
|
||||
|
||||
Matching downstream: the DDIC drives the OLED bias rails over the SWIRE
|
||||
wire, not software.
|
||||
|
||||
- DT: `qcom,swire-control` on the pmi8998 labibb node (1b02532a4).
|
||||
- Driver: SWIRE ops are software no-ops (enable/disable/set_voltage return 0);
|
||||
probe writes IBB `ENABLE_CTL=0x40` (SWIRE_RDY, module-enable cleared).
|
||||
LAB `ENABLE_CTL=0x00` is CORRECT in SWIRE mode (slaved to IBB in hardware).
|
||||
- Behavior: panel sleep-in → rails physically drop; sleep-out → rails rise.
|
||||
Register-verified live across DPMS cycles: STATUS1 (IBB 0xdc08 / LAB
|
||||
0xde08 on regmap 0-03) reads 00/00 off → 80/a0 on.
|
||||
- The panel driver's supplies are vddi + vpnl ONLY. It never touches
|
||||
LAB/IBB. c789daaa8 fixed the tail end of the old design (unprepare
|
||||
disabling never-enabled rails → unbalanced-disable → `panel->prepared`
|
||||
stuck true → EBUSY zombie); moot under warm-cycle but the lesson stands:
|
||||
an error return from unprepare wedges the prepared flag forever.
|
||||
- Voltage: software set_voltage is a no-op; hardware stays at the
|
||||
bootloader's 5.5V programming.
|
||||
|
||||
## Userspace wake path
|
||||
|
||||
- Hyprland (Lua build) power-button / tap-to-wake toggle:
|
||||
`blueline-screen-toggle` on the phone. Wake needs BOTH
|
||||
`hl.dsp.dpms("on")` AND `hl.dsp.force_renderer_reload()` — dpms-on alone
|
||||
can issue a commit with no real CRTC transition (crtc=NULL skips
|
||||
drm_panel_prepare; long-standing compositor-side hole).
|
||||
**Gotcha fixed 2026-07-10:** the script called `forceRendererReload()`,
|
||||
which the Lua Hyprland renamed to `force_renderer_reload()` — every
|
||||
power-button wake silently no-op'd. Dispatcher names must be verified
|
||||
against the running Hyprland, errors surface only in the hyprctl reply.
|
||||
- greetd → `start-hyprland` as initial session; the boot chain
|
||||
fbcon → greetd → Hyprland does one panel takeover, warm.
|
||||
|
||||
## Diagnostic quick kit
|
||||
|
||||
- Rails now: `sudo grep -E "^(dc08|de08):" /sys/kernel/debug/regmap/0-03/registers`
|
||||
(80/a0 = up, 00/00 = SWIRE-dropped).
|
||||
- Compositor truth: `grim` screenshot — separates "compositor renders" from
|
||||
"glass shows" (they diverged for two days; the screenshot was pixel-perfect
|
||||
while the glass was black).
|
||||
- DPMS from SSH:
|
||||
`HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr/) XDG_RUNTIME_DIR=/run/user/1000 hyprctl dispatch 'hl.dsp.dpms("off"|"on")'`.
|
||||
- drm.debug=0x1ff wraps the dmesg ring in ~130ms of transition traffic —
|
||||
use `journalctl -k` or a lower mask for transition captures.
|
||||
- Boot-time `DSI PLL lock failed` + `already disabled/unprepared` clk
|
||||
warnings at ~0.4s: probe-time deferred-probe noise, once per boot,
|
||||
benign-but-ugly (error path double-disables a clk). Not a transition bug.
|
||||
|
||||
## Known-good fallback
|
||||
|
||||
`../staging/deploy-139aa/boot-139aa7748181-pmoschain.img` — pre-SWIRE-era
|
||||
kernel (stmfts touch, warm-cycle display of that era). Module tree already
|
||||
on the phone. Prefer fixing forward on the blueline tip.
|
||||
166
saf/device/edge-sense.md
Normal file
166
saf/device/edge-sense.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# PAF · Active Edge (squeeze)
|
||||
|
||||
The Pixel 3's squeeze gesture. **Working on Arch as of 2026-07-28** — force is
|
||||
detected, and the gesture reaches the shell through the session authority.
|
||||
|
||||
Spine doc: current truth, updated in place. The narrative of how it was found
|
||||
lives in `../../SouveraineOS/docs/tasks/13-active-edge-squeeze.md`, which keeps
|
||||
each superseded conclusion verbatim beneath its correction.
|
||||
|
||||
## The hardware chain
|
||||
|
||||
Six strain gauges bonded to the frame → **MAX11261** ADC on the SLPI's own I²C
|
||||
bus → the `max11259` SEE driver on the sensor island → published over SSC as the
|
||||
data type **`elmyra_raw`**, SUID `15717249801838747009 / 6291402259395006624`.
|
||||
|
||||
Not evdev, not a GPIO key. There is no input node and nothing bridges the SUID
|
||||
to one; the only way to it is an SSC client. `sns_touch_gesture` is a **different
|
||||
sensor** — the FTS double-tap nanoapp on the touch controller (slave 0x49, bus 5,
|
||||
irq 125). Opening that one and squeezing correctly produces nothing.
|
||||
|
||||
**Power: PM8998 GPIO 2**, gpiochip0 line 1, held by a devicetree `gpio-hog`
|
||||
(`edge_sense_pwr_hog` in `sdm845-google-common.dtsi`). Confirm with:
|
||||
|
||||
sudo gpioinfo | sed -n '3p'
|
||||
# line 1: unnamed output consumer="edge-sense"
|
||||
|
||||
## Why it did not work before, and what fixed it
|
||||
|
||||
Through 2026-07-27 the gauges converted and drifted but **ignored force**: 399
|
||||
samples across 35 s of hard squeezes gave a median deflection of 49 and a max of
|
||||
127, against a resting noise floor of ~50. The conclusion at the time was that
|
||||
the analog front end is only configurable from inside the `elmyra` CHRE nanoapp
|
||||
via `ashConfigureElmyraFilters`, and that an SSC client gets whatever the AFE
|
||||
defaults to.
|
||||
|
||||
On 2026-07-28 the **same unchanged binary** with **no nanoapp loaded** measured
|
||||
deflections of 2502–2990 under real squeezes.
|
||||
|
||||
What changed between the two measurements is the rail. It became a DT
|
||||
`gpio-hog` (`318f7d0`) instead of a `gpioset` unit that ran during boot. The
|
||||
likely mechanism is **ordering, not voltage**: the hog asserts the rail at
|
||||
kernel probe, before SSC enumerates the MAX11261, where the unit powered it
|
||||
after the ADC had already been probed unpowered. An AFE initialised without its
|
||||
bridge excited stays wrong until reset.
|
||||
|
||||
That is the leading explanation and it is **not proven**. Confirming it means
|
||||
booting a pre-hog kernel and re-measuring. The electrical prediction made at the
|
||||
time — that a pinctrl node changes nothing, since PM8998 GPIO 2 reads `vin-0`
|
||||
on both stacks because neither selects one — still stands and was never the
|
||||
issue.
|
||||
|
||||
## Calibration is real and per-unit
|
||||
|
||||
`/persist/sensors/elmyra/calibration.{0..5}`, factory-written 2019, one file per
|
||||
gauge:
|
||||
|
||||
touch_2_sensitivity: 0.2402884277733113
|
||||
|
||||
This unit: `0.240 0.295 0.176 0.359 0.329 0.342`. They normalise the gauges
|
||||
against each other; the spread is why Google ships a factory calibration step
|
||||
(`edgesense_roller_cal/`) at all. The producer reads them at startup and logs
|
||||
them — if that line shows six `1.000`s, the persist partition is not mounted or
|
||||
the files are gone, and detection will be lopsided rather than absent.
|
||||
|
||||
These are consumed on the **AP side** by our producer. Nothing serves them over
|
||||
HexagonFS: `hexagonrpcd`'s virtual `/persist/sensors/` contains only `registry`
|
||||
(`rpcd_builder.c`). That matters only if the nanoapp path is ever taken.
|
||||
|
||||
## The threshold model
|
||||
|
||||
From Google's own sweep, `edgesense_roller_cal/0_raw/sensor_data.csv`, which
|
||||
captures the gauges at a known 10 N and 20 N — the loaded bank moves 400–2000
|
||||
counts per 10 N step. Raw values sit near ±100000, so an absolute threshold
|
||||
against them is meaningless; the first version compared exactly that and never
|
||||
fired.
|
||||
|
||||
- `DEFAULT_THRESHOLD 2500` — summed normalised deflection from a rolling baseline
|
||||
- `BASELINE_ALPHA 0.02` — the baseline must follow temperature and grip drift,
|
||||
and is frozen while a squeeze is held or the release reads as a squeeze the
|
||||
other way
|
||||
- `RELEASE_FRACTION 0.6` — hysteresis, same reason proximity has it
|
||||
|
||||
Observed squeezes land at 2502–2990, which is close to the line. The binary
|
||||
takes the threshold as an argument, so tuning needs no rebuild.
|
||||
|
||||
## It reports; it does not decide
|
||||
|
||||
The producer does **not** call the shell. It asks `souveraine-sessiond` first:
|
||||
|
||||
{"op":"input","trigger":"squeeze"} → $XDG_RUNTIME_DIR/souveraine/sessiond.sock
|
||||
|
||||
and delivers `qs -c souveraine ipc call gesture squeeze` only on `ok:true`.
|
||||
sessiond treats a squeeze as real user input — it resets the idle budget and
|
||||
lands in the forensic trail — but applies the pocket veto first: a covered
|
||||
proximity sensor refuses it with `refused_by_state`, exactly as it already
|
||||
refused tap-to-wake, because a phone in a tight pocket *is* a squeezed chassis.
|
||||
|
||||
Calling the shell directly, which is what it did first, made this an eighth
|
||||
blind actor: no idle reset, nothing in the trail, no veto, and no way for the
|
||||
machine to notice the source dying.
|
||||
|
||||
**On an unreachable authority it fails closed** and says so. A squeeze that
|
||||
cannot be checked is not delivered; the alternative would mean the pocket veto
|
||||
silently ceases to exist every time sessiond restarts.
|
||||
|
||||
What a squeeze *means* is the shell's routing table (`services/Gestures.qml`,
|
||||
`squeezeAction`, default `dial` → `dial toggle`), never this producer's business.
|
||||
|
||||
## Packaging
|
||||
|
||||
`pkgs/blueline-edge-sense` — `blueline-edge-sense 0.2.0-1`, aarch64, built by
|
||||
`build.sh` on archdev. Ships the binary, the user unit, and a
|
||||
`souveraine-sessiond.service.wants` symlink so it is **enabled by default** and
|
||||
survives a reprovision — the reader wants the authority it reports to, not the
|
||||
session at large. It hung off `graphical-session.target` until 2026-07-29,
|
||||
which started nothing then; the target is live as of TASK-76, but sessiond
|
||||
remains the correct thing to want.
|
||||
|
||||
Before 2026-07-28 the binary sat unowned in `/usr/bin` since 07-26 — one of
|
||||
TASK-25's 5,624. Installing the package hit that exact conflict; the old copy is
|
||||
at `/var/backups/unowned-preupgrade/blueline-edge-sense.20260728`.
|
||||
|
||||
## Known gaps
|
||||
|
||||
- **No detection while the AP sleeps.** This is an AP-side process sampling six
|
||||
channels at 100 Hz forever, which is precisely what `DozeDeep` exists to kill.
|
||||
Squeeze-to-wake from deep doze is only possible from the DSP side — the
|
||||
nanoapp. Grip and doze are currently mutually exclusive and nothing in the
|
||||
code knows it.
|
||||
- **Our detector, not Google's.** `elmyra.so` ships `AggregateDetector`,
|
||||
`DetectorSetup`, `Filter`, `decision_threshold_min/max`, `global_sensitivity`
|
||||
and a `GESTURE_PROGRESS` stream that would give hold-vs-tap. Ours is a
|
||||
baseline-delta threshold firing 2 counts above its own line.
|
||||
- **No haptic confirmation from the right place.** `elmyra_haptics_control` and
|
||||
`haptic_gpio 88` say the buzz is the nanoapp's job downstream; ours is the
|
||||
shell guessing.
|
||||
- **Nothing proves which binary is reporting.** sessiond takes an `input` from
|
||||
whoever connects to the socket, and the shell takes a gesture from whoever
|
||||
calls its IPC — both demonstrated from an SSH shell on 2026-07-28. TASK-41.
|
||||
- **Inference posture unresolved.** TASK-40 designates the raw 100 Hz six-gauge
|
||||
stream a bring-up instrument, not a product surface: it is a typing
|
||||
side-channel and a grip biometric. Enabled anyway by Casey's call, because the
|
||||
exposure is not created by this reader — any AP process linking libssc can
|
||||
open `elmyra_raw` today. Confining *that* is TASK-40's half.
|
||||
|
||||
## Where the code is
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Producer | `pkgs/blueline-edge-sense/edge-sense.c` |
|
||||
| Package + unit | `pkgs/blueline-edge-sense/{PKGBUILD,build.sh,*.service}` |
|
||||
| Rail (gpio-hog) | kernel `sdm845-google-common.dtsi`, `edge_sense_pwr_hog` |
|
||||
| libssc fixes | `pkgs/libssc-souveraine` — single-output sensors, fastest advertised rate (1 Hz → 100 Hz) |
|
||||
| Authority side | souveraine `src/sessiond/{protocol,device_state,server}.rs` — `InputTrigger::Squeeze`, `suppress_wake`, `note_input_gated` |
|
||||
| Routing | souveraine `surfaces/quickshell/services/Gestures.qml` |
|
||||
| Nanoapp host | `pkgs/hexagonrpc` (chrecd) — loads `elmyra`, sends `RECOGNIZER_START` |
|
||||
|
||||
## Verifying it live
|
||||
|
||||
systemctl --user status blueline-edge-sense
|
||||
journalctl --user -u blueline-edge-sense -f
|
||||
|
||||
A healthy start logs the six sensitivities, the SSC open, and a six-value
|
||||
baseline. A squeeze logs `SQUEEZE (deflection N)` then either `squeeze
|
||||
delivered` or the authority's refusal — and a refusal is normal operation, not
|
||||
a fault.
|
||||
51
saf/device/evidence/README-pmos-era.md
Normal file
51
saf/device/evidence/README-pmos-era.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# PAF — Pixel 3 (blueline) pmOS Architecture Framework
|
||||
|
||||
The single living spine for this port. Replaces the accreting handoffs/findings: instead of
|
||||
appending a new dated doc each session, **update the relevant layer in place.** The spine:
|
||||
|
||||
- **[modem.md](modem.md)** — the modem bring-up stack as layers, status per layer, the
|
||||
eliminated-leads ledger (don't re-chase these), and the one live frontier.
|
||||
- **[platform.md](platform.md)** — device hardware map, boot/storage architecture, how to
|
||||
reach the phone, recovery procedures, and the hard hardware lessons.
|
||||
- **[build.md](build.md)** — how the kernel + device package are actually built with
|
||||
pmbootstrap, the two-package split, and the build-system gotchas that have each cost a session.
|
||||
- **[slpi.md](slpi.md)** — SLPI sensor island bring-up and the `sar.cc:27` crash-loop fix.
|
||||
- **[secure_load_tz_frontier.md](secure_load_tz_frontier.md)** — 🔴 SUPERSEDED: secure-load is
|
||||
resolved (modem loads clean via DELTA-1). Historical −22/fastrpc_mem record; the live frontier is
|
||||
the firmware OFFLINE/52 park — see `modem.md` + `diag_capture.md`.
|
||||
- **[README.md](README.md)** — this index.
|
||||
|
||||
Deeper/dated capture docs (`modem_*`, `qcril_*`, `pmos_fix_plan.md`, `diag_capture.md`, …) sit
|
||||
alongside as evidence; the spine above is what you read first. Sibling docs kept outside the PAF
|
||||
on purpose: `../README.md` (the generic port guide) and `../SIDE-TASKS.md` (parked non-modem work).
|
||||
|
||||
## Status at a glance (2026-07-01)
|
||||
|
||||
| Layer | State |
|
||||
|---|---|
|
||||
| Firmware load (MBA→MPSS) | 🟢 healthy — loads clean, **no −22 on the modem path** (DELTA-1 `c2923cb49`) |
|
||||
| Power (rpmhpd cx/mx/mss → mss.lvl) | 🟢 healthy, proven equivalent to Android |
|
||||
| QMI / QRTR transport | 🟢 healthy |
|
||||
| rmtfs / tqftpserv | 🟢 healthy (rmtfs in `-P -s` rw partition mode) |
|
||||
| pd-mapper | 🟢 fixed live (2026-06-29 late). `CONFIG_QCOM_PD_MAPPER=m` is in the flashed kernel and `qcom_pd_mapper` loads. This restored WLAN PD advertisement and `ath10k_snoc` probe; `wlan0` exists. |
|
||||
| SIM / UIM provisioning | 🟢 healthy (usim ready, Primary-GW slot1/app1) |
|
||||
| PDC MCFG autoselect | 🟡 works (configs go Active) — **NOT the gate** (modem parks OFFLINE regardless) |
|
||||
| SLPI sensor island (`sar.cc:27`) | 🟠 real firmware crash, still unfixed — **but now cleanly bounded and PROVEN independent of the modem 52 wall** (2026-07-01, three live boots: crashed continuously / running / cleanly parked all gave byte-identical `DeviceNotReady`). Ordering + crash-recovery both fixed (see `slpi.md`); the sensor island itself is out of scope for modem bring-up now. |
|
||||
| **RF arm (`Set Operating Mode=online`)** | **🔴 BLOCKED — offline/mode 0/52. THE frontier; proven kernel- and SLPI-independent.** `dms-ui-ready.py` (DMS 0x0064, the one load-bearing QCRIL pre-online step never replayed) built 2026-07-01, untested on hardware — next pmOS boot is the test. |
|
||||
| Data / voice (IPA, MM, calls) | gated behind RF arm |
|
||||
|
||||
**One-line state:** the modem MSS loads clean, enumerates fully (IMEI/SIM/firmware all read
|
||||
reliably), and the phone is stable on 7.1.1 with WiFi working. The 52 wall is the modem's
|
||||
firmware-internal RF-init decision (pmOS parks op-mode 3 OFFLINE vs Android's op-mode 1
|
||||
LOW_POWER) — now **proven independent of SLPI** with hard repeated evidence, closing out the prior
|
||||
"make SLPI healthy first" ranked move as a dead end for this specific blocker. The live frontier is
|
||||
`dms-ui-ready.py`: the one QCRIL pre-online QMI step (disassembly-confirmed load-bearing) that pmOS
|
||||
has built but never fired on real hardware yet.
|
||||
|
||||
**Build/packaging milestone (2026-06-22):** all fixes unified into one **7.1.1** kernel tree as
|
||||
commits (not scattered patches), built via pmbootstrap; the PDC self-provisioner
|
||||
(`qcril-autoselect`) moved out of hand-deployed `tools/` glue and **into the
|
||||
`device-google-blueline` package**, auto-enabled via the systemd preset and ordered
|
||||
`After=rmtfs` / `Before=ModemManager`. One piece still hand-built: the openimsd **libqmi fork**
|
||||
(PDC Set Feature) is not yet its own aport — see [build.md](build.md). This is "how the PAF
|
||||
ought to look": kernel fixes in-tree, provisioner in the device package, nothing scp'd by hand.
|
||||
20
saf/device/evidence/README.md
Normal file
20
saf/device/evidence/README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# PAF evidence
|
||||
|
||||
Dated captures, handoffs, and archaeology live here. They preserve how a
|
||||
conclusion was reached; they do not compete with the layer docs one directory
|
||||
up. When evidence changes the answer, update the layer doc and leave the dated
|
||||
record legible as history.
|
||||
|
||||
- `audio/` — microphone, PipeWire, and call-audio handoffs from 2026-07-19.
|
||||
Current authority: `../audio.md`.
|
||||
- `power/` — charge-control implementation handoff. Current authority:
|
||||
`../platform.md` and TASK-08.
|
||||
- The remaining files preserve pmOS, modem/QCRIL, TrustZone, provisioning, and
|
||||
diagnostic research cited by the living spine.
|
||||
|
||||
Recovery anchors for the consolidation:
|
||||
|
||||
- SouveraineOS `72aaa53` — first copied PAF tree, 2026-07-24.
|
||||
- Pixel3Arch `9b1afd1` — last complete PAF tree before its retirement.
|
||||
- Pixel3Arch tree `27350cd5f5bec5062f361cbbb329cf7a04efb2f2` — exact snapshot
|
||||
promoted into SouveraineOS before reconciliation.
|
||||
74
saf/device/evidence/android_modem_bringup_capture.md
Normal file
74
saf/device/evidence/android_modem_bringup_capture.md
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# PAF · Android modem bring-up — COMPLETE captured sequence (the working reference)
|
||||
|
||||
The full modem boot Android does, end to end, extracted from the real captures in
|
||||
`android-capture-20260620/` (modem SSR full re-init -> ONLINE, and the cold online capture).
|
||||
This is the ENTIRE process — not one diff. Each phase lists what Android does (with file:line)
|
||||
and the pmOS gap. Android reaches **IN_SERVICE / LTE / Fido (302/720)** at the end; pmOS stalls
|
||||
at phase 1.
|
||||
|
||||
Source files:
|
||||
- `qcril-ssr-fullinit.txt` (66k lines, modem SSR -> full re-init)
|
||||
- `qcril-online-capture.txt` (24k lines, cold bring-up to ONLINE)
|
||||
- `android-radio-log.txt`, `qcril-ssr-TIMELINE.txt` (digest)
|
||||
SIM: ICCID <redacted>, IMSI <redacted>, MCC/MNC 302/370 (Fido).
|
||||
|
||||
---
|
||||
|
||||
## PHASE 0 — kernel/firmware secure-load (pre-QMI; NOT in these AP logs)
|
||||
The modem PIL loads MBA -> PBL -> MPSS with the TZ memory-assign + XPU unlock. Android passes this.
|
||||
pmOS is where the **-22 / XPU violation** lives — see `secure_load_tz_frontier.md`. NOTE: downstream
|
||||
(`peripheral-loader.c:334` `pil_assign_mem_to_subsys`) makes the **same** HLOS->subsys_vmid RW
|
||||
hyp_assign that mainline `qcom_q6v5_mss.c:463` does — the assign is load-bearing, the skip was wrong.
|
||||
|
||||
## PHASE 1 — firmware comes up at op-mode 1 = LOW_POWER ← THE DIVERGENCE
|
||||
- Android: `qcril_qmi_nas_query_cur_power_state: known modem operating mode 1` then
|
||||
`dms_handle_modem_operating_mode: modem's operating mode needs to be changed`
|
||||
(`qcril-online-capture.txt:90,96`), and the DMS event report shows `oprt 1 / 0`
|
||||
(`qcril-online-capture.txt`, qcril_qmi_nas.cpp:6562).
|
||||
- **pmOS: modem comes up at op-mode 3 = OFFLINE** (`tools/modem-online-provisioner/RESULTS.md`).
|
||||
OFFLINE->online = **52 DeviceNotReady**; low_power->online would be a legal transition.
|
||||
- QMI DMS operating-mode enum: 0=ONLINE 1=LOW_POWER 2=FACTORY 3=OFFLINE 4=RESET 5=SHUTDOWN
|
||||
6=PERSISTENT_LOW_POWER.
|
||||
- **The real question is NOT "how to force OFFLINE->online" (it can't) — it is "why does our
|
||||
modem boot to OFFLINE when Android's boots to LOW_POWER".** OFFLINE is the firmware's
|
||||
"something-wrong / not-provisioned" state. Prime suspects: phase-0 secure-load health (degraded
|
||||
boot), or EFS/rmtfs provisioning (modem can't read/write its NV -> falls to OFFLINE).
|
||||
|
||||
## PHASE 2 — UIM / SIM card init (Android: OK)
|
||||
- `card_state 3` (present), app `state 5` (READY), app `type 2`=USIM / `type 5`=ISIM
|
||||
(`qcril-online-capture.txt:336-337`). SIM ICCID/IMSI read fine.
|
||||
- pmOS gap: untested past phase 1 — modem must be online first.
|
||||
|
||||
## PHASE 3 — PDC: select carrier MBN config (per SIM slot) ← pmOS likely missing
|
||||
- Android: `qcril_qmi_pdc_get_selected_mbn_config_by_sim_slot` (`qcril-ssr-fullinit.txt:4308…`)
|
||||
and `qcril_qmi_pdc_get_selection_carrier: cnv is valid, cnv=[72]` (`qcril-online-capture.txt:9367`,
|
||||
`qcril_qmi_nas: prop_cnv = 72`). cnv 72 = the Fido carrier config.
|
||||
- This is the carrier/modem config (MBN) activation. pmOS has no RIL doing it; the planned
|
||||
replacement is the PDC self-provisioner `qcril-autoselect.py` (see `build.md`,
|
||||
`device-google-blueline`). Without a selected config the modem may stay inert on RF.
|
||||
|
||||
## PHASE 4 — drive operating mode -> ONLINE
|
||||
- Android: NAS decides "needs to be changed" (phase 1) and sets the modem ONLINE; subsequent
|
||||
state shows `is_online 1` (`qcril-online-capture.txt`, qcril_qmi_nas.cpp:26131).
|
||||
- pmOS: this is exactly the `--dms-set-operating-mode=online` we fire — returns **52** because we
|
||||
start from OFFLINE (phase 1), not LOW_POWER.
|
||||
|
||||
## PHASE 5 — NAS attach / network registration (full QMI choreography)
|
||||
- Module/QMI send order (from `qcril-ssr-TIMELINE.txt` section A): Nas, Pdc, Voice, DSD, Auth, WDS,
|
||||
Pbm, Sms, Vs — dozens of NasModem sends, all rc=0. This is the post-online network attach.
|
||||
|
||||
## PHASE 6 — IN_SERVICE (the goal)
|
||||
- `combinePsRegistrationStates: mVoiceRegState=0(IN_SERVICE), mDataRegState=0(IN_SERVICE) …
|
||||
getRilVoiceRadioTechnology=14(LTE) … mOperatorAlphaLong=Fido` (`qcril-online-capture.txt:24110`).
|
||||
- CS domain: VOICE,SMS,VIDEO; PS domain: DATA,MMS; registrationState=HOME; LTE EARFCN 2000, Fido
|
||||
302/720 (`qcril-online-capture.txt:24107-24132`). IMS registered (`:24083`).
|
||||
|
||||
---
|
||||
|
||||
## What "the whole job" actually is (so it stops getting half-done)
|
||||
1. **Phase 0** — fix the secure-load so the modem boots HEALTHY (the -22 / XPU). Not the skip.
|
||||
2. **Phase 1** — get the modem to come up at LOW_POWER, not OFFLINE. Likely a consequence of (1)
|
||||
and/or EFS/rmtfs provisioning. This is the current wall.
|
||||
3. **Phase 3** — replicate Android's PDC MBN carrier-config selection (qcril-autoselect provisioner).
|
||||
4. **Phase 4** — only then does `set-operating-mode=online` succeed instead of 52.
|
||||
Doing (4) without (1)-(3) is why every "flash and test" returns 52. All four are required.
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
# HANDOFF — Put the Pixel 3 audio to bed (Fable, evening 2026-07-19)
|
||||
|
||||
> Historical handoff. The current contract is `../../audio.md`; this file is
|
||||
> retained as dated evidence and may describe work that was later completed.
|
||||
|
||||
We are *this close*. Mic captures nonzero on every boot now, both loudspeakers
|
||||
fire, modem connects. What's left: reinstate PipeWire (the reason it was banned
|
||||
is gone), keep the mic/speaker gains, and make the phone **call-ready**
|
||||
(two-way call audio). This doc is your orders, not a draft to critique.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ READ THIS FIRST — the references are ALL local, and they are VAST
|
||||
|
||||
Casey's #1 instruction for this handoff: **stop and internalize that nearly
|
||||
every fact you could want is already on this drive** — every bring-up attempt,
|
||||
every Android LIVE capture, every modem trace, every regmap dump, the full
|
||||
Android vendor mixer map, the pmOS known-good config, the raw partition images.
|
||||
Even the outgoing session (me) *barely* knew this reference surface existed and
|
||||
wasted hours re-deriving things that were sitting in a file. **Do not repeat
|
||||
that.** Before you form any theory or run any experiment, grep/read the local
|
||||
references. If you catch yourself reverse-engineering ALSA/UCM/modem internals
|
||||
from scratch, you have already failed — go find the file.
|
||||
|
||||
### The reference surface (4 repos, ~70 GB, all local)
|
||||
|
||||
**1. `~/Projects/Pixel3Arch/PAF/` — the then-current base of truth.** Doctrine + current state.
|
||||
- `audio.md` — the entire audio saga. THE ALTERNATION LAW, THE BOOT RACE,
|
||||
speaker routing provenance, mic transport. Read top-to-bottom.
|
||||
- `audio-2026-07-19-LIVE-reconciliation.md` — **supersedes the top of
|
||||
audio.md.** LIVE Android register captures (recorder vs voice call),
|
||||
the ADM-usecase hypothesis, and the **airplane/radio-mode wedge warning**
|
||||
(some historical "wedges" were mode toggles, not driver faults — rule
|
||||
mode out before logging any wedge).
|
||||
- `HANDOFF-mic-unpin.md` — the un-pin experiment (corrected header; read
|
||||
audio.md "THE BOOT RACE" first).
|
||||
- `modem.md`, `pmos_fix_plan.md`, `qcril_*_capture.md`,
|
||||
`modem_dt_rf_analysis_*.md`, `tz_listener_archaeology.md`,
|
||||
`secure_load_tz_frontier.md`, `qrtr_tools_reference.md`,
|
||||
`tier1_los_vs_mainline_diff.md` — the **modem bring-up archaeology**.
|
||||
Deep. Detailed. Read before touching the modem.
|
||||
- `evidence/`, `captures/` — raw traces (e.g. glink).
|
||||
|
||||
**2. `~/Projects/PostMarketOS-Blueline/` — the pmOS port + all Android captures.**
|
||||
- `android-reference/` — **the gold.** `mixer_paths_tavil_b1.xml` (the FULL
|
||||
Android vendor route map — every named route: `handset`, `speaker`,
|
||||
`voice-handset`, `voice-speaker`, all mics), CS35L36 regmaps
|
||||
(idle/playing), tavil PGD dumps, `wcd9xxx_core-android-LIVE-*.txt`
|
||||
(recording AND voicecall, captured 2026-07-19), `wcd934x-android-ground-truth.md`.
|
||||
- `android-capture-2026*` — nine per-day Android trace dirs.
|
||||
- `PAF/` — a second full modem-trace tree (`android_modem_bringup_capture.md`,
|
||||
`qcril_full_provisioning.md`, `qcril_online_sequence.md`,
|
||||
`modem_consumer_chain_*.md`, `los-mss-translated.c`).
|
||||
- `HANDOFF.md` (80 KB), `PROGRESS.md`, `START-HERE.md`, `README.md`.
|
||||
|
||||
**3. `~/Projects/pixel3-downstream-audio/` — the Android HAL source.**
|
||||
`techpack/audio/` — downstream `q6adm.c`, `q6voice*.c`, ASoC machine driver.
|
||||
This is what the working Android side actually runs; diff mainline behavior
|
||||
against it when a route/topology question comes up.
|
||||
|
||||
**4. `~/pixel3-backup/` (in $HOME, not Projects) — raw partition dumps + pmOS config.**
|
||||
`modem_a.img`, `persist.img`, `vendor_a.img`, `system_a.img` (raw Android),
|
||||
plus `pmos-20260703/` — **the known-good pmOS config backup** the speaker
|
||||
routing was lifted from (see the memory note: this is the source of truth for
|
||||
working modem/cellular + speaker config; restore from here, never guess).
|
||||
|
||||
If a route value, a mixer control, a modem sequence, or a UCM verb is in
|
||||
question — it is decoded in one of the above. Cite `file:line`. Do not invent.
|
||||
|
||||
---
|
||||
|
||||
## CURRENT STATE (verified live this session, phone at WiFi `10.10.20.234`, USB `172.16.42.1`)
|
||||
|
||||
**Kernel:** `7.1.1-sdm845-gcd7758eaeca1` (mic-race-fix, HEAD). Boot-race fix
|
||||
LIVE and working — `MICDBG boot sysclk hold armed` fires in dmesg, capture open
|
||||
joins a live codec (`users=2 skip`) instead of racing bring-up.
|
||||
|
||||
**Mic — WORKING.** `parecord --device=blueline_mic` yields nonzero PCM every
|
||||
boot. Deterministic now (kernel sysclk hold from `wcd934x_comp_probe`).
|
||||
|
||||
**Speakers — WORKING.** Both CS35L36 amps armed, correct route (measured live):
|
||||
`Left/Right AMP Enable = on`, `BOOST = on`, `PCM Volume = 700`,
|
||||
`Left Channel Mux = RX1` (top), `Right Channel Mux = RX2` (bottom). Matches the
|
||||
07-12/07-13 known-good. The `handset` (earpiece) vs `speaker` distinction is a
|
||||
**single control**: `Right AMP Enable` — Android's `handset` route runs the top
|
||||
amp only (`R AMP Enable=0`, low gain); `speaker` runs both. Decoded from
|
||||
`mixer_paths_tavil_b1.xml`.
|
||||
|
||||
**Modem — connects.** Registered (Fido, MNC 720). Call *signaling* works. Note:
|
||||
saw a transient roaming reject (MNC 490, `roaming-not-allowed`) mid-session —
|
||||
that's network, not us, and consistent with the reconciliation doc's mode/radio
|
||||
caveat. Rule mode out before chasing modem "faults."
|
||||
|
||||
**Calls — NO AUDIO yet.** `q6voiced` is now `enabled + running` holding
|
||||
`hw:0,2` (the live VoiceMMode1 PCM — NOT device 5 as the staged conf wrongly
|
||||
said; fixed this session in both `/etc/q6voiced.conf` on-device and
|
||||
`pkgs/q6voiced/q6voiced.conf`). But it logs `Failed to open tx/rx: Invalid
|
||||
argument` on call-start because nothing applies the VoiceCall route. See below.
|
||||
|
||||
---
|
||||
|
||||
## THE TWO HOLDS ON THE MIC (know the difference)
|
||||
|
||||
1. **PA loopback pin** (`90-blueline-alsa.pa`, modules 20/21): null-sink +
|
||||
`module-loopback source=blueline_mic ... source_dont_move sink_dont_move`.
|
||||
Holds the mic open from boot. **Nobody else does this** — verified via GitHub
|
||||
code search: no working sdm845 phone (OnePlus 6 enchilada/fajita, holioka)
|
||||
pins the mic. It's a boot-race workaround.
|
||||
2. **Kernel sysclk hold** (today's fix, in `wcd934x_comp_probe`): permanent
|
||||
sysclk ref + MICB2 bias from probe. Honest hack — pins the winning-boot
|
||||
state so every boot wins.
|
||||
|
||||
**With the kernel hold in place, the PA pin is very likely now redundant.** The
|
||||
un-pin test was never run on a race-won boot. Running it de-hacks the daily
|
||||
driver. See `HANDOFF-mic-unpin.md` for the exact procedure (unload loopback,
|
||||
same-map reopen ×2, watch for `CHAN_CTRL remove ret=0` + fresh `DEF_ACT_CHAN`).
|
||||
If reopen is nonzero → delete the PA pin, keep the kernel hold. If zero → keep
|
||||
both, root-cause stays open.
|
||||
|
||||
---
|
||||
|
||||
## PIPEWIRE — the ban is lifted; bring it back
|
||||
|
||||
**Why it was removed (`da53133`):** the *only* documented reason was the
|
||||
**premature capture source** — wireplumber published the mic before ALSA
|
||||
capture was nonzero (the boot race). PAF audio.md:516/590. PipeWire *playback*
|
||||
was never the problem — `8920c3c` had PW playing on glass fine once the sink
|
||||
was pinned S16LE + RW + no-mmap (the DSP lies about S24/mmap; both play silent).
|
||||
|
||||
**Why it's safe now:** the boot race is fixed in-kernel — **capture is nonzero
|
||||
from boot, deterministically.** The precondition PipeWire failed on is
|
||||
satisfied. Casey has explicitly cleared reinstating it.
|
||||
|
||||
### PipeWire task
|
||||
- Reinstall the full PipeWire + WirePlumber stack. Note the current shims:
|
||||
`pkgs/pipewire-abi-provider`, `pkgs/pulse-native-provider` (they provide the
|
||||
PW ABI *without* a server — you're replacing that arrangement with a real
|
||||
server, so reconcile/remove the shims cleanly, don't leave a half state).
|
||||
- **Re-apply the S16LE playback rule** (the wireplumber
|
||||
`51-blueline-audio-s16.conf` that `8920c3c` created and `da53133` deleted —
|
||||
recover it from git: `git show 8920c3c`). Without it, playback is silent.
|
||||
- Verify on the fixed kernel, in this order, and DO NOT proceed past a failure:
|
||||
1. mic still nonzero via PW (`pw-record`) — this is the thing PW broke before;
|
||||
2. speakers play via PW (`pw-play` a chime) — both top+bottom;
|
||||
3. the mic source is NOT published dead at boot (the old failure mode).
|
||||
- Keep the new features intact: the QS audio service fix
|
||||
(`Audio.qml` — `blueline_mic` description matcher), the mic/speaker gains,
|
||||
the speaker Channel Mux routing.
|
||||
|
||||
**Ponytail guard:** PipeWire is the bigger hammer. Its payoff is that
|
||||
**callaudiod can then route calls via UCM** — which is the whole reason we want
|
||||
it back. If PW re-breaks the mic despite the race fix, fall back to the raw-PA
|
||||
call hook (below) rather than fighting it — the mic is non-negotiable.
|
||||
|
||||
---
|
||||
|
||||
## MAKE IT CALL-READY (the actual finish line)
|
||||
|
||||
The working sdm845 reference (OnePlus 6, `chayleaf/dotfiles`
|
||||
`system/hardware/oneplus-enchilada/default.nix`, and `syntheit/nix`
|
||||
`hosts/fajita`) routes call audio through **UCM (consumed by PipeWire) +
|
||||
callaudiod + q6voiced tied to ModemManager**. Key facts pulled from it:
|
||||
- `q6voiced hw:0,6` there (per-board PCM number; **ours is `hw:0,2`** — always
|
||||
read `/proc/asound/pcm`, never copy the number).
|
||||
- q6voiced is `After=`/`wantedBy=ModemManager.service`.
|
||||
- `alsa-ucm-conf` is shipped and `alsa-ucm-meld = true` — **UCM is the routing
|
||||
layer.** callaudiod flips the verb on call state; q6voiced carries the audio.
|
||||
|
||||
### The call chain we need
|
||||
`gnome-calls → callaudiod → UCM VoiceCall verb → q6voiced carries modem audio`
|
||||
|
||||
Both middle links are currently broken/absent on our raw-PA setup:
|
||||
- **callaudiod** is installed and running but has zero log activity — it routes
|
||||
via PipeWire ports/profiles, which don't exist under raw-PA. **PipeWire back
|
||||
gives callaudiod something to route with.** This is the clean path and the
|
||||
reason PipeWire is worth the risk.
|
||||
- **UCM VoiceCall verb** exists (`Google/blueline/VoiceCall.conf`) but has the
|
||||
**same `hw:0,5` bug** (`PlaybackPCM/CapturePCM "hw:${CardId},5"`) — fix to
|
||||
`,2`. Its EnableSequence is the real voice route:
|
||||
`SLIMBUS_0_RX Voice Mixer VoiceMMode1=1`,
|
||||
`VoiceMMode1 Capture Mixer SLIMBUS_0_TX=1`. Cross-check the RX/TX and DMIC/DEC
|
||||
selections against `mixer_paths_tavil_b1.xml` `voice-handset`/`voice-speaker`
|
||||
and the downstream `q6voice*.c` before trusting them.
|
||||
- **UCM currently fails to load via `alsaucm -c G3`** (`-2`). Under raw-PA this
|
||||
is moot (PA doesn't consume UCM). Once PipeWire+wireplumber drive UCM this
|
||||
must actually resolve — verify wireplumber opens the card's UCM cleanly.
|
||||
(The card driver/id: `id=G3`, longname `Google Pixel 3`, ASoC machine
|
||||
`msm-snd-sdm845`; conf.d lives in `conf.d/sdm845/`. If UCM won't match, this
|
||||
driver-name-vs-dir mapping is the first suspect — but let a real UCM client,
|
||||
not the bare CLI, be your test.)
|
||||
|
||||
### Definition of done — ALL MET 2026-07-29
|
||||
1. PipeWire back, mic + both speakers verified on the fixed kernel. **Done
|
||||
2026-07-19.**
|
||||
2. Place a real call (Casey → his wife). **Two-way audio.** Downlink out the
|
||||
speaker, uplink from the mic, on the call. **Done 2026-07-29** — incoming
|
||||
call answered, both directions confirmed by ear at both ends. Five defects
|
||||
had to fall first; see `PAF/audio.md`, 2026-07-29.
|
||||
3. On hangup, audio reverts to normal HiFi (no stuck voice route). **Done** —
|
||||
and note the reverse was silently broken: the mic read exact zeros after
|
||||
*every* call until the outgoing capture leg was unmapped explicitly.
|
||||
4. q6voiced device number correct (`hw:0,2`) in every place it's written.
|
||||
**Done.**
|
||||
5. New features + gains preserved. Mic still nonzero every boot. **Done** —
|
||||
~90k nonzero frames verified repeatedly across the session.
|
||||
|
||||
Not in the original list, completed 2026-08-02: the earpiece/speaker executor
|
||||
(`blueline-callaudio-hook speaker on|off`) is driven by the patched
|
||||
`souveraine-callaudiod`; see `../../audio.md` and TASK-39.
|
||||
|
||||
### Fallback (if PipeWire re-breaks the mic)
|
||||
Raw-PA call hook: a small ModemManager call-state trigger that applies the
|
||||
VoiceCall mixer controls directly via `amixer`/`alsaucm cset` on call-start and
|
||||
reverts on hangup. ~15 lines. No server swap, zero risk to the working mic.
|
||||
Smaller diff, fewer niceties. Only take this if PipeWire loses the mic.
|
||||
|
||||
---
|
||||
|
||||
## RULES OF ENGAGEMENT
|
||||
- Base of truth = PAF. Every reference is local (the 4 repos above). **Read
|
||||
before theorizing.** Cite `file:line` or say "not in the code."
|
||||
- Rule out airplane/radio mode before logging any modem/audio "wedge."
|
||||
- The mic is the crown jewel. Never trade it away for a nicety.
|
||||
- Verify claims live on the phone; don't assert kernel/route state from memory.
|
||||
- The doctrine holds: build the finished thing, not a v1. This is the last mile.
|
||||
114
saf/device/evidence/audio/HANDOFF-mic-unpin.md
Normal file
114
saf/device/evidence/audio/HANDOFF-mic-unpin.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# Handoff — Pixel 3 mic: un-pin the microphone
|
||||
|
||||
> Historical handoff. Current functional status and the remaining power debt
|
||||
> live in `../../audio.md` and `../../../docs/tasks/07-mic-wcd9340-slim-tx.md`.
|
||||
|
||||
> **CORRECTED 2026-07-19 evening — read audio.md "THE BOOT RACE" first.**
|
||||
> The QRTR=y contamination theory below is disproven: all of today's dead-mic
|
||||
> boots (every kernel, every config) lost a boot-time sysclk race; the working
|
||||
> 262k boot and one untested bdf098937 boot won it. bdf098937 is clean and
|
||||
> deployed; the un-pin test below is still valid but ONLY on a boot whose
|
||||
> pinned baseline reads nonzero. Cold boot until baseline is nonzero, then run
|
||||
> the test as written.
|
||||
|
||||
## The one task
|
||||
Fable got the mic **working** via a PA mic-hold that pins `blueline_mic` open
|
||||
from boot (`rootfs-overlay/etc/pulse/default.pa.d/90-blueline-alsa.pa`, the
|
||||
`module-loopback` into a null sink). The **only** job left: make the mic
|
||||
survive close/reopen so the always-on pin can be removed. Nothing else. Do not
|
||||
expand scope.
|
||||
|
||||
## Known-good state (trust this, do not re-derive)
|
||||
- Working-mic kernel: **`323b29715`** (branch `mic-fix`, gitea
|
||||
`Fimeg/linux-blueline`). MCLK root cause already fixed upstream of it
|
||||
(LN_BB_CLK3, commit `7c6559e0c`). With the PA pin loaded, capture is real
|
||||
audio: ~262k/263k nonzero samples/3s, verified this morning.
|
||||
- The un-pin attempt: **`bdf098937`** = `323b29715` + one commit only
|
||||
("slimbus: qcom-ngd: send CHAN_CTRL remove on stream disable"). Its parent
|
||||
IS the working kernel, so `bdf098937` is exactly "working mic + un-pin,
|
||||
nothing else."
|
||||
|
||||
## What `bdf098937` does (already reviewed, wire format is correct)
|
||||
- `drivers/slimbus/qcom-ngd-ctrl.c`: adds `qcom_slim_ngd_disable_stream` →
|
||||
sends `CHAN_CTRL(REMOVE)` + `RECONFIG_NOW` to the ADSP master on stream
|
||||
disable. Mainline had dropped all 0x40–0x5F reconfig messages, so the master
|
||||
kept the channel "active" after close and a same-map reopen no-op'd → zombie
|
||||
zeros. Wire format matches downstream `slim-msm-ngd.c` exactly:
|
||||
`(SLIM_CH_REMOVE<<6)|(laddr&0x1f)`, TID, chan ids, `mc=SLIM_USR_MC_CHAN_CTRL`.
|
||||
- `sound/soc/codecs/wcd934x.c`: swaps teardown order to `disable` **before**
|
||||
`unprepare` (unprepare frees `stream->ports`, which made disable a silent
|
||||
`-EINVAL` no-op).
|
||||
- On-phone evidence it reached the wire: `MICDBG CHAN_CTRL remove ... ret=0`,
|
||||
GENERIC_ACK code 0x20. So the removal is sent and ACKed.
|
||||
|
||||
## The actual open question
|
||||
`bdf098937` has **never had a clean test.** Every observation today was
|
||||
contaminated:
|
||||
1. Tested with the PA pin still loaded → `disable` never fires (0 shutdowns in
|
||||
dmesg), so the new code path never ran.
|
||||
2. Then it got built ON TOP of a QRTR/PD_MAPPER `=y` config change into
|
||||
`fdb22e5` (see contamination below), and every "still zero" result was on
|
||||
that kernel, not on clean `bdf098937`.
|
||||
|
||||
So it is unknown whether the CHAN_CTRL-remove actually un-pins the mic. That is
|
||||
the thing to find out.
|
||||
|
||||
## Contamination to be aware of (my mess — Opus, this session)
|
||||
- Commit `fdb22e5` on `mic-fix` flips `CONFIG_QRTR=y` + `CONFIG_QCOM_PD_MAPPER=y`
|
||||
(was `=m`/`=m`). This is a **boot-time change** stacked on top of the un-pin
|
||||
commit and it correlates with the mic going dead. Whether QRTR=y is causally
|
||||
breaking the mic or is a red herring is NOT established — but it must NOT be
|
||||
in the kernel used to test the un-pin. Ruled out as a probe-race: on
|
||||
`fdb22e5`, audio_pd came UP at 1.3s, codec armed capture at 13.5s (12s later,
|
||||
no race). `0x1fffffff` = SERVREG_SERVICE_STATE_UP (I first misread it as
|
||||
down — it is UP).
|
||||
- The repo config `kernel/config-blueline.aarch64` currently has QRTR/PD_MAPPER
|
||||
`=y` (my commit `f245323` in `Fimeg/Pixel3Arch`). For a clean un-pin test the
|
||||
build config must be QRTR=m/PD_MAPPER=m (the working-mic config).
|
||||
- Phone is currently running the contaminated `gfdb22e5`; mic reads zero.
|
||||
- ArchDev `~/build/pixel3arch-kernel` was hand-abused with direct `sed` +
|
||||
`makepkg` (NOT the pipeline). Ignore that dir. `config-blueline.aarch64`
|
||||
there was left at `=y`.
|
||||
|
||||
## The correct next step (build ONE kernel, via the pipeline)
|
||||
Build `bdf098937` with the working-mic config (QRTR=m, PD_MAPPER=m), clean, and
|
||||
test the un-pin. Concretely:
|
||||
1. Point the build at commit `bdf098937` (its config, in `Fimeg/Pixel3Arch`,
|
||||
must be QRTR=m/PD_MAPPER=m — the pre-`f245323` values).
|
||||
2. Build through the real pipeline only: `~/Projects/Pixel3Arch/kernel` on
|
||||
ArchDev, `./build.sh <branch-at-bdf098937>` (see PAF/build.md — it
|
||||
fresh-clones from gitea and hard-asserts built hash == gitea tip; never
|
||||
hand-makepkg, never the `~/build` side dir).
|
||||
3. Deploy with `scripts/deploy-kernel.sh` (modules-first, refuses to bless a
|
||||
boot where `uname -r` != installed module tree — PAF/build.md doctrine).
|
||||
4. Cold boot (true power-off, unplug — no warm reboot; warm reboots poison
|
||||
TZ/remoteproc on blueline).
|
||||
|
||||
## The test that answers it
|
||||
On the clean `bdf098937` boot:
|
||||
1. Confirm mic works pinned: `parecord --device=blueline_mic` → nonzero
|
||||
(baseline sanity; pin is loaded from boot).
|
||||
2. `pactl unload-module module-loopback` (drop the pin → the source can now
|
||||
actually close).
|
||||
3. `parecord` again, close it, `parecord` a second time on the SAME TX5 map.
|
||||
4. Watch `dmesg | grep MICDBG` for `shutdown` + `CHAN_CTRL remove ret=0` on the
|
||||
close, then a fresh `DEF_ACT_CHAN` on the reopen.
|
||||
5. **Success = the second/third reopen reads nonzero.** That means CHAN_CTRL
|
||||
remove re-armed the path and the pin can be deleted from
|
||||
`90-blueline-alsa.pa`.
|
||||
6. If it still zeros: the un-pin theory needs more than channel-remove. Next
|
||||
thing to check is whether the **port** (not just the channel) needs
|
||||
explicit disconnect/reconnect — downstream `slim-msm-ngd.c` handles
|
||||
`DISCONNECT_PORT` (mainline does it in `slim_stream_unprepare` via
|
||||
`slim_disconnect_port`); verify it actually fires on close in dmesg, and
|
||||
whether a port re-CONNECT is needed on reopen. Downstream source is local
|
||||
at `~/Projects/pixel3-downstream-audio/drivers/slimbus/slim-msm-ngd.c`
|
||||
(git: `git show HEAD:drivers/slimbus/slim-msm-ngd.c`).
|
||||
|
||||
## Do NOT
|
||||
- Do not roll back / chase old commits beyond `bdf098937`.
|
||||
- Do not remove QRTR/PD_MAPPER as the permanent config — pd-mapper being
|
||||
built-in is wanted; it just must not be in the *un-pin test* kernel.
|
||||
- Do not hand-`sed` configs on ArchDev or hand-`makepkg`. One change, in the
|
||||
repo, pushed, built by `build.sh`.
|
||||
- Do not tear down the working pin except as step 2 of the test above.
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
# 2026-07-19 LIVE Android reconciliation — READ THIS FIRST (supersedes the top of audio.md)
|
||||
|
||||
> Historical capture. The reconciled current contract is `../../audio.md`.
|
||||
|
||||
The `## 0x097/0x032/0x11c are 00 on a WORKING recording` section currently at the
|
||||
top of `audio.md` is **partly WRONG** — it was written after reading only the
|
||||
regmap-cache node. A second live read minutes later (same confirmed-active
|
||||
recording, user watching the phone) via the live-PGD node corrected it. This file
|
||||
is the accurate version. Trust this, not that section.
|
||||
|
||||
## ⭐ VOICE-CALL reconciliation — 0x097 is voice-usecase-specific (the biggest finding)
|
||||
|
||||
Captured the working Android side LIVE via the live node
|
||||
(`/sys/kernel/debug/wcd9xxx_core/slimslave_reg_dump`) under TWO different working
|
||||
usecases, both user-confirmed-live (timer / wife on the call):
|
||||
|
||||
```
|
||||
recorder capture VOICE CALL
|
||||
0x032 irq_en_TX 0x80 0x80
|
||||
0x057 TX7 port cfg 0x05 0x05
|
||||
0x097 TX7 region 00 0x21 <-- voice ONLY
|
||||
0x098 TX7 region 00 0x21 <-- voice ONLY
|
||||
0x11c TX7 MULTI_CHNL 0x80 0x80
|
||||
0x120 TX8 MULTI_CHNL 00 0x80 <-- voice ONLY (2nd mic, dual-mic)
|
||||
```
|
||||
|
||||
Saved: `android-reference/wcd9xxx_core-android-LIVE-voicecall-20260719.txt`
|
||||
(plus `...-LIVE-recording-20260719.txt`).
|
||||
|
||||
**THE RECONCILIATION:** the 2026-07-03 ground-truth doc's `0x097=0x21` was RIGHT
|
||||
— it was captured during a **voice call** (dual-mic handset+endfire, TX7+TX8), not
|
||||
a multimedia recorder capture. The recorder path never sets `0x097/0x098` or TX8's
|
||||
`0x120`. So `0x097` was never a "samples flow for multimedia capture" register —
|
||||
it's set by the **voice usecase's** ADM topology (dual-mic, AANC/fluence).
|
||||
**Chasing `0x097` for the recorder/multimedia path was a category error.**
|
||||
|
||||
This is **strong evidence for the ADM/usecase hypothesis** (see below): the working
|
||||
Android side programs different DSP topology per usecase (recorder = single TX7
|
||||
multimedia; voice = dual TX7+TX8 with AANC). `0x097/0x098` + TX8 `0x120` are the
|
||||
voice-topology footprint.
|
||||
|
||||
**For our broken multimedia mic:** our `MultiMedia1/2` capture on Arch corresponds
|
||||
to Android's **recorder** path. For THAT path the working registers are
|
||||
`0x032=0x80, 0x057=0x05, 0x11c=0x80` only — which we MATCH. So the multimedia-
|
||||
capture register layer genuinely matches working↔broken. The bug for multimedia is
|
||||
NOT in IFD/PGD registers (now confirmed for the recorder usecase specifically).
|
||||
The voice path (`VoiceMMode1`/`hw:0,2`) is a separate, untested question — and if
|
||||
voice is what's broken, `0x097/0x098/0x120` become relevant again, but as voice
|
||||
topology, not as a general "samples flow" reg.
|
||||
|
||||
Do NOT chase `0x097` for the multimedia/recorder mic.
|
||||
|
||||
## What actually happened
|
||||
|
||||
Two debug nodes on Android (LineageOS 22, slot B) read **LIVE during a
|
||||
confirmed-active recording** (LineageOS Recorder, timer counting up, user at the
|
||||
phone said "recording"):
|
||||
|
||||
```
|
||||
regmap/tavil-slim-pgd/registers wcd9xxx_core/slimslave_reg_dump
|
||||
(regmap CACHE — reads 00/stale) (live PGD read over SLIMbus)
|
||||
0x032 PGD irq_en_TX 00 0x80
|
||||
0x057 TX7 port cfg 00 0x05
|
||||
0x097 "mystery" TX7 reg 00 00
|
||||
0x11c TX7 MULTI_CHNL_0 00 0x80
|
||||
```
|
||||
|
||||
**The two nodes read different things.** `regmap/.../registers` reads the regmap
|
||||
CACHE (all 00, not live). `wcd9xxx_core/slimslave_reg_dump` reads the live PGD and
|
||||
shows the real recording values.
|
||||
|
||||
## What this means (correct conclusions)
|
||||
|
||||
1. **The original 2026-07-03 ground-truth doc was RIGHT about 0x032/0x057/0x11c**
|
||||
(it used `wcd9xxx_core`, the live node): working recording = `0x032=0x80`,
|
||||
`0x057=0x05`, `0x11c=0x80`. Those are real.
|
||||
|
||||
2. **The doc's `0x097=0x21` claim was RIGHT — but it was a VOICE call, not a
|
||||
recorder capture.** See the ⭐ VOICE-CALL section above: `0x097/0x098=0x21` and
|
||||
TX8 `0x120=0x80` are voice-usecase-specific. They are `00` during a recorder
|
||||
capture (which is the usecase our multimedia mic maps to). So `0x097` is NOT a
|
||||
multimedia-capture register at all — stop chasing it for the recorder path.
|
||||
|
||||
3. **Our side (Arch / SouveraineOS) matches the working side on
|
||||
0x032/0x057/0x11c** (measured this session after the MCLK fix: our IFD shows
|
||||
`0x032=0x80`, `0x057=0x05`, `0x11c=0x80`). So the IFD/PGD register layer
|
||||
genuinely matches between working Android and our broken capture.
|
||||
**The differentiator is NOT in the IFD/PGD register layer.**
|
||||
|
||||
4. **Methodological correction for our side:** when reading PGD regs, do NOT
|
||||
trust `regmap/.../registers` (it's the cache — lies with 00s). Use the live
|
||||
equivalent. Several "this reg is never set" conclusions on our side this
|
||||
session were probably cache-read artifacts, not real states.
|
||||
|
||||
## Saved artifacts (both LIVE, same recording, 2026-07-19)
|
||||
- `PostMarketOS-Blueline/android-reference/tavil-pgd-android-LIVE-recording-20260719.txt`
|
||||
(regmap cache view — the misleading 00s)
|
||||
- `PostMarketOS-Blueline/android-reference/wcd9xxx_core-android-LIVE-recording-20260719.txt`
|
||||
(live PGD — the real recording state)
|
||||
|
||||
## Verified-correct stack as of 2026-07-19 (live-confirmed)
|
||||
- MCLK to codec: FIXED (LN_BB_CLK2→LN_BB_CLK3 in `sdm845-google-common.dtsi`
|
||||
`&wcd9340` override, uncommitted; live on DTB). eFuse sense completes
|
||||
(`status=0x01`, version `0x401`) — no more `-110`.
|
||||
- Codec init/capture regs: bit-for-bit identical to Android ground truth.
|
||||
- Codec DAPM power-up: fires (`0x0601/0x0626/0x0aa1` toggle on capture).
|
||||
- SLIMbus NGD DEF_ACT_CHAN: sent (qcom-ngd-ctrl.c:1020).
|
||||
- AFE slave-config send (`AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG` + REG_PAGE +
|
||||
REG_INIT): already built in mainline as `q6afe_send_cdc_codec_config()`
|
||||
(q6afe.c:1854), sent with correct codec enum addr `ea=0x21702500100`, `ret=0`.
|
||||
DSP accepts (no "DSP returned error"). **A prior session already built the
|
||||
"port the missing slave-config" fix — do NOT re-derive/re-port it.**
|
||||
- IFD/PGD regs (0x032/0x057/0x11c): MATCH between working and broken (via live
|
||||
node). `0x097`: 00 on both — ruled out.
|
||||
- ADSP file-serving: PD requests only `adsp_avs_config.acdb`, served. OEM ACDB
|
||||
tree not requested via FastRPC — not the gap (falsified).
|
||||
|
||||
## Where the differentiator actually is (still open)
|
||||
Everything the kernel can observe/send/serve — codec regs, SLIMbus NGD, AFE
|
||||
command ack, IFD/PGD regs, file-serving — is identical between working Android
|
||||
and our broken capture. The fault is past all of those.
|
||||
|
||||
**Top hypothesis (NOT yet tested) — ADM usecase routing, not AFE:**
|
||||
On Android, opening the Recorder app goes through the Android audio HAL:
|
||||
`adev_open_input_stream` → platform HAL → `audio_extn` → **`adm_open` with a
|
||||
usecase + device + flags**, which programs the ADM (Audio Dynamic Mixer) topology
|
||||
on the DSP that actually routes capture data into the AFE port. Without an ADM
|
||||
usecase the DSP has an open AFE port with nothing routed into it → samples never
|
||||
flow, every command ACKs. That fits the exact signature.
|
||||
|
||||
**We are Arch / SouveraineOS — there is no Android audio HAL in our stack at
|
||||
all.** We drive ALSA directly (PulseAudio → ALSA `hw:0,0`). No userspace ever
|
||||
calls `adm_open`. No usecase is ever set. Either the kernel does it or it
|
||||
doesn't — and mainline's `q6adm.c` is much thinner than downstream's
|
||||
(`pixel3-downstream-audio/techpack/audio/dsp/q6adm.c`).
|
||||
**This is the unexplored-at-this-depth difference, unlike the AFE slave-config
|
||||
which was already ported.**
|
||||
|
||||
Concrete next move on our side: check whether our kernel/driver has a `q6adm`
|
||||
path wired for capture usecases, and whether opening capture sends ANY
|
||||
`ADM_CMD_ASM` / ADM commands at all. If zero — that's the gap.
|
||||
|
||||
Secondary candidates (also not falsified):
|
||||
- **AFE/APR command sequence on the WIRE (payload bytes), not register state.**
|
||||
No payload-bearing APR capture exists on either side. The one glink trace
|
||||
(`PAF/captures/glink_trace_pmos-20260702.txt`) is framing-only AND modem-context
|
||||
(qmi-proxy traffic), not a mic capture — no AFE opcodes. The Android side has
|
||||
`msm_apr_debug` + `ipc_logging/apr` nodes but they returned empty even during a
|
||||
live recording on this LOS build (APR packet logging not enabled in this image).
|
||||
Getting a payload-bearing APR trace needs either a different LOS build with APR
|
||||
debug enabled, or a kernel tracepoint patch on our side.
|
||||
- On Android, APR debug nodes exist but were empty live: `/sys/kernel/debug/
|
||||
msm_apr_debug` (0 bytes), `/sys/kernel/debug/ipc_logging/apr/{log,log_cont}`
|
||||
(0 bytes even while recording). Glink lpass edge was moving real bytes
|
||||
(TX/RX fifo nonzero) — traffic flows, just not logged at APR layer on this image.
|
||||
- Android's silent/vibrate/sound/airplane modes are audio-policy/stream-mute
|
||||
states, NOT hardware triggers — but they shape WHICH ADM usecase the HAL opens,
|
||||
which is why the usecase-routing question matters.
|
||||
|
||||
## System identity (don't mislabel — this was called "pmOS" by mistake earlier)
|
||||
- **Our phone runs Arch / SouveraineOS**, NOT pmOS. pmOS is only the historical
|
||||
lineage of the kernel/DTB. The running userspace is Arch + SouveraineOS shell
|
||||
+ PulseAudio driving ALSA directly. No Android audio HAL, no `audio_extn`.
|
||||
- Android/LineageOS reference is slot B (boots fine; user was in it this session
|
||||
recording). The mic works there.
|
||||
|
||||
## ⚠️ METHODOLOGICAL WARNING — some historical "wedges" were a real radio/mode switch, not a driver fault
|
||||
Casey flagged 2026-07-19: **at least some of the recurring "wedge" / "seizure" /
|
||||
"the modem grabbed the card" / "SLIM -110 wedge" / "capture wedge" / "chip-wide
|
||||
wedge" events in the audio/mic/modem docs were NOT kernel/codec/SLIM faults at
|
||||
all — they were the phone having switched to airplane mode (or an equivalent
|
||||
radio-off / audio-policy-reroute state), and the resulting silence / dead bus /
|
||||
NACK was misread as a driver-level hang.**
|
||||
|
||||
This means:
|
||||
- Some "wedge" investigations across multiple sessions may have been chasing a
|
||||
mode toggle, not a bug. Treat any historical "wedge" / "seizure" / "poison"
|
||||
conclusion as suspect until reconciled against the mode/radio state at the time.
|
||||
- We do NOT know exactly which wedges were real faults vs mode swaps. Both kinds
|
||||
exist. Don't assume all wedges were phantom — and don't assume all were real.
|
||||
- **BEFORE logging a wedge / NACK / seizure / dead-bus / silence on our side**,
|
||||
check and record: is the phone in airplane mode? Is the modem/radio off (check
|
||||
rfkill, modem state, ofono/ModemManager, the cellular/quickshell overlay)?
|
||||
Is an audio policy rerouting happening? Rule the mode out FIRST, every time.
|
||||
- On Arch/SouveraineOS a radio-off / airplane-equivalent state may be less
|
||||
obvious than Android's UI toggle (a Souveraine service, the cellular overlay,
|
||||
a quickshell switch, a default). It could be getting toggled accidentally or
|
||||
by default, which would explain how it kept biting us unnamed.
|
||||
- Relevant docs to revisit with this lens: `PAF/audio.md` ("SLIM wedge",
|
||||
"chip-wide wedge", "TZ-poison"), `docs/mic-slim-findings-2026-07-17.md`
|
||||
("the SLIM -110 wedge", "capture wedge", "TX7-contaminated wedge",
|
||||
"status-read NACK → 1 Hz wedge loop"). Not all of these are phantom — but the
|
||||
possibility that some are must be checked before re-investigating.
|
||||
|
||||
## Process note for the resuming session
|
||||
- The codec/SLIM/AFE/file-serving layers are exhaustively ruled out (with the
|
||||
wedge caveat above — if a "ruled out" result was taken during a mode-swap, it
|
||||
is NOT valid). Do not re-investigate them UNLESS a prior test's mode state
|
||||
wasn't recorded.
|
||||
- Do NOT re-port the SLIMBUS_SLAVE_CONFIG send (already done, ret=0).
|
||||
- Do NOT chase `0x097`.
|
||||
- Top lead: ADM usecase routing — mainline q6adm vs downstream, and whether our
|
||||
capture sends any ADM commands. We have no HAL to do it, so the kernel must.
|
||||
- Secondary: payload-bearing APR/glink wire capture during a capture on BOTH
|
||||
sides, then byte-diff. That artifact does not exist yet.
|
||||
- **Record mode/radio state with every test going forward.** No more unattributed
|
||||
"wedge" logs.
|
||||
- Android access (airplanemode kills the NCM gadget): `adb` (device serial
|
||||
89JX0AAJP), `adb shell 'su -c "..."'` (magisk root works). Our-side access:
|
||||
ssh `~/.ssh/ani` to the USB gadget IP or WiFi `10.10.20.234`.
|
||||
150
saf/device/evidence/build-pmos-era.md
Normal file
150
saf/device/evidence/build-pmos-era.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# PAF · Build & packaging (pmbootstrap, the proper path)
|
||||
|
||||
How the blueline kernel + device package are actually built and shipped. This
|
||||
is the canonical build layer — earlier sessions wasted hours on host `make`
|
||||
and on undocumented pmbootstrap behaviours. Build with **pmbootstrap**; it is
|
||||
what every other sdm845-mainline contributor uses and what produces installable
|
||||
APKs. Host `make ARCH=arm64 LLVM=1` is not the path (it needs llvm-readelf/nm/
|
||||
objcopy on the host and does not produce a package).
|
||||
|
||||
## The two packages — do not conflate them
|
||||
|
||||
- **`linux-postmarketos-qcom-sdm845`** (`device/community/`) — the KERNEL
|
||||
package. Kernel image (`vmlinuz`/Image.gz), all modules, the blueline DTB.
|
||||
Userspace never goes here.
|
||||
- **`device-google-blueline`** (`device/testing/`) — the DEVICE package.
|
||||
Device userspace: firmware deps, hexagonrpcd/q6voiced confs, the systemd
|
||||
preset, and now the **modem PDC self-provisioner** (`qcril-autoselect.py` +
|
||||
`.service`, enabled via the preset). This is where per-device userspace
|
||||
belongs — modelled on how `q6voiced.conf` is already installed.
|
||||
|
||||
## Unified 7.1.1 kernel build (2026-06-22)
|
||||
|
||||
Base is the sdm845-mainline tree at tag `85f1df2a` (= 7.1.1), checked out at
|
||||
`references/linux-7.1.1-sdm845-CANONICAL`. All accumulated fixes are now **commits in that
|
||||
tree**, not scattered `source=` patches:
|
||||
|
||||
- SLPI keep-proxy-power-on (the `sar.cc:27` crash-loop fix — see slpi.md)
|
||||
- SLPI enabled in the blueline DTS
|
||||
- fastrpc SDSP hyp-assign tolerance
|
||||
|
||||
Built via source override:
|
||||
```
|
||||
cd ~/pmos/pmaports
|
||||
pmbootstrap build --src=$REPO/references/linux-7.1.1-sdm845-CANONICAL \
|
||||
linux-postmarketos-qcom-sdm845
|
||||
```
|
||||
APK lands in `~/.local/var/pmbootstrap/packages/edge/aarch64/`.
|
||||
|
||||
## pmbootstrap build gotchas (each cost real time — RECORD)
|
||||
|
||||
0. **The in-tree `arch/arm64/configs/sdm845.config` is merged LAST and silently
|
||||
overrides our pmaports config.** `prepare()` runs
|
||||
`merge_config.sh -m <pmaports config-*.aarch64> arch/$_carch/configs/sdm845.config`
|
||||
— and `merge_config -m` is last-wins. So any symbol set in the **in-tree**
|
||||
fragment beats the same symbol in the pmaports config, even though the
|
||||
pmaports config "looks" authoritative. **This silently broke wifi
|
||||
(2026-06-29):** pmaports set `CONFIG_QCOM_PD_MAPPER=m`, but the upstream
|
||||
sdm845-mainline in-tree fragment carried `=n` (their "use userspace daemon,
|
||||
poco-f1 mic race" default), so every build shipped pd-mapper OFF → no wlan PD
|
||||
→ `ath10k_snoc` probe defers → no `wlan0`. The `=n` rode in via the vanilla
|
||||
import commit `310c17cc6`, not a deliberate choice. **Fix:** set the symbol in
|
||||
the in-tree fragment (`references/linux-7.1.1-sdm845-CANONICAL/arch/arm64/configs/sdm845.config`),
|
||||
not just pmaports. **Audit rule:** when a built kernel disagrees with the
|
||||
pmaports config, `grep` the in-tree fragment first — it wins. Verify on-device
|
||||
with `zcat /proc/config.gz | grep <SYMBOL>`.
|
||||
|
||||
1. **`olddefconfig` is mandatory on a version bump.** 7.1.1 added Kconfig
|
||||
symbols not in the rc1 fragment config; abuild's build runs non-interactive,
|
||||
so a bare `make` hits the `oldconfig` EOF prompt and dies at `prepare0`.
|
||||
The APKBUILD `build()` now runs `make ARCH=$_carch LLVM=1 olddefconfig`
|
||||
first, then a serial `make ... -j1 prepare`, then the parallel build.
|
||||
|
||||
2. **The `*.bc` gitignore drops a tracked kernel source file.** pmbootstrap's
|
||||
source-override rsync is gitignore-aware but honors `*.bc` (line 16) WITHOUT
|
||||
the negation `!kernel/time/timeconst.bc` (line 180). So `timeconst.bc` — a
|
||||
real bc(1) source needed by `prepare0` for `include/generated/timeconst.h` —
|
||||
is silently excluded → `No rule to make target 'kernel/time/timeconst.bc'`.
|
||||
Same class of breakage hits the negated dotfiles (`.clang-format`, etc.).
|
||||
**Fix:** the kernel APKBUILD `prepare()` restores these from the source mount
|
||||
(`/mnt/pmbootstrap/source-override/`) after `default_prepare`.
|
||||
|
||||
3. **The persistent build copy poisons incremental builds.** pmbootstrap rsyncs
|
||||
the override into `/tmp/pmbootstrap-local-source-copy` (inside the native
|
||||
chroot) WITHOUT `--delete`. A prior interrupted build leaves stale host-tool
|
||||
binaries (`scripts/basic/fixdep`, `scripts/mod/mk_elfconfig`) + `.cmd` files;
|
||||
the next build's `make` then trusts the stale stamps, never recompiles them,
|
||||
and fails `mk_elfconfig: not found` at `prepare0`. If you see host-tool
|
||||
"not found" with no preceding `HOSTCC` line, the copy is dirty.
|
||||
**Recovery:** let pmbootstrap recreate it cleanly (delete the copy dir;
|
||||
a fresh rsync from the clean host tree rebuilds host tools from scratch).
|
||||
Note: `git clean -dfx` on the HOST tree does NOT touch this chroot copy.
|
||||
|
||||
## Kernel-swap / boot.img (from modem.md, kept here for the build flow)
|
||||
|
||||
The modem stack is fully modular (`qcom_q6v5_mss`, `qrtr*`, `qcom_glink_smem`,
|
||||
`mhi*` all `=m`), so a kernel swap needs the matching `lib/modules/<kver>/` on
|
||||
the rootfs or the modem silently no-loads (a false 52). UFS + USB-gadget-NCM are
|
||||
`=y`, so a module-mismatched boot still reaches rootfs + USB.
|
||||
|
||||
Boot image: `cat vmlinuz <blueline>.dtb` + the standing initramfs, `mkbootimg`
|
||||
header v0, base 0x0, kernel_off 0x8000, ramdisk_off 0x1000000, tags 0x100, real
|
||||
cmdline `quiet splash … pmos_boot_uuid=2CE8-836F pmos_root_uuid=26733d78…`.
|
||||
Rootfs is found by `pmos_root_uuid` (the ext4 FS UUID), so the slot LETTER only
|
||||
decides which block device we write into — the cmdline is slot-agnostic.
|
||||
|
||||
SLOT MAP (verified live via `fastboot getvar current-slot` 2026-06-26):
|
||||
**Android = slot B** (`current-slot: b`, `slot-successful:b: yes`). **pmOS = slot A.**
|
||||
Flash ONLY `boot_a` (`fastboot flash boot_a …; fastboot --set-active=a`) and write
|
||||
the rootfs to `system_a`. NEVER touch `boot_b` / `system_b` (that is Android now),
|
||||
EFS(modemst1/2), userdata (single SHARED partition, ~119 GB), or TZ/XBL/ABL.
|
||||
(Earlier revs of this doc had the slots reversed — they were wrong; the device is
|
||||
authoritative.)
|
||||
|
||||
### FLASH GOTCHAS (each cost a boot this session — RECORD)
|
||||
|
||||
1. **`system_a` rejects bootloader-`fastboot flash` → must use TWRP cat (or fastbootd).**
|
||||
`fastboot flash system_a` from the bootloader fails `Partition should be flashed
|
||||
in fastbootd`. This fastboot/bootloader gates system writes to userspace. We DON'T
|
||||
have a working `reboot fastbootd` (binary says "unknown reboot target"), so the
|
||||
path is: `fastboot boot ~/Downloads/twrp-3.7.1_12-0-blueline.img`, wait for adb
|
||||
`recovery`, then `adb shell -T "cat > /dev/block/by-name/system_a; sync" < raw.img`
|
||||
(`system_a` → `/dev/block/sda5`, a real 2.95 GB physical partition, NOT dynamic).
|
||||
|
||||
2. **pmbootstrap's `google-blueline.img` is ANDROID-SPARSE, not raw — un-sparse before cat.**
|
||||
THE bug that gave a kernel-7.1.0rc1 **debug-shell boot** (initramfs can't find root).
|
||||
deviceinfo has `deviceinfo_flash_sparse="true"`, so the final image starts with the
|
||||
sparse magic `3a ff 26 ed`, not a partition table. `cat`'ing it raw onto `system_a`
|
||||
writes a sparse container, so there's no GPT/ext4 on the partition → initramfs
|
||||
`findfs UUID=` fails → debug shell. **Fix:** `simg2img google-blueline.img raw.img`
|
||||
first, THEN cat `raw.img`. Verify: a correct raw image starts with `0000`, reads as
|
||||
GPT @ **4096-byte sectors** (`fdisk -b 4096 -l`: p1 ≈236M EFI/boot, p2 ext4 root at
|
||||
byte 255852544), and has the ext4 magic `53ef` at `255852544+1080`. This matches the
|
||||
known-good `slotb-backup` byte-for-byte. (UFS is 4096-sector — `fdisk` default 512
|
||||
shows NO partitions on the raw image; that's a red herring, not corruption.)
|
||||
|
||||
3. **Quarantine stale `_p<timestamp>` kernel apks before `install`, or apk grabs the wrong one.**
|
||||
A `--src` build auto-versions as `7.1.1_p20260625…`, which apk sorts ABOVE the tagged
|
||||
`7.1.1-r2` (the `_p` post-suffix outranks the bare tag). `pmbootstrap install` then
|
||||
silently pulls the OLD kernel. Move the `_p` apks out of
|
||||
`~/.local/var/pmbootstrap/packages/edge/aarch64/`, rebuild the index (`apk index
|
||||
*.apk` + `abuild-sign` in the native chroot — the `pmbootstrap index` wrapper failed
|
||||
on its own quoting), then `pmbootstrap zap` (the rootfs chroot has the `_p` kernel
|
||||
installed and apk won't downgrade) and re-install.
|
||||
|
||||
## The libqmi fork — packaged as the temp/libqmi override
|
||||
|
||||
`qcril-autoselect.py` needs the **openimsd libqmi fork** (PDC Set Feature msg
|
||||
0x2E) via GObject-introspection; stock libqmi lacks both the message and the
|
||||
typelib. It is packaged as **`temp/libqmi`** (pkgname=`libqmi`, source
|
||||
`gitlab.postmarketos.org/modem/openimsd/libqmi` commit `b683efb`, built with
|
||||
`-Dintrospection=true`, provides `libqmi` + `qmi-utils`). Because the aport's
|
||||
pkgname is `libqmi`, it shadows Alpine's stock libqmi, and `device-google-blueline`
|
||||
pulls it in via its `libqmi` / `qmi-utils` deps — a fresh `pmbootstrap install`
|
||||
lays down kernel + provisioner + fork as one unit. The built apk carries
|
||||
`usr/lib/girepository-1.0/Qmi-1.0.typelib` (verified).
|
||||
|
||||
Watch-item: the override is pinned at `1.39.0-r0`, equal to current Alpine edge.
|
||||
If Alpine bumps libqmi above this, apk could prefer stock at install — bump the
|
||||
override's pkgver/pkgrel to stay ahead. (`references/openimsd-libqmi` +
|
||||
`tools/build-openimsd-libqmi.sh` remain as the on-device overlay fallback.)
|
||||
3137
saf/device/evidence/captures/glink_trace_pmos-20260702.txt
Normal file
3137
saf/device/evidence/captures/glink_trace_pmos-20260702.txt
Normal file
File diff suppressed because it is too large
Load diff
750
saf/device/evidence/d10-adp-touch-2026-08-09.patch
Normal file
750
saf/device/evidence/d10-adp-touch-2026-08-09.patch
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
From 2e349ddf2fe35102ef7c281d356c0e9a53c0f526 Mon Sep 17 00:00:00 2001
|
||||
From: Casey Tunturi <casey@samaritansolutions.net>
|
||||
Date: Mon, 10 Aug 2026 02:05:11 +0000
|
||||
Subject: [PATCH] apple: d10: touch runtime reports and the display pipe
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
apple_z2: D10 shares D11 gen2 runtime reports. Without the enable, boot
|
||||
frames arrive and the panel goes silent about a second later.
|
||||
|
||||
adp: first display pipe on T8010. disp0 be 0x06200000/0x9000, fe
|
||||
0x06400000/0x4000, irqs 154/158; dart 0x06304000 irq 156; dsi
|
||||
0x06600000/0x100000 — byte-identical to T8012, which is A10 silicon.
|
||||
Values read off the D10 ADT. panel-summit gains a 750x1334 mode; the
|
||||
loader lights the panel, so there is no init sequence either way.
|
||||
---
|
||||
arch/arm64/boot/dts/apple/t8010-d10.dts | 179 ++++++++++++++++++++++++
|
||||
arch/arm64/boot/dts/apple/t8010.dtsi | 57 ++++++++
|
||||
drivers/gpu/drm/panel/panel-summit.c | 26 +++-
|
||||
drivers/input/touchscreen/apple_z2.c | 110 +++++++++------
|
||||
4 files changed, 327 insertions(+), 45 deletions(-)
|
||||
|
||||
diff --git a/arch/arm64/boot/dts/apple/t8010-d10.dts b/arch/arm64/boot/dts/apple/t8010-d10.dts
|
||||
index 39cdd12db..5f00bbe19 100644
|
||||
--- a/arch/arm64/boot/dts/apple/t8010-d10.dts
|
||||
+++ b/arch/arm64/boot/dts/apple/t8010-d10.dts
|
||||
@@ -2,6 +2,10 @@
|
||||
/*
|
||||
* Apple iPhone 7 (Qualcomm), D10, iPhone9,1 (A1660/A1778/A1779/A1780)
|
||||
* Copyright (c) 2022, Konrad Dybcio <konradybcio@kernel.org>
|
||||
+ *
|
||||
+ * Board values derived from the D10 ADT read off hardware 2026-08-08.
|
||||
+ * Bus topology is identical to D11; see d10-port/DELTAS.md for the five
|
||||
+ * values that differ and which of them still need driver work.
|
||||
*/
|
||||
|
||||
/dts-v1/;
|
||||
@@ -11,4 +15,179 @@
|
||||
/ {
|
||||
compatible = "apple,d10", "apple,t8010", "apple,arm-platform";
|
||||
model = "Apple iPhone 7 (Qualcomm)";
|
||||
+
|
||||
+ aliases {
|
||||
+ touchscreen0 = &touchscreen;
|
||||
+ };
|
||||
+
|
||||
+ d10_battery: battery {
|
||||
+ compatible = "simple-battery";
|
||||
+ constant-charge-current-max-microamp = <1950000>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * Wi-Fi is deliberately absent. The ADT describes BCM4350 on PCIe port 3
|
||||
+ * (perst 15, clkreq 19, device_wake 88, reg_on pmu_gpio 10, module-instance
|
||||
+ * "kristoff"), but no driver claims it, so PCI pwrctrl never completes and the
|
||||
+ * deferred host bridge takes NVMe down with it:
|
||||
+ * pcie-apple-h9p: waiting for PCI power controls
|
||||
+ * Restore the port node only alongside a BCM4350 driver. Values: d10-port/DELTAS.md.
|
||||
+ */
|
||||
+
|
||||
+&i2c0 {
|
||||
+ status = "okay";
|
||||
+
|
||||
+ pmic@27 {
|
||||
+ compatible = "apple,chestnut-pmic";
|
||||
+ reg = <0x27>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <1>;
|
||||
+
|
||||
+ touch_hv: regulator-touch-hv {
|
||||
+ compatible = "apple,chestnut-touch-hv-regulator";
|
||||
+ regulator-name = "touch-hv";
|
||||
+ };
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&i2c1 {
|
||||
+ charger@75 {
|
||||
+ compatible = "apple,sn2400";
|
||||
+ reg = <0x75>;
|
||||
+
|
||||
+ sn2400_charger: charger {
|
||||
+ compatible = "apple,sn2400-charger";
|
||||
+ monitored-battery = <&d10_battery>;
|
||||
+ input-current-limit-microamp = <2000000>;
|
||||
+ mux-states = <&hdq_mux 0>;
|
||||
+ mux-state-names = "hdq";
|
||||
+ };
|
||||
+
|
||||
+ hdq_mux: hdq-mux {
|
||||
+ compatible = "apple,sn2400-hdq-mux";
|
||||
+ #mux-state-cells = <1>;
|
||||
+ };
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&i2c2 {
|
||||
+ status = "okay";
|
||||
+
|
||||
+ light-sensor@29 {
|
||||
+ compatible = "apple,ct821", "amstaos,tsl2581";
|
||||
+ reg = <0x29>;
|
||||
+ interrupts-extended = <&pinctrl_ap 141 IRQ_TYPE_LEVEL_LOW>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&pinctrl_ap {
|
||||
+ battery_swi_pins: battery-swi-pins {
|
||||
+ pinmux = <APPLE_PINMUX(173, 1)>;
|
||||
+ };
|
||||
+
|
||||
+};
|
||||
+
|
||||
+&serial5 {
|
||||
+ pinctrl-0 = <&battery_swi_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+ status = "okay";
|
||||
+
|
||||
+ onewire {
|
||||
+ compatible = "ti,hdq-uart";
|
||||
+ mux-states = <&hdq_mux 1>;
|
||||
+ mux-state-names = "hdq";
|
||||
+
|
||||
+ /* ADT: gas-gauge,bq27540 — D11 is a bq27545. */
|
||||
+ fuel-gauge {
|
||||
+ compatible = "apple,d10-bq27540", "ti,bq27540-hdq";
|
||||
+ monitored-battery = <&d10_battery>;
|
||||
+ };
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&spi2 {
|
||||
+ cs-gpios = <&pinctrl_ap 81 GPIO_ACTIVE_LOW>;
|
||||
+ dmas = <&sio 0x18>;
|
||||
+ dma-names = "tx";
|
||||
+ status = "okay";
|
||||
+
|
||||
+ touchscreen: touchscreen@0 {
|
||||
+ compatible = "apple,d10-touchscreen";
|
||||
+ reg = <0>;
|
||||
+ spi-max-frequency = <3000000>;
|
||||
+ spi-cpol;
|
||||
+ spi-cpha;
|
||||
+ apple,z2-no-init-ack;
|
||||
+ interrupts-extended = <&pinctrl_ap 166 IRQ_TYPE_EDGE_FALLING>;
|
||||
+ reset-gpios = <&pinctrl_ap 161 GPIO_ACTIVE_LOW>;
|
||||
+ hv-supply = <&touch_hv>;
|
||||
+ core-supply = <&touch_core>;
|
||||
+ clocks = <&touchclk>;
|
||||
+ /* ADT hid-merge-personality = C1F5D,2 (D11/D111 are C1F5E,2). */
|
||||
+ firmware-name = "apple/dfrmtfw-d10-c1f5d-2.bin";
|
||||
+ touchscreen-size-x = <750>;
|
||||
+ touchscreen-size-y = <1334>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&sio {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&touchclk {
|
||||
+ clock-frequency = <32768>;
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * Apple Display Pipe. ADP inherits bootloader-initialised state, so the
|
||||
+ * back-end domain must stay powered or the driver loses the panel m1n1 lit.
|
||||
+ */
|
||||
+&ps_disp0_be {
|
||||
+ apple,always-on;
|
||||
+};
|
||||
+
|
||||
+&disp0 {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&disp0_dart {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&disp0_mipi {
|
||||
+ status = "okay";
|
||||
+};
|
||||
+
|
||||
+&mipi_out {
|
||||
+ mipi_out_panel: endpoint@0 {
|
||||
+ reg = <0>;
|
||||
+ remote-endpoint = <&panel_in>;
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+&disp0_mipi {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ panel@0 {
|
||||
+ compatible = "apple,d10-panel";
|
||||
+ reg = <0>;
|
||||
+ max-brightness = <2047>;
|
||||
+
|
||||
+ port {
|
||||
+ panel_in: endpoint {
|
||||
+ remote-endpoint = <&mipi_out_panel>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+};
|
||||
+
|
||||
+/*
|
||||
+ * ADP owns the panel, so the loader framebuffer must not also claim it:
|
||||
+ * simpledrm binds first as the boot console and udev then names it the
|
||||
+ * primary GPU, which is the card the compositor picks. One writer.
|
||||
+ */
|
||||
+&framebuffer0 {
|
||||
+ status = "disabled";
|
||||
};
|
||||
diff --git a/arch/arm64/boot/dts/apple/t8010.dtsi b/arch/arm64/boot/dts/apple/t8010.dtsi
|
||||
index 8dc02eb60..7ec65050e 100644
|
||||
--- a/arch/arm64/boot/dts/apple/t8010.dtsi
|
||||
+++ b/arch/arm64/boot/dts/apple/t8010.dtsi
|
||||
@@ -358,6 +358,63 @@ ehci1: usb-ehci@20c400000 {
|
||||
#endif
|
||||
};
|
||||
|
||||
+ disp0: display-pipe@206200000 {
|
||||
+ compatible = "apple,t8010-display-pipe", "apple,h7-display-pipe";
|
||||
+ reg = <0x2 0x06200000 0x0 0x9000>,
|
||||
+ <0x2 0x06400000 0x0 0x4000>;
|
||||
+ reg-names = "be", "fe";
|
||||
+ power-domains = <&ps_disp0_fe>, <&ps_disp0_be>;
|
||||
+ interrupts = <AIC_IRQ 154 IRQ_TYPE_LEVEL_HIGH>,
|
||||
+ <AIC_IRQ 158 IRQ_TYPE_LEVEL_HIGH>;
|
||||
+ interrupt-names = "be", "fe";
|
||||
+ iommus = <&disp0_dart 0>;
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ port {
|
||||
+ adp_out_mipi: endpoint {
|
||||
+ remote-endpoint = <&mipi_in_adp>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ disp0_dart: iommu@206304000 {
|
||||
+ compatible = "apple,t8010-dart", "apple,s5l8960x-dart";
|
||||
+ reg = <0x2 0x06304000 0x0 0x4000>;
|
||||
+ interrupts = <AIC_IRQ 156 IRQ_TYPE_LEVEL_HIGH>;
|
||||
+ #iommu-cells = <1>;
|
||||
+ power-domains = <&ps_disp0_fe>;
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ disp0_mipi: dsi@206600000 {
|
||||
+ compatible = "apple,t8010-display-pipe-mipi", "apple,h7-display-pipe-mipi";
|
||||
+ reg = <0x2 0x06600000 0x0 0x100000>;
|
||||
+ power-domains = <&ps_mipi_dsi>;
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ ports {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ mipi_in: port@0 {
|
||||
+ reg = <0>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ mipi_in_adp: endpoint@0 {
|
||||
+ reg = <0>;
|
||||
+ remote-endpoint = <&adp_out_mipi>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ mipi_out: port@1 {
|
||||
+ reg = <1>;
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
pcie0_dart0: iommu@601008000 {
|
||||
compatible = "apple,t8010-dart", "apple,s5l8960x-dart";
|
||||
reg = <0x6 0x01008000 0x0 0x4000>;
|
||||
diff --git a/drivers/gpu/drm/panel/panel-summit.c b/drivers/gpu/drm/panel/panel-summit.c
|
||||
index 6d40b9ddf..42e4c24fb 100644
|
||||
--- a/drivers/gpu/drm/panel/panel-summit.c
|
||||
+++ b/drivers/gpu/drm/panel/panel-summit.c
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
+#include <linux/of.h>
|
||||
#include <linux/property.h>
|
||||
#include <drm/drm_device.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
@@ -48,15 +49,35 @@ static struct drm_display_mode summit_mode = {
|
||||
.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC,
|
||||
};
|
||||
|
||||
+/* iPhone 7 (D10): 750x1334, lit by m1n1 before Linux starts. */
|
||||
+static struct drm_display_mode d10_mode = {
|
||||
+ .hdisplay = 750,
|
||||
+ .vdisplay = 1334,
|
||||
+ .hsync_start = 750 + 8,
|
||||
+ .hsync_end = 750 + 8 + 16,
|
||||
+ .htotal = 750 + 8 + 16 + 16,
|
||||
+ .vsync_start = 1334 + 4,
|
||||
+ .vsync_end = 1334 + 4 + 8,
|
||||
+ .vtotal = 1334 + 4 + 8 + 8,
|
||||
+ .clock = ((750 + 8 + 16 + 16) * (1334 + 4 + 8 + 8) * 60) / 1000,
|
||||
+ .type = DRM_MODE_TYPE_DRIVER,
|
||||
+ .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC,
|
||||
+};
|
||||
+
|
||||
static int summit_get_modes(struct drm_panel *panel,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
+ const struct drm_display_mode *mode =
|
||||
+ of_device_get_match_data(panel->dev);
|
||||
+
|
||||
+ if (!mode)
|
||||
+ mode = &summit_mode;
|
||||
connector->display_info.non_desktop = true;
|
||||
drm_object_property_set_value(&connector->base,
|
||||
connector->dev->mode_config.non_desktop_property,
|
||||
connector->display_info.non_desktop);
|
||||
|
||||
- return drm_connector_helper_get_modes_fixed(connector, &summit_mode);
|
||||
+ return drm_connector_helper_get_modes_fixed(connector, mode);
|
||||
}
|
||||
|
||||
static const struct drm_panel_funcs summit_panel_funcs = {
|
||||
@@ -113,7 +134,8 @@ static DEFINE_SIMPLE_DEV_PM_OPS(summit_pm_ops, summit_suspend,
|
||||
summit_set_brightness);
|
||||
|
||||
static const struct of_device_id summit_of_match[] = {
|
||||
- { .compatible = "apple,summit" },
|
||||
+ { .compatible = "apple,summit", .data = &summit_mode },
|
||||
+ { .compatible = "apple,d10-panel", .data = &d10_mode },
|
||||
{},
|
||||
};
|
||||
|
||||
diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
|
||||
index ec84de96f..065fa8214 100644
|
||||
--- a/drivers/input/touchscreen/apple_z2.c
|
||||
+++ b/drivers/input/touchscreen/apple_z2.c
|
||||
@@ -93,6 +93,7 @@ enum apple_z2_protocol_state {
|
||||
enum apple_z2_variant {
|
||||
APPLE_Z2_VARIANT_GENERIC,
|
||||
APPLE_Z2_VARIANT_J172,
|
||||
+ APPLE_Z2_VARIANT_D10,
|
||||
APPLE_Z2_VARIANT_D11,
|
||||
APPLE_Z2_VARIANT_D111,
|
||||
};
|
||||
@@ -150,19 +151,35 @@ static bool apple_z2_is_j172(const struct apple_z2 *z2)
|
||||
return z2->variant == APPLE_Z2_VARIANT_J172;
|
||||
}
|
||||
|
||||
+static bool apple_z2_is_d10(const struct apple_z2 *z2)
|
||||
+{
|
||||
+ return z2->variant == APPLE_Z2_VARIANT_D10;
|
||||
+}
|
||||
+
|
||||
static bool apple_z2_is_d11(const struct apple_z2 *z2)
|
||||
{
|
||||
return z2->variant == APPLE_Z2_VARIANT_D11;
|
||||
}
|
||||
|
||||
+/* D10 and D11 share the gen2 runtime report protocol. Without the enable,
|
||||
+ * boot frames arrive and the panel then goes silent — measured on d10
|
||||
+ * 2026-08-09: touch reported for about a second after probe, then nothing. */
|
||||
+static bool apple_z2_is_gen2_reports(const struct apple_z2 *z2)
|
||||
+{
|
||||
+ return z2->variant == APPLE_Z2_VARIANT_D10 ||
|
||||
+ z2->variant == APPLE_Z2_VARIANT_D11;
|
||||
+}
|
||||
+
|
||||
static bool apple_z2_is_d111(const struct apple_z2 *z2)
|
||||
{
|
||||
return z2->variant == APPLE_Z2_VARIANT_D111;
|
||||
}
|
||||
|
||||
-static bool apple_z2_is_iphone7_plus(const struct apple_z2 *z2)
|
||||
+/* Whole T8010 iPhone 7 generation, both panel sizes. */
|
||||
+static bool apple_z2_is_iphone7(const struct apple_z2 *z2)
|
||||
{
|
||||
- return apple_z2_is_d11(z2) || apple_z2_is_d111(z2);
|
||||
+ return apple_z2_is_d10(z2) || apple_z2_is_d11(z2) ||
|
||||
+ apple_z2_is_d111(z2);
|
||||
}
|
||||
|
||||
struct apple_z2_finger {
|
||||
@@ -404,7 +421,7 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
|
||||
msg_len, nfingers);
|
||||
return;
|
||||
}
|
||||
- if (apple_z2_is_iphone7_plus(z2) && nfingers &&
|
||||
+ if (apple_z2_is_iphone7(z2) && nfingers &&
|
||||
!z2->surface_descriptor_valid) {
|
||||
dev_warn_ratelimited(&z2->spidev->dev,
|
||||
"iPhone 7 Plus touch packet without surface descriptor\n");
|
||||
@@ -422,7 +439,7 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
|
||||
input_mt_slot(z2->input_dev, slot);
|
||||
if (!input_mt_report_slot_state(z2->input_dev, MT_TOOL_FINGER, slot_valid))
|
||||
continue;
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
abs_x = (s16)le16_to_cpu(fingers[i].abs_x);
|
||||
abs_y = (s16)le16_to_cpu(fingers[i].abs_y);
|
||||
coords_valid = apple_z2_scale_iphone7_coord(abs_x,
|
||||
@@ -454,7 +471,7 @@ static void apple_z2_parse_touches(struct apple_z2 *z2,
|
||||
input_report_abs(z2->input_dev, ABS_MT_WIDTH_MINOR,
|
||||
le16_to_cpu(fingers[i].tool_minor));
|
||||
orientation = (s16)le16_to_cpu(fingers[i].orientation);
|
||||
- if (apple_z2_is_iphone7_plus(z2))
|
||||
+ if (apple_z2_is_iphone7(z2))
|
||||
orientation = (s16)(0x4000 - orientation);
|
||||
input_report_abs(z2->input_dev, ABS_MT_ORIENTATION, orientation);
|
||||
input_report_abs(z2->input_dev, ABS_MT_TOUCH_MAJOR,
|
||||
@@ -473,7 +490,7 @@ static void apple_z2_dispatch_frame(struct apple_z2 *z2, const u8 *payload,
|
||||
u16 report_len;
|
||||
|
||||
/* iPhone 7 Plus Gen2 packets carry the legacy report header directly. */
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
if (payload_len < APPLE_Z2_FINGERS_OFFSET)
|
||||
return;
|
||||
|
||||
@@ -572,11 +589,11 @@ static int apple_z2_read_packet(struct apple_z2 *z2)
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
wire_len = pkt_len;
|
||||
- if (apple_z2_is_d11(z2))
|
||||
+ if (apple_z2_is_gen2_reports(z2))
|
||||
wire_len = max_t(size_t, wire_len,
|
||||
APPLE_Z2_GEN2_MIN_RESULT_SIZE);
|
||||
|
||||
- if (apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) {
|
||||
memset(z2->rx_buf, 0xa5, wire_len);
|
||||
xfer.tx_buf = z2->rx_buf;
|
||||
xfer.rx_buf = z2->rx_buf;
|
||||
@@ -587,11 +604,11 @@ static int apple_z2_read_packet(struct apple_z2 *z2)
|
||||
}
|
||||
if (error)
|
||||
return error;
|
||||
- if (apple_z2_is_iphone7_plus(z2))
|
||||
+ if (apple_z2_is_iphone7(z2))
|
||||
apple_z2_post_z2_xfer_delay(z2);
|
||||
|
||||
/* D11 touch reports do not follow the requested alternating tag. */
|
||||
- strict_counter = !apple_z2_is_d11(z2) ||
|
||||
+ strict_counter = !apple_z2_is_gen2_reports(z2) ||
|
||||
(pkt_len > 5 && z2->rx_buf[5] ==
|
||||
APPLE_Z2_D11_STATUS_REPORT);
|
||||
z2->runtime_frame_valid =
|
||||
@@ -705,7 +722,7 @@ static void apple_z2_avoid_short_dma(struct apple_z2 *z2,
|
||||
* an RX buffer makes shorter writes full-duplex, which the T8010
|
||||
* controller handles through PIO without changing the wire transfer.
|
||||
*/
|
||||
- if (!apple_z2_is_iphone7_plus(z2) || !z2->dma_min_len ||
|
||||
+ if (!apple_z2_is_iphone7(z2) || !z2->dma_min_len ||
|
||||
!xfer->tx_buf || xfer->rx_buf ||
|
||||
xfer->len >= z2->dma_min_len)
|
||||
return;
|
||||
@@ -831,7 +848,7 @@ static int apple_z2_write_report_locked(struct apple_z2 *z2, u8 report,
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
- if (apple_z2_is_d11(z2) &&
|
||||
+ if (apple_z2_is_gen2_reports(z2) &&
|
||||
(z2->rx_buf[0] != APPLE_Z2_CMD_LAST ||
|
||||
!apple_z2_z2_checksum_valid(z2->rx_buf, APPLE_Z2_CMD_SIZE)))
|
||||
return -EPROTO;
|
||||
@@ -978,7 +995,7 @@ static int apple_z2_store_surface_descriptor(struct apple_z2 *z2, u16 len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int apple_z2_iphone7_plus_init_locked(struct apple_z2 *z2)
|
||||
+static int apple_z2_iphone7_init_locked(struct apple_z2 *z2)
|
||||
{
|
||||
static const u8 disable_legacy_reports[] = { 0 };
|
||||
u16 surface_len;
|
||||
@@ -1289,17 +1306,17 @@ static void apple_z2_platform_power_off(struct apple_z2 *z2)
|
||||
{
|
||||
int error;
|
||||
|
||||
- if (apple_z2_is_iphone7_plus(z2))
|
||||
+ if (apple_z2_is_iphone7(z2))
|
||||
gpiod_set_value_cansleep(z2->reset_gpio, 1);
|
||||
|
||||
if (z2->clk_enabled) {
|
||||
clk_disable_unprepare(z2->clk);
|
||||
z2->clk_enabled = false;
|
||||
}
|
||||
- if (apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2))
|
||||
+ if (apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2))
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
if (z2->core_enabled) {
|
||||
error = regulator_disable(z2->core_supply);
|
||||
if (error) {
|
||||
@@ -1381,7 +1398,7 @@ static int apple_z2_j172_power_on(struct apple_z2 *z2)
|
||||
APPLE_Z2_STATE_POWERED);
|
||||
}
|
||||
|
||||
-static int apple_z2_iphone7_plus_power_on(struct apple_z2 *z2)
|
||||
+static int apple_z2_iphone7_power_on(struct apple_z2 *z2)
|
||||
{
|
||||
int error;
|
||||
|
||||
@@ -1422,8 +1439,8 @@ static int apple_z2_platform_power_on(struct apple_z2 *z2)
|
||||
|
||||
if (apple_z2_is_j172(z2))
|
||||
return apple_z2_j172_power_on(z2);
|
||||
- if (apple_z2_is_iphone7_plus(z2))
|
||||
- return apple_z2_iphone7_plus_power_on(z2);
|
||||
+ if (apple_z2_is_iphone7(z2))
|
||||
+ return apple_z2_iphone7_power_on(z2);
|
||||
|
||||
apple_z2_set_gpio(z2->power_ana_gpio, 1);
|
||||
usleep_range(1000, 2000);
|
||||
@@ -1609,14 +1626,14 @@ static int apple_z2_send_firmware_blob(struct apple_z2 *z2, const u8 *data,
|
||||
* SmartIO may still DMA these transfers, but using 16-bit SPI words would
|
||||
* swap every byte pair a second time.
|
||||
*/
|
||||
- if (!init && !apple_z2_is_iphone7_plus(z2) &&
|
||||
+ if (!init && !apple_z2_is_iphone7(z2) &&
|
||||
size >= z2->bpw16_min_len)
|
||||
blob_xfer.bits_per_word = 16;
|
||||
|
||||
dev_dbg(&z2->spidev->dev, "firmware blob len=%u bpw=%u\n",
|
||||
size, blob_xfer.bits_per_word);
|
||||
|
||||
- if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) && init) {
|
||||
+ if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) && init) {
|
||||
if (size != 4 || data[0] != 0x1a || data[1] != 0xa1 ||
|
||||
data[2] != 0x18 || data[3] != 0xe1)
|
||||
return dev_err_probe(&z2->spidev->dev, -EINVAL,
|
||||
@@ -1634,7 +1651,7 @@ static int apple_z2_send_firmware_blob(struct apple_z2 *z2, const u8 *data,
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
- if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) && init) {
|
||||
+ if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) && init) {
|
||||
ready = z2->rx_buf[0] == 0x1f && z2->rx_buf[1] == 0x01;
|
||||
if (!ready)
|
||||
return dev_err_probe(&z2->spidev->dev, -EPROTO,
|
||||
@@ -1648,7 +1665,7 @@ static int apple_z2_send_firmware_blob(struct apple_z2 *z2, const u8 *data,
|
||||
return error;
|
||||
apple_z2_post_ack_delay(z2);
|
||||
}
|
||||
- if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) && init) {
|
||||
+ if ((apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) && init) {
|
||||
error = apple_z2_advance_protocol(z2, APPLE_Z2_STATE_BOOT_IRQ,
|
||||
APPLE_Z2_STATE_HBPP_READY);
|
||||
if (error)
|
||||
@@ -1741,7 +1758,7 @@ static int apple_z2_wait_ready_irq(struct apple_z2 *z2, u32 timeout_ms)
|
||||
{
|
||||
if (!apple_z2_wait_firmware_irq(z2, timeout_ms))
|
||||
return -ETIMEDOUT;
|
||||
- if (!apple_z2_is_j172(z2) && !apple_z2_is_iphone7_plus(z2))
|
||||
+ if (!apple_z2_is_j172(z2) && !apple_z2_is_iphone7(z2))
|
||||
return 0;
|
||||
|
||||
if (apple_z2_is_j172(z2) &&
|
||||
@@ -1778,7 +1795,7 @@ static int apple_z2_apply_fw_config(struct apple_z2 *z2, const u8 *data,
|
||||
cpol = apple_z2_fw_config_word(data, 5);
|
||||
boot_timeout = apple_z2_fw_config_word(data, 9);
|
||||
if ((valid & APPLE_Z2_FW_CONFIG_MIN_DMA) && min_dma) {
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
if (min_dma > APPLE_Z2_RX_BUF_SIZE)
|
||||
return -EINVAL;
|
||||
z2->dma_min_len = min_dma;
|
||||
@@ -1790,7 +1807,7 @@ static int apple_z2_apply_fw_config(struct apple_z2 *z2, const u8 *data,
|
||||
z2->z2_inter_packet_delay_us = z2_delay;
|
||||
/* iPhone 7 Plus uses zero to retain the required 1 ms CS delay. */
|
||||
if ((valid & APPLE_Z2_FW_CONFIG_CS_DELAY) &&
|
||||
- (!apple_z2_is_iphone7_plus(z2) || cs_delay))
|
||||
+ (!apple_z2_is_iphone7(z2) || cs_delay))
|
||||
z2->z2_cs_delay_us = cs_delay;
|
||||
if ((valid & APPLE_Z2_FW_CONFIG_BOOT_TIMEOUT) && boot_timeout)
|
||||
z2->boot_timeout_ms = boot_timeout;
|
||||
@@ -1922,7 +1939,7 @@ static int apple_z2_upload_firmware(struct apple_z2 *z2)
|
||||
u32 max_size;
|
||||
u32 provider;
|
||||
|
||||
- if (!apple_z2_is_iphone7_plus(z2))
|
||||
+ if (!apple_z2_is_iphone7(z2))
|
||||
return -EINVAL;
|
||||
if (size != sizeof(struct apple_z2_fw_calibration) ||
|
||||
size > fw->size - fw_idx)
|
||||
@@ -1969,19 +1986,19 @@ static int apple_z2_upload_firmware(struct apple_z2 *z2)
|
||||
fw_idx = round_up(fw_idx, 4);
|
||||
}
|
||||
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
msleep(50);
|
||||
mutex_lock(&z2->io_lock);
|
||||
- error = apple_z2_iphone7_plus_init_locked(z2);
|
||||
+ error = apple_z2_iphone7_init_locked(z2);
|
||||
if (!error) {
|
||||
z2->booted = true;
|
||||
error = apple_z2_read_packet(z2);
|
||||
}
|
||||
- if (!error && apple_z2_is_d11(z2) &&
|
||||
+ if (!error && apple_z2_is_gen2_reports(z2) &&
|
||||
(!z2->runtime_frame_valid ||
|
||||
z2->rx_buf[5] != APPLE_Z2_D11_STATUS_REPORT))
|
||||
error = -EPROTO;
|
||||
- if (!error && apple_z2_is_d11(z2))
|
||||
+ if (!error && apple_z2_is_gen2_reports(z2))
|
||||
error = apple_z2_enable_d11_reports_locked(z2);
|
||||
mutex_unlock(&z2->io_lock);
|
||||
return error;
|
||||
@@ -2087,7 +2104,7 @@ static int apple_z2_boot_preamble(struct apple_z2 *z2, bool full_duplex)
|
||||
APPLE_Z2_STATE_SPI_CONFIGURED);
|
||||
}
|
||||
|
||||
-static int apple_z2_iphone7_plus_post_boot_preamble(struct apple_z2 *z2)
|
||||
+static int apple_z2_iphone7_post_boot_preamble(struct apple_z2 *z2)
|
||||
{
|
||||
struct spi_transfer xfer = {
|
||||
.tx_buf = z2->tx_buf,
|
||||
@@ -2114,7 +2131,7 @@ static int apple_z2_boot(struct apple_z2 *z2)
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
- if (apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) {
|
||||
error = apple_z2_apply_initial_config(z2);
|
||||
if (error)
|
||||
goto err_stop;
|
||||
@@ -2123,7 +2140,7 @@ static int apple_z2_boot(struct apple_z2 *z2)
|
||||
error = apple_z2_boot_preamble(z2, false);
|
||||
if (error)
|
||||
goto err_stop;
|
||||
- } else if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ } else if (apple_z2_is_iphone7(z2)) {
|
||||
/* iPhone 7 Plus performs a legacy reset and zero preamble first. */
|
||||
error = apple_z2_pulse_reset(z2);
|
||||
if (error)
|
||||
@@ -2142,7 +2159,7 @@ static int apple_z2_boot(struct apple_z2 *z2)
|
||||
irq_enabled = true;
|
||||
if (apple_z2_is_j172(z2)) {
|
||||
error = apple_z2_pulse_reset(z2);
|
||||
- } else if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ } else if (apple_z2_is_iphone7(z2)) {
|
||||
gpiod_set_value_cansleep(z2->reset_gpio, 0);
|
||||
usleep_range(1000, 2000);
|
||||
error = 0;
|
||||
@@ -2158,20 +2175,20 @@ static int apple_z2_boot(struct apple_z2 *z2)
|
||||
error = -ETIMEDOUT;
|
||||
goto err_stop;
|
||||
}
|
||||
- if (apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) {
|
||||
error = apple_z2_advance_protocol(z2,
|
||||
APPLE_Z2_STATE_SPI_CONFIGURED,
|
||||
APPLE_Z2_STATE_BOOT_IRQ);
|
||||
if (error)
|
||||
goto err_stop;
|
||||
}
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
- error = apple_z2_iphone7_plus_post_boot_preamble(z2);
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
+ error = apple_z2_iphone7_post_boot_preamble(z2);
|
||||
if (error)
|
||||
goto err_stop;
|
||||
}
|
||||
|
||||
- if (apple_z2_is_j172(z2) || apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_j172(z2) || apple_z2_is_iphone7(z2)) {
|
||||
disable_irq(z2->spidev->irq);
|
||||
z2->upload_irq_masked = true;
|
||||
}
|
||||
@@ -2211,6 +2228,11 @@ static const struct apple_z2_chip_info apple_z2_j172_info = {
|
||||
.variant = APPLE_Z2_VARIANT_J172,
|
||||
};
|
||||
|
||||
+static const struct apple_z2_chip_info apple_z2_d10_info = {
|
||||
+ .name = "iPhone9,1 Touchscreen",
|
||||
+ .variant = APPLE_Z2_VARIANT_D10,
|
||||
+};
|
||||
+
|
||||
static const struct apple_z2_chip_info apple_z2_d11_info = {
|
||||
.name = "iPhone9,2 Touchscreen",
|
||||
.variant = APPLE_Z2_VARIANT_D11,
|
||||
@@ -2249,7 +2271,7 @@ static int apple_z2_probe(struct spi_device *spi)
|
||||
z2->spidev = spi;
|
||||
z2->variant = info->variant;
|
||||
z2->boot_timeout_ms = 20;
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
z2->z2_inter_packet_delay_us = 1000;
|
||||
z2->z2_cs_delay_us = 1000;
|
||||
z2->boot_timeout_ms = 500;
|
||||
@@ -2269,7 +2291,7 @@ static int apple_z2_probe(struct spi_device *spi)
|
||||
if (IS_ERR(z2->reset_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(z2->reset_gpio),
|
||||
"unable to get reset GPIO\n");
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
z2->hv_supply = devm_regulator_get(dev, "hv");
|
||||
if (IS_ERR(z2->hv_supply))
|
||||
return dev_err_probe(dev, PTR_ERR(z2->hv_supply),
|
||||
@@ -2316,7 +2338,7 @@ static int apple_z2_probe(struct spi_device *spi)
|
||||
error = device_property_read_string(dev, "firmware-name", &z2->fw_name);
|
||||
if (error)
|
||||
return dev_err_probe(dev, error, "unable to get firmware name\n");
|
||||
- if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ if (apple_z2_is_iphone7(z2)) {
|
||||
static const struct {
|
||||
const char *name;
|
||||
unsigned int max_size;
|
||||
@@ -2361,7 +2383,7 @@ static int apple_z2_probe(struct spi_device *spi)
|
||||
|
||||
if (apple_z2_is_j172(z2)) {
|
||||
slots = APPLE_Z2_J172_MAX_CONTACTS;
|
||||
- } else if (apple_z2_is_iphone7_plus(z2)) {
|
||||
+ } else if (apple_z2_is_iphone7(z2)) {
|
||||
slots = 10;
|
||||
if (apple_z2_is_d11(z2))
|
||||
mt_flags |= INPUT_MT_DROP_UNUSED;
|
||||
@@ -2415,6 +2437,7 @@ static const struct of_device_id apple_z2_of_match[] = {
|
||||
{ .compatible = "apple,j293-touchbar", .data = &apple_z2_j293_info },
|
||||
{ .compatible = "apple,j493-touchbar", .data = &apple_z2_j493_info },
|
||||
{ .compatible = "apple,j172-touchscreen", .data = &apple_z2_j172_info },
|
||||
+ { .compatible = "apple,d10-touchscreen", .data = &apple_z2_d10_info },
|
||||
{ .compatible = "apple,d11-touchscreen", .data = &apple_z2_d11_info },
|
||||
{ .compatible = "apple,d111-touchscreen", .data = &apple_z2_d111_info },
|
||||
{}
|
||||
@@ -2425,6 +2448,7 @@ static struct spi_device_id apple_z2_of_id[] = {
|
||||
{ .name = "j293-touchbar", .driver_data = (kernel_ulong_t)&apple_z2_j293_info },
|
||||
{ .name = "j493-touchbar", .driver_data = (kernel_ulong_t)&apple_z2_j493_info },
|
||||
{ .name = "j172-touchscreen", .driver_data = (kernel_ulong_t)&apple_z2_j172_info },
|
||||
+ { .name = "d10-touchscreen", .driver_data = (kernel_ulong_t)&apple_z2_d10_info },
|
||||
{ .name = "d11-touchscreen", .driver_data = (kernel_ulong_t)&apple_z2_d11_info },
|
||||
{ .name = "d111-touchscreen", .driver_data = (kernel_ulong_t)&apple_z2_d111_info },
|
||||
{}
|
||||
--
|
||||
2.55.0
|
||||
|
||||
104
saf/device/evidence/diag_capture.md
Normal file
104
saf/device/evidence/diag_capture.md
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# PAF · DIAG capture runbook (Route B instrument)
|
||||
|
||||
Goal: get the modem's OWN view of RF-init — why it never self-completes, parks `offline`, and
|
||||
refuses `online` (DeviceNotReady 52). Everything so far is QMI reporting a *refusal code*; DIAG
|
||||
(QCDM/DM) gives the modem's internal **F3 debug messages**, radio frames, and **NV/EFS** state.
|
||||
This is the byte-for-byte trace every prior frontier (#5/#7/#8) named as the missing piece.
|
||||
|
||||
## Why DIAG (vs the coredump path)
|
||||
- F3/DM debug messages = the modem narrating its RF/NAS/MCFG init in its own words. Richer than a
|
||||
remoteproc coredump/SSR reason.
|
||||
- NV item + EFS read/write over DIAG = Route B's EFS/NV diff (GLM thread #2) at the authoritative
|
||||
source, and the write path for `mcfg_autoselect_by_uim` / power-up operating mode.
|
||||
- The decisive comparison: run the SAME tool on BOTH OSes against the SAME modem silicon and diff.
|
||||
|
||||
## Channel facts (confirmed)
|
||||
- DIAG is exposed on Android via `/dev/diag`, created by the **`diagchar`** kernel module (root).
|
||||
- Enable frame capture: `DIAG_IOCTL_SWITCH_LOGGING` → `MEMORY_DEVICE_MODE = 2`; check
|
||||
`DIAG_IOCTL_REMOTE_DEV` for the extra device_type byte.
|
||||
- Packet framing: `cmd_code(1B) + payload + CRC16-CCITT(2B) + 0x7e`, HDLC escaping
|
||||
(`0x7e`→`0x7d 0x5e`, `0x7d`→`0x7d 0x5d`).
|
||||
- Message classes: `DIAG_LOG_F` (binary log packets / radio frames), `DIAG_MSG_F` /
|
||||
`DIAG_EXT_MSG_F` (F3 debug strings — the narration we want). NV/EFS/L1-3 commands supported.
|
||||
- Tool: **QCSuper** (P1sec, open source) for frame + F3 capture → pcap/GSMTAP. For arbitrary NV
|
||||
item read/write, a raw DIAG client (libqcdm-style framer, or QCSuper's diag layer directly).
|
||||
- CHECK FIRST: `sdm845-mainline/pmtools` may already ship a diag/modem helper — look before
|
||||
reinventing the framer.
|
||||
|
||||
---
|
||||
|
||||
## Side A — Android (slot B, rooted): the WORKING reference [HIGH VALUE — never captured]
|
||||
`diagchar` + `/dev/diag` are present on the downstream Android kernel. Two ways in:
|
||||
- **USB-DIAG** (exposes a serial/COM port): `adb root; adb shell` → `su` →
|
||||
`setprop sys.usb.config diag,serial_cdev,rmnet_gsi,adb` (XDA-confirmed on Pixel 3). Our ledger
|
||||
saw this HAL-revert to adb-only on this LineageOS build — RETRY it; if it reverts, use local.
|
||||
- **Local `/dev/diag`** (reliable fallback): run the client on-device over adb, not USB-DIAG.
|
||||
|
||||
1. `adb root; adb shell ls -l /dev/diag` — confirm node exists.
|
||||
2. Push/run QCSuper on-device (or proxy /dev/diag over adb). Start F3 + frame capture.
|
||||
3. Trigger a clean offline→online: **airplane mode OFF→ON** (we already have this at QCRIL-function
|
||||
granularity in `android-capture-20260620/`; DIAG gets it at message/F3 granularity).
|
||||
4. Save: F3 stream + radio frames spanning the transition. This is the modem self-onlining at its
|
||||
own RF-init — the thing pmOS never reaches.
|
||||
5. While here, DIAG-read the NV/EFS state the working modem holds:
|
||||
- power-up operating mode NV
|
||||
- `mcfg_autoselect_by_uim`
|
||||
- RF-calibration NV / QCN region
|
||||
(Confirm exact NV item indices from the modem's NV table / QCSuper — do NOT guess indices.)
|
||||
|
||||
Output → `android-capture-YYYYMMDD/diag/`.
|
||||
|
||||
## Side B — pmOS (slot A, mainline): the FAILURE side [verify availability first]
|
||||
`diagchar` is a downstream module; mainline SDM845 does NOT ship it. So `/dev/diag` may be absent.
|
||||
Determine the transport before assuming symmetry:
|
||||
|
||||
1. `ls -l /dev/diag*` ; `dmesg | grep -i diag` ; `zcat /proc/config.gz | grep -i diag` (or check
|
||||
the built kernel config) ; look for a glink/rpmsg channel named `DIAG`.
|
||||
2. If a DIAG transport exists → run QCSuper, attempt `--dms-set-operating-mode=online`, capture the
|
||||
F3 stream of the FAILED RF-init. Diff F3 against Side A around the offline→online point — the
|
||||
first divergence is the answer.
|
||||
3. If NO DIAG transport on mainline → fall back to GLM thread #1: remoteproc coredump/SSR reason +
|
||||
`tqftpserv`/`rmtfs` request trace during the failed online. Diff that against Side A's DIAG F3.
|
||||
(Retry-safe: online attempts do NOT poison the modem — the old "one-shot per boot" rule is
|
||||
disproven. A QMI `Set Operating Mode = RESET (4)` cleanly reinitializes it without reboot.
|
||||
Retry freely. See HANDOFF.md CORRECTIONS.)
|
||||
|
||||
---
|
||||
|
||||
## What we're diffing for
|
||||
**KEY: a static NV/QCN diff between the two OSes will be NULL.** modemst1/2 + fsg/fsc + persist are
|
||||
on UNSLOTTED partitions → the modem reads BYTE-IDENTICAL persisted EFS/NV/cal on both Android and
|
||||
pmOS (ledger L83: Android-primed EFS → still fails). The modem has no separate flash; its NV lives
|
||||
in that shared EFS. So a QPST QCN backup / NV-item dump will read the same bytes on both sides — it
|
||||
will NOT reveal the delta. **The difference is necessarily RUNTIME behavior**, which is exactly why
|
||||
the F3 stream (not a static snapshot) is the instrument:
|
||||
- The F3 line(s) where Android's modem advances RF-init past the point pmOS's stalls.
|
||||
- Whether the working modem boots into LPM and flips LPM→online, vs pmOS sitting in `offline`
|
||||
(mode 3) — and what in the F3 stream gates that state.
|
||||
- Whether the working modem's online even TOUCHES NV/PDC (frontier #4 says steady-state Android is
|
||||
verify-only) — if it doesn't, the gate is pure firmware-state/timing the AP can't feed.
|
||||
|
||||
(NV reads in Side A step 5 are still worth doing ONCE — to confirm the shared-EFS assumption and
|
||||
record the working values — but expect them equal to pmOS. Don't build a Route B fix around a static
|
||||
NV diff until F3 shows the modem actually consuming that item at init.)
|
||||
|
||||
## Tools reality (we're on Arch, not Windows)
|
||||
- **QPST / QXDM are Windows-only proprietary.** QPST = QCN backup/restore; QXDM = deep F3 logging.
|
||||
Given the shared-EFS point above, QPST's QCN backup buys little here.
|
||||
- **QCSuper (Linux, open) is the path** for F3 + frame capture. For raw NV item read/write, a
|
||||
libqcdm-style framer or QCSuper's diag layer. Check `pmtools` first.
|
||||
|
||||
## SIDE note — glink rpmsg teardown is broken upstream (applies to CHANNEL open/close, NOT online)
|
||||
Dynamically opening/closing glink rpmsg channels is documented as broken on the upstreaming list
|
||||
(the `wwan_remove_port`/`rpmsg_wwan_ctrl` 614s hung_task seen on op6t). This is a real risk when
|
||||
*creating/tearing down a glink channel* (e.g. opening a raw DIAG rpmsg endpoint). It is NOT the
|
||||
"one failed online poisons the modem" rule that used to be here — that rule is disproven: a plain
|
||||
`--dms-set-operating-mode=online` is an ordinary QMI message on the existing IPCRTR transport, it
|
||||
does not open a glink channel, and it is retry-safe (RESET reinitializes). Keep the caution for
|
||||
raw glink channel-open probes only. An AT read of the modem's own reason (`+CEER`, `+CFUN?`) has
|
||||
no free path anyway: QMI service 8 is AT-*forwarding*, not AT-execution (see qrtr_tools_reference
|
||||
2026-07-02), and a raw glink AT port is exactly the channel-open that this note warns about.
|
||||
|
||||
## Refs
|
||||
- QCSuper: https://github.com/P1sec/QCSuper (docs/The Diag protocol.md)
|
||||
- pmtools (check for existing diag helper): https://gitlab.com/sdm845-mainline/pmtools
|
||||
108
saf/device/evidence/first_provision_capture.md
Normal file
108
saf/device/evidence/first_provision_capture.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# First-Provision Capture — force QCRIL to run the real MBN load sequence
|
||||
|
||||
**Why:** Every Android capture we have (`android-live-20260619/`) was an *already-provisioned*
|
||||
device, so QCRIL took the verify-only fast path (`is_modem_mbn_updated: prev_ver == cur_ver` →
|
||||
skip). We have **never recorded** the actual load→select→activate sequence, nor whether
|
||||
`pdc_enable_auto_selection` / `qcril_qmi_pdc_enable_modem_update` is a required precondition.
|
||||
This runbook forces a first-provision and captures it. See `PAF/modem.md` frontier #4.
|
||||
|
||||
**Run from the host with the phone on Android slot B, rooted (`adb root`).** All commands are
|
||||
host-side `adb`; device shell is Android (toybox), not pmOS ash.
|
||||
|
||||
---
|
||||
|
||||
## 0. Preconditions + backup (reversible-safety)
|
||||
|
||||
```sh
|
||||
adb root && adb wait-for-device
|
||||
adb shell getprop ro.product.device # expect: blueline
|
||||
adb shell getprop gsm.version.baseband # expect: g845-00194-...
|
||||
|
||||
# Back up QCRIL's writable provisioning cache + the trigger props, so we can prove
|
||||
# what changed and restore if needed.
|
||||
adb shell 'tar -czf /data/local/tmp/modem_config.bak.tgz -C /data/vendor modem_config 2>/dev/null'
|
||||
adb pull /data/local/tmp/modem_config.bak.tgz ./fp-capture/
|
||||
adb shell 'getprop | grep -E "vendor.radio.(cnv|hw_mbn|sw_mbn|mbn)" ' | tee ./fp-capture/props.before.txt
|
||||
```
|
||||
|
||||
> Note: we deliberately do **NOT** touch the modem's EFS (`modemst1/2`, `fsg`, `fsc`). Those hold
|
||||
> the modem's own persisted config + calibration; wiping them is destructive and not needed. We
|
||||
> only clear QCRIL's *userspace* cache + the version prop — that is the documented trigger for the
|
||||
> `prev_ver != cur_ver` reload branch.
|
||||
|
||||
## 1. Enlarge the radio log ring so early-boot lines survive until we dump
|
||||
|
||||
```sh
|
||||
adb shell setprop persist.vendor.radio.smlog_switch 1 # verbose radio logging (best-effort)
|
||||
adb logcat -b radio -G 64M # 64 MB radio buffer
|
||||
adb logcat -b system -G 16M
|
||||
```
|
||||
|
||||
## 2. Force the first-provision trigger, then reboot
|
||||
|
||||
```sh
|
||||
# Clear QCRIL's writable provisioning state (rebuilt automatically on next provision).
|
||||
adb shell 'rm -rf /data/vendor/modem_config/*'
|
||||
|
||||
# Blank the cached version string QCRIL compares against (this is the prev_ver in
|
||||
# is_modem_mbn_updated). Empty prev_ver => mismatch => QCRIL must re-evaluate the load path.
|
||||
adb shell 'setprop persist.vendor.radio.cnv.ver_info ""'
|
||||
adb shell 'setprop persist.vendor.radio.hw_mbn_loaded ""'
|
||||
adb shell 'setprop persist.vendor.radio.sw_mbn_loaded ""'
|
||||
|
||||
adb shell sync
|
||||
adb reboot
|
||||
```
|
||||
|
||||
## 3. Capture from as early as possible after boot
|
||||
|
||||
```sh
|
||||
adb wait-for-device
|
||||
adb root && adb wait-for-device
|
||||
# Dump the whole radio ring immediately — the 64M buffer should still hold the early
|
||||
# provision window (~10-40s into boot).
|
||||
adb logcat -b radio -d > ./fp-capture/radio-firstprov.txt
|
||||
adb logcat -b system -d > ./fp-capture/system-firstprov.txt
|
||||
adb shell 'getprop | grep -E "vendor.radio.(cnv|hw_mbn|sw_mbn|mbn)"' | tee ./fp-capture/props.after.txt
|
||||
```
|
||||
|
||||
If the early window was missed (buffer wrapped), repeat from §2 but instead of a plain dump, run
|
||||
a **streaming** capture across the reboot: `adb logcat -b radio > radio-firstprov.txt &` then
|
||||
`adb reboot`; logcat auto-reconnects on `wait-for-device` with `adb logcat ... -T 1`.
|
||||
|
||||
## 4. Analyze — the three questions
|
||||
|
||||
```sh
|
||||
cd ./fp-capture
|
||||
# (Q1) Did QCRIL actually run the load path this time (vs skip again)?
|
||||
grep -niE 'load_config|select_config|activate_config|REQUEST_MBN|start_mbn_update|is_ssr_or_bootup' radio-firstprov.txt | head -40
|
||||
|
||||
# (Q2) THE unknown: is pdc_enable_auto_selection / enable_modem_update called, and when
|
||||
# relative to load? This settles gap-analysis item #8 / open question #2.
|
||||
grep -niE 'enable_auto_select|auto.?select|enable_modem_update|disable_modem_update|set_feature_version' radio-firstprov.txt
|
||||
|
||||
# (Q3) The decision point — what did prev_ver/cur_ver read this time?
|
||||
grep -niE 'is_modem_mbn_updated|prev_ver_info|cur_ver_info' radio-firstprov.txt
|
||||
```
|
||||
|
||||
## 5. Interpreting the outcome (both results are informative)
|
||||
|
||||
- **QCRIL runs load→select→activate** → we finally have the real sequence + ordering + timing, and
|
||||
Q2 tells us if `enable_auto_selection` is a required precondition. Feeds path A (`qcril-prov.py`).
|
||||
- **QCRIL still skips** (queries modem, finds the active config already present in modem EFS, just
|
||||
rewrites the prop) → strong confirmation of **frontier #4 path B**: the modem self-persists in
|
||||
`modemst` and userspace never re-loads in normal operation. That makes the pmOS fix
|
||||
"provision-once + ensure `modemst` persistence," not a per-boot loader.
|
||||
|
||||
## 6. Restore (only if anything misbehaves)
|
||||
|
||||
QCRIL repopulates `/data/vendor/modem_config/` and the props automatically on the provision pass,
|
||||
so normally no restore is needed. If you want the exact prior cache back:
|
||||
|
||||
```sh
|
||||
adb push ./fp-capture/modem_config.bak.tgz /data/local/tmp/
|
||||
adb shell 'cd /data/vendor && rm -rf modem_config && tar -xzf /data/local/tmp/modem_config.bak.tgz'
|
||||
adb reboot
|
||||
```
|
||||
|
||||
EFS baseline (untouched by this procedure) remains at `efs-postandroid/{modemst1,modemst2,fsg,fsc}.img`.
|
||||
392
saf/device/evidence/los-mss-translated.c
Normal file
392
saf/device/evidence/los-mss-translated.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* LOS pil-msa.c / peripheral-loader.c modem boot sequence
|
||||
* TRANSLATED to mainline qcom_q6v5_mss.c coding style.
|
||||
*
|
||||
* Purpose: make the downstream boot ordering readable so the delta
|
||||
* with mainline is obvious. Not compilable — a reference document.
|
||||
*
|
||||
* LEGEND:
|
||||
* [SAME] — mainline does the same thing
|
||||
* [DELTA] — mainline does something different (the interesting part)
|
||||
* [EXTRA] — downstream does this, mainline does not
|
||||
* [MISS] — mainline does this, downstream does not
|
||||
*
|
||||
* Source: los-kernel-blueline-4.9/drivers/soc/qcom/pil-msa.c
|
||||
* los-kernel-blueline-4.9/drivers/soc/qcom/peripheral-loader.c
|
||||
* Target: linux-7.1.1-sdm845/drivers/remoteproc/qcom_q6v5_mss.c
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* DOWNSTREAM MEMORY ASSIGNMENT HELPERS (translated to mainline API) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* [DELTA] Downstream uses hyp_assign_phys() from secure_buffer.c which
|
||||
* allocates its argument buffers via kzalloc + dmac_flush_range.
|
||||
* Mainline uses qcom_scm_assign_mem() which allocates from the
|
||||
* qcom_tzmem pool. Same SCM call (SVC=0x0C CMD=0x16), different
|
||||
* buffer provenance. Unlikely to matter but noted.
|
||||
*/
|
||||
|
||||
/* HLOS → MSS exclusively */
|
||||
static int los_assign_to_subsys(struct q6v5 *qproc,
|
||||
phys_addr_t addr, size_t size)
|
||||
{
|
||||
struct qcom_scm_vmperm dest = {
|
||||
.vmid = QCOM_SCM_VMID_MSS_MSA, /* 0xF */
|
||||
.perm = QCOM_SCM_PERM_RW,
|
||||
};
|
||||
u64 src = BIT(QCOM_SCM_VMID_HLOS);
|
||||
|
||||
return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), &src, &dest, 1);
|
||||
}
|
||||
|
||||
/* MSS → HLOS exclusively (panics on failure in LOS — they're serious) */
|
||||
static int los_assign_to_linux(struct q6v5 *qproc,
|
||||
phys_addr_t addr, size_t size)
|
||||
{
|
||||
struct qcom_scm_vmperm dest = {
|
||||
.vmid = QCOM_SCM_VMID_HLOS,
|
||||
.perm = QCOM_SCM_PERM_RWX,
|
||||
};
|
||||
u64 src = BIT(QCOM_SCM_VMID_MSS_MSA);
|
||||
|
||||
return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), &src, &dest, 1);
|
||||
}
|
||||
|
||||
/* HLOS → HLOS+MSS shared */
|
||||
static int los_assign_shared(struct q6v5 *qproc,
|
||||
phys_addr_t addr, size_t size)
|
||||
{
|
||||
struct qcom_scm_vmperm dest[2] = {
|
||||
{ .vmid = QCOM_SCM_VMID_HLOS, .perm = QCOM_SCM_PERM_RW },
|
||||
{ .vmid = QCOM_SCM_VMID_MSS_MSA, .perm = QCOM_SCM_PERM_RW },
|
||||
};
|
||||
u64 src = BIT(QCOM_SCM_VMID_HLOS);
|
||||
|
||||
return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), &src, dest, 2);
|
||||
}
|
||||
|
||||
/* HLOS+MSS → target_vmid exclusively (the final handoff) */
|
||||
static int los_reclaim_mem(struct q6v5 *qproc,
|
||||
phys_addr_t addr, size_t size, int target_vmid)
|
||||
{
|
||||
struct qcom_scm_vmperm dest = {
|
||||
.vmid = target_vmid,
|
||||
.perm = (target_vmid == QCOM_SCM_VMID_HLOS)
|
||||
? QCOM_SCM_PERM_RWX : QCOM_SCM_PERM_RW,
|
||||
};
|
||||
u64 src = BIT(QCOM_SCM_VMID_HLOS) | BIT(QCOM_SCM_VMID_MSS_MSA);
|
||||
|
||||
return qcom_scm_assign_mem(addr, ALIGN(size, SZ_4K), &src, &dest, 1);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* THE BOOT SEQUENCE (downstream pil_boot + pil_msa_mss_ops_selfauth) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* This is the FULL modem cold-boot path on LOS blueline, translated
|
||||
* to mainline style. It's called like mainline's q6v5_start().
|
||||
*
|
||||
* Downstream splits this across:
|
||||
* pil_boot() → ops->init_image() → ops->mem_setup() →
|
||||
* segment load → ops->auth_and_reset()
|
||||
*
|
||||
* where init_image = pil_msa_mss_reset_mba_load_auth_mdt()
|
||||
* mem_setup = pil_mss_mem_setup() [no-op on SDM845]
|
||||
* auth_and_reset = pil_msa_mba_auth()
|
||||
*/
|
||||
static int los_q6v5_start(struct rproc *rproc)
|
||||
{
|
||||
struct q6v5 *qproc = rproc->priv;
|
||||
dma_addr_t mba_phys;
|
||||
void *mba_virt;
|
||||
dma_addr_t mdata_phys;
|
||||
void *mdata_virt;
|
||||
int ret;
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 1: Load MBA (from pil_mss_reset_load_mba)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* [DELTA] MBA buffer: downstream allocates a DMA buffer from
|
||||
* general DDR. Mainline uses the static reserved-memory node
|
||||
* mba@98500000 (2 MB). Both work for MBA load, but TZ may
|
||||
* treat them differently for hyp-assign. On blueline, the
|
||||
* mainline approach works (MBA boots, no -22).
|
||||
*/
|
||||
mba_virt = dma_alloc_attrs(qproc->dev, SZ_1M, &mba_phys,
|
||||
GFP_KERNEL, DMA_ATTR_SKIP_ZEROING);
|
||||
|
||||
memcpy(mba_virt, mba_fw->data, mba_fw->size);
|
||||
wmb();
|
||||
|
||||
/* Load debug policy if present (same in both) */
|
||||
/* [SAME] */
|
||||
|
||||
/*
|
||||
* [SAME] Assign MBA buffer to MSS so PBL can read it.
|
||||
* Downstream: pil_assign_mem_to_subsys(mba_dp_phys, mba_dp_size)
|
||||
* Mainline: q6v5_xfer_mem_ownership(mba_perm, false, true, mba_phys, mba_size)
|
||||
*/
|
||||
if (qproc->need_mem_protection) {
|
||||
ret = los_assign_to_subsys(qproc, mba_phys, SZ_1M);
|
||||
if (ret)
|
||||
goto err_mba;
|
||||
}
|
||||
|
||||
/*
|
||||
* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
* [DELTA — THE KEY DIFFERENCE]
|
||||
*
|
||||
* Downstream does NOT pre-assign the MPSS region here.
|
||||
* Mainline (before our fix) did:
|
||||
* q6v5_xfer_mem_ownership(mpss_perm, false, true,
|
||||
* mpss_phys, mpss_size);
|
||||
* This 120-152 MB exclusive HLOS→MSS assignment happens BEFORE
|
||||
* MBA boots, and blueline TZ may reject it (-22), causing the
|
||||
* XPU crash-loop.
|
||||
*
|
||||
* Our commit c2923cb49 gates this on (version != MSS_SDM845),
|
||||
* matching downstream. VERIFIED: modem boots clean on 7.1.1.
|
||||
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
*/
|
||||
|
||||
/* [SAME] Power up MSS, enable clocks, assert/deassert reset */
|
||||
ret = pil_mss_power_up(qproc);
|
||||
ret = pil_mss_enable_clks(qproc);
|
||||
pil_mss_assert_resets(qproc);
|
||||
udelay(200);
|
||||
pil_mss_deassert_resets(qproc);
|
||||
|
||||
/* [SAME] Program MBA address into RMB register */
|
||||
writel(mba_phys, qproc->rmb_base + RMB_MBA_IMAGE);
|
||||
mb();
|
||||
|
||||
/* [SAME] Bring Q6 out of reset */
|
||||
pil_q6v5_reset(qproc);
|
||||
|
||||
/* [SAME] Wait for PBL success, then XPU_UNLOCKED from MBA */
|
||||
ret = q6v5_rmb_pbl_wait(qproc, 1000);
|
||||
ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_XPU_UNLOCKED, 1000);
|
||||
|
||||
dev_info(qproc->dev, "MBA boot done\n");
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 2: Auth modem metadata (from pil_msa_auth_modem_mdt)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* [DELTA] Metadata buffer: downstream allocates a DMA buffer,
|
||||
* copies metadata, assigns to MSS, programs RMB, waits for
|
||||
* META_DATA_AUTH_SUCCESS, then reclaims to Linux.
|
||||
*
|
||||
* Mainline (q6v5_mpss_init_image) does essentially the same
|
||||
* but uses the mpss-metadata reserved-memory region instead
|
||||
* of a DMA allocation. Functionally equivalent.
|
||||
*/
|
||||
mdata_virt = dma_alloc_attrs(qproc->dev, mdata_size, &mdata_phys,
|
||||
GFP_KERNEL, DMA_ATTR_SKIP_ZEROING);
|
||||
memcpy(mdata_virt, mdt_metadata, mdata_size);
|
||||
wmb();
|
||||
|
||||
if (qproc->need_mem_protection)
|
||||
los_assign_to_subsys(qproc, mdata_phys, mdata_size);
|
||||
|
||||
writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
|
||||
writel(mdata_phys, qproc->rmb_base + RMB_PMI_META_DATA_REG);
|
||||
writel(CMD_META_DATA_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG);
|
||||
|
||||
ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_META_DATA_AUTH_SUCCESS, 10000);
|
||||
|
||||
if (qproc->need_mem_protection)
|
||||
los_assign_to_linux(qproc, mdata_phys, mdata_size);
|
||||
|
||||
dma_free_attrs(qproc->dev, mdata_size, mdata_virt, mdata_phys, 0);
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 3: mem_setup (from pil_mss_mem_setup)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* [SAME] SDM845 does NOT set pil_mss_memsetup in DT.
|
||||
* Downstream: this function returns 0 immediately.
|
||||
* Mainline: need_pas_mem_setup = false for SDM845.
|
||||
* Both skip PAS_MEM_SETUP_CMD. No delta.
|
||||
*/
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 4: MPSS region assignment + segment loading
|
||||
* (from pil_boot lines 1063-1119)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* [SAME on cold boot, DELTA on SSR]
|
||||
*
|
||||
* Cold boot (modem_ssr == false):
|
||||
* - Skip the reclaim-to-linux (MPSS is already Linux-owned)
|
||||
* - Go straight to shared assignment
|
||||
*
|
||||
* SSR (modem_ssr == true):
|
||||
* - First reclaim: MSS→HLOS exclusively
|
||||
* - Then share: HLOS→HLOS+MSS
|
||||
*
|
||||
* Mainline does the reclaim unconditionally (lines 1504-1505)
|
||||
* but on cold boot it's a no-op because mpss_perm already
|
||||
* equals HLOS. So functionally equivalent on cold boot.
|
||||
*/
|
||||
if (modem_ssr) {
|
||||
/* SSR only: reclaim from previous boot */
|
||||
los_assign_to_linux(qproc, qproc->mpss_phys, qproc->mpss_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
* [SAME — this matches mainline line 1508]
|
||||
*
|
||||
* Share MPSS between HLOS and MSS during segment loading.
|
||||
* Downstream: pil_assign_mem_to_subsys_and_linux()
|
||||
* srcVM = [HLOS], destVM = [HLOS, MSS_MSA], perm = [RW, RW]
|
||||
* Mainline: q6v5_xfer_mem_ownership(mpss_perm, true, true, ...)
|
||||
* srcVM from current_perm, destVM = [HLOS, MSS_MSA]
|
||||
* Same SCM call. VERIFIED matching.
|
||||
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
*/
|
||||
ret = los_assign_shared(qproc, qproc->mpss_phys, qproc->mpss_size);
|
||||
if (ret)
|
||||
goto err_mpss;
|
||||
|
||||
/* [SAME] Load each MPSS segment into the shared region */
|
||||
for_each_segment(seg) {
|
||||
ptr = memremap(qproc->mpss_phys + offset, seg->memsz,
|
||||
MEMREMAP_WC);
|
||||
/* copy from firmware or zero-fill */
|
||||
memcpy(ptr, seg_data, seg->filesz);
|
||||
memset(ptr + seg->filesz, 0, seg->memsz - seg->filesz);
|
||||
memunmap(ptr);
|
||||
|
||||
/* [SAME] Program RMB for MBA to verify each segment */
|
||||
if (!code_length) {
|
||||
writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG);
|
||||
writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG);
|
||||
}
|
||||
code_length += seg->memsz;
|
||||
writel(code_length, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG);
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 5: Final MPSS handoff + auth complete
|
||||
* (from pil_boot lines 1108-1127)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
* [DELTA — SUBTLE DIFFERENCE IN THE FINAL HANDOFF]
|
||||
*
|
||||
* Downstream: pil_reclaim_mem(region, subsys_vmid)
|
||||
* srcVM = [HLOS, MSS_MSA] (both listed explicitly)
|
||||
* destVM = [MSS_MSA]
|
||||
* This transfers from shared → MSS exclusive.
|
||||
*
|
||||
* Mainline: q6v5_xfer_mem_ownership(mpss_perm, false, true, ...)
|
||||
* srcVM = from current_perm (which is HLOS|MSS after the share)
|
||||
* destVM = [MSS_MSA]
|
||||
* Same result — but mainline tracks perm state in mpss_perm
|
||||
* while downstream hardcodes the srcVM.
|
||||
*
|
||||
* FUNCTIONALLY EQUIVALENT. The end state (MSS exclusive) is
|
||||
* the same. VERIFIED.
|
||||
* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
||||
*/
|
||||
ret = los_reclaim_mem(qproc, qproc->mpss_phys, qproc->mpss_size,
|
||||
QCOM_SCM_VMID_MSS_MSA);
|
||||
if (ret)
|
||||
goto err_mpss;
|
||||
|
||||
/* [SAME] Wait for MBA to report authentication complete */
|
||||
ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000);
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* PHASE 6: Reclaim MBA + cleanup
|
||||
* (from pil_msa_mba_auth)
|
||||
* ============================================================ */
|
||||
|
||||
/*
|
||||
* [DELTA] Downstream reclaims MBA DMA buffer: MSS→HLOS, then
|
||||
* frees the DMA allocation. Mainline reclaims the mba_region
|
||||
* reserved-memory. Same assign direction, different region type.
|
||||
*/
|
||||
if (qproc->need_mem_protection)
|
||||
los_assign_to_linux(qproc, mba_phys, SZ_1M);
|
||||
dma_free_attrs(qproc->dev, SZ_1M, mba_virt, mba_phys, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
err_mpss:
|
||||
err_mba:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================== */
|
||||
/* SUMMARY OF DELTAS */
|
||||
/* ================================================================== */
|
||||
|
||||
/*
|
||||
* 1. MBA BUFFER LOCATION
|
||||
* Downstream: dma_alloc_attrs() — general DDR, dynamic
|
||||
* Mainline: reserved-memory mba@98500000 — static carveout
|
||||
* Impact: None observed — MBA boots fine both ways.
|
||||
*
|
||||
* 2. MPSS PRE-ASSIGN (the one we fixed)
|
||||
* Downstream: NEVER assigns MPSS before MBA boot
|
||||
* Mainline: Was HLOS→MSS exclusively before MBA boot
|
||||
* Fix: c2923cb49 skips for MSS_SDM845
|
||||
* Status: VERIFIED WORKING — modem boots clean, no -22
|
||||
*
|
||||
* 3. METADATA BUFFER LOCATION
|
||||
* Downstream: dma_alloc_attrs() — general DDR
|
||||
* Mainline: reserved-memory mpss-metadata@bfffc000 (16 KB)
|
||||
* Impact: None observed.
|
||||
*
|
||||
* 4. rmtfs REGION SCM ASSIGN
|
||||
* Downstream: NO SCM call — uses qcom,sharedmem-uio, no TZ
|
||||
* Mainline: qcom_scm_assign_mem(0xf2700000, 0x202000,
|
||||
* HLOS→HLOS+MSS_MSA)
|
||||
* Impact: Assign succeeds on blueline (verified). But this
|
||||
* EXTRA TZ interaction doesn't exist downstream.
|
||||
*
|
||||
* 5. FASTRPC / SLPI VMIDs
|
||||
* Downstream: Not applicable (different fastrpc driver)
|
||||
* Mainline: qcom_scm_assign_mem for fastrpc heap with
|
||||
* [HLOS, SSC_Q6] — TZ rejects with -22
|
||||
* Impact: SLPI crash-loops. Independent of modem.
|
||||
*
|
||||
* 6. OPERATING MODE AT BOOT
|
||||
* Downstream: modem firmware enters LPM at boot
|
||||
* Mainline: modem firmware enters OFFLINE at boot
|
||||
* Impact: THIS IS THE REMAINING BLOCKER.
|
||||
* The driver-level boot sequence is now correct
|
||||
* (matching downstream ordering). The firmware
|
||||
* itself decides LPM vs OFFLINE based on... what?
|
||||
* NOT the driver — the assign ordering is matching.
|
||||
* Candidates:
|
||||
* - ABL/bootloader state (verified-boot chain)
|
||||
* - QCRIL "UI ready" handshake (rejected by firmware)
|
||||
* - Some EFS NV item the firmware checks at RF-init
|
||||
* - Subsystem coordination (SLPI PD state?)
|
||||
*
|
||||
* CONCLUSION:
|
||||
* The driver-level memory assignment is now correct. The modem boots
|
||||
* clean with zero XPU/assign errors. The remaining 52/offline blocker
|
||||
* is ABOVE the driver layer — in the modem firmware's own RF-init
|
||||
* decision logic.
|
||||
*/
|
||||
134
saf/device/evidence/modem_community_research_20260622.md
Normal file
134
saf/device/evidence/modem_community_research_20260622.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Community research — blueline modem on mainline (2026-06-22)
|
||||
|
||||
Purpose: GLM's "cheap parallel" ask — is there a KNOWN blueline/SDM845 modem-on-mainline fix we'd
|
||||
otherwise rediscover? Done read-only (phone was on Android). Verdict + a new lead that reorders the
|
||||
frontier.
|
||||
|
||||
## Headline: there is no known blueline fix to rediscover
|
||||
- The **sdm845-mainline** project's actively-supported, modem-working devices are **OnePlus 6/6T
|
||||
(enchilada/fajita) and Pocophone F1 (beryllium)** — NOT the Pixel 3 generation.
|
||||
- **crosshatch (Pixel 3 XL — blueline's near-identical twin) modem is also NOT confirmed working** on
|
||||
mainline (kernel boots, SSH ok; WiFi/BT/modem are the open items). So this is a **Pixel-3-generation
|
||||
gap, not a blueline-unique one.** Nobody has shipped this modem on mainline.
|
||||
- Implication: don't wait on / search for a blueline-specific recipe — there isn't one. The play is to
|
||||
port the **enchilada** (known-good) path and isolate the **device delta**.
|
||||
|
||||
## The concrete bring-up chain (now confirmed from sources)
|
||||
Modem firmware **RF-init → reads radio CALIBRATION from EFS (`modemst1`/`modemst2`) → via `rmtfs` ↔
|
||||
the `qcom_rmtfs_mem` reserved-memory carveout**. kernel.org confirms `qcom_rmtfs_mem`'s client "is
|
||||
generally found in the modem firmware and is used for accessing persistent storage for things such as
|
||||
**radio calibration**." `modemst1/2` hold the NV/cal the modem needs to come online.
|
||||
|
||||
### Why this reorders the frontier (the new lead)
|
||||
GLM's strongest surviving lead is **Route B: a modem-side EFS NV item** (mcfg_autoselect_by_uim /
|
||||
power-up op-mode), to be read over **DIAG**. But DIAG NV-read uses the **AP↔modem DIAG channel** — a
|
||||
*different* path than the modem's **own rmtfs-mediated EFS access during RF-init**. So:
|
||||
|
||||
> The NV *content* can be byte-perfect (it is — shared EFS) and the modem can STILL park offline if the
|
||||
> **rmtfs ↔ qcom_rmtfs_mem ↔ EFS** bridge is broken/misconfigured on pmOS. That bridge is **upstream**
|
||||
> of the NV-content question and is exactly the kind of device-specific thing that's wrong on an
|
||||
> untested device tree.
|
||||
|
||||
This is consistent with everything GLM proved equal (firmware, EFS bytes, configs, stack, DT
|
||||
"byte-identical to upstream") — because **upstream blueline DT modem support is itself unproven**
|
||||
(nobody got the modem up), so a wrong/incomplete `qcom,rmtfs-mem` carveout or modem `memory-region`
|
||||
would not have been caught.
|
||||
|
||||
## What enchilada (works) does that we should check against blueline
|
||||
- Ships a curated **`firmware-oneplus-sdm845`** set; modem cal/region files matter (VoLTE needs extra
|
||||
carrier config files).
|
||||
- **`rmtfs-copy-files` (pmaports MR !4674, draft):** the modem WRITES to its EFS at runtime and those
|
||||
writes must persist; they copy the partitions at boot and write changes back. EFS is "a black box —
|
||||
a diff before/after shows almost every byte changed." (We share EFS unslotted, so reads are fine;
|
||||
but note the modem expects a writable EFS path via rmtfs.)
|
||||
- Uses **`hexagonrpcd`** (open Hexagon DSP RPC) — DSP/sensors, but part of the enchilada stack.
|
||||
- Voice/VoLTE historically needed **provisioning from a specific OxygenOS build first** — i.e. the
|
||||
working modem was put in a known EFS/NV state from Android before pmOS. (Rhymes with Route B.)
|
||||
|
||||
## Proposed instruments (complementary to GLM's DIAG NV probe — do on the SAME pmOS boot)
|
||||
All mostly read-only; one clean boot.
|
||||
|
||||
1. **Verify the rmtfs/carveout chain is actually bridging during the failed RF-init** (NEW, highest value):
|
||||
- Is `qcom_rmtfs_mem` reserved-memory present and at the address the modem firmware expects?
|
||||
`cat /proc/iomem | grep -i rmtfs`, `dmesg | grep -i rmtfs`, check the DT `qcom,rmtfs-mem` node.
|
||||
- Is `rmtfs` serving `modemst1/2`? Run it foreground/verbose or `strace -f` during modem boot:
|
||||
does the modem issue rmtfs read requests for the EFS, and do they **succeed**?
|
||||
- modem issues reads that **fail** → that's the gate (wrong partition map / perms).
|
||||
- modem issues reads that **succeed** but still offline → bridge is fine, push to GLM's NV-content + SSR reason.
|
||||
- modem issues **no** rmtfs reads → RF-init never reaches EFS; gate is earlier (carveout/glink/firmware load).
|
||||
- Cross-check rmtfs invocation/flags vs enchilada's (`device-oneplus-enchilada` + its rmtfs service).
|
||||
2. **GLM's DIAG NV read** (mcfg_autoselect_by_uim, power-up op-mode) via edl-pixel `qc_diag` + nvitems.xml — still worth it, but now interpreted as "content check," with #1 as the bridge check.
|
||||
3. **Forced modem SSR + devcoredump/minidump for the firmware's own failure reason** (escalation):
|
||||
- `echo enabled > /sys/kernel/debug/remoteproc/remoteprocN/coredump`, trigger SSR via
|
||||
`/sys/kernel/debug/msm_subsys/modem` or a failed online, then read `/sys/class/devcoredump/devcdN/data`.
|
||||
- Also check `sdm845-mainline/pmtools` for an existing diag/modem helper (per diag_capture.md note).
|
||||
|
||||
## Open question worth one cheap action
|
||||
Does **crosshatch** (the twin) hit the **identical** DeviceNotReady(52)? If yes, it's confirmed a
|
||||
family/DT-level gap and any crosshatch progress is directly portable. A targeted ask in
|
||||
sdm845-mainline (Caleb Connolly / Joel Selvaraj orbit) about Pixel-3-gen modem status would settle it
|
||||
without us reverse-engineering blind. (The DeviceNotReady QMI-52 itself is generic — the modemmanager
|
||||
list thread on it reached no root cause; it just means "modem firmware not in a ready state," which
|
||||
points back to RF-init/cal, i.e. the chain above.)
|
||||
|
||||
## Sources
|
||||
- [SDM845 Mainlining — postmarketOS Wiki](https://wiki.postmarketos.org/wiki/SDM845_Mainlining)
|
||||
- [OnePlus 6 (oneplus-enchilada) — postmarketOS Wiki](https://wiki.postmarketos.org/wiki/OnePlus_6_(oneplus-enchilada))
|
||||
- [Pixel 3 XL (google-crosshatch) — postmarketOS Wiki](https://wiki.postmarketos.org/wiki/Google_Pixel_3_XL_(google-crosshatch))
|
||||
- [pmaports MR !4674 — modem/rmtfs: add rmtfs-copy-files](https://gitlab.com/postmarketOS/pmaports/-/merge_requests/4674)
|
||||
- [pmaports MR !1640 — make modem work on downstream again](https://gitlab.com/postmarketOS/pmaports/-/merge_requests/1640)
|
||||
- [kernel.org — qcom,rmtfs-mem.txt binding](https://www.kernel.org/doc/Documentation/devicetree/bindings/reserved-memory/qcom,rmtfs-mem.txt)
|
||||
- [linux-msm/rmtfs](https://github.com/linux-msm/rmtfs)
|
||||
- [Caleb Connolly — state of mainline on SDM845 (5.14)](https://connolly.tech/posts/2021_07_20-sdm845-mainline-5.14/)
|
||||
- [FOSDEM 2022 — From Android to mainline on the Snapdragon 845 (slides)](https://archive.fosdem.org/2022/schedule/event/mobile_kernel_snapdragon_845/)
|
||||
- [sdm845-mainline GitLab group](https://gitlab.com/sdm845-mainline)
|
||||
- [crosshatch mainlining attempt (#153)](https://gitlab.com/postmarketOS/pmaports/-/issues/153)
|
||||
- [modemmanager-devel — DeviceNotReady on set operating mode](https://www.mail-archive.com/modemmanager-devel@lists.freedesktop.org/msg07101.html)
|
||||
|
||||
---
|
||||
|
||||
## pmOS-side LIVE RESULTS (2026-06-22 boot, slot A, kernel 7.1.0-rc1) — instruments run, doors closed
|
||||
|
||||
Ran the proposed battery on a fresh pmOS boot. Results eliminate the rmtfs lead AND both pmOS deep-probe paths, and corroborate GLM's reframe from a second angle.
|
||||
|
||||
1. **rmtfs bridge: HEALTHY this boot — eliminated as the gate.** `rmtfs -P -s` (pid 697) has every link
|
||||
open: `/dev/qcom_rmtfs_mem1` (fd5), `remoteproc2/state` (fd6), `modemst1`/`modemst2` (sdf3/sdf4),
|
||||
`fsg`/`fsc` (sdf5/sdf6), and OEM copies in `/var/lib/rmtfs/`. Carveout `rmtfs-region@f2700000`
|
||||
present. The `Failed to get rprocfd` crashes are *other* boots, not this one. **Modem reads its EFS
|
||||
via rmtfs successfully and still parks offline** → bridge is not the gate.
|
||||
2. **DIAG unavailable on pmOS** — no `/dev/diag`, no diagchar module (mainline doesn't ship it, as
|
||||
diag_capture.md warned). **GLM's Route-B DIAG NV read cannot run on pmOS.** It's Android-only.
|
||||
3. **SIM/UIM fully ready** — `uim-get-card-status`: card present, USIM app `ready`, Primary GW
|
||||
provisioned (slot 1 / app 1), PIN1 disabled. Reads work.
|
||||
4. **NEW corroborating signal — modem refuses STATE CHANGES, serves all READS:**
|
||||
- `dms-set-operating-mode=online` → instant `DeviceNotReady (52)` (clean QMI: Alloc CID → Set Op
|
||||
Mode → FAILURE → Release CID; modem replies immediately, does not hang).
|
||||
- `uim-change-provisioning-session` (what `msm-modem-uim-selection` runs) → **Transaction timed out**,
|
||||
repeatably, even with modem fully up.
|
||||
- vs reads (card status, op-mode get, qrtr service list) → all succeed instantly.
|
||||
- The modem registered its QMI services on qrtr (NAS, Voice, PDC, **Modem EFS svc 21**, etc.).
|
||||
5. **devcoredump path yields nothing** — armed `remoteproc2/coredump=enabled`, attempted online: modem
|
||||
does NOT crash/SSR, no `devcdN`, no SMEM failure string, no dmesg modem reaction. **There is no crash
|
||||
reason to harvest** — the modem is in a stable, deliberate "offline / not RF-ready" state.
|
||||
6. ModemManager is **masked** (intentional, for manual qmicli testing). Modem not poisoned by the
|
||||
attempt (clean refusal); phone still usable on pmOS.
|
||||
|
||||
### What this leaves (frontier now)
|
||||
Every STATIC AP-side layer is proven equal/healthy: firmware loads, rmtfs serves EFS, EFS bytes shared
|
||||
& identical to Android, SIM ready, configs active (GLM), DT byte-identical. The modem firmware itself
|
||||
decides RF-init is incomplete and **fast-refuses every state transition** while answering queries — and
|
||||
exposes no reason on pmOS (no DIAG, no crash). So a *static* NV/EFS diff is NULL (confirmed direction)
|
||||
and the deep-probe instruments are exhausted on pmOS.
|
||||
|
||||
**The remaining differentiator is RUNTIME: the QMI choreography Android's `qcril` performs before/around
|
||||
going online** that pmOS (cold `set-operating-mode=online`) never does. The qcril strings we pulled show
|
||||
exactly this shape — `INTERNAL_MMGSDI_CARD_POWER_UP`, indication-register / set-event-report across
|
||||
DMS/NAS/UIM, `qcril_qmi_nas_trigger_polling_operating_mode`, `POST_OPRT_ONLINE_ACTION`, the bootup
|
||||
optimization state machine. The `uim-change-provisioning-session` timeout is plausibly part of the same
|
||||
missing handshake.
|
||||
|
||||
### Next action (the fix path, not another probe)
|
||||
Decompile the pulled `qcrild` / `libril-qc-hal-qmi.so` (`android-reference/vendor-stack-20260621/`) to
|
||||
extract the exact pre-online QMI request sequence, then **replay that choreography on pmOS over
|
||||
libqmi/qmicli before `set-operating-mode=online`** (the qcom-imsd / provisioner pattern). That is the
|
||||
discriminating *and* corrective experiment; the read-only probes have told us all they can.
|
||||
76
saf/device/evidence/modem_community_research_20260626.md
Normal file
76
saf/device/evidence/modem_community_research_20260626.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# PAF · Community research + reconciliation (2026-06-26)
|
||||
|
||||
Prior-art sweep run against our live evidence from this session (the modem
|
||||
XPU-violation crash-loop = the 52; see modem.md TOP LEAD #11). Conclusion up
|
||||
front: **the community's known fix does NOT match our failure mode; our wall
|
||||
(secure-load / XPU) is likely uncharted in the public SDM845 mainline ecosystem.**
|
||||
|
||||
## What the evidence RULES OUT
|
||||
|
||||
- **tqftpserv `rsize`/`seek` MCFG bug** (the Fairphone 4 / SM7225 unblock).
|
||||
- Refs: ModemManager issue #484
|
||||
(gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/484);
|
||||
tqftpserv PR #13 (github.com/andersson/tqftpserv/pull/13); pmaports !4747 / #2550.
|
||||
- Their modem fetched MCFG with `rsize=`/`seek=` TFTP options that an old
|
||||
tqftpserv mis-served → modem never RF-inits → permanent DeviceNotReady(52).
|
||||
- **NOT us:** our tqftpserv RRQ lines show `rsize=0 seek=0`, and tqftpserv is
|
||||
actively serving `/readwrite/mcfg.tmp`. Transport works. Our modem also does
|
||||
NOT sit stably offline — it **crash-loops** (theirs stayed up and refused).
|
||||
|
||||
## What the evidence RULES IN / leaves open
|
||||
|
||||
- **pd-mapper / MPSS protection domains.** Research: SDM845 needs rmtfs +
|
||||
pd-mapper + tqftpserv; pd-mapper brokers `mpss_root_pd` / `mpss_wlan_pd`.
|
||||
- Refs: in-kernel pd-mapper v9 (mail-archive linux-kernel msg2570727);
|
||||
LWN 973399; RFC linux-remoteproc msg00281 (modemuw.jsn side-load).
|
||||
- **Our state:** userspace `pd-mapper` = `inactive / not-found` (likely not
|
||||
installed). Kernel PDR framework is partly alive (`PDR: Indication …
|
||||
adsp/audio_pd`). OPEN next-session check: does the **MPSS** protection domain
|
||||
actually register in-kernel, or is the modem's PD coordination missing? PD =
|
||||
the secure-memory layer the XPU violation lives in, so this is the lead that
|
||||
fits our evidence.
|
||||
- Note: no public source explicitly ties pd-mapper absence to DeviceNotReady(52)
|
||||
or to an XPU violation — inference, not documented.
|
||||
|
||||
- **Platform MCFG missing (0 platform configs, 25 software, Rogers active).**
|
||||
- openimsd `qcom-baseband-profile-manager` (openimsd.com; NLnet VoLTE-Qualcom
|
||||
grant) selects among EXISTING software configs — it does not manufacture a
|
||||
missing platform/HW MCFG. OnePlus 6 ships `firmware-oneplus-sdm845` with
|
||||
factory MCFG extracted from Android; blueline's EFS lacks the platform MCFG.
|
||||
- **No prior art** for the zero-platform-MCFG case or `--pdc-load-config` of a
|
||||
platform MBN from scratch. Possibly novel. But per our timeline the modem
|
||||
crashes (XPU) *before* the platform-MCFG layer matters, so this is downstream
|
||||
of the secure-load fault, not the first blocker.
|
||||
|
||||
## Confirmed external bugs (not ours to fix)
|
||||
|
||||
- **`qmicli --pdc-list-configs` segfault = upstream libqmi issue #24**
|
||||
(gitlab.freedesktop.org/mobile-broadband/libqmi/-/issues/24, since 1.24.0,
|
||||
segfaults in PDC response cleanup AFTER printing). Not the openimsd fork's
|
||||
fault. Workaround: ignore exit code / `2>&1`. Watch libqmi git log on
|
||||
`qmi-pdc.c` cleanup for the fix version.
|
||||
- **PDC Set Feature 0x2E timeout** — zero public reports. Inferred cause: PDC
|
||||
service present on QRTR but not processing because the modem hasn't finished
|
||||
init (consistent with our crash-loop). Not independently a blocker.
|
||||
|
||||
## Net direction (for next session)
|
||||
|
||||
The 52 is a secure-load / TZ-XPU problem unique enough that the community has no
|
||||
recipe. Concrete next experiments, cheapest first:
|
||||
1. Confirm whether **MPSS protection domain** registers in-kernel; if not, try
|
||||
installing/running userspace `pd-mapper` (apk add pd-mapper) and re-capture
|
||||
whether the XPU violation changes.
|
||||
2. Pull the **`qcom_scm_assign_mem` -22** thread: diff LOS pil-tz secure-load
|
||||
region/VMID setup (mba `qcom,mba-mem`, mpss assign) vs mainline `q6v5_mpss`
|
||||
on this device — the XPU violation means the modem's secure regions aren't
|
||||
assigned the way TZ expects.
|
||||
3. Verify whether 6.16.7 *also* XPU-crash-loops — but NOT as a regression test.
|
||||
**Correction (Casey, 2026-06-26):** no mainline kernel ever had a working modem.
|
||||
6.16.7 "booted + QMI registered" (lead #10) but never went online either — that
|
||||
is NOT a working-modem baseline. The ONLY known-working modem state is
|
||||
**LineageOS on its downstream kernel** (PAF provenance: `msm-4.9` 4.9.337, slot B
|
||||
— Casey recalls it as ~4.x; the substance is *downstream LOS, not mainline*).
|
||||
So the only valid parity yardstick is **mainline vs LineageOS**, never
|
||||
6.16-vs-7.1.1. Checking whether 6.16.7 XPU-loops is still useful (tells us if the
|
||||
XPU fault is mainline-wide vs 7.1.1-specific), but frame it as a mainline-wide
|
||||
question, not "did we regress from a working 6.16."
|
||||
311
saf/device/evidence/modem_consumer_chain_20260626.md
Normal file
311
saf/device/evidence/modem_consumer_chain_20260626.md
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
# PAF · The modem userspace CONSUMER CHAIN — why the modem stays OFFLINE (2026-06-26, Opus)
|
||||
|
||||
> ## SESSION-3 UPDATE (2026-06-27, Opus) — SLPI SAR CRASH LOOP **FIXED**; ~~PROVEN the SLPI does NOT gate the modem 52~~
|
||||
>
|
||||
> ### TL;DR
|
||||
> Built the missing **AP-side SNS registry provider** (a patched `hexagonrpcd`) that feeds the
|
||||
> SLPI its sensor registry over FastRPC `apps_std`. The SLPI's `sar.cc:27:chre_utils fatal`
|
||||
> crash-loop (150+ crashes/boot) is **broken — SLPI now stays up indefinitely** (0 crashes over
|
||||
> multiple 25–40s windows). **Then enabled the modem anyway → still `QMI error 52
|
||||
> DeviceNotReady`.** So the SLPI/SAR subsystem is **independent of** the modem-52 wall. That
|
||||
> question (could not be answered by *disabling* SLPI — only by making it actually work) is now
|
||||
> settled: **error 52 is not SLPI-gated.**
|
||||
>
|
||||
> **🟠 SUPERSEDED 2026-06-29 (Casey) — this conclusion is INVALID.** The "SLPI fixed / 0 crashes / stays
|
||||
> up" above was only *remoteproc-not-crashing*: this doc's own body (search *"never exposes an SNS QMI
|
||||
> service"*) shows the SLPI firmware never completed CHRE init / never exposed SNS QMI — i.e. **not a
|
||||
> functioning sensor stack.** The reasoning "disabling couldn't answer it, only making it work can" was
|
||||
> correct — but "making it work" was never achieved, only "stopped crashing." If the modem's RF-init
|
||||
> queries the SLPI and gets silence from a never-initialized sensor stack, it parks OFFLINE regardless.
|
||||
> The SLPI is a **candidate root-cause**, NOT proven-independent. See `slpi.md` top banner + `HANDOFF.md`.
|
||||
>
|
||||
> ### HOW the SLPI was fixed (the real consumer chain Android calls `sscrpcd`)
|
||||
> The SLPI boots → makes FastRPC **reverse** calls (`apps_std` fopen/fread) back to the AP to read
|
||||
> `/persist/sensors/registry/...`. pmOS shipped **no AP-side listener**, so the registry sensor
|
||||
> starved and the SAR (sx932x) CHRE nanoapp fataled → crash loop. The pmOS-native listener
|
||||
> **`hexagonrpcd`** (linux-msm, already in the repo, `-s` = attach sensorspd on `/dev/fastrpc-sdsp`)
|
||||
> is the right tool but had two gaps:
|
||||
> 1. **Ran as `User=fastrpc`** → couldn't read the persist registry files. Fixed with a systemd
|
||||
> drop-in `User=root` (`/etc/systemd/system/hexagonrpcd-sdsp.service.d/10-root.conf`).
|
||||
> 2. **`construct_root_dir()` (rpcd_builder.c) only mapped `registry/registry/*`** — its virtual
|
||||
> FS had **no nodes** for the top-level `sns_reg_config` / `sns_reg_version` / `sns_reg_ctrl`
|
||||
> that blueline's SLPI requests at `/persist/sensors/registry/<file>`. hexagonfs is node-based
|
||||
> (no real-FS fallthrough), so those ENOENT'd no matter what was on disk. **Patched** to add
|
||||
> those three nodes (`hfs_map_or_empty`). NOTE: `hfs_map_or_empty` presents a missing physical
|
||||
> file as an empty **directory** (`S_IFDIR`), which still fails a *file* open — so the physical
|
||||
> file must EXIST (even 0 bytes). `sns_reg_version` is absent from the Android dump → `touch` an
|
||||
> empty one and it serves as a regular empty file, which satisfies the SLPI.
|
||||
> 3. Registry DATA staged to **`/usr/share/qcom/sensors/registry/`** (hexagonrpcd's default `-R`
|
||||
> root; the 177 groups from the LineageOS persist dump + `sns_reg_config`, empty `sns_reg_version`,
|
||||
> `sns_reg_ctrl`). Persist files also `chmod -R a+rX` (they were `0600 casey:casey` from a tar
|
||||
> restore).
|
||||
>
|
||||
> ### STATE OF THE FIXES — now converted from live temps to REAL in-code fixes (2026-06-27)
|
||||
> All of the live drop-in/temp changes have been turned into aport sources so a clean
|
||||
> `pmbootstrap install` reproduces them (idempotent fresh + upgrade). What's in code now:
|
||||
> - **hexagonrpcd patch → local aport** `pmaports/temp/hexagonrpcd/` (fork of Alpine community
|
||||
> aport, `pkgrel=4`, adds `hexagonrpcd-blueline-registry-nodes.patch`, checksums regenerated).
|
||||
> `pmbootstrap build hexagonrpcd` produces the patched binary instead of the live hand-installed
|
||||
> `/usr/bin/hexagonrpcd` overwrite.
|
||||
> - **root drop-in** → `device-google-blueline/hexagonrpcd-sdsp-root.conf`, installed to
|
||||
> `/usr/lib/systemd/system/hexagonrpcd-sdsp.service.d/10-root.conf` (APKBUILD `package()`).
|
||||
> - **registry data** → NOT shipped as blobs. `blueline-sns-registry-stage.sh` +
|
||||
> `.service` (oneshot, `Before=hexagonrpcd-sdsp`, ordered after persist mount) stage the registry
|
||||
> from the device's OWN `/mnt/vendor/persist/sensors/registry` into `/usr/share/qcom/sensors/registry`
|
||||
> (hexagonrpcd's served root; persist layout is off-by-one for a single `-R`). Touches the empty
|
||||
> `sns_reg_version`. Idempotent, ships no proprietary calibration.
|
||||
> - **service enable** → preset enables `blueline-sns-registry-stage.service` + `hexagonrpcd-sdsp.service`.
|
||||
> - **persist perms**: handled by the stage script's `chmod -R a+rX` on the served copy; the persist
|
||||
> `chmod` I did live is NOT needed by the real fix (hexagonrpcd reads `/usr/share/qcom`, not persist).
|
||||
>
|
||||
> **REMAINING build/verify steps (need device + reflash — do NOT half-run unattended):**
|
||||
> 1. `pmbootstrap build hexagonrpcd` (local aport) + `pmbootstrap build device-google-blueline`.
|
||||
> 2. Install/upgrade on device (`pmbootstrap` sideload or apk add the built .apk) — replaces the
|
||||
> hand-installed binary + lands the drop-in/stage-service/preset.
|
||||
> 3. Reboot clean and VERIFY idempotency: SLPI stays up (0 `crash #` delta), provider active,
|
||||
> `blueline-sns-registry-stage` populated `/usr/share/qcom/sensors/registry`.
|
||||
> 4. Revert the hand-installed temps once the packages own them: restore `/usr/bin/hexagonrpcd.orig`
|
||||
> is NOT needed (apk upgrade overwrites), remove the manual `systemctl enable` (preset handles it).
|
||||
> - **Kernel DT vmid change** (`sdm845.dtsi` sdsp fastrpc `qcom,vmids = <HLOS SSC_Q6>`): I hand-patched
|
||||
> the DTB into boot_a to test the TZ hyp-assign theory. LOGGED RESULT: with `<3 5>` live (confirmed
|
||||
> via `dtc` decompile of `/sys/firmware/fdt`), `dmesg` still prints
|
||||
> `SDSP fastrpc hyp-assign rejected by TZ (-22); registering node without reserved-heap assignment`
|
||||
> (32 occurrences). So the vmid change did NOT change the TZ result — the crash fix was the registry
|
||||
> serving, not the heap. The change is harmless but unnecessary.
|
||||
> - Orig boot.img backups: scratchpad `boot.img` + `/tmp/boot-backup_a.img` on device.
|
||||
> - NOTE on a reboot during testing: `lsusb` showed `18d1:d001` and SSH returned `Network is
|
||||
> unreachable`; root cause was the HOST iface `enp0s20f0u6 DOWN` (per `ip -br addr`), fixed by
|
||||
> `ip link set up` + `ip addr add 172.16.42.2/24` → phone answered, `uptime` = up 2 min. The
|
||||
> phone had booted normally; it was a host USB-net enumeration issue, not a device boot failure.
|
||||
>
|
||||
> ### THE REMAINING WALL — modem `QMI 52 DeviceNotReady`, now provably INDEPENDENT of SLPI
|
||||
> With SLPI fully up + sensors registry served, `mmcli -m 0 -e` still fails at DMS
|
||||
> `set-operating-mode` with **QMI 52**. modem0 enumerates (QUALCOMM, SIM present, slot 2 active),
|
||||
> sits `disabled / power off`, enable → `enabling → (QMI 52) → disabled`. The MSS remoteproc
|
||||
> (`4080000.remoteproc`) is `running`. LOGGED: `mmcli -m 0 -e` → `state changed (disabled ->
|
||||
> enabling)`, `couldn't enable interface: 'Couldn't set operating mode: QMI protocol error (52):
|
||||
> DeviceNotReady'`, `state changed (enabling -> disabled)`. So 52 comes from the modem path, with the
|
||||
> SLPI fully up — the two are independent.
|
||||
> **Scope note:** the goal is the WHOLE device, not just telephony. The SLPI/sensors fix is a real
|
||||
> bringup win on its own (sensors infra now functional). For the 52 specifically, the SLPI is not the
|
||||
> lever. **Next session for 52: modem NV/EFS/identity** (the `model 0` / LOW_POWER hypothesis from
|
||||
> session 2), rmtfs/EFS sync, or modem firmware-init.
|
||||
>
|
||||
> ### Open loose end (minor): hexagonrpcd reconnect churn
|
||||
> Provider shows `activating` with ~18 restarts/min — it exits on FastRPC "Broken pipe" when the
|
||||
> SLPI closes the session after serving, then `Restart=always` reattaches. SLPI stays up regardless
|
||||
> (0 crashes), so it's cosmetic, but worth a `RestartSec`/keepalive tweak later.
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> ## SESSION-2 UPDATE (2026-06-26 late, Opus) — CHAIN BUILT + WORKING; wall is now the modem 52 / SLPI-SAR firmware crash
|
||||
>
|
||||
> ### What got BUILT this session (all in pmaports `device/testing/device-google-blueline/`, deployed live + wired into APKBUILD/preset — idempotent)
|
||||
> The whole consumer chain now comes up automatically on a clean boot and ModemManager
|
||||
> starts + creates the modem. Verified on hardware.
|
||||
> 1. **`modem-qrtr-ready.service` + `modem-qrtr-ready.sh`** (NEW) — the pmOS equivalent of
|
||||
> Android's `wait_for_prop vendor.qcom.devup 1`. Blocks until modem QMI answers and is stable
|
||||
> (2 probes/3s) before releasing consumers. Killed the boot race
|
||||
> (`node with id 0 not found in QRTR bus`). VERIFIED: gate waits, then releases.
|
||||
> 2. **UIM-selection fix** — `blueline-uim-select.sh` (NEW) wraps the stock binary: it selects the
|
||||
> SIM app fine but then a trailing QMI op times out (SLPI/XPU churn) and exits 1 — a false
|
||||
> negative that failed MM's hard `Requires=`. Wrapper treats "app selected" as success.
|
||||
> `uim-wait.conf` rewritten: gate on modem-qrtr-ready, Type=oneshot, run the wrapper. VERIFIED
|
||||
> `msm-modem-uim-selection` now = active.
|
||||
> 3. **`qcril-prov.py` — fully fixed** (was crashing on `out.get_configs()`):
|
||||
> - `list_configs` + `get_selected_config` reworked to read the PDC **indication**
|
||||
> (`IndicationPdc*Output.get_configs()/get_active_id()`), not the response.
|
||||
> - GI array-length args fixed (`set_id(id)`, `set_type_with_id_v2(t,id)`).
|
||||
> - Targets configs by **sha1(.mbn) == PDC config id**; selects+activates HW + Rogers SW.
|
||||
> - **Skip-if-already-active**: checks get_selected_config; if the inherited config is already
|
||||
> active, skips select/activate → **0 modem restarts** (was 2). VERIFIED:
|
||||
> "HW/SW already SELECTED+ACTIVE — no restart".
|
||||
> - **Deadlock fix**: exits 0 after provisioning even if RF not armed (RF arming is MM's job,
|
||||
> and MM is gated behind this service — requiring RF here deadlocked). VERIFIED exit 0.
|
||||
> 4. **`blueline-dpm-open-port.service` / `qcril-prov.service`** — gated on modem-qrtr-ready + retry.
|
||||
> 5. Kernel: mem-protect skip reverted (`980c4ae1f`); on clean baseline 7.1.1; `verify-fixes.sh` green.
|
||||
>
|
||||
> ### THE WALL NOW (single, precise)
|
||||
> Full chain runs → **ModemManager starts, creates `modem0`** (qcom-soc plugin, 3 ports), reads the
|
||||
> SIM ATR, goes `disabled → enabling` → calls set-operating-mode → **`QMI error 52 DeviceNotReady`**
|
||||
> → falls back to `disabled`. This is the SAME 52 from day one. The modem is **OFFLINE, model `0`**
|
||||
> (up-but-unprovisioned) and **refuses to enable even when driven correctly by MM**. So the entire
|
||||
> userspace plumbing is now proven-good; the wall is the modem itself refusing RF.
|
||||
>
|
||||
> ### SLPI / SAR — the suspected gate (Casey's thesis), what we proved
|
||||
> - SLPI **crash-loops `sar.cc:27:chre_utils fatal` every ~10s** (crash #15+). SAR = Semtech
|
||||
> **SX932x**, the modem's RF SAR/TX-power sensor.
|
||||
> - It is a **CHRE nanoapp crash INSIDE the SLPI firmware at init**, and **qrtr-lookup is empty —
|
||||
> the SLPI never exposes an SNS QMI service before it dies**. So an AP-side QMI "SAR consumer"
|
||||
> has nothing to attach to; the producer fatals first. A downstream subscribe-consumer is NOT
|
||||
> the missing piece.
|
||||
> - The real missing piece is whatever **feeds the SLPI its sensor registry / CHRE settings at
|
||||
> init**: the SAR registry data (`sx932x_0.sar`, `.config`, `_platform.*`) IS present at
|
||||
> `/persist/sensors/registry/registry/` but is **NOT delivered to the SLPI** (its RFS dir
|
||||
> `/persist/rfs/msm/slpi` is empty; tqftpserv serves only `/persist/rfs`, not `/sensors`).
|
||||
> - Android's consumer stack (proprietary, NO open-source equivalent):
|
||||
> **`chre_daemon_msm` + `sensors.qti` + `sscrpcd`** (in `android-reference/vendor-stack-.../vendor/bin/`),
|
||||
> plus the registry under `android-reference/persist-sensors-20260625/`. Our
|
||||
> `drivers/soc/qcom/qcom_ssc_sensors.c` is a **passive ABI shim** — it does NOT deliver the
|
||||
> registry or run CHRE, so it cannot prevent the firmware crash. This is the "shim lacking
|
||||
> components LineageOS has" Casey referenced.
|
||||
>
|
||||
> ### NEXT ANGLE (Casey's call — start here next session)
|
||||
> 1. **DECISIVE CHEAP TEST FIRST — is SAR actually the modem gate?** Stop the SLPI remoteproc so it
|
||||
> is NOT crash-looping (`echo stop > /sys/class/remoteproc/remoteproc2/state`), then let MM try
|
||||
> to enable the modem (or `qmicli --dms-set-operating-mode=online` once). If still **52** →
|
||||
> SLPI/SAR is NOT the modem gate (independent symptoms) → stop chasing SAR for telephony and look
|
||||
> elsewhere for the 52. If it **goes online** → the SLPI churn was the blocker and the SAR/CHRE
|
||||
> consumer becomes critical. **Run this before building anything.**
|
||||
> 2. If SAR IS the gate: the buildable "consumer" is a **registry/CHRE provider** that feeds the
|
||||
> SLPI at boot (reverse the SNS/CHRE QMI registry protocol; or port pieces; or boot SLPI
|
||||
> on-demand AFTER preparing its env, like LineageOS `sensors_ssc.c` does — it does NOT auto-boot
|
||||
> the SLPI raw). Investigate `chre_daemon_msm` / `sscrpcd` behaviour + how the registry reaches
|
||||
> the SLPI on Android (RFS path? SNS QMI? direct?).
|
||||
> 3. Independent of SAR: chase **why the modem boots OFFLINE/model-0** (vs Android's LOW_POWER).
|
||||
> model `0` = NV/identity not fully loaded — possible EFS/NV or firmware-init issue.
|
||||
> 4. Fallback: downstream-kernel hybrid (Droidian/Halium) — the realistic telephony path if mainline
|
||||
> SLPI/CHRE proves intractable.
|
||||
>
|
||||
> ### SAR consumer — EXACT mechanism (from slpi.mbn strings) + the build blocker
|
||||
> The SLPI firmware reveals precisely what the consumer must be:
|
||||
> - Reads registry from **`/persist/sensors/registry/registry`** (+ `sns_reg_config`, `sns_reg_version`).
|
||||
> - **`Sending registry request for group name:%s` / `SNS_REG_INIT` / `Decoding registry group %s` /
|
||||
> `Error decoding registry event`** → the SLPI **requests registry groups over the SNS QMI service
|
||||
> and expects encoded registry-event responses**. The SAR (sx932x) nanoapp fatals (`sar.cc:27
|
||||
> chre_utils fatal`) when it can't get its group → crash-loop.
|
||||
> - Fallback strings exist: **`Registry disabled, leaving default mode enabled` / `LPI Registry
|
||||
> Disabled`** — there may be a registry-disabled mode worth forcing (SAR likely still needs its
|
||||
> device config though).
|
||||
> - **The consumer = an SNS registry provider** (QRTR/QMI daemon) that reads
|
||||
> `/persist/sensors/registry/registry/<group>` and answers the SLPI's requests. On Android this is
|
||||
> `sensors.qti` + `sscrpcd` + `chre_daemon_msm` (binary-only in
|
||||
> `android-reference/vendor-stack-20260621/vendor/bin/`).
|
||||
> - **BUILD BLOCKER (why it's not done):** the SNS protocol is Qualcomm proprietary protobuf
|
||||
> (`sns_client.proto` / `sns_registry`); there are **NO .proto defs or SNS source anywhere in the
|
||||
> tree** — only the registry DATA and the binary daemons. A working provider needs that encoding;
|
||||
> building blind yields non-functional code. Options: (a) extract/RE the SNS protobuf from the
|
||||
> vendor .so (`libsnsapi`/`sensors.qti`) — multi-day; (b) find an open SNS impl to port;
|
||||
> (c) run the vendor `sscrpcd`/`sensors.qti` binaries under a libhybris/Halium shim;
|
||||
> (d) downstream-kernel hybrid (sidesteps mainline SLPI entirely).
|
||||
>
|
||||
> ### CLEAN decisive test to run next boot (settles whether SAR even gates the modem — do BEFORE any SNS build)
|
||||
> The session test was contaminated (modem already poisoned by prior online attempts). Clean version:
|
||||
> `systemctl mask ModemManager qcril-prov qcril-autoselect blueline-dpm-open-port` → reboot →
|
||||
> on boot immediately `echo stop > /sys/class/remoteproc/remoteproc2/state` (kill SLPI before it
|
||||
> crash-loops) → wait for modem QMI up → ONE `qmicli -p -d qrtr://0 --dms-set-operating-mode=online`.
|
||||
> - Still **52** with SLPI cleanly stopped from boot → **SLPI/SAR is NOT the modem gate** (the 52 /
|
||||
> model-0 is independent — chase modem NV/EFS/firmware-init instead; don't invest in the SNS build).
|
||||
> - **Goes online** → SLPI/SAR churn WAS the blocker → the SNS registry provider is worth the build.
|
||||
> Unmask afterwards: `systemctl unmask ModemManager qcril-prov qcril-autoselect blueline-dpm-open-port`.
|
||||
>
|
||||
> ### Files touched this session (for the next agent)
|
||||
> - `references/linux-7.1.1-sdm845-CANONICAL` @ `980c4ae1f` (revert skip); `tools/verify-fixes.sh` (skip-absent check)
|
||||
> - pmaports `device/testing/device-google-blueline/`: `qcril-prov.py`, `modem-qrtr-ready.sh`+`.service`,
|
||||
> `blueline-uim-select.sh`, `uim-wait.conf`, `blueline-dpm-open-port.service`, `qcril-prov.service`,
|
||||
> `APKBUILD`, `80-device-google-blueline.preset`. All also deployed live to the phone.
|
||||
> - NOTE: APKBUILD `sha512sums` NOT yet regenerated for the new/changed sources — run
|
||||
> `pmbootstrap checksum device-google-blueline` before the next package build.
|
||||
|
||||
---
|
||||
|
||||
|
||||
THE session that reframed the whole problem. The modem-online blocker is **not** (only) a
|
||||
kernel/secure-load issue — it is that the **userspace consumer chain that drives the modem is
|
||||
half-built / failing**, exactly as Casey kept saying. Everything below is VERIFIED LIVE on the
|
||||
reverted-baseline 7.1.1 build, not inference.
|
||||
|
||||
## TL;DR causal chain (verified end to end)
|
||||
1. Modem firmware boots, MBA loads MPSS, modem reaches `running`. It has identity (IMEI
|
||||
990012001581294, ESN 8074BB5D, MEID, SW `g845-00194-210812`). **The assign/-22/secure-load is
|
||||
NOT the modem blocker** — the modem loads. (The only `-22`s are fastrpc + ath10k wifi, both
|
||||
tolerated.) One XPU violation at boot, self-recovers (Casey was right).
|
||||
2. BUT the modem comes up **op-mode OFFLINE (3), model `0`** = up-but-unprovisioned/not-ready.
|
||||
`--dms-set-operating-mode=online` → **QMI 52 DeviceNotReady**. Forcing online does nothing.
|
||||
3. **SLPI crash-loops every ~10s**: `err_qdi.c:456:EF:sensor_process:1:sar.cc:27:chre_utils fatal`
|
||||
→ recover → up → crash. SAR (Semtech SX932x) is the modem's RF SAR/TX-power sensor.
|
||||
4. The modem+SLPI instability makes the **QRTR node flap**, so the boot-time consumer services
|
||||
race an unready modem and **fail**:
|
||||
- `msm-modem-uim-selection.service` → `node with id 0 not found in QRTR bus` (boot) /
|
||||
`Transaction timed out` (later). **This is a hard `Requires=` of ModemManager.**
|
||||
- `blueline-dpm-open-port.service` → `node with id 0 not found` at boot (succeeds if re-run later).
|
||||
- `qcril-prov.service` → crashed on a code bug (now FIXED, see below).
|
||||
5. Because `msm-modem-uim-selection` (hard dep) failed → **ModemManager never started at boot.**
|
||||
So nothing ever drove the modem: enable → set-power → register. It just sits OFFLINE.
|
||||
6. Started MM manually (bypassing deps): it DOES reach the modem over qrtr://0, creates `modem0`,
|
||||
reads the SIM ATR — then fails: **`couldn't start parent initialization: Couldn't get primary
|
||||
port`** and `couldn't load carrier config: Operation timed out`. MM can't initialize a modem
|
||||
that's OFFLINE/flapping.
|
||||
|
||||
## What is SOLID now (do not redo)
|
||||
- On reverted-baseline 7.1.1 (`blueline` branch, mem-protect skip reverted `980c4ae1f`). Built,
|
||||
flashed `boot_a`, deployed. `verify-fixes.sh` green (13 checks; mem-protect check now asserts the
|
||||
skip is ABSENT). Android slot B / EFS / userdata untouched.
|
||||
- ~~pd-mapper: in-kernel `CONFIG_QCOM_PD_MAPPER=m`, module loaded … **pd-mapper is fine — ruled
|
||||
out.**~~ **WRONG (corrected 2026-06-29).** The pmaports config sets `=m`, but the running kernel
|
||||
has `# CONFIG_QCOM_PD_MAPPER is not set` — the in-tree `arch/arm64/configs/sdm845.config` carries
|
||||
the upstream `=n` and `merge_config.sh -m` applies it *after* the pmaports config, so `=n` wins
|
||||
every build. Userspace pd-mapper is also uninstalled ⇒ **no pd-mapper of either kind runs.** Caught
|
||||
via the wifi outage (no `wlan0`; ath10k_snoc bound but probe defers with no wlan PD). The Mobian
|
||||
0171 observation stands, but "pd-mapper is fine" did not. Fixed: in-tree fragment → `=m` (commit
|
||||
pending), then rebuild+reflash. Whether this also unblocks the modem-52 is now an open re-test, not
|
||||
a closed lead.
|
||||
- Infra present & correct: `rmtfs -P -s` (block EFS), `tqftpserv` (serves `/persist/rfs` →
|
||||
modem mcfg), sensor registry 177 files at `/mnt/vendor/persist/sensors/registry/registry/`,
|
||||
all mcfg carrier configs incl `mcfg_sw/generic/NA/Rogers/Commercial/CA/mcfg_sw.mbn`.
|
||||
- udev tags MM correctly: `ID_MM_QCOM_SOC=1`, `ID_MM_CANDIDATE=1` on rmnet_ipa0; MM qcom-soc
|
||||
plugin loads. Transport (QMI-over-QRTR) works — MM exchanges QMI with the modem.
|
||||
|
||||
## FIXED this session — qcril-prov.py (the MBN provisioner)
|
||||
Source: `pmaports/device/testing/device-google-blueline/qcril-prov.py` (also live-deployed to
|
||||
`/usr/bin/qcril-prov.py` on phone). Was crashing; now runs end-to-end and provisions Rogers.
|
||||
Bugs fixed (all real, in code):
|
||||
1. `list_configs` read configs from the response (`out.get_configs()` — doesn't exist;
|
||||
`MessagePdcListConfigsOutput` only has `get_result`). The config list arrives via the
|
||||
**`list-configs` INDICATION** → `IndicationPdcListConfigsOutput.get_configs()`; each element has
|
||||
`.id` + `.config_type` only. Reworked to indication-driven (mirrors the working `load-config`).
|
||||
2. GI array-length args: `set_id(cfg_id, len)` → `set_id(cfg_id)`; `set_type_with_id_v2(t,id,len)`
|
||||
→ `set_type_with_id_v2(t,id)`. (`set_config_chunk` was already correct.)
|
||||
3. Strategy: blueline INHERITS Android's full mcfg set (1 platform + 25 software) on shared
|
||||
persist. Don't delete+reload (delete is a no-op; reload → error 41 "already exists"). Instead:
|
||||
compute target id = **sha1 of the .mbn file** (== the PDC config id, verified: HW sha1
|
||||
`6fe571a9…` matched the on-modem id), confirm present, **select + activate by sha1**. Treat
|
||||
load error_code=41 as success. Helpers added: `_sha1_id`, `_hex`, `_id_present`.
|
||||
Result: provisioner selects+activates HW (platform) and Rogers (software) configs cleanly. The
|
||||
modem still ends OFFLINE afterward (carrier config alone doesn't lift it — needs MM to drive it).
|
||||
|
||||
## THE PLAN (next session — ordered)
|
||||
Goal: get ModemManager to fully initialize the modem and drive OFFLINE → online → registered.
|
||||
Two intertwined blockers: (A) modem won't initialize while OFFLINE/flapping; (B) SLPI crash-loop.
|
||||
|
||||
1. **Make MM actually manage the modem (primary blocker).**
|
||||
- Why `Couldn't get primary port`? Check MM qcom-soc plugin port expectations for a pure-QRTR
|
||||
SDM845 modem; check nothing else holds the QMI client (our qcril-* one-shots, the stray
|
||||
`dbus-monitor`, manual `qmicli -p`). Try MM with everything else stopped.
|
||||
- Fix the boot ORDERING/retry so `msm-modem-uim-selection` + `blueline-dpm-open-port` +
|
||||
`qcril-prov` don't race the unready modem: add wait-for-QRTR-node / Restart=on-failure /
|
||||
proper `After=` on a "modem-qrtr-up" condition. This is how the chain stays up like Lineage
|
||||
(Lineage's RIL waits for the modem to be ready before UIM select / PDC).
|
||||
- Once MM inits: it should set-power-up and the OFFLINE→online may resolve via MM's sequence
|
||||
(it owns DMS set-operating-mode). The manual `online`=52 is because nothing did the proper
|
||||
enable sequence first.
|
||||
|
||||
2. **SLPI SAR crash-loop (`sar.cc:27`).** SAR = SX932x, governs RF TX-power; Casey's position is
|
||||
it gates modem RF. The SLPI sensor REGISTRY (`/persist/sensors/registry`) is NOT served to the
|
||||
SLPI — tqftpserv serves `/persist/rfs` only (sibling tree). Leads:
|
||||
- Does the SLPI read registry via tqftpserv (needs `/sensors` served too) or a different path?
|
||||
The tqftp log shows only modem mcfg requests, no `/sensors` requests → SAR may crash BEFORE
|
||||
requesting registry (early init / SX932x i2c on the SLPI bus). Investigate how LineageOS
|
||||
`sensors_ssc.c` + sensor HAL bring SAR up (registry delivery + SX932x i2c/GPIO).
|
||||
- Our `qcom_ssc_sensors.c` is a passive ABI shim; it does NOT boot or feed the SLPI. Mainline
|
||||
auto-boots the SLPI (DTS firmware-name) with no consumer → SAR talks to nobody → crash.
|
||||
LineageOS boots SLPI ON DEMAND after the sensor HAL is up.
|
||||
|
||||
3. **Verify on a clean boot**: fix the services so the whole chain comes up at boot (not manually),
|
||||
reboot, confirm MM auto-starts, modem → registered. One online attempt per boot still applies.
|
||||
|
||||
## Hard constraints (unchanged)
|
||||
- pmOS = slot A; flash ONLY boot_a. NEVER touch Android slot B / EFS (modemst1/2,fsg,fsc) /
|
||||
userdata / TZ-XBL-ABL. rmtfs `-P -s`. No pmic@4/5 SPMI nodes. One `online` attempt per boot.
|
||||
sudo pw 147147. USB 172.16.42.1 (host iface enp0s20f0u6 → `ip addr add 172.16.42.10/24`).
|
||||
- pmOS is Alpine/ash: embedded `sh -c "...(...)..."` with parens fails — use script files via scp.
|
||||
153
saf/device/evidence/modem_dt_rf_analysis_20260622.md
Normal file
153
saf/device/evidence/modem_dt_rf_analysis_20260622.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# Modem DT/RF gap analysis — pmOS vs Android-downstream vs enchilada (2026-06-22)
|
||||
|
||||
Phase 1 of the "is the RF/DT lever still alive?" task. All comparisons are between three device
|
||||
trees on the SAME SoC (SDM845):
|
||||
- **blueline-pmos** (FAILS RF arm) — `dt-work/blueline-pmos.dts`, sourced from upstream
|
||||
`sdm845-google-blueline.dts` + `sdm845-google-common.dtsi`.
|
||||
- **blueline-Android downstream** (modem WORKS, Fido LTE) — `dt-work/android-dtb/android-base.dts`
|
||||
(base DTB) + `dt-work/blueline-live.dts` (live capture from /proc/device-tree, includes DTBO).
|
||||
- **enchilada (OnePlus 6) mainline** (modem WORKS on the exact same mainline kernel/pmaports stack)
|
||||
— `dt-work/sdm845-oneplus-enchilada.dts`.
|
||||
|
||||
Two working references on the same silicon is the strongest possible control. Where BOTH working
|
||||
trees agree and pmOS differs, that difference is a real, citable suspect.
|
||||
|
||||
---
|
||||
|
||||
## VERDICT: DT IS NOT THE GATE
|
||||
|
||||
The modem DT is **functionally identical** between working Android and non-working pmOS.
|
||||
Every modem-relevant node (remoteproc@4080000, reserved-memory, rmtfs, power-domains, clocks,
|
||||
glink, SPMI/PMIC) is structurally the same. The remaining differentiator is **runtime kernel
|
||||
driver behavior**, not the device tree.
|
||||
|
||||
---
|
||||
|
||||
## RANKED CANDIDATES
|
||||
|
||||
### #1 (LIVE — Android applies the 32MB offset map; pmOS must carry it too) — Modem reserved-memory map
|
||||
|
||||
The subagent's top candidate claimed pmOS uses a non-standard MPSS size (0x9800000) and MBA
|
||||
address (0x98500000) while Android uses the standard map (0x7800000 / 0x96500000). **This is wrong.**
|
||||
|
||||
The subagent compared pmOS against `android-base.dts` (the raw base DTB) and `enchilada.dts`
|
||||
(standard map). But it failed to account for the **DTBO overlay** (dtbo_idx=8) that the bootloader
|
||||
applies at boot. The DTBO transforms the base standard map into the Pixel 3 override on the live
|
||||
Android device. The live captured DTS (`blueline-live.dts`, from /proc/device-tree) proves this:
|
||||
|
||||
| Region | Android base DTB | Android LIVE (with DTBO) | pmOS | enchilada |
|
||||
|---|---|---|---|---|
|
||||
| MPSS | 0x8e000000 sz **0x7800000** | 0x8e000000 sz **0x9800000** | 0x8e000000 sz **0x9800000** | 0x8e000000 sz **0x7800000** |
|
||||
| MBA | **0x96500000** | **0x98500000** | **0x98500000** | **0x96500000** |
|
||||
| venus | 0x95800000 | 0x97800000 | 0x97800000 | 0x95800000 |
|
||||
| cdsp | 0x95d00000 | 0x97d00000 | 0x97d00000 | 0x95d00000 |
|
||||
|
||||
**Live Android and pmOS use the IDENTICAL Pixel 3 override map.** The modem works on Android with
|
||||
this map. Therefore the memory map cannot be the gate. (The override comes from
|
||||
`sdm845-google-common.dtsi:17-77` which `/delete-node/`s the standard regions and redefines them.
|
||||
pmOS includes this via the mainline DTS chain; Android gets it via the DTBO overlay.)
|
||||
|
||||
Citations:
|
||||
- Base DTB: `android-dtb/android-base.dts:19146` (reg 0x7800000), `:19167` (mba@96500000)
|
||||
- Live Android: `blueline-live.dts:931` (reg 0x9800000), `:949` (mba@98500000)
|
||||
- pmOS: `blueline-pmos.dts:906` (reg 0x9800000), `:924` (mba@98500000)
|
||||
- enchilada: `sdm845-oneplus-enchilada.dts:884` (reg 0x7800000), `:902` (mba@96500000)
|
||||
- DTBO source: `references/sdm845-linux-20260610-ONHOLD-DAVID-TREE/arch/arm64/boot/dts/qcom/sdm845-google-common.dtsi:17-77`
|
||||
|
||||
### #2 (REFUTED twice) — pm8998 LDOs absent from pmOS rpmh-regulators
|
||||
|
||||
`dt-work/regulator-diff-android-vs-pmos.txt`: Android defines ldo4/6/8/9/10/11/15/16/18/23/27 that
|
||||
pmOS omits. **Already refuted twice** (modem.md #9/#10): (a) enchilada's WORKING modem does NOT
|
||||
declare these RF LDOs either — same minimal mainline rpmh-regulators set as pmOS; (b) all 11 show
|
||||
`use=0` from the AP on live working Android (`rf-rail-diff-FINAL.txt`) — the modem votes its RF rails
|
||||
via RPMh directly, not the AP regulator framework. Since enchilada arms RF without them, they are not
|
||||
the gate. **Do NOT add the LDOs / do NOT flash `blueline-live.dtb`.**
|
||||
|
||||
### #3 (eliminated) — RFFE / SPMI bus / RF-frontend regulator nodes
|
||||
|
||||
No RFFE node and no WTR/QFE/PA/FEM/GRFC regulator node exists in ANY of the three trees, including
|
||||
both working ones. RF front-end power/control is modem-firmware-driven over RFFE/GRFC, not an AP DT
|
||||
resource. Dead.
|
||||
|
||||
### #4 (eliminated) — modem node clocks / power-domains / smem-states / glink
|
||||
|
||||
`remoteproc@4080000` is structurally identical across all three trees: same
|
||||
`qcom,sdm845-mss-pil`, same clocks, same power-domains, same glink-edge. Only firmware-name path
|
||||
and phandles differ. rmtfs carveout structurally identical. Dead.
|
||||
|
||||
### #5 (eliminated) — DTBO overlay modem content
|
||||
|
||||
The DTBO (dtbo_a.img.dtb8, 112 fixups) touches display, audio, camera, charger, thermal, GPIO,
|
||||
touchscreen, NFC, UFS, PCIe, debug/ramoops. **Zero modem/remoteproc/rmtfs/glink fragments.**
|
||||
`modemsmem` node is a no-op placeholder (compatible only, no properties). The DTBO's only
|
||||
modem-relevant effect is transforming the memory map from standard to Pixel 3 override (fragment@45
|
||||
for MPSS, fragment@48 for MBA) — which pmOS already has natively. Dead.
|
||||
|
||||
### #6 (eliminated) — CDT / bootargs / hardware identifiers
|
||||
|
||||
Android has a `chosen/cdt/cdb2` node (IMEI, MEID, BT/WLAN MACs, SKU, PCB config, eSIM) and
|
||||
`androidboot.*` bootargs that pmOS lacks. These are AP-side identifiers — the modem gets IMEI from
|
||||
EFS, not DT. Mainline kernel parses none of the `androidboot.*` params. Dead.
|
||||
|
||||
---
|
||||
|
||||
### #2 (WEAK / for-the-record) — pm8998 LDOs absent from pmOS rpmh-regulators
|
||||
|
||||
`dt-work/regulator-diff-android-vs-pmos.txt`: Android defines ldo4/6/8/9/10/11/15/16/18/23/27 that
|
||||
pmOS omits. **Already refuted twice** (modem.md #9/#10): (a) enchilada's WORKING modem does NOT
|
||||
declare these RF LDOs either — same minimal mainline rpmh-regulators set as pmOS; (b) all 11 show
|
||||
`use=0` from the AP on live working Android (`rf-rail-diff-FINAL.txt`) — the modem votes its RF rails
|
||||
via RPMh directly, not the AP regulator framework. Since enchilada arms RF without them, they are not
|
||||
the gate. Kept here only so it is not re-chased. **Do NOT add the LDOs / do NOT flash
|
||||
`blueline-live.dtb`.**
|
||||
|
||||
### #3 (eliminated) — RFFE / SPMI bus / RF-frontend regulator nodes
|
||||
|
||||
No RFFE node and no WTR/QFE/PA/FEM/GRFC regulator node exists in ANY of the three trees, including
|
||||
both working ones (`rf-rail-diff-FINAL.txt`, re-verified today: `qcom,rffe` absent everywhere; SPMI
|
||||
bus `spmi@c440000` `qcom,spmi-pmic-arb` present and identical in pmOS + enchilada + Android). RF
|
||||
front-end power/control is modem-firmware-driven over RFFE/GRFC, not an AP DT resource. Nothing to
|
||||
add. Dead.
|
||||
|
||||
### #4 (eliminated) — modem node clocks / power-domains / smem-states / glink
|
||||
|
||||
`remoteproc@4080000` is structurally identical between blueline-pmos and enchilada: same
|
||||
`qcom,sdm845-mss-pil`, same `clock-names` (iface/bus/mem/gpll0_mss/snoc_axi/mnoc_axi/prng/xo), same
|
||||
`power-domains` (cx/mx/mss), same `qcom,smem-states "stop"`, same glink-edge label "modem"
|
||||
remote-pid 1 (`dt-work/modem-blueline.txt` vs enchilada `:3647`). Only firmware-name path and
|
||||
phandles differ (correct). rmtfs carveout structurally identical (size `0x202000`, vmid 0x0f,
|
||||
client-id 1, guard-pages); base differs (`0xf2700000` vs enchilada `0xf5b00000`) which is normal
|
||||
per-device placement and already proven to serve EFS fine on pmOS
|
||||
(`modem_community_research_20260622.md` live result #1). Dead.
|
||||
|
||||
---
|
||||
|
||||
## Runtime time-injection angle (secondary lead from the task)
|
||||
Checked: pmOS does NOT run a modem time daemon. The qrtr "Time" service (TS, svc 64) sync that
|
||||
Android's RIL feeds is not provided by ModemManager. However this is downstream of the 52 wall —
|
||||
the modem refuses the `dms-set-operating-mode=online` *write itself* instantly
|
||||
(`modem_community_research_20260622.md` result #4: clean Alloc CID -> Set Op Mode -> FAILURE 52,
|
||||
immediate), before any time-dependent NAS attach logic runs. Time injection gates *attach*, not the
|
||||
op-mode flip. Only worth chasing if the 52 gate is cleared and the modem then fails to attach.
|
||||
|
||||
---
|
||||
|
||||
## VERDICT (Phase 1) — CORRECTED
|
||||
|
||||
**The DT is not the gate.** Every modem-relevant node is byte-identical between working Android and
|
||||
non-working pmOS. The subagent's #1 candidate (memory map difference) was refuted by accounting for
|
||||
the DTBO overlay: live Android and pmOS use the identical Pixel 3 override map.
|
||||
|
||||
The remaining differentiator is **runtime**: the mainline kernel's remoteproc/PIL driver, firmware
|
||||
load sequence, power-domain sequencing, QMP link bring-up, or glink/mailbox initialization differs
|
||||
from the downstream Android kernel. The modem firmware is identical (same files, same EFS, same
|
||||
shared partition). The DT is identical. The kernel and its drivers are what's different.
|
||||
|
||||
### Next steps (kernel/driver level, not DT)
|
||||
1. **Compare remoteproc driver behavior**: dmesg on Android vs pmOS during modem boot — look for
|
||||
differences in power-domain enable order, QMP link state, firmware load timing.
|
||||
2. **Check if mainline qcom_q6v5_pas driver handles the MSS PIL differently** from the downstream
|
||||
driver — e.g., missing a handover step, wrong power-domain sequence, or not waiting for a
|
||||
firmware-ready signal.
|
||||
3. **diag_mdlog F3 trace on Android**: capture the modem's own RF-init narration to see exactly
|
||||
where it succeeds on Android — this is the one instrument that can name the gate directly.
|
||||
135
saf/device/evidence/platform-pmos-era.md
Normal file
135
saf/device/evidence/platform-pmos-era.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# PAF · Platform — hardware, boot, storage, recovery
|
||||
|
||||
Load-bearing device facts. Add a line when something costs more than 2 minutes to relearn.
|
||||
|
||||
## Reaching the phone
|
||||
|
||||
- WiFi (preferred): `ssh -i ~/.ssh/ani_id_ed25519 casey@10.10.20.220` (Samaritan Solutions).
|
||||
**Drops on reboot / ~60–90 s to rejoin** — poll, don't assume.
|
||||
- USB fallback: `casey@172.16.42.1`. Helper: `../phone.sh {ssh,vnc,push,pull,run}`.
|
||||
- sudo password `147147` → `echo 147147 | sudo -S <cmd>`. **Never** pipe file content into a
|
||||
`sudo -S tee` (password and content fight over stdin). Write `/tmp/x` as user, then
|
||||
`echo PW | sudo -S cp /tmp/x /dest`.
|
||||
- pmOS is **Alpine — shell is ash/busybox, not bash.** No `[[ ]]`, no arrays, no `function`.
|
||||
- Host (Arch) ↔ Android: `adb` works with root (`magisk`); `fastboot` for slot/boot ops.
|
||||
|
||||
## Slots & boot architecture
|
||||
|
||||
- **slot A = pmOS, slot B = LineageOS (Android).** Verified 2026-06-25 on-device: pmOS
|
||||
ran with `androidboot.slot_suffix=_a`; `fastboot getvar current-slot` = a; flipping
|
||||
active to b booted Android; Android (slot B) had `system_b`/sda6 mounted. So pmOS =
|
||||
`boot_a` (sda11) + `system_a` (sda5); Android = `boot_b` (sda12) + `system_b` (sda6).
|
||||
Older PAF entries saying A=Android/B=pmOS or rootfs in system_b are pre-swap and stale.
|
||||
Switch active slot: from pmOS `sudo reboot bootloader` (~12 s → fastboot), then
|
||||
`fastboot --set-active={a,b} && fastboot reboot`.
|
||||
- pmOS rootfs lives **inside** `system_a` (sda5) as a packed **GPT image with 4096-byte
|
||||
sectors** (UFS). Two inner partitions:
|
||||
- boot (vfat, ~236 MB) at **byte offset 8388608** in sda6 — holds the kernel, initramfs,
|
||||
`linux.efi`, and DTBs. The appended DTB in the boot_b partition image IS used by ABL
|
||||
(it is baked into the kernel image in the boot.img). The `dtbs/qcom/` copies in the vfat
|
||||
are for a U-Boot/EFI path that is NOT used on this device currently.
|
||||
- root (ext4, ~2.5 GB) at **byte offset 255852544**.
|
||||
UUID: `26733d78-aa54-454d-ba43-0fa1eabfb3fd` (pmOS_root label)
|
||||
- boot vfat UUID: `2CE8-836F` (pmOS_boot label)
|
||||
- `fastboot flash system` is blocked (needs fastbootd, which won't enumerate on this device).
|
||||
Write the rootfs via `dd` over ADB from Android, after `simg2img`.
|
||||
- 4K sectors: `sgdisk` needs `-a 256` for 1 MiB alignment; `gdisk` reports "GPT not present"
|
||||
if it assumes 512-byte sectors — the image is valid.
|
||||
- **`fastboot boot <img>` has a hardware watchdog timeout (~60 s)** — the phone reboots itself
|
||||
if the image doesn't complete boot. Always `fastboot flash boot_b <img>` instead.
|
||||
- **/tmp is tmpfs (volatile, wiped on reboot) — never park load-bearing artifacts there.**
|
||||
|
||||
## Boot image parameters (slot A — pmOS)
|
||||
|
||||
The canonical pmOS boot image for slot A was `/tmp/pmos-boot-b.img` (29245440 bytes) — **wiped on 2026-06-21 reboot (/tmp is tmpfs).** The rescued recovery boot is now at `artifacts/tmp-rescue-20260626/recovery-boots/boot_a_restore.img` (65M). The filename predated the slot swap; always flash to `boot_a`, not `boot_b`.
|
||||
Rebuild targets `/tmp/pmos-debug2.img` (debug shell) and `/tmp/pmos-nodebug.img` (normal boot)
|
||||
must be regenerated. To rebuild from rescued kernel+ramdisk in `artifacts/tmp-rescue-20260626/bootimg-workspace/`:
|
||||
|
||||
```sh
|
||||
# Normal boot (no debug shell):
|
||||
mkbootimg --kernel artifacts/tmp-rescue-20260626/bootimg-workspace/kernel --ramdisk artifacts/tmp-rescue-20260626/bootimg-workspace/ramdisk \
|
||||
--base 0x0 --kernel_offset 0x00008000 --ramdisk_offset 0x01000000 \
|
||||
--tags_offset 0x00000100 --pagesize 4096 --header_version 0 \
|
||||
--cmdline "console=tty0 console=ttyMSM0,115200 consoleblank=0 \
|
||||
pmos_boot_uuid=2CE8-836F pmos_root_uuid=26733d78-aa54-454d-ba43-0fa1eabfb3fd \
|
||||
pmos_rootfsopts=defaults" \
|
||||
-o /tmp/pmos-nodebug.img
|
||||
|
||||
# Debug shell (drops to telnet:23 before mounting rootfs, adds pmos.debug-shell):
|
||||
# ... same but append: pmos.debug-shell
|
||||
```
|
||||
|
||||
Flash to slot A (pmOS) and reboot:
|
||||
```sh
|
||||
fastboot flash boot_a /tmp/pmos-nodebug.img && fastboot set_active a && fastboot reboot
|
||||
```
|
||||
|
||||
**Emergency debug shell** (when pmOS won't boot — flash debug image to boot_a):
|
||||
```sh
|
||||
fastboot flash boot_a /tmp/pmos-debug2.img && fastboot set_active a && fastboot reboot
|
||||
# USB gadget comes up as 18d1:d001 at 172.16.42.1 (~8 s after reboot)
|
||||
# telnet 172.16.42.1 23 → shell with pmos_continue_boot / pmos_logdump
|
||||
# After diagnosing, re-flash the normal image and reboot.
|
||||
```
|
||||
|
||||
**USB host setup** (permanent, already configured 06-19):
|
||||
- `/etc/NetworkManager/conf.d/99-pmos-usb.conf` — NM ignores `cdc_ncm`/`rndis_host`/`cdc_ether`
|
||||
- `/etc/udev/rules.d/99-pmos-usb.rules` — auto-assigns `172.16.42.2/24` on interface up
|
||||
|
||||
**Disk full recovery** (happened 06-19 — logs filled rootfs to 100%):
|
||||
Boot debug image → telnet in → mount_subpartitions → mount /dev/loop0p2 /sysroot →
|
||||
`rm -rf /sysroot/var/log/*` → pmos_continue_boot. Frees ~94 MB.
|
||||
The rootfs will slowly refill — **set up log rotation on pmOS** (`/etc/logrotate.d/` or
|
||||
`journalctl --vacuum-size=10M`) once booted.
|
||||
|
||||
## Recovery: swap the slot-B DTB from rooted Android (no TWRP)
|
||||
|
||||
Used 06-19 to un-brick slot B after the SPMI-pmic DTB panic. Reversible; backs up the old DTB.
|
||||
|
||||
```sh
|
||||
adb push <good>.dtb /data/local/tmp/recovery.dtb
|
||||
adb shell 'su -c "
|
||||
for l in /dev/block/loop*; do losetup -d \$l 2>/dev/null; done
|
||||
LO=\$(losetup -f); losetup -o 8388608 \$LO /dev/block/by-name/system_b
|
||||
mkdir -p /data/local/tmp/bmnt; mount -t vfat \$LO /data/local/tmp/bmnt
|
||||
T=/data/local/tmp/bmnt/dtbs/qcom/sdm845-google-blueline.dtb
|
||||
cp \$T \$T.broken.bak
|
||||
cp /data/local/tmp/recovery.dtb \$T
|
||||
sync; md5sum \$T /data/local/tmp/recovery.dtb
|
||||
umount /data/local/tmp/bmnt"' # loop auto-detaches on umount
|
||||
```
|
||||
|
||||
Building a DTB: base = the pristine kernel-package DTB (decompile
|
||||
`linux-postmarketos-qcom-sdm845-*.apk`, or `/tmp/pmos-packages/blueline.dts`). Edit, then
|
||||
`dtc -I dts -O dtb -o out.dtb in.dts`. The pristine DTB is 111641 bytes; verify
|
||||
`grep -c 'pmic@4\|pmic@5'` is **0** (those panic).
|
||||
|
||||
## Storage situation
|
||||
|
||||
- Root (ext4 inside sda6) is **2.4 GB, ~80 MB free** (94 MB logs cleared 06-19), 828 packages.
|
||||
Nothing significant installs until userdata is resized. `apk cache clean` buys ~300 MB
|
||||
temporarily. **Logs will refill** — set up log rotation once booted.
|
||||
- **userdata (sda21, 111 GB) is FBE + metadata-encrypted** (dm-default-key, AES-256-XTS; key
|
||||
KeyMint-wrapped in TEE, stored in `/metadata` sda20). **Cannot shrink offline** — no plaintext
|
||||
F2FS superblock outside Android; key can't be re-derived in TWRP. Shrinking = full wipe.
|
||||
Decision (06-18): do **not** wipe until calls + Linux desktop both justify it.
|
||||
- GPT backup of the outer table: `../gpt-sda-20260618.bak`. Nothing on sda was modified.
|
||||
|
||||
## Hardware lessons (do not relearn)
|
||||
|
||||
1. **Never `misc:vfr = 0`, never DPMS off/on** in Hyprland — wedges the DSI panel + stmfts
|
||||
touch IC. Recovery: `systemctl restart getty@tty1` for a clean modeset. Use `vfr = true` +
|
||||
`cursor:no_hardware_cursors = true`. Skip earboxer's power-button DPMS binds.
|
||||
2. **hyprgrass must match the Hyprland version exactly** (build against the `hl-x.y.z` tag).
|
||||
3. Display already correct: `monitor = DSI-1,1080x2160@60,0x0,2.0` (scale 2.0 → 540×1080
|
||||
logical). ~443 PPI panel.
|
||||
4. Restarting rmtfs blips ath10k → WiFi can drop; prefer USB during modem work.
|
||||
5. USB-net flaps because NetworkManager grabs `enp0s20f0u6`. Fix with **pkexec** (not sudo):
|
||||
`nmcli dev set <if> managed no; ip addr add 172.16.42.2/24 dev <if>; ip link set <if> up`.
|
||||
|
||||
## Backups on the laptop
|
||||
|
||||
`~/pixel3-backup/` — full Android partition images (gzipped): `boot_a` (downstream DTB +
|
||||
kernel), `modem_a` (FAT16 with `mba.mbn` + `modem.b00..b25`), `persist` (cal: audio/sensors/
|
||||
rfs/DRM), `system_a`, `vendor_a`, `vbmeta_a`, `dtbo_a`, `userdata` (87 GB). EFS snapshot:
|
||||
`../efs-backup-20260618/` (modemst1/2, fsg, fsc).
|
||||
148
saf/device/evidence/pmos_fix_plan.md
Normal file
148
saf/device/evidence/pmos_fix_plan.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# pmOS Modem Fix — Implementation Plan (autoselect, EFS-persisted)
|
||||
|
||||
Consolidates the 2026-06-20 live-Android + binary-RE session. This is the build plan for the
|
||||
pmOS side. Evidence is cited to live capture lines or binary addresses; nothing here is inferred
|
||||
without a source.
|
||||
|
||||
## The mechanism (works — but is NOT the gate)
|
||||
|
||||
> **🟡 RE-STAMPED 2026-06-29.** The MCFG auto-select-by-UIM mechanism is real and PDC configs do go
|
||||
> Active (`qcril-autoselect` runs, Set Feature accepted) — but this is **proven NOT to be the gate**:
|
||||
> the modem parks OFFLINE / model 0 and refuses every pre-RF write (online→52, low_power→60,
|
||||
> NAS SSP→25) regardless of provisioning state. Choreography is downstream of the OFFLINE state.
|
||||
> See `modem.md` eliminated-leads ledger. Kept as the mechanism reference (how MCFG/EFS autoselect
|
||||
> works), not as a live fix path.
|
||||
|
||||
The modem provisions itself by **MCFG auto-selection keyed on the SIM (UIM)**, applied at the
|
||||
modem's own firmware RF-init, and **persisted in the modem's EFS** (`modemst1/2`, which is
|
||||
**shared** between the Android and pmOS slots — `sdf3/sdf4`, not slotted).
|
||||
|
||||
1. **Persistent form (community-proven on SDM845 / OnePlus 6/6T):** an EFS NV item
|
||||
`/nv/item_files/mcfg/mcfg_autoselect_by_uim` plus the mcfg uploaded into EFS. Written over
|
||||
DIAG (EfsTools). Once set, the modem self-selects + self-applies every boot.
|
||||
2. **Runtime form (reverse-engineered from `libril-qc-hal-qmi.so`):**
|
||||
`pdc_enable_auto_selection(enable, id)` @ `0x1d45c40` builds an 80-byte
|
||||
`pdc_set_feature_req_msg_v01` (feature value `3` when enabling, `0` when disabling) and calls
|
||||
`pdc_set_feature` @ `0x1d45ac0`, which issues **QMI_PDC_SET_FEATURE**:
|
||||
- **message ID `0x2E` (46)** (`mov w1,#0x2e` at `0x1d45b4c`)
|
||||
- request length `0x50` = 80 bytes (`mov w3,#0x50`), response 8 bytes (`mov w5,#0x8`),
|
||||
timeout 10000 ms (`mov w6,#0x2710`).
|
||||
|
||||
## Live-Android ground truth (this session, device 89JX0AAJP)
|
||||
|
||||
- Modem fully working: `dumpsys telephony` → IN_SERVICE, LTE, Fido 302/720, voice+data.
|
||||
- **Self-persist confirmed:** blanked `persist.vendor.radio.cnv.ver_info`, restarted live
|
||||
`qcrild`. `is_modem_mbn_updated` logged `version info updated` (prev empty), `start_mbn_update`
|
||||
fired — but QCRIL only queried `get_active_config_info` (`config_version=0x801481a`, CNV
|
||||
`0x48`=72), re-synced, and **never loaded/activated**. The modem already held the config in its
|
||||
own NV. → userspace is reconcile-only; the modem persists. (`/tmp/qcril-re/live-firstprov.txt`)
|
||||
- **Online-arm reference captured** (the step that returns `DeviceNotReady` on pmOS):
|
||||
airplane toggle → `dms_handle_modem_operating_mode → "operating mode needs to be changed" →
|
||||
SETTING_OP_MODE → modem ONLINE`. (`/tmp/qcril-re/airplane-arm.txt`)
|
||||
- `/data/vendor/modem_config/` does **not exist** on this device — QCRIL reads MBNs straight from
|
||||
`/vendor/rfs/msm/mpss/readonly/vendor/mbn/` (the readonly vendor tree), not a `/data` cache.
|
||||
|
||||
## Why pmOS fails, and what the fix must do
|
||||
|
||||
pmOS boots with the modem at 0/0 configs and `DeviceNotReady`. The Android-equivalent state is:
|
||||
modem set to auto-select by UIM + the mcfg tree reachable at RF-init + EFS writable so the modem
|
||||
persists what it applied. The fix must guarantee all three on pmOS:
|
||||
|
||||
1. **mcfg tree served to the modem at RF-init** — `tqftpserv` serving
|
||||
`/mnt/vendor/persist/rfs/readonly/vendor/mbn/` (present, per modem.md). Verify the modem's
|
||||
requested path matches.
|
||||
2. **EFS writable** — `rmtfs -P -s` (read-write, drop `-r`) so the modem can commit/persist its
|
||||
applied config (frontier #4; the read-only `-r` shadow is why nothing persisted before).
|
||||
3. **Auto-selection enabled** — set it so the modem self-applies. Two routes below.
|
||||
|
||||
## Build — two routes (do A first; B is the persistent backstop)
|
||||
|
||||
### Route A — runtime QMI via PDC Set Feature (ALREADY AVAILABLE in pmOS's libqmi fork)
|
||||
**This is the path. It is not new work — the pmOS community already built the libqmi side.**
|
||||
|
||||
Stock libqmi PDC has no Set Feature. But the **openimsd libqmi fork**
|
||||
(`https://gitlab.postmarketos.org/modem/openimsd/libqmi`, base 1.39.0) already defines
|
||||
**`QMI_PDC_SET_FEATURE` at id `0x2E`** — the *exact* message ID we independently reverse-engineered
|
||||
from `pdc_set_feature` (`0x1d45b4c: mov w1,#0x2e`). Two sources, same message. Its request TLVs
|
||||
(`data/qmi-service-pdc.json`, "since 1.40"):
|
||||
|
||||
| TLV | id | type | meaning |
|
||||
|---|---|---|---|
|
||||
| Slot ID | common-ref | guint32 | slot |
|
||||
| Token | common-ref | guint32 | request token |
|
||||
| **Selection Mode** | 0x11 | guint32 | **set to 3 = auto-select-by-UIM** (matches the `enable→feature 3` in `pdc_enable_auto_selection`) |
|
||||
| Refresh Mode | 0x14 | gboolean | refresh on change |
|
||||
| IMS | 0x19 | gboolean | ims feature |
|
||||
|
||||
Output: Operation Result (+ a Set Feature *indication* carrying the Token).
|
||||
|
||||
Implementation on pmOS:
|
||||
- Build/install the **openimsd libqmi fork** (it generates `Qmi.MessagePdcSetFeatureInput` with
|
||||
`set_selection_mode` / `set_refresh_mode` / `set_ims` / `set_slot_id` / `set_token`).
|
||||
- Call it from **python-GI** — the fork's **qmicli has no `--pdc-set-feature` CLI**, so do it the
|
||||
way `qcom-imsd` does (Qmi GI over `qrtr://0`), not via a qmicli flag. Set Selection Mode = 3,
|
||||
Refresh Mode on. This is the runtime equivalent of QCRIL's `pdc_enable_auto_selection`.
|
||||
- pmOS boot service: enable auto-select, let the modem self-provision from the served mbn tree.
|
||||
|
||||
Note: the fork still carries the **load-config `g_free` segfault at `qmicli-pdc.c:1130`** (it's
|
||||
1.39.0). Only relevant if we fall back to explicit `--pdc-load-config`; the autoselect path
|
||||
shouldn't need it. Our one-line patch + `tools/build-qmicli.sh` still apply if so.
|
||||
|
||||
**Prior art / proof:** `qcom-imsd` (same fork, libqmi-GI over qrtr PDC) is **known-working on
|
||||
Pixel 3a (sargo)** — blueline's SDM845 sister. It does IMS-layer PDC (select/activate/register);
|
||||
we add the lower-layer Set Feature autoselect. The pmOS PDC-over-qrtr approach is proven on this
|
||||
silicon.
|
||||
|
||||
### Route B — persistent EFS NV item (DIAG, the community method)
|
||||
Write `/nv/item_files/mcfg/mcfg_autoselect_by_uim` (enable) into the modem EFS via a DIAG-EFS2
|
||||
client on pmOS. pmOS diag is **not** HAL-locked the way the production-Android Pixel gadget is
|
||||
(this session: the Pixel USB gadget HAL reverted configfs to adb-only, blocking USB-diag;
|
||||
`/dev/diag` local access remained available). Because `modemst` is shared, this is a one-time
|
||||
write that both slots inherit. Tooling: EfsTools (Linux/.NET — `dotnet` is available) or a small
|
||||
DIAG-EFS2 client against pmOS `/dev/diag`.
|
||||
|
||||
## Test loop (on pmOS, next boot)
|
||||
1. Confirm rmtfs `-P -s` (rw) + tqftpserv serving the mbn tree.
|
||||
2. Apply Route A (or B) to enable auto-selection.
|
||||
3. Reboot; check `qmicli --dms-get-capabilities` → `Networks` non-empty = RF armed.
|
||||
4. If armed: confirm it survives a second reboot (persistence). If not, apply Route B so the
|
||||
modem commits to EFS.
|
||||
|
||||
## LIVE RESULT 2026-06-20 (Route A run on pmOS hardware — config blocker SOLVED)
|
||||
|
||||
Ran the full fix on pmOS slot B (kernel 7.1.0-rc1-sdm845), live modem over qrtr://0:
|
||||
|
||||
1. Built + installed the **openimsd libqmi fork** on-device (Alpine); `Qmi.MessagePdcSetFeatureInput
|
||||
present: True`.
|
||||
2. Ran `qcril-autoselect.py` → PDC client allocated, **Set Feature (0x2E) accepted**. Signal names
|
||||
`set-feature`/`get-feature` were correct (indications arrived; only a strict-parse warning:
|
||||
the modem's Get Feature indication omits the mandatory Result TLV 0x02 libqmi expects — cosmetic).
|
||||
3. **Immediately after Set Feature, the modem self-loaded its configs: PDC 0/0 → 2 platform
|
||||
(SS-LA-SDM845 ACTIVE) + 25 software (Rogers_Canada).** It pulled them itself from the
|
||||
tqftpserv-served mbn tree. This is the modem auto-selecting by UIM — the mechanism working.
|
||||
4. **Rebooted with nothing enabled → the modem self-loaded 2+25 again at RF-init, platform ACTIVE.**
|
||||
So the autoselect **persisted** (Set Feature is sticky, not just runtime). This is the documented
|
||||
project blocker (0/0 every cold boot, frontier #3/#4) **SOLVED**: blueline's modem now
|
||||
self-provisions MCFG persistently on mainline pmOS — believed a first for Pixel 3.
|
||||
|
||||
**Remaining wall (NEW, deeper than config):** despite firmware up (mba+mpss booted, IPA running,
|
||||
handover signaled), platform config ACTIVE, and ModemManager seeing the configs,
|
||||
`--dms-set-operating-mode=online` still returns **DeviceNotReady (QMI 52)**; modem stays offline,
|
||||
Networks ''. `low-power` → InvalidTransition (already LPM). A full reboot with configs active did
|
||||
NOT arm RF — so it is not a re-init-count problem.
|
||||
|
||||
**Leading hypothesis for the remaining wall → Route B (firmware-level autoselect via EFS NV).**
|
||||
Our Set Feature operates at the **PDC/RIL layer** (post-firmware-boot): the modem loads+activates
|
||||
the config in PDC, but the **firmware RF-init at ~12s already ran without it**, so PDC-ACTIVE ≠
|
||||
RF-consumed. The Android/OnePlus mechanism writes the EFS NV item
|
||||
`/nv/item_files/mcfg/mcfg_autoselect_by_uim` so the **firmware itself** auto-selects at its
|
||||
earliest RF-init, before RF comes up. Next step: write that EFS NV item on pmOS (diag/EfsTools —
|
||||
not HAL-locked here) so the firmware applies MCFG at RF-init. Alternative to rule out: a genuinely
|
||||
separate non-config blocker (cal/IMEI) — but the same modem arms on Android, so config-apply timing
|
||||
is the stronger lead.
|
||||
|
||||
## Artifacts from this session
|
||||
`/tmp/qcril-re/`: `live-firstprov.txt` (qcrild restart, reconcile-only proof),
|
||||
`airplane-arm.txt` (online-arm reference), `auto_sel.txt` + objdump of
|
||||
`pdc_enable_auto_selection` / `pdc_set_feature` (msg 0x2E). EFS clones already in repo:
|
||||
`efs-postandroid/`, `efs-backup-20260618/` (`modemst1/2`, `fsg`, `fsc`).
|
||||
158
saf/device/evidence/power/HANDOFF-upower-charge-control.md
Normal file
158
saf/device/evidence/power/HANDOFF-upower-charge-control.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# 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
|
||||
103
saf/device/evidence/qcril_bringup_capture.md
Normal file
103
saf/device/evidence/qcril_bringup_capture.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# QCRIL modem bring-up — CAPTURED exact sequence (recreate this on pmOS)
|
||||
|
||||
This is the captured, ordered sequence QCRIL runs to bring the modem online on the **working**
|
||||
blueline Android. The standing goal (Casey): **recreate this exactly on pmOS** — approximating has
|
||||
been the failure. Do NOT re-capture this; analyze the saved files below.
|
||||
|
||||
## Capture files (in repo, `android-capture-20260620/`)
|
||||
- `qcril-ssr-fullinit.txt` — **the primary capture.** Modem SSR (`echo restart >
|
||||
/sys/kernel/debug/msm_subsys/modem`) with verbose radio logging → QCRIL re-runs its FULL bring-up
|
||||
→ modem ONLINE. 66k lines, ts 21:07:04–21:07:35.
|
||||
- `qcril-ssr-TIMELINE.txt` — distilled ordered timeline (section A: QMI sends per endpoint;
|
||||
section B: per-module handling order).
|
||||
- `qcril-online-capture.txt` — airplane off→on (the LPM→online flip in isolation).
|
||||
- `android-radio-log.txt` — steady-state runtime reference (attached, Fido LTE).
|
||||
|
||||
## SIM identity (for replay)
|
||||
ICCID `<redacted>`, IMSI `<redacted>`, MCC/MNC **302/370 (Fido)**, carrierId 1962,
|
||||
GW app1 AID `A0000000871002F310FFFF89B00140FF`, slot 1.
|
||||
|
||||
## The exact ordered sequence (SSR → ONLINE), with evidence
|
||||
1. **Modem SHUTTING_DOWN → re-init.** `qcril_qmi_nas_log_radio_power_process_state: telephony ONLINE
|
||||
| ril NONE | card UP | modem SHUTTING_DOWN`.
|
||||
2. **Endpoint-status indications fire for EVERY service as it re-registers** (qcril-ssr-fullinit
|
||||
lines ~252–1325): IMSS, IMSA, **PDC** (→ MbnModule), SMS, NAS, PBM, **DMS** (→ DmsModule AND
|
||||
MbnModule), UIM (`uim.card_status_update slot 0`), VS, Voice, WDS, Auth. QCRIL drives each
|
||||
service's endpoint, not just DMS.
|
||||
3. **MbnModule re-syncs PDC — VERIFY ONLY, no load/select/activate.** `PDC_ENDPOINT_STATUS_IND` and
|
||||
`DMS_ENDPOINT_STATUS_IND` both forwarded to MbnModule; no `load_config`/`select_config`/
|
||||
`activate_config`/`enable_auto_selection` anywhere in the SSR window. MCFG persisted in modem NV
|
||||
across the SSR (consistent with frontier #4). **→ On pmOS we must get MCFG in modem NV ONCE; it
|
||||
then persists.**
|
||||
4. **UIM provisioning is NOT re-run on SSR** (no `change_provisioning_session` in the capture) — it
|
||||
persisted. **→ On pmOS, primary-GW provisioning must be established once and stick.**
|
||||
5. **NAS config burst (the "QCRIL does more" set), then power.** QCRIL calls, in the bring-up window:
|
||||
`qcril_qmi_nas_init`, `request_set_preferred_network_type` (tells the modem which RATs to
|
||||
acquire), `request_radio_tech`, `set_reported_voice_radio_tech`, `request_set_unsol_response_filter`,
|
||||
`configure_sig_info`, `set_lpm_after_ssr_in_apm`, `set_bootup_power_optimization_state`,
|
||||
`request_power`. ModemManager issues only a small subset of these.
|
||||
6. **DMS operating-mode flip: LPM → ONLINE.** `qcril_qmi_nas_dms_handle_modem_operating_mode:
|
||||
modem's operating mode needs to be changed` → ril SETTING_OP_MODE → `modem ONLINE` (~270 ms after
|
||||
SSR; line ~15967). post: `set_bootup_power_optimization_state=2`, ATEL UI-READY, enable
|
||||
networking/sys-info indications → `is_online 1` → forces NW search.
|
||||
|
||||
## The decisive state difference (Android vs pmOS)
|
||||
- **Android: the modem is held in `LPM` (low_power, powered, RF parked); QCRIL flips LPM→online.**
|
||||
- **pmOS: the modem sits in `offline` (deeper) and `offline→online` is what returns DeviceNotReady /
|
||||
no-ops.** Plus pmOS boots with 0 PDC configs (autoselect not active that boot) and provisioning
|
||||
failing on the early-boot race.
|
||||
|
||||
## Recreate-exactly plan for pmOS (the test we have NEVER fully run)
|
||||
On one clean slot-B boot, in order, all three prereqs together:
|
||||
1. Get MCFG into modem NV (PDC autoselect / load+select+activate) → configs ACTIVE.
|
||||
2. Establish primary-GW provisioning cleanly (run AFTER the QRTR race; it succeeds manually:
|
||||
`--uim-change-provisioning-session=slot=1,activate=yes,session-type=primary-gw-provisioning,aid=
|
||||
A0000000871002F310FFFF89B00140FF`).
|
||||
3. Set the NAS preferred-network-type / radio-tech (QCRIL step 5) — replicate via libqmi NAS.
|
||||
4. Bring the modem up through **LPM** (set `--dms-set-operating-mode=low_power` if it's in `offline`)
|
||||
THEN `=online`. Do NOT flip offline→online directly.
|
||||
5. Verify `--dms-get-capabilities` Networks non-empty + NAS serving system registers.
|
||||
|
||||
Replay vehicle: the openimsd libqmi fork (Set Feature for autoselect) + libqmi NAS/DMS/UIM via
|
||||
python-GI. The point is the ORDER and the LPM state, captured above — not a partial subset.
|
||||
|
||||
## 2026-06-21 LIVE — clean provisioning + configs is NOT sufficient (eliminated)
|
||||
Fresh slot-B boot, modem running (remoteproc2 up, no fatal in dmesg, zero modem rail failure).
|
||||
State: mode=offline, configs PRESENT (sw 0x8013303 + platform loaded), USIM app=ready.
|
||||
- msm-modem-uim-selection FAILED at boot (early-QRTR race: "Application was already selected"
|
||||
then Transaction timed out) — the #7 symptom.
|
||||
- Replicated the service BY HAND post-boot: deactivate timed out, but ACTIVATE returned
|
||||
"Successfully changed provisioning session" => primary-gw provisioning now cleanly active.
|
||||
- Immediately retried: low_power => InvalidTransition(60); online => DeviceNotReady(52).
|
||||
Mode stayed offline. Networks '', Max TX 0.
|
||||
CONCLUSION: configs-active + clean-primary-gw-provisioning + USIM-ready + healthy rails is
|
||||
NOT enough to arm RF. The modem is wedged in `offline` (RF subsystem not-ready) and refuses
|
||||
BOTH offline->low_power and offline->online. PAF #7's "clean provisioning -> online stops
|
||||
returning 52" did NOT reproduce. The remaining differentiator is QCRIL's boot-time QMI/NAS
|
||||
choreography (#8: radio-power state machine + UI-ready/poweron_opt) that brings the modem to
|
||||
LPM. Android boots the modem INTO low_power; pmOS boots it into the deeper `offline`.
|
||||
NEXT: capture how QCRIL puts/keeps the modem in LPM on rooted Android, replay on pmOS.
|
||||
|
||||
## 2026-06-21 REFRAME — Android modem SELF-ONLINES (mode 0); there is no QCRIL online handshake
|
||||
Mined existing captures (android-live-20260619/deep/radio-boot.txt, android-capture-20260620/
|
||||
qcril-ssr-fullinit.txt). The op-mode VALUE settles the "LPM" confusion:
|
||||
- radio-boot.txt:3694 qcril_qmi_nas_dms_get_current_power_state: ".. known modem operating mode 0"
|
||||
(DMS op-mode 0 = ONLINE).
|
||||
- radio-boot.txt:3949 "telephony LPM | ril NONE | card UP | modem ONLINE"
|
||||
=> the "LPM" is the AP/telephony airplane-state layer; the MODEM is ONLINE.
|
||||
- radio-boot.txt:3862 qcril_qmi_nas_telephony_ready: "poweron_opt: 'UI ready' NOT sent to modem"
|
||||
(skipped BECAUSE modem already online).
|
||||
- ssr-fullinit: QCRIL only POLLS op-mode ("Operating mode is not available, polling...") and sets
|
||||
bootup_power_optimization_state=2. It never issues DMS Set Operating Mode = online.
|
||||
|
||||
CONCLUSION (corrects #8's "Android boots modem into LPM"): on Android the modem firmware
|
||||
SELF-COMPLETES RF-init and SELF-TRANSITIONS to ONLINE (mode 0) with NO online command from
|
||||
QCRIL. The Android "LPM" is the RIL/airplane layer, not the modem. Therefore replaying a
|
||||
"QCRIL online handshake" on pmOS is a dead end -- there isn't one.
|
||||
|
||||
The pmOS gate, precisely: the modem firmware's own RF-init does NOT complete on pmOS, so it
|
||||
parks in OFFLINE (mode 3) and returns DeviceNotReady(52) when we ASK it to online (Android
|
||||
never has to ask). Everything AP-side is equal/eliminated. The remaining differentiator is
|
||||
what the modem reads/does at its OWN firmware RF-init -- strongest lead = modem-side EFS NV
|
||||
state (Route B: /nv/item_files/.../power-up operating mode and/or mcfg_autoselect_by_uim),
|
||||
i.e. something the Android modem persisted that makes RF-init succeed and self-online.
|
||||
680
saf/device/evidence/qcril_full_provisioning.md
Normal file
680
saf/device/evidence/qcril_full_provisioning.md
Normal file
|
|
@ -0,0 +1,680 @@
|
|||
# QCRIL Full Provisioning — Gap Analysis & Design
|
||||
|
||||
**Purpose:** Reconstruct what QCRIL does to provision the modem every boot, identify every gap
|
||||
vs. our current Python tools, and design a full replacement provisioner. This feeds a real
|
||||
hardware bring-up; every structural claim cites a symbol, string, or file.
|
||||
|
||||
---
|
||||
|
||||
## A. What Our Tools Do Today
|
||||
|
||||
Two scripts, manually invoked in sequence.
|
||||
|
||||
### pdc_load.py (tools/pdc_load.py)
|
||||
|
||||
A chunked PDC LoadConfig implementation via libqmi GI bindings (1024-byte chunks). Reads an MBN
|
||||
file, computes its SHA-1, and uploads it to the modem via `QMI_PDC_LOAD_CONFIG` with a rolling
|
||||
token. Uses `EXPECT_INDICATIONS` flag so the modem can ack each chunk. On `remaining_size == 0`
|
||||
the upload is complete. Accepts `platform` or `software` config type on the CLI.
|
||||
|
||||
**QMI sequence:**
|
||||
1. Open QMI device (QRTR node)
|
||||
2. Allocate PDC client
|
||||
3. Connect `load-config` indication handler
|
||||
4. Loop: `QMI_PDC_LOAD_CONFIG_REQ` (chunk) → indication → next chunk
|
||||
5. Terminate on `remaining_size == 0`
|
||||
|
||||
No version checking. No selection. No activation. No carrier selection. No EFS interaction.
|
||||
No SIM awareness. No timing logic.
|
||||
|
||||
### pdc_activate.py (tools/pdc_activate.py)
|
||||
|
||||
Implements the `SetSelectedConfig` → `ActivateConfig` two-step per the libqmi qmicli source
|
||||
for `run_activate_config`. Takes a hex config ID on the CLI.
|
||||
|
||||
**QMI sequence:**
|
||||
1. Open QMI device
|
||||
2. Allocate PDC client
|
||||
3. `QMI_PDC_SET_SELECTED_CONFIG_REQ` (type + ID + token)
|
||||
4. On `set-selected-config` indication → `QMI_PDC_ACTIVATE_CONFIG_REQ` (type + token)
|
||||
5. On `activate-config` indication → done (modem restart expected)
|
||||
|
||||
No version check (always activates). No load-first. No sequencing of HW before SW.
|
||||
No handling of the restart-then-load ordering. No awareness of what was already active.
|
||||
|
||||
### modem_health.sh (tools/modem_health.sh)
|
||||
|
||||
Read-only snapshot: remoteproc state, QMI up/down, PDC list for platform and software configs
|
||||
with active flags, DMS get-capabilities (the RF-arm signal), NAS home network, operating mode,
|
||||
ModemManager status. Non-mutating by contract.
|
||||
|
||||
**What this tells us:** the `DMS Networks: ''` == RF not armed. `PDC Total: 0` == modem reset
|
||||
since last provisioning.
|
||||
|
||||
---
|
||||
|
||||
## B. What QCRIL Actually Does
|
||||
|
||||
Evidence sources: `libril-qc-hal-qmi.so` (34 MB, arm64 ELF, class `main`); embedded source
|
||||
paths `vendor/qcom/sm7250/proprietary/qcril-hal/modules/mbn/src/`; `qcril.db` (sqlite3);
|
||||
`mbn_hw.txt`, `mbn_sw.txt`, `mcfg.version`; `qcrild.rc`, `init.sdm845.rc`.
|
||||
|
||||
### B.1 Timing — When QCRIL Runs vs Modem Boot
|
||||
|
||||
From `qcrild.rc`:
|
||||
|
||||
```
|
||||
service vendor.qcrild /vendor/bin/hw/qcrild
|
||||
class main
|
||||
user radio
|
||||
```
|
||||
|
||||
`class main` services start at the `main` class trigger, which fires after `post-fs-data`
|
||||
completes. From `init.sdm845.rc`, the modem subsystem is NOT explicitly gated here — there is
|
||||
no `wait_for_prop` on a modem-ready property before qcrild starts. `rmt_storage` (EFS daemon)
|
||||
is also `class core` and starts earlier.
|
||||
|
||||
The modem firmware itself (MPSS via remoteproc `q6v5-mss`) loads at ~12.8 s after Linux boots,
|
||||
independent of Android init stages. QCRIL starts asynchronously as part of `class main` (roughly
|
||||
after `post-fs-data`, typically 10–30 s into boot, well before userspace is fully up).
|
||||
|
||||
**The decisive timing insight** (from PROGRESS.md 2026-06-20 entry): the modem applies MCFG at
|
||||
its own firmware RF-init window, which occurs during/after MPSS comes up (~12 s). QCRIL must
|
||||
deliver configs within this window. QCRIL achieves this because `qcrild` (class main) starts
|
||||
early and its MBN update sequence begins as soon as the DMS client becomes ready — triggered by
|
||||
`DmsModule::handleDmsEndpointStatusIndMessage` → `qcril_qmi_start_mbn_update()`.
|
||||
|
||||
**The trigger:** strings `"DMS client is ready. start MBN update"` and
|
||||
`"Module is ready,Start Mbn update"` both come from the `DmsModule` source
|
||||
(`vendor/qcom/sm7250/proprietary/qcril-hal/modules/dms/src/DmsModule.cpp`). When DMS QMI
|
||||
service becomes available on QRTR, `DmsModule::handleDmsEndpointStatusIndMessage` fires and
|
||||
calls `qcril_qmi_start_mbn_update()` (nm: `_Z26qcril_qmi_start_mbn_updatev`). This is the
|
||||
entry point into the full HW+SW MBN update state machine.
|
||||
|
||||
**Critical observation:** QCRIL does NOT wait for the modem to be "online" or for an RF-init
|
||||
complete signal before loading configs. It loads as soon as DMS is up (the modem is in some
|
||||
early QMI-ready state but not yet RF-armed). The modem then applies the loaded MCFG at its
|
||||
own RF-init pass, which happens after configs are in place.
|
||||
|
||||
The string `"is_ssr_or_bootup %d"` (`qcril_mbn_sw_update.cpp`) shows that the SW update path
|
||||
distinguishes cold boot from SSR. On warm SSR (subsystem restart), QCRIL only queries — does
|
||||
NOT reload (`qcril_qmi_pdc_get_active_config_info`, `qcril_qmi_pdc_get_selected_mbn_config`).
|
||||
On cold bootup, it runs the full load sequence. This matches the Android forensics finding:
|
||||
QCRIL skips reload on warm SSR (configs survived) but loads fresh on cold boot.
|
||||
|
||||
### B.2 Config Discovery — Where QCRIL Finds MBN Files
|
||||
|
||||
Two source directories (strings in libril-qc-hal-qmi.so):
|
||||
- Primary: `/data/vendor/modem_config/` (writable, symlinked or copied from vendor)
|
||||
- Vendor source: `/vendor/rfs/msm/mpss/readonly/vendor/mbn/` (read-only vendor partition)
|
||||
|
||||
On Android, QCRIL reads `mcfg.version` from `/vendor/rfs/msm/mpss/readonly/vendor/mbn/mcfg.version`
|
||||
(path literal in strings). It stores local DB metadata at
|
||||
`/data/vendor/modem_config/ver_info.txt` and in `qcril.db` tables.
|
||||
|
||||
The hardware config tree is indexed by `mbn_hw.txt`; the software config tree by `mbn_sw.txt`.
|
||||
QCRIL enumerates these lists to populate its internal DB (`qcril_mbn_hw_load_to_db`,
|
||||
`qcril_mbn_sw_load_to_db`) with version metadata parsed from each MBN file's header.
|
||||
|
||||
Error strings `"QCRIL_ERROR:IO: No hw mbn config directory"` and
|
||||
`"QCRIL_ERROR:IO: No sw mbn config directory"` confirm it expects both
|
||||
`/data/vendor/modem_config/mcfg_hw/` and `/data/vendor/modem_config/mcfg_sw/` to exist and
|
||||
be populated. The MBN path is also stored in `persist.vendor.radio.mbn_path` (property string
|
||||
in libril-qc-hal-qmi.so).
|
||||
|
||||
**On pmOS:** our MBN files live at `/mnt/vendor/persist/rfs/readonly/vendor/mbn/` (bind-mounted
|
||||
from the persist partition, analogous to tqftpserv's serve path). We have both `mcfg_hw/` and
|
||||
`mcfg_sw/` trees. The provisioner must be told where to find them.
|
||||
|
||||
### B.3 Config Selection — How QCRIL Picks the Right MBN
|
||||
|
||||
#### Hardware MBN Selection
|
||||
|
||||
Symbol `qcril_mbn_hw_get_hw_name_to_look_for` (T, exported): looks up the hardware platform
|
||||
name to match against. Symbol `qcril_mbn_hw_get_hw_config_from_db` looks up the best matching
|
||||
HW config from the internal DB by that name.
|
||||
|
||||
The HW name comes from the MBN metadata header embedded in each `mcfg_hw.mbn` file. Symbol
|
||||
`qcril_mbn_meta_retrieve_hw_name` reads the `HW_NAME` field from the MBN binary. The DB table
|
||||
`qcril_hw_mbn_file_type_table` stores `(FILE, HW_NAME, SHORT_NAME, CONFIG_ID, version fields)`.
|
||||
|
||||
The selection is a name-match: QCRIL identifies the running SoC/platform (from a system property
|
||||
or DMS device ID) and picks the HW MBN whose `HW_NAME` matches. For blueline/SDM845 + LA + SS
|
||||
(single-SIM), the match is `mcfg_hw/generic/common/SDM845/LA/SS/mcfg_hw.mbn`
|
||||
(verified: only SDM845/LA/SS and SDM845/LA/7+7_mode/SR_DSDS are in `mbn_hw.txt`; blueline is
|
||||
single-SIM so SS wins). The `HW_NAME` field in that MBN file must contain the identifier QCRIL
|
||||
matches against.
|
||||
|
||||
**Note:** The `HW_NAME` used for matching is extracted from the MBN binary header by
|
||||
`qcril_mbn_meta_retrieve_hw_name` — we don't have a text listing of what that string IS. For
|
||||
blueline we know the correct MBN from Android forensics: `mcfg_hw/generic/common/SDM845/LA/SS/mcfg_hw.mbn`.
|
||||
Our provisioner can hardcode this for SDM845/LA/SS rather than implement the full name extraction.
|
||||
|
||||
#### Software MBN Selection (Carrier)
|
||||
|
||||
This is the complex path. QCRIL uses a multi-level lookup in priority order:
|
||||
|
||||
**Level 1 — ICCID long-IIN (9-digit):** `qcril_mbn_db_retrieve_sw_mbn_file_for_long_iccid`
|
||||
queries `qcril_sw_mbn_iin_table` with the first 9 digits of the SIM's ICCID (`MCFG_LONG_IIN`
|
||||
field). If matched, uses that SW MBN. The ICCID is stored in `qcril_mbn_sw_iccid` (B, bss) and
|
||||
the current ICCID is read from `/data/vendor/radio/iccid` (path string in libril-qc-hal-qmi.so)
|
||||
or via UIM QMI `get_iccid` (`com.qualcomm.qti.qcril.uim.get_iccid_sync_request`).
|
||||
|
||||
**Level 2 — ICCID short-IIN (6-digit):** `qcril_mbn_db_retrieve_sw_mbn_file_for_iccid` queries
|
||||
`qcril_sw_mbn_iin_table` with the first 6 digits (`MCFG_IIN`).
|
||||
|
||||
**Level 3 — MCC/MNC:** `qcril_mbn_db_query_sw_mbn_file_with_mcc_mnc` queries
|
||||
`qcril_sw_mbn_mcc_mnc_table` with the SIM's MCC+MNC. MCC/MNC is retrieved via
|
||||
`com.qualcomm.qti.qcril.legacy.event.INTERNAL_UIM_GET_MCC_MNC` and/or the UIM IMSI path.
|
||||
|
||||
The DB tables (`qcril_sw_mbn_iin_table`, `qcril_sw_mbn_mcc_mnc_table`) are empty in the
|
||||
`qcril.db` we have — QCRIL populates them at runtime by parsing the MBN files and inserting
|
||||
rows. The `qcril.db` version in `qcril-config/` is a prebuilt baseline; QCRIL rebuilds the MBN
|
||||
tables on first boot or when `mcfg.version` changes.
|
||||
|
||||
The `qcril_manual_prov_table` contains 4 ICCID entries with `USER_PREF=1` — these are
|
||||
user-overridden manual provisioning entries that skip the automatic lookup. Our Fido SIM is
|
||||
unlikely to match these.
|
||||
|
||||
**Exception tables:** `qcril_mbn_iccid_exception_table` and `qcril_mbn_imsi_exception_table`
|
||||
are both empty in this build — no exceptions defined.
|
||||
|
||||
**For Rogers/Fido (MCC 302):** Fido is an MVNO on the Rogers network. The MBN tree contains
|
||||
`mcfg_sw/generic/NA/Rogers/Commercial/CA/mcfg_sw.mbn` (line 25 in `mbn_sw.txt`). Rogers MCC
|
||||
is 302. Whether QCRIL matches Fido (sub-MVNO) to the Rogers MBN depends on whether Rogers's
|
||||
ICCID/IIN prefix or MCC/MNC 302-480 (Rogers) vs 302-370 (Fido) maps to that file in the
|
||||
runtime-built DB. QCRIL's logic also has a `persist.vendor.radio.sw_mbn_openmkt` (open-market)
|
||||
flag that may affect fallback behavior — evidence: property string in libril-qc-hal-qmi.so.
|
||||
|
||||
**IMEI awareness:** QCRIL does NOT use the IMEI for MBN selection. IMEI is read via
|
||||
`RilRequestGetDeviceIdentityMessage` / `DmsModule::handleDeviceIdentiyRequestMessage` for
|
||||
device identity reporting, not for PDC config selection. The `qcril_mbn_cur_instance_id` (B)
|
||||
is the slot/instance ID (0/1), not the IMEI.
|
||||
|
||||
**Subscription tracking:** QCRIL caches the current subscription's ICCID
|
||||
(`qcril_mbn_sw_iccid`), MCC (`qcril_mbn_cur_instance_mcc`), MNC (`qcril_mbn_cur_instance_mnc`),
|
||||
and sub_id (`qcril_mbn_cur_sub_id`). When any of these change (SIM swap), `qcril_mbn_sw_is_sim_info_different_from_cache`
|
||||
detects it and `qcril_mbn_sw_if_restart_needed` decides whether to re-run the SW provisioning
|
||||
sequence. String: `"restart needed due to mcc/mnc/iccid/sub_id change"`.
|
||||
|
||||
### B.4 Version/Diff/Skip Logic
|
||||
|
||||
Symbols `qcril_mbn_db_is_sw_version_updated` (T, exported) and the string
|
||||
`"file_name: %s, config_name: %s, version: 0x%08x, is_matched: %d"` describe the core
|
||||
version-check logic:
|
||||
|
||||
QCRIL parses each candidate MBN's version from its header (`mcfg_get_oem_version`,
|
||||
`mcfg_get_qc_version` — both exported T symbols) and compares them to what the modem currently
|
||||
holds. The modem's active config version is retrieved via `qcril_qmi_pdc_get_active_config_info`
|
||||
(which calls `QMI_PDC_GET_CONFIG_INFO` with the active config ID).
|
||||
|
||||
Version is stored as a 32-bit split field: `MCFG_VERSION_FAMILY`, `MCFG_VERSION_OEM`,
|
||||
`MCFG_VERSION_CARRIER`, `MCFG_VERSION_MINOR` (function `qcril_mbn_db_split_version`, T exported).
|
||||
|
||||
If the modem's active config version matches the candidate MBN's version (`is_matched == 1`),
|
||||
QCRIL skips the reload. This is the "already provisioned" fast path tracked via Android
|
||||
property `persist.vendor.radio.hw_mbn_loaded` / `persist.vendor.radio.sw_mbn_loaded` and
|
||||
`persist.vendor.radio.cnv.ver_info`. The string `"prev_ver_info: %s, cur_ver_info: %s"`
|
||||
shows QCRIL compares a cached version string (from persist prop) to the current MBN version
|
||||
before deciding to reload.
|
||||
|
||||
The event `QMI_RIL_PDC_PARSE_DIFF_RESULT` (`qcril_evt_id_QMI_RIL_PDC_PARSE_DIFF_RESULT`) and
|
||||
function `parse_mbn_diff_result` indicate that QCRIL can also interpret a diff-result indication
|
||||
from the modem to decide whether the new config differs enough from the active one to warrant
|
||||
a restart. String: `"mbn differences length ="`.
|
||||
|
||||
**Summary of skip conditions:**
|
||||
1. `persist.vendor.radio.hw_mbn_loaded` / `sw_mbn_loaded` is set AND version matches modem active config → skip reload (fast path).
|
||||
2. On warm SSR: query get_active_config_info; if active matches → skip reload.
|
||||
3. On cold boot with no active config (count=0): always load.
|
||||
|
||||
**For pmOS:** Since configs are volatile (proven: 0/0 after every cold boot, PROGRESS.md
|
||||
2026-06-20), version check is moot — there is never an existing active config on our cold boot.
|
||||
We always need to load. The skip logic matters only when we build a persistent-prop mechanism
|
||||
or if we add SIM-change re-provisioning.
|
||||
|
||||
### B.5 The Full Load → Select → Activate Sequence
|
||||
|
||||
Reconstructed from the symbol table event chain:
|
||||
|
||||
#### Phase 1: HW MBN (platform config) — `REQUEST_MBN_HW_*` events
|
||||
|
||||
1. `REQUEST_MBN_HW_INIT` → `qcril_mbn_hw_update_init_hdlr`: Initialize HW update state machine.
|
||||
2. `REQUEST_MBN_HW_GET_SELECTED_CONFIG` → `qcril_mbn_hw_query_selected_config_hndlr`:
|
||||
Call `QMI_PDC_GET_SELECTED_CONFIG` for type=PLATFORM. If active config matches candidate,
|
||||
skip to SW phase.
|
||||
3. `REQUEST_MBN_HW_LOAD_CONFIG` → `qcril_mbn_hw_load_config_hndlr`:
|
||||
Chunked `QMI_PDC_LOAD_CONFIG` for the selected HW MBN file.
|
||||
4. `REQUEST_MBN_HW_SELECT_CONFIG` → `qcril_mbn_hw_select_config_hndlr`:
|
||||
`QMI_PDC_SET_SELECTED_CONFIG` for type=PLATFORM with the just-loaded config ID.
|
||||
5. `REQUEST_MBN_HW_ACTIVATE_CONFIG` → `qcril_mbn_hw_activate_config_hndlr`:
|
||||
`QMI_PDC_ACTIVATE_CONFIG` for type=PLATFORM. Modem restarts MPSS to apply MCFG.
|
||||
|
||||
Interleaved: `REQUEST_MBN_HW_DELETE_CONFIG` and `REQUEST_MBN_HW_DEACTIVATE_CONFIG` are also
|
||||
in the symbol table; QCRIL cleans up old/stale configs. `qcril_mbn_cleanup_inactive_configs`
|
||||
and `qcril_mbn_pdc_delete_all_sw_configs` delete inactive entries from modem RAM before loading
|
||||
new ones (to avoid hitting the modem's config count limit).
|
||||
|
||||
#### Phase 2: SW MBN (carrier config) — `REQUEST_MBN_SW_*` events
|
||||
|
||||
After HW phase completes (modem has restarted), SW phase runs:
|
||||
|
||||
1. `REQUEST_MBN_SW_INIT` → `qcril_mbn_sw_update_init_hdlr`: Start SW state machine; read ICCID
|
||||
and MCC/MNC from SIM (requires UIM to be up and SIM app selected).
|
||||
2. `VERIFY_MBN_SW_INIT` / `REQUEST_VERIFY_MBN_SW_INIT`: Check whether modem supports the MBN
|
||||
update feature at all (`qcril_qmi_imss_query_modem_supported_features`). String:
|
||||
`"Modem feature not supported. Continue with sw mbn update"` — if unsupported, SW update
|
||||
continues anyway (the feature check gates VoLTE-specific paths, not the basic PDC load).
|
||||
3. `REQUEST_MBN_SW_GET_SELECTED_CONFIG` → `qcril_mbn_sw_query_selected_config_hndlr`:
|
||||
Query `QMI_PDC_GET_SELECTED_CONFIG` for type=SOFTWARE. Version-check against candidate.
|
||||
4. `REQUEST_MBN_SW_COUNT_PENDING_CONFIGS` → `qcril_mbn_sw_count_pending_configs_hndlr`:
|
||||
Count pending (loaded but not yet selected) SW configs on the modem. String:
|
||||
`"has pending configuration"`. If a pending config already matches the desired SW MBN, QCRIL
|
||||
may skip the load step and go directly to select.
|
||||
5. `REQUEST_MBN_SW_LOAD_CONFIG` → `qcril_mbn_sw_load_config_hndlr`:
|
||||
Chunked `QMI_PDC_LOAD_CONFIG` for the selected SW MBN file (type=SOFTWARE).
|
||||
6. `REQUEST_MBN_SW_SELECT_CONFIG` → `qcril_mbn_sw_select_config_hndlr`:
|
||||
`QMI_PDC_SET_SELECTED_CONFIG` for type=SOFTWARE.
|
||||
7. `REQUEST_MBN_SW_ACTIVATE_CONFIG` → `qcril_mbn_sw_activate_config_hndlr`:
|
||||
`QMI_PDC_ACTIVATE_CONFIG` for type=SOFTWARE. Second modem restart.
|
||||
8. `REQUEST_MBN_SW_CLEANUP_CONFIG` → `qcril_mbn_sw_cleanup_config_hndlr`: Delete stale configs.
|
||||
|
||||
#### Phase 3: PDC Refresh Indication
|
||||
|
||||
`qcril_qmi_pdc_refresh_ind_hdlr` (`_Z30qcril_qmi_pdc_refresh_ind_hdlrPvj`) and
|
||||
`PDCRefreshIndication` / `"handlePDCRefreshInd(): "` — the modem can emit a PDC refresh
|
||||
indication after activation. QCRIL handles this to re-query config state. The `DataModule` also
|
||||
handles `PDCRefreshIndication` (`_ZN7rildata10DataModule26handlePDCRefreshIndicationENSt3__110shared_ptrI7MessageEE`).
|
||||
|
||||
This is the modem telling userspace "I've applied the new configs." QCRIL listens for it but
|
||||
does NOT depend on it to proceed (it's a notification, not a gate).
|
||||
|
||||
#### The enable/disable modem update mechanism
|
||||
|
||||
Symbols `qcril_qmi_pdc_enable_modem_update`, `qcril_qmi_pdc_disable_modem_update`,
|
||||
`pdc_enable_auto_selection` indicate that before loading configs, QCRIL may call
|
||||
`QMI_PDC_SET_FEATURE_VERSION` or a similar PDC command to enable/lock auto-selection on the
|
||||
modem side. `qcril_qmi_pdc_is_modem_mbn_updated` checks if the modem already has the desired
|
||||
config loaded (query path, not load path). **The exact QMI message ID for enable/disable is not
|
||||
visible from strings alone** — this is an under-documented PDC message not exposed via libqmi
|
||||
public API. Evidence is ambiguous; mark as uncertain.
|
||||
|
||||
### B.6 EFS Interaction
|
||||
|
||||
Minimal and indirect. QCRIL itself does NOT write to EFS for config provisioning. The evidence:
|
||||
|
||||
- `current MBNs in modem EFS:` (string in libril-qc-hal-qmi.so) — QCRIL can enumerate what's
|
||||
in modem EFS via a PDC query command (list configs), but this is a read.
|
||||
- Android forensics (PROGRESS.md 2026-06-19): `rmt_storage` serves EFS read-write; the modem
|
||||
writes ~2 MB to `modem_fs2` AFTER going online. This EFS write happens INSIDE the modem
|
||||
firmware after RF-init completes — not driven by QCRIL.
|
||||
- The `EFS lead at PAF/modem.md L38` is marked DEAD: rmtfs writable + Android-primed EFS still
|
||||
gave DeviceNotReady. EFS content is not what's missing.
|
||||
|
||||
**Conclusion:** QCRIL does not write EFS for PDC provisioning. It loads configs into modem RAM
|
||||
via QMI PDC, the modem applies them at RF-init and then writes its own EFS state. Our provisioner
|
||||
does not need to write EFS.
|
||||
|
||||
### B.7 The OTA Update Path (not needed for us)
|
||||
|
||||
`qcril_mbn_kick_ota_update_in_dedicated_thread` handles over-the-air config updates (carrier
|
||||
pushing new MBNs). This runs in a dedicated thread, separate from boot provisioning. Not
|
||||
relevant to our bring-up.
|
||||
|
||||
### B.8 Properties Used by QCRIL
|
||||
|
||||
| Property | Purpose |
|
||||
|---|---|
|
||||
| `persist.vendor.radio.hw_mbn_loaded` | Cached: HW MBN was loaded (skip on next boot if version matches) |
|
||||
| `persist.vendor.radio.sw_mbn_loaded` | Cached: SW MBN was loaded |
|
||||
| `persist.vendor.radio.mbn%d` | Per-slot carrier index (e.g., `mbn0=72`) |
|
||||
| `persist.vendor.radio.mbn_path` | Path to the active MBN directory |
|
||||
| `persist.vendor.radio.cnv.ver_info` | Cached version info string (prev vs current comparison) |
|
||||
| `persist.vendor.radio.ver_info` | Version info |
|
||||
| `persist.vendor.radio.sw_mbn_update` | SW update enabled/disabled flag |
|
||||
| `persist.vendor.radio.hw_mbn_update` | HW update enabled/disabled flag |
|
||||
| `persist.vendor.radio.sw_mbn_openmkt` | Open-market SIM handling flag |
|
||||
| `persist.vendor.radio.long_iin_mbn` | Use long-IIN (9-digit) matching for SW MBN |
|
||||
|
||||
These are Android system properties not directly available on pmOS. Our provisioner should
|
||||
implement equivalent state tracking in a config file (e.g., `/var/lib/qcril-prov/state.json`).
|
||||
|
||||
---
|
||||
|
||||
## C. The Gap — Itemized
|
||||
|
||||
Each item marked: **(RF-arm)** = required to arm RF at all, **(carrier)** = carrier-correctness,
|
||||
**(nice)** = quality improvement.
|
||||
|
||||
| # | Gap | Severity |
|
||||
|---|---|---|
|
||||
| 1 | **Timing: provisioner must run before RF-init window closes** — our scripts run manually; QCRIL runs automatically on DMS-service-ready event, before `sys.boot_completed`. A systemd service that fires before ModemManager and monitors DMS readiness is absent. | **(RF-arm)** |
|
||||
| 2 | **HW MBN must be loaded before SW MBN** — our scripts don't enforce ordering. QCRIL runs the full HW phase (load+select+activate → modem restart) before starting SW phase. Interleaving or reversing causes modem inconsistency. | **(RF-arm)** |
|
||||
| 3 | **Post-activate modem restart handling** — on `QMI_PDC_ACTIVATE_CONFIG` the modem subsystem restarts. QCRIL re-waits for DMS to become ready after each restart before proceeding to the next phase (SW after HW). Our script ignores the restart and the QMI transport drop. | **(RF-arm)** |
|
||||
| 4 | **SW MBN carrier selection by ICCID/MCC-MNC** — we hardcode the Rogers MBN path. QCRIL reads ICCID from `/data/vendor/radio/iccid` and queries UIM MCC/MNC, then does a DB lookup. Without this, a SIM swap would need a manual provisioner edit. | **(carrier)** |
|
||||
| 5 | **Stale config cleanup before loading** — QCRIL calls `qcril_mbn_cleanup_inactive_configs` / `qcril_mbn_pdc_delete_all_sw_configs` to delete old configs before loading new ones. Without cleanup, the modem accumulates stale RAM-configs and may hit internal limits or pick the wrong active config. | **(RF-arm / carrier)** |
|
||||
| 6 | **SW pending config check** — QCRIL calls `QMI_PDC_QUERY_PENDING_CONFIG` (event `REQUEST_MBN_SW_QUERY_PENDING_CONFIG`) before loading to see if the desired config is already pending. Missing this means redundant loads, not a breakage, but it is part of the correct sequence. | **(nice)** |
|
||||
| 7 | **Version skip logic** — QCRIL skips reload when modem already holds matching config version. Without this, we reload every boot unconditionally (harmless functionally but slow and causes an extra modem restart). | **(nice)** |
|
||||
| 8 | **PDC modem-update enable/disable** — QCRIL may call a PDC feature-version command around the load. The exact behavior is unclear from strings alone (ambiguous evidence). May be needed to unlock PDC loading on certain modem states. **UNCERTAIN — needs disassembly to confirm.** | **(RF-arm, uncertain)** |
|
||||
| 9 | **SIM-change re-provisioning** — QCRIL monitors UIM refresh indications and re-runs SW provisioning on SIM swap. Not needed for single-boot bring-up but needed for production use. | **(nice)** |
|
||||
| 10 | **HW name extraction from MBN header** — QCRIL reads the `HW_NAME` field from each `mcfg_hw.mbn` binary to populate the DB, then matches it against the running platform. We can hardcode SDM845/LA/SS for blueline, deferring this. | **(nice for generality)** |
|
||||
|
||||
---
|
||||
|
||||
## D. Design — Full Provisioner
|
||||
|
||||
### D.0 Guiding Constraints
|
||||
|
||||
- Must complete (both HW and SW activated) before the modem's RF-init window closes. From
|
||||
PROGRESS.md: PDC LoadConfig times out if attempted before ~45 s uptime; succeeds by ~226 s.
|
||||
The RF-init window is within the modem's post-MPSS-up initialization, which is between ~12 s
|
||||
and some unknown deadline (likely within 60 s of MPSS-up). **The critical open question:**
|
||||
does the modem wait indefinitely for a config or does it time out and RF-init with whatever
|
||||
is loaded? This must be determined empirically (see Next Steps).
|
||||
- Run as a systemd service, ordered before ModemManager, after QRTR/QMI is up.
|
||||
- Read-only EFS — no EFS writes needed from our side.
|
||||
- Python (libqmi GI) to reuse pdc_load.py/pdc_activate.py primitives.
|
||||
|
||||
### D.1 Service Unit
|
||||
|
||||
File: `/etc/systemd/system/qcril-prov.service`
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=QMI PDC MBN provisioner (QCRIL equivalent)
|
||||
After=qrtr.service rmtfs.service
|
||||
Before=ModemManager.service
|
||||
Wants=qrtr.service rmtfs.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/local/bin/qcril-prov.py
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
### D.2 Provisioner Script Design (`qcril-prov.py`)
|
||||
|
||||
#### Stage 0: Wait for DMS
|
||||
|
||||
Poll `qmicli -d qrtr://0 --dms-get-ids` until it returns IMEI without error. Timeout 120 s.
|
||||
This replicates QCRIL's `DmsModule::handleDmsEndpointStatusIndMessage` trigger. Retry interval:
|
||||
2 s. Add a jitter-free deadline check (not a sleep loop — use subprocess with timeout).
|
||||
|
||||
EVIDENCE: string `"DMS client is ready. start MBN update"` from DmsModule.
|
||||
|
||||
#### Stage 1: Read SIM Identity (for SW MBN selection)
|
||||
|
||||
Read ICCID and MCC/MNC before touching PDC, as QCRIL does.
|
||||
|
||||
- Option A (simple): read `/data/vendor/radio/iccid` (path string in libril-qc-hal-qmi.so).
|
||||
On pmOS this file may not be populated by anything. Fallback to QMI.
|
||||
- Option B (robust): `qmicli --uim-get-card-status` to get ICCID, then
|
||||
`qmicli --nas-get-home-network` or `--uim-get-imsi` to get MCC/MNC.
|
||||
|
||||
For Rogers/Fido: MCC=302, MNC=480 (Rogers) or 302-370 (Fido).
|
||||
The mbn_sw.txt has `mcfg_sw/generic/NA/Rogers/Commercial/CA/mcfg_sw.mbn` — this is the file
|
||||
to use. Fido uses Rogers infrastructure (same MCC prefix 302); whether the DB lookup maps Fido's
|
||||
IIN/MNC to the Rogers MBN depends on what was in the runtime-built DB on Android. Empirically,
|
||||
the modem ran Fido LTE on Android with the Rogers MBN active, so Rogers/CA is the correct SW
|
||||
MBN for both carriers on this device. Hardcode Rogers for now; add MCC/MNC dispatch later.
|
||||
|
||||
#### Stage 2: Delete All Existing Configs
|
||||
|
||||
Replicate `qcril_mbn_cleanup_inactive_configs` / `qcril_mbn_pdc_delete_all_sw_configs`.
|
||||
|
||||
Use `QMI_PDC_LIST_CONFIGS` (both types) → for each config ID returned, call
|
||||
`QMI_PDC_DELETE_CONFIG`. This clears the modem's RAM before we load fresh.
|
||||
|
||||
EVIDENCE: event `QMI_RIL_PDC_DELETE_ALL` (`qcril_evt_id_QMI_RIL_PDC_DELETE_ALL`) and
|
||||
`qcril_mbn_pdc_delete_all_sw_configs`.
|
||||
|
||||
Implementation via libqmi GI: `Qmi.MessagePdcDeleteConfigInput.new()`, set type and config_id,
|
||||
call `client.delete_config()`. Loop over both PLATFORM and SOFTWARE types.
|
||||
|
||||
**Why needed:** avoids stale config accumulation and ensures we control what's active. On cold
|
||||
boot the list is 0/0 (PROGRESS.md 2026-06-20), so this is a no-op on cold boot but defensive
|
||||
for warm SSR.
|
||||
|
||||
#### Stage 3: Load + Select + Activate HW MBN
|
||||
|
||||
MBN file: `/mnt/vendor/persist/rfs/readonly/vendor/mbn/mcfg_hw/generic/common/SDM845/LA/SS/mcfg_hw.mbn`
|
||||
(56 KB, confirmed in PAF/modem.md)
|
||||
|
||||
1. **Load:** use `pdc_load.py` logic (already working) with type=PLATFORM.
|
||||
2. **Get config ID:** after load completes, call `QMI_PDC_LIST_CONFIGS` type=PLATFORM to find
|
||||
the ID of the just-loaded config. (The LoadConfig indication does not return the ID;
|
||||
ListConfigs does.)
|
||||
3. **Select:** `QMI_PDC_SET_SELECTED_CONFIG` type=PLATFORM, id=<returned ID>.
|
||||
4. **Activate:** `QMI_PDC_ACTIVATE_CONFIG` type=PLATFORM. Modem MPSS restarts.
|
||||
|
||||
EVIDENCE: full symbol chain `qcril_mbn_hw_load_config_hndlr` → `qcril_mbn_hw_select_config_hndlr`
|
||||
→ `qcril_mbn_hw_activate_config_hndlr` in libril-qc-hal-qmi.so nm output.
|
||||
|
||||
#### Stage 4: Wait for Modem to Come Back After HW Restart
|
||||
|
||||
After HW activation, the modem MPSS restarts. QCRIL detects this via QMI transport drop +
|
||||
re-registration of DMS service. Poll same as Stage 0. Timeout 60 s.
|
||||
|
||||
Without this wait, the SW load in Stage 5 will fail (PDC client not available / modem not yet
|
||||
QMI-ready).
|
||||
|
||||
EVIDENCE: pdc_activate.py comment: "config activation is expected to reboot the device"; modem
|
||||
restart events `QCRIL_EVT_QMI_RIL_MODEM_RESTART_*`; PROGRESS.md 2026-06-19 "qmicli
|
||||
--pdc-activate-config=platform triggered modem restart that cleared the platform config."
|
||||
|
||||
#### Stage 5: Load + Select + Activate SW MBN
|
||||
|
||||
MBN file (Rogers/CA): `/mnt/vendor/persist/rfs/readonly/vendor/mbn/mcfg_sw/generic/NA/Rogers/Commercial/CA/mcfg_sw.mbn`
|
||||
(MCC=302 carrier confirmed active on Android in PROGRESS.md 2026-06-19)
|
||||
|
||||
Same sequence as Stage 3 but type=SOFTWARE.
|
||||
|
||||
1. Load (chunked, type=SOFTWARE)
|
||||
2. List configs → get ID
|
||||
3. SetSelectedConfig (type=SOFTWARE)
|
||||
4. ActivateConfig (type=SOFTWARE) → second modem restart
|
||||
|
||||
EVIDENCE: `qcril_mbn_sw_load_config_hndlr` → `qcril_mbn_sw_select_config_hndlr` →
|
||||
`qcril_mbn_sw_activate_config_hndlr` in nm output.
|
||||
|
||||
#### Stage 6: Wait and Verify
|
||||
|
||||
After SW activation, wait for DMS to come back (Stage 0 again). Then run the modem_health.sh
|
||||
check logic inline: call `QMI_DMS_GET_CAPABILITIES` and verify `Networks` is non-empty.
|
||||
|
||||
If `Networks` is non-empty, provisioning succeeded. Set a state file
|
||||
(`/var/lib/qcril-prov/last_provisioned`) and exit 0 so systemd marks the service complete.
|
||||
|
||||
If `Networks` is still empty: log a diagnostic (PDC list state, DMS caps) and exit non-zero.
|
||||
Systemd will not start ModemManager (it's ordered After this service).
|
||||
|
||||
#### Stage 7: Hand Off to ModemManager
|
||||
|
||||
With both configs active and RF armed (Networks populated), ModemManager's
|
||||
`Set Operating Mode=online` should succeed. ModemManager starts as normal.
|
||||
|
||||
### D.3 SW MBN Selection Logic (future)
|
||||
|
||||
When we want auto-carrier selection (beyond hardcoded Rogers):
|
||||
|
||||
```python
|
||||
# Selection priority order (matches QCRIL):
|
||||
# 1. ICCID long-IIN (9 digits) → qcril_sw_mbn_iin_table.MCFG_LONG_IIN
|
||||
# 2. ICCID short-IIN (6 digits) → qcril_sw_mbn_iin_table.MCFG_IIN
|
||||
# 3. MCC+MNC → qcril_sw_mbn_mcc_mnc_table
|
||||
# 4. Wildcard → mcfg_sw/generic/common/WildCard/Wildcard/mcfg_sw.mbn
|
||||
|
||||
def select_sw_mbn(iccid, mcc, mnc, db_path):
|
||||
import sqlite3
|
||||
db = sqlite3.connect(db_path)
|
||||
# Populate DB from MBN tree if empty (QCRIL does this at runtime)
|
||||
# ... (qcril_mbn_sw_load_to_db equivalent)
|
||||
long_iin = iccid[:9] if iccid else None
|
||||
short_iin = iccid[:6] if iccid else None
|
||||
if long_iin:
|
||||
row = db.execute("SELECT FILE FROM qcril_sw_mbn_iin_table WHERE MCFG_LONG_IIN=?",
|
||||
[long_iin]).fetchone()
|
||||
if row: return row[0]
|
||||
if short_iin:
|
||||
row = db.execute("SELECT FILE FROM qcril_sw_mbn_iin_table WHERE MCFG_IIN=?",
|
||||
[short_iin]).fetchone()
|
||||
if row: return row[0]
|
||||
if mcc and mnc:
|
||||
row = db.execute("SELECT FILE FROM qcril_sw_mbn_mcc_mnc_table WHERE MCC=? AND MNC=?",
|
||||
[mcc, mnc]).fetchone()
|
||||
if row: return row[0]
|
||||
return "mcfg_sw/generic/common/WildCard/Wildcard/mcfg_sw.mbn" # fallback
|
||||
```
|
||||
|
||||
**Note:** The `qcril.db` tables will be empty until we populate them. QCRIL populates them by
|
||||
parsing each MBN binary's header (IIN/MCC-MNC metadata embedded in the MCFG blob). We need to
|
||||
implement MBN metadata parsing to extract IIN and MCC-MNC lists from the MBN binary format to
|
||||
build the DB. This is deferred — hardcoded Rogers is correct for this device and SIM.
|
||||
|
||||
### D.4 Solving the Timing Wall
|
||||
|
||||
> **CORRECTION 2026-06-20 (capture analysis — see `PAF/modem.md` frontier #4):** The "load +
|
||||
> activate every boot, accept two restarts" model below is a *reconstruction of the unobserved
|
||||
> first-provision path*, NOT what Android does in steady state. The captured Android radio logs
|
||||
> (`android-live-20260619/deep/`) show steady-state boot is **verify-only**: `is_modem_mbn_updated`
|
||||
> finds `prev_ver == cur_ver` and SKIPS the entire load/select/activate sequence; the modem
|
||||
> self-applies MCFG persisted in its own EFS (`modemst`) at firmware RF-init. The full load path
|
||||
> (and any `pdc_enable_auto_selection` call) runs only on first-provision / version change, which
|
||||
> we have never captured. This section is retained as the design for the per-boot *workaround*
|
||||
> (`tools/qcril-prov.py`, path A); the hardware-faithful target is provision-once + `modemst`
|
||||
> persistence (path B). Resolve via the first-provision capture: `PAF/first_provision_capture.md`.
|
||||
|
||||
**The question:** Does the modem wait indefinitely for configs before RF-init, or does it have
|
||||
a deadline?
|
||||
|
||||
**What we know:** PROGRESS.md 2026-06-20 states "PDC LoadConfig times out if attempted too
|
||||
early (uptime ~45 s, modem not yet PDC-ready) but succeeds by ~226 s." This is the PDC-client
|
||||
readiness window (when QRTR route is established). The RF-init window is a separate question.
|
||||
|
||||
**Hypothesis A:** The modem RF-inits as part of MPSS boot (~12 s) with whatever configs are
|
||||
loaded. On cold pmOS boot there are none (0/0), so RF-init proceeds with no MCFG = no RF arm.
|
||||
Provisioning after the fact cannot retroactively arm RF. The modem must be restarted (via
|
||||
PDC ActivateConfig) after configs are loaded, which triggers a new RF-init pass WITH the
|
||||
configs. This is the mechanism QCRIL uses.
|
||||
|
||||
**This matches all evidence:** PROGRESS.md 2026-06-20 states "A post-init userspace load cannot
|
||||
arm an already-initialized RF, and nothing persists to carry configs to next boot." And:
|
||||
"configs are PURELY VOLATILE, NOT persisted."
|
||||
|
||||
**Implication for our provisioner:** The timing wall is NOT about being early enough — it's
|
||||
about triggering a PDC ActivateConfig (which causes modem restart + re-RF-init with configs
|
||||
loaded). QCRIL's "early" timing is an optimization to reduce the number of restarts (load before
|
||||
first RF-init = one restart; load after first RF-init = two restarts). Our provisioner will
|
||||
cause two MPSS restarts per boot (once for HW, once for SW). This is acceptable for bring-up.
|
||||
|
||||
**The `--pdc-monitor-refresh` question** (PROGRESS.md 2026-06-20 "NEXT"): The modem may emit
|
||||
a PDC refresh indication (`PDCRefreshIndication`, `qcril_qmi_pdc_refresh_ind_hdlr`) after
|
||||
ActivateConfig, signaling completion. We should subscribe to this indication (via libqmi
|
||||
`RegisterForPDCIndication`) to confirm activation before proceeding to the SW phase.
|
||||
EVIDENCE: `"handleRegisterForPDCIndication(): failed with rc="` and
|
||||
`"]: pdc refresh Completed"` strings.
|
||||
|
||||
**Recommended approach for `--pdc-monitor-refresh`:** Add a PDC indication registration at
|
||||
startup and listen for `QMI_PDC_CONFIG_CHANGE_IND` (which is what `pdc_refresh_ind_hdlr`
|
||||
handles). After each ActivateConfig, wait for the refresh-completed indication before polling
|
||||
DMS. Timeout 30 s if indication doesn't arrive (fall through to DMS poll).
|
||||
|
||||
---
|
||||
|
||||
## E. Implementation Sequence (Recommended)
|
||||
|
||||
### Step 0 (validation experiment, before any code): confirm two-restart approach works
|
||||
|
||||
Manual test on device:
|
||||
```sh
|
||||
# Clean slate
|
||||
qmicli -d qrtr://0 --pdc-list-configs=platform # should show 0
|
||||
qmicli -d qrtr://0 --pdc-list-configs=software # should show 0
|
||||
|
||||
# Load + select + activate HW
|
||||
python3 pdc_load.py qrtr://0 platform .../mcfg_hw.mbn
|
||||
# get ID from pdc-list-configs=platform
|
||||
python3 pdc_activate.py qrtr://0 platform <ID>
|
||||
# wait ~30s for modem restart and DMS back
|
||||
|
||||
# Load + select + activate SW (Rogers)
|
||||
python3 pdc_load.py qrtr://0 software .../mcfg_sw.mbn
|
||||
# get ID from pdc-list-configs=software
|
||||
python3 pdc_activate.py qrtr://0 software <ID>
|
||||
# wait ~30s
|
||||
|
||||
# Check RF
|
||||
qmicli -d qrtr://0 --dms-get-capabilities # expect Networks non-empty
|
||||
```
|
||||
|
||||
This is the minimal end-to-end proof before building the daemon.
|
||||
|
||||
### Step 1: `qcril-prov.py` v1 (hardcoded)
|
||||
|
||||
Single script implementing the full D.2 flow with:
|
||||
- Hardcoded HW MBN path (SDM845/LA/SS)
|
||||
- Hardcoded SW MBN path (Rogers/CA)
|
||||
- DMS poll loop (Stage 0)
|
||||
- Delete-all existing configs (Stage 2)
|
||||
- HW load/select/activate (Stage 3)
|
||||
- Wait for modem restart (Stage 4, DMS poll again)
|
||||
- SW load/select/activate (Stage 5)
|
||||
- Verify Networks non-empty (Stage 6)
|
||||
|
||||
No carrier selection DB needed yet.
|
||||
|
||||
### Step 2: systemd integration
|
||||
|
||||
Wire the service unit (D.1), enable it, reboot, confirm RF arms without manual intervention.
|
||||
|
||||
### Step 3: add carrier selection (future, when needed for other SIMs)
|
||||
|
||||
Implement MBN binary parser for IIN/MCC-MNC metadata, populate a local sqlite3 DB, implement
|
||||
`select_sw_mbn()` (D.3).
|
||||
|
||||
---
|
||||
|
||||
## F. Open Questions (evidence-bounded)
|
||||
|
||||
1. **Does the modem accept ActivateConfig before DMS is "fully" ready?** We know DMS must be
|
||||
up for QCRIL to start — but does DMS-up precede or follow the RF-init window? Unknown without
|
||||
`--pdc-monitor-refresh` experiment or timing capture.
|
||||
|
||||
2. **What is `pdc_enable_auto_selection(ii)` / `qcril_qmi_pdc_enable_modem_update`?** The
|
||||
symbol `_Z25pdc_enable_auto_selectionii` exists in the binary. It may correspond to
|
||||
`QMI_PDC_SET_FEATURE_VERSION` or a similar private PDC message that tells the modem to
|
||||
accept auto-selected configs. If this is required, LoadConfig will succeed but ActivateConfig
|
||||
may no-op. **Needs disassembly of `qcril_mbn_hw_update_init` to determine if it's called
|
||||
before the load sequence.** This is the only remaining significant unknown.
|
||||
|
||||
3. **Does the Fido SIM (MVNO on Rogers) match the Rogers/CA MBN via MCC-MNC or IIN?** Fido's
|
||||
MCC-MNC is 302-370; Rogers is 302-720. The Rogers MBN was active on Android with this Fido
|
||||
SIM. So Rogers/CA is confirmed correct. The IIN or MCC-MNC lookup that produced this result
|
||||
is unknown without the runtime-populated DB — but the outcome is known.
|
||||
|
||||
4. **Is the HW MBN name match on "SDM845-LA-SS" or another string?** `qcril_mbn_hw_get_hw_name_to_look_for`
|
||||
returns the string used to match HW configs. The actual string is not visible without
|
||||
disassembly. For blueline, only one SDM845/LA/SS entry exists in mbn_hw.txt; even if our
|
||||
provisioner gets the name wrong, we can hardcode the file path.
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `android-reference/qcril-libs/libril-qc-hal-qmi.so` | Primary evidence: symbols (nm -D), strings |
|
||||
| `android-reference/vendor-mbn/mbn/mbn_hw.txt` | HW MBN index (2 entries for SDM845) |
|
||||
| `android-reference/vendor-mbn/mbn/mbn_sw.txt` | SW MBN index (55 entries, includes Rogers/CA) |
|
||||
| `android-reference/vendor-mbn/mbn/mcfg.version` | MCFG version: `g845-00194-220517-B-8604334` |
|
||||
| `android-reference/qcril-config/qcril.db` | DB schema; tables empty in prebuilt (QCRIL populates at runtime) |
|
||||
| `android-reference/init-rc/qcrild.rc` | `class main`, confirms qcrild timing |
|
||||
| `android-reference/init-rc/init.sdm845.rc` | Boot ordering; `rmt_storage` = `class core` (earlier than main) |
|
||||
| `PAF/modem.md` | Eliminated leads; confirmed facts about PDC volatility and EFS |
|
||||
| `PROGRESS.md` (2026-06-20 entry) | Decisive timing proof; QCRIL symbol inventory |
|
||||
| `tools/pdc_load.py`, `tools/pdc_activate.py` | Our current implementation baseline |
|
||||
229
saf/device/evidence/qcril_online_sequence.md
Normal file
229
saf/device/evidence/qcril_online_sequence.md
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# QCRIL pre-online QMI choreography — extracted from libril-qc-hal-qmi.so
|
||||
|
||||
Binary: `android-reference/vendor-stack-20260621/vendor/lib64/libril-qc-hal-qmi.so`
|
||||
(ARM64, stripped of locals but **full dynamic symbol table present** — 22045 syms, qcril
|
||||
function names intact). Decompiler unavailable (no rz-ghidra / pdg); extraction is from
|
||||
disassembly of named functions + QMI message-id immediates + the log-string call flow +
|
||||
the working-Android logs in `android-capture-20260621/diag/`.
|
||||
|
||||
All addresses are file offsets into the `.so` `.text`. Disassembly cited inline.
|
||||
|
||||
---
|
||||
|
||||
## 0. What the binary settles (resolves the doc contradiction)
|
||||
|
||||
`PAF/qcril_bringup_capture.md` ends with a REFRAME claiming "the Android modem self-onlines;
|
||||
there is no QCRIL online handshake." The binary shows that is **half right**: QCRIL **does**
|
||||
issue DMS Set Operating Mode, but only as the **last step of a gated state machine**
|
||||
(`qcril_qmi_nas_boot_up_optimization_state_machine` @ **0xfe4730**). The thing pmOS skips is
|
||||
not "an online command" — it is the **pre-conditioning QCRIL performs on the modem before it
|
||||
asks it to go online**. That pre-conditioning is the choreography below.
|
||||
|
||||
The "self-online" observation on Android (op-mode already 0 at QCRIL attach) is because on a
|
||||
*warm* / SSR path the modem was already conditioned in a prior boot and the state persisted;
|
||||
the cold first-boot path runs the full state machine. pmOS only ever does the cold bare
|
||||
`dms-set-operating-mode=online` with **none** of the pre-conditioning → DeviceNotReady(52).
|
||||
|
||||
---
|
||||
|
||||
## 1. The bring-up orchestrator — `qcril_qmi_nas_boot_up_optimization_state_machine` @ 0xfe4730
|
||||
|
||||
Disassembled call order (filtered to the load-bearing `bl`s):
|
||||
|
||||
```
|
||||
fe4850 bl qcril_qmi_nas_get_bootup_power_optimization_state
|
||||
fe4880 bl qcril_qmi_nas_decide_mode_pref_to_set_on_modem(&pref) ; 0xfe4c00
|
||||
fe489c bl qmi_ril_nwreg_request_mode_pref(i, &pref) ; 0x10f6f98 <-- NAS mode-pref push
|
||||
fe48cc bl qcril_qmi_nas_save_android_mode_pref(i)
|
||||
fe48d8 bl qcril_qmi_nas_get_atel_ui_ready_req_came_from_cache() ; 0xfe4ec4
|
||||
fe48f0 bl qcril_qmi_nas_set_atel_ui_status_to_modem(h) ; 0xfe5070 <-- DMS UI-ready push
|
||||
fe48f8 bl qcril_qmi_nas_set_atel_ui_status_to_cache(h)
|
||||
fe4904 bl qcril_qmi_nas_set_operating_mode_state(enum) ; 0xfe53c8 <-- DMS Set Operating Mode
|
||||
fe490c bl qcril_qmi_nas_set_bootup_power_optimization_state(2)
|
||||
```
|
||||
|
||||
So the ordered, **load-bearing** pre-online sequence the modem is conditioned with is:
|
||||
|
||||
1. **Decide + push the NAS mode preference (RAT set)** — tell the modem which radio
|
||||
technologies to acquire — BEFORE asking it to power the RF.
|
||||
2. **Push the ATEL "UI ready" status to the modem over DMS** — a Qualcomm OEM DMS message.
|
||||
3. **Only then** issue DMS Set Operating Mode.
|
||||
|
||||
`qcril_qmi_nas_telephony_ready` @ 0x1015a50 carries the log
|
||||
`poweron_opt: 'UI ready' NOT sent to modem` (string confirmed in binary) — i.e. the UI-ready
|
||||
push is conditionally skipped when the modem is already online, which is exactly the
|
||||
"self-online" warm path that misled the earlier reframe.
|
||||
|
||||
---
|
||||
|
||||
## 2. Step detail + QMI message IDs (from the immediates)
|
||||
|
||||
### Step A — NAS mode preference (LOAD-BEARING)
|
||||
`qmi_ril_nwreg_request_mode_pref` @ **0x10f6f98** builds the mode/band/acq-order prefs
|
||||
(`qcril_qmi_nas_get_mode_pref` @0x1da4890, `qcril_qmi_nas_get_gw_acq_order_pref` @0x1da4d50,
|
||||
`qcril_qmi_nas_get_5g_mode_pref` @0x1da3940) and dispatches through the shared sender at
|
||||
`qcril_qmi_nas_request_set_preferred_network_type+0x340` (0x10f6f68 region). That sender
|
||||
issues **NAS Set System Selection Preference** (QMI NAS id **0x0033**, confirmed present in
|
||||
`references/libqmi/data/qmi-service-nas.json` line 1437 "Set System Selection Preference").
|
||||
→ **Replayable via `qmicli --nas-set-system-selection-preference`.**
|
||||
|
||||
### Step B — ATEL UI-ready push (LOAD-BEARING, **NOT replayable with stock tooling**)
|
||||
`qcril_qmi_nas_set_atel_ui_status_to_modem` @ **0xfe5070**:
|
||||
```
|
||||
fe5124 mov w0, #0x4 ; qcril client enum 4 = DMS
|
||||
fe5128 bl qcril_qmi_client_get_user_handle
|
||||
fe512c mov w1, #0x64 ; <-- QMI message id 0x64 (100) on DMS
|
||||
fe5134 mov w3, #0x2 ; req_len = 2 bytes (the UI-ready u8 + TLV hdr)
|
||||
fe5140 mov w5, #0x8 ; resp_len = 8
|
||||
fe5148 movk w6,#0x1,lsl#16 / 0x2110 ; sync timeout
|
||||
fe514c bl qmi_client_send_msg_sync_with_shm
|
||||
```
|
||||
DMS message **0x0064 is a Qualcomm OEM extension** (string
|
||||
`com.qualcomm.qti.qcril.nas.telephony_ui_ready`) and is **not defined in libqmi** (checked
|
||||
`references/libqmi/data/qmi-service-dms.json` — 0x0064 absent; only 0x002E Set Operating Mode
|
||||
etc.). There is **no qmicli flag** for it and **no `Qmi.MessageDms*` GI binding**. Sending it
|
||||
requires a hand-built raw QMI PDU via `qmi_device_command_full`. See gap note in §4.
|
||||
|
||||
### Step C — DMS Set Operating Mode (the step pmOS already does)
|
||||
`qcril_qmi_nas_set_operating_mode_state` @ **0xfe53c8**:
|
||||
```
|
||||
fe547c mov w0, #0x4 ; DMS client
|
||||
fe5484 mov w1, #0x2e ; QMI DMS id 0x2E = Set Operating Mode (libqmi dms.json:498)
|
||||
fe548c req @ x29-0x18, len 0x10
|
||||
fe549c movk ... 0x12110 timeout
|
||||
fe54a4 bl qmi_client_send_msg_sync_with_shm
|
||||
```
|
||||
The op-mode enum value comes in as the function argument (`dms_operating_mode_enum_v01`);
|
||||
`online` = 0. → This is exactly `qmicli --dms-set-operating-mode=online`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Surrounding init context (incidental vs load-bearing)
|
||||
|
||||
`qcril_qmi_nas_init` @ 0xfc7058 also runs, at NAS bring-up (de-mangled call trace):
|
||||
- `qcril_qmi_nas_configure_sig_info2` — signal-strength delta config (incidental to RF arm)
|
||||
- `qcril_qmi_util_enable_networking_indications(int,int,int)` — NAS indication-register
|
||||
(sys-info / serving-system). NAS Indication Register = QMI NAS **0x0003**
|
||||
(`qmi-service-nas.json:395 "Register Indications"`). Replayable, but **incidental** to RF
|
||||
arming — it only controls which *unsolicited* indications the modem pushes back; it does not
|
||||
gate op-mode. Useful so we can *observe* the modem reacting.
|
||||
- `qcril_qmi_nas_register_for_5g_modem_vote_indication`, sig-config — incidental.
|
||||
|
||||
UIM path (separate module, runs from card-status-update, not the op-mode state machine):
|
||||
- `qcril_qmi_uim_power_up` @ 0x11dc650 (MMGSDI card power-up) and
|
||||
`qcril_qmi_uim_change_provisioning_session` @ 0x11e07e0. On Android these establish the
|
||||
USIM session before NAS asks for RF. On pmOS the SIM already reads `ready` and primary-GW
|
||||
is provisioned (per live results), so card power-up is **already satisfied** — not the gap.
|
||||
`uim-change-provisioning-session` *timing out* on pmOS is a **symptom** of the same modem
|
||||
not-ready state, not an independent cause (it answers reads, refuses state changes).
|
||||
|
||||
`qmi_ril_nwreg_post_oprt_online_action_handler` @ 0x104ddd8 runs **after** online succeeds
|
||||
(enables more indications) — not part of the pre-online arm.
|
||||
|
||||
### Load-bearing-for-RF-init, in order (the replay target)
|
||||
1. NAS Set System Selection Preference (mode/RAT pref) — **0x0033**, qmicli-able
|
||||
2. DMS ATEL UI-ready push — **DMS 0x0064**, OEM, *not* qmicli-able
|
||||
3. DMS Set Operating Mode = online — **0x002E**, qmicli-able
|
||||
|
||||
### Incidental (safe to include for observability, not expected to gate)
|
||||
- NAS Register Indications (0x0003), sig-info config
|
||||
- UIM card power-up / provisioning (already satisfied on pmOS)
|
||||
|
||||
---
|
||||
|
||||
## 4. Precise gap (what the binary requires that stock pmOS tooling can't send)
|
||||
|
||||
**DMS message 0x0064 ("telephony UI ready") is the one pre-online step QCRIL performs that
|
||||
neither qmicli nor libqmi-GI exposes.** It is a 2-byte request on the DMS service. We can:
|
||||
(a) replay steps 1 + 3 with qmicli now (cheap, may be sufficient if the mode-pref is the real
|
||||
missing precondition), and
|
||||
(b) if 1+3 still give 52, send 0x0064 as a raw QMI PDU via `Qmi.Device.command_full` (the TLV
|
||||
layout is a single mandatory u8 = 1; we have the message id and req_len=2 from the binary,
|
||||
which is `[type=0x01][len=0x01 0x00][val=0x01]` = 4 bytes payload after the QMI msg header
|
||||
— note binary's "req_len 2" counts the c-struct, libqmi will frame the TLV).
|
||||
|
||||
The provisioner (`tools/modem-online-provisioner/`) implements (a) as the primary path and
|
||||
documents (b) as the staged escalation, so we spend device boots in the right order.
|
||||
|
||||
**UPDATE 2026-07-01 — (b) is now built AND run on hardware. Result: real response, still 52.**
|
||||
`device-google-blueline/dms-ui-ready.py` sends the raw 0x0064 PDU using libqmi's own untyped
|
||||
raw-message primitives (`qmi_message_new` + `qmi_message_tlv_write_init/guint8/complete` +
|
||||
`qmi_device_command_full`). **First attempt (Python/GI) failed immediately**: `Qmi.Message` is
|
||||
NOT exposed via GObject-Introspection in this build — confirmed live (`AttributeError:
|
||||
'gi.repository.Qmi' object has no attribute 'Message'`) and by enumerating the actual typelib
|
||||
(only per-message typed classes exist, e.g. `MessageDmsGetOperatingModeOutput`; no generic
|
||||
`Message`/raw-`Command` type). The doc-comment annotations on `qmi_message_new`/
|
||||
`qmi_message_tlv_write_*` in the C headers do NOT mean GI exposes them — they don't here.
|
||||
|
||||
**Second attempt (C, direct against libqmi's real C API) worked and got a genuine protocol
|
||||
response.** Cross-compiled a small standalone binary (`pmbootstrap chroot -b aarch64`,
|
||||
`libqmi-dev` 1.39.0 — the openimsd fork, matching the phone's installed version) linking
|
||||
directly against `libqmi-glib.so.5` already on the phone; pushed and ran it live:
|
||||
|
||||
```
|
||||
dms-ui-ready: sending DMS 0x0064 (telephony UI ready=1)
|
||||
dms-ui-ready: response: is_response=1 message_id=0x0064 length=20
|
||||
dms-ui-ready: RESULT: FAILURE (result=1 error=58)
|
||||
```
|
||||
|
||||
The modem answered `0x0064` as a real, understood message (not "unsupported message" / not a
|
||||
transport reject) — this alone is new information: the OEM message ID and TLV framing are
|
||||
correct. It returned a structured FAILURE with error code 58. Standard libqmi enum 58 is
|
||||
`QMI_PROTOCOL_ERROR_WMS_ENCODING` (an SMS-service error) which cannot be its real meaning here —
|
||||
this is a Qualcomm OEM message, so its error-code namespace is very likely private/vendor-defined
|
||||
and just numerically reuses slot 58; not confidently decodable from public libqmi headers.
|
||||
Retried `mmcli --enable` immediately after (on both the pre-existing and, after ModemManager's
|
||||
own transport reprobe, a freshly re-created modem object): **`DeviceNotReady` (52), byte-identical
|
||||
to every prior attempt all night, with or without this message.** Sending it also correlated with
|
||||
a transient QMI transport hiccup (ModemManager's own connection self-recovered via reprobe within
|
||||
~1s; no lasting harm, but note for next time: release the DMS client / close the device cleanly
|
||||
before exit, which this quick test skipped).
|
||||
|
||||
**Verdict:** DMS 0x0064 is real, reachable, and rejects with a specific (undecoded) error — but
|
||||
does not clear the 52 wall by itself. Either the TLV value/precondition isn't exactly right, or
|
||||
this step genuinely isn't sufficient alone and step 1 (NAS mode-preference, `qmicli
|
||||
--nas-set-system-selection-preference`, previously observed to fail with `DeviceUnsupported`
|
||||
pre-RF) needs to succeed first, or the real gate is elsewhere entirely (EFS NV item, bootloader/
|
||||
ABL verified-boot state — see `los-mss-translated.c`'s remaining two candidates). Kept wired
|
||||
in the boot chain (best-effort, harmless) since it's now proven to do something real, just not
|
||||
(yet, alone) the fix.
|
||||
|
||||
**UPDATE 2026-07-02 — full ordered trilogy executed on hardware. Candidate #2 CLOSED.**
|
||||
Run conditions: ModemManager stopped, fresh mss restart, first commands into the new
|
||||
firmware instance, QCRIL's exact order. Results:
|
||||
1. NAS 0x0033 (mode-pref) → DeviceUnsupported(25), identical to every out-of-order
|
||||
attempt. Order does not rescue it. But `--nas-get-system-selection-preference`
|
||||
succeeds and shows the stored pref is ALREADY complete (all RATs, full band lists,
|
||||
persisted from Android) — step 1's content is pre-satisfied; the push is refused
|
||||
AND redundant.
|
||||
2. DMS 0x0064 (UI-ready) → delivered, NoEffect(26), as always.
|
||||
3. DMS 0x002E online → DeviceNotReady(52), byte-identical.
|
||||
Same night context: rfs file serving fully fixed (ota_firewall/ruleset + modem_efs;
|
||||
XPU-violation boot crashes eliminated), zero TQFTP rejects, clean crash-free modem
|
||||
boot. The pre-online conditioning theory is now closed in ALL variants: the modem is
|
||||
conditioned, fed, and healthy, answers every read, refuses every state change.
|
||||
Remaining candidates: EFS NV item (#3), ABL/bootloader-passed state (#5), with the
|
||||
TZ/SFS secure-storage identity path (test IMEI 990012001581294) as the concrete
|
||||
mechanism consistent with both. Next experiment queued: QSEECOM listeners started
|
||||
pre-modem at cold boot (registration proven; receive/resume relay still unexercised).
|
||||
|
||||
**UPDATE 2026-07-03 — DMS 0x0064 NOT_READY-then-READY handshake tested live. FALSIFIED.**
|
||||
Real vendor QCRIL source (leaked SDM670/710 BSP,
|
||||
`qcril-hal/modules/nas/src/qcril_qmi_nas.cpp`, boot-triggered-ONLINE branch of the
|
||||
unsolicited operating-mode indication handler) shows the real sequence sends
|
||||
`ui_status=FALSE` (NOT_READY) first, then later `TRUE` (READY) — our prior test
|
||||
(2026-07-01) only ever sent READY standalone. Built a corrected C tool
|
||||
(`tools/modem-online-provisioner/dms-ui-ready-sequence.c`, cross-compiled via
|
||||
`pmbootstrap chroot -b aarch64` against a sysroot pulled live from the phone's own
|
||||
`/usr/include/libqmi-glib` + `.so`, since neither `glib-dev` nor `libqmi-glib-dev`
|
||||
are in the buildroot's own repo index) and ran the full corrected sequence live on
|
||||
pmOS: baseline `offline` confirmed, `0x0064(0)` sent → `result=1 error=58`,
|
||||
`0x0064(1)` sent 1s later → `result=1 error=58` (identical failure, order didn't
|
||||
matter), final `dms-get-operating-mode` read → still `offline`. Both values fail
|
||||
identically — this rules out "wrong value" or "wrong order" as the cause of error
|
||||
58. Error 58 is very likely a hard precondition-not-met rejection independent of
|
||||
what value is sent, not a sequencing bug. Do not re-attempt this specific
|
||||
NOT_READY/READY ordering fix without new information; it's closed. Tool kept in
|
||||
`tools/modem-online-provisioner/` for reference (working example of raw libqmi
|
||||
C message construction against DMS OEM messages, useful if a future OEM message
|
||||
needs the same treatment).
|
||||
262
saf/device/evidence/qrtr_tools_reference.md
Normal file
262
saf/device/evidence/qrtr_tools_reference.md
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
# PAF · QRTR tools reference — what we have, what each does, what's untested
|
||||
|
||||
Written 2026-07-01 because we'd been using `qrtr-lookup` all session without ever checking
|
||||
whether it was the only tool available, or what its full capability actually is. It wasn't —
|
||||
`qrtr-cfg` exists too and had never been touched. This file exists so that doesn't happen again.
|
||||
|
||||
## Package: `qrtr-1.2-r1` (Alpine/pmOS)
|
||||
|
||||
Ships exactly two binaries. Confirmed via `apk info -L qrtr`:
|
||||
|
||||
```
|
||||
usr/bin/qrtr-cfg
|
||||
usr/bin/qrtr-lookup
|
||||
```
|
||||
|
||||
Both come from the upstream `qrtr-utils` project (Qualcomm's own AF_QIPCRTR userspace
|
||||
tools). There is no `qrtr-ns` (nameserver) package installed separately — check whether one
|
||||
is needed; if the kernel-side QRTR nameserver is absent, service registration/lookup could
|
||||
be running in a degraded mode we haven't checked for.
|
||||
|
||||
## `qrtr-lookup` — service registry dump
|
||||
|
||||
**Usage:** `qrtr-lookup [<service> [<instance> [<filter>]]]`
|
||||
|
||||
Run with no arguments, dumps the full QRTR service registry: every registered
|
||||
`{service, version, instance, node, port}` tuple currently known to the local nameserver,
|
||||
with a decoded service name where the local database has one (`<unknown>` otherwise).
|
||||
|
||||
**Untested this session:** the `<service>` / `<instance>` / `<filter>` positional
|
||||
arguments. We only ever ran it bare. Querying a **specific** service number directly
|
||||
(e.g. `qrtr-lookup 24` for PDC) may return additional detail the bare dump doesn't show,
|
||||
or may support a watch/blocking mode for a single service — not yet checked. Worth trying
|
||||
before assuming the bare dump is the whole picture.
|
||||
|
||||
### Full service table observed this session (node 0 = modem edge, node 5/10 = other DSPs)
|
||||
|
||||
| Service ID | Name (as decoded by qrtr-lookup) | Notes |
|
||||
|---|---|---|
|
||||
| 66 | Service registry notification service | meta |
|
||||
| 43 | Subsystem control service | SSR |
|
||||
| 15 | Test service | — |
|
||||
| 51 | CoreSight remote tracing service | debug/trace, multiple instances across DSPs |
|
||||
| 21 | Modem embedded file system service | EFS — **never directly queried this session** |
|
||||
| 24 | Thermal mitigation device service | — |
|
||||
| 23 | Thermal sensors service | — |
|
||||
| 22 | Time service | used by `blueline-timed.sh`/`modem-time-set.py` (base=2/ATS_USER only) |
|
||||
| 36 | **Persistent device configuration service (PDC)** | the config/carrier-selection service — today's main thread |
|
||||
| 49 | IPA control service | data-plane |
|
||||
| 17 | Specific absorption rate service (SAR) | — |
|
||||
| 34 | Coexistence service | — |
|
||||
| 231 | Vendor-specific service | **never queried — unknown OEM extension, worth probing** |
|
||||
| 55 | QMI-SLIM service | — |
|
||||
| 9 | Voice service | — |
|
||||
| 3 | Network Access Service (NAS) | RAT/mode-pref, serving-system |
|
||||
| 12 | Phonebook Management service | — |
|
||||
| 10 | Card Application Toolkit service (v2) | UIM/SIM toolkit |
|
||||
| 47 | Data Port Mapper service | — |
|
||||
| 11 | User Identity Module service (UIM) | SIM reads — this is what confirms ICCID/IMSI, no RF needed |
|
||||
| 1 | Wireless Data Service (WDS) | — |
|
||||
| 4 | Quality Of Service service | — |
|
||||
| 26 | Wireless data administrative service | — |
|
||||
| 7 | Authentication service | — |
|
||||
| 8 | AT service | **never queried — a raw AT-command channel may exist here, unexplored** |
|
||||
| 29 | Circuit switched videotelephony service | — |
|
||||
| 71 | UIMHTTP service | — |
|
||||
| 5 | Wireless Messaging Service (SMS) | — |
|
||||
| 42 | Data system determination service | — |
|
||||
| 2 | Device Management Service (DMS) | operating-mode, capabilities, IDs |
|
||||
| 48 | QMI DFS service | — |
|
||||
| 16 | Location service (~PDS v2) | GNSS |
|
||||
| 69 | ATH10k WLAN firmware service | WiFi, separate from modem entirely |
|
||||
| 77 | IMS QMI Priv service | — |
|
||||
| 57 | WLPS service | — |
|
||||
| 14 | Remote file system service | rmtfs — this is what actually serves modemst1/2 |
|
||||
| 64 | Service registry locator service | meta |
|
||||
| 770 | IMS data service | — |
|
||||
| 4096 | TFTP | tqftpserv |
|
||||
| 769 | SLIMbus control service | audio |
|
||||
| 4098/4099 | `<unknown>` | **never decoded — no name in qrtr-lookup's local DB, worth checking libqmi's `qmi-service-*.json` files for these numeric IDs directly instead of relying on qrtr-lookup's built-in names** |
|
||||
|
||||
**Services we have NEVER directly queried this whole session, despite them being live and
|
||||
registered:** Modem EFS service (21), Vendor-specific service (231), AT service (8),
|
||||
CoreSight remote tracing (51). Any of these could carry signal about the RF-init decision
|
||||
we haven't looked at. AT service (8) in particular is notable — a raw AT-command interface
|
||||
bypassing QMI entirely is a completely different instrument than anything tried today.
|
||||
|
||||
## `qrtr-cfg` — node ID configuration
|
||||
|
||||
**Usage:** `qrtr-cfg <node-id>`
|
||||
|
||||
Attempts to set this machine's own local QRTR node ID via the `SIOCSIFADDR` ioctl on an
|
||||
`AF_QIPCRTR` socket. **Confirmed non-functional on this kernel build**, tested live
|
||||
2026-07-01: `qrtr-cfg 0` → `ERROR qrtr-cfg: failed to configure node id: Not supported`.
|
||||
|
||||
Root cause, confirmed by reading the kernel driver directly
|
||||
(`references/linux-7.1.1-sdm845-CANONICAL/net/qrtr/af_qrtr.c`, ioctl handler ~line 1140+):
|
||||
only `SIOCGIFADDR` (get local address) is implemented. `SIOCSIFADDR` (set) is not handled —
|
||||
falls through to the unsupported/default case. The local node ID
|
||||
(`qrtr_local_nid`, default `1`, `af_qrtr.c:102`) is fixed at this value on this kernel;
|
||||
mainline's `qrtr` core doesn't currently expose a way to change it at runtime on this
|
||||
build. Not a bug we introduced — this is upstream kernel behavior on this SDM845 mainline
|
||||
tree. `qrtr-cfg` is effectively dead weight on this specific system; don't spend more time
|
||||
on it unless the kernel driver changes.
|
||||
|
||||
## Update 2026-07-01 evening — services 8/21/231 confirmed LIVE, real RE progress
|
||||
|
||||
Built `tools/probe-qmi-service.py`-equivalent (raw AF_QIPCRTR socket, `NEW_LOOKUP`, then a
|
||||
raw QMI message send — same mechanism as `modem-time-set.py`, no risky glink channel
|
||||
creation needed since these are QMI services multiplexed over the existing IPCRTR
|
||||
transport, not separate glink channels). Result: **all three respond**, not silent:
|
||||
|
||||
| Service | qrtr node/port | Result |
|
||||
|---|---|---|
|
||||
| AT service (8) | 0 / 58 | responded (rejected a CTL-service message sent to the wrong dispatch — see below, not a dead port) |
|
||||
| Vendor-specific (231) | 0 / 43 | responded (same rejection pattern) |
|
||||
| Modem EFS service (21) | 0 / 24 | responded (same rejection pattern) |
|
||||
|
||||
**Why the response was a rejection, and why that's not a dead end:** the probe sent a
|
||||
`CTL` service message (`Get Client ID`, msg `0x0022`) directly to each service's own port.
|
||||
That's the wrong message for their dispatch tables — `modem-time-set.py` never did a CID
|
||||
handshake either, it sends the real functional message straight to the looked-up port.
|
||||
Next probe needs each service's actual message ID, not a generic CTL probe.
|
||||
|
||||
**AT service (`QMI_SERVICE_AT` = `0x08`) — real progress via modem blob strings.**
|
||||
`libqmi`'s own enum documents this service by name (`qmi-enums.h:50`) but implements zero
|
||||
messages for it (no `qmi-service-at.json`, no `qmicli --at-*` flags) — same gap class as
|
||||
Time (`TS`) before it got RE'd this session. Found real function names in `modem.mbn`'s
|
||||
strings (`strings -n 6 modem.mbn`, file offset ~0x172b653+):
|
||||
|
||||
```
|
||||
ds_qmi_at.c
|
||||
qmi_ati_reset / qmi_ati_reset_client
|
||||
qmi_ati_reg_at_cmd_fwd / qmi_ati_reg_at_cmd_fwd_ex
|
||||
qmi_ati_fwd_at_cmd_cback / qmi_ati_fwd_at_cmd_ind
|
||||
qmi_ati_send_at_repsonse (sic, real modem-firmware typo) / qmi_ati_send_at_urc
|
||||
qmi_ati_process_cmd / qmi_ati_process_cmd_hdlr
|
||||
qmi_ati_process_alloc_clid / qmi_ati_process_dealloc_clid
|
||||
qmi_ati_initialize_client
|
||||
qmi_ati_process_at_client_reg_status / qmi_ati_process_at_cmd_reg_status
|
||||
qmi_ati_send_response
|
||||
qmi_ati_alloc_clid_cb / qmi_ati_dealloc_clid_cb / qmi_ati_cmd_hdlr_cb
|
||||
```
|
||||
|
||||
Confirms `ATI` = "AT Interface", a real, structured, table-dispatched QMI service — not
|
||||
guesswork. Tried to find the exact numeric message IDs via:
|
||||
- **Byte-scan for the string's own virtual address as a literal pointer** (file offset
|
||||
0x172b653 → vaddr 0xc177b653, computed from the ELF program headers): zero hits. Hexagon
|
||||
code doesn't appear to store bare absolute pointers to this table; PC-relative
|
||||
addressing likely, not greppable without real disassembly.
|
||||
- **`llvm-objdump -d` DOES support Hexagon** (`llc --version` lists `hexagon - Hexagon`;
|
||||
confirmed disassembles `mba.mbn` correctly) — but `modem.mbn`/`mba.mbn` are fully
|
||||
stripped (`readelf -S`: "There are no sections in this file", no symtab). Raw
|
||||
disassembly without function boundaries or xrefs isn't tractable by hand at 67MB scale
|
||||
without a real Hexagon-aware decompiler (Ghidra + Hexagon processor module, or IDA) —
|
||||
don't have one in this environment.
|
||||
- **Public docs**: none found. `QMI_SERVICE_AT`'s real message IDs aren't published
|
||||
anywhere searchable. Checked `quic/qmi-framework` (Qualcomm's own open-source repo) —
|
||||
generic transport only, no service-specific IDL.
|
||||
- **`atfwd_daemon`** (the usual Android userspace AT-forwarding daemon that would exercise
|
||||
this service): **not present anywhere in `android-reference`'s extracted vendor stack**.
|
||||
Pixel 3 doesn't ship it — dead end specific to this device, not a search failure.
|
||||
- **Closest real reference found**: `QMI_SERVICE_ATR` (`0x?`, "Telit AT Relay Service",
|
||||
`qmi-service-atr.json`) IS fully implemented in libqmi (different vendor extension, but
|
||||
same architectural family). Protocol: `Send` (msg `0x0000`, TLV `0x01` = AT command
|
||||
string, size-prefixed, max 1024B) → Operation Result; `Received` (indication `0x0001`,
|
||||
same string TLV) delivers the response async. This is the best structural template for
|
||||
what the real `AT` service's wire format probably looks like, even though it's a
|
||||
different service ID — worth trying as a first guess against service 8 directly
|
||||
(`msg 0x0000`, single string TLV `0x01`) next time the phone's available, though
|
||||
`qmi_ati_reg_at_cmd_fwd` existing as a SEPARATE step from send/receive suggests the real
|
||||
`AT` service may require an explicit registration message first, unlike ATR's simpler
|
||||
always-on relay.
|
||||
|
||||
**Modem EFS service (21) and Vendor-specific (231)**: confirmed alive, zero further
|
||||
research done — same "find the real message ID" problem, not yet attempted.
|
||||
|
||||
## What we should try next with these tools, specifically
|
||||
|
||||
1. Against AT service (8): try the ATR-shaped guess first (`msg 0x0000`, TLV `0x01` =
|
||||
AT command string like `"AT\r\n"`) directly at node 0 port 58. Cheap, safe (worst case
|
||||
a clean rejection like the CTL probe, not a crash — this isn't a glink channel-open,
|
||||
it's an ordinary QMI message on the existing IPCRTR transport).
|
||||
2. If that's rejected, try `qmi_ati_reg_at_cmd_fwd`-shaped registration first (a
|
||||
`Register`-style message, unknown ID — try 0x0001-0x0005 range as a first sweep) before
|
||||
the send.
|
||||
3. **Speculative but cheap dispatch-table-order guess**: the 5 strings carrying the
|
||||
`(qmi_svc_hdlr_ftype)` cast prefix in `modem.mbn` appear in this exact sequence:
|
||||
`qmi_ati_reset`, `qmi_ati_reg_at_cmd_fwd`, `qmi_ati_send_at_repsonse`,
|
||||
`qmi_ati_send_at_urc`, `qmi_ati_reg_at_cmd_fwd_ex`. If the string table mirrors the
|
||||
dispatch table's own ordering (common but not guaranteed), these would be 5
|
||||
consecutive message IDs. Worth a sweep starting at a plausible custom-message base
|
||||
(`0x0020`): try `0x0020`=Reset, `0x0021`=RegAtCmdFwd, `0x0022`=SendAtResponse,
|
||||
`0x0023`=SendAtUrc, `0x0024`=RegAtCmdFwdEx against service 8, watching for anything
|
||||
other than a uniform rejection.
|
||||
4. `qrtr-lookup 21`/`231` — still completely unexplored beyond confirming they're alive.
|
||||
No `qmi_mfs_`/`qmi_efs_`-style strings found anywhere in `modem.mbn` (searched both
|
||||
narrow and broad patterns) — either stripped entirely, or MFS's handler code lives in
|
||||
a different firmware image than the main MPSS blob. Vendor-specific (231) has zero
|
||||
name leads at all; would need blind message-ID sweeping, no better starting point.
|
||||
5. Try `qrtr-lookup <n>` with explicit service numbers to see if per-service querying
|
||||
surfaces anything the bare dump suppresses (untested all session).
|
||||
|
||||
## Update 2026-07-02 — services 8 / 21 / 231 all mapped live. Frontier CLOSED (negative).
|
||||
|
||||
Ran `tools/at-service-probe.py` and the new `tools/qmi-service-map.py` (empty-payload
|
||||
dispatch-table sweep, reads the QMI result-TLV error pattern: 71/94 = no handler, 1/3/17/etc
|
||||
= real handler that rejected the args). All three "never-queried, worth-probing" services are
|
||||
now characterized on the live modem. None exposes a lever on RF-init or narrates the 52 wall.
|
||||
|
||||
- **AT service (8), node 0 port 58 — AT-command FORWARDING, wrong direction. DEAD END.**
|
||||
Dispatch table: `0x0000`=RESET (returns OK with or without a TLV — the "accepted an AT
|
||||
string" earlier was a false positive; empty payload also returns OK). `0x0020`/`0x0022`/
|
||||
`0x0024` = real handlers (`qmi_ati_reg_at_cmd_fwd` / `send_at_repsonse` / `reg_at_cmd_fwd_ex`
|
||||
— INTERNAL(3) on empty, MALFORMED(1) on a wrong TLV). `0x0021`/`0x0023` = indications
|
||||
(modem→AP). This is `ds_qmi_at.c`: apps *register* to receive AT commands the modem
|
||||
forwards to them (Android's `atfwd_daemon` role). There is no AT-*execution* path here to
|
||||
command the modem's own RF. Four `0x0020` registration-TLV shapes all returned MALFORMED;
|
||||
nailing the shape only registers us as a forwarding target — it can't move 52. Not pursued
|
||||
further.
|
||||
|
||||
- **Modem EFS service (21), node 0 port 24 — file-ops gated, low-yield. NOT pursued.**
|
||||
Most of `0x0000`–`0x0030` return error 57 (a service-state/"not open" gate). Real handler
|
||||
cluster at `0x001e` (OK on empty — a no-arg open/action), `0x001f`/`0x0020`/`0x0021`
|
||||
(MISSING_ARG — the read/write/list file ops). Cracking the file-path TLV shape is possible
|
||||
but pointless for 52: per `diag_capture.md`, EFS content is byte-identical to Android
|
||||
(shared unslotted partition), so any static NV/file read reads the same bytes both OSes.
|
||||
Empty-payload probes only were sent to 21 (an empty request can't carry a write).
|
||||
|
||||
- **Vendor-specific service (231), node 0 port 43 — modem telemetry / event-log. DEAD END.**
|
||||
This is a stats/bugreport service, self-describing: msg `0x000f` = "Report all modem stats",
|
||||
returns result OK with TLV `0x16` = **16 bytes, all zero** (the rest of its fixed 4136-byte
|
||||
frame is unused). Msg `0x0012` is an event-log *indication* carrying timestamped lines
|
||||
(`"<ts> Bugreport triggered"` — an echo of our own trigger). Fired across a live
|
||||
`mmcli --enable` (the 52 moment) and re-fired immediately after a failed enable: the stat
|
||||
block stays all-zero, no RF-init narration, no error counter moves. It is NOT a DIAG/F3
|
||||
substitute — the counters are empty and the bugreport content is not delivered through this
|
||||
channel (fetched/stored elsewhere; EFS-backed at best, which loops back to 21's null diff).
|
||||
Many `0x0000`–`0x0017` handlers want args (MISSING_ARG) but blind TLV-shape guessing here is
|
||||
the same low-yield trap as AT(8)'s registration TLV; not worth it without a name/IDL lead.
|
||||
|
||||
**Net:** the "probe the other live services" frontier is exhausted with a negative result. No
|
||||
standard, AT, EFS, or vendor-telemetry QMI service on this modem exposes the RF-init lever or
|
||||
its reason. Consistent with `modem-online-provisioner/RESULTS.md`: the gate is the modem
|
||||
firmware's own RF-init decision, and the only instrument that could narrate it (DIAG F3) is
|
||||
walled by production fusing ("MBA booted without debug policy") on both OSes — see
|
||||
`HANDOFF.md`'s DIAG-over-glink note.
|
||||
|
||||
## `tools/pdc-pending-test.py` — ready to run, not yet executed live
|
||||
|
||||
Built and syntax-checked (not yet run — needs phone access) to correctly test whether the
|
||||
`Pending` PDC config status (found live 2026-07-01, see `HANDOFF.md`) ever resolves to
|
||||
`Active` on its own, or needs a forced modem restart. Fixes two real mistakes from earlier
|
||||
manual testing, both confirmed by reading the actual working scripts rather than guessing:
|
||||
- `--pdc-monitor-refresh` watches the **wrong indication** (`Refresh`, `0x2F`) — the one
|
||||
that actually reports Active/Pending transitions is **`Config Change`** (`0x21`),
|
||||
delivered only to a client that called `Register` with reporting+refresh enabled
|
||||
*before* touching config state. No test this session had done that.
|
||||
- Device must be opened via `Qrtr.Bus → peek_node → Qmi.Device.new_from_node` with
|
||||
`Qmi.DeviceOpenFlags.EXPECT_INDICATIONS` (verified against `qcril-autoselect.py`'s
|
||||
`open_device()`) — a plain `Gio.File` open (my first draft) would have silently never
|
||||
delivered any indication at all, making a "no signal" result meaningless. Caught before
|
||||
running, not after.
|
||||
259
saf/device/evidence/secure_load_tz_frontier.md
Normal file
259
saf/device/evidence/secure_load_tz_frontier.md
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# PAF · Secure-load / TrustZone frontier
|
||||
|
||||
> **🔴 SUPERSEDED 2026-06-29.** Secure-load is **RESOLVED** for the modem — the MSS loads clean
|
||||
> (MBA boots, MPSS loads, `remoteproc running`, **zero −22 on the modem path**) via DELTA-1
|
||||
> (`c2923cb49` MPSS assign-ordering). This doc is kept as the **historical −22 / fastrpc_mem
|
||||
> investigation record**. The −22 that still floods the logs is the **SLPI's** SDSP heap, not the
|
||||
> modem's — see `slpi.md`. The current frontier is the firmware-internal OFFLINE/52 park, not
|
||||
> secure-load. (Body below is the 2026-06-26 investigation as-written.)
|
||||
|
||||
## 2026-06-26 (OPUS session) — mem-protect SKIP broke the boot on hardware; reverted. LOS does mem-protect differently, not skip it.
|
||||
|
||||
**Platform is now clean and reproducible (this part is solid, keep it):**
|
||||
- ONE canonical branch **`blueline`** (was split across `mobian-sdm845-7.1` + `blueline-slpi-build`,
|
||||
neither complete — consolidated). It = Mobian `mobian-sdm845-7.1` base (mainline 7.1.1 tag +
|
||||
full 179-patch sdm845 device series: camera/imx sensors, q6voice, haptics, fuel-gauge, panels)
|
||||
+ our 6 fixes. Other branches renamed `reference/mobian-7.1.1-base`, `archive/superseded-*`.
|
||||
- Guard against drift: **`tools/verify-fixes.sh`** (13 checks, asserts SOURCE CONTENT not commit
|
||||
messages; covers our 6 fixes AND the inherited Mobian migrations). Run before every build.
|
||||
- Build: `pmbootstrap build --src=references/linux-7.1.1-sdm845-CANONICAL linux-postmarketos-qcom-sdm845`
|
||||
(host `make` is NOT the path — build.md:7). Deploy: on-device `apk add --allow-untrusted` →
|
||||
`boot-deploy` auto-flashes `boot_a`. `uname -r = 7.1.1-sdm845`. Rollback image:
|
||||
`build-output/boot_a-rollback-20260626-1605.img.gz`.
|
||||
- **Found + fixed the silent killer behind every prior "flash and test":** the phone's kernel and
|
||||
its `/lib/modules` were ABI-mismatched — `qcom_q6v5_mss` gave `Exec format error`, the modem
|
||||
driver never loaded, so the modem was dead regardless of any fix. Matched build → driver loads,
|
||||
modem remoteproc `4080000.remoteproc` now registers.
|
||||
|
||||
**The mem-protect skip (`need_mem_protection = false` for sdm845) is REFUTED — likely roll back:**
|
||||
- It removed the `-22` from the modem path as designed — but the modem now fails EARLIER:
|
||||
`Booting .../mba.mbn → PBL returned unexpected status 0xEF110000 → can't start rproc: -22`.
|
||||
It never reaches "MBA booted".
|
||||
- The r2 baseline (below) had the modem boot MBA then XPU-crash-loop at 52. So the skip REGRESSED
|
||||
the boot. `qcom_scm_assign_mem` (`q6v5_xfer_mem_ownership`, qcom_q6v5_mss.c:463/489) transfers the
|
||||
MBA region HLOS→MSS_MSA so the modem PBL can read MBA; skipping starves PBL. **The assign is
|
||||
load-bearing. The real gate is WHY TrustZone returns -22 (EINVAL), not the call's existence.**
|
||||
|
||||
**The LOS claim in `HANDOFF_20260626_OPUS.md` was unverified inference — treat as RETRACTED:**
|
||||
- It blended mainline (`need_mem_protection`) with downstream (`subsys_vmid` / `qcom,mem-protect-id`)
|
||||
concepts into one story.
|
||||
- LOS Pixel 3 runs the **downstream msm-google 4.9 kernel**; its modem loader is `subsys-pil-msa`,
|
||||
NOT mainline `qcom_q6v5_mss.c`. We have NOT pulled that source (Tier 1 still open) — so what LOS
|
||||
does was never read off real code.
|
||||
- Hardware contradicts the inference: if LOS did no mem-setup and MBA self-unlocked XPU, the skip
|
||||
would have worked. It made things worse.
|
||||
|
||||
**PRINCIPLE (Casey, 2026-06-26): LineageOS is the ONLY known-working modem reference on this device.
|
||||
Other sdm845 devices (op6 / enchilada / beryllium / shift) are IRRELEVANT to the secure-load / XPU
|
||||
question — do not cite them as modem-bringup evidence. The downstream Pixel-3 stack is the authority.**
|
||||
|
||||
**Current phone state:** booted on `blueline` 7.1.1 (the skip build). Modem PBL-fails every boot;
|
||||
SLPI crash-loops ~10s (the `-22` spam every 10s is the SDSP/SLPI hyp-assign, NOT the modem).
|
||||
|
||||
**OPTIONS — left for Casey to choose (not picking one):**
|
||||
1. **Revert the skip** → restore the MBA-boots-then-XPU-52 baseline on the clean 7.1.1 platform,
|
||||
then work secure-load Tier 0/1 from a known state.
|
||||
2. **Pull `msm-google` downstream kernel** → diff `subsys-pil-msa` mem-setup + hyp-assign vs
|
||||
mainline. Answers "what does LOS actually do" with real code (Tier 1).
|
||||
3. **Downstream-kernel hybrid** (Droidian / Halium pattern) — this doc's honest realistic
|
||||
1–2 week path to working telephony.
|
||||
4. **Chase the TZ `-22` directly** — why does mainline's `qcom_scm_assign_mem` get EINVAL on
|
||||
blueline (VMID / mem-protect-id / TZ expectation)? Open-ended.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-26 — TRIGGER RESOLVED: this doc is the plan. Hard secure-memory evidence captured.
|
||||
|
||||
The trigger condition below ("still 52, modem boots then refuses RF → the gate is the
|
||||
secure-load/TZ layer → this doc is the plan") is now MET, with a concrete error string instead of
|
||||
inference. On the rebuilt r2 LLVM kernel (slot A, live ssh), `dmesg` shows the modem **crash-looping
|
||||
on a hardware secure-memory protection fault**:
|
||||
|
||||
```
|
||||
qcom-q6v5-mss 4080000.remoteproc: fatal error: modem_xpu_int.c:456:[modem_sec_xpu_I]XPU violation
|
||||
(also crash #1: "SFR Init: wdog or kernel error suspected")
|
||||
```
|
||||
and EVERY modem + slpi restart logs the shared TZ rejection:
|
||||
```
|
||||
qcom_scm firmware:scm: Assign memory protection call failed -22 (qcom_scm_assign_mem → -EINVAL)
|
||||
SDSP fastrpc hyp-assign rejected by TZ (-22)
|
||||
```
|
||||
|
||||
**XPU = eXtreme Protection Unit**, the hw memory guard TZ programs. `modem_sec_xpu XPU violation` =
|
||||
the modem firmware touched a region the XPU forbids → its secure memory regions aren't assigned
|
||||
right for mainline → crash → q6v5 recovers → repeat → never RF-inits → **52**. The fastrpc/SLPI
|
||||
`assign -22` is the same mechanism (TZ refusing mainline's hyp-assign); fastrpc tolerates it, the
|
||||
modem can't. **The gate is concretely the secure-memory-assign / XPU region setup — pull the
|
||||
`assign_mem -22` thread.** (See modem.md TOP LEAD #11, slpi.md.)
|
||||
|
||||
CAVEAT (don't overclaim): this XPU crash-loop was captured on **7.1.1 r2**. The 6.16.7 / 7.1rc1
|
||||
captures recorded the modem "up + QMI registers" (lead #10) — QMI still answers here too, in the
|
||||
up-windows between crashes, so a single-sample `running` looked fine before. OPEN: re-check whether
|
||||
6.16.7 *also* XPU-crash-loops (i.e. always-present, just not noticed) or whether 7.1.1 worsened it —
|
||||
diff `journalctl -p err` across both kernels before assuming XPU is identical on the older tree.
|
||||
|
||||
Original status (pre-2026-06-26): **alternative, not primary.** The primary lever was Opus's
|
||||
reserved-memory geometry fix (`modem_dt_rf_analysis_20260622.md` candidate #1: revert the
|
||||
`sdm845-google-common.dtsi` override so MPSS = `0x7800000` / MBA = `0x96500000`, matching both
|
||||
working devices). That swing did not clear the 52; the secure-load/TZ layer (this doc) is now primary.
|
||||
|
||||
It is written assuming the reader has `modem.md`, `modem_dt_rf_analysis_20260622.md`,
|
||||
`tools/modem-online-provisioner/RESULTS.md`, and `android-capture-20260622/EFS_FINDINGS.md` in head.
|
||||
|
||||
## Trigger condition (the decision point)
|
||||
|
||||
Flash the reserved-memory DT fix to `boot_a`, cold-boot pmOS, one `--dms-set-operating-mode=online`:
|
||||
- **online works** → geometry was the gate. Stay mainline; push the fix upstream (sdm845-mainline /
|
||||
pmaports), bisect the `google-common` override. **This doc is not needed.**
|
||||
- **modem fails to load** (`remoteproc Boot failed`) → the firmware was signed for the old geometry;
|
||||
the override existed for a reason. Revert; geometry is load-bearing-correct as-is. Come here.
|
||||
- **still 52 (modem boots clean, refuses RF)** → the gate is **not** the DT geometry. It is in the
|
||||
secure-load / TrustZone / subsystem-coordination layer. **This doc is the plan.**
|
||||
|
||||
## The gate, precisely (today's hard proof — do not re-derive)
|
||||
|
||||
Source: `tools/modem-online-provisioner/RESULTS.md` (raw QMI over `AF_QIPCRTR`, 2026-06-22).
|
||||
|
||||
- Modem is in **op-mode 3 = OFFLINE** (deepest non-persistent off state). Not LPM, not transient.
|
||||
- **Accepts** every read, non-RF writes (DMS/NAS indication registration), and **DMS Set Operating
|
||||
Mode = RESET (0x002E mode 4) → OK**.
|
||||
- **Refuses every RF-requiring transition**: online → **52 DeviceNotReady**; low_power → **60
|
||||
InvalidTransition**; NAS Set System Selection Preference (0x0033) → **25 DeviceUnsupported**; DMS
|
||||
ATEL UI-ready (0x0064) → **58** (arg-rejected, message id confirmed correct).
|
||||
- After the accepted **RESET**, firmware re-initialises **straight back to OFFLINE** and refuses
|
||||
again, with **zero** sys-info/serving-system indications in an 8 s listen. The firmware is inert
|
||||
on RF by decision, not attempting and failing.
|
||||
|
||||
**Conclusion (raw-level, not inference):** no AP-side QMI message or ordering unlocks RF. The
|
||||
choreography-replay path (`qcril_online_sequence.md`) is closed: the modem rejects the pre-online
|
||||
writes *themselves* before we reach the online flip. QCRIL only succeeds on Android because the
|
||||
firmware has **already self-completed RF-init** there; on pmOS it never does.
|
||||
|
||||
## Why this points at secure-load / TZ (the RESET argument)
|
||||
|
||||
A QMI RESET re-initialises the modem firmware **without re-doing the cold-boot setup** — PIL image
|
||||
auth, the TZ/SCM state, the bootloader (ABL/XBL) modem init, and sibling-subsystem coordination are
|
||||
all established **once at cold boot** and persist through a QMI reset. The RESET reproducing OFFLINE
|
||||
tells us the gate lives in that **persistent cold-boot environment**, not in a one-shot race or a
|
||||
userspace omission. That environment is exactly four things, in suspect order:
|
||||
|
||||
1. **DT reserved-memory geometry** — Opus's test (may already settle it).
|
||||
2. **Sibling subsystem / Protection-Domain coordination** — the modem RF-init can gate on a PD
|
||||
registered over glink/pd-mapper by ADSP/CDSP/**SLPI**. pmOS DT has **no SLPI region at all**
|
||||
(absent in the `modem_dt_rf_analysis` table; Android + enchilada both carry `slpi@0x96700000`).
|
||||
Never confirmed up on pmOS. Mainline-fixable if so — *not* TZ.
|
||||
3. **PIL / SCM secondary auth** — image auth clearly succeeded (modem boots), but a non-fatal
|
||||
secondary secure step (a hash-table segment, a secure-regulator vote, a TUST handshake) may not
|
||||
have completed, leaving the firmware in a degraded RF-locked mode.
|
||||
4. **Bootloader / verified-boot state** — pmOS boots a hand-built `--header_version 0` image with a
|
||||
custom cmdline. If ABL sees an unverified/inconsistent chain, it can skip modem secure-env init
|
||||
that stock Android gets.
|
||||
|
||||
## Ranked investigation — cheapest discriminator first
|
||||
|
||||
### Tier 0 — free, same boot (run the instant reserved-memory fails, before any build)
|
||||
1. **Mine dmesg for the secure/PD layer.** Image auth succeeded, so look for *non-fatal* warnings:
|
||||
`dmesg | grep -iE 'pas|scm|auth|hash|mba|qcom_q6v5|remoteproc|glink|pdr|pil'`.
|
||||
A non-zero `qcom_scm_pas_*` return, a PDR restart-notification timeout, or a glink intent-failure
|
||||
is a citable pointer into #2/#3.
|
||||
2. **Confirm sibling DSPs are up and their PDs registered.**
|
||||
`for r in /sys/class/remoteproc/remoteproc*; do printf '%s: %s %s\n' "$r" "$(cat $r/name 2>/dev/null)" "$(cat $r/state 2>/dev/null)"; done`
|
||||
and `qrtr-lookup | grep -iE 'adsp|slpi|cdsp|sns|domain'`. If SLPI/ADSP is `offline`/absent and
|
||||
the modem waits on its PD, **that is the gate and it is mainline-fixable** (firmware path +
|
||||
remoteproc node + a `pd-mapper` entry), not TZ. This is the highest-value cheap check — the whole
|
||||
elimination tracked `remoteproc2` (modem) closely and never verified the siblings.
|
||||
3. **Timing/missed-response.** `strace -f -p $(pidof pd-mapper)` and rmtfs during a QMI RESET. If
|
||||
the modem's RF-init window closes before pd-mapper/rmtfs answer a request, you'll see the missed
|
||||
response. (Likely already excluded — services are confirmed up post-boot — but cheap to confirm.)
|
||||
4. **LDO13 rail state — the one rail NOT on the eliminated list.** Unlike the refuted #9 LDO set
|
||||
(ldo4/6/8/9/10/11/15/16/18/23/27, which were *absent* from pmOS), `ldo13` IS defined in
|
||||
`dt-work/blueline-pmos.dts:5466`, and on SDM845 LDO13 feeds RFFE (per OpenStick prior art — the
|
||||
`l13: voltage operation not allowed` line is benign on MSM8917 but not automatically so here).
|
||||
Static dumps show a state delta: pmOS `ldo13 … 0 0 0 …` vs Android `pm8998_l13 … 0 1 0 …`, and
|
||||
there is **no** `l13`/`voltage operation not allowed` line in the captured pmOS dmesg. **Live
|
||||
check at the moment of `online`:**
|
||||
`for r in /sys/class/regulator/regulator.*/; do n=$(cat $r/name 2>/dev/null); [ "$n" = ldo13 ] && echo "$n state=$(cat $r/state) @ $(cat $r/microvolts)uV"; done`
|
||||
— is it `on` @ 1.8 V while the modem is booted-but-refusing-RF? Then retry `online` and watch
|
||||
whether L13 flips. Likely falls to the same "modem votes RF via RPMh, not the AP framework"
|
||||
refutation as #9 — but it's the one rail never explicitly probed, so confirm live before closing.
|
||||
Plus `dmesg | grep -iE 'l13|voltage operation not allowed'` on the same boot.
|
||||
|
||||
### Tier 1 — diff the downstream kernel (the real working reference for secure-load)
|
||||
Enchilada proves the mainline *stack* works on SDM845; it does **not** prove the secure-load code
|
||||
paths work on blueline's exact modem. The authority for those is **Google's downstream Pixel-3
|
||||
kernel** (`msm-google`, 4.4-era — confirm the exact branch against `boot_a`'s kernel version on
|
||||
`android.googlesource.com/kernel/msm-google` before cloning). Pull it and diff, against mainline,
|
||||
exactly three things:
|
||||
- `drivers/remoteproc/qcom_q6v5_mss.c` — the PAS auth + mba mem-setup + reset sequence.
|
||||
- `drivers/soc/qcom/{scm,pdr_interface,smem}` — the secure-channel and PD-restart path.
|
||||
- modem `reserved-memory` + `qcom,smem-states` + `power-domains` in the DT.
|
||||
|
||||
The reserved-memory hit is the first output of this method; the SCM/auth/PDR delta is the next.
|
||||
This is the strongest "honest swing" if reserved-memory alone fails — methodical, sourced, no RE
|
||||
of opaque blobs. **Artifact we have but haven't used this way:** `android-reference/vendor-stack-20260621/`
|
||||
(vendor RIL + MBN tree). **Artifact we still need:** the downstream kernel source tree.
|
||||
|
||||
### Tier 2 — instrument the firmware's own RF-init (the only inside view)
|
||||
Every capture so far is userspace (QCRIL/radio logs). Nobody has captured the **MPSS firmware's own
|
||||
RF-init trace** — the ordered set of things it reads and waits for. On rooted Android (slot A),
|
||||
QCSuper continuous diag over **`/dev/diag`** (local — confirmed working; USB-diag is HAL-locked on
|
||||
this production Pixel, per `pmos_fix_plan.md`) captures the F3/log stream during a cold boot. An
|
||||
open F3 decoder (linux-msm / sdm845-mainline ecosystem; QCSuper has partial decode) turns it into
|
||||
the firmware's RF-init step list. Diff which of those steps can't complete on pmOS. This is the one
|
||||
instrument that can name the gate from inside the firmware; everything on pmOS only observes the
|
||||
refusal. **Read-only capture — do not write EFS.**
|
||||
|
||||
### Tier 3 — TZ / SCM RE (the hard pole, deprioritize)
|
||||
If Tiers 0–2 point at a specific SCM call or secure state: disassemble the QSEE trustlets or diff
|
||||
XBL/ABL's modem init. Long, uncertain, opaque. Keep as the escalation, not the plan.
|
||||
|
||||
## The strategic fork (be honest about the 1–2 week telephony goal)
|
||||
|
||||
Mainline-native secure-load RE (Tier 2–3) is realistically **not** a 1–2 week path to working
|
||||
telephony/SMS/data — it's open-ended research. If Tier 0 + reserved-memory don't clear 52, the
|
||||
fastest route to "telephony on Linux like I have on rooted Android" is to use the stack that already
|
||||
works on this silicon:
|
||||
|
||||
- **Downstream-kernel hybrid (Droidian / Halium / pmOS-downstream pattern):** boot the downstream
|
||||
`msm-google` kernel under a Linux userspace, where the modem firmware gets the exact cold-boot
|
||||
environment it expects so RF-init completes, then drive it with the vendor `qcrild` already pulled
|
||||
in `android-reference/`. Trades mainline purity for a known-good outcome. This is the realistic
|
||||
1–2 week path.
|
||||
|
||||
- **WRONG TURN — do not burn time on this:** "run vendor QCRIL on the *mainline* kernel via
|
||||
libhybris" will **not** fix it. QCRIL does not trigger RF-init (the firmware self-arms on Android;
|
||||
QCRIL only queries/confirms — proven by today's raw results). The gate is the cold-boot
|
||||
environment, so only changing that environment (DT → subsystems → TZ → bootloader) **or** booting
|
||||
the downstream kernel helps. Vendor-RIL-on-mainline addresses a different (already-solved) layer.
|
||||
|
||||
Net: **reserved-memory fix + Tier 0 (free) → if clear, stay mainline; if not, decide
|
||||
mainline-secure-load-research vs downstream-kernel-hybrid on evidence, not on momentum.**
|
||||
|
||||
## Artifacts
|
||||
|
||||
- **Have:** `android-reference/vendor-stack-20260621/` (libril-qc-hal-qmi.so + 174-file MBN tree +
|
||||
selection DB); `tools/modem-online-provisioner/{qrtr_raw.py,qrtr_seq.py}` (send any QMI PDU over
|
||||
AF_QIPCRTR — validated against DMS 0x002D); QCSuper efs-shell + diag over `/dev/diag` on rooted
|
||||
slot A; `slotb-backup-20260621/pmos-7.1-boot.img` recovery; 6.16.7 known-good kernel; EFS clones.
|
||||
- **Need (not yet pulled):** downstream `msm-google` kernel source (Tier 1 ground truth); an open
|
||||
Hexagon F3/diag decoder (Tier 2); possibly a devcoredump/minidump parser if SSR ever yields one
|
||||
(today it did not — modem doesn't crash, it deliberates).
|
||||
|
||||
## Guardrails (secure-load-specific)
|
||||
|
||||
- pmOS-side secure-load investigation is **read-only**: `dmesg`, `/sys/class/remoteproc/*`,
|
||||
`/sys/kernel/debug/{remoteproc,msm_subsys}/*`, `qrtr-lookup`. Safe.
|
||||
- diag/F3 capture is on **rooted slot A `/dev/diag`** — read-only capture. Never write EFS or NV
|
||||
from these probes (shared unslotted `modemst1/2` — a write is seen by both slots).
|
||||
- **Never flash/patch TZ, trustlets, XBL, or ABL** — brick territory. `references/edl-pixel` exists
|
||||
as last-resort recovery but **assume no EDL safety net**; behave as if a bad flash is permanent.
|
||||
- Only ever touch `boot_a` / pmOS. Never slot B, `system_a`, the shared EFS, or userdata.
|
||||
- Recovery image: `slotb-backup-20260621/pmos-7.1-boot.img`. Document every slot switch + flash.
|
||||
- One `online` attempt per boot (a failed attempt poisons the modem until a power cycle).
|
||||
- Standard hard constraints apply: pmOS is Alpine/ash (no `[[ ]]`/arrays); `rmtfs -P -s` (never
|
||||
`-r` alone); never add `pmic@4`/`pmic@5` SPMI nodes; sudo pw `147147`.
|
||||
|
||||
## On link-in
|
||||
Not yet referenced from `START-HERE.md` or `PAF/README.md` (left for the Opus handoff to wire, to
|
||||
avoid colliding with in-flight edits). If reserved-memory fails, point START-HERE's "actual
|
||||
frontier" at this doc and demote the reserved-memory lever to the trigger-condition note above.
|
||||
146
saf/device/evidence/tier1_los_vs_mainline_diff.md
Normal file
146
saf/device/evidence/tier1_los_vs_mainline_diff.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# PAF Tier 1: LOS `pil-q6v5-mss` (4.9.337) vs Mainline `qcom_q6v5_mss` (7.1.1) — Complete Diff
|
||||
|
||||
**Date:** 2026-06-27
|
||||
**Source LOS:** `references/los-kernel-blueline-4.9/drivers/soc/qcom/{pil-msa.c, peripheral-loader.c, pil-q6v5-mss.c, secure_buffer.c}`
|
||||
**Source Mainline:** `Downloads/mobian-sdm845-7.1/drivers/remoteproc/qcom_q6v5_mss.c` + `drivers/firmware/qcom/qcom_scm.c`
|
||||
**Live Android capture:** 4.9.337 cold boot, `89JX0AAJP`, 2026-06-27
|
||||
|
||||
## Live Android ground truth (captured this session)
|
||||
|
||||
```
|
||||
pil-q6v5-mss 4080000.qcom,mss: modem: loading from 0x8e000000 to 0x97800000 (MPSS 152MB)
|
||||
pil-q6v5-mss 4080000.qcom,mss: Loading MBA and DP from 0x98500000 to 0x98600000 (MBA 1MB)
|
||||
pil-q6v5-mss 4080000.qcom,mss: MBA boot done
|
||||
pil-q6v5-mss 4080000.qcom,mss: modem: Brought out of reset
|
||||
```
|
||||
|
||||
Zero `hyp_assign` errors. Zero `xpu`. Zero `-22`. The modem boots clean on Android.
|
||||
|
||||
Running Android reserved-memory (from `/sys/firmware/devicetree/base/reserved-memory/`):
|
||||
|
||||
| Region | Address | Size |
|
||||
|--------|---------|------|
|
||||
| modem (MPSS) | `0x8e000000` | `0x9800000` (152MB) |
|
||||
| MBA | `0x98500000` | `0x200000` (2MB) |
|
||||
| SLPI | `0x98700000` | `0x1400000` (20MB) |
|
||||
| video | `0x97800000` | `0x500000` (5MB) |
|
||||
| CDSP | `0x97d00000` | `0x800000` (8MB) |
|
||||
|
||||
These match the mainline `sdm845-google-common.dtsi` override exactly. TZ accepts this layout.
|
||||
|
||||
---
|
||||
|
||||
## Area 1: Driver boot sequence — MBA load + MPSS load
|
||||
|
||||
### MBA load
|
||||
|
||||
| Step | LOS (`pil_mss_reset_load_mba`) | Mainline (`q6v5_mba_load`) |
|
||||
|------|------|---------|
|
||||
| 1 | `pil_notify_aop("on")` | `q6v5_load_state_toggle(true)` via QMP — **same message** |
|
||||
| 2 | Enable proxy clocks/regulators | Enable proxy clocks/regulators + power-domains |
|
||||
| 3 | `dma_alloc_attrs(mba_mem_dev_fixed, ...)` — alloc from reserved `pil_mba_mem` at `0x98500000` | `memremap(mba_phys)` — directly maps reserved `mba_region` at `0x98500000` |
|
||||
| 4 | `memcpy` MBA fw into buffer | `memcpy` MBA fw into mapped region |
|
||||
| 5 | — | **`q6v5_xfer_mem_ownership(&mpss_perm, false, true, mpss_phys, mpss_size)` — HLOS->MSS for 152MB MPSS** |
|
||||
| 6 | `pil_assign_mem_to_subsys(mba_dp_phys, mba_dp_size)` — HLOS->MSS for MBA only | `q6v5_xfer_mem_ownership(&mba_perm, false, true, mba_phys, mba_size)` — HLOS->MSS for MBA |
|
||||
| 7 | Trigger boot FSM, wait PBL + MBA `XPU_UNLOCKED` | Trigger boot FSM, wait PBL + MBA `XPU_UNLOCKED` |
|
||||
|
||||
**DELTA 1 (step 5): Mainline pre-assigns the ENTIRE 152MB MPSS region to MSS before MBA boots. LOS does not touch MPSS until after MBA boots and segment loading begins.** This is a 152MB hyp-assign call that LOS never makes at this stage.
|
||||
|
||||
### MPSS load (segment loading + final handoff)
|
||||
|
||||
| Step | LOS (`pil_boot` in `peripheral-loader.c`) | Mainline (`q6v5_mpss_load`) |
|
||||
|------|------|---------|
|
||||
| 1 | `init_image` (MBA authenticates metadata) | `q6v5_mpss_init_image` (MBA authenticates metadata) |
|
||||
| 2 | `mem_setup` (MBA sets up MPSS memory) | `qcom_scm_pas_mem_setup` — **SKIPPED for SDM845** (`need_pas_mem_setup=false`) |
|
||||
| 3 | SSR: `pil_assign_mem_to_linux(region)` — reclaim from MSS back to HLOS | SSR: `q6v5_xfer_mem_ownership(true, false)` — reclaim |
|
||||
| 4 | `pil_assign_mem_to_subsys_and_linux(region)` — src={HLOS}, dest={HLOS+MSS}, both RW | `q6v5_xfer_mem_ownership(true, true)` — shared HLOS+MSS |
|
||||
| 5 | Load segments | Load segments |
|
||||
| 6 | `pil_reclaim_mem(region, subsys_vmid)` — src={HLOS,MSS}, dest={MSS only} | `q6v5_xfer_mem_ownership(false, true)` — MSS only |
|
||||
| 7 | `auth_and_reset` | Wait `RMB_MBA_AUTH_COMPLETE` |
|
||||
|
||||
**DELTA 2 (step 3-4): Cold boot ordering difference.** On first boot (non-SSR), LOS skips step 3 (comment: "linux already owns it") and goes straight to step 4 (shared). Mainline at step 3 does `q6v5_xfer_mem_ownership(&mpss_perm, true, false, ...)` — but `mpss_perm` was already set to `BIT(MSS_MSA)` by the pre-assign in DELTA 1. So mainline's step 3 is MSS->HLOS (reclaim), then step 4 is HLOS->{HLOS+MSS} (share). LOS goes straight from HLOS->{HLOS+MSS}. The net result should be the same IF the pre-assign succeeded, but if DELTA 1 failed with -22, `mpss_perm` is still `BIT(HLOS)` and step 3 would be a no-op (already HLOS-owned), then step 4 works. Except — the pre-assign failure should have already aborted `q6v5_mba_load`.
|
||||
|
||||
### Metadata authentication
|
||||
|
||||
| Step | LOS (`pil_msa_mss_reset_mba_load_auth_mdt`) | Mainline (`q6v5_mpss_init_image`) |
|
||||
|------|------|---------|
|
||||
| buffer | `dma_alloc_attrs` — dynamic | From `mdata_mem` reserved region if present, else `dma_alloc_attrs` |
|
||||
| assign | `pil_assign_mem_to_subsys(mdata, size)` — HLOS->MSS | `q6v5_xfer_mem_ownership(false, true, phys, size)` — HLOS->MSS |
|
||||
| auth | Write to RMB, wait `META_DATA_AUTH_SUCCESS` | Write to RMB, wait `META_DATA_AUTH_SUCCESS` |
|
||||
| reclaim | `pil_assign_mem_to_linux(mdata)` — MSS->HLOS | `q6v5_xfer_mem_ownership(true, false)` — MSS->HLOS |
|
||||
|
||||
**DELTA 3: Mainline has a dedicated `mdata_mem` reserved region (`alloc-ranges 0xa0000000`, 16KB, `no-map`). LOS uses a dynamic DMA allocation.** If TZ doesn't know about this `mdata_mem` range (it's an `alloc-ranges` not a fixed `reg`), the assign could fail.
|
||||
|
||||
---
|
||||
|
||||
## Area 2: SCM / secure-channel path
|
||||
|
||||
### The SCM call itself
|
||||
|
||||
| | LOS | Mainline |
|
||||
|--|-----|---------|
|
||||
| SCM SVC/CMD | `SCM_SVC_MP (0x0C)` / `MEM_PROT_ASSIGN_ID (0x16)` | `QCOM_SCM_SVC_MP (0x0C)` / `QCOM_SCM_MP_ASSIGN (0x16)` |
|
||||
| Call convention | `scm_call2(SCM_SIP_FNID(...), &desc)` — 7-arg SIP | `qcom_scm_call(dev, &desc, &res)` via `scm_smc_call` — 7-arg SIP |
|
||||
| VMID_HLOS | `0x3` | `0x3` |
|
||||
| VMID_MSS_MSA | `0xF` | `0xF` |
|
||||
| Perms | `PERM_READ=4, PERM_WRITE=2, PERM_EXEC=1` | `QCOM_SCM_PERM_READ=4, WRITE=2, EXEC=1` |
|
||||
| Dest struct | `{u32 vm, u32 perm, u64 ctx, u32 ctx_size}` (20B, pads to 24B on arm64) | `{__le32 vmid, __le32 perm, __le64 ctx, __le32 ctx_size, __le32 unused}` (24B) |
|
||||
| Src struct | `mem_prot_info {phys_addr_t addr, u64 size}` | `qcom_scm_mem_map_info {__le64 mem_addr, __le64 mem_size}` |
|
||||
| Buffer alloc | `kzalloc` + `virt_to_phys` + `dmac_flush_range` | `qcom_tzmem_alloc` from dedicated TZ memory pool |
|
||||
| Endianness | native (cpu-endian u32/u64) | explicit `cpu_to_le32`/`cpu_to_le64` |
|
||||
|
||||
**DELTA 4: SCM buffer memory source.** LOS uses `kzalloc` (normal kernel memory) + `virt_to_phys` for the SCM descriptor buffers. Mainline uses `qcom_tzmem_alloc` from a special TZ-visible memory pool. If `qcom_tzmem` is misconfigured or the pool isn't set up correctly on SDM845, TZ can't read the descriptor and returns -22.
|
||||
|
||||
**DELTA 5: Endianness handling.** LOS writes native-endian `u32`/`u64` to the descriptor buffers. Mainline uses `cpu_to_le32`/`cpu_to_le64`. On arm64 (little-endian), these are no-ops. Not a functional difference.
|
||||
|
||||
**DELTA 6: src (mem_to_map) struct.** LOS: `{phys_addr_t addr, u64 size}` — on arm64 this is `{u64 addr, u64 size}` = 16 bytes. Mainline: `{__le64 mem_addr, __le64 mem_size}` = 16 bytes. Same wire format.
|
||||
|
||||
---
|
||||
|
||||
## Area 3: DT — reserved-memory, power-domains, smem-states
|
||||
|
||||
### Reserved memory
|
||||
|
||||
| Region | LOS DTS (`sdm845.dtsi`) | Mainline (`sdm845-google-common.dtsi`) | Live Android |
|
||||
|--------|----|----|-----|
|
||||
| MPSS | `0x8e000000`, `0x7800000` (120MB) | `0x8e000000`, `0x9800000` (152MB) | `0x8e000000`, `0x9800000` (152MB) |
|
||||
| MBA | `0x96500000`, `0x200000` (2MB) | `0x98500000`, `0x200000` (2MB) | `0x98500000`, `0x200000` (2MB) |
|
||||
| SLPI | `0x96700000`, `0x1400000` (20MB) | `0x98700000`, `0x1400000` (20MB) | `0x98700000`, `0x1400000` (20MB) |
|
||||
| Metadata | (none — uses dma_alloc) | `alloc-ranges 0xa0000000`, `0x4000` (16KB), `no-map` | N/A |
|
||||
|
||||
The LOS DTS has different BASE ADDRESSES from what Android actually uses. The google-common override produces the correct values that match the live device.
|
||||
|
||||
**DELTA 7: LOS DTS says 120MB MPSS, but Android actually runs with 152MB.** The google-common override (which LOS applies via board-level .dtsi) matches. Mainline has the same override. Geometry is the same in practice.
|
||||
|
||||
**DELTA 8: Mainline has a `mdata_mem` reserved region. LOS does not.** Mainline's `q6v5_mpss_init_image` checks for a `metadata` child node with `memory-region` and uses that reserved region for metadata. If absent, falls back to `dma_alloc_attrs`. LOS always uses DMA. If the `mdata_mem` alloc-ranges region (`0xa0000000`) is not TZ-known, the metadata assign would fail. But `q6v5_mpss_init_image` is called AFTER MBA boots, so this wouldn't cause the MBA-level failure.
|
||||
|
||||
### MSS DT node differences
|
||||
|
||||
| Property | LOS | Mainline |
|
||||
|----------|-----|---------|
|
||||
| compatible | `qcom,pil-q6v55-mss` | `qcom,sdm845-mss-pil` |
|
||||
| reg | 8 regions (qdsp6, halt_q6/modem/nc, rmb, restart, pdc_sync, alt_reset) | 2 regions (qdsp6, rmb) — halts via syscon, reset via reset-controller |
|
||||
| `memory-region` | `<&pil_modem_mem>` on the node itself | Via `mpss` child node |
|
||||
| `qcom,mem-protect-id` | `<0xF>` — read by `peripheral-loader.c:912` into `subsys_vmid` | **ABSENT** — mainline uses hardcoded `need_mem_protection=true` in platform descriptor |
|
||||
| Regulators | `vdd_cx`, `vdd_mx`, `vdd_mss` as direct supplies with TURBO voltage | `power-domains` via `rpmhpd` (cx, mx, mss) |
|
||||
| AOP | `qcom,signal-aop` + `mboxes = <&qmp_aop 0>` | `qcom,qmp = <&aoss_qmp>` — same path, different binding |
|
||||
| `qcom,pil-self-auth` | present | **ABSENT** — mainline always self-auths |
|
||||
| `qcom,override-acc` | present | **ABSENT** — mainline handles ACC differently |
|
||||
| `qcom,qdsp6v65-1-0` | present (selects q6v65 reset sequence) | **ABSENT** — mainline selects via `version = MSS_SDM845` |
|
||||
| `qcom,mss_pdc_offset` | `<8>` | **ABSENT** — mainline uses `pdc_reset` reset-controller |
|
||||
| MBA child | `qcom,mba-mem@0` with `qcom,pil-mba-mem` compatible + `memory-region` | `mba` child with just `memory-region` |
|
||||
| Metadata child | (none) | `metadata` child with `memory-region = <&mdata_mem>` |
|
||||
|
||||
---
|
||||
|
||||
## Summary of deltas, ranked by likelihood of causing the -22
|
||||
|
||||
1. **DELTA 1 — MPSS pre-assign before MBA boot.** Mainline assigns 152MB MPSS to MSS before MBA boots. LOS does not. This is a large, early hyp-assign that LOS avoids entirely. If this returns -22, the modem never starts. This is the most likely trigger — it's the first hyp-assign call mainline makes that LOS doesn't, and it's for the biggest region.
|
||||
|
||||
2. **DELTA 4 — SCM buffer allocation (tzmem vs kzalloc).** If `qcom_tzmem` allocates from a region TZ can't read, every SCM call fails. Would affect ALL assign calls, not just modem.
|
||||
|
||||
3. **DELTA 3 — Metadata reserved region.** Mainline uses a fixed `mdata_mem` at alloc-ranges `0xa0000000`. LOS uses dynamic DMA. If TZ doesn't know about this range, metadata auth assign fails. But this is after MBA boot, so it wouldn't cause the PBL-level failure.
|
||||
|
||||
4. **DELTA 8 — No `qcom,mem-protect-id` in mainline DT.** LOS reads VMID `0xF` from DT. Mainline hardcodes `need_mem_protection=true` and uses `QCOM_SCM_VMID_MSS_MSA` (also `0xF`). Functionally equivalent.
|
||||
|
||||
**The test:** Remove the MPSS pre-assign from `q6v5_mba_load` (lines 1236-1241 in `qcom_q6v5_mss.c`) — let MBA boot with only the MBA region assigned, matching LOS behavior. The MPSS assign happens later in `q6v5_mpss_load` anyway. If this clears the -22, DELTA 1 was the gate.
|
||||
378
saf/device/evidence/tz_listener_archaeology.md
Normal file
378
saf/device/evidence/tz_listener_archaeology.md
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
# PAF · qseecomd / TZ-listener archaeology on the LOS vendor extraction
|
||||
|
||||
Scope: identify exactly which AP-side TrustZone components (qseecomd + listeners) the modem's
|
||||
secure-storage (SFS) path depends on, using the already-extracted vendor stack. No device/ssh
|
||||
touched; read-only archaeology on `android-reference/`.
|
||||
|
||||
Prior art read first (do not re-derive): `HANDOFF.md:560-587` (the IMEI-mismatch finding that
|
||||
opened this frontier — "SFS/secure-EFS layer... Keymaster decrypts persist blobs | none"),
|
||||
`PAF/modem.md:610-634` (Android-vs-pmOS service parity table, `Keymaster | decrypts persist blobs
|
||||
| none | IMEI encrypted at rest`), `android-capture-20260622/EFS_FINDINGS.md` (2026-06-22, **now
|
||||
superseded** by the later IMEI finding — its "no persisted modem-side state is missing" conclusion
|
||||
was written before the TEST-IMEI-under-pmOS discovery).
|
||||
|
||||
## Materials actually available (correction to task brief)
|
||||
|
||||
`pixel3-work/los-vendor/vendor.img` does **not exist** in this project. What exists instead is
|
||||
better: a fully unpacked vendor partition tree at
|
||||
`android-reference/vendor-stack-20260621/vendor/{bin,lib,lib64}/` — no debugfs/7z mounting needed,
|
||||
direct file/strings access. Also present: `android-reference/init-rc/init.sdm845.rc` (29KB, the
|
||||
real device init.rc), `init.sdm845.mpssrfs.rc`, `init.sdm845.diag.rc`, `qcrild.rc`; and
|
||||
`android-live-20260619/` (boot/radio logs, partition map, rmt_storage rc — no qseecomd rc captured
|
||||
live).
|
||||
|
||||
## CONFIRMED — binaries and libraries present
|
||||
|
||||
`find android-reference/vendor-stack-20260621/vendor -iname '*qseecom*' -o -iname '*listener*' -o
|
||||
-iname '*keymaster*' -o -iname '*rpmb*' -o -iname '*ssd*' -o -iname '*sfs*' -o -iname '*drmfs*'`:
|
||||
|
||||
- `bin/qseecomd` — the listener-service host daemon (stripped, ELF aarch64, Android 31 ABI)
|
||||
- `lib64/libQSEEComAPI.so` — QSEECom userspace API (talks to `/dev/qseecom` char device via ioctl)
|
||||
- `lib64/librpmb.so`, `lib/librpmb.so` — RPMB listener backend
|
||||
- `lib64/libssd.so`, `lib/libssd.so` — SSD ("Secure Storage Data") listener backend
|
||||
- `lib64/libdrmfs.so`, `lib/libdrmfs.so` — DRM/persist-file listener backend
|
||||
- `lib64/libadsp_default_listener.so`, `libcdsp_default_listener.so`, `libssc_default_listener.so`
|
||||
— per-DSP FastRPC default listeners (ADSP/CDSP/SLPI-adjacent, not modem-SFS)
|
||||
- Keymaster stack: `android.hardware.keymaster@{3.0,4.0,4.1}.so`, `libqtikeymaster4.so`,
|
||||
`libkeymaster4support.so`, `libkeymasterprovision.so`, `nos_app_keymaster.so`,
|
||||
`bin/hw/android.hardware.keymaster@4.0-service-qti`,
|
||||
`bin/hw/android.hardware.keymaster@4.1-service.citadel` (Citadel = discrete security chip
|
||||
StrongBox, separate from the SDM845-embedded TZ/QSEE path — Pixel 3 has both)
|
||||
- **No dedicated modem-SFS listener library exists in the extraction.** No `libsfs*`, no
|
||||
`*modem*sec*`, no `*qsee*modem*` name anywhere in `vendor/`. Grepping
|
||||
`libril-qc-hal-qmi.so` for `modemst|persist|tzstorage|sfs` returns nothing — the RIL HAL doesn't
|
||||
touch secure storage directly either. This means modem SFS most plausibly rides through the
|
||||
**generic** `ssd`/`rpmb`/`drmfs` listeners rather than a modem-specific one (Qualcomm's modem SFS
|
||||
client library lives in the *modem firmware*, calling out to these generic AP listeners — nothing
|
||||
AP-side is modem-branded).
|
||||
|
||||
## CONFIRMED — init.rc service definitions and ordering
|
||||
|
||||
`android-reference/init-rc/init.sdm845.rc`:
|
||||
|
||||
- Line 607-610: `service vendor.qseecomd /vendor/bin/qseecomd` — `class core`, `user root`, `group
|
||||
root`.
|
||||
- Lines 73-84 (in the `on init && property:vendor.skip.init=0` block, i.e. very early):
|
||||
```
|
||||
# QSEE rpmb listener need symlink for bootdevice
|
||||
wait /dev/block/platform/soc/${ro.boot.bootdevice}
|
||||
symlink /dev/block/platform/soc/${ro.boot.bootdevice} /dev/block/bootdevice
|
||||
# start qseecomd early as we mount system/ vendor/ early
|
||||
# vendor needs keymaster that needs qseecomd
|
||||
start vendor.qseecomd
|
||||
start keymaster-4-0
|
||||
# start Citadel keymaster (StrongBox) early
|
||||
start vendor.citadeld
|
||||
start vendor.keymaster-4-1-citadel
|
||||
```
|
||||
The comment **"QSEE rpmb listener need symlink for bootdevice"** confirms the RPMB listener
|
||||
requires `/dev/block/bootdevice` to exist before qseecomd's listener threads can start — an
|
||||
explicit ordering dependency the AP init script encodes and pmOS's init/OpenRC does not.
|
||||
- Line 281 (`on post-fs-data`): `wait_for_prop vendor.sys.listeners.registered true` — init
|
||||
**blocks** IPA firmware loading on this property.
|
||||
- The property `vendor.sys.listeners.registered` is set by `qseecomd` itself — confirmed by
|
||||
`strings bin/qseecomd`: `Total listener services to start = %d`, `%s: Started (Listener ID =
|
||||
%d)`, and the literal string `vendor.sys.listeners.registered` present in the binary.
|
||||
- `qseecomd` strings also show two fatal-if-failed gates:
|
||||
`ERROR: RPMB_INIT failed, shall not start listener services`
|
||||
`ERROR: SSD_INIT failed, shall not start listener services`
|
||||
`SEC_UI_INIT failed, shall not start secure ui listener` — i.e. RPMB and SSD backend init are
|
||||
hard prerequisites for the listener framework to come up at all; if either fails, **no** listener
|
||||
(including whatever handles modem SFS) starts.
|
||||
- `rmt_storage` (line 644-648, `class core`) and `modem_svc` (line 655-659, `class core`) are
|
||||
declared **after** qseecomd/keymaster in the file but all are `class core` — Android's init classes
|
||||
don't strictly serialize within a class by file order alone; the *documented* explicit dependency
|
||||
is the `wait_for_prop vendor.sys.listeners.registered true` gate on IPA (data path), not on
|
||||
rmt_storage/modem load directly. **No explicit `wait_for_prop` ties modem/rmt_storage start to
|
||||
qseecomd** in this rc — the dependency for modem SFS is therefore INFERRED (via the modem firmware's
|
||||
own runtime SFS calls reaching TZ reaching the AP listener), not an init-script ordering fact.
|
||||
|
||||
## CONFIRMED — where the secure blobs plausibly live
|
||||
|
||||
From `strings` on the listener backend libraries themselves:
|
||||
|
||||
- `libssd.so`: `/dev/block/bootdevice/by-name/ssd`, `/dev/block/platform/msm_sdcc.1/by-name/ssd` —
|
||||
a **dedicated GPT partition named `ssd`** ("Secure Storage Data"). `QSEECom_register_listener` /
|
||||
`ssd_init_service` / `ssd_read` / `ssd_write` confirm this backs a QSEECom listener that a TZ
|
||||
trustlet calls into for opaque encrypted blob storage.
|
||||
- `libdrmfs.so`: `/data/vendor/tzstorage/`, `/data/misc/qsee/`, `/mnt/vendor/persist/data/`,
|
||||
`/persist/data/`, plus XML-driven `gp_persist_path` config — a **file-backed** persist store
|
||||
(DRM/general-purpose TZ file storage), separate from the raw `ssd` partition.
|
||||
- `librpmb.so`: `/dev/block/bootdevice` (via `by-name` symlink), `/dev/block/mmcblk0rpmb`,
|
||||
`RPMB.128KB/256KB/512KB/1MB` size buckets — the actual **RPMB (Replay Protected Memory Block)**
|
||||
hardware region on the UFS/eMMC boot device, accessed via `rpmb_emmc_read/write` ioctls.
|
||||
|
||||
**Partition-map check — RESOLVED 2026-07-02, live on pmOS:** the June partition-map capture was
|
||||
incomplete, not authoritative. `ls /dev/disk/by-partlabel/` on the live phone tonight shows the
|
||||
`ssd` partition **does exist**. Also confirmed live: the UFS well-known RPMB LUN is present
|
||||
(`ufs-bsg0`, scsi target `0:0:0:49488`), and `/mnt/vendor/persist/data/` holds real factory (Oct
|
||||
2018 / May-Jul 2019 dated) SFS-shaped blob trees — main+`_Alt` twin dirs with `.bak` twins per
|
||||
file, matching Qualcomm's dual-copy anti-rollback layout. Both listener backends (`ssd`, `rpmb`)
|
||||
have real backing storage on this exact device; nothing is missing at the hardware/partition
|
||||
level. The gap is entirely software (no AP-side listener driver/daemon under mainline).
|
||||
|
||||
**INFERRED (not proven on disk):** modem SFS blobs (IMEI, RF identity, calibration trust anchors)
|
||||
most likely live in either the `ssd` partition (if present) or under `/persist/data/`
|
||||
(`libdrmfs.so`'s default paths overlap with the `persist` partition pmOS already mounts and rmtfs
|
||||
already serves plaintext EFS from). If modem SFS uses `/persist/data/` via `libdrmfs`, the **bytes
|
||||
are already visible to pmOS at the block level** (persist is not RPMB) but are TZ-key-encrypted
|
||||
**in the file contents**, decrypted only via a live TZ round-trip through `qseecomd`'s listener —
|
||||
consistent with "plain EFS I/O works, secure layer doesn't" exactly as HANDOFF.md describes.
|
||||
|
||||
## CONFIRMED — no modem-specific listener ID found
|
||||
|
||||
Searched every `.so` under `vendor/lib64` for modem+SFS co-occurring strings
|
||||
(`modem.*sfs|sfs.*modem|modemst|modem_fsg`): only hits were `android.hardware.radio@1.{3,4,5}.so`
|
||||
and `libril-qc-hal-qmi.so`, and those are RIL/QMI Android HAL symbols (`getModemStackStatus` etc.),
|
||||
**not** QSEEComAPI listener registration. No listener ID constant (e.g. `0x3000`/`8192`) was found
|
||||
readable in any stripped binary via `strings` — listener IDs are typically passed as integer
|
||||
arguments at `QSEECom_register_listener()` call sites, not embedded as readable strings; extracting
|
||||
them requires disassembly (Tier 3, out of scope for this pass).
|
||||
|
||||
## Could a mainline userspace replace this?
|
||||
|
||||
**What the dependency IS, precisely:** the modem firmware's own SFS client (inside the MPSS
|
||||
firmware blob, not AP code) makes a TZ syscall that TZ cannot service locally; TZ round-trips to
|
||||
the AP via a **QSEECom listener** (RPMB and/or SSD and/or DRM-persist backend, hosted by
|
||||
`qseecomd`), which does raw block I/O against either the RPMB hardware region or the `ssd`/`persist`
|
||||
partition and returns the (TZ-en/decrypted) blob. Without `qseecomd` alive and its
|
||||
`RPMB_INIT`/`SSD_INIT` having succeeded, TZ's callback to the AP has no listener to answer it —
|
||||
the modem's SFS call would time out or the TZ syscall would return an error, which is consistent
|
||||
with the modem parking itself in OFFLINE rather than crashing (a "deliberate refusal," matching
|
||||
`secure_load_tz_frontier.md`'s framing).
|
||||
|
||||
**Feasibility notes (brief — this doc identifies the dependency, does not scope the fix):**
|
||||
- Open-source reimplementations of `qseecomd`'s listener framework exist in the wild for other
|
||||
Qualcomm mainline bring-ups (postmarketOS/msm8916, sdm660, sdm845 devices with eMMC RPMB
|
||||
trustlets) — typically a minimal userspace daemon that opens `/dev/qseecom`, registers the RPMB
|
||||
and SSD listener IDs, and does raw partition I/O, without needing Android's HIDL/keymaster stack
|
||||
around it. This is architecturally plausible to port: `librpmb.so`/`libssd.so`'s job is generic
|
||||
block I/O, not Android-specific.
|
||||
- The harder unknown is whether TZ on this specific signed XBL/TZ image expects listener
|
||||
**registration handshake details** (listener ID, buffer negotiation, `QSEECom_register_listener`
|
||||
call sequence) that differ from a generic mainline shim — that requires either disassembling
|
||||
`libssd.so`/`librpmb.so` (small, no external deps, tractable) or the QSEECom kernel driver's ioctl
|
||||
ABI (mainline has a `qcom_scm`/`qseecom` driver already, so the kernel side may already exist —
|
||||
not checked in this pass).
|
||||
- Keymaster/Citadel (StrongBox) is a red herring for the modem specifically — HANDOFF's "Keymaster
|
||||
decrypts persist blobs" framing is plausible for *other* persist data but the modem SFS path more
|
||||
directly implicates RPMB/SSD listeners, not the Keymaster HAL itself (Keymaster protects Android
|
||||
keystore keys; the modem's own TZ trustlet does its own key derivation independent of Android
|
||||
Keymaster, per Qualcomm's usual architecture — **INFERRED**, not confirmed on this disk).
|
||||
|
||||
## What to try next
|
||||
|
||||
1. **Settle whether an `ssd` partition exists on this device.** Not in
|
||||
`android-live-20260619/partition-map.txt`; re-run a full `by-name` partition enumeration
|
||||
(`ls -la /dev/block/bootdevice/by-name/` on rooted Android, read-only) specifically looking for
|
||||
`ssd`. This single fact discriminates between the RPMB-only path and the SSD-partition path.
|
||||
2. **Capture `qseecomd`'s runtime behavior on Android** (read-only): `logcat -b all | grep -i
|
||||
qseecom`, and `getprop vendor.sys.listeners.registered` at various boot stages, to see exactly
|
||||
when listeners come up relative to `rmt_storage`/modem PIL load. Confirms whether the ordering
|
||||
this doc inferred from init.rc is real at runtime.
|
||||
3. **DONE 2026-07-02 — see "Live disassembly results" section below.** Listener IDs recovered:
|
||||
SSD = `0x5000`, RPMB = `0x6400`. (Both differ from the earlier chat-only agent's string-based
|
||||
guesses of `0x3000`/`0x2000` — those were unread relocation offsets, not real constants. Trust
|
||||
this section's numbers, not that prior report.)
|
||||
4. **Check whether mainline's existing `drivers/misc/qcom-qseecom` / `qcom_scm` kernel driver
|
||||
(if present in `references/sdm845-mainline-*`) already exposes `/dev/qseecom`** — if the kernel
|
||||
ABI is already there, only a userspace listener daemon is missing, which is a much smaller lift
|
||||
than expected.
|
||||
5. Cross-check this doc's ordering claims against `secure_load_tz_frontier.md`'s XPU/`assign_mem
|
||||
-22` findings — if the XPU crash-loop and the SFS-listener gap are the same root cause
|
||||
(TZ-side memory/permission setup Android does and mainline doesn't), the fixes may converge.
|
||||
|
||||
## Live disassembly results — 2026-07-02
|
||||
|
||||
Toolchain note: this system's `objdump` is x86-only (no AArch64 target compiled in — confirmed
|
||||
via `objdump -i`). `rizin` (0.8.2, installed) has its own capstone-based AArch64 disassembler and
|
||||
does NOT depend on binutils target support. Use `rizin -q -c 'e asm.arch=arm; e asm.bits=64; af @
|
||||
<addr>; pdf @ <addr>' <file>` for any ARM64 .so in this project. This works for stripped Android
|
||||
libs (dynsym exports still give function boundaries even with no local symtab).
|
||||
|
||||
### `libssd.so` (lib64, 11KB) — thin wrapper, confirms the theory exactly
|
||||
|
||||
Exports only 3 functions: `ssd_init_service`, `ssd_start`, `ssd_close`. All real work is in
|
||||
`libQSEEComAPI.so` (NEEDED entry) via `QSEECom_register_listener`/`_receive_req`/`_send_resp`.
|
||||
|
||||
- `ssd_init_service` (0x1014): opens `/dev/block/platform/msm_sdcc.1/by-name/ssd`, falls back to
|
||||
`/dev/block/bootdevice/by-name/ssd` on failure. Stores fd at offset `0x290` in a global struct.
|
||||
Confirms live phone finding: the `ssd` GPT partition is the real backing store, block-device
|
||||
raw I/O, no filesystem.
|
||||
- `ssd_start` (0x10e0): `QSEECom_register_listener(&global+0x298, 0x5000, 0)`.
|
||||
**SSD listener ID = 0x5000.**
|
||||
|
||||
### `librpmb.so` (lib64, 29KB) — real UFS RPMB driver, much richer than libssd
|
||||
|
||||
Exports: `rpmb_init_service`, `rpmb_start`, `rpmb_init`, `rpmb_default_init`, `rpmb_close`,
|
||||
`rpmb_exit`, `rpmb_read`/`rpmb_write`, plus separate eMMC (`rpmb_emmc_*`) and UFS (`rpmb_ufs_*`)
|
||||
backends selected at init — `rpmb_ufs_sg_dev_open`/`_close` uses the SCSI generic (`sg`) device,
|
||||
matching the live phone's `ufs-bsg0` / `0:0:0:49488` well-known LUN found tonight.
|
||||
|
||||
- `rpmb_start` (0x4a68): `QSEECom_register_listener(&global+0x918, 0x6400, 0)`.
|
||||
**RPMB listener ID = 0x6400.** (Rizin's disassembly initially mis-labeled the immediate-load
|
||||
instruction as `reloc.rpmb_ufs_read` because 0x6400 collides with a PLT slot address in its
|
||||
symbol table — decoded the raw instruction bytes by hand to confirm: `52 8c 80 02` =
|
||||
`MOVZ w2, #0x6400`. Don't trust rizin's auto-symbolization on immediate loads without
|
||||
double-checking raw bytes when the "symbol" name looks suspicious.)
|
||||
- `rpmb_ufs_send_request` (0x38b0, 536 bytes) is the actual TZ-facing wire call for UFS RPMB
|
||||
frames — not yet disassembled, next concrete step (see below).
|
||||
|
||||
### `libQSEEComAPI.so` (lib64, 32KB) — the real kernel ABI boundary
|
||||
|
||||
`QSEECom_register_listener(void **handle, uint32_t listener_id, uint32_t sb_size)` fully traced
|
||||
(0x43bc, 684 bytes):
|
||||
1. If `*handle == NULL`: `open("/dev/qseecom", O_RDWR)`, fd stored at struct offset `0x10`.
|
||||
2. ION shared-buffer alloc via internal `__QSEECom_ION_memalloc(handle, listener_id, 0x1b, sb_size)`
|
||||
— `0x1b` is the ION heap-id mask argument.
|
||||
3. `ioctl(fd, 0xc004970a, &mem_param_req)` — this is `QSEECOM_IOCTL_SET_MEM_PARAM_REQ` by ioctl
|
||||
number shape (`_IOWR` direction bits `0xc0`, size `0x04`, type `0x97`, nr `0x0a`).
|
||||
4. `ioctl(fd, 0xc0189701, &listener_req)` — `QSEECOM_IOCTL_REGISTER_LISTENER_REQ`. Request struct
|
||||
built at `var_60h`: `{listener_id: w21, ion_fd: w22 (or -1 on alloc fail path), sb_virt: x0,
|
||||
sb_size: w21 (dup)}` — needs one more pass to nail exact field order/padding against upstream
|
||||
downstream `qseecom.h`'s `qseecom_register_listener_req` struct definition (not yet fetched).
|
||||
5. On ioctl failure with `errno == 16 (EBUSY)`: returns `-2` ("already registered", not a hard
|
||||
error) instead of `-1`. `ssd_start`/`rpmb_start` both treat `-2` as success-equivalent (skip
|
||||
the error path, fall through to the pthread_create for the receive loop).
|
||||
|
||||
This is enough to write a minimal mainline `/dev/qseecom` listener client IF mainline's QSEECOM
|
||||
driver ever grows `SET_MEM_PARAM`/`REGISTER_LISTENER` ioctl support — which it currently doesn't
|
||||
(confirmed separately: `qcom_qseecom.c` has no ioctl surface at all, single hardcoded app table).
|
||||
So this ABI trace is necessary-but-not-sufficient; the missing piece is still kernel-side.
|
||||
|
||||
### Not yet done (next session, in priority order)
|
||||
|
||||
1. **`rpmb_ufs_send_request`** (librpmb.so 0x38b0) — the actual SCM/TZ syscall wire format for
|
||||
RPMB frames. This is the piece that would tell us whether RPMB access could theoretically be
|
||||
done via raw `qcom_scm_call()` from kernel space without the full listener ABI, or whether it
|
||||
genuinely needs the userspace round-trip through `/dev/qseecom`.
|
||||
2. **`QSEECom_receive_req`/`QSEECom_send_resp`** (libQSEEComAPI.so) — the listener's blocking
|
||||
receive-request / send-response loop, i.e. what actually happens after registration succeeds.
|
||||
This is the runtime protocol a mainline replacement daemon would need to implement.
|
||||
2b. Decode the exact `qseecom_register_listener_req` struct field layout — need upstream/downstream
|
||||
`qseecom.h` (check `references/los-kernel-blueline-4.9/drivers/misc/qseecom.h` if it exists,
|
||||
agent's TZ report cited `qseecom.c` but layout header wasn't confirmed read).
|
||||
3. **`libdrmfs.so`** — not touched this pass at all. Handles `/data/vendor/tzstorage/` and
|
||||
`/persist/data/` per the prior agent's string analysis; the `/mnt/vendor/persist/data/` factory
|
||||
SFS trees found live tonight are exactly its target. Same rizin workflow applies.
|
||||
4. Cross-check ioctl numbers `0xc004970a`/`0xc0189701` against mainline's `include/uapi/misc/
|
||||
qcom_essivtm.h`-style headers if any QSEECOM uapi header exists anywhere in the kernel tree —
|
||||
would confirm/deny whether mainline's ioctl numbering (if it ever adds these) needs to match
|
||||
Android's exactly, or if TZ itself doesn't care about the AP-side ioctl numbers (likely, since
|
||||
ioctls are a kernel-driver-internal convention, not part of the SCM wire protocol to TZ).
|
||||
|
||||
## `libdrmfs.so` disassembly — 2026-07-02
|
||||
|
||||
Two independent listener registrations found, NOT one:
|
||||
|
||||
- `fs_start` (0x239c, 112 bytes): `QSEECom_register_listener(&global+0x440, 0x5000, 0)`.
|
||||
**Registers the SAME listener ID as `ssd_start` (0x5000).** Not a bug/collision — either this
|
||||
is dead/legacy code in this build (superseded by `gpfs_start`), or `fs`/`ssd` are two client
|
||||
libraries that both legitimately attach to one shared TZ-side listener namespace. Not resolved
|
||||
which; `fs_start` has zero XML/dynamic-path logic (unlike `gpfs_start`), suggesting it's the
|
||||
older/simpler of the two and `gpfs_start` is what's actually active at runtime — check
|
||||
qseecomd's actual startup log strings (`Total listener services to start = %d`) against a live
|
||||
Android boot to see which of `fs`/`gpfs`/`ssd` actually get invoked, if that's ever recaptured.
|
||||
- `gpfs_start` (0x3ae8, 1260 bytes) — the real, dynamic one: `opendir()`-probes candidate config
|
||||
dirs, `xmlParseFile()`s an XML descriptor (this is the "XML-driven `gp_persist_path`" the prior
|
||||
agent found via strings), then `QSEECom_register_listener(&global+0x450, 0x7e000, 0x80)`.
|
||||
**GPFS (general-purpose filesystem) listener ID = 0x7e000.** Distinct ID space from ssd/rpmb —
|
||||
0x7e000 is far larger, consistent with Qualcomm's listener-ID allocation using a different
|
||||
range for "general purpose"/OEM-extensible listeners vs. the fixed system ones.
|
||||
|
||||
**Confirmed listener ID table (all three, disassembly-verified, not string-guessed):**
|
||||
|
||||
| Backend | Listener ID | Source function | Backing storage (live-verified tonight) |
|
||||
|---|---|---|---|
|
||||
| SSD | `0x5000` | `libssd.so:ssd_start` (+ `libdrmfs.so:fs_start`, same ID) | `ssd` GPT partition — confirmed present |
|
||||
| RPMB | `0x6400` | `librpmb.so:rpmb_start` | UFS RPMB well-known LUN — confirmed present (`ufs-bsg0`) |
|
||||
| GPFS | `0x7e000` | `libdrmfs.so:gpfs_start` | XML-configured paths, likely `/persist/data/`, `/data/vendor/tzstorage/` |
|
||||
|
||||
## RPMB transport mechanism — architecturally important finding
|
||||
|
||||
`rpmb_ufs_send_request_sense` (librpmb.so, 0x38b0) disassembled: builds a SCSI CDB (opcode byte
|
||||
`0x60`, a JEDEC UFS RPMB SECURITY PROTOCOL frame shape) and submits it via
|
||||
`ioctl(fd, 0x2285 /* SG_IO */, &sg_io_hdr)` against a raw SCSI-generic (`/dev/sg*`) device handle
|
||||
opened earlier by `rpmb_ufs_sg_dev_open`.
|
||||
|
||||
**This is plain kernel block-layer I/O — SG_IO passthrough, zero TrustZone/SCM/QSEECOM
|
||||
involvement in the actual RPMB frame transport.** The QSEECOM listener registration
|
||||
(`rpmb_start`, ID `0x6400`) is a SEPARATE mechanism: it's how the *modem firmware*, running in
|
||||
its own TZ-adjacent execution context, asks the AP to relay RPMB frames on ITS behalf (modem
|
||||
firmware has no direct UFS access — a TZ trustlet calls out to the registered AP listener, which
|
||||
does the SG_IO work and returns the result back through TZ to the modem).
|
||||
|
||||
**Why this matters for the mainline fix:** it means a mainline replacement doesn't need to
|
||||
reverse-engineer any SCM/TZ RPMB protocol — the RPMB wire format is bog-standard JEDEC UFS
|
||||
SECURITY PROTOCOL IN/OUT over SG_IO, which mainline's own kernel UFS/BSG stack already fully
|
||||
understands (this is not modem-specific or Qualcomm-specific at all). The ONLY missing piece is
|
||||
the QSEECOM listener registration/dispatch loop that lets TZ hand a pending RPMB request to
|
||||
userspace and get the SG_IO result back — i.e. exactly the `/dev/qseecom`
|
||||
`SET_MEM_PARAM`/`REGISTER_LISTENER`/receive-request/send-response ABI traced in the
|
||||
`libQSEEComAPI.so` section above. **The actual RPMB disk I/O is a solved problem already in
|
||||
mainline; only the TZ-listener relay is missing.**
|
||||
|
||||
## Updated priority for next session
|
||||
|
||||
Given the above, the size of "minimal mainline replacement" shrinks:
|
||||
1. A mainline QSEECOM listener client (kernel: needs `/dev/qseecom` ioctl support that doesn't
|
||||
exist yet in `qcom_qseecom.c` — this is the real blocking gap, confirmed structural earlier
|
||||
tonight) that can register listener IDs `0x5000`/`0x6400`/`0x7e000`.
|
||||
2. A userspace daemon that, on receiving a relayed request, does exactly what `librpmb.so`/
|
||||
`libssd.so` do: raw block I/O (SG_IO for RPMB, plain read/write for the `ssd` partition) —
|
||||
this part has NO remaining unknowns, it's now fully specified by tonight's disassembly.
|
||||
3. `QSEECom_receive_req`/`QSEECom_send_resp` (libQSEEComAPI.so) — still not disassembled; this is
|
||||
the blocking-loop protocol the daemon would need to implement against the new kernel ioctls.
|
||||
Next concrete disassembly target.
|
||||
|
||||
## `QSEECom_receive_req`/`QSEECom_send_resp` — full listener loop protocol MAPPED
|
||||
|
||||
The complete Android-side listener runtime protocol, fully traced (libQSEEComAPI.so):
|
||||
|
||||
**`QSEECom_receive_req(handle, out_buf, out_buf_len)`** (0x4d40):
|
||||
1. `ioctl(fd, 0x9705 /* QSEECOM_IOCTL_RECEIVE_REQ */, NULL)` — **blocks** until TZ has queued a
|
||||
request for this listener's shared buffer. No payload pointer passed to the ioctl itself — the
|
||||
request data is already sitting in the ION shared buffer registered at listener-registration
|
||||
time; this ioctl is purely a "wake me up" signal.
|
||||
2. On `ioctl` failure with `errno == -0x200` (i.e. `ERESTARTSYS`/interrupted): logs and treats
|
||||
distinctly from a hard error (retry-worthy, not fatal) — same handle-internal cached fields
|
||||
(`shared_buf_ptr`, `shared_buf_len`) at offsets `0x0`/`0x8` used to bounds-check.
|
||||
3. Once woken: bounds-checks the pending request size against `out_buf_len`, `memcpy`s the
|
||||
request out of the shared ION buffer into the caller-provided `out_buf`, then `memset`s the
|
||||
shared buffer clean.
|
||||
|
||||
**`QSEECom_send_resp(handle, resp_buf, resp_len)`** (0x4ea4):
|
||||
1. Bounds-checks `resp_len` against the registered shared-buffer size (handle offset `0x8`).
|
||||
2. `memcpy`s `resp_buf` into the shared ION buffer.
|
||||
3. Special-cases command type `0x13` at handle offset `0x1c` — zeroes an extra field, likely a
|
||||
listener-specific response-tag/status clear (not fully resolved, low priority).
|
||||
4. `ioctl(fd, 0x9706 /* QSEECOM_IOCTL_SEND_RESP_REQ */, NULL)` — hands the response back to TZ,
|
||||
also payload-free (data already placed in shared memory by the memcpy above).
|
||||
|
||||
**Full ioctl number set now confirmed (all four, disassembly-verified):**
|
||||
|
||||
| ioctl | Number | Direction | Purpose |
|
||||
|---|---|---|---|
|
||||
| `SET_MEM_PARAM_REQ` | `0xc004970a` | `_IOWR` | register the ION shared buffer with the kernel driver |
|
||||
| `REGISTER_LISTENER_REQ` | `0xc0189701` | `_IOWR` | register `{listener_id, ion_fd, vaddr, size}` |
|
||||
| `RECEIVE_REQ` | `0x9705` | `_IO` (no payload) | block until TZ has a pending request in shared mem |
|
||||
| `SEND_RESP_REQ` | `0x9706` | `_IO` (no payload) | tell TZ the shared-mem response is ready |
|
||||
|
||||
**This closes out the userspace-side unknowns entirely.** A minimal mainline listener daemon now
|
||||
has a complete, disassembly-verified spec to implement against, IF the kernel gains the matching
|
||||
`/dev/qseecom` ioctl surface. The three listener IDs (`0x5000` ssd, `0x6400` rpmb, `0x7e000` gpfs)
|
||||
plus this four-ioctl protocol plus the plain-SG_IO RPMB transport (previous section) is a complete
|
||||
picture of "what Android does" — the remaining gap is 100% kernel-side (`qcom_qseecom.c` needs
|
||||
these four ioctls implemented against real SCM calls) and TZ-handshake-side (whether this exact
|
||||
signed TZ image accepts registration from a differently-built AP driver — unknown, untestable
|
||||
without trying it).
|
||||
|
||||
## Session-end status (2026-07-02)
|
||||
|
||||
Every planned disassembly target for this pass is done: libssd.so, librpmb.so (both listener
|
||||
registration + RPMB transport), libdrmfs.so (fs_start + gpfs_start), libQSEEComAPI.so
|
||||
(register_listener, receive_req, send_resp — the full 4-ioctl protocol). Not yet touched:
|
||||
`libQSEEComAPI.so`'s `QSEECom_start_app`/`QSEECom_load_external_elf` family (TZ *app* loading,
|
||||
as opposed to *listener* registration — likely irrelevant to SFS/RPMB but unconfirmed) and
|
||||
`qseecomd` binary's own main()/startup logic (which of ssd/rpmb/gpfs it actually launches and in
|
||||
what order — inferred from init.rc gates so far, never disassembled directly).
|
||||
193
saf/device/mms.md
Normal file
193
saf/device/mms.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# PAF · MMS (picture messaging)
|
||||
|
||||
## STATUS (2026-07-15): MMS SEND WORKS end-to-end. Two loose ends remain.
|
||||
|
||||
**A real picture message sent successfully today** — image to +12897767709,
|
||||
508 KB, POSTed through Fido's MMS proxy and cleared from the queue. The core
|
||||
problem (reaching Fido's MMS servers with the right APN) is SOLVED. See
|
||||
"THE WORKING RECIPE" below.
|
||||
|
||||
Two things still not fully nailed:
|
||||
1. **No auto-start / lifecycle for the daemon.** `mmsd-tng` has no D-Bus
|
||||
auto-spawn or systemd service — it only runs when launched by hand. When it
|
||||
isn't running, sends silently pile up in the queue (that's how 4 messages
|
||||
got stuck). Needs a proper service unit.
|
||||
2. **Chatty says "MMSC is not configured."** The daemon's *internal* config is
|
||||
correct and it sends fine, but it does NOT expose the
|
||||
`org.ofono.mms.ModemManager` D-Bus interface (only `org.ofono.mms.Service`),
|
||||
which is what Chatty's settings menu reads. So Chatty can't see the MMSC even
|
||||
though sending works. Setting values in Chatty's own menu did NOT bring that
|
||||
interface up. Daemon-side interface-registration issue, not a wrong-value
|
||||
issue — the file `~/.mms/modemmanager/mms` already reads
|
||||
`MMS_APN=netsvcs`, `CarrierMMSC=http://mms.fido.ca`,
|
||||
`CarrierMMSProxy=205.151.11.13:80` (all correct).
|
||||
|
||||
---
|
||||
|
||||
## THE WORKING RECIPE (proven live 2026-07-15)
|
||||
|
||||
1. **APN = `netsvcs`** (NOT `mms.fido.ca`). Patched into the carrier database at
|
||||
`rootfs-overlay/usr/share/mobile-broadband-provider-info/serviceproviders.xml`
|
||||
(Fido entry, `<apn value="netsvcs">`, mmsc `http://mms.fido.ca`, proxy
|
||||
`205.151.11.13:80`). `mmsd-tng` re-reads that DB and writes the right values
|
||||
into `~/.mms/modemmanager/mms` — BUT only re-looks-up when its current MMSC is
|
||||
the placeholder `http://mms.invalid`, so to force a refresh you must
|
||||
`rm ~/.mms/modemmanager/mms` before restarting the daemon.
|
||||
2. **The `netsvcs` bearer must be connected AND have its IPv4 applied to the
|
||||
netdev.** ModemManager reports the IP but nothing installs it — apply
|
||||
manually (see cheat-sheet). Once the IP is on `qmapmux0.X`, the proxy becomes
|
||||
reachable and `mmsd-tng`'s `SO_BINDTODEVICE` binds sends to it correctly.
|
||||
3. With both in place, `mmsd-tng` POSTs queued messages through the proxy and
|
||||
they send. `AutoProcessOnConnection=true` means it auto-flushes the queue
|
||||
when a usable bearer appears.
|
||||
|
||||
**To reproduce a send right now:** ensure the `netsvcs` bearer is up with its IP
|
||||
applied, `rm ~/.mms/modemmanager/mms`, then
|
||||
`mmsdtng --debug` — it re-looks-up `netsvcs` from the DB and flushes the queue.
|
||||
|
||||
---
|
||||
|
||||
Texting (SMS) already works. This doc is about **MMS** — the picture/group
|
||||
messages. They still don't send/receive on their own yet, but today we found
|
||||
*why*, and proved the hard part (reaching Fido's MMS servers) actually works.
|
||||
|
||||
---
|
||||
|
||||
## ELI5 — what MMS even is, and why it's fussy
|
||||
|
||||
A plain text (SMS) is tiny, so it rides along on the same signaling channel the
|
||||
phone uses to talk to the tower — no internet needed. A picture message (MMS) is
|
||||
too big for that. So MMS actually works like this:
|
||||
|
||||
1. The tower sends you a tiny SMS that secretly means *"there's a picture waiting
|
||||
for you."*
|
||||
2. Your phone then has to **open a special little internet connection** and go
|
||||
**download the picture from the carrier's own server** (the "MMSC").
|
||||
3. To send a picture, your phone uploads it to that same server the same way.
|
||||
|
||||
The catch: that "special little internet connection" is **not** your normal data
|
||||
or WiFi. Carriers put their MMS server on a **private, members-only road** that
|
||||
only exists inside their own cellular network. You can't get to it from WiFi, and
|
||||
you can't get to it from the normal data connection either. You have to dial a
|
||||
**second, separate connection** specifically for MMS.
|
||||
|
||||
Every piece of confusion we hit came from that one fact: **MMS needs its own
|
||||
special door, and we kept knocking on the wrong ones.**
|
||||
|
||||
---
|
||||
|
||||
## The pieces (and the plain-English name for each)
|
||||
|
||||
| Thing | What it really is | Value for this SIM |
|
||||
|---|---|---|
|
||||
| **APN** | The *name of the door* you dial for a given service | `netsvcs` (NOT `mms.fido.ca`) |
|
||||
| **MMSC** | The *server* that actually holds the pictures | `http://mms.fido.ca` → `209.4.229.111` |
|
||||
| **MMS proxy** | A *doorman* you must go through to reach the MMSC | `205.151.11.13:80` |
|
||||
| **The daemon** | `mmsd-tng`, the background program that does all this for you | already installed |
|
||||
| **The phone book** | `serviceproviders.xml`, a big list of carrier settings the daemon trusts | had the WRONG APN |
|
||||
|
||||
---
|
||||
|
||||
## What we got wrong (and how we found the right answer)
|
||||
|
||||
### Wrong door #1: `mms.fido.ca`
|
||||
Every website, and the settings file already on the phone, said Fido's MMS APN is
|
||||
`mms.fido.ca`. We dialed it. The network slammed the door:
|
||||
`ServiceOptionNotSubscribed` — *"you're not signed up for that."*
|
||||
|
||||
That error sounds like an account/billing problem, but it wasn't. It just means
|
||||
**that door name doesn't exist on this SIM.** The public instructions are for
|
||||
manually typing settings into an Android phone; they don't necessarily match what
|
||||
the SIM is actually *provisioned* for underneath.
|
||||
|
||||
**How we found the truth:** we asked the modem to list the doors it *actually*
|
||||
knows about (`mmcli -m 0 --3gpp-profile-manager-list`). It answered with a real
|
||||
list, and there it was:
|
||||
|
||||
```
|
||||
profile 4: apn: netsvcs apn type: mms
|
||||
```
|
||||
|
||||
The real MMS door on this SIM is called **`netsvcs`**, not `mms.fido.ca`. Dialed
|
||||
`netsvcs` → it **connected immediately.** (Confirmed the same name shows up in the
|
||||
raw Android radio logs from this same SIM, so it's genuinely the carrier's name
|
||||
for it, not a fluke.)
|
||||
|
||||
> **Gotcha:** `ltemobile.apn` is the *generic Rogers-family* internet door
|
||||
> (Rogers/Fido/Chatr all share it). Some guides say "just use that for MMS too."
|
||||
> It's a red herring here — it's not Fido-specific, and it came up **IPv6-only**,
|
||||
> which can't even reach the IPv4 MMS server. `netsvcs` is the right door.
|
||||
|
||||
### Wrong door #2: trying to reach the server the normal way
|
||||
Once `netsvcs` connected, we tried to reach the MMS proxy (`205.151.11.13`) — and
|
||||
it hung, dead silence.
|
||||
|
||||
Two reasons, both now understood:
|
||||
|
||||
1. **The connection was "connected" but had no address actually plugged in.**
|
||||
The modem dialed the door and the carrier handed us an address
|
||||
(`10.145.x.x`) — but *nothing put that address onto the network card.* It's
|
||||
like being handed the keys to a car that nobody parked in your driveway. On the
|
||||
normal data connection, a program called NetworkManager does this parking step
|
||||
automatically. For our hand-dialed MMS connection, **nobody did it.** The
|
||||
interface had no IPv4 address at all.
|
||||
|
||||
The moment we manually "parked the car" (`ip addr add 10.145.146.168/28` onto
|
||||
the interface, plus routes to the proxy through the carrier gateway) — **the
|
||||
proxy answered.** Reliably, every time. That was the real unlock.
|
||||
|
||||
2. **The proxy only speaks "MMS language."** After it started answering, a plain
|
||||
web request (`curl`) to it still got silence. That's expected: Fido's proxy is
|
||||
a **WAP proxy** — it only understands the special MMS protocol (WSP), and
|
||||
ignores normal web traffic. So a normal browser test will *always* look like a
|
||||
failure even when the path is perfectly open. Only `mmsd-tng` speaks the right
|
||||
language to it.
|
||||
|
||||
---
|
||||
|
||||
## Where it actually stands right now
|
||||
|
||||
**PROVEN working:**
|
||||
- The correct APN is `netsvcs`, and it connects.
|
||||
- Once its IP is applied to the interface, **Fido's MMS proxy is reachable** (TCP
|
||||
handshake succeeds every time, from the bearer's own address).
|
||||
|
||||
**Still missing (the automation):**
|
||||
- Nothing yet automatically does the "park the car" step — dial the `netsvcs`
|
||||
MMS connection, apply its IP + routes to the interface, and hand it to
|
||||
`mmsd-tng` — at the moment a picture needs to go out, then tear it down after.
|
||||
- `mmsd-tng`'s own config keeps regenerating from the carrier phone book
|
||||
(`serviceproviders.xml`) with the wrong `mms.fido.ca` APN, so editing its
|
||||
local config by hand gets overwritten. Fixing the phone book entry is the
|
||||
durable path (started: patched to `netsvcs` in `rootfs-overlay/usr/share/
|
||||
mobile-broadband-provider-info/serviceproviders.xml`), **but** `mmsd-tng`'s
|
||||
lookup only matches an entry when the *active* bearer's APN equals the entry's
|
||||
`value=`, so the exact match behavior still needs verifying against the real
|
||||
code path before calling it solved.
|
||||
|
||||
**Next step:** re-test `mmsd-tng` end-to-end now that the network road is
|
||||
manually open (send one real MMS), to confirm the daemon itself works, *before*
|
||||
building the automation that opens the road on its own.
|
||||
|
||||
---
|
||||
|
||||
## Cheat-sheet: manually open the MMS road (for testing)
|
||||
|
||||
```sh
|
||||
# 1. dial the real MMS door, IPv4
|
||||
mmcli -m 0 --create-bearer="apn=netsvcs,apn-type=mms,ip-type=ipv4"
|
||||
mmcli -m 0 -b <N> --connect # note the interface (qmapmux0.X) + address
|
||||
|
||||
# 2. "park the car": put the handed-out IP onto the interface
|
||||
sudo ip addr add <address>/<prefix> dev qmapmux0.X
|
||||
|
||||
# 3. lay down the private road to the proxy + MMSC via the carrier gateway
|
||||
sudo ip route add 205.151.11.13/32 via <gateway> dev qmapmux0.X # proxy
|
||||
sudo ip route add 209.4.229.111/32 via <gateway> dev qmapmux0.X # MMSC
|
||||
|
||||
# 4. proof the road is open (TCP handshake to the proxy should succeed):
|
||||
python3 -c "import socket;s=socket.socket();s.bind(('<address>',0));s.connect(('205.151.11.13',80));print('proxy OPEN')"
|
||||
```
|
||||
|
||||
(A plain `curl` through the proxy will still hang — that's normal, it only
|
||||
speaks MMS, not web. Real proof is `mmsd-tng` sending a message.)
|
||||
837
saf/device/modem.md
Normal file
837
saf/device/modem.md
Normal file
|
|
@ -0,0 +1,837 @@
|
|||
# PAF · Modem bring-up
|
||||
|
||||
## ARCH STATUS (2026-07-10): WORKING — verified live on the Arch install.
|
||||
|
||||
`mmcli`: state connected, LTE, operator Fido, packet service attached,
|
||||
signal 73%. The whole solved stack made the jump to Arch: `modemsmem`
|
||||
is in our kernel tree (0c2eb6bf3 "soc: qcom: add modemsmem, wire up on
|
||||
blueline"), rmtfs/tqftpserv/pd-mapper/ModemManager run as services, and
|
||||
`blueline-clat.service` handles 464XLAT. Everything below is the research
|
||||
record of HOW the 52 wall fell (pmOS era, 2026-06/07) — the mechanism is
|
||||
identical on Arch; read it before touching anything modem-adjacent.
|
||||
Deep evidence docs referenced below live in `evidence/`.
|
||||
|
||||
## RESOLVED 2026-07-03 — modem ONLINE, registered, passing LTE traffic. Root cause of the 52 wall: missing modemsmem driver (SMEM item 134).
|
||||
|
||||
Google Pixel modem firmware reads SoC info from SMEM_ID_VENDOR0 (item 134) during boot;
|
||||
downstream writes it via `drivers/soc/qcom/modemsmem/`. Mainline never had the driver, so
|
||||
the MPSS RF task found nothing there and parked op-mode OFFLINE → every RF transition
|
||||
illegal → QMI 52. Fix: Richard Acayan's sdm670 mainline port of the driver (commit
|
||||
d9afe44b) + `google,modemsmem`/`qcom,smem = <134>` DT node — canonical tree `4ef9b6943`,
|
||||
pmaports `9d52b47813` (CONFIG_QCOM_MODEMSMEM=y). Verified live: op-mode online, Fido LTE
|
||||
registration home, IPv6 bearer passing packets. Full details in HANDOFF.md (2026-07-03).
|
||||
Everything below this line is the (closed) investigation record; the leads/ledger remain
|
||||
correct as history. Remaining open threads are identity (SFS/test-IMEI) and polish
|
||||
(IPv4 bearer, calls/SMS), not RF.
|
||||
|
||||
## TOP LEAD 2026-07-02 #15 — DIAG_DATA glink channel: AP CAN request it, modem acks but never completes its own open. Real firmware-behavior finding, not a kernel gap.
|
||||
|
||||
**Correction to this doc's own earlier framing tonight**: an initial pass concluded mainline's glink
|
||||
core lacked any AP-initiated ("locally initiated") channel-open primitive at all, and that reaching
|
||||
DIAG_DATA would need core `qcom_glink` infrastructure work. **That was wrong** — re-read
|
||||
`drivers/rpmsg/qcom_glink_native.c` properly: `qcom_glink_create_local()` (a complete, working
|
||||
implementation of exactly this, sends `GLINK_CMD_OPEN`, waits for both acks) already exists and is
|
||||
already wired into the standard `.create_ept` op every transport uses. The real, and much smaller,
|
||||
entry-point gap was just: no *driver* was calling `rpmsg_create_ept()` against a fresh channel name
|
||||
from an already-bound `rpdev`. Fixed with **zero new kernel code** — userspace's existing
|
||||
`RPMSG_CREATE_EPT_IOCTL` on `/dev/rpmsg_ctrl2` (the modem-edge control device; `rpmsg_ctrl0`/`1` are
|
||||
CDSP/ADSP respectively, confirmed via `/sys/devices/.../4080000.remoteproc/.../rpmsg_ctrl2`) reaches
|
||||
this exact path already. Built a 60-line test binary (`diag_open_test.c`, cross-compiled via
|
||||
pmbootstrap chroot) to drive it.
|
||||
|
||||
**Live result, confirmed clean and repeatable across multiple fresh single-attempt modem boots**:
|
||||
- `GLINK_CMD_OPEN` sent for `DIAG_DATA` → modem.
|
||||
- Modem sends `GLINK_CMD_OPEN_ACK` back, consistently in <10ms when it responds at all.
|
||||
- **The modem never sends its own reciprocal `GLINK_CMD_OPEN` back** — the other half of glink's
|
||||
bidirectional handshake `qcom_glink_create_local()` requires (`wait_for_completion_timeout(&channel
|
||||
->open_req, ...)`) before a channel counts as usable.
|
||||
- Tested whether this was just slow: bumped the wait from 5s→60s (kernel commit `7441ca3dc`), rebuilt,
|
||||
reflashed, ran ONE clean attempt on a fresh modem boot, waited the full 63.84s. **Still nothing.**
|
||||
Falsified the "just needs more time" theory outright. Reverted the bump (`b4185e71b`) — no reason
|
||||
to ship a 60s stall for a case that structurally doesn't complete.
|
||||
- Cross-checked against downstream (`references/los-kernel-blueline-4.9/drivers/soc/qcom/glink.c`):
|
||||
its `GLINK_CONNECTED` notification requires the exact same bidirectional
|
||||
`ch_is_fully_opened(ctx)` condition (checked from both the incoming-open and the open-ack RX
|
||||
handlers) — downstream isn't looser about what counts as "open," it's just event-driven with no
|
||||
hard timeout instead of blocking-with-timeout. So this isn't mainline enforcing an extra
|
||||
requirement Android doesn't have; it's the same requirement, and the modem doesn't satisfy it
|
||||
either way under mainline's bring-up.
|
||||
|
||||
**What this means**: the modem's DIAG service is reachable and responsive at the glink-negotiation
|
||||
level (it correctly acks a request for a channel it recognizes by name) but will not commit to
|
||||
opening it — a "deliberate partial refusal" shape that rhymes with the *entire* rest of this frontier
|
||||
(SIM/QMI all healthy, DMS answers reads, refuses RF-arming writes; modem parks `offline` not `LPM`).
|
||||
Whatever state gate blocks RF-arm plausibly also gates DIAG. **F3 access via this channel is not
|
||||
reachable by any change on the AP side alone** — this is now a real, evidenced, narrow finding, not
|
||||
an assumption. Retest if/when the LPM-vs-offline or any other frontier item moves; a modem that
|
||||
reaches a healthier init state might complete this handshake as a side effect.
|
||||
|
||||
Test binary + method preserved: `diag_open_test.c` (cross-built via `pmbootstrap chroot -b aarch64`),
|
||||
`RPMSG_CREATE_EPT_IOCTL` against `/dev/rpmsg_ctrl2`, then `open()` the resulting `/dev/rpmsgN` — same
|
||||
approach works for probing ANY named glink channel on ANY edge (swap `/dev/rpmsg_ctrl0/1/2` for
|
||||
cdsp/adsp/modem) if a similar question comes up for a different service.
|
||||
|
||||
---
|
||||
|
||||
## TOP LEAD 2026-07-02 #14 — frontier #8's 3-precondition test finally run: provisioning-churn fixed & shipped, LPM-vs-offline confirmed unreachable from AP side
|
||||
|
||||
**Fixed and shipped precondition (b) from frontier #8 below**: `blueline-uim-select.sh` was
|
||||
unconditionally deactivating+reactivating the primary-gw provisioning session even when it already
|
||||
matched the slot/AID it was about to select — churn on an already-correct session, and exactly the
|
||||
class of write the raw-QMI RESULTS.md proved times out during the modem's not-ready window. Patched
|
||||
to skip the deactivate/reactivate when the existing binding already matches
|
||||
(`device-google-blueline-modem-tools` 7-r18→7-r21, committed `1ed2df1788`). **Verified live**: the
|
||||
log now reads "primary-gw session already bound to slot 1 app 1 ... skipping stock
|
||||
deactivate/reactivate" — the session is genuinely left alone across a modem restart for the first
|
||||
time this whole investigation.
|
||||
|
||||
**Result: does NOT flip DMS online.** Immediately after the clean-session restart,
|
||||
`--dms-set-operating-mode=online` still returns `DeviceNotReady (52)`. Polled operating-mode every
|
||||
3s for 18s post-restart: **steady `offline` the entire window, never once observed `LPM`.** So
|
||||
precondition (c) — "modem in LPM (not offline) for the online flip" — is now confirmed, not just
|
||||
suspected, to never occur spontaneously on pmOS: the firmware decides `offline` vs `LPM` before any
|
||||
AP-side QMI write is even possible, so no host script/wrapper/choreography (this fix included) can
|
||||
create the window frontier #8 needed.
|
||||
|
||||
**Where this leaves it:** frontier #8's 3-precondition experiment (configs active + clean
|
||||
provisioning + LPM-not-offline) is no longer "untested" — it's tested, with (a) and (b) now cheaply
|
||||
satisfiable and (b) fixed for real, and (c) confirmed firmware-internal and unreachable from any
|
||||
AP-side lever. This converges with TOP LEAD #13's conclusion (SLPI/ADSP/fastrpc/choreography all
|
||||
closed, F3 narration is the remaining instrument) via a different path — both land on "the
|
||||
offline-vs-LPM decision happens inside the firmware before Linux gets a vote." **Next: same F3
|
||||
instrument #13 named, now aimed at a sharper question** — not generic RF-init narration, but
|
||||
specifically why the firmware selects `offline` over `LPM` at its own boot, since that's now the
|
||||
single remaining precondition standing between here and an online modem.
|
||||
|
||||
Also confirmed same session: mainline blueline has **no DIAG transport at all** (`/dev/diag` absent,
|
||||
no `diagchar`-equivalent config, no `DIAG` glink channel among the live rpmsg devices) — so
|
||||
`diag_capture.md`'s Side B is a real, confirmed dead end on the AP side as of kernel
|
||||
`7.1.1_p20260702080259-r2`; F3 access (if pursued) needs either a from-scratch DIAG-over-glink
|
||||
kernel path or stays Android-side-only (Side A) for comparison purposes.
|
||||
|
||||
---
|
||||
|
||||
## TOP LEAD 2026-07-01 #13 — fastrpc `pageslen` kernel bug FIXED & shipped; 52 wall now proven independent of adsprpcd/ADSP health too
|
||||
|
||||
**Real, named, diff-attached kernel defect — the first of the whole investigation — found, fixed, shipped, verified.** `fastrpc_init_create_static_process()` in `drivers/misc/fastrpc.c` advertised `inbuf.pageslen = 0` while actually passing one remote_heap page in `args[2]`; the sibling dynamic `fastrpc_init_create_process()` correctly sets `pageslen = 1` for the identical one-page layout. The DSP trusts the metadata field, believes zero pages were passed, and aborts `INIT_CREATE_STATIC` — surfacing in userspace as `Broken pipe` and the adsprpcd coredump cascade (`dsp information is incorrect err: -1` for ADSP+CDSP). One-line fix (`0`→`1`); matches `FisiFla/nabu-cachyos` `0005-fastrpc-fix-init-create-static-pageslen.patch` (SM8250, independently shipped for the identical signature, commit claims it also killed an SLPI crash loop there). Committed `e3c47d44e` in `references/linux-7.1.1-sdm845-CANONICAL`. **Also committed the previously-floating-uncommitted RTC read-only fix** (`68ffb014b` — the load-bearing half of the 0-SPMI-writes result; it had never been committed, only sitting in the worktree). Both shipped in kernel `7.1.1_p20260701093924-r1`, flashed to `boot_a`.
|
||||
|
||||
**Process correction (saved to memory `pmos-src-skips-apkbuild-patches`):** `pmbootstrap --src` builds the **working tree as-is**, not committed-only. A first build silently included the two then-uncommitted RTC/DTS files; caught by inspecting `chroot_native/mnt/pmbootstrap/source-override/`. "Bake as commits" is necessary but not sufficient — the worktree itself must be in the intended state at build time.
|
||||
|
||||
**Verified live post-flash:**
|
||||
- **adsprpcd: no longer a fatal crash-loop.** Previously coredumped every boot with `dsp information is incorrect` / `Broken pipe`. Now: coredumps ONCE on first probe, systemd restarts it (NRestarts=1), then runs stably — **successfully opens `adsp_default_listener` handles on ADSP (domain 0)**. Old fatal signatures absent from the journal. Residual non-fatal noise remains: `remote_handle64_invoke ... method 2 ... Operation not permitted` + `open_mod_table_handle_invoke ... Error 0xe` on domain 0 — a *separate* ADSP-invoke-permissions issue, not the pageslen bug; next fastrpc thread if pursued.
|
||||
- **SPMI write flood: still 0** — RTC read-only fix preserved across the rebuild.
|
||||
- **`/dev/fastrpc-sdsp` still ABSENT** → `hexagonrpcd-sdsp` still "Could not attach to FastRPC node: Operation canceled". The pageslen fix targets ADSP/CDSP static-process creation; the SDSP node is a different path (depends on SLPI remoteproc fastrpc edges), so the SLPI/sensor-island story is **not** changed by this fix.
|
||||
- **dms-ui-ready (r17 C binary): ran clean**, same `DMS 0x0064 RESULT: FAILURE (result=1 error=58)` as before (unchanged, expected — orthogonal to fastrpc).
|
||||
|
||||
**Modem 52 wall: STILL THERE — now proven independent of adsprpcd/ADSP health too.** First `mmcli --enable` of the entire session with a non-crashing adsprpcd (all chain units active: modem-qrtr-ready, qcril-autoselect, qcril-prov, dms-ui-ready, ModemManager): identical `QMI protocol error (52): 'DeviceNotReady'` on `Set Operating Mode=online`, op-mode stays `offline`, state `disabled`, lock `sim-pin2`, SIM on slot 2 (correct). Combined with TOP LEAD #12's SLPI proof, **the modem's RF-ready gate is now robust to the entire host-side DSP coordination layer** (SLPI state × 3 boots, adsprpcd/ADSP health × 1 boot). The ADSP-PD/servreg candidate is substantially weakened. The wall is the modem firmware's own internal decision; nothing host-side we can move has touched it.
|
||||
|
||||
**Where that leaves the candidate space:** of `los-mss-translated.c`'s four deltas — (1) EFS NV item, (2) bootloader/ABL verified-boot state, (3) subsystem coordination [SLPI/PD], (4) host-QMI-replay timing — #3 is now closed and #4 is substantially exhausted (DMS 0x0064 sends+responds but error 58; NAS mode-pref fails DeviceUnsupported pre-RF). #1/#2 were researched to ground with no static delta. The one instrument that could show the modem's *own* RF-init reasoning — F3 narration — remains uncaptured (QCSuper's blanket arm skips the NAS/RRC SSID ranges; per-range SET_RT_MASK arms cleanly but yields no traffic, and we lack a Qualcomm SSID name table to confirm those ranges *are* NAS/RRC). That F3 capture + SSID identification is the most likely remaining source of new signal.
|
||||
|
||||
---
|
||||
|
||||
## TOP LEAD 2026-07-01 #12 — SLPI independence now PROVEN (not just argued); DMS 0x0064 "UI ready" built, untested
|
||||
|
||||
**SLPI-state dependency is now definitively closed, with hard repeated evidence** (2026-06-30
|
||||
night → 2026-07-01, three separate boots, live hardware). Fixed a real regression along the way:
|
||||
`blueline-slpi-ready.sh` disabled SLPI crash-recovery for a "single-shot" start; when the SAR/CHRE
|
||||
firmware fault crashed it, the disabled-recovery path left the glink-edge channel permanently torn
|
||||
— `intent request timed out` storming every ~10s indefinitely, which took the modem's own QMI/QRTR
|
||||
transport down with it (`endpoint hangup`, `Connection reset by peer`). Fix: recovery must stay
|
||||
**enabled** (matches Android's `ssr_setup`/`ssr_diag`, which never disables it) so the kernel's own
|
||||
crash handler tears down/rebuilds glink cleanly; the script then does ONE clean attempt and parks
|
||||
SLPI (`echo stop`) once caught in a healthy `running` window, instead of crash-looping forever.
|
||||
Verified over three clean boots: SLPI crashed (0 times / 50 times / 0 times depending on timing),
|
||||
**every single time `DeviceNotReady` on RF-enable was byte-for-byte identical** regardless of SLPI's
|
||||
state (crashed continuously, running, offline, or cleanly parked). Also wired `modem-qrtr-ready.service`
|
||||
`After=`/`Wants=blueline-slpi-ready.service` to match Android's real architecture — `vendor.qcom.devup`
|
||||
(what `modem-qrtr-ready` mirrors) is **literally set by** `init.qcom.devstart.sh` right after it kicks
|
||||
SLPI (`echo 1 > /sys/kernel/boot_slpi/boot; setprop vendor.qcom.devup 1`), confirmed by reading the
|
||||
real Android script off the mounted LOS vendor image. Architecturally correct, safe, zero cost — but
|
||||
does not move the 52 wall. **Candidate #4 ("Subsystem coordination — SLPI PD state?") in
|
||||
`los-mss-translated.c`'s delta list is now closed.**
|
||||
|
||||
**Checked and closed a real diagnostic gap**: `qmicli --dms-get-operating-mode`'s output includes an
|
||||
optional "Offline reason:" line (libqmi `qmi_message_dms_get_operating_mode_output_get_offline_reason`,
|
||||
bitmask of `HOST_IMAGE_MISCONFIGURATION`/`PRI_IMAGE_MISCONFIGURATION`/`PRI_VERSION_INCOMPATIBLE`/
|
||||
`DEVICE_MEMORY_FULL`) that no prior capture had actually looked for. Checked the existing captured
|
||||
output (`tools/modem-online-provisioner/modem-online-provision-20260622-063154.log`): the field never
|
||||
appears — the modem doesn't populate any of the four documented reasons. Dead end, but a clean one.
|
||||
|
||||
**Built AND tested: `dms-ui-ready.py`/`.service` + a raw-C fallback. Sends clean, modem responds,
|
||||
52 unchanged.** `PAF/qcril_online_sequence.md` (prior session, full disassembly of
|
||||
`libril-qc-hal-qmi.so`) identified DMS message **0x0064** ("telephony UI ready") as the one
|
||||
load-bearing pre-online step QCRIL performs that pmOS had never replayed. The Python/GI
|
||||
implementation (`Qmi.Message` + raw TLV writers) **failed at runtime**: this libqmi build does
|
||||
not expose a generic raw-message type via GObject-Introspection at all — only per-message typed
|
||||
classes exist in the typelib, confirmed by enumerating `dir(Qmi)` live on the phone. Built a
|
||||
small C program instead, cross-compiled against the same libqmi (openimsd fork, 1.39.0) via
|
||||
`pmbootstrap chroot -b aarch64`, linked directly against the `.so` already on the phone. Ran it
|
||||
live: the modem answered 0x0064 with a real, understood protocol response (not "unsupported
|
||||
message"), `RESULT: FAILURE (result=1 error=58)`. Error 58's standard libqmi meaning
|
||||
(`WMS_ENCODING`, an SMS-service code) can't be its true meaning for an OEM DMS message — almost
|
||||
certainly a vendor-private error space reusing that numeric slot. Immediately retried
|
||||
`mmcli --enable`: **`DeviceNotReady` (52), byte-identical to every attempt all night.** Full
|
||||
detail + the C source path in `qcril_online_sequence.md`. Kept `dms-ui-ready.service` wired in
|
||||
the boot chain (best-effort, harmless, now proven to do something real) — the Python version in
|
||||
the package needs porting to the same raw-C approach before it'll actually work at boot; that's
|
||||
the concrete next step, not another blind guess.
|
||||
|
||||
---
|
||||
|
||||
## TOP LEAD 2026-06-26 #11 — THE 52 IS A MODEM XPU (SECURE-MEMORY) CRASH-LOOP
|
||||
|
||||
> **🔴 SUPERSEDED 2026-06-29.** Captured on the **pre-DELTA-1 r2 LLVM kernel**, where the modem
|
||||
> genuinely crash-looped on an XPU violation. **DELTA-1 (`c2923cb49` MPSS assign-ordering) fixed the
|
||||
> load** — on the current kernel the modem MSS runs *steady* (MBA boots, MPSS loads, `remoteproc
|
||||
> running`, **no XPU crash-loop, zero −22 on `4080000`**; the only crash-lines are the SLPI's
|
||||
> `SNS_REG_INIT`). Yet it **still parks OFFLINE / model 0 / 52**. So **52 is NOT an XPU crash-loop**
|
||||
> — it's a firmware-internal RF-init decision, kernel-independent (6.16.7 and 7.1-rc1 both give 52,
|
||||
> see #10). The −22 referenced below is the SLPI's fastrpc heap. Kept as the XPU/load investigation
|
||||
> record.
|
||||
|
||||
**Captured live on the rebuilt r2 LLVM kernel (slot A, full system, ssh).** The
|
||||
`DeviceNotReady (52)` wall is now explained at the mechanism level: the modem
|
||||
**boots then crash-loops on a hardware secure-memory protection violation**, so it
|
||||
can never complete RF-init and parks `offline`. dmesg (`qcom-q6v5-mss
|
||||
4080000.remoteproc`, remoteproc3):
|
||||
|
||||
```
|
||||
Booting fw image qcom/sdm845/Google/blueline/mba.mbn ... MBA booted ... loading mpss
|
||||
remote processor 4080000.remoteproc is now up
|
||||
crash #1: fatal error received: SFR Init: wdog or kernel error suspected.
|
||||
crash #2: fatal error received: modem_xpu_int.c:456:[modem_sec_xpu_I]XPU violation
|
||||
... port failed halt ... modem offline ... starting ... up ... crash ... (loop)
|
||||
```
|
||||
|
||||
**XPU = Qualcomm eXtreme Protection Unit** — hardware memory guard programmed by
|
||||
TrustZone. `modem_sec_xpu XPU violation` = the modem firmware accessed a memory
|
||||
region the XPU does not permit it → the modem's **secure memory regions are not
|
||||
assigned/programmed correctly** for mainline. The modem comes up, touches a
|
||||
protected region, XPU faults it, q6v5 recovers it, repeat. The "running" state in
|
||||
a one-shot `remoteproc/state` read is just a momentary up-window between crashes —
|
||||
do not trust a single-sample `running`; watch `journalctl -b -p err` over ~30s.
|
||||
|
||||
**This is the same TZ layer as the SLPI/fastrpc failure.** Every modem AND slpi
|
||||
restart also logs `qcom_scm firmware:scm: Assign memory protection call failed -22`
|
||||
(hyp-assign / `qcom_scm_assign_mem` → -EINVAL). The fastrpc patch *tolerates* it;
|
||||
the modem cannot — it needs its secure regions actually assigned. **Shared root:
|
||||
this blueline's TZ rejects mainline's secure-memory-assign, → modem XPU violation
|
||||
→ 52.** See `secure_load_tz_frontier.md`.
|
||||
|
||||
**Ruled OUT as the cause of 52 (this session, hard evidence):**
|
||||
- *Provisioning / MCFG* — software MCFG `Rogers_Canada` (Casey's real carrier) is
|
||||
`Active` (PDC `--pdc-list-configs=software`). Selecting the carrier config — the
|
||||
thing qcril/Set-Feature does on Android — is already done, and 52 reproduces
|
||||
anyway. Platform configs = 0 (blueline EFS gap) and PDC Set Feature 0x2E times
|
||||
out, but neither is the 52: the modem crashes below the QMI layer.
|
||||
- *Kernel config regression* — 52 reproduces on the corrected 1135-module r2 LLVM
|
||||
kernel with SLPI/SSC in. Not the GCC-stripped-config disaster.
|
||||
- *libqmi* — confirmed the openimsd fork is installed (apk desc: "openimsd fork,
|
||||
adds PDC Set Feature msg 0x2E"; ships `Qmi-1.0.typelib`). Not the blocker.
|
||||
|
||||
**[RETIRED 2026-07-01 — see HANDOFF CORRECTIONS]** ~~The one-per-boot online attempt was
|
||||
spent this boot ... Modem now poisoned until power-cycle.~~ Online attempts are retry-safe
|
||||
within a boot; QMI `Set Operating Mode = RESET (4)` cleanly reinitializes, no reboot needed
|
||||
(`tools/modem-online-provisioner/RESULTS.md`). Kept for history: that boot's attempt gave 52,
|
||||
mode stayed `offline`, serving-system not-registered/detached, signal InformationUnavailable.
|
||||
|
||||
**Next frontier:** make TZ accept the modem's secure-memory assignment — i.e. the
|
||||
XPU/hyp-assign region setup. Compare LOS pil-tz secure-load (mba region
|
||||
`qcom,mba-mem`, mpss region assign, `qcom_scm_assign_mem` VMID lists) vs mainline
|
||||
`q6v5_mpss` region handling on this device. The `assign -22` is the thread to pull.
|
||||
(qmicli also **segfaults** on `--pdc-list-configs` — separate libqmi/fork bug to log.)
|
||||
|
||||
## TOP LEAD 2026-06-21 #10 — KERNEL-REGRESSION HYPOTHESIS: DISPROVEN. Kernel layer ruled out airtight.
|
||||
|
||||
**Result (clean-boot test, 2026-06-21 eve):** built blueline on `sdm845-6.16.7-r0` (last pre-regression
|
||||
stable, 2025-09-19), flashed boot_b, booted clean. The modem boots **fully** on 6.16.7 — firmware
|
||||
loads (`Booting fw image …/mba.mbn` → `remote processor is now up`), ALL QMI services register
|
||||
(qrtr-lookup shows DMS/NAS/UIM/Voice/WDS/PDC…), UIM is healthy (card present, USIM `ready`, Primary-GW
|
||||
slot1/app1), `remoteproc2` = `running`. Yet `--dms-set-operating-mode=online` → **DeviceNotReady (QMI
|
||||
52)**, mode stuck `offline`, Networks `''`, Max TX/RX 0 — **byte-for-byte identical to 7.1rc1.** A
|
||||
second run on a *clean* boot (modem auto-started at boot-time, UIM freshly provisioned) reproduced the
|
||||
same 52, removing the manual-start confound. Per this lead's own triage rule (still-52 on the
|
||||
pre-regression kernel) = **the kernel is ruled out airtight.** The online wall is NOT a kernel
|
||||
regression; it is device-internal (Route B) — the modem's own firmware RF-init never self-completes, so
|
||||
it parks `offline` and refuses `online` (Android never has to ask; see frontier #8 REFRAME).
|
||||
6.16.7 is now a known-good working kernel for blueline (modem boots clean); either 6.16.7 or 7.1rc1 is
|
||||
fine going forward (both give 52). **The live frontier is Route B — see `frontier #8 REFRAME` below.**
|
||||
|
||||
### Firmware-path gotcha found + fixed (RECORD — bites any upstream-tree blueline DTB)
|
||||
The 6.16.7 *upstream* `sdm845-google-blueline.dts` requests firmware at
|
||||
`qcom/sdm845/pixel3/{mba,modem,adsp,cdsp,ipa_fws,a630_zap}.mbn`, but pmaports packages blueline
|
||||
firmware at `qcom/sdm845/Google/blueline/`. So the upstream-tree DTB silently fails `request_firmware`
|
||||
(ENOENT -2) → modem/ADSP/CDSP never boot (`remoteproc: Boot failed: -2`). The 7.1rc1 *pmaports* DTB
|
||||
uses `Google/blueline/` (correct) — which is why 7.1rc1 loaded firmware and 6.16.7 did not until fixed.
|
||||
**Fix applied on the rootfs (persists across reboots):**
|
||||
`ln -snf /lib/firmware/qcom/sdm845/Google/blueline /lib/firmware/qcom/sdm845/pixel3`
|
||||
Any future build using the upstream blueline DTB needs this symlink (or a DT patch correcting
|
||||
firmware-name paths) or the modem will not load firmware. (Stray: the 6.16.7 DTB also references
|
||||
`qcom/sdm845/oneplus6/venus.mbn` for venus — harmless, video-decoder only.)
|
||||
|
||||
### Kernel-swap procedure (RECORD — the modular-modem-stack gotcha)
|
||||
The blueline modem stack is **fully modular** on this config: `qcom_q6v5_mss`, `qrtr`/`qrtr-smd`,
|
||||
`qcom_glink_smem`, `qcom_sysmon`, `mhi*` all `=m`. So swapping kernels needs the matching modules on the
|
||||
rootfs or the modem never loads (a false 52). UFS + USB-gadget-NCM are built-in (`=y`), so a
|
||||
module-mismatched boot still reaches rootfs + USB. Working procedure (validated this session):
|
||||
build kernel .apk → `tar xzf` it → extract `lib/modules/<kver>/` → scp to phone → `sudo cp -a` into
|
||||
`/lib/modules/` + `depmod <kver>` → reboot. Boot.img = `cat vmlinuz <blueline>.dtb` + the standing
|
||||
initramfs, `mkbootimg` (header v0, base 0x0, kernel_off 0x8000, ramdisk_off 0x1000000, tags 0x100, the
|
||||
real cmdline `quiet splash … pmos_boot_uuid=2CE8-836F pmos_root_uuid=26733d78…`). USB iface
|
||||
`enp0s20f0u6` (cdc_ncm) needs manual bring-up each boot: `nmcli dev set … managed no; ip addr add
|
||||
172.16.42.2/24 dev …; ip link set … up`.
|
||||
|
||||
### Original #10 thesis + bisect-window analysis (HISTORICAL — hypothesis disproven above)
|
||||
Thesis (Casey): the 7.1rc1 bump (pmaports MR !8446, merged 2026-04-28, "basic smoke testing… Pixel 3")
|
||||
never validated blueline's modem; the modem may have regressed there. Corroboration: beryllium wiki
|
||||
flags 7.1.0-rc1-sdm845 for modem breakage on the same SoC; Joel Selvaraj had SDM845 modem working on
|
||||
mainline back at 5.11, so the capability predates the regression. **Running test:** rebuild blueline
|
||||
kernel against `sdm845-6.16.7-r0` (last pre-regression stable, 2025-09-19), flash boot_b, retry
|
||||
`--dms-set-operating-mode=online`. Outcomes: online → regression confirmed, bisect the bump; flaky
|
||||
like beryllium → kernel layer validated, inherit beryllium recipe; still DeviceNotReady(52) → kernel
|
||||
ruled out airtight, it's device-internal firmware/EFS state (Route B).
|
||||
|
||||
### Kernel-tree pull (2026-06-21) — what the AP-driver diff says about the bisect window
|
||||
Pulled `references/sdm845-linux-20260610-ONHOLD-DAVID-TREE` (codeberg `sdm845-next-20260610`, base **7.1.0**) and the
|
||||
`sdm845-next-20260306` tarball (base **7.0.0**) and diffed the modem-relevant subsystems:
|
||||
- **`drivers/remoteproc/qcom_q6v5_mss.c`: functionally identical** 7.0→7.1 (brace-whitespace only).
|
||||
- **`drivers/soc/qcom/pdr_interface.c` + `pdr_internal.h`: rename-only** — `SERVREG_LOCATOR_SERVICE`→
|
||||
`QMI_SERVICE_ID_SERVREG_LOC` (both 0x40), `SERVREG_NOTIFIER_SERVICE`→`QMI_SERVICE_ID_SERVREG_NOTIF`
|
||||
(both 0x42), `reason[257]`→`reason[SERVREG_PFR_LENGTH+1]`(=257). No behaviour change.
|
||||
- **`qcom_pd_mapper.c` present in BOTH trees** → the in-kernel pd-mapper is NOT new to 7.1 (already
|
||||
in-kernel by 20260306 / 7.0.0; it landed ~mainline 6.13, so 6.16.7 has it too). The "in-kernel
|
||||
pd-mapper is the regression" sub-hypothesis is weak; the **sdm845-mainline rebase** is the live one.
|
||||
- `qmi_interface.c`, `qmi_encdec.c`, `rmtfs_mem.c`: identical 7.0→7.1.
|
||||
**Implication for the bisect:** in the codeberg 7.0→7.1 window there is NO functional change in
|
||||
mss/pdr/qmi/rmtfs — so if the regression is real and in that window, it is NOT in those drivers
|
||||
(look at clk/regulator/interconnect/scm/smem/glink/IPA instead). **Caveat:** pmaports tracks
|
||||
`gitlab.com/sdm845-mainline/linux`, NOT codeberg `sdm845/linux` (the two repos the Matrix thread
|
||||
argues over), and my window (7.0→7.1) does not cover the 6.16.7→7.0 jump. So this scopes, it does
|
||||
not pin — Casey's 6.16.7 build is still the right swing.
|
||||
|
||||
### Upstream blueline DTS confirms our DT is already correct (kills #9 a second way)
|
||||
The 20260610 tree ships an **in-tree `sdm845-google-blueline.dts` + `sdm845-google-common.dtsi`**
|
||||
(model "Google Pixel 3"). Its modem nodes: `&mss_pil` = `blueline/mba.mbn`+`blueline/modem.mbn`,
|
||||
`&ipa` = `blueline/ipa_fws.mbn`, `mpss memory@8e000000` size `0x9800000`, `rmtfs-region@f2700000`
|
||||
vmid MSS_MSA. **`dt-work/blueline-pmos.dts` already matches all of these byte-for-byte.** Zero
|
||||
WTR/QFE/RFFE/FEM/GRFC regulator nodes in the upstream blueline DTS — same as fajita, same as pmOS.
|
||||
So the pmOS modem DT is upstream-aligned, not a fajita guess. RF power is modem-firmware/RFFE driven
|
||||
(see `dt-work/rf-rail-diff-FINAL.txt`). **Lead #9 (add 11 pm8998 LDOs) is refuted** by (a) the
|
||||
upstream blueline DTS not declaring them either, and (b) all 11 candidate LDOs showing use=0 from the
|
||||
AP on live working Android. Do NOT flash `dt-work/blueline-live.dtb` for this reason.
|
||||
|
||||
### qrild finding folded in — there is no hidden online handshake
|
||||
`references/qrild` (abandoned AOSPM RIL): its modem bring-up (`qrild_android_modem.cc` ctor) is
|
||||
DMS Set Operating Mode RESET → powerup → NAS register-indications, with `setRadioPower` a no-op stub.
|
||||
Same levers ModemManager already pulls — **no secret QMI sequence** that arms RF. This corroborates
|
||||
the #8 REFRAME (`qcril_bringup_capture.md`): on Android the modem firmware SELF-onlines at its own
|
||||
RF-init; replaying a "QCRIL/RIL online handshake" on pmOS is a dead end because there isn't one.
|
||||
|
||||
---
|
||||
|
||||
## (REFUTED 2026-06-21) #9 — pmOS DT under-defines the pm8998 regulator set
|
||||
|
||||
Refuted by #10 above (upstream blueline DTS does not declare these LDOs either; all 11 are use=0 on
|
||||
live Android) and by `dt-work/rf-rail-diff-FINAL.txt` (RF power is firmware/RFFE-driven, not AP-DT).
|
||||
Kept for the record; do NOT flash the LDO DTB. Original reasoning below.
|
||||
|
||||
Direction (Casey + GLM): the fix is in OUR pmOS implementation/DT, make it mimic Android. Concrete,
|
||||
evidenced bug found this session:
|
||||
|
||||
**Our mainline blueline DT instantiates only ~14 of the 24 pm8998 LDOs. Android defines all of them.**
|
||||
Missing from `dt-work/blueline-pmos.dts` rpmh-regulators (present in downstream Android DTB):
|
||||
`ldo4, ldo6, ldo8, ldo9, ldo10, ldo11, ldo15, ldo16, ldo18, ldo23, ldo27`.
|
||||
Voltages (from `dt-work/android-dtb/android-base.dts`, full diff in `dt-work/regulator-diff-android-vs-pmos.txt`):
|
||||
ldo6=1.856V ldo8=1.2V ldo9=1.704V ldo10=1.704V ldo11=1.0V ldo15=1.8V ldo16=2.704V ldo18=2.704V ldo23=3.0V.
|
||||
|
||||
Why this matters even though all 11 are `deviceless`/use=0 in BOTH regulator_summaries: **the modem
|
||||
votes its RF-frontend rails (WTR5975/PA/tuners) directly via RPMh, not the AP regulator framework.**
|
||||
If the AP DT doesn't define a rail in the rpmh-regulators node, it isn't in the Linux RPMh regulator
|
||||
set — so when the modem's RF-init requests it, the vote can't be satisfied/arbitrated. Modem RF-init
|
||||
fails → modem parks in `offline` and refuses LPM/online (DeviceNotReady / silent no-op). This fits
|
||||
every symptom: modem boots (DSP up) but RF never inits; `offline` won't flip; configs don't help.
|
||||
|
||||
Supporting evidence this session:
|
||||
- Modem FATAL crash in pmOS dmesg: `qcom-q6v5-mss: fatal error received: ...Asserted in
|
||||
wlan_vdev.c:_wlan_vdev_create` + `port failed halt` on stop (subsystem not halting cleanly →
|
||||
modem can't flush NV → MCFG selection lost across restart → 0 configs every boot).
|
||||
- Modem comes up `offline` (not LPM like Android); `set low_power`/`online` return rc=0 but mode
|
||||
never changes; PDC autoselect (Set Feature) does NOT persist across modem restart (RIL-layer, not
|
||||
firmware NV).
|
||||
|
||||
### Executable plan (start here next session — do NOT re-derive):
|
||||
1. **Diff our blueline DTS rpmh-regulators block vs UPSTREAM linux-next `sdm845-google-blueline.dts`**
|
||||
(Heidelberg's). Our pmaports kernel build may predate the full regulator set. If upstream already
|
||||
defines all LDOs, bump/patch the kernel DTS; if not, add them.
|
||||
2. **Add the 11 missing LDOs** to the `pm8998-rpmh-regulators` node in the blueline DTS at the Android
|
||||
voltages above; mark the RF-candidate analog rails `regulator-always-on` so they're up at RF-init.
|
||||
3. Build via pmbootstrap (`linux-postmarketos-qcom-sdm845`), flash `boot_b` (see platform.md), boot.
|
||||
4. `dmesg | grep -iE 'regulator|vreg|q6v5|fatal'` — confirm no rail/halt errors; then
|
||||
`--dms-set-operating-mode=online` → `--dms-get-capabilities` Networks non-empty = RF armed.
|
||||
5. **Leverage other devs (Casey): do NOT silo.** Cross-check the SDM845 mainlining wiki, sargo/bonito
|
||||
(Google sister devices) DTS, and the `#sdm845:postmarketos.org` channel for how RF rails are
|
||||
declared. Heidelberg's blueline series on lore.kernel.org is the authoritative reference.
|
||||
|
||||
Artifacts: `dt-work/regulator-diff-android-vs-pmos.txt`, `dt-work/android-dtb/android-base.dts`
|
||||
(decompiled downstream DTB), `dt-work/blueline-pmos.dts`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
The modem is a self-contained DSP (MPSS) running Qualcomm firmware. Linux only loads it, powers
|
||||
it, and talks QMI to it. RF is the modem's own internal state machine — Linux cannot drive RF
|
||||
directly. Read this stack bottom-up; the blocker is at L6.
|
||||
|
||||
## The bring-up stack
|
||||
|
||||
| L | Layer | What it is | Status (2026-06-19) |
|
||||
|---|---|---|---|
|
||||
| L0 | Silicon / RF | SDM845 SoC; **SDR845 + WTR5975** transceivers; pm8005/pm8998 PMICs | fixed HW |
|
||||
| L1 | Firmware load | `mba.mbn` (238 KB) → `modem.mbn` (67 MB MPSS), via remoteproc `q6v5-mss` | healthy — "remote processor is now up" ~12.8 s |
|
||||
| L2 | Power | rpmhpd domains `cx`/`mx`/`mss`; `mss`→`mss.lvl` ARC→`pm8005_s2_level` | healthy — `q6v5` votes `mss` INT_MAX; verified vs Android |
|
||||
| L3 | Transport | QMI over QRTR; `glink-edge` to the modem PID | healthy — all QMI services register |
|
||||
| L4 | Host services | `rmtfs` (EFS), `pd-mapper`, `tqftpserv`, `qrtr-ns` | **CRITICAL 06-19**: rmtfs binary was replaced (69KB→141KB) and config changed from `-r -P -s` to `-P -s`, causing EFS corruption + modem crash + system freeze at "[stack] dump". Original binary at `/usr/bin/rmtfs.orig-20260618` must be restored. **CORRECTION (06-19 #2): `-r` = read-only is WRONG for arming the modem — Android serves EFS read-write and the modem writes modemst2 on boot. Use `-P -s` (read-write). The 02:38 corruption was the wrong binary, not the rw flag.** |
|
||||
| L5 | SIM / UIM | card detect, app select, primary-GW provisioning | healthy — `usim (2) ready`, slot1/app1 |
|
||||
| **L6** | **RF arm** | `DMS Set Operating Mode → online` arms the RF stack | **BLOCKED — DeviceNotReady (QMI 52).** The 06-19 #2 "rmtfs read-only" root-cause is **DISPROVEN (2026-07-02)**: rmtfs runs `-P -s` read-write live (drop-in enforced, all EFS partitions present) and 52 persists. rmtfs ruled out. Real cause is the modem's own boot-time RF-init decision (Route B), not an AP EFS-write path. |
|
||||
| L7 | Data / voice | IPA/rmnet, ModemManager, calls/SMS/data | gated behind L6 |
|
||||
|
||||
**The blocker, precisely:** `DMS Set Operating Mode=online` → **DeviceNotReady (QMI 52)**;
|
||||
`DMS Get Capabilities` → zero channel rates / empty networks = the RF stack never initialized
|
||||
inside the DSP. Everything upstream (L1–L5) is proven healthy. ModemManager reaches `enabling`,
|
||||
hits this, falls back to `disabled`, cycles.
|
||||
|
||||
## Eliminated leads — DO NOT RE-CHASE
|
||||
|
||||
Each killed with proof. The "proof" column is why it's dead, not a hypothesis.
|
||||
|
||||
| Lead | Verdict | Proof |
|
||||
|---|---|---|
|
||||
| rmtfs NV-files / `msm-cros-efs-loader` (serve EFS from file copies) | DEAD ( UPDATED 06-19 ) | **REAL CAUSE FOUND**: rmtfs binary was replaced (69KB→141KB) on 06-19 02:38 and service config changed from `-r -P -s` to `-P -s`. This broke EFS partition access, causing modem crash → "[stack] dump" → system freeze. Fixed by restoring `/usr/bin/rmtfs.orig-20260618` (69192 bytes) and resetting `/etc/systemd/system/rmtfs.service.d/writable.conf` to `-r -P -s`. The NV-files approach is dead; the original partition-mode rmtfs MUST be used. |
|
||||
| `removed-region@88f00000` carveout (was "lead #1") | DEAD | Android iomem: `88f00000-8aafffff : System RAM`. The device whose modem **works** leaves it allocatable. OnePlus-specific quirk. (06-19) |
|
||||
| cmd-db maps `mss.lvl` to wrong PMIC (was "lead #3") | DEAD | Android regulator tree: `4080000.qcom,mss → pm8005_s2_level` (correct rail). cmd-db is the same firmware blob on both OSes. pm8005 absent from pmOS `regulator_summary` is **expected** — mainline drives it via rpmhpd/genpd, not the regulator framework. (06-19) |
|
||||
| `vdd_mss-supply` / SPMI `pm8005@4` in DT | DEAD | Mainline `q6v5-mss` is power-domains-only for SDM845 (no `devm_regulator_get`). `qcom,pm8005` has **no SPMI driver** in mainline → adding `pmic@4/5` panics the kernel. Only `smps1/2` under the rpmh-regulators-2 block is safe. (06-18/19) |
|
||||
| "blueline lacks `soc-qcom-sdm845-modem` dep" | DEAD (stale doc) | Current pmaports pkg is `soc-qcom-modem`; blueline depends on it just like enchilada. Device-pkg deps + firmware manifests are equivalent. (06-19) |
|
||||
| fcc-unlock missing | DEAD | `fcc-unlock` exists nowhere in pmaports; no SDM845 device uses one. Modem is not FCC-locked-waiting. (06-19) |
|
||||
| Modem DT node differs from working device | DEAD | blueline modem node is byte-identical to fajita (same power-domains/clocks/resets); only firmware path + phandles differ. (06-18) |
|
||||
| Storage / EFS calibration is missing | DEAD | rmtfs writable + Android-primed EFS (modem online in Android first) → still DeviceNotReady in pmOS. EFS content is not what's missing. (06-18) |
|
||||
|
||||
**Net:** every kernel / DT / power / packaging layer is proven equal to blueline-**Android**
|
||||
(which runs the modem online, Fido LTE) on the **same silicon**. The blocker is not there.
|
||||
|
||||
## The frontier (REVISED 2026-06-20 #8 — QCRIL does more at bring-up; mainline stack under-initializes the modem)
|
||||
|
||||
Casey's standing thesis (correct, and the through-line of this whole effort): **QCRIL powers and
|
||||
initializes more of the modem than ModemManager does** — and mainline pmOS, as a minimal port,
|
||||
under-powers/under-initializes the board. We are hunting the specific delta. Evidence gathered:
|
||||
|
||||
1. **QCRIL sends a `'UI ready'` / power-on-optimization handshake to the modem that MM never sends.**
|
||||
Android cold-boot capture (`android-live-20260619/deep/radio-boot.txt`): `qcril_qmi_nas_telephony_ready:
|
||||
poweron_opt: send 'UI ready' to modem` (qcril_qmi_nas.cpp:17527), plus an active radio-power
|
||||
state machine (`qcril_qmi_nas_update_radio_power_request_info` → `BOOTUP_RADIO_POWER_OFF`,
|
||||
`qcril_qmi_nas_radio_power_transaction_handler`, state line `telephony LPM | ril NONE | card UP |
|
||||
modem ONLINE`). ModemManager has no equivalent of this poweron_opt / UI-ready signalling.
|
||||
2. **No DT RF node on either OS** (see retracted #6) — so the delta is QMI-level (modem-side
|
||||
init/power), not a Linux regulator we can add. The mainline DT brings up cx/mx/mss + the basic
|
||||
stack; it does NOT replicate QCRIL's QMI bring-up choreography.
|
||||
3. **Provisioning race (real bug, partially validated).** `msm-modem-uim-selection` fails every boot:
|
||||
it fires during the early QRTR window ("node with id 0 not found"), then its primary-GW provisioning
|
||||
ACTIVATE times out → service exits FAILURE → the live `primary-gw-provisioning` session loops
|
||||
`end-with-failure`/`app-reset`. Run manually AFTER boot it SUCCEEDS (`Successfully changed
|
||||
provisioning session`). KEY: with provisioning freshly+cleanly active, `--dms-set-operating-mode=
|
||||
online` **no longer returns DeviceNotReady(52)** (returns rc=0) — though the modem still stays
|
||||
`offline` because PDC configs were 0 that boot. We have never had configs-active AND
|
||||
clean-provisioning AND the QCRIL init handshake all at once.
|
||||
|
||||
4. **QCRIL online path captured (airplane off→on, `android-capture-20260620/qcril-online-capture.txt`):**
|
||||
`qcril_qmi_nas_dms_handle_modem_operating_mode` (qcril_qmi_nas.cpp:18289) → "modem's operating mode
|
||||
needs to be changed" → ril SETTING_OP_MODE → **modem LPM → ONLINE** in ~30ms (a plain DMS
|
||||
set-operating-mode=online). post-transition: `set_bootup_power_optimization_state=2`, ATEL UI-READY,
|
||||
enable networking/sys-info indications. **No hidden RF-power QMI command** — the modem is already
|
||||
powered (LPM) and the flip arms RF. **KEY STATE DIFFERENCE:** Android modem starts in **LPM**
|
||||
(low_power) and does LPM→online; the pmOS modem sits in **`offline`** (deeper state) and
|
||||
offline→online is what fails. So the gate is the combination: (a) MCFG configs loaded, (b)
|
||||
primary-GW provisioning active, (c) the modem in LPM (not offline) for the online flip. We have
|
||||
NEVER had all three at once on pmOS — that is the decisive untested experiment.
|
||||
|
||||
**The decisive instrument (next):** a QMI-message-level capture of QCRIL's bring-up on the rooted
|
||||
Android (enable QMI verbose radio logging, cold boot or airplane-toggle), then diff the QMI request
|
||||
sequence against ModemManager's (we already have MM's via `--debug`). The delta = exactly what to
|
||||
replay on pmOS (UI-ready/poweron_opt, any NAS/DMS/RF config QCRIL issues). Then replay on pmOS with
|
||||
configs active + provisioning clean. Rooted Android (slot B) is the instrument; keep this doc current
|
||||
as the capture lands.
|
||||
|
||||
---
|
||||
|
||||
## The frontier (REVISED 2026-06-20 #7 — live RF-init trace: blocker reframed to primary-GW provisioning, NOT RF cal)
|
||||
|
||||
Ran the decisive pmOS-side RF-init trace (slot B, kernel 7.1.0-rc1, modem over qrtr://0). Two
|
||||
big results — one closes a thread, one opens a sharper one.
|
||||
|
||||
**The modem is not faulting and nothing it requests is missing.** dmesg: clean boot, mba→mpss→
|
||||
running→handover, NO crash/coredump/fatal in the RF-arm window; an `online` attempt produces ZERO
|
||||
dmesg delta. tqftpserv serves only the readonly MBN tree (read OK) + an empty `mcfg.tmp` scratch
|
||||
file the modem RRQ/WRQ-loops on — and `mcfg.tmp` being 0 bytes is NORMAL (tqftpserv is bind-mounted
|
||||
to the **persistent** persist partition `/dev/sdf2[/rfs]` — NOT volatile tmpfs as first feared —
|
||||
and persist is SHARED with Android, so if the working modem needed content there it would persist;
|
||||
it's empty on both). So serving is correct and complete. **The modem boots healthy and simply
|
||||
REFUSES `online` at the QMI layer (DeviceNotReady = clean refusal, not a load failure).**
|
||||
|
||||
**NEW lead — primary-GW SIM provisioning session fails.** ModemManager's log during the refusal:
|
||||
`refresh indication received: session type 'primary-gw-provisioning', stage 'end-with-failure',
|
||||
mode 'app-reset'` — looping every MM retry (~72s), alongside a NAS system-info indication that
|
||||
returns `FAILURE: InformationUnavailable`. Operating mode `offline`, **HW restricted `no`** (not
|
||||
FCC/HW-locked), serving system fully detached, radio interface `none`. AND `msm-modem-uim-selection`
|
||||
**failed this boot**: "Application was already selected" → `operation failed: Transaction timed out`
|
||||
→ service exit FAILURE. Yet the static card read is healthy: Primary GW bound (slot1/app1), USIM
|
||||
app `ready`, personalization `ready`, PIN1 `disabled`. So the binding exists but the live
|
||||
provisioning SESSION keeps failing + app-resetting. This is distinct from (and more proximate than)
|
||||
the RF-cal lead — the modem may refuse `online` because primary-GW subscription provisioning never
|
||||
completes, not because RF is uncalibrated. (mBands=[] is non-diagnostic per #6; generic IMEI works
|
||||
on Android per Casey — so cal/QCN lead is weak.)
|
||||
|
||||
**OPEN — causality not yet proven.** Why does the provisioning session end-with-failure while the
|
||||
GW binding shows bound+ready? Candidates: (a) `msm-modem-uim-selection` timing out leaves the
|
||||
subscription half-selected; (b) the modem triggers an app-reset refresh (post-MCFG) that nothing
|
||||
on the AP side completes the way Android's RIL does; (c) downstream symptom of offline state.
|
||||
**Decisive next test (fresh boot, one clean shot):** on a fresh slot-B boot, before MM enables,
|
||||
(1) confirm whether the primary-gw-provisioning refresh fails on its own; (2) re-run UIM selection
|
||||
cleanly (the service timed out) and retry `online`; (3) capture the UIM/provisioning QMI sequence.
|
||||
Compare to what Android's RIL does on the same refresh. Known: a remoteproc reset (online is retry-safe, see CORRECTIONS)
|
||||
both clear UIM provisioning, so this needs a clean boot.
|
||||
|
||||
---
|
||||
|
||||
## The frontier (REVISED 2026-06-20 #5 — MCFG SOLVED; online blocker isolated to device-specific RF bring-up)
|
||||
|
||||
Live pmOS run this session SOLVED the MCFG provisioning blocker AND, by doing so, proved MCFG was
|
||||
never the RF-arm blocker. The online wall (`DeviceNotReady` QMI 52) survives every config/stack/SIM
|
||||
fix. Full elimination, all on hardware (slot B, kernel 7.1.0-rc1-sdm845):
|
||||
|
||||
**SOLVED — persistent MCFG self-provisioning.** `qcril-autoselect.py` (PDC Set Feature 0x2E,
|
||||
Selection Mode=3) made the modem self-load 2 platform (SS-LA-SDM845) + 25 software (Rogers_Canada)
|
||||
configs from the tqftpserv tree; survives reboot (self-loads at RF-init). Both ACTIVE; software
|
||||
version `0x801481A` == Android's exact value. The "0/0 every cold boot" blocker (frontier #3/#4)
|
||||
is dead. Tooling: `tools/qcril-autoselect.py` + `tools/build-openimsd-libqmi.sh` (openimsd libqmi
|
||||
fork, has Set Feature). Believed a Pixel 3 mainline first.
|
||||
|
||||
**RULED OUT as the online (RF-arm) blocker — every one tested on hardware:**
|
||||
- MCFG/PDC — both configs ACTIVE, matching Android. online still 52.
|
||||
- UIM/SIM — card present, USIM app `ready`, Primary GW slot1 app1 provisioned.
|
||||
- timing/lock — MM stopped, 10 patient online retries over 30s, all 52.
|
||||
- ModemManager — its full 9-step enable (power-state → op-mode indication register → Set Operating
|
||||
Mode 0x2E=online) returns the same 52. Not a manual-qmicli artifact.
|
||||
- missing stack — pd-mapper, hexagonrpcd, q6voiced, rmtfs (`-P -s` rw), tqftpserv ALL running;
|
||||
`soc-qcom-modem` installed; PD JSONs (modemr.jsn/modemuw.jsn) + firmware present; no errors.
|
||||
Matches working SDM845 devices (enchilada/fajita) component-for-component.
|
||||
- EFS not served — modemst1/2, fsg, fsc all populated (~260K nonzero) + served. (modemcal sdd4 is
|
||||
all-zero; persist sdf2 has data.)
|
||||
- firmware mismatch — modem sw version `g845-00194-210812-B-7635520` IDENTICAL to Android;
|
||||
revision `MPSS.AT.4.0.c2.17-00194-SDM845_GEN_PACK`.
|
||||
|
||||
**THE REMAINING BLOCKER = device-specific RF bring-up.** Same firmware + same shared EFS + complete
|
||||
stack + DT byte-identical to fajita, yet: NAS `get-system-info` empty, `nas-get-rf-band-info` →
|
||||
no bands, mode stuck offline, `DeviceNotReady` on online. The modem boots but its RF never inits.
|
||||
Two leads:
|
||||
1. **RF cal / QCN** — generic test IMEI `990012001581294`, model `0`, no bands. Classic
|
||||
no-calibration signature. UNCONFIRMED: does the WORKING Android show the REAL IMEI (→ pmOS isn't
|
||||
loading cal/QCN) or the same generic one (→ red herring)? Decisive next test: reboot slot A,
|
||||
read IMEI + DMS caps + bands; compare. (Not done autonomously — avoids leaving the phone
|
||||
off-pmOS while unattended.)
|
||||
2. **Board RF-frontend** — fajita (OnePlus 6T) works on mainline; blueline (Pixel 3) shares the
|
||||
q6v5-mss DT node but has a different PMIC/RF frontend (WTR/QFE/antenna tuners). If blueline's RF
|
||||
frontend needs power/GPIO/cal mainline doesn't provide for THIS board, the modem can't arm RF
|
||||
even with identical firmware. (Distinct from the DEAD pm8005/mss-rail lead — this is the RF
|
||||
frontend, not the modem-subsystem rail.)
|
||||
|
||||
### 2026-06-20 #6 — RETRACTED: do NOT treat the board-power lead as closed
|
||||
I previously wrote this section as "lead #2 CLOSED with positive proof." That was an overclaim and
|
||||
Casey was right to reject it. What the regulator/DT evidence below actually shows is narrower: there
|
||||
is no *labeled RF-frontend regulator node* in the device tree — confirmed on BOTH mainline AND the
|
||||
**downstream Android DTB** (`dt-work/android-dtb/android-base.dts`, decompiled from slot-A boot_a;
|
||||
its `qcom,mss@4080000` declares only vdd_cx/vdd_mx/vdd_mss, no RF/WTR/antenna node anywhere). That
|
||||
means we can't fix this by copying an "RF rail" node — NOT that board power is irrelevant. Mainline
|
||||
blueline is a **minimal fresh port**: it instantiates only rails with explicit consumers and leaves
|
||||
every other board rail off, whereas the downstream stack + QCRIL bring up far more (subsystems,
|
||||
power-on handshakes, NV/RF init via QMI). The live lead remains: **what does the modem need powered/
|
||||
initialized — via Linux rails the mainline port omits, or via QMI commands QCRIL sends — that pmOS
|
||||
does not provide.** See the #8 frontier (top of file) for the QCRIL-does-more evidence.
|
||||
|
||||
(original text retained below for the evidence it contains)
|
||||
|
||||
Brought the "power Android provides" thread full circle on live hardware (Android slot A, rooted):
|
||||
|
||||
- **No Linux-side RF rail exists on EITHER OS.** In both the Android (`rf-compare/regulators-android.txt`)
|
||||
and pmOS (`regulators-pmos.txt`) regulator dumps, the modem `4080000.qcom,mss` consumes ONLY the
|
||||
Q6 DSP level rails (s6_level=CX, s9_level=MX, s2_level=MSS). There is no WTR/PA/tuner regulator
|
||||
in Linux on Android either — RF-frontend power is driven by the modem firmware over RFFE + GRFC,
|
||||
not the Linux regulator framework. So a "missing regulator in the pmOS DT" was never possible.
|
||||
- **DT modem node is correct + complete** (`dt-work/blueline-pmos.dts`): power-domains cx/mx/mss,
|
||||
xo/bus/mem clocks, glink "modem" transport, `gpio-reserved-ranges = <0 4, 81 4>` (Linux leaves
|
||||
the modem-owned GPIOs alone). No regulator-supply phandles — correct for mainline q6v5-mss.
|
||||
- **Pinctrl: no AP/modem GPIO conflict.** Android TLMM (`3400000.pinctrl`) owns only camera-CCI,
|
||||
gpio-keys, and USB-extcon pins; the modem's GRFC/RF-control pins are MSS-internal and not
|
||||
AP-routed. The AP cannot be stepping on an RF pin.
|
||||
- **"No bands" is NOT a distinguishing signal.** The fresh working-Android radio capture
|
||||
(`android-capture-20260620/android-radio-log.txt`) shows `mBands=[]` in CellIdentityLte while
|
||||
fully attached to Fido LTE (REG_HOME, EARFCN 2000 = B4). So the empty mBands we saw on pmOS does
|
||||
not by itself mean RF is dead — weakens (does not kill) lead #1's reasoning.
|
||||
|
||||
**Verdict:** every AP-side power/GPIO/pinctrl/DT input the modem receives is provably equal to
|
||||
blueline-Android (which arms RF on the same silicon). Lead #2 is closed the same way IMEI closed
|
||||
lead #1. The blocker lives strictly inside the modem firmware's RF-init, which the AP only feeds
|
||||
via: firmware blobs, EFS (rmtfs), persist/rfs (tqftpserv), power-domains, clocks, QMI. All proven
|
||||
present/equal — EXCEPT we have never traced what the modem *requests* from rmtfs/tqftpserv during
|
||||
its RF-init, nor captured the modem's own failure reason.
|
||||
|
||||
**Decisive next experiment (pmOS-side, not yet run):** boot slot A, attempt `online`, and capture
|
||||
(a) `tqftpserv`/`rmtfs` request trace during RF-init — which file/NV path the modem asks for and
|
||||
whether it's served (frontier #4 showed Android rmt_storage *reads* on boot; we never logged the
|
||||
pmOS read side), and (b) the modem remoteproc coredump / SSR reason on the failed online. This is
|
||||
the one byte-for-byte trace we still lack; it points at the specific RF-init resource that differs.
|
||||
|
||||
Route B (autoselect EFS NV item) is DEPRIORITIZED: it would only persist the config state we
|
||||
already achieved, and config-active does not fix online.
|
||||
|
||||
---
|
||||
|
||||
## The frontier (REVISED 2026-06-20 #4 — Android boot is VERIFY-ONLY; the modem persists MCFG itself)
|
||||
|
||||
Source: analysis of the captured Android radio logs in `android-live-20260619/deep/`
|
||||
(`radio-boot.txt`, `radio-full.txt`, `ssr-capture.txt`), 2026-06-20. This **overturns** both
|
||||
frontier #3's "just make the platform config active" and the `qcril_full_provisioning.md §D.4`
|
||||
"load+activate every boot" model. Read this before either.
|
||||
|
||||
### What the captures actually show
|
||||
|
||||
Across all three logs (~12K PDC log lines, including a full modem SSR) QCRIL made **only query
|
||||
calls** — never load/select/activate, never `pdc_enable_auto_selection`:
|
||||
|
||||
- `ssr-capture.txt:24293` — the decision point:
|
||||
`qcril_qmi_pdc_is_modem_mbn_updated: prev_ver_info: MCFG-g845-00194-220517-B-8604334,
|
||||
cur_ver_info: MCFG-g845-00194-220517-B-8604334` → **prev == cur → reload SKIPPED.**
|
||||
- `ssr-capture.txt:23949` — `get_active_config_info_ind: config_version = 0x801481a`,
|
||||
`Update CNV from 0x48 to 0x48` (0x48 = **72** = `cnv=[72]` = `persist.vendor.radio.mbn0=72`).
|
||||
- Only PDC functions logged anywhere: `get_active_config_info`, `get_selected_mbn_config`,
|
||||
`get_selection_carrier`, `is_modem_mbn_updated`. **Zero** `*_load_config`, `*_select_config`,
|
||||
`*_activate_config`, `enable_auto_selection`, `enable_modem_update`.
|
||||
|
||||
### What that means
|
||||
|
||||
**Android's steady-state boot does almost nothing to PDC.** The modem holds its MCFG persisted in
|
||||
its own EFS (`modemst1/2` — the 2 MB write to `modem_fs2` the `rmt_storage` capture caught),
|
||||
**self-applies it at firmware RF-init every boot**, and QCRIL just confirms the active version
|
||||
matches `persist.vendor.radio.cnv.ver_info` and stops. The full load→select→activate path runs
|
||||
**only on first provision or an MCFG version change** (the `prev_ver != cur_ver` branch) — which
|
||||
we have **never captured**, because every capture was an already-provisioned device. That is
|
||||
exactly why `pdc_enable_auto_selection` never appears: it lives on the unobserved path.
|
||||
|
||||
### Consequences for the two prior theories
|
||||
|
||||
- **Frontier #3 ("just need an active platform config")** — necessary but NOT sufficient. PROGRESS
|
||||
L165–172 had BOTH configs active (SS-LA-SDM845 + Rogers) and DMS Networks stayed empty. Active
|
||||
≠ armed. Don't chase "make it active" as the finish line.
|
||||
- **`§D.4` ("load+activate every boot, accept two restarts")** — that is a *reconstruction of the
|
||||
unobserved first-provision path*, not what Android does in steady state. It remains a valid
|
||||
pmOS *workaround* (see `tools/qcril-prov.py`), but it is unproven on hardware and is not the
|
||||
mechanism that actually arms RF on Android.
|
||||
|
||||
### The pmOS problem, restated from the evidence
|
||||
|
||||
The "0/0 configs every cold boot" volatility (PROGRESS L140–144) is **not** something QCRIL fixes
|
||||
per-boot. It's that **the modem cannot persist/restore its PDC selection in EFS the way Android
|
||||
does.** The earlier "rmtfs read-write didn't fix it" result (ledger L38) is a chicken-and-egg:
|
||||
writable EFS is useless if the modem never had a valid MCFG to commit, and a one-shot userspace
|
||||
activate is useless if it cannot persist to survive the next firmware RF-init. **Both are
|
||||
required: a valid config loaded AND modem-side persistence that survives reboot.**
|
||||
|
||||
### Two candidate paths forward
|
||||
|
||||
- **(A) Per-boot provisioner workaround** — `tools/qcril-prov.py` loads HW+SW, selects, activates
|
||||
each boot. Mirrors the reconstructed first-provision path. Unproven; "active ≠ armed" is the
|
||||
open risk.
|
||||
- **(B) Provision-once + persistence (the Android mechanism)** — get a valid MCFG into the modem
|
||||
once, then ensure `modemst` persistence so the modem self-restores + self-applies at RF-init,
|
||||
and userspace reverts to verify-only. This is what the captures show actually works on hardware.
|
||||
|
||||
### The capture we still need (next experiment)
|
||||
|
||||
A normal-boot capture only re-shows the skip path. To record the **real first-provision load
|
||||
sequence** (and settle whether `pdc_enable_auto_selection` / `enable_modem_update` is a required
|
||||
precondition), **force a first-provision on Android**: clear `/data/vendor/modem_config/` and
|
||||
`persist.vendor.radio.cnv.ver_info`, reboot, then capture. Runbook:
|
||||
`PAF/first_provision_capture.md`.
|
||||
|
||||
---
|
||||
|
||||
## The frontier (SUPERSEDED 2026-06-19 #3 — see #4 above — PDC platform config is the blocker)
|
||||
|
||||
After a full day of Android-vs-pmOS forensics, the blocker is identified:
|
||||
|
||||
### What's actually wrong
|
||||
|
||||
The modem's **PDC (Policy Decision Control)** stores MBN configs in modem NV:
|
||||
|
||||
- **25 software (carrier) configs** loaded — **Rogers_Canada is ACTIVE** (133KB, v0x801481A)
|
||||
- **2 platform (hardware) configs** loaded — **SS-LA-SDM845 is INACTIVE** (56KB)
|
||||
|
||||
Without an **active platform config**, the modem doesn't know its RF hardware capabilities.
|
||||
DMS reports `Networks: ''`, NAS home network returns `NotProvisioned (QMI 16)`, and
|
||||
`Set Operating Mode=online` returns `DeviceNotReady (QMI 52)`.
|
||||
|
||||
On Android, QCRIL (`libril-qc-hal-qmi.so`) loads MBN files from `/vendor/rfs/msm/mpss/readonly/vendor/mbn/`
|
||||
via QMI PDC, activates both platform and software configs, and the modem restarts with full RF.
|
||||
|
||||
ModemManager does none of this — it assumes the modem is pre-provisioned.
|
||||
|
||||
### Attempted: direct PDC activation
|
||||
|
||||
`qmicli --pdc-activate-config=platform,6FE571A9...` returned **"Successfully requested config
|
||||
activation"** but triggered a modem restart that **cleared the platform config** (0 configs after
|
||||
restart). The activation request succeeded at the QMI level but the modem's internal handling
|
||||
removed the config during restart. Needs Android QCRIL to re-load it.
|
||||
|
||||
`qmicli --pdc-load-config=...` **segfaults** on MBN binary files (qmicli 1.39.0 / libqmi 1.38.0).
|
||||
Cannot load MBN files from pmOS side — libqmi bug or MBN format incompatibility.
|
||||
|
||||
### Key facts confirmed (06-19)
|
||||
|
||||
- **IMEI: identical** on Android and pmOS (`990012001581294`). NOT the blocker. Android works on
|
||||
Fido LTE with this same test IMEI.
|
||||
- **Modem firmware: identical** SHA256 (`97c1d19c...`) between slots A and B.
|
||||
- **Modem crashes** (SFR Init / XPU violation): **intermittent** — some boots 0 crashes, some 2.
|
||||
Even clean boots get DeviceNotReady. Not the blocker.
|
||||
- **rmtfs DOES serve correct partitions**: partlabels are GPT-stable; UFS LUN renumbering changes
|
||||
`/dev/sdX` names between boots but `by-partlabel` symlinks always resolve correctly.
|
||||
- **DMS Get Capabilities** consistently shows `Networks: ''` and `Max TX/RX: 0` on pmOS.
|
||||
Android shows populated networks (Fido LTE, Rogers 302720, full band support).
|
||||
- **NAS Get System Selection Preference** shows FULL band tables on both OSes (43 LTE bands +
|
||||
extended). NAS has the data; DMS doesn't. The bridge is the platform MBN activation.
|
||||
- **Android rmt_storage**: reads EFS on boot (no writes), writes 2MB to modem_fs2 AFTER going
|
||||
online. Normal operation — not initial provisioning.
|
||||
- **pmOS rmtfs journal**: empty (rmtfs doesn't log successful reads/writes). No errors either.
|
||||
|
||||
### Fix path
|
||||
|
||||
1. Boot Android to let QCRIL re-load the platform MBN into PDC (it was there before activation)
|
||||
2. Boot pmOS — do NOT touch PDC, do NOT run `qmicli` activation
|
||||
3. Verify: `qmicli --pdc-list-configs=platform` shows SS-LA-SDM845 as Active
|
||||
4. Verify: `qmicli --dms-get-capabilities` shows `Networks: 'gsm, wcdma, lte'`
|
||||
5. Let ModemManager drive enable→online (NEVER manual qmicli online)
|
||||
6. If platform config is still Inactive after Android: need to fix `qmicli --pdc-load-config`
|
||||
segfault, or write a minimal MBN loader using libqmi directly
|
||||
|
||||
### What we have on device
|
||||
|
||||
- rmtfs: read-write (`-P -s`), serving correct EFS partitions via partlabels
|
||||
- tqftpserv: bind-mounted to `/mnt/vendor/persist/rfs` (persist partition)
|
||||
- pd-mapper: installed and running from boot
|
||||
- MBN files: 179 files (11MB) at `/mnt/vendor/persist/rfs/readonly/vendor/mbn/`
|
||||
(extracted from Android `/vendor/rfs/msm/mpss/readonly/vendor/mbn/`)
|
||||
- MCFG version: `g845-00194-220517-B-8604334`
|
||||
- Rogers Canada MBN: `mcfg_sw/generic/NA/Rogers/Commercial/CA/mcfg_sw.mbn`
|
||||
- SDM845 LA/SS platform MBN: `mcfg_hw/generic/common/SDM845/LA/SS/mcfg_hw.mbn` (56KB)
|
||||
- Kernel: building from `sdm845/7.1-dev` HEAD (`85f1df2a`), config fixed to `sdm845`
|
||||
|
||||
### Android vs pmOS service comparison
|
||||
|
||||
| Component | Android | pmOS | Notes |
|
||||
|-----------|---------|------|-------|
|
||||
| EFS storage | rmt_storage r/w | rmtfs `-P -s` r/w | Parity |
|
||||
| TFTP | tftp_server → vendor/rfs | tqftpserv → persist bind | Parity |
|
||||
| pd-mapper | running (maps PDs) | running | Parity |
|
||||
| RIL | QCRIL (`libril-qc-hal-qmi.so`) | ModemManager | **Missing PDC provisioning** |
|
||||
| ipacm | running (`/dev/ipa` FDs) | not installed | Data path only |
|
||||
| Keymaster | decrypts persist blobs | none | IMEI encrypted at rest |
|
||||
| QMI services | comparable (50+) | comparable (50+) | Parity |
|
||||
|
||||
### (superseded) earlier frontiers
|
||||
|
||||
**Read-only rmtfs theory (06-19 #2):** rmtfs read-only prevented modem from persisting EFS
|
||||
writes. Fixing to read-write didn't resolve DeviceNotReady. EFS r/w is necessary but not
|
||||
sufficient — the modem needs valid MCFG in EFS first, which comes from PDC provisioning.
|
||||
|
||||
**QMI-handshake theory (original):** "MM doesn't send QCRIL's init sequence." Dead —
|
||||
same MM binary works on OnePlus 6. The missing sequence is PDC MBN loading, not a
|
||||
handshake gap. Other SDM845 devices likely have platform config pre-loaded from factory.
|
||||
|
||||
**fsg_oem theory:** Modem requesting `modem_fsg_oem_1/2`. Patched rmtfs to serve them.
|
||||
Modem never actually requests these when EFS is properly provisioned. Symptom, not cause.
|
||||
|
||||
**XPU violation theory:** Modem crashes twice on some boots. Even on clean (0-crash) boots,
|
||||
DeviceNotReady persists. Not the blocker.
|
||||
|
||||
## Useful commands (pmOS)
|
||||
|
||||
```sh
|
||||
qrtr-lookup | awk '$1==11' # UIM service → note Port
|
||||
qmicli -p -d qrtr://0/<port> --uim-get-card-status
|
||||
qmicli -d qrtr://0 --dms-get-operating-mode
|
||||
qmicli -d qrtr://0 --dms-set-operating-mode=online
|
||||
qmicli -d qrtr://0 --dms-get-capabilities # zero rates = RF uninit
|
||||
# clean modem re-init (rmtfs runs -P -s read-write; see 2026-07-02 correction):
|
||||
echo stop | sudo tee /sys/class/remoteproc/remoteproc2/state ; sleep 3
|
||||
echo start | sudo tee /sys/class/remoteproc/remoteproc2/state
|
||||
# preferred: QMI Set Operating Mode = RESET (4) reinitializes without touching UIM/provisioning.
|
||||
```
|
||||
|
||||
Forcing a remoteproc reset clears UIM provisioning; `msm-modem-uim-selection` only re-runs at
|
||||
boot. (The old note here claimed `-r -P -s` read-only was required for re-provisioning — stale
|
||||
and wrong; rmtfs runs `-P -s` read-write live and the modem re-provisions fine across a
|
||||
stop→start. For a clean reinit that does NOT drop provisioning, use QMI RESET, not remoteproc.)
|
||||
|
||||
## Cross-references worth mining
|
||||
|
||||
- **Working SDM845 (same SoC):** OnePlus 6/6T (enchilada/fajita), Poco F1 (beryllium),
|
||||
SHIFT6mq (axolotl) — data + SMS work via the same mainline kernel + pmaports packages.
|
||||
Their *AP-side* config is already in `~/pmos/pmaports`; nothing to pull. Their working
|
||||
firmware/EFS is device-specific and not transplantable.
|
||||
- **Pixel 3a (sargo) / 3a XL (bonito):** the closest *Google* analogs with modem support in
|
||||
pmaports (SDM670). Best target if a Google-specific RIL quirk is suspected.
|
||||
- **Android on this exact device** is the cleanest reference: same silicon, modem works.
|
||||
Diff blueline-Android vs blueline-pmOS, not against OnePlus.
|
||||
|
||||
## TOP LEAD 2026-07-02 #16 — locally-initiated DIAG_CTRL/DIAG_DATA open CRASHES the modem (glink_channel_migration assertion), repeatably
|
||||
|
||||
**Real, causal, previously-unknown finding — not a theory.** Following up #15's ack-but-no-reciprocal-
|
||||
open result: tried the same locally-initiated open against `DIAG_CTRL` (not just `DIAG_DATA`).
|
||||
Confirmed via `dmesg -T` (not my own client's stderr, which kept getting lost across SSH session
|
||||
churn — the KERNEL'S OWN log line is the ground truth here): `rpmsg rpmsg0: failed to open DIAG_CTRL`
|
||||
fires cleanly at the expected 5s mainline timeout, exactly as designed. But roughly 18-40 seconds
|
||||
LATER, every single time this was attempted (three separate occasions, two of them minutes apart on
|
||||
different modem boots), the modem hard-crashes:
|
||||
```
|
||||
qcom-q6v5-mss 4080000.remoteproc: fatal error received: [disable_ssr]glink_channel_migration.c:602:
|
||||
[IST336]Assertion status == GLINK_STATUS_SUCCESS failed
|
||||
remoteproc remoteproc3: crash detected in 4080000.remoteproc: type fatal error
|
||||
remoteproc remoteproc3: handling crash #N in 4080000.remoteproc
|
||||
```
|
||||
This is a **repeatable causal chain**, confirmed across crashes #5 and #6 in the same dmesg buffer,
|
||||
both immediately following a `failed to open DIAG_CTRL` line, both with the identical
|
||||
`glink_channel_migration.c:602` assertion. `glink_channel_migration.c` is the FIRMWARE's own glink
|
||||
channel-transport-migration logic (this is a modem-firmware-side log line, reported via SFR/fatal
|
||||
error, not an AP-side kernel message) — meaning the incomplete/abandoned local-open handshake left
|
||||
channel/migration bookkeeping in a state the modem's own firmware asserts on and can't tolerate.
|
||||
|
||||
**Practical, urgent implication**: DO NOT locally-initiate an open for `DIAG_DATA` or `DIAG_CTRL`
|
||||
casually or repeatedly during further investigation — it reliably crashes the whole modem MSS a
|
||||
short time later, disrupting whatever else was being tested (this crashed WLAN's firmware too, via
|
||||
the shared XPU/hyp-assign path, matching TOP LEAD #11's earlier finding about shared secure-memory
|
||||
fragility). If this path is pursued further, it needs to be done RARELY, with full crash-recovery
|
||||
tolerance, ideally on a boot with nothing else running that a modem SSR would disrupt.
|
||||
|
||||
**What this actually tells us, functionally**: the modem's glink/migration layer is NOT robust to an
|
||||
AP-initiated open attempt that times out — downstream never hits this because (a) it doesn't time out
|
||||
in 5s (event-driven, no hard deadline, per #15's downstream comparison) and/or (b) Android's DIAG
|
||||
daemon presumably completes successfully before any abandonment would occur. This is now a second,
|
||||
independent data point (alongside offline-vs-LPM, #14) that mainline's bring-up leaves this modem's
|
||||
firmware in a MORE FRAGILE state than Android's does — not just "missing a feature" but "structurally
|
||||
less tolerant of imperfect AP-side timing/sequencing." Worth weighing against #14's LPM finding: both
|
||||
point at the same underlying idea -- whatever Android's fuller bring-up sequence does before touching
|
||||
these channels, mainline's minimal sequence skips, and the modem firmware was never hardened against
|
||||
being touched without it.
|
||||
|
||||
**Honest gap, not yet resolved**: unclear if the crash is specific to DIAG_CTRL/DATA's particular
|
||||
migration-eligible status, or would happen for ANY locally-initiated open of ANY channel abandoned via
|
||||
timeout. Worth testing later (carefully, expecting a crash) against a channel we know is NOT normally
|
||||
migration-sensitive, to isolate whether this is DIAG-specific or a general "don't abandon a local
|
||||
open" hazard on this modem.
|
||||
123
saf/device/platform.md
Normal file
123
saf/device/platform.md
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# PAF · Platform — hardware, boot, storage, recovery (Arch era)
|
||||
|
||||
Load-bearing device facts. Add a line when something costs more than 2
|
||||
minutes to relearn. pmOS-era snapshot preserved at
|
||||
`evidence/platform-pmos-era.md` — its Alpine/ash and pmOS-slot notes no
|
||||
longer apply.
|
||||
|
||||
## Reaching the phone
|
||||
|
||||
- USB (primary): `ssh -F /dev/null -i ~/.ssh/ani -o BatchMode=yes casey@172.16.42.1`
|
||||
(NCM gadget, phone serves DHCP; laptop autoconfigures).
|
||||
- WiFi: DHCP on the LAN, drifts — check the router or `nmcli` on-device via USB first.
|
||||
- User `casey`, passwordless-sudo NO — sudo password `147147`
|
||||
(`echo 147147 | sudo -S <cmd>`). **Never** stream sudo-gated output through
|
||||
further pipes over SSH (silent truncation, repeatedly) — write to a file on
|
||||
the phone, pull it separately.
|
||||
- Shell is **bash on Arch** (systemd, GNU coreutils, pacman). The pmOS-era
|
||||
busybox/ash caveats are dead.
|
||||
- Serial fallback: ACM gadget → laptop-side `ttyACM0`; getty enabled on ttyGS0.
|
||||
|
||||
## Slots & boot architecture
|
||||
|
||||
- **Slot A = Arch (ours). Slot B = LineageOS (Android, rooted, on the 4.9
|
||||
vendor kernel).** Slot B is the living downstream reference AND the
|
||||
fallback OS — never touch `boot_b` or Android's partitions.
|
||||
- Boot chain: ABL boots `boot_a` (Android bootimg v0, our kernel Image.gz
|
||||
with **appended DTB** + the pmOS-chain ramdisk — see build.md). Root is
|
||||
found by UUID from the baked cmdline.
|
||||
- `qbootctl` (in the device package since ef21637) marks the slot successful
|
||||
and provides reboot-to-android; closes the A/B retry-lockout that used to
|
||||
eat boots.
|
||||
- Switch to Android: `sudo reboot bootloader` → `fastboot --set-active=b` →
|
||||
`fastboot reboot` (and back with `--set-active=a`).
|
||||
|
||||
## Storage (live table, verified on-device 2026-07-10)
|
||||
|
||||
UFS `/dev/sda`, GPT, **4096-byte sectors** (sgdisk needs `-a 256`; a tool
|
||||
assuming 512B sectors will misread the table).
|
||||
|
||||
| Part | Size | FS | Name | Role |
|
||||
|---|---|---|---|---|
|
||||
| sda21 | 42.9G | — | userdata | **Android's, FBE-encrypted, NEVER TOUCH** (near-formatted once — always verify identity before writing any partition) |
|
||||
| sda22 | 10.0G | fat32 | shared | Arch↔Android shared data, mounted /shared |
|
||||
| sda23 | 253M | vfat | Arch_boot | staging/aux boot filesystem |
|
||||
| sda24 | 66.2G | ext4 | Arch_root | the Arch rootfs, `UUID=a094e031-ea89-4847-a299-082a0ae8c9ab` |
|
||||
|
||||
`persist` lives on a **different UFS LUN (sdf2)** — physically immune to sda
|
||||
operations; mounted ro,nofail. Backup in `../blobs/` +
|
||||
`~/pixel3-backup/persist.img`.
|
||||
|
||||
- Boot-image A/B slots (`boot_a`/`boot_b`, 67MB each, partitions 11/12) are
|
||||
separate from the rootfs partitions above — `fastboot flash boot_a` is the
|
||||
only flash operation this project performs.
|
||||
|
||||
## Recovery
|
||||
|
||||
- TWRP: `fastboot boot ~/Downloads/twrp-3.7.1_12-0-blueline.img` (boot, not
|
||||
flash). Used for repartition-class work; a live pmOS/Arch root can't
|
||||
modify its own mounted partitions.
|
||||
- Known-good fallback kernel image (stmfts touch, pre-SWIRE display):
|
||||
`../staging/deploy-139aa/boot-139aa7748181-pmoschain.img` → flash to
|
||||
boot_a. Its module tree is on the phone.
|
||||
- Full stock partition clone (2026-06-18) at `~/pixel3-backup/`; LOS boot
|
||||
image at `~/Downloads/lineage-22.2-20260629-boot.img`.
|
||||
- Last resort: slot B is a complete, working Android.
|
||||
|
||||
## Hardware map (what's proven on this exact unit)
|
||||
|
||||
- SoC sdm845, 4G RAM. Panel: **LG SW43408** (DSC command-mode, 1080x2160) —
|
||||
settled from per-unit persist calibration data; the Samsung s6e3ha8/sec_ts
|
||||
DTBO fragments are the OTHER hardware variant, not ours.
|
||||
- Touch: **ST FTS @ 0x49 on i2c-2 (888000 geni SE, GPI-DMA-only)**; reset
|
||||
tlmm 99 (active-low RESETB), IRQ tlmm 125, AP/SLPI bus switch tlmm 136.
|
||||
- OLED bias: PMI8998 LAB/IBB under **SWIRE (DDIC hardware) control** — see
|
||||
display.md.
|
||||
- WiFi wcn3990 (ath10k_snoc), BT (hci_uart/btqca), modem MPSS (see
|
||||
modem.md), sensors behind SLPI DSP (see slpi.md), audio WCD9340 over
|
||||
SLIMbus (working HiFi speaker, microphone and two-way calls; see audio.md).
|
||||
- Power button = pm8941_pwrkey → input event; volume keys GPIO.
|
||||
|
||||
## Charging (pmi8998, `qcom_smbx`)
|
||||
|
||||
Two supplies: `pmi8998-charger` (the charger) and `qcom-battery` (fuel
|
||||
gauge). The charger IC does full CC/CV — trickle, pre, fast, full-on,
|
||||
taper — and `BATTERY_CHARGER_STATUS_1` bits 0:2 report which stage it is
|
||||
in. Mainline read that register only to derive `status`, collapsing all
|
||||
five states into "Charging" and discarding the detail.
|
||||
|
||||
Since f08dc70b3 the driver exposes them:
|
||||
|
||||
| attribute | register | notes |
|
||||
|---|---|---|
|
||||
| `charge_type` | `BATTERY_CHARGER_STATUS_1` (0x06) | read-only; Trickle/Fast/Standard |
|
||||
| `constant_charge_current_max` | `FAST_CHARGE_CURRENT_CFG` (0x61) | 25000µA/step, 8-bit |
|
||||
| `voltage_max` | `FLOAT_VOLTAGE_CFG` (0x70) | `raw=(uV-3487500)/7500+1`; 4.4V → 122 |
|
||||
| `charge_control_end_threshold` | same as above | percentage form, 0-100 |
|
||||
|
||||
**Lowering the float voltage is what caps the charge.** The cell is held
|
||||
at whatever CV target is programmed, so 4.1V parks it near 80% and buys
|
||||
calendar life. Writes are bounded twice — by the DT cell rating
|
||||
(`voltage-max-design-microvolt = <4400000>` in
|
||||
`sdm845-google-blueline.dts`) and by what the 8-bit fields can encode, so
|
||||
neither an overcharge nor a silent masked-off write is possible.
|
||||
|
||||
**These registers are volatile — every setting is lost on reboot.** Use
|
||||
`/etc/blueline-charge-limit` + `blueline-charge-limit.service` to persist
|
||||
one. Front end is `blueline-charge status|limit|current`.
|
||||
|
||||
Not exposed by this driver: `charge_full`/`charge_now` (fuel gauge, see
|
||||
`qcom-battery`) and any charge *scheduling* — downstream's `google_charger`
|
||||
ran a taper state machine with DT tunables (`google,chg-itaper`,
|
||||
`chg-soctaper`); mainline has no equivalent and this patch does not add one.
|
||||
|
||||
## Hard lessons (each cost real time — do not relearn)
|
||||
|
||||
- **Verify partition identity before any write** (`parted -s /dev/sda print`
|
||||
+ `blkid`); an unlabeled lsblk row is not free space. The sda21 near-miss
|
||||
is the standing warning.
|
||||
- `/tmp` on the phone is tmpfs — never park artifacts there.
|
||||
- `fastboot boot <img>` has a ~60s hardware watchdog — flash, don't
|
||||
test-boot, anything that takes long to come up.
|
||||
- Trust the user's direct visual report of the glass over any remote
|
||||
telemetry — registers can say "on" while the panel shows black.
|
||||
909
saf/device/slpi.md
Normal file
909
saf/device/slpi.md
Normal file
|
|
@ -0,0 +1,909 @@
|
|||
# PAF · SLPI bring-up (sensor island) — SOLVED 2026-07-10
|
||||
|
||||
## Status update — 2026-07-25: SLPI recovers, hexagonrpcd-sdsp does not
|
||||
|
||||
**Sequencing regression, live for hours before it was noticed.** The CHRE
|
||||
fatal documented below recurred on an ordinary running system (not a cold
|
||||
boot) at 06:04:33:
|
||||
|
||||
kernel: qcom_q6v5_pas 5c00000.remoteproc: fatal error received:
|
||||
err_qdi.c:456:EF:sensor_process:1:fatal_error.cc:22:CHRE
|
||||
fatal@platform_sensor_manager.cc:158
|
||||
remoteproc remoteproc2: crash detected in slpi / handling crash #1
|
||||
remoteproc remoteproc2: remote processor slpi is now up
|
||||
|
||||
remoteproc recovery worked — `remoteproc2: slpi = running` afterwards. But
|
||||
`blueline-hexagonrpcd-sdsp.service` took
|
||||
`Could not fetch next FastRPC message: Broken pipe` and **"Deactivated
|
||||
successfully"**: systemd read a clean exit and did not restart it. SLPI came
|
||||
back; the HexagonFS daemon that serves it did not. The unit sat `inactive
|
||||
(dead)` while `blueline-hexagonrpcd-adsp` (audio PD) stayed up, which is why
|
||||
audio was unaffected and the loss was invisible.
|
||||
|
||||
Symptom, ~100s apart, restart counter climbing to 5:
|
||||
|
||||
iio-sensor-proxy: 'registry' sensor unavailable, is hexagonrpcd running?
|
||||
|
||||
net.hadess.SensorProxy ->
|
||||
HasAccelerometer: false HasAmbientLight: false HasProximity: false
|
||||
|
||||
**Every sensor was down** — no autorotate, and `blueline-proximity-lock` kept
|
||||
running against a proxy with nothing behind it. A standalone consumer of a
|
||||
dead sensor is indistinguishable from one whose sensor is merely quiet, so
|
||||
nothing reported the outage.
|
||||
|
||||
**Fix belongs in `rootfs-overlay/`, not on the device.** The recovery order
|
||||
is the bug: SLPI restarts itself, so its filesystem daemon must follow it
|
||||
(`Restart=` plus a binding/ordering relationship that survives remoteproc
|
||||
crash-recovery, not just boot). A fix applied live is erased by the next
|
||||
reprovision — see `SouveraineOS/docs/tasks/25-one-repo-all-packages.md`.
|
||||
|
||||
Consumed-by note: this was the concrete argument for evidence source health
|
||||
being device state, and it is now built — `SouveraineOS/docs/DEVICE-STATE-MACHINE.md`
|
||||
§10 (2026-07-26). A source that reported and then went silent for 90 s is
|
||||
recorded `down`, not quiet, and every forensic snapshot taken during the outage
|
||||
is stamped `sensors_degraded`. The half that made it possible is here:
|
||||
`blueline-proximity-lock` re-sends its last reading every 30 s, because
|
||||
`monitor-sensor` only emits on change and a phone on a table is otherwise
|
||||
indistinguishable from a CHRE fatal. (TASK-26 itself was never written; the
|
||||
requirement outlived the placeholder.)
|
||||
|
||||
## Status update — 2026-07-20: iio-sensor-proxy is always-on now
|
||||
|
||||
The wake-lockup caveat below drove `blueline-autorotate` to start/stop the
|
||||
libssc client on demand — that hazard is confirmed gone. `iio-sensor-proxy`
|
||||
now runs continuously from boot via `blueline-sensors-enable.service`
|
||||
(`After=blueline-hexagonrpcd-sdsp.service`), and `AutoRotateToggle` in the
|
||||
shell only starts/stops the rotation daemon, not the sensor proxy. Proximity
|
||||
(TMD2725) is live and consumed by `blueline-screen-toggle` (DT2W gate) and
|
||||
the new `blueline-proximity-lock` user service (blanks an already-on locked
|
||||
screen on proximity-near). See touch.md and README.md's Tap-to-wake row.
|
||||
The historical on-demand/no-autostart record below is superseded by this but
|
||||
left as-is per PAF doctrine — see `docs/tasks/paf-reconciliation.md`.
|
||||
|
||||
## Reliability caveat — 2026-07-11: libssc wake lockup, root cause unproven
|
||||
|
||||
### New, separate cold-boot incident — 2026-07-13: delayed CHRE sensor-manager fatal
|
||||
|
||||
On Casey's confirmed unplugged cold boot, SLPI came up at kernel monotonic
|
||||
17.692s and then, with `iio-sensor-proxy.service` inactive, crashed at
|
||||
331.667s: `err_qdi.c:456:EF:sensor_process:1:fatal_error.cc:22:CHRE
|
||||
fatal@platform_sensor_manager.cc:158`. Remoteproc recovery immediately booted
|
||||
SLPI again. This is **not** the historical immediate `sar.cc:27` signature and
|
||||
must be tracked independently; its five-minute delay makes a runtime
|
||||
CHRE/SEE-sensor-manager timeout or deferred initialization path plausible.
|
||||
Active firmware was `/lib/firmware/qcom/sdm845/Google/blueline/slpi.mbn`, SHA-256
|
||||
`83e16ce8bffd8e4b963280dd30fa782becd005b40a3fd4dd6199093f98b82826`.
|
||||
The raw journal sequence is retained by `journalctl -k -b`; do not clear the
|
||||
kernel ring before copying a newly observed fatal into this record.
|
||||
|
||||
**Source correlation (2026-07-13):** AOSP CHRE's SLPI
|
||||
`platform_sensor_manager.cc` explicitly schedules `crashAfterSensorNotFound`
|
||||
for `5 * 60` seconds when required SEE sensors are missing at initialization.
|
||||
The observed delay is 313.975 seconds from SLPI-up to fatal. This pins the
|
||||
failure to the required-SEE-sensor discovery set, rather than a random runtime
|
||||
crash or the audio stack. Identify the absent SUID(s) from the live SSC/SEE
|
||||
registry before touching firmware or remoteproc state.
|
||||
|
||||
**Required-SUID sweep (2026-07-13):** The bounded read-only
|
||||
`tools/slpi-suid-probe` found SUIDs for
|
||||
`sensor_temperature` (LIS2MDL), `accel`/`gyro` (BMI160), `mag` (LIS2MDL),
|
||||
`pressure` (BMP380), and `ambient_light`/`proximity` (TMD2725). `amd` alone
|
||||
returned no matching SUID. The preserved Pixel Android `sns_amd` group is
|
||||
byte-for-byte reproduced by the OEM/soc-321 source wrapper. The reverse-file
|
||||
trace then proved the failure mode: after startup the DSP reads the added
|
||||
runtime `config/sns_amd.json`, opens the valid static group with write/truncate,
|
||||
and emits no payload for that group, leaving it zero bytes. This is not a host
|
||||
permission issue (the daemon successfully writes many other groups). Pixel's
|
||||
captured Android vendor config has no `sns_amd.json`, so the staged repair keeps
|
||||
the static group but omits that dynamic config input. **Cold-boot result:** the
|
||||
served group remained byte-identical to Android (239 bytes) and the DSP read
|
||||
it without a write-open, yet SLPI still fataled at 322.907s. Therefore the
|
||||
truncation path was a genuine regression but is not sufficient to restore the
|
||||
AMD SUID or satisfy CHRE. The probe was made null-safe after libssc returned an
|
||||
unavailable data type without a `GError`; no SLPI crash was caused by the probe.
|
||||
|
||||
**AMD provisioning correction (2026-07-13):** Qualcomm's public split runtime
|
||||
pair (`sns_amd.json` plus `sns_amd_sw_enabled.json`) was also tested. The
|
||||
correct vendor-input location is `config/registry/sns_amd.json`, never the
|
||||
served flat registry and never a physical `registry/registry` tree. Its trace
|
||||
is decisive: SEE opens that input, opens the valid static `sns_amd` group in
|
||||
write mode, allocates write-FD 256, and closes it **without an `fwrite`**; it
|
||||
then writes `sns_reg_config` normally. `O_TRUNC` in hexagonrpcd therefore
|
||||
turned the valid 239-byte Android-identical group into zero bytes without any
|
||||
DSP replacement payload. A clean `0014-lazy-truncate-reverse-writes.patch` is
|
||||
installed for the next boot: `w` opens are truncated only at their first actual
|
||||
`fwrite`, while append mode is unchanged. This preserves no-write static
|
||||
groups, but it is a prerequisite—not yet evidence that AMD will publish.
|
||||
**Validation:** the subsequent boot kept `sns_amd` at the exact 239-byte
|
||||
Android SHA-256 (`4756a6…2959d2`) through the complete no-write open/close
|
||||
sequence, yet CHRE still fataled at 325.595s in
|
||||
`platform_sensor_manager.cc:158`. Registry placement and host-side truncation
|
||||
are therefore fixed prerequisites, not the remaining AMD-publication cause.
|
||||
|
||||
**Required-SUID timeout cleared (2026-07-13):** the public SDM845 AMD split
|
||||
was retried only after the no-write truncation repair: dynamic
|
||||
`config/registry/sns_amd.json` supplies parameters while
|
||||
`sns_amd_sw_enabled.json` supplies enablement; the preserved 239-byte Android
|
||||
group remains the static registry seed. SEE opened **both** dynamic files,
|
||||
opened `sns_amd` in write mode without sending a payload, and the lazy-truncate
|
||||
daemon retained the exact reference hash. At 375 seconds uptime—well past the
|
||||
former 325-second `platform_sensor_manager.cc:158` deadline—SLPI remained
|
||||
`running`, `hexagonrpcd` remained active, and no fatal was logged. This is the
|
||||
first boot to clear CHRE's required-sensor watchdog. Do not immediately start
|
||||
`iio-sensor-proxy`/`ssccli`: their libssc client connection is the separately
|
||||
documented wake-lock hazard and needs a bounded follow-up test.
|
||||
|
||||
**Bounded client-path validation (2026-07-13):** On that stable boot,
|
||||
`iio-sensor-proxy` was started explicitly (the package's boot-time udev
|
||||
autostart remains masked). It discovered the SSC TMD2725 proximity and ambient
|
||||
light sensors plus BMI160 acceleration. The desktop D-Bus API successfully
|
||||
claimed and delivered acceleration (`face-up`) and ambient-light samples
|
||||
(~14 lux); proximity also reports normally. The device rule now explicitly
|
||||
selects `ssc-accel ssc-proximity ssc-light` on demand, while preserving the
|
||||
no-autostart policy. Direct `ssccli` reads also returned live TMD2725 light,
|
||||
BMI160 acceleration, and LIS2MDL magnetometer values. `ssccli`'s compass
|
||||
client alone still returns `UNKNOWN`. SLPI and hexagonrpcd remained running,
|
||||
with no CHRE fatal, after these clients. This validates the practical sensor
|
||||
path without reclassifying the historical boot-time libssc incident as solved.
|
||||
|
||||
**Service-loop regression fixed (2026-07-13):** After the one legitimate
|
||||
five-minute fatal at 326.652s, `hexagonrpcd` correctly exited on its broken
|
||||
FastRPC pipe with status 0. Its unit unfortunately used `Restart=always`, so
|
||||
systemd reattached during remoteproc recovery six times in seven seconds. Each
|
||||
reattach immediately crashed SLPI in `SNS_REG_INIT:39`, then the service hit
|
||||
systemd's start limit. This was a userspace retry-policy bug, not six
|
||||
independent firmware failures. The deployed and overlay unit now uses
|
||||
`Restart=on-failure` with a five-second delay; no service/remoteproc restart
|
||||
was performed while installing it. A trace-enabled `hexagonrpcd` was also
|
||||
deployed for the next cold boot: `HEXAGONRPC_TRACE_FILES=1` correlates virtual
|
||||
open paths with the previously opaque `Could not read file: Is a directory`
|
||||
FD. The binary and service changes take effect on the next genuine cold boot.
|
||||
|
||||
**DMA-heap permission regression fixed (2026-07-13):** The documentation said
|
||||
the FastRPC allocation heap was owned by `root:fastrpc 0660`, but the actual
|
||||
cold-boot device node was `root:root 0600`: the overlay had a rule for
|
||||
`/dev/fastrpc-*` only and never contained the claimed `dma_heap` rule. Added
|
||||
`11-fastrpc-dma-heap.rules`, narrowly assigning only
|
||||
`/dev/dma_heap/system` to `fastrpc:0660`; firmware-reserved heaps remain
|
||||
root-only. It was installed live with `udevadm trigger` and verified as the
|
||||
`fastrpc` user before any new SLPI action. This removes a genuine local
|
||||
permissions blocker for non-root FastRPC clients, but does not by itself
|
||||
explain an EPERM returned from an already-open DSP RPC session.
|
||||
|
||||
**CORRECTION — cold-boot validation failed (2026-07-13):** The following
|
||||
paragraph describes a staged-path hypothesis, **not a completed repair**. On
|
||||
the next cold boot the registry was readable but SLPI again fataled at 323.7s.
|
||||
The corrected root-owned listener was then installed and enabled; on its first
|
||||
validation boot it attached before SLPI's delayed window but the fatal still
|
||||
occurred at 326.97s. The listener deployment is necessary infrastructure, not
|
||||
a sensor fix. Its remaining `sns_reg_config` virtual-path error points to
|
||||
hexagonrpcd's mapping, and CHRE required-sensor discovery remains open.
|
||||
|
||||
**Concrete boot-path repair staged and installed (2026-07-13):** before the
|
||||
fatal, `hexagonrpcd` logged that
|
||||
`/mnt/vendor/persist/sensors/registry/sns_reg_config` did not exist, even
|
||||
though the staged registry under `/usr/share/qcom/sensors` was complete. All
|
||||
required types subsequently resolve through SSC (BMI160 accel/gyro and
|
||||
motion/stationary; LIS2MDL magnetometer; BMP380 pressure; TMD2725 ambient
|
||||
light/proximity), so this is an initialization-path race rather than a
|
||||
permanently missing physical sensor. The enabled
|
||||
`blueline-sns-registry-stage.service` now creates the guarded persistent link
|
||||
`/mnt/vendor/persist/sensors → /usr/share/qcom/sensors` before
|
||||
`hexagonrpcd-sdsp.service`. The next **unplugged shutdown/power-on** is the
|
||||
validation: retain the kernel journal and confirm that the delayed fatal does
|
||||
not occur after five minutes.
|
||||
|
||||
**Correction — the tracked VFS fix was not in the installed binary
|
||||
(2026-07-13 evening):** The fresh cold boot again logged
|
||||
`Could not open /mnt/vendor/persist/sensors/registry/sns_reg_config`, despite
|
||||
the flat staged host file existing. Inspection of `/usr/local/bin/hexagonrpcd`
|
||||
and `~/hexagonrpc/hexagonrpcd/rpcd_builder.c` proved that the live
|
||||
`hexagonrpc-blueline 0.4.0-2` binary had only the old `sns_reg_version` map;
|
||||
the recipe tracked 0008 but its installed artifact did not contain it. Applied
|
||||
0008 to the live source, rebuilt, and installed a backed-up replacement. The
|
||||
new binary embeds maps for `sns_reg_config`, `sns_reg_version`,
|
||||
`sns_reg_ctrl`, `file1`, and `file2`; `/usr/share/qcom/sensors/registry/`
|
||||
remains physically flat (no `registry/registry`). No DSP lifecycle action was
|
||||
taken. The next unplugged shutdown/power-on must confirm the initial ENOENT is
|
||||
gone and then observe CHRE past its five-minute required-sensor timeout. The
|
||||
package database still says `0.4.0-2`; rebuild/install the tracked `pkgrel=3`
|
||||
package before treating this live binary repair as image-persistent.
|
||||
|
||||
**Cold-boot validation — VFS control maps fixed; CHRE still missing a required
|
||||
SEE sensor (2026-07-13 evening):** On the next unplugged shutdown/power-on,
|
||||
SLPI reached `running` at 13.014s. `hexagonrpcd` successfully opened virtual
|
||||
`registry/sns_reg_config`, `sns_reg_version`, and `sns_reg_ctrl`; the former
|
||||
`sns_reg_config` ENOENT was absent. At 327.479s (314.465s after SLPI-up), CHRE
|
||||
again fataled at `platform_sensor_manager.cc:158`, then remoteproc recovered
|
||||
SLPI and the daemon exited cleanly. This conclusively fixes the VFS-control
|
||||
prerequisite without fixing required-SUID discovery. Remaining trace noise is
|
||||
benign: `testsig*.so` is absent test firmware, and the reported `EISDIR` is
|
||||
CHRE's expected read-then-`opendir` probe of virtual `registry/registry`.
|
||||
|
||||
SLPI itself remains up; this is not a return of the `sar.cc` crash. Toggling
|
||||
sensors off/on reproducibly identifies the libssc/iio-sensor-proxy SSC QMI
|
||||
connection as the trigger for a wake lockup: kernel and SSH survive, display
|
||||
commits stop completing without DSI/DPU errors, FTS becomes calibration-blind,
|
||||
and GENI I2C GPI DMA times out concurrently. This narrows the mechanism to an
|
||||
AP-side shared-bus/power interaction on SLPI wake. An rpmh/interconnect vote
|
||||
race is the leading hypothesis, **not a proven root cause**.
|
||||
|
||||
**HACK shipped:** SLPI stays booted, but the auto-rotate toggle starts/stops
|
||||
its iio-sensor-proxy/libssc client instead of leaving that client enabled from
|
||||
boot. It reduces exposure only; it is not a fix. Netconsole and pstore_blk are
|
||||
armed for an instrumented reproduction before any permanent change is attempted.
|
||||
|
||||
**Boot-safety correction, 2026-07-11:** the initial HACK was ineffective at
|
||||
boot: `/usr/lib/udev/rules.d/80-iio-sensor-proxy.rules` starts the proxy on
|
||||
every `fastrpc-sdsp` add event via `SYSTEMD_WANTS`. The overlay now supplies a
|
||||
comment-only `/etc/udev/rules.d/80-iio-sensor-proxy.rules`, which shadows that
|
||||
vendor rule while leaving the explicit polkit/toggle start path intact. Live
|
||||
`udevadm test` confirmed `/etc` rule precedence and the running boot-started
|
||||
proxy was stopped. The next reboot must still verify that it remains off.
|
||||
|
||||
**Instrumented reproduction, 2026-07-11 10:45 EDT:** netconsole over the USB
|
||||
gadget was configured dynamically (`usb0` 172.16.42.1:6665 → laptop
|
||||
172.16.42.2:6666) and packet-capture verified before the proxy was started.
|
||||
With the proxy active, physical sleep/wake reproduced the stale-frame lockup:
|
||||
the glass remained at 08:02 while SSH, low system load, Hyprland, and
|
||||
Quickshell remained alive. No panic or pstore record occurred. Starting 28
|
||||
seconds after the proxy-start marker, the FTS driver emitted repeated invalid
|
||||
Mutual/Self Strength calibration events; no DSI/DPU error, GENI timeout, or
|
||||
rpmh/ICC diagnostic was logged in this run. Netconsole captured only the
|
||||
explicit console-level start marker, so it proves the transport but not the
|
||||
missing low-priority driver messages. The proxy was stopped afterwards. This
|
||||
is further trigger evidence, **not** proof of the rpmh/interconnect hypothesis.
|
||||
Next capture: raise the temporary console loglevel and add narrowly scoped
|
||||
rpmh/ICC tracing before another deliberate reproduction.
|
||||
|
||||
## ARCH STATUS (2026-07-10 afternoon): THE sar.cc WALL IS DOWN. SLPI runs.
|
||||
|
||||
SLPI boots, stays up (0 fatals, was deterministic-fatal at ~10s), SEE fully
|
||||
initializes (node 9 publishes svc 400 + services that never appeared in the
|
||||
crash era), the accelerometer streams real data to userspace (ssccli), and
|
||||
iio-sensor-proxy reports HasAccelerometer=true over D-Bus. Boot-started by
|
||||
`blueline-slpi.service` (rootfs-overlay).
|
||||
|
||||
**Root cause of sar.cc:27 (found by source archaeology, kernel 25d56ec84):**
|
||||
the SSC firmware's `remote_proc_state` sensor does
|
||||
`smp2p_register(SMEM_APPS, "sleepstate")` in its init thread and NEVER
|
||||
publishes if that fails (Qualcomm source: Rivko/android-firmware-qti-sdm670,
|
||||
adsp_proc/ssc/sensors/remote_proc_state/). Mainline's sdm845 smp2p-slpi node
|
||||
has no "sleepstate" entry, so registration failed, remote_proc_state never
|
||||
published, and CHRE's SeeHelper::init exhausted its ~10s findSuidSync retry
|
||||
budget on it (AOSP chre platform/slpi/see/see_helper.cc — initCalSensors
|
||||
passes, initRemoteProcSensor fails) → "Failed to initialize ASH SEE helper"
|
||||
→ chre_utils FATAL at sar.cc:27 → whole sensor_process aborts. The SAR
|
||||
module was only ever the messenger. FIX: sdm845.dtsi sleepstate smp2p entry
|
||||
+ new drivers/soc/qcom/smp2p-sleepstate.c asserting bit 12 (= SMP2P_AWAKE
|
||||
0x1000 in SSC source; = downstream PROC_AWAKE_ID msm-4.9
|
||||
smp2p_sleepstate.c), dropped across suspend. CONFIG_QCOM_SMP2P_SLEEPSTATE=y.
|
||||
|
||||
**Dead ends, so nobody repeats them:** (1) qrtr ns announce-replay patch
|
||||
(3fe96cf2d, REVERTED in 94ac4a5e1) — replaying all nodes' services on HELLO
|
||||
wedged node 9's own service registration (flow control on the ns worker);
|
||||
sar.cc never needed modem services. (2) The whole 2026-07-08 elimination
|
||||
list stands (registry, socinfo, chrecd, proxy-power, sessions — all real
|
||||
fixes, none the wall). (3) Registry-diff vs LOS persist: the 42 missing
|
||||
entries are wrong-device pollution + factory cal, not the cause.
|
||||
|
||||
Five-fix SNS_REG_INIT chain (hw_platform OEM socinfo, module tree, chrecd,
|
||||
sscregistrygen registry, socinfo sysfs) remains prerequisite and correct.
|
||||
Sensor stack persists across re-provision since cd82f29. AP-side gesture
|
||||
dt2w works independently (touch.md); the SLPI gesture-nanoapp path is now
|
||||
UNBLOCKED for future work (CHRE runs). Auto-rotate WORKS on glass
|
||||
(2026-07-10 ~13:00: blueline-autorotate + ACCEL_MOUNT_MATRIX udev fix,
|
||||
see rootfs-overlay). Remaining: sx932x "sar" SUID still absent
|
||||
(grip sensor hardware probe — separate, non-fatal now).
|
||||
Everything below is the pmOS-era record — the archaeology that fed the
|
||||
chain above; capture docs it cites live in `evidence/`.
|
||||
|
||||
## 2026-06-30 — chronic RTC-write flood found + fixed; wrong listener used in a failed test; dma_heap permissions fixed
|
||||
|
||||
**Context:** a fresh SLPI one-shot attempt (manual `sdsprpcd sensorspd` + `recovery=disabled`
|
||||
+ `echo start`, **not** via `blueline-slpi-ready.service`) wedged the phone. Investigating the
|
||||
wedge surfaced three unrelated, real bugs — none of them SLPI firmware behavior — that were
|
||||
corrupting every capture attempt and blocking the correct listener from working at all.
|
||||
|
||||
**1. Chronic `disallowed SPMI write to sid=0, addr=0x6046` flood (~40/sec, continuous,
|
||||
present even with SLPI offline) — unrelated to SLPI, was drowning every capture.**
|
||||
Traced to `drivers/spmi/spmi-pmic-arb.c:1364` (`pmic_arb_offset_v5`): a hardware EE-ownership
|
||||
check the AP fails every time. `sid=0`/`0x60xx` is the pm8998 RTC peripheral
|
||||
(`pm8998.dtsi:104`, `rtc@6000`). Root cause: `drivers/rtc/rtc-pm8xxx.c:622-631` force-overrides
|
||||
`allow_set_time = true` regardless of the DT `allow-set-time` property, on the premise
|
||||
(comment at :624-628) that "the modem reads real wall-clock time during RF-init" via this
|
||||
write. **That premise is wrong** — cross-referenced against LineageOS on the same silicon:
|
||||
`los-blueline.dts:2371` sets `qcom,qpnp-rtc-write = <0x00>` (write disabled),
|
||||
`qpnp-rtc.c:474,587-588` defaults to read-only ops and only switches to read-write if that
|
||||
property is true, and `init.sdm845.rc:233-237` never touches `rtc` at all — only
|
||||
`start time_daemon`. Android does not write this register either; it can't, for the same
|
||||
hardware-ownership reason. The actual (real, working, already-verified — see `file.txt`
|
||||
2026-06-30, "FIRST ACCEPT... modem User time is now real wall-clock") mechanism for feeding
|
||||
the modem wall-clock time is `tools/blueline-timed.service`, a **QMI TIME-service-22 push
|
||||
straight to the modem** (`modem-time-set.py`), completely unrelated to the PMIC RTC. The
|
||||
`rtc-pm8xxx.c` patch never achieved its stated goal either way: `__pm8xxx_rtc_set_time()`
|
||||
(:294-343) fails at its very first register access (`alarm_ctrl`, same address) every call,
|
||||
before ever reaching the actual time-write registers.
|
||||
**Fix:** reverted the force-true override (falls through to the driver's existing
|
||||
`pm8xxx_rtc_update_offset()` software-offset path, which is what LOS's read-only mode is the
|
||||
equivalent of); dropped `allow-set-time;` from `sdm845-google-blueline.dts`. Kernel rebuilt
|
||||
2026-06-30 ~21:56.
|
||||
|
||||
**2. The SLPI wedge test used the wrong listener with no ordering — not a firmware crash we
|
||||
observed correctly.** `device-google-blueline` ships `blueline-slpi-ready.service`, which
|
||||
`Requires=`+`After=` **`blueline-sns-registry-stage.service`** and **`hexagonrpcd-sdsp.service`**
|
||||
— exactly the registry-staged/listener-ready ordering the S6 session below already proved is
|
||||
required. The failed manual test instead started the proprietary `sdsprpcd` (from the
|
||||
`fastrpc` apk, installed this session) directly, with zero registry staging and zero
|
||||
wait-for-listener. **Next SLPI attempt: `systemctl start blueline-slpi-ready.service`,
|
||||
not a hand-rolled sequence.** All three services were `disabled` (not masked) on this
|
||||
rootfs — never actually run, not deliberately blocked.
|
||||
|
||||
**3. `/dev/dma_heap/*` was `root:root 0600`, silently blocking `hexagonrpcd-sdsp.service`
|
||||
(`User=fastrpc`) from allocating FastRPC memory at all.** `60-fastrpc.rules`'s fix for this
|
||||
(`RUN+="/bin/setfacl -m g:fastrpc:rw $env{DEVNAME}"`, and only for the `system` heap, not
|
||||
`adsp_region`/`default_cma_region`/`reserved`) silently no-ops on every boot because
|
||||
`setfacl`/the `acl` package was never installed. Confirmed live: `fastrpc` user got
|
||||
`Permission denied` opening `/dev/dma_heap/system` before the fix, clean `open()` after.
|
||||
**Fixed** both live (`chgrp fastrpc; chmod 0660` on all four) and durably in the rules file
|
||||
(replaced the `setfacl` RUN directive with `GROUP="fastrpc", MODE="0660"`, matching the
|
||||
`fastrpc-*` device rules already in the same file) — not yet verified across a real cold
|
||||
boot.
|
||||
|
||||
**4. Separately, `/vendor/dsp/{adsp,cdsp,sdsp}` didn't exist on the rootfs at all** (needed
|
||||
by `adsprpcd`/`cdsprpcd`/`sdsprpcd`/`hexagonrpcd` alike). The live `vendor_b` partition
|
||||
(`/dev/sda10`) mounts but reads as directory-corrupted via both the kernel VFS and
|
||||
`debugfs -R "ls /"` — likely a raw-mount-bypasses-dm-verity artifact, not real corruption of
|
||||
the working LOS install (left untouched, not fsck'd). Used the already-extracted, genuinely
|
||||
readable copy from an earlier session, `pixel3-work/los-vendor/vendor.img` (531MB ext2,
|
||||
volume name "vendor"), and copied just `dsp/{adsp,cdsp,sdsp}` (17MB) to `/vendor/dsp/` on
|
||||
the phone. This did **not** fix `adsprpcd_audiopd` (its real failure is Qualcomm QDI status
|
||||
`0x80000414`, not literally ENOENT despite the log text — needs an audio-PD servreg
|
||||
registration the vendor tree alone doesn't provide; out of scope, re-masked), but the tree
|
||||
is now in place for whatever `hexagonrpcd`/SDSP actually needs from it.
|
||||
|
||||
**Net effect: the next SLPI attempt is the first one with quiet logs, the correct listener
|
||||
+ ordering, correct dma_heap permissions, and the vendor DSP tree all present
|
||||
simultaneously.** No prior attempt (including tonight's wedge) had all four.
|
||||
|
||||
## 2026-06-30 (cont.) — first clean-ish trace: two racing FastRPC-sdsp consumers, one legit, one not
|
||||
|
||||
After the RTC fix (kernel flashed, rebooted, SPMI flood confirmed **0 hits** — first quiet
|
||||
boot ever), ran a real test: staged the registry (`blueline-sns-registry-stage.service`,
|
||||
179 files), then a poller script raced `hexagonrpcd -f /dev/fastrpc-sdsp -d sdsp -s` onto
|
||||
the device the instant it appeared (`/dev/fastrpc-sdsp` doesn't exist pre-boot; neither
|
||||
`hexagonrpcd` nor any udev rule retries for it — the poller was necessary, see script in
|
||||
`tools/` history / this session). `echo start` on `remoteproc2`. Captured via
|
||||
`journalctl -f` to a persistent file (works — unlike `tail -f /dev/kmsg`, which silently
|
||||
dies on the ring-buffer overflow the RTC bug used to cause; **this is now the standard
|
||||
capture method**, not kmsg tail).
|
||||
|
||||
**What actually happened, in order (journal-confirmed, `slpi-test2/journal-live.txt`):**
|
||||
1. `remoteproc remoteproc2: powering up slpi` → boots clean, heap already preassigned
|
||||
(no `-22`, confirms the earlier adsprpc memory conversion still holds).
|
||||
2. `/dev/fastrpc-sdsp` appears. **Two things react simultaneously**, not one:
|
||||
- `systemd[1]: Starting sDSP RPC daemon...` → **`sdsprpcd.service`**, auto-started by
|
||||
`60-fastrpc.rules`'s `KERNEL=="fastrpc-sdsp" ... SYSTEMD_WANTS+="sdsprpcd.service"`.
|
||||
This is the **Qualcomm proprietary daemon from the `fastrpc` apk installed earlier
|
||||
this same session** — an unintended side effect of installing that package to get
|
||||
`sdsprpcd` for testing. It attached first (`attachguestos` on domain 2 succeeded,
|
||||
opened `adsp_default_listener` handles), then hit
|
||||
`Error 0x4e ... errno Operation not permitted` on an RPC invoke.
|
||||
- `hexagonrpcd` (the poller-launched, intended listener) tried immediately after and
|
||||
got `Could not attach to FastRPC node: Operation not permitted` — plausibly because
|
||||
`sdsprpcd` had already claimed the attach slot, not necessarily a firmware-side
|
||||
rejection of `hexagonrpcd` specifically.
|
||||
- A **third** thing also started at the same instant:
|
||||
`systemd[1]: Starting IIO Sensor Proxy service...` (`iio-sensor-proxy.service`,
|
||||
pre-existing base-image package, installed 2026-05-08, unrelated to tonight — see
|
||||
below).
|
||||
3. ~1s later: `qcom_q6v5_pas ...: watchdog received: SFR Init: wdog or kernel error
|
||||
suspected.` → `remoteproc2` crashes.
|
||||
|
||||
**`sdsprpcd.service` is a real bug (masked) — but not a new one tonight.** Checked the
|
||||
history properly rather than assume: the 2026-06-29 session *also* installed the `fastrpc`
|
||||
apk and ran `sdsprpcd sensorspd` manually as its one-shot test — and that is the exact
|
||||
command HANDOFF's own notes say caused *that* session's wedge too ("the same one-shot
|
||||
`sdsprpcd sensorspd` + SLPI start caused SSH/network/UI to wedge"). `file.txt`'s later
|
||||
session was also checking for `sdsprpcd` specifically. So `sdsprpcd`-as-manual-test is a
|
||||
repeated pattern across at least three sessions, not something introduced tonight — this
|
||||
session continued that same habit (reinstalling the `fastrpc` apk to get `sdsprpcd` back
|
||||
after the rootfs rewrite lost it) before realizing `hexagonrpcd` was the actually-intended
|
||||
listener. `hexagonrpcd`, and the `blueline-slpi-ready.service`/`hexagonrpcd-sdsp.service`
|
||||
ordering, come from `device-google-blueline` (a separate, independently-developed track
|
||||
within this same project) and were never actually exercised in isolation until tonight's
|
||||
`sdsprpcd`-masked re-test. `systemctl mask sdsprpcd.service` done this session — first
|
||||
time this collision has been diagnosed rather than just repeatedly hit.
|
||||
|
||||
**`iio-sensor-proxy.service` is NOT a bug and should NOT be masked long-term** (briefly
|
||||
masked mid-session, then **unmasked** once this was understood — leave it unmasked).
|
||||
It's built against `libssc.so.2` (`qmi_client_ssc_control`, "SSC QMI client") — a genuine
|
||||
**QMI Sensor SubSystem Client**, i.e. exactly the downstream consumer this whole SLPI
|
||||
effort is trying to feed ("no SNS QMI service exposed" has been the standing symptom
|
||||
description). Its reacting to SLPI coming up is systemd/udev doing its job correctly, not
|
||||
a coincidence. The real fix here is **ordering, not suppression**: it needs to start
|
||||
*after* SLPI/hexagonrpcd are confirmed stable, not the instant the remoteproc powers up —
|
||||
right now nothing enforces that, so its QMI connection attempt lands during the same
|
||||
fragile registry-init window the S6 finding already identified as timing-critical, on top
|
||||
of the `sdsprpcd` collision. Untested whether `iio-sensor-proxy` alone (with `sdsprpcd`
|
||||
masked) still perturbs the window enough to matter — that's the next isolated test.
|
||||
|
||||
**Next step:** re-run the identical poller+`hexagonrpcd` test with `sdsprpcd.service` now
|
||||
masked. If `iio-sensor-proxy` still auto-fires and coincides with a crash, that tells us
|
||||
the ordering fix is needed for real; if `hexagonrpcd` attaches cleanly this time, the
|
||||
`sdsprpcd` collision was the whole story and today's `Operation not permitted` /
|
||||
`SFR Init` crash was never a hexagonrpcd/firmware problem at all — just two proprietary
|
||||
and community daemons fighting over one singleton resource.
|
||||
|
||||
## 2026-06-30 (cont. 2) — isolated re-test: `sdsprpcd` wasn't the story either. The SAR/CHRE fault is real and reproduces clean.
|
||||
|
||||
Re-ran the identical poller+`hexagonrpcd` test with `sdsprpcd.service` masked (previous
|
||||
section). `iio-sensor-proxy.service` still auto-fired on SLPI coming up (as expected, it's
|
||||
legitimate) but did **not** coincide with any crash this time — no evidence it perturbs
|
||||
anything on its own.
|
||||
|
||||
**Clean timeline (`journalctl`, unfiltered, this run):**
|
||||
```
|
||||
22:25:26 remoteproc remoteproc2: powering up slpi
|
||||
22:25:27 remoteproc remoteproc2: remote processor slpi is now up
|
||||
22:25:27 [hexagonrpcd] Starting hexagonrpcd (INIT_ATTACH_SNS) on /dev/fastrpc-sdsp
|
||||
22:25:27 [hexagonrpcd] Could not attach to FastRPC node: Operation not permitted
|
||||
22:25:36 qcom_q6v5_pas ...: fatal error received: err_qdi.c:456:EF:sensor_process:1:sar.cc:27:chre_utils fatal
|
||||
22:25:36 remoteproc remoteproc2: crash detected in slpi: type fatal error
|
||||
```
|
||||
No AP-level instability this time — `remoteproc2` settled into `crashed` (recovery still
|
||||
`disabled`, no relaunch spam) and the phone stayed fully reachable throughout. **The hard
|
||||
wedge in the first test tonight was very likely the `sdsprpcd`/`hexagonrpcd` collision
|
||||
itself (two clients contending for one singleton attach), not simply "SLPI crashes."** A
|
||||
clean, isolated SLPI crash does not wedge the AP.
|
||||
|
||||
**Conclusion: this is the same `sar.cc:27` SAR/CHRE fatal documented in the "2026-06-29
|
||||
live result" section above, now reproduced with everything else this session found and
|
||||
fixed already in place** — quiet logs (RTC fix), correct listener alone (no `sdsprpcd`
|
||||
collision), correct `dma_heap` permissions, `/vendor/dsp/` present, registry staged
|
||||
(179→182 files). **None of tonight's plumbing fixes were the actual blocker.** They were
|
||||
real bugs and worth fixing (nothing else could be tested cleanly until they were), but the
|
||||
live frontier is exactly where it was before tonight: `INIT_ATTACH_SNS` returns `EPERM`,
|
||||
and ~9s later the firmware faults inside CHRE's SAR path. **Next real work is still the
|
||||
already-identified target**: convert the remaining Lineage sensor/SAR FastRPC path
|
||||
(`sscrpcd sensorspd` equivalent, sensors PDR/service-location behavior, whatever makes
|
||||
`INIT_ATTACH_SNS` attach cleanly) — see "Status / next" above. That work has not started;
|
||||
tonight only cleared the ground for it to be tested honestly.
|
||||
|
||||
## 2026-06-30 (cont. 3) — the "proper" pmOS chain has a real, previously-undiagnosed timing bug
|
||||
|
||||
Per Casey's explicit direction: stop working around the shipped mechanism, make the
|
||||
shipped mechanism itself work. The gap: `hexagonrpcd-sdsp.service` has
|
||||
`ConditionPathExists=/dev/fastrpc-sdsp`, which can never be true at the moment systemd
|
||||
evaluates it (the device doesn't exist until SLPI starts) — `device-google-blueline` never
|
||||
shipped a udev rule to re-trigger it once the device appears. Added one, as a local admin
|
||||
override (not yet upstreamed into the package):
|
||||
```
|
||||
# /etc/udev/rules.d/61-hexagonrpcd-sdsp.rules
|
||||
ACTION=="add", SUBSYSTEM=="misc", KERNEL=="fastrpc-sdsp", TAG+="systemd", ENV{SYSTEMD_WANTS}+="hexagonrpcd-sdsp.service"
|
||||
```
|
||||
With this in place, `systemctl start blueline-slpi-ready.service` — the actual, sole,
|
||||
intended entry point — runs end-to-end with **no manual scripts at all**: registry stages,
|
||||
`hexagonrpcd-sdsp.service` auto-triggers via udev the instant the device appears (via a
|
||||
`blueline-hexagonrpcd-sdsp-wait.sh` wrapper this project hadn't inspected before), SLPI
|
||||
starts, `hexagonrpcd` attaches. This is real progress — the shipped mechanism now actually
|
||||
runs as designed, on the current rootfs, for the first time.
|
||||
|
||||
**But it lost the registry-init race — a real bug, not the earlier `sdsprpcd` collision.**
|
||||
Clean journal, unfiltered:
|
||||
```
|
||||
22:48:43 blueline-slpi-ready.sh: disabling recovery, starting SLPI once
|
||||
22:48:43 remoteproc remoteproc2: powering up slpi
|
||||
22:48:44 remoteproc remoteproc2: remote processor slpi is now up
|
||||
22:48:44 blueline-hexagonrpcd-sdsp-wait.sh: starting hexagonrpcd on /dev/fastrpc-sdsp
|
||||
22:48:44 hexagonrpcd: Starting hexagonrpcd (INIT_ATTACH_SNS) on /dev/fastrpc-sdsp
|
||||
22:48:44 qcom_q6v5_pas: fatal error received: err_qdi.c:456:EX:sensor_process:1:SNS_REG_INIT:39:PC=b20226c0
|
||||
22:48:44 remoteproc remoteproc2: crash detected in slpi: type fatal error
|
||||
```
|
||||
This is `SNS_REG_INIT` failing — the **exact same registry-init ordering race the S6
|
||||
session (2026-06-27) diagnosed and believed `auto_boot=false` had solved** — reproducing
|
||||
in the *same second* SLPI came up, before `hexagonrpcd`'s attach could complete.
|
||||
|
||||
**Compare to the earlier isolated test tonight** (this doc, "isolated re-test" section):
|
||||
manually racing `hexagonrpcd` onto the device with a raw 20ms-poll bash loop (no systemd
|
||||
involved) won that same race reliably — SLPI ran a full ~9s before failing later, in
|
||||
`sar.cc:27:chre_utils` (deeper into CHRE/SAR init, past registry-init). **The manual
|
||||
poller was faster and more reliable than the real systemd service-start chain.**
|
||||
`hexagonrpcd-sdsp.service`'s startup path — udev event → systemd service activation →
|
||||
`blueline-hexagonrpcd-sdsp-wait.sh` → exec `hexagonrpcd` → device open → `INIT_ATTACH_SNS`
|
||||
— has enough cumulative latency to lose a race a tight bash loop wins.
|
||||
|
||||
**FIXED, same session, same night.** Checked `~/pmos/pmaports/device/testing/device-google-blueline/`
|
||||
(the actual local source for this package) and found the design was already correct — the
|
||||
`hexagonrpcd-sdsp.service` drop-in (`hexagonrpcd-sdsp-root.conf`) already clears
|
||||
`ConditionPathExists`, already redirects to a wrapper (`blueline-hexagonrpcd-sdsp-wait.sh`)
|
||||
that runs a persistent `while :; do wait for device; exec hexagonrpcd; wait for
|
||||
teardown; done` loop — i.e. it was *already* starting before SLPI, via the normal
|
||||
`Requires=`/`After=` chain in `blueline-slpi-ready.service`, exactly as the S6 finding
|
||||
prescribed. (My first instinct — that a missing udev rule was the gap — was wrong; added
|
||||
`/etc/udev/rules.d/61-hexagonrpcd-sdsp.rules` then confirmed via `systemctl show
|
||||
hexagonrpcd-sdsp.service -p ConditionResult` → `yes` that it was never needed, and removed
|
||||
it again.) **The actual bug was one number**: the wrapper's poll interval,
|
||||
`blueline-hexagonrpcd-sdsp-wait.sh 0.2` (200ms), was too coarse to reliably detect
|
||||
`/dev/fastrpc-sdsp` and get `hexagonrpcd` attached inside SLPI's registry-init window.
|
||||
Changed to `0.02` (20ms, matching the interval that won the race in the manual poller
|
||||
test above). **Retested through the pure, unmodified entry point —
|
||||
`systemctl start blueline-slpi-ready.service`, zero manual scripts — and it now reliably
|
||||
reproduces the same deep `sar.cc:27:chre_utils fatal` crash tonight's best manual test
|
||||
got**, not the shallow `SNS_REG_INIT` race-loss. Fix committed to
|
||||
`hexagonrpcd-sdsp-root.conf` in the local pmaports source (not yet built into a package/
|
||||
flashed — currently live-patched onto the running rootfs only).
|
||||
|
||||
**Net: the shipped mechanism is now correct and reliable.** No more manual scripts, no
|
||||
udev workaround, no competing daemons — `systemctl start blueline-slpi-ready.service`
|
||||
alone gets to the real wall every time. The remaining problem is exactly what it's been
|
||||
all along: `INIT_ATTACH_SNS` → `EPERM`, firmware faults in CHRE's SAR path ~10s later.
|
||||
That's real kernel/source work (the Lineage sensor/SAR FastRPC conversion), not another
|
||||
timing or plumbing bug.
|
||||
|
||||
(Also observed, unrelated background noise in this capture: `qcril-autoselect` PDC
|
||||
Set Feature requests timing out repeatedly (`Transaction timed out (2)`) — pre-existing,
|
||||
not investigated tonight, flag for later.)
|
||||
|
||||
Note: `qrtr-lookup` is genuinely **absent from this rootfs** (`find / -xdev -iname
|
||||
'qrtr-lookup*'` finds nothing at all — not a `$PATH` issue). It's used routinely across
|
||||
`modem.md`, `tools/slpi-capture.sh`, and `tools/phone-slpi-sdsprpcd-collect.sh`, so it was
|
||||
clearly present and working in earlier sessions, but there is **no apk package or
|
||||
reference source for it anywhere in this project** (`qrtr-libs` only ships the shared
|
||||
library, not the CLI tool) — it must have been a manually-built binary on the pre-wedge
|
||||
rootfs with no packaging record, same category of loss as the SLPI service masks. Needs
|
||||
building from the upstream `qrtr` tools source (not currently cloned into `references/`)
|
||||
to restore. Did not block tonight's test since it wasn't needed, but real and unsolved.
|
||||
|
||||
> **🟠 REFRAME 2026-06-29 (Casey) — the SLPI is a CANDIDATE ROOT-CAUSE for the modem 52, NOT a side-quest.**
|
||||
> The prior "SLPI proven independent of the 52" conclusion (`modem_consumer_chain_20260626.md` SESSION-3)
|
||||
> is **invalid**. It was tested only with an SLPI that was either crash-looping OR merely
|
||||
> remoteproc-not-crashing — **never a functioning sensor stack** (that doc itself notes: *"qrtr-lookup
|
||||
> empty — the SLPI never exposes an SNS QMI service before it dies"*; *"CHRE nanoapp crash INSIDE the
|
||||
> SLPI firmware at init"*). "Stop SLPI → still 52" only proves stopping doesn't worsen it: if the modem's
|
||||
> RF-init (~12 s window) queries the SLPI for sensor/NV/registry data and gets silence, it parks OFFLINE
|
||||
> identically whether the SLPI is crashing or stopped. **The SLPI has never been healthy in any modem
|
||||
> test.** Path to actually test it: get the SLPI *genuinely* healthy — `auto_boot=false` + start after
|
||||
> the correct SDSP listener is ready + Lineage-compatible FastRPC/adsprpc behavior so the sensor stack
|
||||
> initializes and SNS QMI exposes — **then** test the modem. That is the first real modem test with a
|
||||
> functioning SLPI.
|
||||
|
||||
## 2026-06-29 late session — current SLPI frontier and phone state
|
||||
|
||||
**Phone is stable only with SLPI held offline.** Current handoff state:
|
||||
|
||||
- Kernel: `7.1.1-sdm845`, package `linux-postmarketos-qcom-sdm845-7.1.0_p20260629134644-r1.apk`.
|
||||
- WiFi regression fixed live: `CONFIG_ATH10K_SNOC=m`, `CONFIG_QCOM_PD_MAPPER=m`, `wlan0` present.
|
||||
- MSS/modem remoteproc running; modem still DMS op-mode `offline`, HW restricted `no`.
|
||||
- SLPI is **offline**, recovery disabled when touched.
|
||||
- `blueline-slpi-ready.service` and `hexagonrpcd-sdsp.service` are masked via
|
||||
`/etc/systemd/system/*.service -> /dev/null`. Do **not** unmask/start SLPI on boot until the
|
||||
next capture path is planned.
|
||||
|
||||
**Conversions landed:**
|
||||
|
||||
- `qcom_q6v5_pas`: `sdm845_slpi_resource_init.auto_boot = false`.
|
||||
- Blueline Lineage/downstream adsprpc memory conversion:
|
||||
fixed no-map `pil_adsp_mem@8c500000` for `adsp_pas`, reusable `adsp_region` for adsprpc,
|
||||
SDSP FastRPC VMIDs `HLOS + MSS_MSA + SSC_Q6 + ADSP_Q6`, and a new
|
||||
`qcom,msm-adsprpc-mem-region` helper in `drivers/misc/fastrpc.c` that preassigns the heap.
|
||||
- Qualcomm FastRPC userspace installed: `sdsprpcd`, `adsprpcd`, `libsdsp_default_listener.so`.
|
||||
- `blueline-slpi-ready.sh` now disables remoteproc crash recovery before its one-shot SLPI start.
|
||||
|
||||
**What the tests proved:**
|
||||
|
||||
1. The old TZ/heap failure is fixed. With the Lineage adsprpc memory conversion, the boot log shows
|
||||
the heap preassigned and SDSP FastRPC skipping duplicate hyp-assign. The previous
|
||||
`qcom_scm Assign memory protection call failed -22` / `SDSP fastrpc hyp-assign rejected by TZ`
|
||||
wall is no longer the current failure.
|
||||
2. Crash recovery had been hiding first-failure evidence. `auto_boot=false` only prevents PAS probe
|
||||
boot; remoteproc still relaunches after a crash unless `recovery` is set to `disabled`.
|
||||
3. Clean one-shot test **before** the SDSP capability-query patch:
|
||||
`sdsprpcd sensorspd` was started, SLPI recovery disabled, then SLPI started once. Result:
|
||||
`remoteproc2` became `crashed`. First useful kernel failure:
|
||||
`qcom,fastrpc ... Error: dsp information is incorrect err: -1`, followed by
|
||||
`watchdog received: SFR Init: wdog or kernel error suspected`.
|
||||
4. Kernel compatibility patch then added: for SDSP only, if the DSP utilities capability query is
|
||||
rejected with `-EPERM`, cache zero capabilities and continue. Rationale: Qualcomm userspace treats
|
||||
missing capabilities as "not supported"; Lineage downstream adsprpc does not make this optional
|
||||
query a hard sensors-PD gate.
|
||||
5. One-shot test **after** that patch caused SSH/network/UI to wedge during the SLPI start window
|
||||
before post-start logs could be collected. Host later saw the phone in fastboot. So the patch
|
||||
likely moved execution past the old `dsp information` gate, but we do **not** have a clean next
|
||||
failure line yet.
|
||||
|
||||
**Next real work:** do not keep hand-starting SLPI over SSH and hoping logs flush. Add a capture path
|
||||
that survives the wedge (pstore/ramoops, serial/console, persistent journal sync, or earlier in-kernel
|
||||
tracepoints), then repeat the one-shot `sdsprpcd sensorspd` test. In parallel, source-diff mainline
|
||||
FastRPC SDSP/sensors-PD against Lineage `drivers/char/adsprpc.c`, especially `INIT_ATTACH_SNS`,
|
||||
sensors static PD, listener1/geteventfd, service-location/PDR, and the domain/session handling around
|
||||
`sdsprpcd sensorspd`.
|
||||
|
||||
## 2026-06-29 (cont.) — FastRPC sensors-PD/servreg gap isolated (move #2 design)
|
||||
|
||||
Source-diff of mainline `drivers/misc/fastrpc.c` vs Lineage `drivers/char/adsprpc.c` around the
|
||||
sensors PD (`INIT_ATTACH_SNS`). This is the concrete next-conversion target (move #2); it is gated
|
||||
behind making the SLPI-start wedge survivably observable (move #1).
|
||||
|
||||
**Mainline sensors attach is a bare RPC** — `references/linux-7.1.1-sdm845-CANONICAL/drivers/misc/fastrpc.c:1735`:
|
||||
`fastrpc_init_attach(fl, SENSORS_PD)` sends `FASTRPC_RMID_INIT_ATTACH` (method 0, tgid arg), sets
|
||||
`fl->pd = 2`, returns. `grep` confirms mainline fastrpc.c has NO `pdr`/`servreg`/`service_location`/
|
||||
`spdname` wiring — only the generic `INIT_CREATE_STATIC` (method 8) ioctl (`:1340`/`:2193`).
|
||||
|
||||
**Lineage wraps the sensors PD in a servreg/PDR scaffold mainline lacks:**
|
||||
- `INIT_ATTACH_SENSORS` tags the client (`adsprpc.c:2250`):
|
||||
`fl->spdname = SENSORS_PDR_SERVICE_LOCATION_CLIENT_NAME;` (= `"sensors_pdr_adsprpc"`), `fl->pd = 2`.
|
||||
- At probe, gated on DT prop `qcom,fastrpc-adsp-sensors-pdr` (`adsprpc.c:4371`), it registers a
|
||||
service-location notifier: `get_service_location("sensors_pdr_adsprpc", "tms/servreg", &get_service_nb);`
|
||||
|
||||
So the production SLPI firmware expects the AP to track the sensors PD via `tms/servreg`
|
||||
(PDR/service-location) around `INIT_ATTACH_SNS`. Mainline supplies servreg via **pd-mapper**
|
||||
(userspace DB) + the glink reverse channel — so the open question is whether the sensors-PD servreg
|
||||
domain is present in pd-mapper's DB AND whether the reverse listener is up when `INIT_ATTACH_SNS`
|
||||
fires (ties to the S6 auto-boot ordering race).
|
||||
|
||||
**Conversion target (move #2):** register a PDR service-location lookup for the SDSP channel in
|
||||
`fastrpc_rpmsg_probe` (`fastrpc.c:2432`) when `domain_id == SDSP_DOMAIN_ID`, using mainline's existing
|
||||
`pdr_handle_alloc()` (`drivers/soc/qcom/pdr_interface.c`, already used by `apr.c`/`pmic_glink.c`) —
|
||||
mirroring Lineage's `get_service_location`. Service path TBD from the SLPI firmware expectation /
|
||||
pd-mapper config (likely an `"msm/slpi/sensors"`-style servreg domain).
|
||||
|
||||
**Capture (move #1) — state:**
|
||||
- Running kernel already has `CONFIG_PSTORE_RAM=y`/`PSTORE_CONSOLE`/`PSTORE_PMSG`/`PSTORE_COMPRESS`
|
||||
(verified on phone via `/proc/config.gz`). pstore backend is currently `(null)` because there is
|
||||
**no `ramoops` reserved-memory node** in our DTS (only apq8016/msm8992 boards carry one).
|
||||
- So ramoops capture needs only a reserved-memory node — NO config change. BUT the physical address
|
||||
must be bootloader-preserved across the watchdog→reset (the wedge lands the phone in fastboot).
|
||||
Do NOT guess an address (brick risk, no EDL net). Candidate preserved region to verify against the
|
||||
live map in `/proc/device-tree/reserved-memory/`: Lineage `removed_region@85fc0000`, or a top-of-RAM
|
||||
carve. Verify ABL preserves it across reset before flashing.
|
||||
- Safe, no-flash capture helper staged now: `tools/slpi-capture.sh` — snapshots dmesg to a timestamped
|
||||
file on the UFS rootfs and tails `/dev/kmsg` continuously through the SLPI start, so a partial
|
||||
capture survives the wedge on reboot even before ramoops is wired. Inert until invoked; does NOT
|
||||
start SLPI.
|
||||
|
||||
**Coordination note (2026-06-29):** another model is baking the RTC time-fix (pm8998 `rtc@6000`
|
||||
label + blueline `&rtc { allow-set-time; }`) — uncommitted in the canonical worktree. Do not edit
|
||||
`pm8998.dtsi`/`sdm845-google-blueline.dts` until that lands; the fastrpc conversion (a different
|
||||
file) and the ramoops node (`sdm845.dtsi` shared reserved-memory) can proceed collision-free.
|
||||
|
||||
**CORRECTION 2026-06-29 (post-Opus, supersedes the "pd-mapper missing" premise above):**
|
||||
pd-mapper is **IN-KERNEL** here, not userspace — `CONFIG_QCOM_PD_MAPPER=m` (module
|
||||
`qcom_pd_mapper`, aux devices `qcom_common.pd-mapper.{0,1,3}`), and it is **serving**
|
||||
(`wlan0` up proves the wlan PD is advertised). The earlier bug was that the in-tree
|
||||
`arch/arm64/configs/sdm845.config` carried upstream `CONFIG_QCOM_PD_MAPPER=n` and,
|
||||
being merged *last* by `merge_config.sh -m`, overrode pmaports' `=m` on every build
|
||||
→ wifi/sound/SLPI-sensor all lost their PD broker. Opus flipped that line to `=m`,
|
||||
rebuilt, reflashed → wifi + sound came back. See `build.md` gotcha #0.
|
||||
Crucially, the in-kernel `sdm845_domains[]` table **includes `slpi_root_pd` AND
|
||||
`slpi_sensor_pd`** — so the sensors PD *is* being advertised now. Therefore:
|
||||
1. The "servreg server missing → install userspace pd-mapper" premise in this section
|
||||
was WRONG (I installed then reverted the userspace `pd-mapper` apk; userspace pd-mapper
|
||||
is explicitly NOT used on blueline). Do not install it.
|
||||
2. The in-kernel fastrpc `pdr_add_lookup` conversion (move #2 above) is NOT the gap —
|
||||
the sensors-PD servreg server is present in-kernel. Drop that conversion.
|
||||
3. **Every prior SLPI conclusion (auto-boot ordering, fastrpc-attach EPERM, sar.cc:27)
|
||||
was drawn on a substrate with no sensors PD advertised.** Re-test SLPI on the
|
||||
post-pd-mapper (`=m`) kernel before any more fastrpc/SLPI source work.
|
||||
|
||||
## 2026-06-29 live result — Lineage adsprpc memory conversion moved the wall
|
||||
|
||||
**This was real progress, not log suppression.** We ported the blueline-specific
|
||||
Lineage/downstream adsprpc memory behavior into the 7.1.1 mainline tree:
|
||||
|
||||
- `adsp_mem` / `fastrpc_mem` are split like Lineage: fixed no-map
|
||||
`pil_adsp_mem@8c500000` for `adsp_pas`, dynamic reusable `adsp_region` for adsprpc.
|
||||
- The SDSP FastRPC node uses the Lineage VMID set:
|
||||
`HLOS + MSS_MSA + SSC_Q6 + ADSP_Q6`.
|
||||
- A new mainline `qcom,msm-adsprpc-mem-region` helper preassigns `adsp_region`
|
||||
before SDSP FastRPC probes, matching downstream `qcom,msm-adsprpc-mem` instead
|
||||
of letting mainline assign the heap late.
|
||||
|
||||
**Verified on hardware after deploy to slot A:**
|
||||
- `qcom_scm Assign memory protection call failed -22` count: **0**.
|
||||
- Boot log says the adsprpc heap was preassigned and SDSP FastRPC skipped duplicate
|
||||
hyp-assign.
|
||||
- With corrected manual ordering (listener watcher running before SLPI start),
|
||||
`SNS_REG_INIT` count after start: **0**.
|
||||
- The failure moved later to:
|
||||
`EF:sensor_process:1:sar.cc:27:chre_utils fatal`.
|
||||
|
||||
So the old wall is fixed: the TZ/heap `-22` and `SNS_REG_INIT` are no longer the
|
||||
current explanation. The current SLPI wall is downstream of attach/init:
|
||||
`hexagonrpcd -f /dev/fastrpc-sdsp -d sdsp -s` still reports
|
||||
`Could not attach to FastRPC node: Operation not permitted`, and the firmware later
|
||||
dies in SAR/CHRE. The next Lineage conversion target is therefore the **sensor/SAR
|
||||
FastRPC attach path**, not another attempt to mask the crash-loop.
|
||||
|
||||
Symptom: SLPI remoteproc loads and reaches `running`, then the firmware
|
||||
faults in CHRE / SAR sensor init (`sar.cc:27`, chre sar_utils) and
|
||||
crash-loops every ~10s. hexagonrpcd ends up STOPPED. SLPI runs fine on
|
||||
the LineageOS/Android side of the same phone, so the gap is on ours.
|
||||
|
||||
## Reference sources (corrected 2026-06-22 — was wrong before)
|
||||
|
||||
Earlier "Android reference" diffs were against `references/msm-google-crosshatch`
|
||||
= Google AOSP `kernel/msm` 4.9.270 (`android.googlesource.com/kernel/msm`).
|
||||
That is STOCK Google, not what the phone runs. The phone runs LineageOS 22.2
|
||||
(slot B). The authoritative references are now on disk and provenance-verified:
|
||||
|
||||
- LOS kernel SOURCE: `references/los-kernel-blueline-4.9/`
|
||||
= `LineageOS/android_kernel_google_msm-4.9` @ `lineage-22.2`, **4.9.337,
|
||||
commit b929452c5**. The blueline device tree
|
||||
(`LineageOS/android_device_google_crosshatch`, `lineage_blueline.mk`,
|
||||
PRODUCT_MODEL "Pixel 3") pulls this kernel via `lineage.dependencies`
|
||||
→ `kernel/google/msm-4.9`.
|
||||
Provenance match: boot.img string is `4.9.337-gb929452c5c49` (built 0615) —
|
||||
same sublevel, same commit hash. This is the literal source of the kernel
|
||||
running on slot B.
|
||||
- LOS DTB: `/home/casey/Downloads/los-blueline.dts` (decompiled).
|
||||
- LOS vendor partition: `pixel3-work/vndlos-mnt` (confirmed LOS via
|
||||
`vendor.lineage.health-service`, `hal_lineage_livedisplay_qti` — not stock).
|
||||
|
||||
Note: LineageOS does NOT ship its own qcom remoteproc/PIL/fastrpc drivers —
|
||||
it inherits `google/msm-4.9`. So crosshatch 4.9 is the correct driver-source
|
||||
base, just an older point release; the LOS-specific deltas are DTB + vendor HALs.
|
||||
|
||||
## Mainline vs LOS SLPI lifecycle — what is and isn't a real gap
|
||||
|
||||
LOS uses the downstream `qcom,pil-tz-generic` driver
|
||||
(`subsys-pil-tz.c` + `subsystem_restart.c` + `peripheral-loader.c`).
|
||||
Mainline uses `qcom,sdm845-slpi-pas` (`qcom_q6v5_pas.c` + `qcom_q6v5.c`).
|
||||
The DT property lists differ, but most differences are spelling, not behavior:
|
||||
|
||||
NOT gaps (verified):
|
||||
- **Identity** — PAS id 12, crash-reason SMEM 424, ssctl 0x16, firmware "slpi"
|
||||
all match mainline match-data (`qcom_q6v5_pas.c:1300-1312`).
|
||||
- **Lifecycle handshake** — LOS `qcom,gpio-err-fatal/err-ready/stop-ack/force-stop`
|
||||
resolve to `qcom,smp2pgpio-ssr-smp2p-3-in`, entry "slave-kernel",
|
||||
remote-pid 3 (`los-blueline.dts:10453`) — i.e. SMP2P, not raw GPIO. Mainline
|
||||
wires the same SMP2P as `slpi_smp2p_in` fatal/ready/handover/stop-ack +
|
||||
`slpi_smp2p_out` stop (`blueline-pmos-slpi.dts:3460-3469`). Same hardware.
|
||||
Behavioral proof: the subsystem reaches `running` (the `ready` SMP2P fired) —
|
||||
a handshake gap could not produce a post-boot crash.
|
||||
- **signal-aop / AOP load_state** — LOS `pil_notify_aop()` sends QMP
|
||||
`{class: image, res: load_state, name: slpi, val: on}`
|
||||
(`peripheral-loader.c:949-955,980`). Mainline `q6v5_load_state_toggle()`
|
||||
sends the BYTE-IDENTICAL string via `qmp_send` (`qcom_q6v5.c:31-32`), gated on
|
||||
`qmp_get` returning non-NULL (`:28,335`). Our DTS wires it: SLPI node has
|
||||
`qcom,qmp = <0x2f>` (`blueline-pmos-slpi.dts:3464`) → `qcom,sdm845-aoss-qmp`
|
||||
(`:4912`), and `CONFIG_QCOM_AOSS_QMP=y`. So this signal genuinely fires.
|
||||
- The LOS GPIO/proxy/mbox/pil-tz DT props are bindings of `subsys-pil-tz.c`/
|
||||
`subsystem_restart.c`, which mainline does not have. Pasting them onto our
|
||||
PAS node is inert — the PAS driver never reads them. (This was the earlier
|
||||
"reconcile the DT" dead end.)
|
||||
|
||||
THE REAL GAP (runtime power, SLPI-specific):
|
||||
- **`qcom,keep-proxy-regs-on`.** Mainline `qcom_pas_handover()` drops ALL proxy
|
||||
power the instant the remote signals handover — px/cx regulators, xo/aggre2
|
||||
clocks, and the lcx/lmx power domains (`qcom_q6v5_pas.c:386-397`,
|
||||
unconditional). LOS deliberately keeps SLPI's vdd_cx/vdd_mx rails enabled for
|
||||
the subsystem's whole life: `disable_regulators(..., reg_no_disable=true)`
|
||||
skips `regulator_disable()` when `keep_proxy_regs_on` is set
|
||||
(`subsys-pil-tz.c:501-502`, via `pil_remove_proxy_vote` `:583`).
|
||||
Strongest signal it's load-bearing: `qcom,keep-proxy-regs-on` is set on the
|
||||
SLPI node and NOT on adsp (`los-blueline.dts` ssc node ~3108 vs adsp ~3053).
|
||||
Hypothesis: SLPI's CHRE/SAR runtime needs the AP to hold CX/MX after boot;
|
||||
mainline yanking them post-handover collapses the rails → `sar.cc:27` fault.
|
||||
Matches the boot-then-crash signature exactly, and matches every prior win on
|
||||
this project (Android holds a resource mainline drops; port it).
|
||||
|
||||
(Secondary, same direction: `qcom,proxy-timeout-ms = 10000` — LOS holds proxy
|
||||
power 10s post-boot then unvotes; mainline releases at handover with no grace.)
|
||||
|
||||
FastRPC: LOS `qcom,msm-fastrpc-compute` (`adsprpc.c`, 7 CBs + per-CB IOMMU) vs
|
||||
mainline `qcom,fastrpc` (vmids + shared-dma-pool) — different drivers, but our
|
||||
mainline node already attaches (`/dev/fastrpc-sdsp` exists). Not the blocker.
|
||||
|
||||
## The patch (2026-06-22)
|
||||
|
||||
`pmaports .../linux-postmarketos-qcom-sdm845/slpi-keep-proxy-power-on.patch`
|
||||
(wired into APKBUILD `source=`, pkgrel 3→4, checksums updated).
|
||||
|
||||
Adds a per-SoC opt-in `keep_proxy_pds_on` flag to `qcom_pas_data`/`qcom_pas`,
|
||||
set true only for `sdm845_slpi_resource_init`, and makes `qcom_pas_handover()`
|
||||
return early (skipping the proxy teardown) when set. adsp/cdsp/modem unchanged.
|
||||
This mirrors downstream `keep-proxy-regs-on`: SLPI's cx/mx (lcx/lmx) stay
|
||||
powered past handover. Patch verified to apply against the build tree
|
||||
(tag 85f1df2a, identical to `references/linux-7.1.1-sdm845-CANONICAL`); dry-run rc=0.
|
||||
|
||||
Known trade-off (acceptable on a throwaway test bench): power is held across
|
||||
stop/SSR-restart too, so refcounts are not balanced on a clean shutdown — same
|
||||
"never turns the rails off" behavior downstream has. One online/boot attempt
|
||||
per cycle anyway.
|
||||
|
||||
## Status / next
|
||||
|
||||
- [done] References corrected + provenance-verified.
|
||||
- [done] Behavioral diff (LOS source vs mainline) — gap isolated to proxy power.
|
||||
- [done] Patch written and proven to apply.
|
||||
- [done] Folded into the **unified 7.1.1 tree as a commit** (no longer a `source=`
|
||||
`.patch`): `references/linux-7.1.1-sdm845-CANONICAL`, alongside the SLPI-enable DTS commit
|
||||
and the fastrpc SDSP tolerance commit. Built via pmbootstrap (see build.md).
|
||||
- [done] Deploy unified 7.1.1 kernel to slot A, reboot (2026-06-26, kernel r2 LLVM).
|
||||
- [DISPROVEN 2026-06-26] LIVE TEST: keep-proxy-power-on does **NOT** stop the
|
||||
crash-loop. On the booted r2 kernel SLPI still faults
|
||||
`err_qdi.c:456:EF:sensor_process:1:sar.cc:27:chre_utils fatal` and crash-loops
|
||||
every ~10s (observed crash #66→#72 in one capture). So the proxy-rails-held
|
||||
hypothesis is wrong — holding cx/mx past handover did not save it. The
|
||||
`qcom_ssc_sensors` module (CONFIG_QCOM_SSC_SENSORS=m) is **present and loaded**
|
||||
this build, and SLPI still crashes — so the SSC sensor *transport* being absent
|
||||
was not the (whole) cause either. The fault is inside the SLPI firmware's SAR
|
||||
(CHRE sar.cc) path itself: mainline isn't giving the firmware the SAR-sensor
|
||||
hardware/config it expects, and neither rails nor the SSC glink transport fix it.
|
||||
- [RESOLVED 2026-06-29] The `qcom_scm ... Assign memory protection call failed -22`
|
||||
/ `SDSP fastrpc hyp-assign rejected by TZ (-22)` wall is fixed by the Lineage
|
||||
adsprpc memory conversion above. Do **not** chase this as the current failure.
|
||||
- [current] Next: convert the remaining Lineage sensor/SAR FastRPC path:
|
||||
`sscrpcd sensorspd`, `libsdsprpc.so`, sensors PDR/service-location behavior, and
|
||||
whatever makes `INIT_ATTACH_SNS` attach cleanly instead of returning `EPERM`.
|
||||
|
||||
## 2026-06-27 (S6, Opus) — VERBOSE TRACE settles the registry theory: it's an auto-boot ORDERING race, not file serving
|
||||
|
||||
Built a **verbose hexagonrpcd** (the `-Dhexagonrpcd_verbose=true` meson option only
|
||||
exists in the newer source `pmos/_src/hexagonrpc` @ dd9ac70, NOT in the v0.4.0 tarball;
|
||||
our two patches — registry-nodes + method-29 fstat — are already applied in that working
|
||||
tree). Snapshotted it to `temp/hexagonrpcd/hexagonrpc-0.4.0.tar.gz` and built with verbose
|
||||
on. Deployed, captured two clean traces of what the SLPI actually requests.
|
||||
|
||||
**THE FINDING (instrument-proven, not inferred):**
|
||||
- Across both captures the SLPI made **zero** registry file requests (`openat`/`opendir`
|
||||
of real paths = 0) before crashing at `SNS_REG_INIT:36`. hexagonrpcd's reverse listener
|
||||
was attached and blocked in the listen loop, and **received nothing**.
|
||||
- The ~981 log lines were all hexagonrpcd's OWN failures to hold the device node:
|
||||
972 `No such file or directory` (`/dev/fastrpc-sdsp` vanishes while SLPI is down mid
|
||||
crash-cycle), 139 `Broken pipe`, 94 `Operation not permitted`, 9 `Resource busy`.
|
||||
- So **`SNS_REG_INIT:36` fails BEFORE the SLPI requests any file.** Serving the registry
|
||||
(staging path, dsp/sdsp libs, the gate, RestartSec=0) was answering a request that, at
|
||||
this crash point, never happens. The registry-serving theory is DEAD for SNS_REG_INIT.
|
||||
|
||||
**Root cause (circular, self-reinforcing):** the SLPI auto-boots at qcom_q6v5_pas module
|
||||
probe (~3s) → its registry-init needs a reverse-RPC to the AP → no listener registered yet
|
||||
(hexagonrpcd is userspace, ~33s) → fails instantly at SNS_REG_INIT → crashes → the crash
|
||||
tears down `/dev/fastrpc-sdsp` → reappears ~0.3s later → vanishes again. hexagonrpcd can
|
||||
never hold a stable attach long enough to register its listener; the node thrashes faster
|
||||
than it can attach + enter the listen loop. The rare earlier glimpses of `sar.cc:27` were a
|
||||
hammering hexagonrpcd momentarily aligning with one restart's request window — not a stable
|
||||
state. (Note: crash line drifts :36/:39 boot-to-boot; same failure, pre-file-request.)
|
||||
|
||||
**Real fix direction (Casey's architectural read, now proven):** the SLPI must NOT auto-boot
|
||||
at module probe. Hold it stopped until hexagonrpcd is attached AND in its listen loop, then
|
||||
start it once so its first registry request hits a ready listener. That's a kernel/remoteproc
|
||||
ordering change (`auto_boot=false` for the SLPI PAS, or defer module bind), NOT more userspace
|
||||
plumbing. Mirrors Android: SLPI firmware boots but sensor_process is triggered by the AP
|
||||
(chrecd `chre_slpi_start_thread`) AFTER the listener/daemon is up — confirmed by subagent that
|
||||
`INIT_ATTACH_SNS` does NOT itself start sensor traffic; chrecd does.
|
||||
|
||||
**Still-valid hygiene from S5/S6 (real, but does NOT fix SNS_REG_INIT):** the registry staging
|
||||
path bug (`cp -a .../registry/.` was nesting `registry/registry/`; fixed to flatten) and the
|
||||
`blueline-slpi-ready.service` gate pattern. `libchre_slpi_skel.so` IS needed for chrecd and was
|
||||
found at `pixel3-work/vndlos-mnt/dsp/sdsp/` (whole `dsp/sdsp/` tree: skel + nanoapps +
|
||||
fastrpc_shell_2); it's static vendor firmware, belongs in a firmware package, not a one-time copy.
|
||||
|
||||
## 2026-07-01 — crash-loop-vs-modem interaction found + fixed; SLPI ruled OUT of the modem 52 wall
|
||||
|
||||
Wired `blueline-slpi-ready.service` into the real boot chain (`modem-qrtr-ready.service`
|
||||
`After=`/`Wants=` it, mirroring Android's `init.qcom.devstart.sh` which sets
|
||||
`vendor.qcom.devup` — the property `modem-qrtr-ready` was modeled after — immediately after
|
||||
kicking SLPI). This surfaced a real regression: `blueline-slpi-ready.sh` disabled SLPI crash
|
||||
recovery for a "single-shot" start. When the SAR/CHRE fault crashed it with recovery disabled,
|
||||
the driver had nowhere to go — glink-edge was left half torn down, storming
|
||||
`intent request timed out` every ~10s indefinitely and taking the modem's own QMI/QRTR
|
||||
transport down with it (`endpoint hangup`). Android never disables recovery for this
|
||||
(`ssr_setup`/`ssr_diag` in `init.sdm845.rc` exist precisely to handle ongoing SSR cleanly).
|
||||
|
||||
**Fix:** recovery stays enabled through the boot attempt (so any crash tears down/rebuilds
|
||||
glink cleanly via the kernel's own SSR path — verified: crash→recovering→running cycling with
|
||||
zero storm across 50 crashes in one test), then the script catches SLPI in its brief `running`
|
||||
window and issues a clean `echo stop` to park it — one attempt, not an infinite crash-loop,
|
||||
since the modem does not benefit from SLPI staying up (see below).
|
||||
|
||||
**Decisive result, three separate live boots:** with SLPI crash-looping 50 times in ~7 minutes,
|
||||
with SLPI crash-free and running continuously, and with SLPI cleanly parked/offline —
|
||||
`mmcli -m 0 --enable` returned byte-for-byte identical `QMI protocol error (52): 'DeviceNotReady'`
|
||||
every time. **SLPI state has zero measurable effect on the modem RF-arm wall.** This closes
|
||||
"Subsystem coordination (SLPI PD state?)" from `los-mss-translated.c`'s delta list as a modem
|
||||
blocker. SLPI's own SAR/CHRE crash (this file, above) remains real and unfixed, but is now
|
||||
fully decoupled from modem bring-up — see `modem.md` TOP LEAD #12 for where the modem hunt
|
||||
went next (`dms-ui-ready.py`, DMS 0x0064).
|
||||
132
saf/device/touch.md
Normal file
132
saf/device/touch.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# PAF · Touch — ST FTS, reset polarity, GPI-DMA, tap-to-wake
|
||||
|
||||
## Current glass result — 2026-07-11
|
||||
|
||||
On kernel `7.1.1-sdm845-g56b0a053bef8`, daytime touch works and the FTS
|
||||
recognizes DBLTAP. The current defect is downstream of gesture recognition:
|
||||
the first double tap can prepare the panel and then immediately unprepare it
|
||||
(green flash then black); a second double tap wakes the display. The first
|
||||
half-wake is kernel-trace confirmed. Treat DT2W as **detected but unreliable**
|
||||
until the screen-toggle/DPMS prepare-unprepare ping-pong is fixed. This is not
|
||||
the old IRQ/FIFO bug.
|
||||
|
||||
Status 2026-07-10 (morning): TAP-TO-WAKE WORKS, verified on glass by
|
||||
Casey on kernel d7a8f7dc9. FTS probes clean, gesture mask armed at boot,
|
||||
double-tap wakes from armed sleep, touch alive after wake, hypridle 60s
|
||||
idle screen-off enabled. The one remaining suspend-path defect was the
|
||||
IRQ handler, not the controller — see the closed item below.
|
||||
|
||||
## Suspend-path doctrine (2026-07-10, learned the hard way)
|
||||
|
||||
- `gesture_enabled` MUST be 1 before any DPMS-off. Unarmed suspend takes
|
||||
the SLPI-handoff branch (fts_suspend_work): controller handed to the
|
||||
PARKED sensor island + IRQ disabled → resume races → level IRQ storm
|
||||
(millions on IRQ 148) → kernel kills the IRQ → touch dead until driver
|
||||
rebind or reboot. With SLPI's sar.cc wall unfixed, the handoff target is
|
||||
unavailable — gesture-armed suspend (AP keeps the bus) is the only safe mode.
|
||||
- The compiled gesture_mask sysfs is the STASH-THEN-APPLY variant:
|
||||
`echo "01 20" >` stages only; the following `cat` applies (updateGestureMask)
|
||||
and sets gesture_enabled=1. blueline-dt2w.sh does both since de4fe40.
|
||||
- Live recovery from a dead touch IRQ, no reboot:
|
||||
`echo 2-0049 > /sys/bus/i2c/drivers/fts/unbind && echo 2-0049 > .../bind`
|
||||
— but Hyprland then holds stale input handles (taps flow at evdev level,
|
||||
UI ignores them); a session restart or reboot is needed after rebind.
|
||||
- VERIFIED 2026-07-10 (clean boot-time arm, Casey on glass): daytime touch
|
||||
works, and touch SURVIVES a full sleep/wake cycle — the regression is
|
||||
closed. Wake was power-button; double-tap did NOT wake.
|
||||
- CLOSED 2026-07-10 (kernel d7a8f7dc9) — tap-to-wake works on glass. The
|
||||
"controller never enters gesture scan" theory was WRONG: enterGestureMode
|
||||
runs and succeeds at every armed suspend (Sense OFF → gesture mode →
|
||||
enableGesture DONE in dmesg). The real defect was fts_interrupt_handler:
|
||||
during armed sleep bus_refmask==0, so the bus-ref gate refused every IRQ
|
||||
and returned WITHOUT reading the FIFO. Level IRQ line stayed asserted →
|
||||
refired forever (the "582k IRQs" were the unserviced line, not full-rate
|
||||
scanning), gesture events were dropped unread (why double-tap never
|
||||
woke), and a bogus release call in the refusal branch logged one pr_err
|
||||
per IRQ — 85k lines in one screen-off window, enough printk to freeze
|
||||
the phone on long sleeps (2026-07-10 morning freezes). Fix: service the
|
||||
FIFO without taking a bus ref when gesture_enabled && sensor_sleep
|
||||
(taking one would queue a resume per IRQ), drop the bogus release,
|
||||
ratelimit the bus-ref pr_err. The port's 30177cb7e (AP keeps bus in
|
||||
gesture mode) had changed the ownership model without teaching the IRQ
|
||||
path — classic lost-handler.
|
||||
|
||||
## The hardware truth (settled — do not re-litigate)
|
||||
|
||||
- This unit is the **LG panel + ST FTS** variant: FTS controller at
|
||||
**0x49 on i2c-2 (888000 geni SE)**. Settled from per-unit persist
|
||||
calibration + LOS init.blueline.rc.
|
||||
- The Samsung `sec_ts@48` nodes in the downstream DTBO (fragment@81,
|
||||
`sec,reset_gpio`, …) belong to the OTHER hardware variant. **Never cite
|
||||
fragment@81 as evidence for our touch config** — that mistake shipped
|
||||
ba0620d5c and killed touch for a day.
|
||||
- Pins: reset tlmm 99 (**active-low RESETB**), IRQ tlmm 125 (level low),
|
||||
AP↔SLPI bus switch tlmm 136.
|
||||
|
||||
## Reset polarity — the post-mortem
|
||||
|
||||
The ported vendor driver (`drivers/input/touchscreen/stm/`) uses **gpiod
|
||||
logical assert semantics**: acquires reset `GPIOD_OUT_HIGH` ("asserted"),
|
||||
pulse is `set(1)` → 20ms → `set(0)` → 50ms, and its own comment says
|
||||
"active-low reset" (fts.c ~4320). Therefore the DT flag must be
|
||||
**`GPIO_ACTIVE_LOW`**, so logical assert = physical LOW.
|
||||
|
||||
With `GPIO_ACTIVE_HIGH` (ba0620d5c, 2026-07-09 14:02) deassert drives the
|
||||
pin physically LOW → RESETB held forever → controller NACKs every I2C
|
||||
access → probe fails after 3 reset attempts. Signature in dmesg:
|
||||
`gpi ... Error in Transaction: code=0x10 status=0x40` on EVERY read
|
||||
(including the first 8-byte poll), `fts_system_reset: ERROR 80000002`,
|
||||
`Probe Failed!`. Nobody noticed for a day because every boot in that window
|
||||
had a black display. f3c5f5a11 reverts to ACTIVE_LOW.
|
||||
|
||||
**Triage rule:** all-reads-fail = controller not out of reset / not powered
|
||||
/ bus switch wrong — a dead chip, not a DMA problem. Large-read-only
|
||||
failure = the (historic) GPI-DMA wall below.
|
||||
|
||||
## GPI-DMA wall (historic — status needs one clean-boot check)
|
||||
|
||||
The 888000 SE is GPI-mode-only (its SE firmware has FIFO disabled — no
|
||||
FIFO fallback exists; verified in i2c-qcom-geni probe). Historically small
|
||||
reads worked and the first large read (209-byte readSysInfo) died with
|
||||
`MSM_GPI_TCE_UNEXP_ERR`. Instrumentation ff6e6e2e8 logs completion status +
|
||||
error-log register on any recurrence.
|
||||
**ANSWERED 2026-07-10, first clean f3c5f5a11 boot: the wall is GONE.**
|
||||
Zero `gpi ... Error in Transaction` lines in dmesg; the driver read the
|
||||
105,140-byte firmware file and ran CX/Panel CRC verification over the bus
|
||||
(`Fw Update Finished! error = 00000000`), FTS input device registered.
|
||||
Large GPI-DMA transfers on the 888000 SE work. Whatever ate the 209-byte
|
||||
read in the 2026-07-08 era was fixed by the intervening driver/power work
|
||||
or was reset-state fallout all along; if it ever recurs, the ff6e6e2e8
|
||||
telemetry will say why. Deep diagnosis archive: ../CLAUDE.md "Touch /
|
||||
FTS-on-mainline key facts".
|
||||
|
||||
## Gesture / suspend design (in-tree, working as of 07-09 morning)
|
||||
|
||||
- 08687bec9 power+reset sequencing, e31aa9da7 prototype fix,
|
||||
30177cb7e keep AP bus ownership during gesture mode,
|
||||
3d9eb1028 sensor_sleep bookkeeping in gesture-mode suspend.
|
||||
- Gesture mode: on suspend the driver keeps the controller alive for
|
||||
double-tap; `XF86WakeUp` → Hyprland bind → `blueline-screen-toggle`
|
||||
(see display.md). dt2w service wiring: `blueline-dt2w.service` in
|
||||
rootfs-overlay (uncommitted edits pending as of 2026-07-10).
|
||||
- **Proximity gate (2026-07-20):** `blueline-screen-toggle` checks
|
||||
`net.hadess.SensorProxy` `ProximityNear` over D-Bus before dispatching
|
||||
DPMS on; if blocked (phone in pocket) the wake is suppressed and logged,
|
||||
the FTS gesture still fires XF86WakeUp either way. Fails open if the
|
||||
D-Bus query errors, so a dead sensor proxy never permanently blocks wake.
|
||||
Requires `iio-sensor-proxy` running, which is now always-on (see slpi.md).
|
||||
This closes half the "hot pocket" problem; the other half (screen
|
||||
already on + locked, phone pocketed) is `blueline-proximity-lock`
|
||||
(`overlays/systemd-user/`), a `monitor-sensor --proximity` watcher that
|
||||
blanks via the same `blueline-screen-toggle off` path when locked.
|
||||
|
||||
## THE GOAL (unchanged): one FTS driver = daytime touch + tap-to-wake
|
||||
|
||||
Like LOS: FTS drives the controller when awake; on suspend it hands the
|
||||
controller to the SLPI over the tlmm 136 switch, where the
|
||||
`sns_touch_gesture` nanoapp (SLPI's own i2c bus 5) watches for double-tap
|
||||
and wakes the AP. stmfts (mainline driver) can never do this — no switch,
|
||||
no handoff; it is fallback-only. Two tracks converge:
|
||||
1. FTS daytime path — WORKING modulo the polarity fix verify.
|
||||
2. SLPI-side tap-to-wake — unblocked by the sar.cc fix (see slpi.md), but not
|
||||
required for the current AP-side gesture design.
|
||||
Loading…
Reference in a new issue