Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/saf/device/evidence/qcril_online_sequence.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

229 lines
14 KiB
Markdown

# 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).