From 98eed4389264f4e0bce6d3a5e7dee58b5b20e424 Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 29 Aug 2025 13:48:52 -0700 Subject: [PATCH 01/10] scripts: Fix PosixPath passed to subprocess instead of string --- scripts/west_commands/packages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/west_commands/packages.py b/scripts/west_commands/packages.py index 795f3d4fe1399..9ae2c39f305e0 100644 --- a/scripts/west_commands/packages.py +++ b/scripts/west_commands/packages.py @@ -159,7 +159,7 @@ def do_run_pip(self, args, manager_args): if len(requirements) > 0: subprocess.check_call( [sys.executable, "-m", "pip", "install"] - + list(chain.from_iterable([("-r", r) for r in requirements])) + + list(chain.from_iterable([("-r", r.as_posix()) for r in requirements])) + manager_args ) else: @@ -169,4 +169,4 @@ def do_run_pip(self, args, manager_args): if len(manager_args) > 0: self.die(f'west packages pip does not support unknown arguments: "{manager_args}"') - self.inf("\n".join([f"-r {r}" for r in requirements])) + self.inf("\n".join([f"-r {r.as_posix()}" for r in requirements])) From b58d64df45782b00dc35141ba23bceefbf71a425 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Wed, 9 Apr 2025 13:59:40 -0400 Subject: [PATCH 02/10] shell: backends: Make UART shell backend line ending format configurable Add a new Kconfig option SHELL_BACKEND_SERIAL_OLF_CRLF that allows configuring the line ending format used by the shell UART backend. When enabled (default), the shell will use CR+LF (\r\n) line endings for output text. When disabled, it will use the system default (typically just LF, \n). This replaces the hardcoded SHELL_FLAG_OLF_CRLF setting with a conditional expression that selects the appropriate flag based on the new configuration option. This gives users more control over terminal output formatting when needed for specific applications or terminal emulators. --- subsys/shell/backends/Kconfig.backends | 8 ++++++++ subsys/shell/backends/shell_uart.c | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends index 13b6997947268..54617d0f60aa7 100644 --- a/subsys/shell/backends/Kconfig.backends +++ b/subsys/shell/backends/Kconfig.backends @@ -141,6 +141,14 @@ config SHELL_BACKEND_SERIAL_CHECK_DTR help Check DTR signal before TX. +config SHELL_BACKEND_SERIAL_OLF_CRLF + bool "Use CR+LF for output line endings" + default y + help + When enabled, the shell backend will use CR+LF (\r\n) for line endings + in output. When disabled, the system default line ending is used (typically + just LF, \n). This affects how the shell formats output text. + module = SHELL_BACKEND_SERIAL default-timeout = 100 source "subsys/shell/Kconfig.template.shell_log_queue_timeout" diff --git a/subsys/shell/backends/shell_uart.c b/subsys/shell/backends/shell_uart.c index 1d19ce19a3181..e8fe7feb27e03 100644 --- a/subsys/shell/backends/shell_uart.c +++ b/subsys/shell/backends/shell_uart.c @@ -520,7 +520,8 @@ SHELL_UART_DEFINE(shell_transport_uart); SHELL_DEFINE(shell_uart, CONFIG_SHELL_PROMPT_UART, &shell_transport_uart, CONFIG_SHELL_BACKEND_SERIAL_LOG_MESSAGE_QUEUE_SIZE, CONFIG_SHELL_BACKEND_SERIAL_LOG_MESSAGE_QUEUE_TIMEOUT, - SHELL_FLAG_OLF_CRLF); + IS_ENABLED(CONFIG_SHELL_BACKEND_SERIAL_OLF_CRLF) ? + SHELL_FLAG_OLF_CRLF : SHELL_FLAG_CRLF_DEFAULT); #ifdef CONFIG_MCUMGR_TRANSPORT_SHELL struct smp_shell_data *shell_uart_smp_shell_data_get_ptr(void) From 4c2e758efe3948d4b2549cb040126d80e363f168 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 6 Jan 2026 08:56:00 -0500 Subject: [PATCH 03/10] drivers: lora: Add noise floor support and RSSI callback Add noise floor measurement functionality to the SX12xx LoRa driver. This includes: - New lora_rssi.h header with noise floor API - sx12xx_lora_get_noise_floor() implementation - RSSI callback registration in Radio struct - Extension API for driver-specific noise floor implementations --- drivers/lora/loramac_node/sx127x.c | 3 + drivers/lora/loramac_node/sx12xx_common.c | 71 +++++++++++++++++++++++ drivers/lora/loramac_node/sx12xx_common.h | 2 + include/zephyr/drivers/lora_rssi.h | 44 ++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 include/zephyr/drivers/lora_rssi.h diff --git a/drivers/lora/loramac_node/sx127x.c b/drivers/lora/loramac_node/sx127x.c index d478b4081c71e..d7c73bfe7ae2a 100644 --- a/drivers/lora/loramac_node/sx127x.c +++ b/drivers/lora/loramac_node/sx127x.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -88,6 +89,7 @@ LOG_MODULE_REGISTER(sx127x, CONFIG_LORA_LOG_LEVEL); #define SX127xSetPublicNetwork SX1276SetPublicNetwork #define SX127xGetWakeupTime SX1276GetWakeupTime #define SX127xSetTxContinuousWave SX1276SetTxContinuousWave +#define SX127xReadRssi SX1276ReadRssi #else #error No SX127x instance in device tree. @@ -548,6 +550,7 @@ const struct Radio_s Radio = { .RxBoosted = NULL, .SetRxDutyCycle = NULL, .SetTxContinuousWave = SX127xSetTxContinuousWave, + .Rssi = SX127xReadRssi, }; static int sx127x_antenna_configure(void) diff --git a/drivers/lora/loramac_node/sx12xx_common.c b/drivers/lora/loramac_node/sx12xx_common.c index 25d72ff806113..83a307f69883f 100644 --- a/drivers/lora/loramac_node/sx12xx_common.c +++ b/drivers/lora/loramac_node/sx12xx_common.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -379,6 +380,76 @@ int sx12xx_lora_test_cw(const struct device *dev, uint32_t frequency, return 0; } +/** + * @brief Get RF noise floor measurement + * + * @param dev Device pointer + * @return int16_t Noise floor value in dBm + */ +int16_t sx12xx_lora_get_noise_floor(const struct device *dev) +{ + int16_t noise_floor = NOISE_FLOOR_DEFAULT_VALUE; + int32_t noise_floor_sum = 0; + int valid_measurements = 0; + + /* Ensure available, released after measurement */ + if (!modem_acquire(&dev_data)) { + LOG_ERR("Radio is busy"); + return NOISE_FLOOR_DEFAULT_VALUE; + } + + LOG_DBG("Starting noise floor measurement using Radio.Rssi()"); + + /* Configure radio for optimal noise floor measurement */ + Radio.SetModem(MODEM_FSK); + + /* Set radio to continuous reception */ + Radio.Rx(0); + + /* Take multiple measurements to get a stable reading */ + for (int i = 0; i < 5; i++) { + /* Give radio time to settle before each measurement */ + k_sleep(K_MSEC(20)); + + /* Get noise floor directly from the radio API */ + int16_t current_noise_floor = Radio.Rssi(MODEM_FSK); + LOG_DBG("Noise floor measurement %d: %d dBm", i, current_noise_floor); + + /* Filter out clearly invalid measurements */ + if (current_noise_floor < 0) { /* Only accept negative values as valid */ + noise_floor_sum += current_noise_floor; + valid_measurements++; + } else { + LOG_WRN("Ignoring invalid noise floor: %d dBm", current_noise_floor); + } + } + + /* Calculate average */ + if (valid_measurements > 0) { + noise_floor = noise_floor_sum / valid_measurements; + LOG_DBG("Average noise floor: %d dBm from %d readings", noise_floor, valid_measurements); + } else { + LOG_WRN("No valid noise floor measurements obtained"); + } + + /* Restore original radio state - no need to sleep before this */ + Radio.Sleep(); + + /* Release modem when done */ + modem_release(&dev_data); + + return noise_floor; +} + +int16_t lora_get_noise_floor(const struct device *dev) +{ + if (!dev) { + return NOISE_FLOOR_DEFAULT_VALUE; + } + + return sx12xx_lora_get_noise_floor(dev); +} + int sx12xx_init(const struct device *dev) { atomic_set(&dev_data.modem_usage, 0); diff --git a/drivers/lora/loramac_node/sx12xx_common.h b/drivers/lora/loramac_node/sx12xx_common.h index 2a26485f2c077..b11aed83bcd29 100644 --- a/drivers/lora/loramac_node/sx12xx_common.h +++ b/drivers/lora/loramac_node/sx12xx_common.h @@ -38,6 +38,8 @@ int sx12xx_lora_test_cw(const struct device *dev, uint32_t frequency, int8_t tx_power, uint16_t duration); +int16_t sx12xx_lora_get_noise_floor(const struct device *dev); + int sx12xx_init(const struct device *dev); #endif /* ZEPHYR_DRIVERS_SX12XX_COMMON_H_ */ diff --git a/include/zephyr/drivers/lora_rssi.h b/include/zephyr/drivers/lora_rssi.h new file mode 100644 index 0000000000000..d08f2a0124370 --- /dev/null +++ b/include/zephyr/drivers/lora_rssi.h @@ -0,0 +1,44 @@ +#ifndef LORA_RSSI_H_ +#define LORA_RSSI_H_ + +#include +#include + +/* Noise floor measurement constants */ +#define NOISE_FLOOR_DEFAULT_VALUE -30 /* Default to "noisy" when measurement fails */ + +/** + * @brief Function pointer type for noise floor measurement implementations + */ +typedef int16_t (*lora_api_get_noise_floor)(const struct device *dev); + +/** + * @brief Extended API functions for LoRa drivers + */ +struct lora_api_extensions { + lora_api_get_noise_floor get_noise_floor; /* Noise floor measurement function */ +}; + +/** + * @brief Get the RF noise floor measurement at the current frequency + * + * @param dev LoRa device + * @return int16_t Noise floor in dBm + */ +int16_t lora_get_noise_floor(const struct device *dev); + +/** + * @brief Register noise floor measurement function with device + * + * @param dev_ptr Pointer to the device + * @param func_ptr Pointer to the get_noise_floor implementation + */ +#define LORA_REGISTER_GET_NOISE_FLOOR(dev_ptr, func_ptr) \ + do { \ + struct sx127x_data *data = dev_ptr->data; \ + if (data) { \ + data->ext_api.get_noise_floor = func_ptr; \ + } \ + } while (0) + +#endif /* LORA_RSSI_H_ */ From 4d2ce8afe39946b57334ed4286fc67561591ce39 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 27 Jan 2026 21:42:51 -0500 Subject: [PATCH 04/10] scripts: footprint: size_report: Fix TLS symbol attribution TLS sections (e.g. .tdata/.tbss) use addresses as TLS offsets and can overlap normal VMA ranges. Avoid using TLS section address ranges for RAM bucketing and classify TLS symbols using section flags instead. Signed-off-by: Jon Ringle --- scripts/footprint/size_report | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/footprint/size_report b/scripts/footprint/size_report index 544efd492702f..f1920af2c4a1c 100755 --- a/scripts/footprint/size_report +++ b/scripts/footprint/size_report @@ -43,6 +43,7 @@ if version.parse(elftools.__version__) < version.parse('0.24'): SHF_WRITE = 0x1 SHF_ALLOC = 0x2 SHF_EXEC = 0x4 +SHF_TLS = 0x400 SHF_WRITE_ALLOC = SHF_WRITE | SHF_ALLOC SHF_ALLOC_EXEC = SHF_ALLOC | SHF_EXEC @@ -155,8 +156,28 @@ def get_symbols(elf, addr_ranges): if get_symbol_size(sym) == 0: continue - ram_sym = is_symbol_in_ranges(sym, ram_addr_ranges) - rom_sym = is_symbol_in_ranges(sym, rom_addr_ranges) + # TLS sections (e.g. .tdata/.tbss) use addresses as TLS offsets + # and can overlap normal VMA ranges. Don't classify based on + # address ranges for TLS. + sym_is_tls = False + tls_section_name = None + try: + shndx = sym['st_shndx'] + if isinstance(shndx, int): + sec = elf.get_section(shndx) + if sec['sh_flags'] & SHF_TLS: + sym_is_tls = True + tls_section_name = sec.name + except Exception: + sym_is_tls = False + tls_section_name = None + + if sym_is_tls: + ram_sym = {'name': tls_section_name} + rom_sym = None + else: + ram_sym = is_symbol_in_ranges(sym, ram_addr_ranges) + rom_sym = is_symbol_in_ranges(sym, rom_addr_ranges) # Determine the location(s) for this symbol. loc = [] @@ -232,10 +253,12 @@ def get_section_ranges(elf): sec_end = sec_start + (size - 1 if size else 0) bound = {'start': sec_start, 'end': sec_end, 'name': section.name} is_assigned = False + flags = section['sh_flags'] if section['sh_type'] == 'SHT_NOBITS': # BSS and noinit sections - ram_addr_ranges.append(bound) + if not (flags & SHF_TLS): + ram_addr_ranges.append(bound) ram_size += size total_size += size is_assigned = True @@ -243,7 +266,6 @@ def get_section_ranges(elf): elif section['sh_type'] == 'SHT_PROGBITS': # Sections to be in flash or memory - flags = section['sh_flags'] if (flags & SHF_ALLOC_EXEC) == SHF_ALLOC_EXEC: # Text section rom_addr_ranges.append(bound) @@ -259,7 +281,8 @@ def get_section_ranges(elf): # since at boot, content is copied from ROM to RAM rom_addr_ranges.append(bound) rom_size += size - ram_addr_ranges.append(bound) + if not (flags & SHF_TLS): + ram_addr_ranges.append(bound) ram_size += size total_size += size is_assigned = True From 92fd80db6279e583fd26059799554e0262310e02 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Thu, 29 Jan 2026 23:00:50 -0500 Subject: [PATCH 05/10] scripts: footprint: plot: Guard against missing total_size When total_size is missing, skip the percentage line in hover text. Signed-off-by: Jon Ringle --- scripts/footprint/plot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/footprint/plot.py b/scripts/footprint/plot.py index 9a278bbc1140d..f37e0d10ae251 100755 --- a/scripts/footprint/plot.py +++ b/scripts/footprint/plot.py @@ -51,7 +51,7 @@ def main(): with open(args.input) as f: data = json.load(f) - totalsize = data.get('total_size') + totalsize = data.get('total_size', 0) ids = [] labels = [] parents = [] @@ -75,7 +75,9 @@ def iter_node(node: dict, parent=''): parents.append(parent) values.append(node.get('size', 0)) - details = [f'percentage: {node.get("size") / totalsize:.2%}'] + details = [] + if totalsize > 0: + details.append(f'percentage: {node.get("size") / totalsize:.2%}') if 'address' in node: details.append(f'address: 0x{node.get("address"):08x}') if 'section' in node: From ec4d477ca2a0d651170a65e65ade58111155f73c Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 27 Jan 2026 21:42:52 -0500 Subject: [PATCH 06/10] scripts: footprint: plot: Increase label font size Improve readability of sunburst labels in generated interactive plot. Signed-off-by: Jon Ringle --- scripts/footprint/plot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/footprint/plot.py b/scripts/footprint/plot.py index f37e0d10ae251..a44c8c72712ef 100755 --- a/scripts/footprint/plot.py +++ b/scripts/footprint/plot.py @@ -103,6 +103,7 @@ def iter_node(node: dict, parent=''): skip_invalid=True, ) fig.update_layout(margin={'t': 0, 'l': 0, 'r': 0, 'b': 0}) + fig.update_traces(textfont=dict(size=24)) if args.html: fig.write_html(args.html, auto_open=False) From 351db5b0cc3a6abe6c895fb12b79cd73ab602464 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 26 Aug 2025 10:37:08 -0700 Subject: [PATCH 07/10] drivers: lora: sx126x: add low energy support Add optional low-power sequencing for SX126x (GPIO and SUBGHZ radio control) so the host can turn the radio off between operations. Remove debug helpers dump_gpio() and dump_rf_ctrl(). Drop gridpoint_stm32wl_subghz_radio compat; use st,stm32wl-subghz-radio only. Signed-off-by: Jon Ringle --- drivers/lora/loramac_node/sx126x.c | 69 ++++++++++++++++++++-- drivers/lora/loramac_node/sx126x_common.h | 7 +++ dts/bindings/lora/semtech,sx126x-base.yaml | 39 ++++++++++++ 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/drivers/lora/loramac_node/sx126x.c b/drivers/lora/loramac_node/sx126x.c index 6221943f5200b..e37e8ceef6568 100644 --- a/drivers/lora/loramac_node/sx126x.c +++ b/drivers/lora/loramac_node/sx126x.c @@ -50,8 +50,15 @@ static const struct sx126x_config dev_config = { #if HAVE_GPIO_RX_ENABLE .rx_enable = GPIO_DT_SPEC_INST_GET(0, rx_enable_gpios), #endif +#if HAVE_GPIO_FE_CTRL + .fe_ctrl1_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl1_enable_gpios), + .fe_ctrl2_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl2_enable_gpios), + .fe_ctrl3_enable = GPIO_DT_SPEC_INST_GET(0, fe_ctrl3_enable_gpios), +#endif + }; + static struct sx126x_data dev_data; void SX126xWaitOnBusy(void); @@ -253,29 +260,57 @@ void SX126xAntSwOff(void) #endif } +#if HAVE_GPIO_TX_ENABLE static void sx126x_set_tx_enable(int value) { -#if HAVE_GPIO_TX_ENABLE gpio_pin_set_dt(&dev_config.tx_enable, value); -#endif } +#endif +#if HAVE_GPIO_RX_ENABLE static void sx126x_set_rx_enable(int value) { -#if HAVE_GPIO_RX_ENABLE gpio_pin_set_dt(&dev_config.rx_enable, value); +} +#endif + + +static void sx126x_set_fe_ctrl(int ctrl1, int ctrl2, int ctrl3) +{ +#if HAVE_GPIO_FE_CTRL + gpio_pin_set_dt(&dev_config.fe_ctrl1_enable, ctrl1); + gpio_pin_set_dt(&dev_config.fe_ctrl2_enable, ctrl2); + gpio_pin_set_dt(&dev_config.fe_ctrl3_enable, ctrl3); #endif } + RadioOperatingModes_t SX126xGetOperatingMode(void) { return dev_data.mode; } +#if HAVE_GPIO_FE_CTRL +static const enum { + RFO_LP, + RFO_HP, +} pa_output = DT_INST_STRING_UPPER_TOKEN(0, power_amplifier_output); +#endif + +#define LORA_NODE DT_NODELABEL(lora) + + void SX126xSetOperatingMode(RadioOperatingModes_t mode) { LOG_DBG("SetOperatingMode: %s (%i)", sx126x_mode_name(mode), mode); +#if HAVE_GPIO_FE_CTRL + const int off_fe_ctrl_lines[] = DT_PROP(LORA_NODE, off_fe_ctrl_lines); + const int rx_fe_ctrl_lines[] = DT_PROP(LORA_NODE, rx_fe_ctrl_lines); + const int txhp_fe_ctrl_lines[] = DT_PROP(LORA_NODE, txhp_fe_ctrl_lines); + const int txlp_fe_ctrl_lines[] = DT_PROP(LORA_NODE, txlp_fe_ctrl_lines); +#endif + dev_data.mode = mode; /* To avoid inadvertently putting the RF switch in an @@ -284,15 +319,29 @@ void SX126xSetOperatingMode(RadioOperatingModes_t mode) */ switch (mode) { case MODE_TX: +#if HAVE_GPIO_TX_ENABLE sx126x_set_rx_enable(0); sx126x_set_tx_enable(1); +#endif +#if HAVE_GPIO_FE_CTRL + if (pa_output == RFO_LP) { + sx126x_set_fe_ctrl(txlp_fe_ctrl_lines[0], txlp_fe_ctrl_lines[1], txlp_fe_ctrl_lines[2]); + } else { + sx126x_set_fe_ctrl(txhp_fe_ctrl_lines[0], txhp_fe_ctrl_lines[1], txhp_fe_ctrl_lines[2]); + } +#endif break; case MODE_RX: case MODE_RX_DC: case MODE_CAD: +#if HAVE_GPIO_TX_ENABLE sx126x_set_tx_enable(0); sx126x_set_rx_enable(1); +#endif +#if HAVE_GPIO_FE_CTRL + sx126x_set_fe_ctrl(rx_fe_ctrl_lines[0], rx_fe_ctrl_lines[1], rx_fe_ctrl_lines[2]); +#endif break; case MODE_SLEEP: @@ -300,8 +349,13 @@ void SX126xSetOperatingMode(RadioOperatingModes_t mode) sx126x_dio1_irq_disable(&dev_data); __fallthrough; default: +#if HAVE_GPIO_TX_ENABLE sx126x_set_rx_enable(0); sx126x_set_tx_enable(0); +#endif +#if HAVE_GPIO_FE_CTRL + sx126x_set_fe_ctrl(off_fe_ctrl_lines[0], off_fe_ctrl_lines[1], off_fe_ctrl_lines[2]); +#endif break; } } @@ -437,7 +491,14 @@ static int sx126x_lora_init(const struct device *dev) if (sx12xx_configure_pin(antenna_enable, GPIO_OUTPUT_INACTIVE) || sx12xx_configure_pin(rx_enable, GPIO_OUTPUT_INACTIVE) || - sx12xx_configure_pin(tx_enable, GPIO_OUTPUT_INACTIVE)) { + sx12xx_configure_pin(tx_enable, GPIO_OUTPUT_INACTIVE) +#if HAVE_GPIO_FE_CTRL + || sx12xx_configure_pin(fe_ctrl1_enable, GPIO_OUTPUT_INACTIVE) + || sx12xx_configure_pin(fe_ctrl2_enable, GPIO_OUTPUT_INACTIVE) + || sx12xx_configure_pin(fe_ctrl3_enable, GPIO_OUTPUT_INACTIVE) +#endif + ) + { return -EIO; } diff --git a/drivers/lora/loramac_node/sx126x_common.h b/drivers/lora/loramac_node/sx126x_common.h index 81ac661859cc0..8dbe8af620f04 100644 --- a/drivers/lora/loramac_node/sx126x_common.h +++ b/drivers/lora/loramac_node/sx126x_common.h @@ -34,6 +34,7 @@ DT_INST_NODE_HAS_PROP(0, antenna_enable_gpios) #define HAVE_GPIO_TX_ENABLE DT_INST_NODE_HAS_PROP(0, tx_enable_gpios) #define HAVE_GPIO_RX_ENABLE DT_INST_NODE_HAS_PROP(0, rx_enable_gpios) +#define HAVE_GPIO_FE_CTRL (DT_INST_NODE_HAS_PROP(0, fe_ctrl1_enable_gpios) && !DT_INST_NODE_HAS_PROP(0, tx_enable_gpios)) struct sx126x_config { struct spi_dt_spec bus; @@ -46,6 +47,12 @@ struct sx126x_config { #if HAVE_GPIO_RX_ENABLE struct gpio_dt_spec rx_enable; #endif +#if HAVE_GPIO_FE_CTRL + struct gpio_dt_spec fe_ctrl3_enable; + struct gpio_dt_spec fe_ctrl2_enable; + struct gpio_dt_spec fe_ctrl1_enable; +#endif + }; struct sx126x_data { diff --git a/dts/bindings/lora/semtech,sx126x-base.yaml b/dts/bindings/lora/semtech,sx126x-base.yaml index d4d1c36b6622c..6d083dd62a80b 100644 --- a/dts/bindings/lora/semtech,sx126x-base.yaml +++ b/dts/bindings/lora/semtech,sx126x-base.yaml @@ -35,6 +35,45 @@ properties: Antenna switch RX enable GPIO. If set, the driver tracks the state of the radio and controls the RF switch. + txlp-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable transmit low-power. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + txhp-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable transmit high-power. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + rx-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to enable receive. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + off-fe-ctrl-lines: + type: array + description: | + RF front-end GPIO bit values to switch off antenna. Array + of 3 binary values corresponding to FE_CTRL1, FE_CTRL2, FE_CTRL3 + + fe-ctrl1-enable-gpios: + type: phandle-array + description: | + FE_CTRL1 signal GPIO controlling RF switch. + + fe-ctrl2-enable-gpios: + type: phandle-array + description: | + FE_CTRL2 signal GPIO controlling RF switch. + + fe-ctrl3-enable-gpios: + type: phandle-array + description: | + FE_CTRL3 signal GPIO controlling RF switch. + dio1-gpios: type: phandle-array description: | From f1b64035883d27ff10b15a5e7195eb93600e643f Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Tue, 11 Nov 2025 11:19:49 -0800 Subject: [PATCH 08/10] rtc: add subsecond support to stm32 driver Add support for RTC_ALARM_TIME_MASK_NSEC on STM32 series that implement the RTC_ALRMASSR/RTC_ALRMBSSR subsecond alarm registers. Infer whether NSEC matching was requested from the programmed MASKSS field in the alarm subsecond register to avoid storing additional alarm state. Signed-off-by: Jon Ringle --- drivers/rtc/rtc_ll_stm32.c | 181 +++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc_ll_stm32.c b/drivers/rtc/rtc_ll_stm32.c index 62f0f5a32a9a7..de8361b6d13e4 100644 --- a/drivers/rtc/rtc_ll_stm32.c +++ b/drivers/rtc/rtc_ll_stm32.c @@ -53,6 +53,16 @@ LOG_MODULE_REGISTER(rtc_stm32, CONFIG_RTC_LOG_LEVEL); #define HW_SUBSECOND_SUPPORT 1 #endif +/* STM32 devices that support subsecond alarms (require RTC_ALRMASSR/RTC_ALRMBSSR registers) */ +#if defined(CONFIG_SOC_SERIES_STM32F1X) || defined(CONFIG_SOC_SERIES_STM32F2X) \ + || defined(CONFIG_SOC_SERIES_STM32L1X) || defined(CONFIG_SOC_SERIES_STM32C0X) +/* STM32F1, STM32F2, STM32L1, and STM32C0 series do not support subsecond alarms */ +#define HW_SUBSECOND_ALARM_SUPPORT (0) +#else +/* STM32F3, STM32F4, STM32L0, STM32L4, STM32H7, STM32G0, STM32G4, STM32WL5, STM32WB, STM32U5 support subsecond alarms */ +#define HW_SUBSECOND_ALARM_SUPPORT (1) +#endif + /* RTC start time: 1st, Jan, 2000 */ #define RTC_YEAR_REF 2000 /* struct tm start time: 1st, Jan, 1900 */ @@ -94,7 +104,8 @@ LOG_MODULE_REGISTER(rtc_stm32, CONFIG_RTC_LOG_LEVEL); #define RTC_STM32_SUPPORTED_ALARM_FIELDS \ (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE \ | RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_WEEKDAY \ - | RTC_ALARM_TIME_MASK_MONTHDAY) + | RTC_ALARM_TIME_MASK_MONTHDAY \ + | (HW_SUBSECOND_ALARM_SUPPORT ? RTC_ALARM_TIME_MASK_NSEC : 0)) #define RTC_STM32_EXTI_LINE_NUM DT_INST_PROP_OR(0, alrm_exti_line, 0) @@ -701,8 +712,43 @@ static int rtc_stm32_get_time(const struct device *dev, struct rtc_time *timeptr return 0; } +#if HW_SUBSECOND_ALARM_SUPPORT +/* Subsecond conversion functions */ + +/** + * @brief Convert nanoseconds to RTC subsecond register value + * @param nsec Nanoseconds (0-999999999) + * @param sync_prescaler RTC sync prescaler value + * @return RTC subsecond register value + */ +static inline uint32_t rtc_stm32_nsec_to_subsecond(uint32_t nsec, uint32_t sync_prescaler) +{ + /* Convert nanoseconds to RTC subsecond register value + * Formula: rtc_subsecond = sync_prescaler - (nsec * (sync_prescaler + 1)) / 1000000000 + */ + uint64_t temp = (uint64_t)nsec * (sync_prescaler + 1); + return sync_prescaler - (uint32_t)(temp / 1000000000L); +} + +/** + * @brief Convert RTC subsecond register value to nanoseconds + * @param rtc_subsecond RTC subsecond register value + * @param sync_prescaler RTC sync prescaler value + * @return Nanoseconds (0-999999999) + */ +static inline uint32_t rtc_stm32_subsecond_to_nsec(uint32_t rtc_subsecond, uint32_t sync_prescaler) +{ + /* Convert RTC subsecond register value to nanoseconds + * Formula: nsec = ((sync_prescaler - rtc_subsecond) * 1000000000) / (sync_prescaler + 1) + */ + uint64_t temp = ((uint64_t)(sync_prescaler - rtc_subsecond)) * 1000000000L; + return (uint32_t)(temp / (sync_prescaler + 1)); +} +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + #ifdef STM32_RTC_ALARM_ENABLED -static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) +static inline void rtc_stm32_get_ll_alrm_time(uint16_t id, struct rtc_time *timeptr, + uint32_t sync_prescaler) { if (id == RTC_STM32_ALRM_A) { timeptr->tm_sec = bcd2bin(LL_RTC_ALMA_GetSecond(RTC)); @@ -710,6 +756,12 @@ static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) timeptr->tm_hour = bcd2bin(LL_RTC_ALMA_GetHour(RTC)); timeptr->tm_wday = bcd2bin(LL_RTC_ALMA_GetWeekDay(RTC)); timeptr->tm_mday = bcd2bin(LL_RTC_ALMA_GetDay(RTC)); +#if HW_SUBSECOND_ALARM_SUPPORT + uint32_t rtc_subsecond = LL_RTC_ALMA_GetSubSecond(RTC); + timeptr->tm_nsec = rtc_stm32_subsecond_to_nsec(rtc_subsecond, sync_prescaler); +#else + timeptr->tm_nsec = 0; +#endif return; } #if RTC_STM32_ALARMS_COUNT > 1 @@ -719,6 +771,12 @@ static void rtc_stm32_alarm_get_alrm_time(uint16_t id, struct rtc_time *timeptr) timeptr->tm_hour = bcd2bin(LL_RTC_ALMB_GetHour(RTC)); timeptr->tm_wday = bcd2bin(LL_RTC_ALMB_GetWeekDay(RTC)); timeptr->tm_mday = bcd2bin(LL_RTC_ALMB_GetDay(RTC)); +#if HW_SUBSECOND_ALARM_SUPPORT + uint32_t rtc_subsecond = LL_RTC_ALMB_GetSubSecond(RTC); + timeptr->tm_nsec = rtc_stm32_subsecond_to_nsec(rtc_subsecond, sync_prescaler); +#else + timeptr->tm_nsec = 0; +#endif } #endif /* RTC_STM32_ALARMS_COUNT > 1 */ } @@ -773,6 +831,90 @@ static inline uint16_t rtc_stm32_alarm_get_alrm_mask(uint16_t id) return zephyr_alarm_mask; } +#if HW_SUBSECOND_ALARM_SUPPORT +/* Subsecond register access functions */ + +/** + * @brief Read RTC subsecond register (RTC_SSR) + * @param rtc_subsecond Pointer to store the subsecond value + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_read_subsecond(uint32_t *rtc_subsecond) +{ + if (rtc_subsecond == NULL) { + return -EINVAL; + } + + *rtc_subsecond = LL_RTC_TIME_GetSubSecond(RTC); + return 0; +} + +/** + * @brief Read alarm subsecond register + * @param id Alarm ID (RTC_STM32_ALRM_A or RTC_STM32_ALRM_B) + * @param rtc_subsecond Pointer to store the subsecond value + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_read_alarm_subsecond(uint16_t id, uint32_t *rtc_subsecond) +{ + if (rtc_subsecond == NULL) { + return -EINVAL; + } + + if (id == RTC_STM32_ALRM_A) { + *rtc_subsecond = LL_RTC_ALMA_GetSubSecond(RTC); + } else if (id == RTC_STM32_ALRM_B) { + *rtc_subsecond = LL_RTC_ALMB_GetSubSecond(RTC); + } else { + return -EINVAL; + } + + return 0; +} + +static inline uint32_t rtc_stm32_alarm_get_subsecond_mask(uint16_t id) +{ + uint32_t reg; + + if (id == RTC_STM32_ALRM_A) { + reg = RTC->ALRMASSR; + } else if (id == RTC_STM32_ALRM_B) { + reg = RTC->ALRMBSSR; + } else { + return 0; + } + + /* + * MASKSS bitfield position/width differs across STM32 series (4..6 bits), + * but is consistently located starting at bit 24. + */ + return (reg >> 24) & 0x3F; +} + +/** + * @brief Write alarm subsecond register + * @param id Alarm ID (RTC_STM32_ALRM_A or RTC_STM32_ALRM_B) + * @param rtc_subsecond Subsecond value to write + * @return 0 on success, negative error code on failure + */ +static inline int rtc_stm32_write_alarm_subsecond(uint16_t id, uint32_t rtc_subsecond) +{ + if (id == RTC_STM32_ALRM_A) { + LL_RTC_ALMA_SetSubSecond(RTC, rtc_subsecond); + /* Compare SS[14:0] (15 bits) */ + LL_RTC_ALMA_SetSubSecondMask(RTC, 15); + } else if (id == RTC_STM32_ALRM_B) { + LL_RTC_ALMB_SetSubSecond(RTC, rtc_subsecond); + /* Compare SS[14:0] (15 bits) */ + LL_RTC_ALMB_SetSubSecondMask(RTC, 15); + } else { + return -EINVAL; + } + + return 0; +} +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + static int rtc_stm32_alarm_get_supported_fields(const struct device *dev, uint16_t id, uint16_t *mask) { @@ -795,6 +937,7 @@ static int rtc_stm32_alarm_get_time(const struct device *dev, uint16_t id, uint1 struct rtc_time *timeptr) { struct rtc_stm32_data *data = dev->data; + const struct rtc_stm32_config *cfg = dev->config; int err = 0; if ((mask == NULL) || (timeptr == NULL)) { @@ -812,12 +955,20 @@ static int rtc_stm32_alarm_get_time(const struct device *dev, uint16_t id, uint1 } memset(timeptr, -1, sizeof(struct rtc_time)); - rtc_stm32_alarm_get_alrm_time(id, timeptr); + rtc_stm32_get_ll_alrm_time(id, timeptr, cfg->sync_prescaler); *mask = rtc_stm32_alarm_get_alrm_mask(id); +#if HW_SUBSECOND_ALARM_SUPPORT + if (rtc_stm32_alarm_get_subsecond_mask(id) != 0) { + *mask |= RTC_ALARM_TIME_MASK_NSEC; + } else { + timeptr->tm_nsec = 0; + } +#else + timeptr->tm_nsec = 0; +#endif - LOG_DBG("get alarm: mday = %d, wday = %d, hour = %d, min = %d, sec = %d, " - "mask = 0x%04x", timeptr->tm_mday, timeptr->tm_wday, timeptr->tm_hour, - timeptr->tm_min, timeptr->tm_sec, *mask); + LOG_DBG("get alarm: %d/%d %d:%d:%d.%d mask=0x%04x", timeptr->tm_mday, timeptr->tm_wday, + timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, timeptr->tm_nsec, *mask); unlock: k_spin_unlock(&data->lock, key); @@ -829,6 +980,7 @@ static int rtc_stm32_alarm_set_time(const struct device *dev, uint16_t id, uint1 const struct rtc_time *timeptr) { struct rtc_stm32_data *data = dev->data; + const struct rtc_stm32_config *cfg = dev->config; struct rtc_stm32_alrm *p_rtc_alrm; int err = 0; @@ -917,6 +1069,23 @@ static int rtc_stm32_alarm_set_time(const struct device *dev, uint16_t id, uint1 /* Disable the write protection for RTC registers */ LL_RTC_DisableWriteProtection(RTC); +#if HW_SUBSECOND_ALARM_SUPPORT + /* Handle subsecond alarm setting if requested */ + if (mask & RTC_ALARM_TIME_MASK_NSEC) { + uint32_t rtc_subsecond = rtc_stm32_nsec_to_subsecond(timeptr->tm_nsec, cfg->sync_prescaler); + rtc_stm32_write_alarm_subsecond(id, rtc_subsecond); + } else { + /* Disable subsecond comparison */ + if (id == RTC_STM32_ALRM_A) { + LL_RTC_ALMA_SetSubSecond(RTC, 0); + LL_RTC_ALMA_SetSubSecondMask(RTC, 0); + } else if (id == RTC_STM32_ALRM_B) { + LL_RTC_ALMB_SetSubSecond(RTC, 0); + LL_RTC_ALMB_SetSubSecondMask(RTC, 0); + } + } +#endif /* HW_SUBSECOND_ALARM_SUPPORT */ + /* Enable Alarm */ rtc_stm32_enable_alarm(RTC, id); /* Clear Alarm flag */ From c71d2eaada2e7cb5dd8e5fd22d5aad3a2755a11d Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Thu, 5 Feb 2026 12:53:46 -0500 Subject: [PATCH 09/10] kernel: queue: guard heap functions with CONFIG_KERNEL_MEM_POOL Wrap alloc_node struct, k_free, z_thread_malloc calls, and k_queue_alloc_append/prepend APIs with #ifdef CONFIG_KERNEL_MEM_POOL. This allows building with CONFIG_KERNEL_MEM_POOL=n without requiring stub implementations for heap functions. The alloc-based queue APIs are only meaningful when heap is available. Signed-off-by: GridPoint --- kernel/queue.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/kernel/queue.c b/kernel/queue.c index af6437879944f..43c9a71f9bf64 100644 --- a/kernel/queue.c +++ b/kernel/queue.c @@ -22,15 +22,18 @@ #include #include +#ifdef CONFIG_KERNEL_MEM_POOL struct alloc_node { sys_sfnode_t node; void *data; }; +#endif /* CONFIG_KERNEL_MEM_POOL */ void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) { void *ret; +#ifdef CONFIG_KERNEL_MEM_POOL if ((node != NULL) && (sys_sfnode_flags_get(node) != (uint8_t)0)) { /* If the flag is set, then the enqueue operation for this item * did a behind-the scenes memory allocation of an alloc_node @@ -44,7 +47,9 @@ void *z_queue_node_peek(sys_sfnode_t *node, bool needs_free) if (needs_free) { k_free(anode); } - } else { + } else +#endif /* CONFIG_KERNEL_MEM_POOL */ + { /* Data was directly placed in the queue, the first word * reserved for the linked list. User mode isn't allowed to * do this, although it can get data sent this way. @@ -153,6 +158,7 @@ static int32_t queue_insert(struct k_queue *queue, void *prev, void *data, } /* Only need to actually allocate if no threads are pending */ +#ifdef CONFIG_KERNEL_MEM_POOL if (alloc) { struct alloc_node *anode; @@ -164,7 +170,10 @@ static int32_t queue_insert(struct k_queue *queue, void *prev, void *data, anode->data = data; sys_sfnode_init(&anode->node, 0x1); data = anode; - } else { + } else +#endif /* CONFIG_KERNEL_MEM_POOL */ + { + ARG_UNUSED(alloc); sys_sfnode_init(data, 0x0); } @@ -212,6 +221,7 @@ void k_queue_prepend(struct k_queue *queue, void *data) SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_queue, prepend, queue); } +#ifdef CONFIG_KERNEL_MEM_POOL int32_t z_impl_k_queue_alloc_append(struct k_queue *queue, void *data) { SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_queue, alloc_append, queue); @@ -253,6 +263,7 @@ static inline int32_t z_vrfy_k_queue_alloc_prepend(struct k_queue *queue, } #include #endif /* CONFIG_USERSPACE */ +#endif /* CONFIG_KERNEL_MEM_POOL */ int k_queue_append_list(struct k_queue *queue, void *head, void *tail) { From a422e9ea8bee4e11ce2a8be24f0ce0bebce68126 Mon Sep 17 00:00:00 2001 From: Jon Ringle Date: Wed, 25 Mar 2026 08:25:50 -0400 Subject: [PATCH 10/10] drivers: lora: sx12xx: clear async_rx_cb when cancelling async reception When lora_recv_async() is called with cb=NULL to cancel ongoing reception, modem_release() is called but async_rx_cb is left pointing to the previous callback. If a stale DIO1 work item fires after the radio has been reconfigured for TX, sx12xx_ev_rx_done() checks async_rx_cb (still set), calls Radio.Rx(0) during an active TX, and fires the stale callback. This corrupts the radio state and leaks packet buffers allocated by the callback. Clear async_rx_cb before calling modem_release() so that any stale DIO1 work item that fires after cancellation takes the synchronous path in sx12xx_ev_rx_done(), which properly checks modem_usage and bails out. Signed-off-by: Jon Ringle --- drivers/lora/loramac_node/sx12xx_common.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/lora/loramac_node/sx12xx_common.c b/drivers/lora/loramac_node/sx12xx_common.c index 83a307f69883f..4488ea54088e6 100644 --- a/drivers/lora/loramac_node/sx12xx_common.c +++ b/drivers/lora/loramac_node/sx12xx_common.c @@ -103,6 +103,12 @@ static void sx12xx_ev_rx_done(uint8_t *payload, uint16_t size, int16_t rssi, { struct k_poll_signal *sig = dev_data.operation_done; + if (!dev_data.async_rx_cb && !sig) { + LOG_WRN("RxDone event with no registered consumer " + "(modem_usage=%d)", + (int)atomic_get(&dev_data.modem_usage)); + } + /* Receiving in asynchronous mode */ if (dev_data.async_rx_cb) { /* Start receiving again */ @@ -312,6 +318,7 @@ int sx12xx_lora_recv_async(const struct device *dev, lora_recv_cb cb, void *user { /* Cancel ongoing reception */ if (cb == NULL) { + dev_data.async_rx_cb = NULL; if (!modem_release(&dev_data)) { /* Not receiving or already being stopped */ return -EINVAL;