Skip to content

WiFi bringup: in-kernel WCN3990 firmware chain (2.4 + 5 GHz) - #124

Closed
greatgitsby wants to merge 10 commits into
commaai:masterfrom
greatgitsby:wifi
Closed

WiFi bringup: in-kernel WCN3990 firmware chain (2.4 + 5 GHz)#124
greatgitsby wants to merge 10 commits into
commaai:masterfrom
greatgitsby:wifi

Conversation

@greatgitsby

@greatgitsby greatgitsby commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Brings up WiFi on mainline with unmodified AGNOS userspace: in-kernel rmtfs and tqftpserv provide the modem firmware chain, and device-matched wlanmdsp.mbn plus SOM-tuned board data (via qcom,calibration-variant) fix the 5 GHz DFS firmware crash. Verified on mici: cold boot associates on ch100 (DFS) with no errors.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

vamOS System Profile

Changes vs master

Metric Change
Used space 3437.5MB → 3437.2MB (-.2MB)
Sparse image 3391.9MB → 3392.0MB (+0MB)
Package count 524 → 524
Directory size changes (>1MB)
Directory Change

Metric Value
Used space 3437.2MB / 5959.5MB
Files 56143
Directories 4762
Symlinks 3589
Packages 524

Top 10 Directories

Directory Size

Category Breakdown

Category Size %
xbps packages 2502.8MB 72.8%
Python venv 591.7MB 17.2%
Firmware 11.2MB .3%
Other 331.3MB 9.6%

Top 10 Packages by Size

Package Size
mit-krb5-libs-1.21.3_1 270.4MB
gawk-5.3.2_1 210.3MB
llvm-21_3 148.6MB
libldns-1.9.2_1 124.3MB
pd-mapper-1.0_1 73.9MB
libclang21-21.1.7_1 70.2MB
glib-2.88.0_1 67.4MB
gcc-14.2.1+20250405_4 66.6MB
libcap-progs-2.78_1 56MB
cmake-4.2.2_2 53.2MB

Top 30 Files by Size

File Size
/usr/lib/llvm/21/lib/libLLVM.so.21.1 124.3MB
/usr/lib/llvm/21/lib/libMLIR.so.21.1 89.7MB
/usr/lib/llvm/21/bin/mlir-transform-opt 82.9MB
/usr/lib/llvm/21/lib/libclang-cpp.so.21.1 56MB
/usr/lib/llvm/21/bin/mlir-translate 53.2MB
/usr/bin/uv 52.6MB
/usr/lib/llvm/21/bin/llvm-exegesis 52MB
/usr/lib/llvm/21/bin/llvm-bolt-binary-analysis 50.6MB
/usr/lib/gcc/aarch64-linux-gnu/14.2/gnat1 40.1MB
/usr/bin/gdb 39.1MB
/usr/lib/libgallium-26.1.3.so 37.7MB
/usr/lib/gcc/aarch64-linux-gnu/14.2/cc1plus 37.3MB
/usr/lib/libllvm-qcom.so 35.2MB
/usr/lib/gcc/aarch64-linux-gnu/14.2/cc1 35.2MB
/usr/lib/libRusticlOpenCL.so.1.0.0 34.3MB
/usr/lib/gcc/aarch64-linux-gnu/14.2/lto1 33.9MB
/usr/bin/lto-dump 33.9MB
/usr/local/venv/bin/ruff 31.8MB
/usr/share/icu/78.3/icudt78l.dat 31.5MB
/usr/lib/llvm/21/lib/libclang.so.21.1.7 30.2MB
/usr/lib/llvm/21/bin/c-index-test 28.6MB
/usr/local/venv/lib/python3.12/site-packages/gcc_arm_none_eabi/toolchain/libexec/gcc/arm-none-eabi/13.2.1/cc1 28.2MB
/usr/lib/libz3.so 27.9MB
/usr/bin/run 26.7MB
/usr/local/venv/lib/python3.12/site-packages/numpy.libs/libscipy_openblas64_-71e1b124.so 23.6MB
/usr/comma/updater 23.5MB
/usr/comma/setup 23.5MB
/usr/comma/reset 23.5MB
/usr/local/uv/python/cpython-3.12.13-linux-aarch64-gnu/bin/python3.12 22.2MB
/usr/local/uv/python/cpython-3.12.13-linux-aarch64-gnu/lib/libpython3.12.so.1.0 22.1MB

Bring up wlan0 on comma four (WCN3990/ath10k_snoc) without any Qualcomm
userspace, by making the kernel drive the entire WCN3990 firmware chain
itself. On the unmodified legacy rootfs the proprietary QRTR daemons are
inert, so the kernel must: auto-boot the modem, answer servreg (in-kernel
pd-mapper), serve wlanmdsp.mbn over TFTP-over-QRTR (new in-kernel tqftpserv),
and let the modem publish the QMI WLFW service ath10k_snoc waits on.

