Skip to content

Commit 8feb34e

Browse files
RadioLib edge-triggered interrupts robustness (#9658)
* Fix potential race condition (read, ISR write, clear) in NotifiedWorkerThread * Check for missed edge-triggered interrupts race condition between startReceive and enableInterrupt * Occasionally poll to catch missed RX_DONE interrupts. (RadioLibInterface::pollMissedIrqs) * Simplify RadioLibInterface::checkRxDoneIrqFlag() --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
1 parent a5523b0 commit 8feb34e

9 files changed

Lines changed: 41 additions & 3 deletions

src/concurrency/NotifiedWorkerThread.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit
7676

7777
void NotifiedWorkerThread::checkNotification()
7878
{
79-
auto n = notification;
80-
notification = 0; // clear notification
79+
// Atomically read and clear. (This avoids a potential race condition where an interrupt handler could set a new notification
80+
// after checkNotification reads but before it clears, which would cause us to miss that notification until the next one comes
81+
// in.)
82+
auto n = notification.exchange(0); // read+clear atomically: like `n = notification; notification = 0;` but interrupt-safe
8183
if (n) {
8284
onNotify(n);
8385
}

src/concurrency/NotifiedWorkerThread.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include "OSThread.h"
4+
#include <atomic>
45

56
namespace concurrency
67
{
@@ -13,7 +14,7 @@ class NotifiedWorkerThread : public OSThread
1314
/**
1415
* The notification that was most recently used to wake the thread. Read from runOnce()
1516
*/
16-
uint32_t notification = 0;
17+
std::atomic<uint32_t> notification{0};
1718

1819
public:
1920
NotifiedWorkerThread(const char *name) : OSThread(name) {}

src/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,14 @@ void loop()
11191119
#endif
11201120
power->powerCommandsCheck();
11211121

1122+
if (RadioLibInterface::instance != nullptr) {
1123+
static uint32_t lastRadioMissedIrqPoll;
1124+
if (!Throttle::isWithinTimespanMs(lastRadioMissedIrqPoll, 1000)) {
1125+
lastRadioMissedIrqPoll = millis();
1126+
RadioLibInterface::instance->pollMissedIrqs();
1127+
}
1128+
}
1129+
11221130
#ifdef DEBUG_STACK
11231131
static uint32_t lastPrint = 0;
11241132
if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) {

src/mesh/LR11x0Interface.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ template <typename T> void LR11x0Interface<T>::startReceive()
263263

264264
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
265265
enableInterrupt(isrRxLevel0);
266+
checkRxDoneIrqFlag();
266267
#endif
267268
}
268269

src/mesh/RF95Interface.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ void RF95Interface::startReceive()
301301

302302
// Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits
303303
enableInterrupt(isrRxLevel0);
304+
checkRxDoneIrqFlag();
304305
}
305306

306307
bool RF95Interface::isChannelActive()

src/mesh/RadioLibInterface.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,22 @@ void RadioLibInterface::startReceive()
521521
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
522522
}
523523

524+
void RadioLibInterface::pollMissedIrqs()
525+
{
526+
// RadioLibInterface::enableInterrupt uses EDGE-TRIGGERED interrupts. Poll as a backup to catch missed edges.
527+
if (isReceiving) {
528+
checkRxDoneIrqFlag();
529+
}
530+
}
531+
532+
void RadioLibInterface::checkRxDoneIrqFlag()
533+
{
534+
if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) {
535+
LOG_WARN("caught missed RX_DONE");
536+
notify(ISR_RX, true);
537+
}
538+
}
539+
524540
void RadioLibInterface::configHardwareForSend()
525541
{
526542
powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn);

src/mesh/RadioLibInterface.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
112112
*/
113113
virtual void enableInterrupt(void (*)()) = 0;
114114

115+
/**
116+
* Poll as a backup to catch missed edge-triggered interrupts.
117+
*/
118+
void pollMissedIrqs();
119+
115120
/**
116121
* Debugging counts
117122
*/
@@ -264,4 +269,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
264269
*/
265270

266271
bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override;
272+
273+
void checkRxDoneIrqFlag();
267274
};

src/mesh/SX126xInterface.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ template <typename T> void SX126xInterface<T>::startReceive()
351351

352352
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
353353
enableInterrupt(isrRxLevel0);
354+
checkRxDoneIrqFlag();
354355
#endif
355356
}
356357

src/mesh/SX128xInterface.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ template <typename T> void SX128xInterface<T>::startReceive()
271271

272272
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
273273
enableInterrupt(isrRxLevel0);
274+
checkRxDoneIrqFlag();
274275
#endif
275276
}
276277

0 commit comments

Comments
 (0)