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
16 KiB
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 -dDOES support Hexagon (llc --versionlistshexagon - Hexagon; confirmed disassemblesmba.mbncorrectly) — butmodem.mbn/mba.mbnare 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. Checkedquic/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 inandroid-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(msg0x0000, TLV0x01= AT command string, size-prefixed, max 1024B) → Operation Result;Received(indication0x0001, same string TLV) delivers the response async. This is the best structural template for what the realATservice'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 TLV0x01) next time the phone's available, thoughqmi_ati_reg_at_cmd_fwdexisting as a SEPARATE step from send/receive suggests the realATservice 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
- Against AT service (8): try the ATR-shaped guess first (
msg 0x0000, TLV0x01= 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). - If that's rejected, try
qmi_ati_reg_at_cmd_fwd-shaped registration first (aRegister-style message, unknown ID — try 0x0001-0x0005 range as a first sweep) before the send. - Speculative but cheap dispatch-table-order guess: the 5 strings carrying the
(qmi_svc_hdlr_ftype)cast prefix inmodem.mbnappear 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): try0x0020=Reset,0x0021=RegAtCmdFwd,0x0022=SendAtResponse,0x0023=SendAtUrc,0x0024=RegAtCmdFwdEx against service 8, watching for anything other than a uniform rejection. qrtr-lookup 21/231— still completely unexplored beyond confirming they're alive. Noqmi_mfs_/qmi_efs_-style strings found anywhere inmodem.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.- 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 isds_qmi_at.c: apps register to receive AT commands the modem forwards to them (Android'satfwd_daemonrole). There is no AT-execution path here to command the modem's own RF. Four0x0020registration-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–0x0030return error 57 (a service-state/"not open" gate). Real handler cluster at0x001e(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: perdiag_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 TLV0x16= 16 bytes, all zero (the rest of its fixed 4136-byte frame is unused). Msg0x0012is an event-log indication carrying timestamped lines ("<ts> Bugreport triggered"— an echo of our own trigger). Fired across a livemmcli --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). Many0x0000–0x0017handlers 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-refreshwatches the wrong indication (Refresh,0x2F) — the one that actually reports Active/Pending transitions isConfig Change(0x21), delivered only to a client that calledRegisterwith 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_nodewithQmi.DeviceOpenFlags.EXPECT_INDICATIONS(verified againstqcril-autoselect.py'sopen_device()) — a plainGio.Fileopen (my first draft) would have silently never delivered any indication at all, making a "no signal" result meaningless. Caught before running, not after.