| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- From 348754e83885638b36432cec7b1b70199c72f9eb Mon Sep 17 00:00:00 2001
- From: ZiB <zib@zi3.xyz>
- Date: Sat, 7 Nov 2026 15:08:00 -0800
- Subject: [PATCH 3/3] drivers: lora: native: sx126x: implement driver-level LBT
- send
- Adds sx126x_lora_send_lbt(), an SX126x-specific extension that does
- CAD then, only if clear, transmits.
- Uses a new SX126X_STATE_CAD_TX state covering the whole CAD->TX op.
- Packet params/FIFO/TX-modulation workaround are staged before SetCAD,
- not after - CAD doesn't touch any of those registers, so this is free.
- On a clear CadDone, the IRQ handler goes straight from "channel is
- clear" to RF-path + SetTx, skipping the sleep that CAD_ONLY's generic
- completion path (sx126x_handle_irq_cad_done()) otherwise always takes.
- The two outcomes (busy vs. sent) are delivered over a new dedicated
- lbt_msgq rather than overloading tx_msgq/cad_msgq, since only this
- state's completion needs to carry both. A caller's cad_timeout bounds
- only the CAD phase: lbt_committed tracks whether CAD already went
- clear and SetTx was issued, so a timeout arriving after that point
- waits out the actual transmission (bounded by SetTx's own 10s
- hardware timeout) instead of aborting an in-flight TX.
- Retry/backoff policy is deliberately not pulled into the driver.
- CAD exit mode stays CAD_ONLY: the SX126x has no CAD-to-TX exit mode in
- hardware (only CAD-to-RX), so this is done by skipping the driver's
- own sleep rather than anything the radio does autonomously.
- Signed-off-by: ZiB <zib@zi3.xyz>
- ---
- drivers/lora/native/sx126x/sx126x.c | 255 +++++++++++++++++++++++++--
- drivers/lora/native/sx126x/sx126x.h | 17 ++
- include/zephyr/drivers/lora/sx126x.h | 24 +++
- 3 files changed, 277 insertions(+), 19 deletions(-)
- diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
- index 8ba8416ed0c..16037a3714d 100644
- --- a/drivers/lora/native/sx126x/sx126x.c
- +++ b/drivers/lora/native/sx126x/sx126x.c
- @@ -817,9 +817,24 @@ static int sx126x_ensure_ready(const struct device *dev)
- static void sx126x_handle_irq_tx_done(const struct device *dev)
- {
- struct sx126x_data *data = dev->data;
- - struct sx126x_tx_result result = { .status = 0 };
-
- LOG_DBG("TX done");
- +
- + /* A send_lbt() TX-after-clear-CAD never changes data->state away
- + * from SX126X_STATE_CAD_TX (see sx126x_handle_irq_cad_done_lbt()),
- + * so that state alone tells a TX_DONE here apart from one started
- + * by lora_send()/lora_send_async().
- + */
- + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
- + struct sx126x_lbt_result result = { .status = 0 };
- +
- + sx126x_set_sleep(dev);
- + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
- + return;
- + }
- +
- + struct sx126x_tx_result result = { .status = 0 };
- +
- sx126x_set_sleep(dev);
-
- if (data->tx_async_signal != NULL) {
- @@ -905,14 +920,61 @@ out:
- }
- }
-
- +/*
- + * CadDone branch for a send_lbt() op (data->state == SX126X_STATE_CAD_TX).
- + * On activity, this is the whole op's result: sleep and report busy. On a
- + * clear channel, sx126x_lora_send_lbt() already staged packet params/FIFO/
- + * TX-modulation workaround before issuing SetCAD (none of those registers
- + * matter to CAD) - so the only things left between "channel is clear" and
- + * "radio is transmitting" are RF-path + SetTx, issued right here, with no
- + * intervening sleep/wake round-trip. data->state is deliberately left at
- + * SX126X_STATE_CAD_TX (not folded back to SLEEP, not advanced to
- + * SX126X_STATE_TX) so sx126x_handle_irq_tx_done()/sx126x_handle_irq_timeout()
- + * can tell the eventual TX_DONE/timeout apart from one started by
- + * lora_send()/lora_send_async() and route it back to lbt_msgq instead of
- + * tx_msgq.
- + */
- +static void sx126x_handle_irq_cad_done_lbt(const struct device *dev, bool activity_detected)
- +{
- + struct sx126x_data *data = dev->data;
- + struct sx126x_lbt_result result;
- + int ret;
- +
- + if (activity_detected) {
- + LOG_DBG("LBT: channel busy, not transmitting");
- + sx126x_set_sleep(dev);
- + result.status = 1;
- + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
- + return;
- + }
- +
- + sx126x_set_rf_path(dev, true, true);
- +
- + ret = sx126x_set_tx(dev, 10000);
- + if (ret < 0) {
- + LOG_ERR("LBT: SetTx failed: %d", ret);
- + sx126x_set_sleep(dev);
- + result.status = ret;
- + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
- + return;
- + }
- +
- + atomic_set(&data->lbt_committed, 1);
- +}
- +
- static void sx126x_handle_irq_cad_done(const struct device *dev, uint16_t irq_status)
- {
- struct sx126x_data *data = dev->data;
- - struct sx126x_cad_result result = {
- - .activity_detected = (irq_status & SX126X_IRQ_CAD_ACTIVITY_DETECTED) != 0,
- - };
- + bool activity_detected = (irq_status & SX126X_IRQ_CAD_ACTIVITY_DETECTED) != 0;
-
- - LOG_DBG("CAD done: activity=%d", result.activity_detected);
- + LOG_DBG("CAD done: activity=%d", activity_detected);
- +
- + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
- + sx126x_handle_irq_cad_done_lbt(dev, activity_detected);
- + return;
- + }
- +
- + struct sx126x_cad_result result = { .activity_detected = activity_detected };
-
- /* CAD_ONLY already left the chip in STDBY_RC; this just folds it
- * back into the driver's own rest state.
- @@ -947,6 +1009,18 @@ static void sx126x_handle_irq_timeout(const struct device *dev)
- return;
- }
-
- + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
- + /* SetTx's own 10s HW timeout, firing on a send_lbt() TX that
- + * committed after a clear CAD - see
- + * sx126x_handle_irq_cad_done_lbt().
- + */
- + struct sx126x_lbt_result result = { .status = -ETIMEDOUT };
- +
- + sx126x_set_sleep(dev);
- + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
- + return;
- + }
- +
- sx126x_set_sleep(dev);
-
- if (data->tx_async_signal != NULL) {
- @@ -1179,6 +1253,33 @@ out:
- return ret;
- }
-
- +/*
- + * Packet params + FIFO + TX-modulation workaround: everything a
- + * transmission needs besides RF-path and SetTx themselves. Shared by
- + * sx126x_lora_send_async() and sx126x_lora_send_lbt(), the latter calling
- + * it *before* SetCAD so that, once CAD comes back clear, only RF-path and
- + * SetTx stand between CadDone and a transmitting radio - see
- + * sx126x_handle_irq_cad_done_lbt().
- + */
- +static int sx126x_stage_tx(const struct device *dev, uint8_t *data_buf, uint32_t data_len)
- +{
- + struct sx126x_data *data = dev->data;
- + int ret;
- +
- + ret = sx126x_set_configured_packet_params(dev, data_len);
- + if (ret < 0) {
- + return ret;
- + }
- +
- + ret = sx126x_hal_write_buffer(dev, 0x00, data_buf, data_len);
- + if (ret < 0) {
- + return ret;
- + }
- +
- + return sx126x_apply_tx_modulation_workaround(
- + dev, !SX126X_IS_GFSK(data) && data->config.bandwidth == BW_500_KHZ);
- +}
- +
- static int sx126x_lora_send_async(const struct device *dev,
- uint8_t *data_buf, uint32_t data_len,
- struct k_poll_signal *async)
- @@ -1223,20 +1324,7 @@ static int sx126x_lora_send_async(const struct device *dev,
- data->tx_async_signal = async;
- k_msgq_purge(&data->tx_msgq);
-
- - /* Set packet parameters */
- - ret = sx126x_set_configured_packet_params(dev, data_len);
- - if (ret < 0) {
- - goto out_error;
- - }
- -
- - /* Write payload to buffer */
- - ret = sx126x_hal_write_buffer(dev, 0x00, data_buf, data_len);
- - if (ret < 0) {
- - goto out_error;
- - }
- -
- - ret = sx126x_apply_tx_modulation_workaround(
- - dev, !SX126X_IS_GFSK(data) && data->config.bandwidth == BW_500_KHZ);
- + ret = sx126x_stage_tx(dev, data_buf, data_len);
- if (ret < 0) {
- goto out_error;
- }
- @@ -1847,6 +1935,133 @@ static int sx126x_lora_cad_async(const struct device *dev, lora_cad_cb cb, void
- return 0;
- }
-
- +/*
- + * Listen-before-talk, done in the driver so the gap between "channel is
- + * clear" and "radio is transmitting" is just RF-path + SetTx, with no
- + * sleep/wake round-trip in between (see sx126x_handle_irq_cad_done_lbt()
- + * and sx126x_stage_tx()). Not part of the generic lora.h API - exposed via
- + * zephyr/drivers/lora/sx126x.h, same as the other SX126x-specific
- + * extensions.
- + *
- + * @p cad_timeout bounds only the CAD phase. Once CAD comes back clear and
- + * the radio commits to transmitting, the wait for TX_DONE is bounded
- + * separately (by SetTx's own 10s hardware timeout) so a short
- + * @p cad_timeout can't abort an in-flight transmission - see
- + * data->lbt_committed below.
- + *
- + * @return 1 if the channel was busy (nothing was sent), 0 if the frame
- + * was sent, or a negative error code.
- + */
- +int sx126x_lora_send_lbt(const struct device *dev, uint8_t *data_buf, uint32_t data_len,
- + k_timeout_t cad_timeout)
- +{
- + struct sx126x_data *data = dev->data;
- + struct sx126x_cad_defaults defaults;
- + enum lora_cad_symbol_num symbol_num;
- + uint8_t det_peak, det_min, symbol_num_reg;
- + struct sx126x_lbt_result result;
- + int ret;
- +
- + if (!data->config_valid) {
- + LOG_ERR("Not configured");
- + return -EINVAL;
- + }
- +
- + if (SX126X_IS_GFSK(data)) {
- + LOG_ERR("CAD is a LoRa-only operation");
- + return -ENOTSUP;
- + }
- +
- + if (data_len > SX126X_MAX_PAYLOAD_LEN) {
- + LOG_ERR("Payload too long: %u", data_len);
- + return -EINVAL;
- + }
- +
- + symbol_num = data->config.cad.symbol_num;
- + det_peak = data->config.cad.detection_peak;
- + det_min = data->config.cad.detection_minimum;
- +
- + if (symbol_num == 0 || det_peak == 0 || det_min == 0) {
- + ret = sx126x_cad_lookup_defaults(data->config.bandwidth, data->config.datarate,
- + &defaults);
- + if (ret < 0) {
- + LOG_ERR("No CAD defaults for this bandwidth/SF; "
- + "set cad.symbol_num/detection_peak/detection_minimum explicitly");
- + return ret;
- + }
- + symbol_num = symbol_num ? symbol_num : defaults.symbol_num;
- + det_peak = det_peak ? det_peak : defaults.det_peak;
- + det_min = det_min ? det_min : defaults.det_min;
- + }
- +
- + ret = sx126x_cad_symbol_num_to_reg(symbol_num, &symbol_num_reg);
- + if (ret < 0) {
- + LOG_ERR("Invalid CAD symbol count: %d", symbol_num);
- + return ret;
- + }
- +
- + if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_CAD_TX)) {
- + LOG_ERR("Busy");
- + return -EBUSY;
- + }
- +
- + k_mutex_lock(&data->lock, K_FOREVER);
- + k_msgq_purge(&data->lbt_msgq);
- + atomic_set(&data->lbt_committed, 0);
- +
- + ret = sx126x_ensure_ready(dev);
- + if (ret < 0) {
- + goto out_error;
- + }
- +
- + /* Stage the payload now, while the chip is awake in STDBY_RC and
- + * before SetCAD - CAD doesn't touch packet params/FIFO/TX-mod, so
- + * none of this needs to happen again after CadDone.
- + */
- + ret = sx126x_stage_tx(dev, data_buf, data_len);
- + if (ret < 0) {
- + goto out_error;
- + }
- +
- + ret = sx126x_set_cad_params(dev, symbol_num_reg, det_peak, det_min,
- + SX126X_CAD_EXIT_MODE_CAD_ONLY, 0);
- + if (ret < 0) {
- + goto out_error;
- + }
- +
- + ret = sx126x_set_cad(dev);
- + if (ret < 0) {
- + goto out_error;
- + }
- +
- + k_mutex_unlock(&data->lock);
- +
- + ret = k_msgq_get(&data->lbt_msgq, &result, cad_timeout);
- + if (ret < 0 && atomic_get(&data->lbt_committed)) {
- + /* CAD already came back clear and TX is underway; the short
- + * CAD-phase wait above isn't the right bound for that - wait
- + * out the transmission itself instead of aborting it.
- + */
- + ret = k_msgq_get(&data->lbt_msgq, &result, K_SECONDS(15));
- + }
- +
- + if (ret < 0) {
- + LOG_DBG("LBT: timeout");
- + k_mutex_lock(&data->lock, K_FOREVER);
- + sx126x_set_standby(dev, SX126X_STANDBY_RC);
- + sx126x_set_sleep(dev);
- + k_mutex_unlock(&data->lock);
- + return -ETIMEDOUT;
- + }
- +
- + return result.status;
- +
- +out_error:
- + sx126x_set_sleep(dev);
- + k_mutex_unlock(&data->lock);
- + return ret;
- +}
- +
- static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
- int8_t tx_power, uint16_t duration)
- {
- @@ -1951,6 +2166,8 @@ static int sx126x_init(const struct device *dev)
- sizeof(struct sx126x_rx_result), 1);
- k_msgq_init(&data->cad_msgq, (char *)&data->cad_result,
- sizeof(struct sx126x_cad_result), 1);
- + k_msgq_init(&data->lbt_msgq, (char *)&data->lbt_result,
- + sizeof(struct sx126x_lbt_result), 1);
- k_work_init(&data->irq_work, sx126x_irq_work_handler);
- data->dev = dev;
- atomic_set(&data->state, SX126X_STATE_IDLE);
- diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h
- index 5f22f620f35..d831d62aaa8 100644
- --- a/drivers/lora/native/sx126x/sx126x.h
- +++ b/drivers/lora/native/sx126x/sx126x.h
- @@ -20,6 +20,7 @@ enum sx126x_state {
- SX126X_STATE_RX,
- SX126X_STATE_RX_DUTY_CYCLE,
- SX126X_STATE_CAD,
- + SX126X_STATE_CAD_TX,
- };
-
- struct sx126x_tx_result {
- @@ -37,6 +38,11 @@ struct sx126x_cad_result {
- bool activity_detected;
- };
-
- +struct sx126x_lbt_result {
- + /* 1 = channel busy, nothing transmitted; 0 = sent; <0 = error */
- + int status;
- +};
- +
- struct sx126x_data {
- struct sx126x_hal_data hal;
-
- @@ -76,6 +82,17 @@ struct sx126x_data {
- lora_cad_cb cad_cb;
- void *cad_cb_user_data;
-
- + /* sx126x_lora_send_lbt() completion via message queue */
- + struct k_msgq lbt_msgq;
- + struct sx126x_lbt_result lbt_result;
- + /*
- + * Set once a send_lbt() op's CAD comes back clear and SetTx has
- + * been issued, so a caller whose CAD-phase wait times out knows
- + * the radio is already transmitting rather than aborting it - see
- + * sx126x_lora_send_lbt().
- + */
- + atomic_t lbt_committed;
- +
- /* RX duty cycle parameters (in SX126x timeout ticks) */
- struct {
- uint32_t rx_period;
- diff --git a/include/zephyr/drivers/lora/sx126x.h b/include/zephyr/drivers/lora/sx126x.h
- index 4052f86aa41..0a2dc9c13c1 100644
- --- a/include/zephyr/drivers/lora/sx126x.h
- +++ b/include/zephyr/drivers/lora/sx126x.h
- @@ -9,6 +9,7 @@
- #define ZEPHYR_INCLUDE_DRIVERS_LORA_SX126X_H_
-
- #include <zephyr/device.h>
- +#include <zephyr/kernel.h>
- #include <zephyr/sys/util.h>
- #include <stdint.h>
- #include <stdbool.h>
- @@ -51,6 +52,29 @@ extern "C" {
- */
- bool sx126x_irq_timestamp_get(const struct device *dev, uint16_t irq_bit, uint64_t *ns_out);
-
- +/**
- + * @brief Listen-before-talk send: CAD, then transmit only if clear.
- + *
- + * This issues a single CAD attempt; retry/backoff policy belongs at the
- + * caller (e.g. a TX queue).
- + *
- + * @param dev SX126x LoRa device, already configured via
- + * @ref lora_config
- + * @param data Payload to send if the channel is clear
- + * @param len Payload length
- + * @param cad_timeout Bound on the CAD phase only. Once CAD comes back
- + * clear and the radio commits to transmitting, the
- + * wait for TX completion is bounded separately (by the
- + * radio's own hardware TX timeout) so a short
- + * @p cad_timeout here can never abort an in-flight
- + * transmission.
- + * @return 1 if the channel was busy and nothing was sent, 0 if the frame
- + * was sent, or a negative error code (including -ETIMEDOUT if
- + * CAD itself never completed within @p cad_timeout)
- + */
- +int sx126x_lora_send_lbt(const struct device *dev, uint8_t *data, uint32_t len,
- + k_timeout_t cad_timeout);
- +
- #ifdef __cplusplus
- }
- #endif
- --
- 2.54.0 (Apple Git-157)
|