diff --git a/src/IRadio.h b/src/IRadio.h index acb9818f..9f8de0bf 100644 --- a/src/IRadio.h +++ b/src/IRadio.h @@ -90,7 +90,10 @@ class IRadio { virtual void StartRxLoop(Action_ParsedRadioPacket packetProcessor) = 0; /* Ask a running StartRxLoop to exit (sets a flag; the caller then joins - * whatever thread runs StartRxLoop). Default no-op. */ + * whatever thread runs StartRxLoop). Default no-op. + * Note: Covers the Init + StartRxLoop race window; the InitWrite + + * StartRxLoop-on-a-thread shape (DEVOURER_TX_WITH_RX=thread) operates outside + * this window and is out of scope. */ virtual void StopRxLoop() {} virtual void SetMonitorChannel(SelectedChannel channel) = 0; diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 51c0c4dd..f31107db 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -1667,6 +1667,8 @@ size_t RtlJaguarDevice::build_tx_block(const uint8_t *packet, size_t length, void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + should_stop = false; + /* A window armed before a (re-)bring-up describes a chip state that no * longer exists; leaving it armed would make the next unrelated * GetChannelBusy() take the armed branch and report a stale period. */ @@ -1798,14 +1800,26 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, std::lock_guard lock(_port0_mu); _station_ready = true; /* every port-0 write of this bring-up is done */ } - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); rollback.release(); } void RtlJaguarDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void RtlJaguarDevice::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + _packetProcessor = std::move(packetProcessor); - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - should_stop = false; + + if (clear_stale) { + should_stop = false; + } else if (should_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } /* DEVOURER_RX_PATHS=0xNN restricts which RX chains the chip enables/combines, * by masking the RX-path-enable register (0x808 byte 0: bits 0/4 = path A diff --git a/src/jaguar1/RtlJaguarDevice.h b/src/jaguar1/RtlJaguarDevice.h index d650346b..3ea05613 100644 --- a/src/jaguar1/RtlJaguarDevice.h +++ b/src/jaguar1/RtlJaguarDevice.h @@ -477,6 +477,8 @@ class RtlJaguarDevice : public IRtlRadio { return true; } + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); + /* Programs 0x520[14]/[15] and, for the EDCCA gate only, the BB thresholds * at 0x8a4. SetCcaMode is apply_cca(d, d) and writes exactly what it * wrote before the split existed. */ diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 1cb08fd4..99222cf7 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -510,6 +510,8 @@ void RtlJaguar2Device::ClearAmpduMode() { SetAmpduMode(devourer::AmpduMode{}); } void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + _rx_stop = false; + /* A window armed before a (re-)bring-up describes a chip state that no * longer exists; leaving it armed would make the next unrelated * GetChannelBusy() take the armed branch and report a stale period. */ @@ -590,13 +592,26 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, std::lock_guard lk(_reg_mu); _station_ready = true; /* every port-0 write of this bring-up is done */ } - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); } void RtlJaguar2Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void RtlJaguar2Device::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + _packetProcessor = std::move(packetProcessor); - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - _rx_stop = false; + + if (clear_stale) { + _rx_stop = false; + } else if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + /* Start the DIG thread: track IGI to the false-alarm rate so weak signals are * caught without an FA storm (a fixed IGI can't span the range). */ _dig_stop = false; diff --git a/src/jaguar2/RtlJaguar2Device.h b/src/jaguar2/RtlJaguar2Device.h index c61273d2..358d3a00 100644 --- a/src/jaguar2/RtlJaguar2Device.h +++ b/src/jaguar2/RtlJaguar2Device.h @@ -418,6 +418,8 @@ class RtlJaguar2Device : public IRtlRadio { * signal-flag pattern used across the library (g_devourer_should_stop). */ volatile bool _rx_stop = false; + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); + /* Shared cold bring-up (power-on -> DLFW -> MAC/BB/RF -> channel -> LCK -> * IQK -> coex -> enable RX/TX engine). Used by both Init (RX) and InitWrite * (TX). Leaves the chip fully calibrated on `channel`. */ diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 6fdef9ee..56995c86 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -83,6 +83,8 @@ struct WriteBatchScope { void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + _rx_stop = false; + /* A new session has sent no wide frame: a re-bring-up must not inherit the * previous session's wide-TX IGI floor (_wide_tx_seen). */ _wide_tx_seen.store(false, std::memory_order_relaxed); @@ -205,13 +207,26 @@ void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, std::lock_guard lk(_reg_mu); _station_ready = true; /* every port-0 write of this bring-up is done */ } - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); } void RtlJaguar3Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void RtlJaguar3Device::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + _packetProcessor = std::move(packetProcessor); - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - _rx_stop = false; + + if (clear_stale) { + _rx_stop = false; + } else if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + /* Take over bulk-IN from the coex thread's C2H drain up front — also during * the register restore below, so its 200 ms bulk reads don't interleave with * the RX-path enable sequence. */ diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index 5d77c6a5..ed5f1560 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -533,6 +533,9 @@ class RtlJaguar3Device : public IRtlRadio { int64_t _tbtt_off_us = 0; /* StartRxLoop stop request (StopRxLoop). */ volatile bool _rx_stop = false; + + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); + /* True while StartRxLoop owns bulk-IN (gates the coex thread's drain). */ std::atomic _rx_loop_active{false}; /* Set when InitWrite zeroes the RX filters (0x6A0-0x6A4) for TX-only diff --git a/src/kestrel/RtlKestrelDevice.cpp b/src/kestrel/RtlKestrelDevice.cpp index 02365437..226a75dd 100644 --- a/src/kestrel/RtlKestrelDevice.cpp +++ b/src/kestrel/RtlKestrelDevice.cpp @@ -295,6 +295,7 @@ void RtlKestrelDevice::EnableTxScheduler() { void RtlKestrelDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + _rx_stop = false; if (!BringUpMonitor(channel)) { _logger->error("Kestrel: monitor bring-up failed"); return; @@ -318,7 +319,7 @@ void RtlKestrelDevice::Init(Action_ParsedRadioPacket packetProcessor, ClearAckResponder(); }); } - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); } void RtlKestrelDevice::InitWrite(SelectedChannel channel) { @@ -437,10 +438,24 @@ void RtlKestrelDevice::InitWriteTail(SelectedChannel channel) { } void RtlKestrelDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void RtlKestrelDevice::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + + if (clear_stale) { + _rx_stop = false; + } else if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + /* Take the bulk-IN over from the TX WP-release drain (this loop also consumes * rpkt_type=7 releases) so the two never read the endpoint concurrently. */ stop_wp_drain(); - _rx_stop = false; + _logger->info("Kestrel: starting RX loop on ch{}", _channel.Channel); /* Async bulk-IN ring; walk each aggregate with the 11ax rxd parser. The * buffer must hold a full RXAGG aggregate (the 8852C LEN_TH is ~20 KB), or diff --git a/src/kestrel/RtlKestrelDevice.h b/src/kestrel/RtlKestrelDevice.h index 5db790b7..26c5200a 100644 --- a/src/kestrel/RtlKestrelDevice.h +++ b/src/kestrel/RtlKestrelDevice.h @@ -301,6 +301,9 @@ class RtlKestrelDevice : public IRtlRadio { SelectedChannel _channel{}; kestrel::EfuseInfo _efuse{}; volatile bool _rx_stop = false; + + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); + /* WP-release drain (Kestrel STF USB): the fw returns each transmitted frame's * WD/PLE pages as an RX packet (rpkt_type=7, TX_PD_RELEASE_HOST) on the bulk- * IN. A TX-only path must still drain the bulk-IN or the pages never recycle diff --git a/src/mt7612u/Mt7612uRadio.cpp b/src/mt7612u/Mt7612uRadio.cpp index 1067f5b8..59597ea4 100644 --- a/src/mt7612u/Mt7612uRadio.cpp +++ b/src/mt7612u/Mt7612uRadio.cpp @@ -324,6 +324,7 @@ void Mt7612uRadio::tick_loop() { void Mt7612uRadio::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + _rx_stop = false; /* Stop() on the way out, as the sibling backends do: a throw from the channel * set or the config would otherwise leave the device open and the tick thread * running until the destructor happens to run, and a caller that catches and @@ -351,7 +352,7 @@ void Mt7612uRadio::Init(Action_ParsedRadioPacket packetProcessor, * failed to start" left the device open and the tick running, which is the * half-open object this whole guard exists to prevent. */ try { - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); } catch (...) { Stop(); throw; @@ -392,6 +393,13 @@ void Mt7612uRadio::InitWrite(SelectedChannel channel) { } void Mt7612uRadio::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void Mt7612uRadio::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + struct mt7612u_dev *mac_failed = nullptr; /* An armed station keeps its managed filter: the monitor request below is * recorded, not installed (mt7612u_set_monitor_rx). The log says which @@ -416,7 +424,12 @@ void Mt7612uRadio::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { if (_rx_active.load()) throw std::runtime_error("MT7612U RX loop is already active"); _rx_processor = std::move(packetProcessor); - _rx_stop = false; + if (clear_stale) { + _rx_stop = false; + } else if (_rx_stop.load()) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } /* 64 slots is about 45 ms of headroom at the measured 1400 fps - enough to * ride out a slow processor without letting the producer block. The ring * itself is allocated here; each slot's payload buffer then grows to the diff --git a/src/mt7612u/Mt7612uRadio.h b/src/mt7612u/Mt7612uRadio.h index d47aa851..35b40291 100644 --- a/src/mt7612u/Mt7612uRadio.h +++ b/src/mt7612u/Mt7612uRadio.h @@ -127,6 +127,7 @@ class Mt7612uRadio : public IRadio { void bring_up(SelectedChannel channel); /* _mu held */ void apply_config(); /* _mu held */ int txpower_target_dbm() const; /* _mu held */ + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); void start_tick(); /* _mu held */ void stop_tick(); /* _mu NOT held */ void tick_loop(); diff --git a/src/rtl8733b/Rtl8733bDevice.cpp b/src/rtl8733b/Rtl8733bDevice.cpp index fec024c4..44ea3110 100644 --- a/src/rtl8733b/Rtl8733bDevice.cpp +++ b/src/rtl8733b/Rtl8733bDevice.cpp @@ -171,6 +171,7 @@ void Rtl8733bDevice::bring_up_to_phy() { void Rtl8733bDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + _rx_stop = false; try { { std::lock_guard lock(_reg_mu); @@ -225,7 +226,7 @@ void Rtl8733bDevice::Init(Action_ParsedRadioPacket packetProcessor, ClearAckResponder(); }); } - StartRxLoop(std::move(packetProcessor)); + start_rx_loop(std::move(packetProcessor), false); } catch (...) { Stop(); throw; @@ -355,6 +356,14 @@ bool Rtl8733bDevice::configure_tx_power(SelectedChannel channel) { void Rtl8733bDevice::StartRxLoop( Action_ParsedRadioPacket packetProcessor) { + start_rx_loop(std::move(packetProcessor), true); +} + +void Rtl8733bDevice::start_rx_loop( + Action_ParsedRadioPacket packetProcessor, bool clear_stale) { + if (g_devourer_should_stop) + return; + Action_ParsedRadioPacket rx_packet_processor = std::move(packetProcessor); uint8_t rx_channel = 0; { @@ -363,7 +372,12 @@ void Rtl8733bDevice::StartRxLoop( throw std::runtime_error("RTL8733B RX loop requires initialized hardware"); if (_rx_active) throw std::runtime_error("RTL8733B RX loop is already active"); - _rx_stop = false; + if (clear_stale) { + _rx_stop = false; + } else if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } _rx_active = true; rx_channel = _channel.Channel; } diff --git a/src/rtl8733b/Rtl8733bDevice.h b/src/rtl8733b/Rtl8733bDevice.h index 84a3d8a5..456a26b8 100644 --- a/src/rtl8733b/Rtl8733bDevice.h +++ b/src/rtl8733b/Rtl8733bDevice.h @@ -146,6 +146,7 @@ class Rtl8733bDevice : public IRtlRadio { return true; } + void start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale); void bring_up_to_phy(); bool configure_tx_power(SelectedChannel channel); /* Fill one [txdesc][frame] block at `out`. `agg_num` is the USB TX