@@ -598,46 +598,54 @@ Stated because the numbers above are uniformly favourable.
598598 metadata and every consumer that trims four bytes are in place - but the
599599 adapter still has to USE it: see Open.
600600
601+ ## Retune while the ring is up
602+
603+ A channel change from the caller's thread while the libusb event thread is
604+ delivering frames is the one concurrent operation this backend has, and it is
605+ measured race-free: ` rxdemo ` under ThreadSanitizer (` DEVOURER_SANITIZE=thread ` )
606+ with ` DEVOURER_RX_SWEEP=1,6,11,36,40,44,48 ` at 150 ms dwells — a full,
607+ cross-band tune every dwell — ran 150 s on a Comfast CF-922AC (SuperSpeed,
608+ ` 40:a5:ef:5f:65:51 ` ), about a thousand retunes and 7221 frames, with ** zero
609+ reports inside the library or libusb** . The two reports that remain are in
610+ ` rxdemo ` itself (its frame counter and the demos' signal-handler stop flag)
611+ and are shared with every generation.
612+
613+ Two facts about the design hold that result up:
614+
615+ - ** The per-channel RSSI correction is one word** , published once per tune
616+ and loaded once per frame — the layout and why are on the declaration
617+ (` mt7612u_cal::rx_corr ` , ` Mt7612uRxCorr.h ` ). What that bought: three
618+ separate bytes gave a frame parsed mid-retune a wrong RSSI, which is the
619+ class of bug this port cares most about.
620+ - ** Synchronous transfers do not use libusb's synchronous API.** Every
621+ register access and MCU exchange is an async submission waited on the
622+ library's own acquire/release flag; the hazard in libusb's own sync layer
623+ and the lifetime rules are documented once, at the helpers in ` usb.cpp ` . The
624+ replacement is A/B-neutral where it could cost: retune latency on the
625+ 7-bin sweep above read p50 791 ms / mean 756 ms on both the sync-API build
626+ and this one (65 dwells each), and a 2000-frame injection witnessed by an
627+ RTL8822BU delivered the same count from both builds at two power back-offs
628+ (1000 at −20 dB, 900 at −10 dB; the ground station sits 20 cm away in
629+ saturation, so those are its ceiling, not the transmitter's).
630+
631+ For scale: the same stress on the shipping RTL8812AU path produces ** 8** TSan
632+ reports, all in devourer's own Jaguar1 state (` RtlJaguarDevice.cpp ` ,
633+ ` RtlAdapter.h ` ). Retune-during-RX is race-free here and not there.
634+
601635## Open list
602636
603637Ordered, and honest about which are unknowns rather than typing:
604638
605- 1 . ** Two data races the library has when a channel change runs while the RX
606- ring is up.** Found with ThreadSanitizer against real hardware, driving
607- ` rxdemo ` with ` DEVOURER_RX_SWEEP ` so the main thread retunes while the
608- libusb event thread delivers frames. Neither is in ` Mt7612uRadio ` — that
609- class's ` _mu ` discipline held, and TSan reported nothing inside it — and
610- neither is reachable from the bring-up harness, which sets the channel
611- before starting a ring and measured 0 warnings over 10667 frames.
612-
613- - ` mt_read_rx_gain() ` (` eeprom.cpp ` ) rewrites the per-channel ` lna_gain ` and
614- ` rssi_offset[] ` from the EEPROM on every tune, while ` mt_rx_parse() `
615- (` rx.cpp ` ) reads them on the event thread to correct each frame's RSSI. A
616- frame parsed mid-retune therefore gets a mixed correction, i.e. a wrong
617- RSSI for that frame. Wrong number, not a crash — which is the class this
618- port cares most about.
619- - A synchronous control transfer issued during the tune (` mt_rr_chk ` ->
620- ` libusb_control_transfer ` ) reaches ` libusb_free_transfer() ` , destroying a
621- transfer's mutex, while the async ring's event thread locks it. That is
622- inside libusb, and it is the documented hazard of mixing the synchronous
623- API with a dedicated event thread on one context.
624-
625- For scale: the same stress on the shipping RTL8812AU path produces ** 8**
626- TSan reports, all in devourer's own Jaguar1 state (` RtlJaguarDevice.cpp ` ,
627- ` RtlAdapter.h ` ). Retune-during-RX is not a race-free operation anywhere in
628- this project today, so this is a shared gap rather than a MediaTek
629- regression — but the two above are specific and fixable, and the second one
630- is a use-after-destroy rather than a torn read.
631- 2 . ` mt76x2_phy_tssi_compensate() ` — periodic temperature correction. Without
639+ 1 . ` mt76x2_phy_tssi_compensate() ` — periodic temperature correction. Without
632640 it output power drifts with die temperature.
633- 3 . Cold-boot verification on a host with switchable USB power.
634- 4 . A witness on different silicon. The regression matrix added a second witness
641+ 2 . Cold-boot verification on a host with switchable USB power.
642+ 3 . A witness on different silicon. The regression matrix added a second witness
635643 * implementation* (` tcpdump ` over ` mt76x2u ` ), and a second * unit* of the part
636644 — but not a second board revision, and not a decoder outside the MediaTek
637645 family.
638- 5 . 80 MHz; VHT and NSS=2 on air.
639- 6 . Retune tuning — batch registers via ` CMD_RANDOM_WRITE ` , drop the inter-command
646+ 4 . 80 MHz; VHT and NSS=2 on air.
647+ 5 . Retune tuning — batch registers via ` CMD_RANDOM_WRITE ` , drop the inter-command
640648 sleep, skip the ` RXDCOC ` on a fast path. Worth doing only if 10–20 ms is
641649 useful to someone.
642- 7 . Whether the single MCS6 frame in the rate-LUT control arm (1 of 84) is a
650+ 6 . Whether the single MCS6 frame in the rate-LUT control arm (1 of 84) is a
643651 witness decode artefact or a real fallback. Unexplained.
0 commit comments