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

16 KiB
Raw Blame History

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 0ERROR 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 0x00000x0030 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 0x00000x0017 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.