- 0012: remoteproc/qcom_q6v5_mss auto_boot=true (nothing in userspace starts
  the modem; firmware is built-in so no rootfs wait is needed).
- 0013: new drivers/soc/qcom/qcom_tqftpserv.c, a minimal read-only in-kernel
  TFTP-over-QRTR server (service 4096) mirroring andersson/tqftpserv:
  blksize/tsize/wsize/rsize/seek/timeoutms options, windowed reads, path
  translation into the kernel firmware loader. Also makes QCOM_PD_MAPPER
  select QCOM_PDR_HELPERS so servreg/PDR stay built-in on a modules-less
  rootfs.
- vamos.config: PD_MAPPER/PDR_HELPERS/PDR_MSG/QMI_HELPERS/TQFTPSERV all =y;
  extend EXTRA_FIRMWARE with mba.mbn, modem_nm.mbn, board-2.bin,
  firmware-5.bin, wlanmdsp.mbn.

Builds clean; boot.img produced. Whole QRTR/modem/wlan config chain verified =y.
Adds drivers/soc/qcom/qcom_rmtfs_serv.c publishing QMI service 14 (rmtfs)
so the SDM845 modem remoteproc no longer self-fatals after ~40s for lack of
EFS storage. Read-only RAM-shadow backend over the modemst1/modemst2/fsc/fsg
partitions; RW_IOVEC payload moves through the qcom,rmtfs-mem region. Enables
CONFIG_QCOM_RMTFS_SERV=y in the vamos config fragment.

Do-not-push local branch work.
Two on-device fixes to patch 0015:

1. qmi_send_response() was passed sizeof(decoded struct) as the encode
   buffer length; the encoded TLVs (3-byte header per element) are larger,
   so encoding failed with -ETOOSMALL (seen as 'failed to send response:
   -525' on the OPEN response, whose 14 encoded bytes exceeded the 12-byte
   struct). All five responses now use explicit encoded *_MAX_LEN macros;
   the handle's recv buffer is a generous 4096 covering RW_IOVEC.

2. The modem's first OPEN races UFS probe, and answering with a QMI
   failure makes the modem fatal instantly. OPEN now polls for the backing
   partition (250ms, up to 60s) before answering; blocking is safe on the
   QMI handle's own ordered workqueue with the modem as sole client.
Round-3 on-device fixes to patch 0015:

1. fsc on the comma four is ~1.5GiB (GPT ground truth), tripping the 16MiB
   sanity refusal and instantly fataling the modem. Now clamp instead of
   refuse: shadow only the first 16MiB (RMTFS_SHADOW_CAP); reads beyond the
   shadow return zeros and writes beyond it are accepted and dropped, each
   logged once per caller. The rmtfs protocol never reports a size to the
   modem, so the cap is invisible on the wire.

2. caller_id collision (fs1 and fsg both got id 0): the del_client QMI op
   freed ALL of a node's callers whenever any client socket on that node
   closed — and the modem opens each caller from its own transient socket
   (0:8, 0:12, ...) while its other QMI clients disconnect constantly.
   Userspace rmtfs treats DEL_CLIENT and BYE as no-ops; callers are keyed
   on (node, caller_id) and survive socket closes. del_client is now a
   no-op; per-node release happens only on BYE (node truly gone) and
   net_reset. RW_IOVEC dispatch keys on caller_id with a node check,
   matching userspace storage_get().
…257)

The modem's first RW_IOVEC carries 257 iovec entries with a 16-bit wire
length prefix; our qmi_elem_info capped the array at 255 (copied from the
userspace linux-msm/rmtfs IDL, which specifies 255 with a u8 prefix — the
modem firmware's own IDL clearly allows more, since a u8 prefix cannot even
represent 257). Decode failed with 'Data len 257 > max spec 255', the modem
got no response and fataled at +40s.

Raise RMTFS_MAX_IOVEC to 512 (struct, elem_info max, and bounds check all
derive from it), make the u16 length prefix explicit via the DATA_LEN
element's elem_size (the kernel codec selects the prefix width from it;
our previous sizeof(u32) already decoded u16, which is how 257 was read
correctly), and grow the QMI receive buffer to 8192 for the ~6.2KB encoded
request. The decoded request is heap-allocated by qmi_interface from
decoded_size; the handler's per-sector stack buffer is unaffected. The
RW_IOVEC response remains a single result TLV.
After the smp2p handover the modem sends TFTP WRQ to its /readwrite/ area and
retries in a tight loop (~16-32ms, incrementing source ports) until the write
succeeds; it never proceeds to the wlanmdsp.mbn RRQ while rejected. Extend
patch 0014:

