mt7612u: make a retune race-free against the live RX ring - #444
Conversation
Two data races when a channel change runs while the libusb event thread is delivering frames, both reproduced with ThreadSanitizer on a Comfast CF-922AC driving rxdemo through a cross-band DEVOURER_RX_SWEEP: - mt_read_rx_gain() rewrote the two chain RSSI offsets and the LNA gain as three separate bytes while mt_rx_parse() read them per frame on the event thread, so a frame parsed mid-retune got the new offset with the old LNA gain. They are now one relaxed atomic word (mt7612u_cal::rx_corr), stored once per tune and loaded once per frame; Mt7612uRxCorr.h carries the packing so the headless mapping selftest can pin the sign handling. - Every register access and MCU exchange went through libusb's synchronous API on a context whose events another thread was handling. libusb's sync layer reads its completion flag with no synchronization on the path where handle_events returns early for an expired timeout, so nothing ordered the event thread's last lock of the transfer mutex (io.c:1702) before the caller's libusb_free_transfer() destroyed it; x86 program order hid it. mt_vendor_req() and mt_bulk() now submit through the async API and wait on their own acquire/release flag, pumping events themselves when nobody else is. A transfer that never completes is cancelled, then leaked with a diagnostic rather than freed in flight. Measured after: 150 s, ~1000 retunes, 7221 frames, zero library or libusb reports; the remaining two are rxdemo globals shared by every generation. A/B-neutral where it could cost: retune p50 791 ms on both builds, and a witnessed 2000-frame injection delivered the same count from both at two power back-offs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR Summary by QodoMake MT7612U live-ring retunes race-free
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
Code Review by Qodo
1.
|
…pointing at a dead frame Review of the async-backed sync helpers: the give-up path returned while the transfer's callback still pointed at a stack-local completion word, and a bulk transfer at the caller's stack buffer. Everything the callback can touch now lives in one heap-owned sync_xfer; the last of the two parties to arrive frees it (an atomic exchange decides which), the caller's bulk buffer is copied both ways, and a give-up marks the device stranded so mt_close() leaks the USB handle and context instead of closing underneath a transfer libusb still owns - the ring's existing policy. Also trims the doc's two design bullets to references: the layout and the lifetime rules are documented once, on the declarations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…efore the event thread With per-call libusb_alloc_transfer() the only ordering between a transfer's mutex initialisation on the caller's thread and the event thread's first lock of it runs through the kernel's URB handoff, which ThreadSanitizer cannot see - the one report class left after the sync rework, benign and intermittent. Transfers for the sync helpers now come from a small pool allocated in mt_dev_state_init(), before any event thread exists, so thread creation orders them; an empty pool falls back to allocating, correct but noisier. Buffers stay per-call and are freed by the helper, not by libusb. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Review addressed, re-validated on the unit:
Re-run of the same 150 s cross-band sweep under TSan: 0 library/libusb reports (only the two rxdemo globals); witnessed 2000-frame injection: 800 hits on the saturated ground station, inside the earlier 800–1000 band. |
What
The two data races
docs/mt7612u.mdlisted for a channel change while the RX ring is up, reproduced under ThreadSanitizer on the first MT7612U in the lab (Comfast CF-922AC) and fixed:mt7612u_cal::rx_corr, packing inMt7612uRxCorr.h), stored once per tune and loaded once per frame. A headless round-trip joins the mapping selftest.libusb_free_transfer()destroyed it (symbolized against libusb 1.0.30 with distro debug symbols). Register and MCU transfers now submit through the async API and wait on the library's own acquire/release flag.Measured
ctest69/69.Write-up:
docs/mt7612u.md"Retune while the ring is up". The open-list item is gone.🤖 Generated with Claude Code