watchthelight labs

hwtrace

Host-side proof tool for changes that must not alter what the WTLWireless core does to the hardware, such as moving chip constants into the table in MtkChip.cpp. It compiles the driver's real sources on the Mac (plain user space, clang, UBSan), runs the bring-up against a fake MT7925 and records every register access, MCU message, firmware chunk, DMA ring setup and TX frame as one text line. Two versions of the driver source give the same trace if and only if they do the same thing to the hardware (as far as the fake device can tell).

Run

tests/hwtrace/run.sh            # build, run twice, fail if the runs differ; trace in out/tests/hwtrace/trace.txt
tests/hwtrace/compare.sh [ref]  # driver sources at ref (default HEAD, temporary git worktree) vs the working tree
tests/hwtrace/selftest.sh       # mutates a scratch copy and checks each change shows up in the trace

All paths are relative to the driver folder (the one with the Makefile). Output goes to out/tests/hwtrace/. No sudo, no kernel, no network. compare.sh prints hwtrace: identical (N events) or a unified diff and exits 1. The harness always comes from the working tree; only the driver sources differ. The driver source list is read from the Makefile SRCS, so a new driver file is picked up without touching the harness (WTLWirelessEth.cpp and MtkUserClient.cpp are left out).

Trace format

W 0x0d4208 0x00000001, R 0x0e0010 -> 0x00000004 (BAR0 offsets, after the driver's own remapping), MCU legacy|uni cid .. seq .. len .. hdr .. fnv .. (payload hashed), FWDL off .. len .. fnv .. (firmware chunk), RING tx 15 base 0x.. cnt 32, SLEEP 1ms, DELAY 100us, PCI .., STEP <driver step text>, PHASE .., TXDATA (TX descriptor and frame hashes), RXMCU / RXDATA (what the model hands back). Device addresses come from a fixed bump allocator. No host pointers, no timestamps. hwtrace <file> --props adds PROP lines (the registry properties the driver publishes) for debugging. --pcidev 7927 makes the fake card report another PCI device ID; run.sh uses it to check that a card without a chip table row gets no register access at all. --oldset pretends the class of the older kext set (MtkWlan) is in the kernel too: the core must then publish MtkConflict on the PCI device and touch nothing. compare.sh works across the rename from MtkWlan to WTLWireless (the harness takes whichever class the tree under test has); against 0.13.0 the only changed line is the status text that names the native front end.

What it covers

Start (PCI command bits, BAR0, driver/firmware ownership, CHIPID gate), hwStart (WFSYS reset, DMA disable, five rings, prefetch table, GLO_CFG, IRQ masks), patch and RAM download with the real firmware, hwInit (NIC capability, EFUSE mode, MDP, per-band MAC init, DEV_INFO, domain, CLC, per-rate power, RTS, RX filter), one firmware scan, a join of an open 5 GHz HT+VHT AP through the connection manager (BSS_INFO, STA_REC, EDCA, ROC, auth, assoc, link up), the disconnect, a country change (non-FCC domain, CLC, SKU), the fixed register windows and the L1/L2 remap paths through rr/wr, the supervisor reload (second hwStart over existing rings), quiesce and stop.

What it does not cover

WPA2 (key code, 4-way handshake, staKey), data TX/RX and block ack (needs mbufs and a front end), the Ethernet and native front ends and the user client (IOKit networking, not compiled), interrupt delivery, FLR, TX-free tokens, timing and the real tick rate. Registry properties are not part of the default trace.

Where the model guesses

Listed as GUESS in model.cpp: BAR0 size (2 MB), chip id and revision values, handshake latency (OWN_SYNC, WFSYS reset done and N9 ready each flip on the second read), DMA engines never busy, plain registers read back what was written, every UNI command accepted with a bare result event, EFUSE all zero (HW_TYPE encap 0), the AP always accepts (AID 1), event layouts for NIC capability, ROC grant and scan done, three made-up networks. The engine only fetches TX descriptors while TX_DMA_EN is set and only fills RX buffers while RX_DMA_EN is set.

Firmware

The real firmware: out/gen/MtkFirmware.c (generated by the kext build, git-ignored). If it is missing, build.sh generates it into out/tests/hwtrace/fw/. Firmware bytes appear only as hashes.

Layout

shim/ kernel and IOKit stand-ins (first on the include path, so the SDK kernel headers are never used; prelude.h opens private so the harness can call driver internals), model.* the fake chip, host_shim.cpp trace sink, DMA space and register routing, trace_main.cpp the scenario and function coverage (functions-run.txt, functions-not-run.txt), regmap_probe.inc the register window probes.