From 68226026abd0bdb061cdcbcbaa5e0f011905186b Mon Sep 17 00:00:00 2001 From: floppyhammer Date: Wed, 28 Oct 2026 09:05:41 +0800 Subject: [PATCH 1/3] fix(devourer): prevent deadlock when StopRxLoop is called during Init Fix a deadlock where calling StopRxLoop() during device initialization (inside Init()) was ignored and overwritten. Root cause: StartRxLoop() unconditionally cleared the stop flag (should_stop / _rx_stop) upon entry. If StopRxLoop() was called while Init() was executing (e.g. during IQK calibration or channel setup), StartRxLoop() wiped out the pending stop request and launched bulk_read_async_loop indefinitely, deadlocking the caller waiting on the worker thread. Changes: - Clear stop flag at entry of Init() to mark the start of initialization. - In StartRxLoop(), check whether stop was requested (should_stop / _rx_stop) before resetting the flag. Abort StartRxLoop() immediately if true. --- src/jaguar1/RtlJaguarDevice.cpp | 3 +++ src/jaguar2/RtlJaguar2Device.cpp | 3 +++ src/jaguar3/RtlJaguar3Device.cpp | 3 +++ src/kestrel/RtlKestrelDevice.cpp | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 51c0c4dd..949b6d83 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -1696,6 +1696,7 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, _logger->error("Jaguar1: post-arm Init rollback was not fully verified; " "see the register-specific error above"); }); + should_stop = false; StartWithMonitorMode(channel); SetMonitorChannel(channel); @@ -1804,6 +1805,8 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguarDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); + if (should_stop || g_devourer_should_stop) + return; /* Restartable: clear any stop request left by a prior StopRxLoop(). */ should_stop = false; diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 1cb08fd4..00ac8965 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -525,6 +525,7 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, _station_ready = false; _station.retire(_device, _logger, "Jaguar2"); } + _rx_stop = false; _channel = channel; bring_up(channel); _rx_bw_code.store(channel_width_to_bw_code(channel.ChannelWidth), @@ -595,6 +596,8 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguar2Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); + if (_rx_stop || g_devourer_should_stop) + return; /* Restartable: clear any stop request left by a prior StopRxLoop(). */ _rx_stop = false; /* Start the DIG thread: track IGI to the false-alarm rate so weak signals are diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 6fdef9ee..923b57ba 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -101,6 +101,7 @@ void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, _station_ready = false; _station.retire(_device, _logger, "Jaguar3"); } + _rx_stop = false; _channel = channel; _rx_bw_code.store(channel_width_to_bw_code(channel.ChannelWidth), std::memory_order_relaxed); @@ -210,6 +211,8 @@ void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguar3Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); + if (_rx_stop || g_devourer_should_stop) + return; /* Restartable: clear any stop request left by a prior StopRxLoop(). */ _rx_stop = false; /* Take over bulk-IN from the coex thread's C2H drain up front — also during diff --git a/src/kestrel/RtlKestrelDevice.cpp b/src/kestrel/RtlKestrelDevice.cpp index 02365437..649229c2 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; @@ -440,6 +441,8 @@ void RtlKestrelDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { /* 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(); + if (_rx_stop || g_devourer_should_stop) + return; _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 From 5a9b33e5996a85623cd95913410f19945e710152 Mon Sep 17 00:00:00 2001 From: floppyhammer Date: Wed, 28 Oct 2026 11:53:02 +0800 Subject: [PATCH 2/3] fix(devourer): differentiate init-time stop requests from stale flags --- src/jaguar1/RtlJaguarDevice.cpp | 31 +++++++++++++++++++--- src/jaguar1/RtlJaguarDevice.h | 3 +++ src/jaguar2/RtlJaguar2Device.cpp | 36 +++++++++++++++++++++++--- src/jaguar2/RtlJaguar2Device.h | 3 +++ src/jaguar3/RtlJaguar3Device.cpp | 44 +++++++++++++++++++++++++++----- src/jaguar3/RtlJaguar3Device.h | 4 +++ src/kestrel/RtlKestrelDevice.cpp | 35 +++++++++++++++++++++++-- src/kestrel/RtlKestrelDevice.h | 4 +++ 8 files changed, 145 insertions(+), 15 deletions(-) diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 949b6d83..b42c782c 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -1667,6 +1667,14 @@ size_t RtlJaguarDevice::build_tx_block(const uint8_t *packet, size_t length, void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + /* Mark that device initialization is currently in progress. */ + _init_in_progress.store(true, std::memory_order_release); + + /* Ensure initialization progress flag is cleared upon exit or rollback. */ + JaguarScopeExit init_guard([this] { + _init_in_progress.store(false, std::memory_order_release); + }); + /* 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. */ @@ -1805,10 +1813,27 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguarDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); - if (should_stop || g_devourer_should_stop) + + if (g_devourer_should_stop) + return; + + /* + * Differentiate between an initialization-time stop request and a stale stop + * request left by a completed receive loop: + * - If initialization is currently in progress and a stop was requested, abort + * StartRxLoop immediately. + * - If this is a standalone restart after initialization has completed, clear + * the stale stop request. + */ + if (_init_in_progress.load(std::memory_order_acquire)) { + if (should_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ return; - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - should_stop = false; + } + } else { + /* Clear a completed loop's stale stop request for a standalone restart */ + should_stop = false; + } /* 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..5abe9bd9 100644 --- a/src/jaguar1/RtlJaguarDevice.h +++ b/src/jaguar1/RtlJaguarDevice.h @@ -477,6 +477,9 @@ class RtlJaguarDevice : public IRtlRadio { return true; } + /* Track whether device initialization (Init()) is currently in progress */ + std::atomic _init_in_progress{false}; + /* 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 00ac8965..3a007358 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -510,6 +510,19 @@ void RtlJaguar2Device::ClearAmpduMode() { SetAmpduMode(devourer::AmpduMode{}); } void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + /* Mark that device initialization is currently in progress. */ + _init_in_progress.store(true, std::memory_order_release); + + /* Ensure initialization progress flag is cleared upon exit or rollback. + * (Equivalent to JaguarScopeExit without needing a class definition) */ + auto init_guard_closer = [&]() { + _init_in_progress.store(false, std::memory_order_release); + }; + struct ScopeGuard { + std::function fn; + ~ScopeGuard() { fn(); } + } init_guard{init_guard_closer}; + /* 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. */ @@ -596,10 +609,27 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguar2Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); - if (_rx_stop || g_devourer_should_stop) + if (g_devourer_should_stop) return; - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - _rx_stop = false; + + /* + * Differentiate between an initialization-time stop request and a stale stop + * request left by a completed receive loop: + * - If initialization is currently in progress and a stop was requested, abort + * StartRxLoop immediately. + * - If this is a standalone restart after initialization has completed, clear + * the stale stop request. + */ + if (_init_in_progress.load(std::memory_order_acquire)) { + if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + } else { + /* Clear a completed loop's stale stop request for a standalone restart */ + _rx_stop = false; + } + /* 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..ae7d0133 100644 --- a/src/jaguar2/RtlJaguar2Device.h +++ b/src/jaguar2/RtlJaguar2Device.h @@ -418,6 +418,9 @@ class RtlJaguar2Device : public IRtlRadio { * signal-flag pattern used across the library (g_devourer_should_stop). */ volatile bool _rx_stop = false; + // Track whether device initialization (Init()) is currently in progress + std::atomic _init_in_progress{false}; + /* 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 923b57ba..6b0fe021 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -83,7 +83,20 @@ struct WriteBatchScope { void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { - /* A new session has sent no wide frame: a re-bring-up must not inherit the + /* Mark that device initialization is currently in progress. */ + _init_in_progress.store(true, std::memory_order_release); + + /* Ensure initialization progress flag is cleared upon exit or rollback. + * (Equivalent to JaguarScopeExit without needing a class definition) */ + auto init_guard_closer = [&]() { + _init_in_progress.store(false, std::memory_order_release); + }; + struct ScopeGuard { + std::function fn; + ~ScopeGuard() { fn(); } + } init_guard{init_guard_closer}; + + /* 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); /* A window armed before a (re-)bring-up describes a chip state that no @@ -211,13 +224,30 @@ void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, void RtlJaguar3Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { _packetProcessor = std::move(packetProcessor); - if (_rx_stop || g_devourer_should_stop) + if (g_devourer_should_stop) return; - /* Restartable: clear any stop request left by a prior StopRxLoop(). */ - _rx_stop = false; - /* 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. */ + + /* + * Differentiate between an initialization-time stop request and a stale stop + * request left by a completed receive loop: + * - If initialization is currently in progress and a stop was requested, abort + * StartRxLoop immediately. + * - If this is a standalone restart after initialization has completed, clear + * the stale stop request. + */ + if (_init_in_progress.load(std::memory_order_acquire)) { + if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + } else { + /* Clear a completed loop's stale stop request for a standalone restart */ + _rx_stop = false; + } + + /* 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. */ _rx_loop_active = true; /* An InitWrite bring-up is TX-oriented: it closed the RX filters diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index 5d77c6a5..e02648a7 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -533,6 +533,10 @@ class RtlJaguar3Device : public IRtlRadio { int64_t _tbtt_off_us = 0; /* StartRxLoop stop request (StopRxLoop). */ volatile bool _rx_stop = false; + + /* Track whether device initialization (Init()) is currently in progress */ + std::atomic _init_in_progress{false}; + /* 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 649229c2..80d8a660 100644 --- a/src/kestrel/RtlKestrelDevice.cpp +++ b/src/kestrel/RtlKestrelDevice.cpp @@ -295,6 +295,19 @@ void RtlKestrelDevice::EnableTxScheduler() { void RtlKestrelDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { + /* Mark that device initialization is currently in progress. */ + _init_in_progress.store(true, std::memory_order_release); + + /* Ensure initialization progress flag is cleared upon exit or rollback. + * (Equivalent to JaguarScopeExit without needing a class definition) */ + auto init_guard_closer = [&]() { + _init_in_progress.store(false, std::memory_order_release); + }; + struct ScopeGuard { + std::function fn; + ~ScopeGuard() { fn(); } + } init_guard{init_guard_closer}; + _rx_stop = false; if (!BringUpMonitor(channel)) { _logger->error("Kestrel: monitor bring-up failed"); @@ -441,9 +454,27 @@ void RtlKestrelDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { /* 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(); - if (_rx_stop || g_devourer_should_stop) + if (g_devourer_should_stop) return; - _rx_stop = false; + + /* + * Differentiate between an initialization-time stop request and a stale stop + * request left by a completed receive loop: + * - If initialization is currently in progress and a stop was requested, abort + * StartRxLoop immediately. + * - If this is a standalone restart after initialization has completed, clear + * the stale stop request. + */ + if (_init_in_progress.load(std::memory_order_acquire)) { + if (_rx_stop) { + /* Stop requested during Init must prevent its receive loop from starting */ + return; + } + } else { + /* Clear a completed loop's stale stop request for a standalone restart */ + _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..13250baf 100644 --- a/src/kestrel/RtlKestrelDevice.h +++ b/src/kestrel/RtlKestrelDevice.h @@ -301,6 +301,10 @@ class RtlKestrelDevice : public IRtlRadio { SelectedChannel _channel{}; kestrel::EfuseInfo _efuse{}; volatile bool _rx_stop = false; + + /* Track whether device initialization (Init()) is currently in progress */ + std::atomic _init_in_progress{false}; + /* 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 From 6e36580173ced1659540df53d66acaf66a00f968 Mon Sep 17 00:00:00 2001 From: floppyhammer Date: Thu, 29 Oct 2026 09:27:06 +0800 Subject: [PATCH 3/3] refactor(devourer): simplify rx-loop startup guard and extend to all backends - Replace _init_in_progress atomic and ScopeGuard with a clean private helper start_rx_loop(packetProcessor, clear_stale) across all 6 radio backends - Ensure the stop flag is cleared as the very first statement of Init() - Extend the init-time vs stale stop guard to Rtl8733bDevice and Mt7612uRadio - Add an early g_devourer_should_stop check at the entry of start_rx_loop - Document the InitWrite + StartRxLoop thread race window in IRadio.h --- src/IRadio.h | 5 ++- src/jaguar1/RtlJaguarDevice.cpp | 36 ++++++-------------- src/jaguar1/RtlJaguarDevice.h | 3 +- src/jaguar2/RtlJaguar2Device.cpp | 44 +++++++----------------- src/jaguar2/RtlJaguar2Device.h | 3 +- src/jaguar3/RtlJaguar3Device.cpp | 58 +++++++++++--------------------- src/jaguar3/RtlJaguar3Device.h | 3 +- src/kestrel/RtlKestrelDevice.cpp | 45 +++++++------------------ src/kestrel/RtlKestrelDevice.h | 3 +- src/mt7612u/Mt7612uRadio.cpp | 17 ++++++++-- src/mt7612u/Mt7612uRadio.h | 1 + src/rtl8733b/Rtl8733bDevice.cpp | 18 ++++++++-- src/rtl8733b/Rtl8733bDevice.h | 1 + 13 files changed, 98 insertions(+), 139 deletions(-) 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 b42c782c..f31107db 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -1667,13 +1667,7 @@ size_t RtlJaguarDevice::build_tx_block(const uint8_t *packet, size_t length, void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { - /* Mark that device initialization is currently in progress. */ - _init_in_progress.store(true, std::memory_order_release); - - /* Ensure initialization progress flag is cleared upon exit or rollback. */ - JaguarScopeExit init_guard([this] { - _init_in_progress.store(false, std::memory_order_release); - }); + 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 @@ -1704,7 +1698,6 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, _logger->error("Jaguar1: post-arm Init rollback was not fully verified; " "see the register-specific error above"); }); - should_stop = false; StartWithMonitorMode(channel); SetMonitorChannel(channel); @@ -1807,32 +1800,25 @@ 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) { - _packetProcessor = std::move(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; - /* - * Differentiate between an initialization-time stop request and a stale stop - * request left by a completed receive loop: - * - If initialization is currently in progress and a stop was requested, abort - * StartRxLoop immediately. - * - If this is a standalone restart after initialization has completed, clear - * the stale stop request. - */ - if (_init_in_progress.load(std::memory_order_acquire)) { - if (should_stop) { - /* Stop requested during Init must prevent its receive loop from starting */ - return; - } - } else { - /* Clear a completed loop's stale stop request for a standalone restart */ + _packetProcessor = std::move(packetProcessor); + + 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, diff --git a/src/jaguar1/RtlJaguarDevice.h b/src/jaguar1/RtlJaguarDevice.h index 5abe9bd9..3ea05613 100644 --- a/src/jaguar1/RtlJaguarDevice.h +++ b/src/jaguar1/RtlJaguarDevice.h @@ -477,8 +477,7 @@ class RtlJaguarDevice : public IRtlRadio { return true; } - /* Track whether device initialization (Init()) is currently in progress */ - std::atomic _init_in_progress{false}; + 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 diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 3a007358..99222cf7 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -510,18 +510,7 @@ void RtlJaguar2Device::ClearAmpduMode() { SetAmpduMode(devourer::AmpduMode{}); } void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { - /* Mark that device initialization is currently in progress. */ - _init_in_progress.store(true, std::memory_order_release); - - /* Ensure initialization progress flag is cleared upon exit or rollback. - * (Equivalent to JaguarScopeExit without needing a class definition) */ - auto init_guard_closer = [&]() { - _init_in_progress.store(false, std::memory_order_release); - }; - struct ScopeGuard { - std::function fn; - ~ScopeGuard() { fn(); } - } init_guard{init_guard_closer}; + _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 @@ -538,7 +527,6 @@ void RtlJaguar2Device::Init(Action_ParsedRadioPacket packetProcessor, _station_ready = false; _station.retire(_device, _logger, "Jaguar2"); } - _rx_stop = false; _channel = channel; bring_up(channel); _rx_bw_code.store(channel_width_to_bw_code(channel.ChannelWidth), @@ -604,30 +592,24 @@ 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) { - _packetProcessor = std::move(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; + return; - /* - * Differentiate between an initialization-time stop request and a stale stop - * request left by a completed receive loop: - * - If initialization is currently in progress and a stop was requested, abort - * StartRxLoop immediately. - * - If this is a standalone restart after initialization has completed, clear - * the stale stop request. - */ - if (_init_in_progress.load(std::memory_order_acquire)) { - if (_rx_stop) { - /* Stop requested during Init must prevent its receive loop from starting */ - return; - } - } else { - /* Clear a completed loop's stale stop request for a standalone restart */ + _packetProcessor = std::move(packetProcessor); + + 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 diff --git a/src/jaguar2/RtlJaguar2Device.h b/src/jaguar2/RtlJaguar2Device.h index ae7d0133..358d3a00 100644 --- a/src/jaguar2/RtlJaguar2Device.h +++ b/src/jaguar2/RtlJaguar2Device.h @@ -418,8 +418,7 @@ class RtlJaguar2Device : public IRtlRadio { * signal-flag pattern used across the library (g_devourer_should_stop). */ volatile bool _rx_stop = false; - // Track whether device initialization (Init()) is currently in progress - std::atomic _init_in_progress{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 diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 6b0fe021..56995c86 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -83,20 +83,9 @@ struct WriteBatchScope { void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { - /* Mark that device initialization is currently in progress. */ - _init_in_progress.store(true, std::memory_order_release); - - /* Ensure initialization progress flag is cleared upon exit or rollback. - * (Equivalent to JaguarScopeExit without needing a class definition) */ - auto init_guard_closer = [&]() { - _init_in_progress.store(false, std::memory_order_release); - }; - struct ScopeGuard { - std::function fn; - ~ScopeGuard() { fn(); } - } init_guard{init_guard_closer}; - - /* A new session has sent no wide frame: a re-bring-up must not inherit the + _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); /* A window armed before a (re-)bring-up describes a chip state that no @@ -114,7 +103,6 @@ void RtlJaguar3Device::Init(Action_ParsedRadioPacket packetProcessor, _station_ready = false; _station.retire(_device, _logger, "Jaguar3"); } - _rx_stop = false; _channel = channel; _rx_bw_code.store(channel_width_to_bw_code(channel.ChannelWidth), std::memory_order_relaxed); @@ -219,35 +207,29 @@ 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) { - _packetProcessor = std::move(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; + return; - /* - * Differentiate between an initialization-time stop request and a stale stop - * request left by a completed receive loop: - * - If initialization is currently in progress and a stop was requested, abort - * StartRxLoop immediately. - * - If this is a standalone restart after initialization has completed, clear - * the stale stop request. - */ - if (_init_in_progress.load(std::memory_order_acquire)) { - if (_rx_stop) { - /* Stop requested during Init must prevent its receive loop from starting */ - return; - } - } else { - /* Clear a completed loop's stale stop request for a standalone restart */ - _rx_stop = false; - } + _packetProcessor = std::move(packetProcessor); + + 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. */ + /* 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. */ _rx_loop_active = true; /* An InitWrite bring-up is TX-oriented: it closed the RX filters diff --git a/src/jaguar3/RtlJaguar3Device.h b/src/jaguar3/RtlJaguar3Device.h index e02648a7..ed5f1560 100644 --- a/src/jaguar3/RtlJaguar3Device.h +++ b/src/jaguar3/RtlJaguar3Device.h @@ -534,8 +534,7 @@ class RtlJaguar3Device : public IRtlRadio { /* StartRxLoop stop request (StopRxLoop). */ volatile bool _rx_stop = false; - /* Track whether device initialization (Init()) is currently in progress */ - std::atomic _init_in_progress{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}; diff --git a/src/kestrel/RtlKestrelDevice.cpp b/src/kestrel/RtlKestrelDevice.cpp index 80d8a660..226a75dd 100644 --- a/src/kestrel/RtlKestrelDevice.cpp +++ b/src/kestrel/RtlKestrelDevice.cpp @@ -295,19 +295,6 @@ void RtlKestrelDevice::EnableTxScheduler() { void RtlKestrelDevice::Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel) { - /* Mark that device initialization is currently in progress. */ - _init_in_progress.store(true, std::memory_order_release); - - /* Ensure initialization progress flag is cleared upon exit or rollback. - * (Equivalent to JaguarScopeExit without needing a class definition) */ - auto init_guard_closer = [&]() { - _init_in_progress.store(false, std::memory_order_release); - }; - struct ScopeGuard { - std::function fn; - ~ScopeGuard() { fn(); } - } init_guard{init_guard_closer}; - _rx_stop = false; if (!BringUpMonitor(channel)) { _logger->error("Kestrel: monitor bring-up failed"); @@ -332,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) { @@ -451,30 +438,24 @@ void RtlKestrelDevice::InitWriteTail(SelectedChannel channel) { } void RtlKestrelDevice::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { - /* 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(); + start_rx_loop(std::move(packetProcessor), true); +} + +void RtlKestrelDevice::start_rx_loop(Action_ParsedRadioPacket packetProcessor, bool clear_stale) { if (g_devourer_should_stop) return; - /* - * Differentiate between an initialization-time stop request and a stale stop - * request left by a completed receive loop: - * - If initialization is currently in progress and a stop was requested, abort - * StartRxLoop immediately. - * - If this is a standalone restart after initialization has completed, clear - * the stale stop request. - */ - if (_init_in_progress.load(std::memory_order_acquire)) { - if (_rx_stop) { - /* Stop requested during Init must prevent its receive loop from starting */ - return; - } - } else { - /* Clear a completed loop's stale stop request for a standalone restart */ + 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(); + _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 13250baf..26c5200a 100644 --- a/src/kestrel/RtlKestrelDevice.h +++ b/src/kestrel/RtlKestrelDevice.h @@ -302,8 +302,7 @@ class RtlKestrelDevice : public IRtlRadio { kestrel::EfuseInfo _efuse{}; volatile bool _rx_stop = false; - /* Track whether device initialization (Init()) is currently in progress */ - std::atomic _init_in_progress{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- 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