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

262 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 -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 `0x0000``0x0030` 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 `0x0000``0x0017` 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.