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
262 lines
16 KiB
Markdown
262 lines
16 KiB
Markdown
# 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.
|