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
14 KiB
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 bls):
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:
- Decide + push the NAS mode preference (RAT set) — tell the modem which radio technologies to acquire — BEFORE asking it to power the RF.
- Push the ATEL "UI ready" status to the modem over DMS — a Qualcomm OEM DMS message.
- 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) andqcril_qmi_uim_change_provisioning_session@ 0x11e07e0. On Android these establish the USIM session before NAS asks for RF. On pmOS the SIM already readsreadyand primary-GW is provisioned (per live results), so card power-up is already satisfied — not the gap.uim-change-provisioning-sessiontiming 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)
- NAS Set System Selection Preference (mode/RAT pref) — 0x0033, qmicli-able
- DMS ATEL UI-ready push — DMS 0x0064, OEM, not qmicli-able
- 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:
- NAS 0x0033 (mode-pref) → DeviceUnsupported(25), identical to every out-of-order
attempt. Order does not rescue it. But
--nas-get-system-selection-preferencesucceeds 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. - DMS 0x0064 (UI-ready) → delivered, NoEffect(26), as always.
- 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).