LR1110 duty cycle finally and zephyr PM vs UART wedge fixes

This commit is contained in:
liquidraver
2026-07-25 11:27:25 +02:00
parent e7adc21687
commit 06a0b03a20
6 changed files with 325 additions and 65 deletions
+20
View File
@@ -581,6 +581,26 @@ config ZEPHCORE_LORA_DC_MIN_SYMBOLS
for extra detection margin on very noisy sites. SF5/6 adds +4
internally. Lowering below 8 risks missed packets at low SNR.
config ZEPHCORE_LORA_DC_MARGIN_PCT
int "RX duty cycle: sleep-budget safety margin (percent)"
range 0 40
default 15
help
Percentage the per-cycle deaf-time budget is derated before the
sleep period is programmed. The theoretical catch budget assumes
the sleep clock and wake transition are exact, but the chip's sleep
timer runs on an internal RC oscillator (RC64k on SX126x, RTC on
LR11xx) that drifts several percent over temperature, and the wake-
transition estimate is a datasheet figure the datasheet itself calls
"not accurate and may vary". Either overshoot silently pushes real
deaf time past the budget and drops the fraction of packets whose
preamble phase aligns with the window edge — signal-strength-
independent loss that presents as random DC packet drops. This
margin trades a little radio-off time for that robustness and is
shared by every radio (SX126x / LR11xx / LR20xx). Set to 0 to
restore the old zero-margin behaviour; 15% is a conservative default,
lower it once you have measured your board's clock+transition on air.
endmenu
config ZEPHCORE_RTC_AUTODISCOVER
@@ -935,6 +935,21 @@ static void gps_uart_set_power(bool on)
if (!device_is_ready(gps_uart_dev)) {
return;
}
if (!on) {
/* Let the GNSS line go quiet before suspending. Every caller
* cuts module power (GPS_EN low / reset asserted / regulator
* off) immediately before this, but a byte can still be in
* flight. Settle so it finishes and the driver's RX ISR re-arms,
* leaving the receiver armed-and-idle when the suspend's STOPRX
* fires — that state yields RXTO, whereas a just-stopped,
* un-rearmed receiver can produce none and (pre-0010) hung the
* main thread. ~5 ms comfortably covers one character time at
* GNSS baud plus ISR latency; standby happens at most every few
* minutes, so the cost is negligible. Backstop: patch 0010
* bounds the driver's RXTO wait so a missed RXTO can never hang
* us even if a byte still lands in the race window. */
k_msleep(5);
}
int ret = pm_device_action_run(gps_uart_dev,
on ? PM_DEVICE_ACTION_RESUME
: PM_DEVICE_ACTION_SUSPEND);
+16 -1
View File
@@ -534,8 +534,23 @@ void LoRaRadioBase::startReceive()
const uint32_t sym_us = (uint32_t)
(((uint64_t)(1U << sf) * 1000000ULL) / bw_hz);
const uint32_t trans_us = hwWakeupTimeUs();
const uint32_t deaf_us =
/* Theoretical per-cycle deaf budget, then derate by
* CONFIG_..._MARGIN_PCT. The budget assumes the sleep
* clock and wake transition are exact; in reality the
* chip sleep timer runs on an RC oscillator that drifts
* several % over temperature and the wake transition is a
* "may vary" datasheet figure. Either overshoot pushes
* real deaf time past the budget and drops phase-edge
* packets (strength-independent DC loss). Deraging the
* whole budget is slightly stricter than deraging sleep
* alone, which is the safe direction. */
const uint32_t deaf_budget_us =
(uint32_t)(P - 2 * D - 1) * sym_us;
const uint32_t deaf_us = deaf_budget_us -
(uint32_t)(((uint64_t)deaf_budget_us *
CONFIG_ZEPHCORE_LORA_DC_MARGIN_PCT) /
100U);
if (deaf_us > trans_us + 2000) {
const uint32_t sleep_us = deaf_us - trans_us;
@@ -32,6 +32,16 @@ LOG_MODULE_REGISTER(lr11xx_lora, CONFIG_LORA_LOG_LEVEL);
#define LR11XX_DIO1_WQ_STACK_SIZE 2560
K_THREAD_STACK_DEFINE(lr11xx_dio1_wq_stack, LR11XX_DIO1_WQ_STACK_SIZE);
/* Wedge-recovery watchdog — its own low-priority queue so the confirm poll
* (a BUSY-high dwell of up to LR11XX_WEDGE_CONFIRM_MS) never blocks DIO1 RX
* processing. */
#define LR11XX_WEDGE_WQ_STACK_SIZE 1024
K_THREAD_STACK_DEFINE(lr11xx_wedge_wq_stack, LR11XX_WEDGE_WQ_STACK_SIZE);
#define LR11XX_WEDGE_CHECK_MS 3000 /* base cadence between health checks */
#define LR11XX_WEDGE_IDLE_MS 12000 /* only probe after this much DIO1 silence */
#define LR11XX_WEDGE_CONFIRM_MS 250 /* continuous BUSY-high past this = wedged */
/* ── Driver data structures ─────────────────────────────────────────── */
struct lr11xx_config {
@@ -120,6 +130,16 @@ struct lr11xx_data {
* bit is cleared and TX released. All accesses under spi_mutex. */
uint32_t preamble_seen_at_ms;
/* Wedge-recovery watchdog: the LR1110 can rarely be left BUSY-high with
* DIO1 low (a command racing the autonomous SetRxDutyCycle sleep phase)
* no IRQ ever fires, so the event-driven driver never re-arms and the node
* goes permanently deaf. last_dio1_ms records the last proof-of-life (a
* handled DIO1 event); the watchdog re-arms RX via a hardware reset when
* BUSY stays continuously high past any legitimate DC cycle with no DIO1. */
uint32_t last_dio1_ms;
struct k_work_delayable wedge_work;
struct k_work_q wedge_wq;
/* RX data buffer — filled in DIO1 handler, passed to callback */
uint8_t rx_buf[256];
};
@@ -303,12 +323,27 @@ static void lr11xx_apply_modem_config(struct lr11xx_data *data,
0);
}
/* ── RX duty cycle — disabled on LR1110 ─────────────────────────────── */
/* ── RX duty cycle ──────────────────────────────────────────────────── */
/* LR1110 SetRxDutyCycle is fundamentally broken: both MODE_RX and
* MODE_CAD fail to detect in-progress preambles, dropping 23-40% of
* packets depending on the sleep fraction. The SX1262 handles this
* correctly. LR1110 always uses continuous RX instead. */
/* SetRxDutyCycle(MODE_RX) works the same way as the SX126x: on a positive
* over-the-air (preamble) detection the chip auto-recomputes its RX timeout
* to 2*rx_period + sleep_period and stays in RX for the whole packet (SWDR001
* lr11xx_radio.h step 2). Because that extension is native, the LR1110 needs
* NO StopTimerOnPreamble and NO parked-RX watchdog: a false detection lets the
* bounded 2*rx+sleep timer expire and re-arm via the normal RX-timeout path,
* so there is no infinite-park failure mode to recover from (unlike the
* SX126x, whose StopTimerOnPreamble freezes the timer). Window sizing
* including the shared clock/transition safety margin is owned by the
* adapter (LoRaRadioBase::startReceive); the driver only re-arms with the
* stored timing.
*
* The earlier "fundamentally broken, 23-40% loss" verdict was a window-sizing
* bug in the adapter, not a chip defect. One item is still HW-unverified: the
* chip's sleep-with-retention warm wakes should keep the boosted RX gain
* (the SX126x needs an explicit retention-list write for this; the LR11xx SDK
* exposes no such list, implying it is automatic measure sensitivity across
* cycles on a live board to confirm). We re-apply SetRxBoosted on every host
* restart regardless, as cheap insurance. */
/* ── Start RX (internal) ────────────────────────────────────────────── */
@@ -421,6 +456,9 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
void *ctx = &data->hal_ctx;
bool rx_restarted = false;
/* Proof of life for the wedge watchdog: the chip generated an IRQ. */
data->last_dio1_ms = k_uptime_get_32();
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Read IRQ status then clear ALL bits. Must use ALL_MASK because
@@ -453,18 +491,21 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
}
/* ── RX done ──
* Gated on no error bits: a CRC-failed packet asserts RX_DONE and
* CRC_ERROR together on this chip family (same as SX126x see the
* sx126x patch's "they co-fire here"; LBM's radio_planner likewise
* checks errors before RX_DONE), so an ungated done-first read
* would deliver corrupted payloads as valid. A good packet
* coalesced with an earlier HEADER_ERROR in the same handler
* window is dropped too the header error may have shifted the
* RX buffer pointer (errata handling below), making the read
* suspect. The error branch below owns the window instead. */
* Deliver on RX_DONE unless it is a GENUINE reception failure:
* - CRC_ERROR: a CRC-failed packet asserts RX_DONE+CRC_ERROR together
* on this chip family, so an ungated read would deliver corruption.
* - HEADER_ERROR with NO valid header (SYNC_WORD_HEADER_VALID clear):
* no good header decoded, the buffer is suspect.
* A HEADER_ERROR that coincides with a VALID header is a foreign/colliding
* signal's error while OUR packet decoded fine RadioLib's readData keeps
* the packet in exactly this case (CRC_ERR || (HDR_ERR && !HDR_VALID)), and
* the SX126x path never routes HEADER_ERROR at all. Dropping these good
* packets was the LR1110 under-load packet-loss bug (verified 2026-07-24:
* ~7-10% loss vs SX1262, load-dependent, size-skewed). */
if ((irq & LR11XX_SYSTEM_IRQ_RX_DONE) &&
!(irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
LR11XX_SYSTEM_IRQ_HEADER_ERROR))) {
!(irq & LR11XX_SYSTEM_IRQ_CRC_ERROR) &&
!((irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) &&
!(irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID))) {
lr11xx_radio_rx_buffer_status_t rx_stat;
lr11xx_radio_get_rx_buffer_status(ctx, &rx_stat);
@@ -477,6 +518,19 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
rx_stat.buffer_start_pointer,
rx_stat.pld_len_in_bytes);
/* Buffer-shift errata, header-error variant: if a foreign
* HEADER_ERROR coalesced with this good packet, a standby
* must reset the +4 buffer_start_pointer drift so it cannot
* corrupt the NEXT packet's read. The payload is already
* captured above, so resetting now keeps the packet AND the
* errata protection. (A plain RX_DONE with no header error
* gets the same reset from the STDBY_RC rx/tx fallback; this
* makes the coalesced case explicit, not fallback-reliant.) */
if (irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) {
lr11xx_system_set_standby(ctx,
LR11XX_SYSTEM_STANDBY_CFG_RC);
}
/* LR1110 errata: RX buffer base shifts 4 bytes per
* received packet. Without clearing, after ~64
* packets the offset wraps the 256-byte buffer and
@@ -559,32 +613,38 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
}
}
/* ── CRC / Header error ──
* Plain `CRC || HDR` no SYNC_WORD_HEADER_VALID gate (RadioLib
* readData's false-alarm filter). IRQ status is bulk-cleared on
* every handler entry, so a set HEADER_ERROR always belongs to
* this window; a SYNC_VALID bit latched by another packet in the
* same window must not suppress the errata standby below. */
if (irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
LR11XX_SYSTEM_IRQ_HEADER_ERROR)) {
/* ── CRC / header error ──
* A HEADER_ERROR that coincides with a VALID header but no RX_DONE means a
* foreign/colliding signal errored while OUR good packet is still
* mid-reception. Do NOT abort it: the old reflex standby()+restart here
* dropped the in-flight packet, and doing that on every foreign header
* error under load is what cost the LR1110 packets (the SX126x never routes
* HEADER_ERROR for exactly this reason). Leave the chip in RX; the good
* packet's own RX_DONE delivers it above. */
if ((irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) &&
(irq & LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID) &&
!(irq & LR11XX_SYSTEM_IRQ_RX_DONE)) {
LOG_DBG("RX: foreign HDR err during valid header — not aborting");
rx_restarted = true; /* reception continues; skip safety-net restart */
} else if (irq & (LR11XX_SYSTEM_IRQ_CRC_ERROR |
LR11XX_SYSTEM_IRQ_HEADER_ERROR)) {
LOG_DBG("RX error: CRC=%d HDR=%d RXDONE=%d",
(irq & LR11XX_SYSTEM_IRQ_CRC_ERROR) ? 1 : 0,
(irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) ? 1 : 0,
(irq & LR11XX_SYSTEM_IRQ_RX_DONE) ? 1 : 0);
/* LR1110 errata: a header error makes the chip's reported
* buffer_start_pointer stale by +4 for every subsequent
* packet (stacking) until a standby resets the shift.
* Arduino MeshCore's CustomLR1110 calls standby() on every
* header error unconditionally mirror that. */
/* LR1110 errata: a real header error (no valid header) drifts the
* reported buffer_start_pointer +4 per subsequent packet until a
* standby resets it (Arduino MeshCore's CustomLR1110 standbys on
* header error). Only for a genuine header error a valid header
* is handled above and must never trigger this abort. */
if (irq & LR11XX_SYSTEM_IRQ_HEADER_ERROR) {
lr11xx_system_set_standby(ctx,
LR11XX_SYSTEM_STANDBY_CFG_RC);
}
/* Drop whatever the failed (or coalesced) packet left in
* the RX buffer RadioLib clears it on the CRC-error read
* path too. */
/* Drop whatever the failed packet left in the RX buffer — RadioLib
* clears it on the CRC-error read path too. */
lr11xx_regmem_clear_rxbuffer(ctx);
if (!data->tx_active) {
@@ -958,8 +1018,28 @@ int16_t lr11xx_get_rssi_inst(const struct device *dev)
struct lr11xx_data *data = dev->data;
int8_t rssi;
k_mutex_lock(&data->spi_mutex, K_FOREVER);
lr11xx_radio_get_rssi_inst(&data->hal_ctx, &rssi);
/* GPIO BUSY read FIRST — bail before the HAL's wait_on_busy stalls (3 s)
* on the autonomous duty-cycle sleep phase. Issuing GetRssiInst (0x0205)
* into a chip parked in SetRxDutyCycle sleep-with-retention wedges it
* BUSY-high permanently (root cause of the 2026-07-24 "T1000 goes deaf"
* wedge). -128 is the busy/contended sentinel the noise-floor sampler
* expects (LoRaRadioBase::triggerNoiseFloorCalibrate retries next tick).
* Direct parity with sx126x_get_rssi_inst. */
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
return -128;
}
if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) {
return -128;
}
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
k_mutex_unlock(&data->spi_mutex);
return -128;
}
if (lr11xx_radio_get_rssi_inst(&data->hal_ctx, &rssi) != LR11XX_STATUS_OK) {
k_mutex_unlock(&data->spi_mutex);
return -128;
}
k_mutex_unlock(&data->spi_mutex);
return (int16_t)rssi;
@@ -987,6 +1067,16 @@ bool lr11xx_is_receiving(const struct device *dev)
{
struct lr11xx_data *data = dev->data;
/* GPIO BUSY read FIRST: during the autonomous duty-cycle sleep phase the
* chip is asleep (BUSY high) and by definition not mid-packet, so report
* "not receiving" WITHOUT issuing get_irq_status which, like GetRssiInst,
* can wedge the chip BUSY-high if issued into DC sleep. Same guard as
* lr11xx_get_rssi_inst; this path runs on every TX gate so it is the other
* async command that could hit the sleep window. */
if (gpio_pin_get_dt(&data->hal_ctx.busy)) {
return false;
}
/* Non-blocking — skip if SPI busy */
if (k_mutex_lock(&data->spi_mutex, K_NO_WAIT) != 0) {
return false;
@@ -1416,6 +1506,66 @@ static int lr11xx_hw_init(struct lr11xx_data *data,
return 0;
}
/* ── Wedge-recovery watchdog ────────────────────────────────────────── */
/* Detects the "BUSY stuck high, DIO1 silent" wedge and re-arms RX with a
* hardware reset. False-positive free by construction: a healthy chip
* continuous RX or autonomous DC cycling always drops BUSY low within one
* duty-cycle period, so a *continuous* BUSY-high dwell longer than any
* legitimate cycle, with no DIO1 event for >12 s, can only be a genuine wedge.
* Passive: reads the BUSY GPIO only (no SPI), so it cannot itself disturb the
* chip or race the autonomous DC state machine. Runs on its own queue at the
* DIO1 priority; the confirm poll yields every 2 ms so a real packet arriving
* mid-confirm is still processed promptly on the DIO1 queue. */
static void lr11xx_wedge_watchdog_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct lr11xx_data *data = CONTAINER_OF(dwork, struct lr11xx_data,
wedge_work);
const struct lr11xx_config *cfg = data->dev->config;
/* Only monitor steady RX. */
if (!data->in_rx_mode || data->tx_active || !data->configured) {
goto rearm;
}
/* Recent DIO1 activity = chip provably alive. */
if ((k_uptime_get_32() - data->last_dio1_ms) < LR11XX_WEDGE_IDLE_MS) {
goto rearm;
}
/* Idle: either quiet RF (chip still DC-cycling) or a wedge. Poll BUSY
* continuously for one confirm window a healthy chip shows a low edge
* within a cycle; a wedge stays high the whole window. */
{
uint32_t start = k_uptime_get_32();
bool saw_low = false;
while ((k_uptime_get_32() - start) < LR11XX_WEDGE_CONFIRM_MS) {
if (!gpio_pin_get_dt(&data->hal_ctx.busy)) {
saw_low = true;
break;
}
k_msleep(2);
}
if (saw_low) {
goto rearm; /* alive */
}
}
LOG_ERR("LR11xx wedge (BUSY stuck, DIO1 silent >%ums) — hardware reset",
LR11XX_WEDGE_IDLE_MS);
k_mutex_lock(&data->spi_mutex, K_FOREVER);
lr11xx_hardware_reset(data, cfg);
lr11xx_start_rx(data, cfg);
k_mutex_unlock(&data->spi_mutex);
data->last_dio1_ms = k_uptime_get_32();
rearm:
k_work_schedule_for_queue(&data->wedge_wq, &data->wedge_work,
K_MSEC(LR11XX_WEDGE_CHECK_MS));
}
/* ── Driver init (lightweight — runs at POST_KERNEL) ────────────────── */
static int lr11xx_lora_init(const struct device *dev)
@@ -1437,6 +1587,17 @@ static int lr11xx_lora_init(const struct device *dev)
K_PRIO_COOP(7), NULL);
k_thread_name_set(&data->dio1_wq.thread, "lr11xx_dio1");
/* Wedge-recovery watchdog on its own queue (see handler). Same priority
* as DIO1 so it never preempts RX; its confirm poll yields every 2 ms. */
k_work_init_delayable(&data->wedge_work, lr11xx_wedge_watchdog_handler);
k_work_queue_start(&data->wedge_wq, lr11xx_wedge_wq_stack,
K_THREAD_STACK_SIZEOF(lr11xx_wedge_wq_stack),
K_PRIO_COOP(7), NULL);
k_thread_name_set(&data->wedge_wq.thread, "lr11xx_wedge");
data->last_dio1_ms = k_uptime_get_32();
k_work_schedule_for_queue(&data->wedge_wq, &data->wedge_work,
K_MSEC(LR11XX_WEDGE_CHECK_MS));
/* Check SPI bus */
if (!spi_is_ready_dt(&cfg->bus)) {
LOG_ERR("SPI bus not ready");
@@ -68,7 +68,7 @@ index 17689720dd2..09982dc2e70 100644
return -EINVAL;
}
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
index 30243ba5dc7..5315e5d80ef 100644
index 30243ba5dc7..f0794661515 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -6,14 +6,21 @@
@@ -282,17 +282,31 @@ index 30243ba5dc7..5315e5d80ef 100644
sx126x_hal_set_rf_switch(dev, enable, tx);
}
}
@@ -455,6 +573,215 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
@@ -455,6 +573,229 @@ static int sx126x_reconnect_rf_gpios(const struct device *dev)
}
#endif /* CONFIG_PM_DEVICE */
+/* -- Duty-cycle support helpers -- */
+
+/* Watchdog sampling period. Must exceed the longest legitimate
+ * preamble-detect -> header gap at any supported preset so that two
+ * consecutive parked sightings can never be one real packet (worst
+ * case SF12/BW62.5, 16-sym preamble: ~1.9 s). */
+#define SX126X_DC_WATCHDOG_MS 3000
+/* Watchdog sampling period, scaled to the active preset. The two-strike
+ * design needs the period to exceed the longest legitimate preamble-detect ->
+ * header gap so two consecutive parked sightings can never both fall inside
+ * one real packet's gap (which would abort a live reception). A fixed value
+ * has to be sized for the slowest preset (SF12/BW62.5, ~1.9 s) -- ~15x larger
+ * than needed at SF8/BW62.5, so every false-preamble park then pins the radio
+ * in full RX for up to two periods, wasting the duty-cycle power savings.
+ * Scale it instead: 2x the (preamble+8)-symbol grace covers the gap with
+ * margin at every preset (SF8/BW62.5 ~0.3 s, SF12/BW62.5 ~3.1 s), floored so
+ * fast presets don't poll the SPI bus raw. Never costs packets -- a parked
+ * radio is still receiving; the period only bounds how long it stays parked. */
+#define SX126X_DC_WATCHDOG_MIN_MS 250
+
+static uint32_t sx126x_dc_watchdog_ms(struct sx126x_data *data)
+{
+ uint32_t ms = 2U * sx126x_preamble_grace_ms(data);
+
+ return (ms < SX126X_DC_WATCHDOG_MIN_MS) ? SX126X_DC_WATCHDOG_MIN_MS : ms;
+}
+
+/* DS sec 9.6: the RX gain register (0x08AC) is not part of the warm-start
+ * retention memory. RxDutyCycle sleep->RX wakes are chip-internal warm
@@ -317,7 +331,7 @@ index 30243ba5dc7..5315e5d80ef 100644
+{
+ data->dc_watchdog_strikes = 0;
+ k_work_reschedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
+ K_MSEC(sx126x_dc_watchdog_ms(data)));
+}
+
+/* -- Lightweight RX restart -- */
@@ -492,13 +506,13 @@ index 30243ba5dc7..5315e5d80ef 100644
+
+rearm:
+ k_work_schedule_for_queue(&data->dio1_wq, &data->dc_watchdog_work,
+ K_MSEC(SX126X_DC_WATCHDOG_MS));
+ K_MSEC(sx126x_dc_watchdog_ms(data)));
+}
+
static int sx126x_set_sleep(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -533,8 +860,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
@@ -533,8 +874,16 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
struct sx126x_data *data = dev->data;
struct sx126x_rx_result result = { 0 };
uint8_t payload_len = 0, offset = 0;
@@ -515,7 +529,7 @@ index 30243ba5dc7..5315e5d80ef 100644
/* Get received packet info */
ret = sx126x_get_rx_buffer_status(dev, &payload_len, &offset);
if (ret < 0) {
@@ -544,9 +879,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
@@ -544,9 +893,26 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
/* Get signal quality */
sx126x_get_packet_status(dev, &result.rssi, &result.snr);
@@ -545,7 +559,7 @@ index 30243ba5dc7..5315e5d80ef 100644
result.status = -EIO;
} else {
/* Read payload into shared buffer */
@@ -564,20 +916,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
@@ -564,20 +930,34 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
}
}
@@ -592,7 +606,7 @@ index 30243ba5dc7..5315e5d80ef 100644
}
} else {
/* Sync mode */
@@ -589,8 +955,69 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
@@ -589,8 +969,69 @@ static void sx126x_handle_irq_rx_done(const struct device *dev, uint16_t irq_sta
static void sx126x_handle_irq_timeout(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -662,7 +676,7 @@ index 30243ba5dc7..5315e5d80ef 100644
sx126x_set_sleep(dev);
if (data->tx_async_signal != NULL) {
@@ -633,10 +1060,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
@@ -633,10 +1074,46 @@ static void sx126x_irq_work_handler(struct k_work *work)
sx126x_handle_irq_rx_done(dev, irq_status);
}
@@ -709,7 +723,7 @@ index 30243ba5dc7..5315e5d80ef 100644
/* Re-enable the DIO1 interrupt for the next event (unless sleeping) */
if (atomic_get(&data->state) != SX126X_REST_STATE) {
sx126x_hal_dio1_irq_enable(dev);
@@ -682,7 +1145,7 @@ static int sx126x_lora_config(const struct device *dev,
@@ -682,7 +1159,7 @@ static int sx126x_lora_config(const struct device *dev,
/* Configure PA and TX power based on chip variant and frequency */
ret = sx126x_hal_configure_tx_params(dev, config->tx_power,
config->frequency,
@@ -718,7 +732,7 @@ index 30243ba5dc7..5315e5d80ef 100644
if (ret < 0) {
goto out;
}
@@ -698,6 +1161,29 @@ static int sx126x_lora_config(const struct device *dev,
@@ -698,6 +1175,29 @@ static int sx126x_lora_config(const struct device *dev,
goto out;
}
@@ -748,7 +762,7 @@ index 30243ba5dc7..5315e5d80ef 100644
/* Set sync word */
ret = sx126x_set_sync_word(dev, config->public_network);
if (ret < 0) {
@@ -721,6 +1207,9 @@ out:
@@ -721,6 +1221,9 @@ out:
return ret;
}
@@ -758,7 +772,7 @@ index 30243ba5dc7..5315e5d80ef 100644
static int sx126x_lora_send_async(const struct device *dev,
uint8_t *data_buf, uint32_t data_len,
struct k_poll_signal *async)
@@ -738,11 +1227,27 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -738,11 +1241,27 @@ static int sx126x_lora_send_async(const struct device *dev,
return -EINVAL;
}
@@ -788,7 +802,7 @@ index 30243ba5dc7..5315e5d80ef 100644
k_mutex_lock(&data->lock, K_FOREVER);
ret = sx126x_ensure_ready(dev);
@@ -752,6 +1257,59 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -752,6 +1271,59 @@ static int sx126x_lora_send_async(const struct device *dev,
return ret;
}
@@ -848,7 +862,7 @@ index 30243ba5dc7..5315e5d80ef 100644
data->tx_async_signal = async;
k_msgq_purge(&data->tx_msgq);
@@ -777,6 +1335,31 @@ static int sx126x_lora_send_async(const struct device *dev,
@@ -777,6 +1349,31 @@ static int sx126x_lora_send_async(const struct device *dev,
/* Enable antenna and set TX path */
sx126x_set_rf_path(dev, true, true);
@@ -880,7 +894,7 @@ index 30243ba5dc7..5315e5d80ef 100644
/* Start transmission with 10 second timeout */
ret = sx126x_set_tx(dev, 10000);
if (ret < 0) {
@@ -847,6 +1430,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
@@ -847,6 +1444,8 @@ static int sx126x_lora_recv(const struct device *dev, uint8_t *data_buf,
}
data->rx_cb = NULL;
@@ -889,7 +903,7 @@ index 30243ba5dc7..5315e5d80ef 100644
k_msgq_purge(&data->rx_msgq);
/* Set packet parameters for variable length reception */
@@ -918,6 +1503,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -918,6 +1517,8 @@ static int sx126x_lora_recv_async(const struct device *dev,
/* Stop async reception */
data->rx_cb = NULL;
data->rx_cb_user_data = NULL;
@@ -898,7 +912,7 @@ index 30243ba5dc7..5315e5d80ef 100644
if (atomic_cas(&data->state, SX126X_STATE_RX, SX126X_STATE_IDLE)) {
sx126x_set_standby(dev, SX126X_STANDBY_RC);
sx126x_set_sleep(dev);
@@ -932,6 +1519,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -932,6 +1533,19 @@ static int sx126x_lora_recv_async(const struct device *dev,
return -EINVAL;
}
@@ -918,7 +932,7 @@ index 30243ba5dc7..5315e5d80ef 100644
if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_RX)) {
LOG_ERR("Busy");
k_mutex_unlock(&data->lock);
@@ -945,8 +1545,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -945,8 +1559,18 @@ static int sx126x_lora_recv_async(const struct device *dev,
return ret;
}
@@ -937,7 +951,7 @@ index 30243ba5dc7..5315e5d80ef 100644
/* Set packet parameters */
ret = sx126x_set_packet_params(dev,
@@ -959,6 +1569,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -959,6 +1583,7 @@ static int sx126x_lora_recv_async(const struct device *dev,
SX126X_LORA_IQ_INVERTED : SX126X_LORA_IQ_STANDARD);
if (ret < 0) {
data->rx_cb = NULL;
@@ -945,7 +959,7 @@ index 30243ba5dc7..5315e5d80ef 100644
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);
return ret;
@@ -971,11 +1582,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
@@ -971,11 +1596,21 @@ static int sx126x_lora_recv_async(const struct device *dev,
ret = sx126x_set_rx(dev, 0);
if (ret < 0) {
data->rx_cb = NULL;
@@ -967,7 +981,7 @@ index 30243ba5dc7..5315e5d80ef 100644
k_mutex_unlock(&data->lock);
return 0;
}
@@ -1051,7 +1672,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
@@ -1051,7 +1686,7 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
/* Set PA config and TX power */
ret = sx126x_hal_configure_tx_params(dev, tx_power, frequency,
@@ -976,7 +990,7 @@ index 30243ba5dc7..5315e5d80ef 100644
if (ret < 0) {
sx126x_set_sleep(dev);
k_mutex_unlock(&data->lock);
@@ -1083,14 +1704,671 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
@@ -1083,14 +1718,671 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
return 0;
}
@@ -1655,7 +1669,7 @@ index 30243ba5dc7..5315e5d80ef 100644
};
#ifdef CONFIG_PM_DEVICE
@@ -1112,6 +2390,7 @@ static int sx126x_pm_action(const struct device *dev,
@@ -1112,6 +2404,7 @@ static int sx126x_pm_action(const struct device *dev,
static int sx126x_init(const struct device *dev)
{
struct sx126x_data *data = dev->data;
@@ -1663,7 +1677,7 @@ index 30243ba5dc7..5315e5d80ef 100644
int ret;
/* Initialize data structures */
@@ -1121,9 +2400,29 @@ static int sx126x_init(const struct device *dev)
@@ -1121,9 +2414,29 @@ static int sx126x_init(const struct device *dev)
k_msgq_init(&data->rx_msgq, (char *)&data->rx_result,
sizeof(struct sx126x_rx_result), 1);
k_work_init(&data->irq_work, sx126x_irq_work_handler);
@@ -0,0 +1,35 @@
diff --git a/drivers/serial/uart_nrfx_uarte.c b/drivers/serial/uart_nrfx_uarte.c
index 60e8c23cd17..e78ba2af1fe 100644
--- a/drivers/serial/uart_nrfx_uarte.c
+++ b/drivers/serial/uart_nrfx_uarte.c
@@ -3011,11 +3011,26 @@ static int uarte_pm_suspend(const struct device *dev)
}
}
#endif
+ bool rxto;
+
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STOPRX);
- while (!nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXTO)) {
- /* Busy wait for event to register */
- Z_SPIN_DELAY(2);
- }
+ /* ZephCore: bound the RXTO wait. Upstream spins here
+ * forever. In interrupt-driven mode the RX runs on a
+ * 1-byte buffer with no ENDRX_STARTRX short, so the
+ * receiver stops after each byte until the ISR re-arms
+ * it -- and the ENDRX interrupt is disabled just above.
+ * If a byte finishes in that window the receiver is
+ * already stopped and un-rearmed, STOPRX then produces
+ * no RXTO, and this loop never exits, hanging the caller
+ * (the main mesh thread on GPS standby -> whole node
+ * wedges: no LoRa/USB/BLE service until reboot). 4 ms is
+ * orders of magnitude past the normal sub-microsecond
+ * RXTO latency; on timeout we fall through to the
+ * unconditional nrf_uarte_disable() below, which
+ * force-stops the receiver regardless. */
+ NRFX_WAIT_FOR(nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXTO),
+ 2000, 2, rxto);
+ (void)rxto;
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXSTARTED);
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXTO);
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_ENDRX);