0003-drivers-lora-native-sx126x-implement-driver-level-LB.patch 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. From 348754e83885638b36432cec7b1b70199c72f9eb Mon Sep 17 00:00:00 2001
  2. From: ZiB <zib@zi3.xyz>
  3. Date: Sat, 7 Nov 2026 15:08:00 -0800
  4. Subject: [PATCH 3/3] drivers: lora: native: sx126x: implement driver-level LBT
  5. send
  6. Adds sx126x_lora_send_lbt(), an SX126x-specific extension that does
  7. CAD then, only if clear, transmits.
  8. Uses a new SX126X_STATE_CAD_TX state covering the whole CAD->TX op.
  9. Packet params/FIFO/TX-modulation workaround are staged before SetCAD,
  10. not after - CAD doesn't touch any of those registers, so this is free.
  11. On a clear CadDone, the IRQ handler goes straight from "channel is
  12. clear" to RF-path + SetTx, skipping the sleep that CAD_ONLY's generic
  13. completion path (sx126x_handle_irq_cad_done()) otherwise always takes.
  14. The two outcomes (busy vs. sent) are delivered over a new dedicated
  15. lbt_msgq rather than overloading tx_msgq/cad_msgq, since only this
  16. state's completion needs to carry both. A caller's cad_timeout bounds
  17. only the CAD phase: lbt_committed tracks whether CAD already went
  18. clear and SetTx was issued, so a timeout arriving after that point
  19. waits out the actual transmission (bounded by SetTx's own 10s
  20. hardware timeout) instead of aborting an in-flight TX.
  21. Retry/backoff policy is deliberately not pulled into the driver.
  22. CAD exit mode stays CAD_ONLY: the SX126x has no CAD-to-TX exit mode in
  23. hardware (only CAD-to-RX), so this is done by skipping the driver's
  24. own sleep rather than anything the radio does autonomously.
  25. Signed-off-by: ZiB <zib@zi3.xyz>
  26. ---
  27. drivers/lora/native/sx126x/sx126x.c | 255 +++++++++++++++++++++++++--
  28. drivers/lora/native/sx126x/sx126x.h | 17 ++
  29. include/zephyr/drivers/lora/sx126x.h | 24 +++
  30. 3 files changed, 277 insertions(+), 19 deletions(-)
  31. diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
  32. index 8ba8416ed0c..16037a3714d 100644
  33. --- a/drivers/lora/native/sx126x/sx126x.c
  34. +++ b/drivers/lora/native/sx126x/sx126x.c
  35. @@ -817,9 +817,24 @@ static int sx126x_ensure_ready(const struct device *dev)
  36. static void sx126x_handle_irq_tx_done(const struct device *dev)
  37. {
  38. struct sx126x_data *data = dev->data;
  39. - struct sx126x_tx_result result = { .status = 0 };
  40. LOG_DBG("TX done");
  41. +
  42. + /* A send_lbt() TX-after-clear-CAD never changes data->state away
  43. + * from SX126X_STATE_CAD_TX (see sx126x_handle_irq_cad_done_lbt()),
  44. + * so that state alone tells a TX_DONE here apart from one started
  45. + * by lora_send()/lora_send_async().
  46. + */
  47. + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
  48. + struct sx126x_lbt_result result = { .status = 0 };
  49. +
  50. + sx126x_set_sleep(dev);
  51. + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
  52. + return;
  53. + }
  54. +
  55. + struct sx126x_tx_result result = { .status = 0 };
  56. +
  57. sx126x_set_sleep(dev);
  58. if (data->tx_async_signal != NULL) {
  59. @@ -905,14 +920,61 @@ out:
  60. }
  61. }
  62. +/*
  63. + * CadDone branch for a send_lbt() op (data->state == SX126X_STATE_CAD_TX).
  64. + * On activity, this is the whole op's result: sleep and report busy. On a
  65. + * clear channel, sx126x_lora_send_lbt() already staged packet params/FIFO/
  66. + * TX-modulation workaround before issuing SetCAD (none of those registers
  67. + * matter to CAD) - so the only things left between "channel is clear" and
  68. + * "radio is transmitting" are RF-path + SetTx, issued right here, with no
  69. + * intervening sleep/wake round-trip. data->state is deliberately left at
  70. + * SX126X_STATE_CAD_TX (not folded back to SLEEP, not advanced to
  71. + * SX126X_STATE_TX) so sx126x_handle_irq_tx_done()/sx126x_handle_irq_timeout()
  72. + * can tell the eventual TX_DONE/timeout apart from one started by
  73. + * lora_send()/lora_send_async() and route it back to lbt_msgq instead of
  74. + * tx_msgq.
  75. + */
  76. +static void sx126x_handle_irq_cad_done_lbt(const struct device *dev, bool activity_detected)
  77. +{
  78. + struct sx126x_data *data = dev->data;
  79. + struct sx126x_lbt_result result;
  80. + int ret;
  81. +
  82. + if (activity_detected) {
  83. + LOG_DBG("LBT: channel busy, not transmitting");
  84. + sx126x_set_sleep(dev);
  85. + result.status = 1;
  86. + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
  87. + return;
  88. + }
  89. +
  90. + sx126x_set_rf_path(dev, true, true);
  91. +
  92. + ret = sx126x_set_tx(dev, 10000);
  93. + if (ret < 0) {
  94. + LOG_ERR("LBT: SetTx failed: %d", ret);
  95. + sx126x_set_sleep(dev);
  96. + result.status = ret;
  97. + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
  98. + return;
  99. + }
  100. +
  101. + atomic_set(&data->lbt_committed, 1);
  102. +}
  103. +
  104. static void sx126x_handle_irq_cad_done(const struct device *dev, uint16_t irq_status)
  105. {
  106. struct sx126x_data *data = dev->data;
  107. - struct sx126x_cad_result result = {
  108. - .activity_detected = (irq_status & SX126X_IRQ_CAD_ACTIVITY_DETECTED) != 0,
  109. - };
  110. + bool activity_detected = (irq_status & SX126X_IRQ_CAD_ACTIVITY_DETECTED) != 0;
  111. - LOG_DBG("CAD done: activity=%d", result.activity_detected);
  112. + LOG_DBG("CAD done: activity=%d", activity_detected);
  113. +
  114. + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
  115. + sx126x_handle_irq_cad_done_lbt(dev, activity_detected);
  116. + return;
  117. + }
  118. +
  119. + struct sx126x_cad_result result = { .activity_detected = activity_detected };
  120. /* CAD_ONLY already left the chip in STDBY_RC; this just folds it
  121. * back into the driver's own rest state.
  122. @@ -947,6 +1009,18 @@ static void sx126x_handle_irq_timeout(const struct device *dev)
  123. return;
  124. }
  125. + if (atomic_get(&data->state) == SX126X_STATE_CAD_TX) {
  126. + /* SetTx's own 10s HW timeout, firing on a send_lbt() TX that
  127. + * committed after a clear CAD - see
  128. + * sx126x_handle_irq_cad_done_lbt().
  129. + */
  130. + struct sx126x_lbt_result result = { .status = -ETIMEDOUT };
  131. +
  132. + sx126x_set_sleep(dev);
  133. + k_msgq_put(&data->lbt_msgq, &result, K_NO_WAIT);
  134. + return;
  135. + }
  136. +
  137. sx126x_set_sleep(dev);
  138. if (data->tx_async_signal != NULL) {
  139. @@ -1179,6 +1253,33 @@ out:
  140. return ret;
  141. }
  142. +/*
  143. + * Packet params + FIFO + TX-modulation workaround: everything a
  144. + * transmission needs besides RF-path and SetTx themselves. Shared by
  145. + * sx126x_lora_send_async() and sx126x_lora_send_lbt(), the latter calling
  146. + * it *before* SetCAD so that, once CAD comes back clear, only RF-path and
  147. + * SetTx stand between CadDone and a transmitting radio - see
  148. + * sx126x_handle_irq_cad_done_lbt().
  149. + */
  150. +static int sx126x_stage_tx(const struct device *dev, uint8_t *data_buf, uint32_t data_len)
  151. +{
  152. + struct sx126x_data *data = dev->data;
  153. + int ret;
  154. +
  155. + ret = sx126x_set_configured_packet_params(dev, data_len);
  156. + if (ret < 0) {
  157. + return ret;
  158. + }
  159. +
  160. + ret = sx126x_hal_write_buffer(dev, 0x00, data_buf, data_len);
  161. + if (ret < 0) {
  162. + return ret;
  163. + }
  164. +
  165. + return sx126x_apply_tx_modulation_workaround(
  166. + dev, !SX126X_IS_GFSK(data) && data->config.bandwidth == BW_500_KHZ);
  167. +}
  168. +
  169. static int sx126x_lora_send_async(const struct device *dev,
  170. uint8_t *data_buf, uint32_t data_len,
  171. struct k_poll_signal *async)
  172. @@ -1223,20 +1324,7 @@ static int sx126x_lora_send_async(const struct device *dev,
  173. data->tx_async_signal = async;
  174. k_msgq_purge(&data->tx_msgq);
  175. - /* Set packet parameters */
  176. - ret = sx126x_set_configured_packet_params(dev, data_len);
  177. - if (ret < 0) {
  178. - goto out_error;
  179. - }
  180. -
  181. - /* Write payload to buffer */
  182. - ret = sx126x_hal_write_buffer(dev, 0x00, data_buf, data_len);
  183. - if (ret < 0) {
  184. - goto out_error;
  185. - }
  186. -
  187. - ret = sx126x_apply_tx_modulation_workaround(
  188. - dev, !SX126X_IS_GFSK(data) && data->config.bandwidth == BW_500_KHZ);
  189. + ret = sx126x_stage_tx(dev, data_buf, data_len);
  190. if (ret < 0) {
  191. goto out_error;
  192. }
  193. @@ -1847,6 +1935,133 @@ static int sx126x_lora_cad_async(const struct device *dev, lora_cad_cb cb, void
  194. return 0;
  195. }
  196. +/*
  197. + * Listen-before-talk, done in the driver so the gap between "channel is
  198. + * clear" and "radio is transmitting" is just RF-path + SetTx, with no
  199. + * sleep/wake round-trip in between (see sx126x_handle_irq_cad_done_lbt()
  200. + * and sx126x_stage_tx()). Not part of the generic lora.h API - exposed via
  201. + * zephyr/drivers/lora/sx126x.h, same as the other SX126x-specific
  202. + * extensions.
  203. + *
  204. + * @p cad_timeout bounds only the CAD phase. Once CAD comes back clear and
  205. + * the radio commits to transmitting, the wait for TX_DONE is bounded
  206. + * separately (by SetTx's own 10s hardware timeout) so a short
  207. + * @p cad_timeout can't abort an in-flight transmission - see
  208. + * data->lbt_committed below.
  209. + *
  210. + * @return 1 if the channel was busy (nothing was sent), 0 if the frame
  211. + * was sent, or a negative error code.
  212. + */
  213. +int sx126x_lora_send_lbt(const struct device *dev, uint8_t *data_buf, uint32_t data_len,
  214. + k_timeout_t cad_timeout)
  215. +{
  216. + struct sx126x_data *data = dev->data;
  217. + struct sx126x_cad_defaults defaults;
  218. + enum lora_cad_symbol_num symbol_num;
  219. + uint8_t det_peak, det_min, symbol_num_reg;
  220. + struct sx126x_lbt_result result;
  221. + int ret;
  222. +
  223. + if (!data->config_valid) {
  224. + LOG_ERR("Not configured");
  225. + return -EINVAL;
  226. + }
  227. +
  228. + if (SX126X_IS_GFSK(data)) {
  229. + LOG_ERR("CAD is a LoRa-only operation");
  230. + return -ENOTSUP;
  231. + }
  232. +
  233. + if (data_len > SX126X_MAX_PAYLOAD_LEN) {
  234. + LOG_ERR("Payload too long: %u", data_len);
  235. + return -EINVAL;
  236. + }
  237. +
  238. + symbol_num = data->config.cad.symbol_num;
  239. + det_peak = data->config.cad.detection_peak;
  240. + det_min = data->config.cad.detection_minimum;
  241. +
  242. + if (symbol_num == 0 || det_peak == 0 || det_min == 0) {
  243. + ret = sx126x_cad_lookup_defaults(data->config.bandwidth, data->config.datarate,
  244. + &defaults);
  245. + if (ret < 0) {
  246. + LOG_ERR("No CAD defaults for this bandwidth/SF; "
  247. + "set cad.symbol_num/detection_peak/detection_minimum explicitly");
  248. + return ret;
  249. + }
  250. + symbol_num = symbol_num ? symbol_num : defaults.symbol_num;
  251. + det_peak = det_peak ? det_peak : defaults.det_peak;
  252. + det_min = det_min ? det_min : defaults.det_min;
  253. + }
  254. +
  255. + ret = sx126x_cad_symbol_num_to_reg(symbol_num, &symbol_num_reg);
  256. + if (ret < 0) {
  257. + LOG_ERR("Invalid CAD symbol count: %d", symbol_num);
  258. + return ret;
  259. + }
  260. +
  261. + if (!atomic_cas(&data->state, SX126X_REST_STATE, SX126X_STATE_CAD_TX)) {
  262. + LOG_ERR("Busy");
  263. + return -EBUSY;
  264. + }
  265. +
  266. + k_mutex_lock(&data->lock, K_FOREVER);
  267. + k_msgq_purge(&data->lbt_msgq);
  268. + atomic_set(&data->lbt_committed, 0);
  269. +
  270. + ret = sx126x_ensure_ready(dev);
  271. + if (ret < 0) {
  272. + goto out_error;
  273. + }
  274. +
  275. + /* Stage the payload now, while the chip is awake in STDBY_RC and
  276. + * before SetCAD - CAD doesn't touch packet params/FIFO/TX-mod, so
  277. + * none of this needs to happen again after CadDone.
  278. + */
  279. + ret = sx126x_stage_tx(dev, data_buf, data_len);
  280. + if (ret < 0) {
  281. + goto out_error;
  282. + }
  283. +
  284. + ret = sx126x_set_cad_params(dev, symbol_num_reg, det_peak, det_min,
  285. + SX126X_CAD_EXIT_MODE_CAD_ONLY, 0);
  286. + if (ret < 0) {
  287. + goto out_error;
  288. + }
  289. +
  290. + ret = sx126x_set_cad(dev);
  291. + if (ret < 0) {
  292. + goto out_error;
  293. + }
  294. +
  295. + k_mutex_unlock(&data->lock);
  296. +
  297. + ret = k_msgq_get(&data->lbt_msgq, &result, cad_timeout);
  298. + if (ret < 0 && atomic_get(&data->lbt_committed)) {
  299. + /* CAD already came back clear and TX is underway; the short
  300. + * CAD-phase wait above isn't the right bound for that - wait
  301. + * out the transmission itself instead of aborting it.
  302. + */
  303. + ret = k_msgq_get(&data->lbt_msgq, &result, K_SECONDS(15));
  304. + }
  305. +
  306. + if (ret < 0) {
  307. + LOG_DBG("LBT: timeout");
  308. + k_mutex_lock(&data->lock, K_FOREVER);
  309. + sx126x_set_standby(dev, SX126X_STANDBY_RC);
  310. + sx126x_set_sleep(dev);
  311. + k_mutex_unlock(&data->lock);
  312. + return -ETIMEDOUT;
  313. + }
  314. +
  315. + return result.status;
  316. +
  317. +out_error:
  318. + sx126x_set_sleep(dev);
  319. + k_mutex_unlock(&data->lock);
  320. + return ret;
  321. +}
  322. +
  323. static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
  324. int8_t tx_power, uint16_t duration)
  325. {
  326. @@ -1951,6 +2166,8 @@ static int sx126x_init(const struct device *dev)
  327. sizeof(struct sx126x_rx_result), 1);
  328. k_msgq_init(&data->cad_msgq, (char *)&data->cad_result,
  329. sizeof(struct sx126x_cad_result), 1);
  330. + k_msgq_init(&data->lbt_msgq, (char *)&data->lbt_result,
  331. + sizeof(struct sx126x_lbt_result), 1);
  332. k_work_init(&data->irq_work, sx126x_irq_work_handler);
  333. data->dev = dev;
  334. atomic_set(&data->state, SX126X_STATE_IDLE);
  335. diff --git a/drivers/lora/native/sx126x/sx126x.h b/drivers/lora/native/sx126x/sx126x.h
  336. index 5f22f620f35..d831d62aaa8 100644
  337. --- a/drivers/lora/native/sx126x/sx126x.h
  338. +++ b/drivers/lora/native/sx126x/sx126x.h
  339. @@ -20,6 +20,7 @@ enum sx126x_state {
  340. SX126X_STATE_RX,
  341. SX126X_STATE_RX_DUTY_CYCLE,
  342. SX126X_STATE_CAD,
  343. + SX126X_STATE_CAD_TX,
  344. };
  345. struct sx126x_tx_result {
  346. @@ -37,6 +38,11 @@ struct sx126x_cad_result {
  347. bool activity_detected;
  348. };
  349. +struct sx126x_lbt_result {
  350. + /* 1 = channel busy, nothing transmitted; 0 = sent; <0 = error */
  351. + int status;
  352. +};
  353. +
  354. struct sx126x_data {
  355. struct sx126x_hal_data hal;
  356. @@ -76,6 +82,17 @@ struct sx126x_data {
  357. lora_cad_cb cad_cb;
  358. void *cad_cb_user_data;
  359. + /* sx126x_lora_send_lbt() completion via message queue */
  360. + struct k_msgq lbt_msgq;
  361. + struct sx126x_lbt_result lbt_result;
  362. + /*
  363. + * Set once a send_lbt() op's CAD comes back clear and SetTx has
  364. + * been issued, so a caller whose CAD-phase wait times out knows
  365. + * the radio is already transmitting rather than aborting it - see
  366. + * sx126x_lora_send_lbt().
  367. + */
  368. + atomic_t lbt_committed;
  369. +
  370. /* RX duty cycle parameters (in SX126x timeout ticks) */
  371. struct {
  372. uint32_t rx_period;
  373. diff --git a/include/zephyr/drivers/lora/sx126x.h b/include/zephyr/drivers/lora/sx126x.h
  374. index 4052f86aa41..0a2dc9c13c1 100644
  375. --- a/include/zephyr/drivers/lora/sx126x.h
  376. +++ b/include/zephyr/drivers/lora/sx126x.h
  377. @@ -9,6 +9,7 @@
  378. #define ZEPHYR_INCLUDE_DRIVERS_LORA_SX126X_H_
  379. #include <zephyr/device.h>
  380. +#include <zephyr/kernel.h>
  381. #include <zephyr/sys/util.h>
  382. #include <stdint.h>
  383. #include <stdbool.h>
  384. @@ -51,6 +52,29 @@ extern "C" {
  385. */
  386. bool sx126x_irq_timestamp_get(const struct device *dev, uint16_t irq_bit, uint64_t *ns_out);
  387. +/**
  388. + * @brief Listen-before-talk send: CAD, then transmit only if clear.
  389. + *
  390. + * This issues a single CAD attempt; retry/backoff policy belongs at the
  391. + * caller (e.g. a TX queue).
  392. + *
  393. + * @param dev SX126x LoRa device, already configured via
  394. + * @ref lora_config
  395. + * @param data Payload to send if the channel is clear
  396. + * @param len Payload length
  397. + * @param cad_timeout Bound on the CAD phase only. Once CAD comes back
  398. + * clear and the radio commits to transmitting, the
  399. + * wait for TX completion is bounded separately (by the
  400. + * radio's own hardware TX timeout) so a short
  401. + * @p cad_timeout here can never abort an in-flight
  402. + * transmission.
  403. + * @return 1 if the channel was busy and nothing was sent, 0 if the frame
  404. + * was sent, or a negative error code (including -ETIMEDOUT if
  405. + * CAD itself never completed within @p cad_timeout)
  406. + */
  407. +int sx126x_lora_send_lbt(const struct device *dev, uint8_t *data, uint32_t len,
  408. + k_timeout_t cad_timeout);
  409. +
  410. #ifdef __cplusplus
  411. }
  412. #endif
  413. --
  414. 2.54.0 (Apple Git-157)