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

9.5 KiB

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-inittqftpserv serving /mnt/vendor/persist/rfs/readonly/vendor/mbn/ (present, per modem.md). Verify the modem's requested path matches.
  2. EFS writablermtfs -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-capabilitiesNetworks 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).