- Implement WRQ per RFC 1350/2347/7440 mirroring andersson/tqftpserv
  handle_wrq/handle_writer: OACK (or ACK 0 without options), strict
  in-sequence DATA, ACK at each window boundary (block % wsize == 0) or on
  the final short block; transfer completes on payload < blksize.
- Backing store is RAM only (no rootfs writes): path-keyed list, 1 MiB per
  file, 8 MiB total, oldest evicted under pressure (logged). Any path
  accepted after ".." sanitization, stored under its virtual path.
- RRQs consult the RAM store before the request_firmware() candidates, so
  the modem can read back what it wrote (served from an owned copy so
  eviction cannot race an in-flight transfer).
- pr_info WRQ path+size on start/completion; repeat/reject logs are
  rate-limited. Remaining rejects are only genuinely malformed requests
  (bad mode, non-NUL-terminated fields, "..", or > 1 MiB writes).
- Fix a latent bug: EAGAIN from a drained socket no longer tears down an
  idle client, and each client socket is drained until EAGAIN per wakeup
  (windowed transfers queue several packets between wakeups).
The generic linux-firmware wlanmdsp.mbn (WLAN.HL.2.0-01387-QCAHLSWMTPLZ-1,
2018-07-19) crashes the WLAN Q6/DSP on 5 GHz DFS channels with
'halphy_caldb.c:3021: Assertion 0 failed', producing the ath10k
'failed to synchronize setup for vdev 0 restart 0: -110' / vdev-start
failure loop. 2.4 GHz is unaffected.

wlanmdsp.mbn is the vendor-signed WLAN firmware image sideloaded to the
modem DSP via tqftpserv; per the ath10k list it is signed for a specific
SoC/device and the generic linux-firmware copy is not calibration-matched
to this board's RF, so its 5 GHz PHY cal DB asserts. Board data is not the
cause: the board-2.bin entry ath10k selects
(bus=snoc,qmi-board-id=ff,qmi-chip-id=30214) is already byte-identical to
the device's /firmware/image/bdwlan.bin.

Replace the embedded wlanmdsp.mbn with the device's own downstream image
(WLAN.HL.2.0.c10-00459-QCAHLSWMTPLZ-1, from /firmware/image/wlanmdsp.mbn).
firmware-5.bin and board-2.bin are unchanged (same WLAN.HL.2.0 interface
descriptor). No config change: the EXTRA_FIRMWARE path is identical, only
the blob contents differ. This blob also feeds the rootfs /lib/firmware
copy via the Dockerfile, so both the built-in and userspace copies match.
5 GHz DFS vdev start still crashed the WLAN DSP (halphy_caldb.c:3026
assert) after the wlanmdsp swap. Root cause: wrong board/cal data.
Mainline was serving the generic default board-2.bin entry
(bus=snoc,qmi-board-id=ff,qmi-chip-id=30214 == generic bdwlan.bin),
but AGNOS does NOT use bdwlan.bin: comma ships RF-tuned board files
in /usr/comma/wlan/ (bdwlan.b00, bdwlan.b01, agnos-builder PRs #199
'Custom bdwlan per SOM type' and #377 'B01 wlan config') and patches
cnss_daemon to select /usr/comma/wlan/bdwlan.b%02x by the SOM id read
from /sys/.../vendor:gpio-som-id/som_id (4 GPIO straps, tlmm 105-108,
bit0 first; driver: agnos-kernel-sdm845 gpio_som_id.c). The legacy
mici DT has no wlan board-id; QMI board_id is 0xff on both stacks.

mici SOM id determined as 1 -> bdwlan.b01: tlmm gpio105 (bit0) reads 1
against a pull-down (hard-strapped high) while gpio106-108 read 0;
only b00/b01 exist, so id=1 is the only consistent hit. bdwlan.b01
differs from generic bdwlan.bin in 298 bytes (RF/cal tuning).

Implementation:
- append two variant-keyed entries to board-2.bin (upstream entries
  untouched):
    bus=snoc,...,qmi-chip-id=30214,variant=comma-mici -> bdwlan.b01
    bus=snoc,...,qmi-chip-id=30214,variant=comma-tizi -> bdwlan.b00
- set qcom,calibration-variant in each board's &wifi DT node; 6.18
  ath10k_core_check_dt picks it up and tries the variant name first.

tizi->b00 mapping is inferred from PR ordering (b00 predates b01);
tizi was unreachable to verify its SOM id. mici is verified as above.

Watch on flash-test dmesg: 'board_file api 2' with non-zero crc32,
no halphy_caldb assert, vdev 0 starts on freq 5500.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant