From a80f451362dc9f9055d840487f0af945d05026c5 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 8 Apr 2026 13:27:30 +0200 Subject: [PATCH 1/8] fix(lp_core): fix LP UART init parameter validation Align data_bits, flow_ctrl, and rx_flow_ctrl_thresh checks with uart_param_config(). Made-with: Cursor --- components/ulp/lp_core/lp_core_uart.c | 6 ++--- .../main/test_lp_core_uart.c | 22 ++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/components/ulp/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core_uart.c index 5bd7bc97263..436f4809729 100644 --- a/components/ulp/lp_core/lp_core_uart.c +++ b/components/ulp/lp_core/lp_core_uart.c @@ -33,9 +33,9 @@ static esp_err_t lp_core_uart_param_config(const lp_core_uart_cfg_t *cfg) esp_err_t ret = ESP_OK; /* Argument sanity check */ - if ((cfg->uart_proto_cfg.rx_flow_ctrl_thresh > SOC_LP_UART_FIFO_LEN) || - (cfg->uart_proto_cfg.flow_ctrl > UART_HW_FLOWCTRL_MAX) || - (cfg->uart_proto_cfg.data_bits > UART_DATA_BITS_MAX)) { + if ((cfg->uart_proto_cfg.rx_flow_ctrl_thresh >= SOC_LP_UART_FIFO_LEN) || + (cfg->uart_proto_cfg.flow_ctrl >= UART_HW_FLOWCTRL_MAX) || + (cfg->uart_proto_cfg.data_bits >= UART_DATA_BITS_MAX)) { // Invalid config return ESP_ERR_INVALID_ARG; } diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c index 25234f94419..488ff290a05 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -79,6 +79,26 @@ TEST_CASE("LP-Core LP-UART initialization test", "[lp_core]") #endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ } +TEST_CASE("LP-Core LP-UART data_bits parameter validation", "[lp_core]") +{ + /* Valid data_bits values (UART_DATA_5_BITS=0 through UART_DATA_8_BITS=3) must succeed */ + ESP_LOGI(TAG, "Verifying LP UART data_bits valid range"); + const uart_word_length_t valid_bits[] = { + UART_DATA_5_BITS, UART_DATA_6_BITS, UART_DATA_7_BITS, UART_DATA_8_BITS, + }; + for (int i = 0; i < (int)(sizeof(valid_bits) / sizeof(valid_bits[0])); i++) { + lp_core_uart_cfg_t cfg = LP_CORE_UART_DEFAULT_CONFIG(); + cfg.uart_proto_cfg.data_bits = valid_bits[i]; + TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(&cfg)); + } + + /* UART_DATA_BITS_MAX (=4) is not a valid word length and must be rejected */ + ESP_LOGI(TAG, "Verifying LP UART data_bits = UART_DATA_BITS_MAX is rejected"); + lp_core_uart_cfg_t cfg_max = LP_CORE_UART_DEFAULT_CONFIG(); + cfg_max.uart_proto_cfg.data_bits = UART_DATA_BITS_MAX; + TEST_ASSERT_NOT_EQUAL(ESP_OK, lp_core_uart_init(&cfg_max)); +} + /* LP UART default config */ static lp_core_uart_cfg_t lp_uart_cfg = LP_CORE_UART_DEFAULT_CONFIG(); From eb6ffbf1eed88667b6446341533ea10688ce899c Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 13 Apr 2026 11:21:55 +0200 Subject: [PATCH 2/8] fix(lp_core): fix LP UART IOMUX pin not bypassing LP GPIO Matrix for RX On chips with SOC_LP_GPIO_MATRIX_SUPPORTED (esp32p4, esp32s31), when the default IOMUX pin is used for LP UART, calling rtc_gpio_iomux_func_sel() alone only selects the IOMUX function on the pad side but does not set sig_in_sel=0 on the peripheral side. This leaves the LP UART RX input still reading from the LP GPIO Matrix (where no signal is connected), causing RX to receive nothing. Apply the same fix that was already in the HP UART driver: use rtc_gpio_iomux_input() / rtc_gpio_iomux_output() which additionally configure the peripheral to bypass the LP GPIO Matrix for IOMUX pins. --- components/ulp/lp_core/lp_core_uart.c | 29 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/components/ulp/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core_uart.c index 436f4809729..bd38bd1254a 100644 --- a/components/ulp/lp_core/lp_core_uart.c +++ b/components/ulp/lp_core/lp_core_uart.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -91,22 +91,31 @@ static esp_err_t lp_uart_config_io(gpio_num_t pin, rtc_gpio_mode_t direction, ui return ESP_FAIL; } - /* Set LP_IO direction */ - ret = rtc_gpio_set_direction(pin, direction); - if (ret != ESP_OK) { - return ESP_FAIL; - } - /* Connect pins */ const uart_periph_sig_t *upin = &uart_periph_signal[LP_UART_PORT_NUM].pins[idx]; #if !SOC_LP_GPIO_MATRIX_SUPPORTED - /* When LP_IO Matrix is not support, LP_IO Mux must be connected to the pins */ - ret = rtc_gpio_iomux_func_sel(pin, upin->iomux_func); + /* On non-matrix chips, LP UART pins are always the default IOMUX pins. + * Use the all-in-one rtc_gpio_iomux APIs which handle func sel and direction. */ + if (direction == RTC_GPIO_MODE_OUTPUT_ONLY) { + ret = rtc_gpio_iomux_output(pin, upin->iomux_func); + } else { + ret = rtc_gpio_iomux_input(pin, upin->iomux_func, UART_PERIPH_SIGNAL(LP_UART_PORT_NUM, idx)); + } #else /* If the configured pin is the default LP_IO Mux pin for LP UART, then set the LP_IO MUX function */ if (upin->default_gpio == pin) { - ret = rtc_gpio_iomux_func_sel(pin, upin->iomux_func); + /* rtc_gpio_iomux_input/output are all-in-one APIs that handle func sel, + * direction, and LP GPIO Matrix bypass in a single call. */ + if (direction == RTC_GPIO_MODE_OUTPUT_ONLY) { + ret = rtc_gpio_iomux_output(pin, upin->iomux_func); + } else { + ret = rtc_gpio_iomux_input(pin, upin->iomux_func, UART_PERIPH_SIGNAL(LP_UART_PORT_NUM, idx)); + } } else { + ret = rtc_gpio_set_direction(pin, direction); + if (ret != ESP_OK) { + return ESP_FAIL; + } /* Select FUNC1 for LP_IO Matrix */ ret = rtc_gpio_iomux_func_sel(pin, 1); /* Connect the LP_IO to the LP UART peripheral signal */ From 952dca66bf9e90a8c3aa910227cb9be23399b9b9 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 13 Apr 2026 15:21:44 +0200 Subject: [PATCH 3/8] fix(lp_core): skip LP UART RTS/CTS pin setup when flow control is disabled lp_core_uart_set_pin() unconditionally configured the RTS and CTS GPIO pins even when flow control was disabled. Only configure the RTS pin when UART_HW_FLOWCTRL_RTS is set, and the CTS pin when UART_HW_FLOWCTRL_CTS is set. --- components/ulp/lp_core/lp_core_uart.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/ulp/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core_uart.c index bd38bd1254a..95933304b6d 100644 --- a/components/ulp/lp_core/lp_core_uart.c +++ b/components/ulp/lp_core/lp_core_uart.c @@ -154,10 +154,13 @@ static esp_err_t lp_core_uart_set_pin(const lp_core_uart_cfg_t *cfg) ret = lp_uart_config_io(cfg->uart_pin_cfg.tx_io_num, RTC_GPIO_MODE_OUTPUT_ONLY, SOC_UART_PERIPH_SIGNAL_TX); /* Configure Rx Pin */ ret = lp_uart_config_io(cfg->uart_pin_cfg.rx_io_num, RTC_GPIO_MODE_INPUT_ONLY, SOC_UART_PERIPH_SIGNAL_RX); - /* Configure RTS Pin */ - ret = lp_uart_config_io(cfg->uart_pin_cfg.rts_io_num, RTC_GPIO_MODE_OUTPUT_ONLY, SOC_UART_PERIPH_SIGNAL_RTS); - /* Configure CTS Pin */ - ret = lp_uart_config_io(cfg->uart_pin_cfg.cts_io_num, RTC_GPIO_MODE_INPUT_ONLY, SOC_UART_PERIPH_SIGNAL_CTS); + /* Configure RTS/CTS only when hardware flow control is enabled */ + if (cfg->uart_proto_cfg.flow_ctrl & UART_HW_FLOWCTRL_RTS) { + ret = lp_uart_config_io(cfg->uart_pin_cfg.rts_io_num, RTC_GPIO_MODE_OUTPUT_ONLY, SOC_UART_PERIPH_SIGNAL_RTS); + } + if (cfg->uart_proto_cfg.flow_ctrl & UART_HW_FLOWCTRL_CTS) { + ret = lp_uart_config_io(cfg->uart_pin_cfg.cts_io_num, RTC_GPIO_MODE_INPUT_ONLY, SOC_UART_PERIPH_SIGNAL_CTS); + } return ret; } From f6c7def5430ef6a1d448c294890b044247366002 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 13 Apr 2026 12:40:17 +0200 Subject: [PATCH 4/8] test(lp_core): extend LP UART multi-device test coverage Refactor the LP UART multi-device tests to use a single parameterised helper pair (test_lp_uart_write_cfg / test_lp_uart_read_cfg) instead of one-off functions per config, and extend coverage to: - write and read tests for 5-, 6-, 7-bit and even-parity configurations - negative tests: word-length mismatch (FRAM_ERR recovery on LP side, garbled receive on HP side) - LP GPIO Matrix routing tests (SOC_LP_GPIO_MATRIX_SUPPORTED chips only): swaps the default TX/RX GPIO numbers so both pins go through the LP GPIO Matrix, covering the lp_gpio_connect_in/out_signal() branch of lp_uart_config_io() that was previously untested at the data-transfer level; the same physical cross-wiring as all other LP UART tests is reused Add rtc_gpio_deinit() cleanup at the end of single-board LP UART tests so configured pins are returned to HP/digital mode and do not interfere with subsequent tests that may reuse the same GPIOs. --- .../main/test_lp_core_uart.c | 1133 ++++++++++------- .../pytest_lp_core_basic.py | 26 +- 2 files changed, 666 insertions(+), 493 deletions(-) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c index 488ff290a05..39a3517b886 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c @@ -18,6 +18,7 @@ #include "esp_log.h" #include "driver/uart.h" +#include "driver/rtc_io.h" #include "soc/soc_caps.h" #if SOC_LIGHT_SLEEP_SUPPORTED #include "esp_sleep.h" @@ -26,36 +27,39 @@ extern const uint8_t lp_core_main_uart_bin_start[] asm("_binary_lp_core_test_app_uart_bin_start"); extern const uint8_t lp_core_main_uart_bin_end[] asm("_binary_lp_core_test_app_uart_bin_end"); -static const char* TAG = "lp_core_uart_test"; +static const char *TAG = "lp_core_uart_test"; -static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t *cfg, + const uint8_t *firmware_start, + const uint8_t *firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, (firmware_end - firmware_start)) == ESP_OK); - TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); - } +/* ========================================================================= + * Single-device: initialisation and parameter validation + * ========================================================================= */ + TEST_CASE("LP-Core LP-UART initialization test", "[lp_core]") { - /* Default UART configuration must be successful */ + /* Default UART configuration must succeed */ ESP_LOGI(TAG, "Verifying default LP UART configuration"); lp_core_uart_cfg_t cfg = LP_CORE_UART_DEFAULT_CONFIG(); TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg)); - /* NULL configuration should result in an error */ + /* NULL configuration must fail */ ESP_LOGI(TAG, "Verifying NULL configuration"); TEST_ASSERT(ESP_OK != lp_core_uart_init(NULL)); - /* RX Flow control must be less than SOC_LP_UART_FIFO_LEN */ + /* RX flow-control threshold > FIFO length must fail */ ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Rx Flow Control Threshold"); lp_core_uart_cfg_t cfg1 = LP_CORE_UART_DEFAULT_CONFIG(); cfg1.uart_proto_cfg.rx_flow_ctrl_thresh = SOC_LP_UART_FIFO_LEN + 1; TEST_ASSERT(ESP_OK != lp_core_uart_init(&cfg1)); #if !SOC_LP_GPIO_MATRIX_SUPPORTED - /* If LP_GPIO Matrix is not supported then the UART pins must be fixed */ ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Tx IO pin"); lp_core_uart_cfg_t cfg2 = LP_CORE_UART_DEFAULT_CONFIG(); cfg2.uart_pin_cfg.tx_io_num++; @@ -66,22 +70,25 @@ TEST_CASE("LP-Core LP-UART initialization test", "[lp_core]") cfg3.uart_pin_cfg.rx_io_num--; TEST_ASSERT(ESP_OK != lp_core_uart_init(&cfg3)); #else - /* When LP_GPIO Matrix is supported then any valid LP_IO should be configurable */ - ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Tx IO pin"); + ESP_LOGI(TAG, "Verifying LP UART configuration with non-default Tx IO pin"); lp_core_uart_cfg_t cfg4 = LP_CORE_UART_DEFAULT_CONFIG(); cfg4.uart_pin_cfg.tx_io_num++; TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg4)); - ESP_LOGI(TAG, "Verifying LP UART configuration with incorrect Rx IO pin"); + ESP_LOGI(TAG, "Verifying LP UART configuration with non-default Rx IO pin"); lp_core_uart_cfg_t cfg5 = LP_CORE_UART_DEFAULT_CONFIG(); cfg5.uart_pin_cfg.rx_io_num--; TEST_ASSERT(ESP_OK == lp_core_uart_init(&cfg5)); #endif /* !SOC_LP_GPIO_MATRIX_SUPPORTED */ + + /* Deinit LP GPIO pins so they don't remain in RTC/LP mode */ + rtc_gpio_deinit(LP_UART_DEFAULT_TX_GPIO_NUM); + rtc_gpio_deinit(LP_UART_DEFAULT_RX_GPIO_NUM); } TEST_CASE("LP-Core LP-UART data_bits parameter validation", "[lp_core]") { - /* Valid data_bits values (UART_DATA_5_BITS=0 through UART_DATA_8_BITS=3) must succeed */ + /* All four valid word lengths must succeed */ ESP_LOGI(TAG, "Verifying LP UART data_bits valid range"); const uart_word_length_t valid_bits[] = { UART_DATA_5_BITS, UART_DATA_6_BITS, UART_DATA_7_BITS, UART_DATA_8_BITS, @@ -92,32 +99,120 @@ TEST_CASE("LP-Core LP-UART data_bits parameter validation", "[lp_core]") TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(&cfg)); } - /* UART_DATA_BITS_MAX (=4) is not a valid word length and must be rejected */ + /* UART_DATA_BITS_MAX (=4) is the sentinel value and must be rejected */ ESP_LOGI(TAG, "Verifying LP UART data_bits = UART_DATA_BITS_MAX is rejected"); lp_core_uart_cfg_t cfg_max = LP_CORE_UART_DEFAULT_CONFIG(); cfg_max.uart_proto_cfg.data_bits = UART_DATA_BITS_MAX; TEST_ASSERT_NOT_EQUAL(ESP_OK, lp_core_uart_init(&cfg_max)); + + /* Deinit LP GPIO pins to not leave them in RTC/LP mode */ + rtc_gpio_deinit(LP_UART_DEFAULT_TX_GPIO_NUM); + rtc_gpio_deinit(LP_UART_DEFAULT_RX_GPIO_NUM); } -/* LP UART default config */ +/* ========================================================================= + * LP UART configurations used by multi-device tests + * ========================================================================= + * + * All configurations below follow the same LP_UART_DEFAULT_GPIO_CONFIG() pin + * assignment; the HP UART mirrors whichever config the test passes in, with + * TX/RX cross-connected so the two UARTs talk to each other. + * + * On SOC_LP_GPIO_MATRIX_SUPPORTED chips the test suite also covers the LP GPIO + * Matrix routing path. lp_uart_cfg_matrix deliberately swaps the default TX + * and RX GPIO numbers so that each pin is non-default for the signal it carries + * (GPIO != upin->default_gpio), forcing lp_uart_config_io() down the matrix + * branch that calls lp_gpio_connect_in/out_signal(). The same physical + * cross-wiring used for the default-pin tests is reused: + * Board-1 GPIO14 (LP RX via matrix) <-> Board-2 GPIO14 (HP TX) + * Board-1 GPIO15 (LP TX via matrix) <-> Board-2 GPIO15 (HP RX) + */ + +/* Default: 115200 / 8-bit / no parity / 1 stop */ static lp_core_uart_cfg_t lp_uart_cfg = LP_CORE_UART_DEFAULT_CONFIG(); -/* LP UART non-default configuration */ +/* Non-default: 9600 / 8-bit / odd parity / 2 stop */ static lp_core_uart_cfg_t lp_uart_cfg1 = { - .uart_proto_cfg.baud_rate = 9600, - .uart_proto_cfg.data_bits = UART_DATA_8_BITS, - .uart_proto_cfg.parity = UART_PARITY_ODD, - .uart_proto_cfg.stop_bits = UART_STOP_BITS_2, + .uart_proto_cfg.baud_rate = 9600, + .uart_proto_cfg.data_bits = UART_DATA_8_BITS, + .uart_proto_cfg.parity = UART_PARITY_ODD, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_2, .uart_proto_cfg.rx_flow_ctrl_thresh = 0, - .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, LP_UART_DEFAULT_GPIO_CONFIG() }; -/* Global test data */ -const uint8_t start_pattern[4] = {0xDE, 0xAD, 0xBE, 0xEF}; -const uint8_t end_pattern[4] = {0xFE, 0xED, 0xBE, 0xEF}; -uint8_t expected_rx_data[UART_BUF_SIZE]; -#define TEST_DATA_LEN 234 // Select a random number of bytes to transmit +/* 5-bit word length: 115200 / 5-bit / no parity / 1 stop */ +static lp_core_uart_cfg_t lp_uart_cfg_5bit = { + .uart_proto_cfg.baud_rate = 115200, + .uart_proto_cfg.data_bits = UART_DATA_5_BITS, + .uart_proto_cfg.parity = UART_PARITY_DISABLE, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + LP_UART_DEFAULT_GPIO_CONFIG() +}; + +/* 6-bit word length: 115200 / 6-bit / no parity / 1 stop */ +static lp_core_uart_cfg_t lp_uart_cfg_6bit = { + .uart_proto_cfg.baud_rate = 115200, + .uart_proto_cfg.data_bits = UART_DATA_6_BITS, + .uart_proto_cfg.parity = UART_PARITY_DISABLE, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + LP_UART_DEFAULT_GPIO_CONFIG() +}; + +/* 7-bit word length: 115200 / 7-bit / no parity / 1 stop */ +static lp_core_uart_cfg_t lp_uart_cfg_7bit = { + .uart_proto_cfg.baud_rate = 115200, + .uart_proto_cfg.data_bits = UART_DATA_7_BITS, + .uart_proto_cfg.parity = UART_PARITY_DISABLE, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + LP_UART_DEFAULT_GPIO_CONFIG() +}; + +/* Even parity: 115200 / 8-bit / even parity / 1 stop */ +static lp_core_uart_cfg_t lp_uart_cfg_even_parity = { + .uart_proto_cfg.baud_rate = 115200, + .uart_proto_cfg.data_bits = UART_DATA_8_BITS, + .uart_proto_cfg.parity = UART_PARITY_EVEN, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + LP_UART_DEFAULT_GPIO_CONFIG() +}; + +#if SOC_LP_GPIO_MATRIX_SUPPORTED +/* + * LP GPIO Matrix path: swap default TX/RX GPIOs so lp_uart_config_io() takes + * the matrix branch (upin->default_gpio != pin) for both pins. + */ +static lp_core_uart_cfg_t lp_uart_cfg_matrix = { + .uart_proto_cfg.baud_rate = 115200, + .uart_proto_cfg.data_bits = UART_DATA_8_BITS, + .uart_proto_cfg.parity = UART_PARITY_DISABLE, + .uart_proto_cfg.stop_bits = UART_STOP_BITS_1, + .uart_proto_cfg.rx_flow_ctrl_thresh = 0, + .uart_proto_cfg.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .lp_uart_source_clk = LP_UART_SCLK_DEFAULT, + /* TX on the default RX GPIO, RX on the default TX GPIO — both non-default, + * so lp_uart_config_io() routes through the LP GPIO Matrix. */ + .uart_pin_cfg.tx_io_num = LP_UART_DEFAULT_RX_GPIO_NUM, + .uart_pin_cfg.rx_io_num = LP_UART_DEFAULT_TX_GPIO_NUM, + .uart_pin_cfg.rts_io_num = (gpio_num_t)(-1), + .uart_pin_cfg.cts_io_num = (gpio_num_t)(-1), +}; +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + +/* ========================================================================= + * Global state used by the print test + * ========================================================================= */ +#define TEST_DATA_LEN 234 + char test_string[25]; char test_long_string[200]; int test_signed_integer; @@ -125,544 +220,404 @@ uint32_t test_unsigned_integer; int test_hex; char test_character; -static void setup_test_data(uint8_t *tx_data, uint8_t *rx_data) -{ - if (tx_data) { - /* Copy the start pattern followed by the test data */ - memcpy(tx_data, start_pattern, sizeof(start_pattern)); - int i = 0; - for (i = sizeof(start_pattern); i < TEST_DATA_LEN + sizeof(start_pattern); i++) { - tx_data[i] = i + 7 - sizeof(start_pattern); // We use test data which is i + 7 - } - /* Copy the end pattern to mark the end of transmission. - * This is used by the LP core during read operations to - * notify the HP core of test completion. - */ - memcpy(tx_data + i, end_pattern, sizeof(end_pattern)); - } +/* ========================================================================= + * Data helpers + * ========================================================================= */ - if (rx_data) { - for (int i = 0; i < TEST_DATA_LEN; i++) { - expected_rx_data[i] = i + 7; - } +/* + * Fill 'data' with TEST_DATA_LEN bytes whose values are masked to the given + * word length. UART_DATA_5_BITS=0 → actual 5 bits → mask 0x1F, ..., + * UART_DATA_8_BITS=3 → mask 0xFF. Both the LP side and the HP side call + * this with the same data_bits so the sent and expected arrays are identical. + */ +static void setup_test_data_nbits(uint8_t *data, uart_word_length_t data_bits) +{ + int bits = (int)data_bits + 5; + uint8_t mask = (uint8_t)((1 << bits) - 1); + for (int i = 0; i < TEST_DATA_LEN; i++) { + data[i] = (uint8_t)((i + 7) & mask); } } static void setup_test_print_data(void) { strcpy(test_string, "Test printf string"); - strcpy(test_long_string, "Print a very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"); - test_signed_integer = -77; + strcpy(test_long_string, + "Print a very loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong string"); + test_signed_integer = -77; test_unsigned_integer = 1234567890; - test_hex = 0xa; - test_character = 'n'; + test_hex = 0xa; + test_character = 'n'; } -static void hp_uart_read(void) -{ - /* Wait for LP UART to be initialized first */ - unity_wait_for_signal("LP UART init done"); +/* ========================================================================= + * Shared transport helpers — matched configuration (both sides identical) + * ========================================================================= + * + * These helpers accept a pointer to an lp_core_uart_cfg_t so that each + * concrete test function (required by TEST_CASE_MULTIPLE_DEVICES to have no + * parameters) is a one-liner wrapper. + * + * Data pattern: setup_test_data_nbits() generates TEST_DATA_LEN bytes whose + * values are masked to the configured word length, ensuring they fit in the + * frame on both the LP UART and the HP UART. No start/end sentinel pattern + * is embedded; instead the HP UART FIFO is clean after uart_driver_install() + * so exact-length receive is reliable for any word width. + */ - /* Configure HP UART driver */ +/* Install HP UART driver mirroring the LP config; cross-connect pins */ +static void hp_uart_setup_cfg(const lp_core_uart_cfg_t *cfg) +{ uart_config_t hp_uart_cfg = { - .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, - .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, - .parity = lp_uart_cfg.uart_proto_cfg.parity, - .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, - .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .baud_rate = cfg->uart_proto_cfg.baud_rate, + .data_bits = cfg->uart_proto_cfg.data_bits, + .parity = cfg->uart_proto_cfg.parity, + .stop_bits = cfg->uart_proto_cfg.stop_bits, + .flow_ctrl = cfg->uart_proto_cfg.flow_ctrl, .source_clk = UART_SCLK_DEFAULT, }; int intr_alloc_flags = 0; - #if CONFIG_UART_ISR_IN_IRAM intr_alloc_flags = ESP_INTR_FLAG_IRAM; #endif - - /* Install HP UART driver */ ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + /* Cross-connect: HP TX → LP RX pin; HP RX ← LP TX pin */ + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, + cfg->uart_pin_cfg.rx_io_num, + cfg->uart_pin_cfg.tx_io_num, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); +} - /* Cross-connect the HP UART pins and the LP UART pins for the test */ - ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - - /* Setup test data */ - setup_test_data(NULL, expected_rx_data); - - /* Notify the LP UART that the HP UART is initialized */ +/* + * HP side for LP-write tests (LP → HP). + * Waits for LP UART init, installs HP UART with the same config, receives + * exactly TEST_DATA_LEN bytes and verifies them against the N-bit pattern. + */ +static void hp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) +{ + unity_wait_for_signal("LP UART init done"); + hp_uart_setup_cfg(cfg); unity_send_signal("HP UART init done"); - /* Receive data from LP UART */ - int bytes_remaining = TEST_DATA_LEN + sizeof(start_pattern); - uint8_t rx_data[UART_BUF_SIZE]; + uint8_t rx_data[UART_BUF_SIZE] = {0}; + int bytes_remaining = TEST_DATA_LEN; int recv_idx = 0; while (bytes_remaining > 0) { - int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); - if (bytes_received < 0) { + int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, + bytes_remaining, 10 / portTICK_PERIOD_MS); + if (n < 0) { TEST_FAIL_MESSAGE("HP UART read error"); - } else if (bytes_received > 0) { - recv_idx += bytes_received; - bytes_remaining -= bytes_received; + } else if (n > 0) { + recv_idx += n; + bytes_remaining -= n; } } - /* Check if we received the start pattern */ - int data_idx = -1; - for (int i = 0; i < UART_BUF_SIZE; i++) { - if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { - data_idx = i + 4; // Index of byte just after the start_pattern - } - } - - /* Test should pass if we received the start_pattern */ - TEST_ASSERT_NOT_EQUAL(-1, data_idx); - - /* Verify test data */ + uint8_t expected[TEST_DATA_LEN]; + setup_test_data_nbits(expected, cfg->uart_proto_cfg.data_bits); ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, rx_data, TEST_DATA_LEN); - /* Uninstall the HP UART driver */ uart_driver_delete(UART_NUM_1); vTaskDelay(1); } -static void test_lp_uart_write(void) +/* + * HP side for LP-read tests (HP → LP). + * Waits for LP UART init, installs HP UART with the same config, sends + * TEST_DATA_LEN bytes of N-bit-masked data, then waits for LP done signal. + */ +static void hp_uart_write_cfg(const lp_core_uart_cfg_t *cfg) { - /* Setup LP UART with default configuration */ - TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + unity_wait_for_signal("LP UART init done"); + hp_uart_setup_cfg(cfg); + unity_send_signal("HP UART init done"); + unity_wait_for_signal("LP UART recv ready"); - /* Notify HP UART once LP UART is initialized */ + uint8_t tx_data[TEST_DATA_LEN]; + setup_test_data_nbits(tx_data, cfg->uart_proto_cfg.data_bits); + uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN); + + unity_wait_for_signal("LP UART recv data done"); + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +/* + * LP side for write tests. + * Inits LP UART with cfg, loads LP core firmware, fills ulp_tx_data with + * N-bit-masked test data, and triggers LP_CORE_LP_UART_WRITE_TEST. + */ +static void test_lp_uart_write_cfg(const lp_core_uart_cfg_t *cfg) +{ + TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(cfg)); unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ unity_wait_for_signal("HP UART init done"); - /* Load and Run the LP core firmware */ ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + load_and_start_lp_core_firmware(&lp_cfg, + lp_core_main_uart_bin_start, + lp_core_main_uart_bin_end); - /* Setup test data */ - setup_test_data((uint8_t *)&ulp_tx_data, NULL); - ulp_tx_len = TEST_DATA_LEN + sizeof(start_pattern); + setup_test_data_nbits((uint8_t *)&ulp_tx_data, cfg->uart_proto_cfg.data_bits); + ulp_tx_len = TEST_DATA_LEN; - /* Configure ULP wakeup source */ #if SOC_LIGHT_SLEEP_SUPPORTED esp_sleep_enable_ulp_wakeup(); + /* PMU_LP_TRIGGER_HP is a WT (Write-Trigger / edge-triggered) bit. If the + * LP core completes the write and fires the wakeup pulse before the HP core + * has fully entered light sleep the pulse is lost and the HP core would + * sleep indefinitely. A 3-second timer wakeup acts as a safety net: when + * the race does not occur the ULP wakeup fires first (~20 ms); when it is + * lost the timer fires after 3 s, the HP core checks ulp_test_cmd_reply + * (already LP_CORE_COMMAND_OK) and the test still passes. */ + esp_sleep_enable_timer_wakeup(3 * 1000 * 1000ULL); #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - /* Start the test */ ESP_LOGI(TAG, "Write test start"); ulp_test_cmd = LP_CORE_LP_UART_WRITE_TEST; #if SOC_LIGHT_SLEEP_SUPPORTED - /* Enter light sleep */ esp_light_sleep_start(); #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - vTaskDelay(10); - TEST_ASSERT_EQUAL(ulp_test_cmd_reply, LP_CORE_COMMAND_OK); + TEST_ASSERT_EQUAL(LP_CORE_COMMAND_OK, ulp_test_cmd_reply); } -static void hp_uart_read_options(void) +/* + * LP side for read tests. + * Inits LP UART with cfg, loads LP core firmware, triggers + * LP_CORE_LP_UART_MULTI_BYTE_READ_TEST to receive exactly TEST_DATA_LEN bytes, + * waits for completion, then verifies the N-bit-masked pattern. + */ +static void test_lp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) { - /* Wait for LP UART to be initialized first */ - unity_wait_for_signal("LP UART init done"); - - /* Configure HP UART driver */ - uart_config_t hp_uart_cfg = { - .baud_rate = lp_uart_cfg1.uart_proto_cfg.baud_rate, - .data_bits = lp_uart_cfg1.uart_proto_cfg.data_bits, - .parity = lp_uart_cfg1.uart_proto_cfg.parity, - .stop_bits = lp_uart_cfg1.uart_proto_cfg.stop_bits, - .flow_ctrl = lp_uart_cfg1.uart_proto_cfg.flow_ctrl, - .source_clk = UART_SCLK_DEFAULT, - }; - int intr_alloc_flags = 0; - -#if CONFIG_UART_ISR_IN_IRAM - intr_alloc_flags = ESP_INTR_FLAG_IRAM; -#endif - - /* Install HP UART driver */ - ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); - ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); - - /* Cross-connect the HP UART pins and the LP UART pins for the test */ - ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg1.uart_pin_cfg.rx_io_num, lp_uart_cfg1.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - - /* Setup test data */ - setup_test_data(NULL, expected_rx_data); - - /* Notify the LP UART that the HP UART is initialized */ - unity_send_signal("HP UART init done"); - - /* Receive data from LP UART */ - int bytes_remaining = TEST_DATA_LEN + sizeof(start_pattern); - uint8_t rx_data[UART_BUF_SIZE]; - - int recv_idx = 0; - while (bytes_remaining > 0) { - int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); - if (bytes_received < 0) { - TEST_FAIL_MESSAGE("HP UART read error"); - } else if (bytes_received > 0) { - recv_idx += bytes_received; - bytes_remaining -= bytes_received; - } - } - - /* Check if we received the start pattern */ - int data_idx = -1; - for (int i = 0; i < UART_BUF_SIZE; i++) { - if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { - data_idx = i + 4; // Index of byte just after the start_pattern - } - } - - /* Test should pass if we received the start_pattern */ - TEST_ASSERT_NOT_EQUAL(-1, data_idx); - - /* Verify test data */ - ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); - - /* Uninstall the HP UART driver */ - uart_driver_delete(UART_NUM_1); - vTaskDelay(1); -} - -static void test_lp_uart_write_options(void) -{ - /* Setup LP UART with updated configuration */ - TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg1)); - - /* Notify HP UART once LP UART is initialized */ + TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(cfg)); unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ unity_wait_for_signal("HP UART init done"); - /* Load and Run the LP core firmware */ ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + load_and_start_lp_core_firmware(&lp_cfg, + lp_core_main_uart_bin_start, + lp_core_main_uart_bin_end); - /* Setup test data */ - setup_test_data((uint8_t *)&ulp_tx_data, NULL); - ulp_tx_len = TEST_DATA_LEN + sizeof(start_pattern); + uint8_t expected[TEST_DATA_LEN]; + setup_test_data_nbits(expected, cfg->uart_proto_cfg.data_bits); + ulp_rx_len = TEST_DATA_LEN; - /* Configure ULP wakeup source */ -#if SOC_LIGHT_SLEEP_SUPPORTED - esp_sleep_enable_ulp_wakeup(); -#endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - - /* Start the test */ - ESP_LOGI(TAG, "Write test start"); - ulp_test_cmd = LP_CORE_LP_UART_WRITE_TEST; - -#if SOC_LIGHT_SLEEP_SUPPORTED - /* Enter light sleep */ - esp_light_sleep_start(); -#endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - - vTaskDelay(10); - TEST_ASSERT_EQUAL(ulp_test_cmd_reply, LP_CORE_COMMAND_OK); -} - -static void hp_uart_write(void) -{ - /* Wait for LP UART to be initialized first */ - unity_wait_for_signal("LP UART init done"); - - /* Configure HP UART driver */ - uart_config_t hp_uart_cfg = { - .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, - .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, - .parity = lp_uart_cfg.uart_proto_cfg.parity, - .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, - .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, - .source_clk = UART_SCLK_DEFAULT, - }; - int intr_alloc_flags = 0; - -#if CONFIG_UART_ISR_IN_IRAM - intr_alloc_flags = ESP_INTR_FLAG_IRAM; -#endif - - /* Install HP UART driver */ - ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); - ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); - - /* Cross-connect the HP UART pins and the LP UART pins for the test */ - ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - - /* Setup test data */ - uint8_t tx_data[UART_BUF_SIZE]; - setup_test_data(tx_data, NULL); - - /* Notify the LP UART that the HP UART is initialized */ - unity_send_signal("HP UART init done"); - - /* Wait for the LP UART to be in receiving state */ - unity_wait_for_signal("LP UART recv ready"); - - /* Write data to LP UART */ - uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN + sizeof(start_pattern) + sizeof(end_pattern)); - - /* Wait for the LP UART receive data done */ - unity_wait_for_signal("LP UART recv data done"); - - /* Uninstall the HP UART driver */ - uart_driver_delete(UART_NUM_1); - vTaskDelay(1); -} - -static void test_lp_uart_read(void) -{ - /* Setup LP UART with updated configuration */ - TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); - - /* Notify HP UART once LP UART is initialized */ - unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ - unity_wait_for_signal("HP UART init done"); - - /* Load and Run the LP core firmware */ - ulp_lp_core_cfg_t lp_cfg = { - .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, - }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); - - /* Setup test data */ - setup_test_data(NULL, expected_rx_data); - - /* Start the test */ - ESP_LOGI(TAG, "Read test start"); - ulp_test_cmd = LP_CORE_LP_UART_READ_TEST; - vTaskDelay(10); - - /* Notify the HP UART to write data */ - unity_send_signal("LP UART recv ready"); - - /* Wait for test completion */ - while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { - vTaskDelay(10); - } - - /* Check if we received the start pattern */ - uint8_t *rx_data = (uint8_t*)&ulp_rx_data; - int data_idx = -1; - for (int i = 0; i < UART_BUF_SIZE; i++) { - if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { - data_idx = i + 4; // Index of byte just after the start_pattern - } - } - - /* Verify test data */ - ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); - - /* Notify the HP UART data received done and delete the UART driver */ - unity_send_signal("LP UART recv data done"); -} - -static void hp_uart_write_options(void) -{ - /* Wait for LP UART to be initialized first */ - unity_wait_for_signal("LP UART init done"); - - /* Configure HP UART driver */ - uart_config_t hp_uart_cfg = { - .baud_rate = lp_uart_cfg1.uart_proto_cfg.baud_rate, - .data_bits = lp_uart_cfg1.uart_proto_cfg.data_bits, - .parity = lp_uart_cfg1.uart_proto_cfg.parity, - .stop_bits = lp_uart_cfg1.uart_proto_cfg.stop_bits, - .flow_ctrl = lp_uart_cfg1.uart_proto_cfg.flow_ctrl, - .source_clk = UART_SCLK_DEFAULT, - }; - int intr_alloc_flags = 0; - -#if CONFIG_UART_ISR_IN_IRAM - intr_alloc_flags = ESP_INTR_FLAG_IRAM; -#endif - - /* Install HP UART driver */ - ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); - ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); - - /* Cross-connect the HP UART pins and the LP UART pins for the test */ - ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg1.uart_pin_cfg.rx_io_num, lp_uart_cfg1.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - - /* Setup test data */ - uint8_t tx_data[UART_BUF_SIZE]; - setup_test_data(tx_data, NULL); - - /* Notify the LP UART that the HP UART is initialized */ - unity_send_signal("HP UART init done"); - - /* Wait for the LP UART to be in receiving state */ - unity_wait_for_signal("LP UART recv ready"); - - /* Write data to LP UART */ - uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN + sizeof(start_pattern) + sizeof(end_pattern)); - - /* Wait for the LP UART receive data done */ - unity_wait_for_signal("LP UART recv data done"); - - /* Uninstall the HP UART driver */ - uart_driver_delete(UART_NUM_1); - vTaskDelay(1); -} - -static void test_lp_uart_read_options(void) -{ - /* Setup LP UART with updated configuration */ - TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg1)); - - /* Notify HP UART once LP UART is initialized */ - unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ - unity_wait_for_signal("HP UART init done"); - - /* Load and Run the LP core firmware */ - ulp_lp_core_cfg_t lp_cfg = { - .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, - }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); - - /* Setup test data */ - setup_test_data(NULL, expected_rx_data); - - /* Start the test */ - ESP_LOGI(TAG, "Read test start"); - ulp_test_cmd = LP_CORE_LP_UART_READ_TEST; - vTaskDelay(10); - - /* Notify the HP UART to write data */ - unity_send_signal("LP UART recv ready"); - - /* Wait for test completion */ - while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { - vTaskDelay(10); - } - - /* Check if we received the start pattern */ - uint8_t *rx_data = (uint8_t*)&ulp_rx_data; - int data_idx = -1; - for (int i = 0; i < UART_BUF_SIZE; i++) { - if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { - data_idx = i + 4; // Index of byte just after the start_pattern - } - } - - /* Verify test data */ - ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN); - - /* Notify the HP UART data received done and delete the UART driver */ - unity_send_signal("LP UART recv data done"); -} - -static void test_lp_uart_read_multi_byte(void) -{ - /* Setup LP UART with updated configuration */ - TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); - - /* Notify HP UART once LP UART is initialized */ - unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ - unity_wait_for_signal("HP UART init done"); - - /* Load and Run the LP core firmware */ - ulp_lp_core_cfg_t lp_cfg = { - .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, - }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); - - /* Setup test data */ - setup_test_data(NULL, expected_rx_data); - ulp_rx_len = TEST_DATA_LEN + sizeof(start_pattern); - - /* Start the test */ ESP_LOGI(TAG, "Read test start"); ulp_test_cmd = LP_CORE_LP_UART_MULTI_BYTE_READ_TEST; vTaskDelay(10); - /* Notify the HP UART to write data */ unity_send_signal("LP UART recv ready"); - /* Wait for test completion */ while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { vTaskDelay(10); } - /* Check if we received the start pattern */ - uint8_t *rx_data = (uint8_t*)&ulp_rx_data; - int data_idx = -1; - for (int i = 0; i < UART_BUF_SIZE; i++) { - if (!memcmp(rx_data + i, start_pattern, sizeof(start_pattern))) { - data_idx = i + 4; // Index of byte just after the start_pattern - } - } - - /* Verify test data. We verify 10 bytes less because the multi-device can sometimes - * begin with garbage data which fills the initial part of the receive buffer. */ ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected_rx_data, rx_data + data_idx, TEST_DATA_LEN - 10); - - /* Notify the HP UART data received done and delete the UART driver */ + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, (uint8_t *)&ulp_rx_data, TEST_DATA_LEN); unity_send_signal("LP UART recv data done"); } +/* ========================================================================= + * Negative test helpers — deliberate configuration mismatch + * ========================================================================= + * + * These tests verify that the LP UART driver detects errors and recovers + * without hanging or crashing when both sides are not in protocol agreement. + * + * Scenario A — LP-RX mismatch (FRAM_ERR path): + * LP UART is configured in 5-bit mode. The HP UART is configured in + * 8-bit mode and sends values that are 5-bit-masked (all ≤ 0x1F, so data + * bit D5 is always 0). After the LP receiver reads 5 data bits it looks + * for a stop bit; it finds D5=0 instead → UART_INTR_FRAM_ERR fires on + * every frame. lp_core_uart_read_bytes() returns -1; the firmware loop + * inside LP_CORE_LP_UART_MULTI_BYTE_READ_TEST breaks immediately and the + * LP core sets test_cmd_reply = LP_CORE_COMMAND_OK. + * The HP side verifies: LP core did not hang, and ulp_rx_data does not + * match the expected values (it stays zero-initialised). + * + * Scenario B — LP-TX mismatch (garbled receive on HP): + * LP UART sends 5-bit frames; HP UART reads in 8-bit mode. Because the + * frame boundaries differ, the HP receives misaligned data that does not + * match the expected 5-bit pattern. + */ + +/* + * HP side for scenario A. + * Installs HP UART with the mismatched (wider) config, sends 5-bit-masked + * values so that D5=0 on every byte, guaranteeing FRAM_ERR on the LP side. + */ +static void hp_uart_write_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg, + const lp_core_uart_cfg_t *hp_cfg) +{ + unity_wait_for_signal("LP UART init done"); + hp_uart_setup_cfg(hp_cfg); /* HP deliberately uses the wrong word length */ + unity_send_signal("HP UART init done"); + unity_wait_for_signal("LP UART recv ready"); + + /* + * Send values masked to the LP's narrower word length so that every byte + * has D5=0. The LP receiver interprets D5 as a stop bit; D5=0 is an + * invalid stop → FRAM_ERR on every frame. + */ + uint8_t tx_data[TEST_DATA_LEN]; + setup_test_data_nbits(tx_data, lp_cfg->uart_proto_cfg.data_bits); + uart_write_bytes(UART_NUM_1, (const char *)tx_data, TEST_DATA_LEN); + + unity_wait_for_signal("LP UART recv data done"); + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +/* + * LP side for scenario A. + * Configures LP UART in 5-bit mode, triggers the multi-byte read, then + * verifies that (a) the LP core replied without hanging, and (b) the receive + * buffer does not contain the expected data (FRAM_ERR aborted the read). + */ +static void test_lp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg) +{ + TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(lp_cfg)); + unity_send_signal("LP UART init done"); + unity_wait_for_signal("HP UART init done"); + + ulp_lp_core_cfg_t lp_core_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_core_cfg, + lp_core_main_uart_bin_start, + lp_core_main_uart_bin_end); + + uint8_t expected[TEST_DATA_LEN]; + setup_test_data_nbits(expected, lp_cfg->uart_proto_cfg.data_bits); + ulp_rx_len = TEST_DATA_LEN; + + ESP_LOGI(TAG, "Mismatch read test start (expect FRAM_ERR, driver must recover)"); + ulp_test_cmd = LP_CORE_LP_UART_MULTI_BYTE_READ_TEST; + vTaskDelay(10); + + unity_send_signal("LP UART recv ready"); + + /* LP core must reply without hanging — FRAM_ERR causes read loop to break */ + int wait_count = 0; + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK && wait_count < 100) { + vTaskDelay(10); + wait_count++; + } + TEST_ASSERT_EQUAL_MESSAGE(LP_CORE_COMMAND_OK, ulp_test_cmd_reply, + "LP core hung — driver did not recover from framing error"); + + /* + * ulp_rx_data starts zero-initialised (re-loaded with firmware). + * expected[0] = (0+7)&0x1F = 7, so any match would be surprising. + * A single differing byte is enough to confirm the error was detected. + */ + ESP_LOGI(TAG, "Verify Rx buffer is corrupt (FRAM_ERR aborted read early)"); + bool data_matches = (memcmp((uint8_t *)&ulp_rx_data, expected, TEST_DATA_LEN) == 0); + TEST_ASSERT_FALSE_MESSAGE(data_matches, + "LP UART received correct data despite word-length mismatch"); + + unity_send_signal("LP UART recv data done"); +} + +/* + * HP side for scenario B. + * Installs HP UART in the wrong (wider) word length and tries to receive + * TEST_DATA_LEN bytes. With mismatched framing the HP receives garbled data; + * the test verifies that the received content does not match the 5-bit pattern. + */ +static void hp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg, + const lp_core_uart_cfg_t *hp_cfg) +{ + unity_wait_for_signal("LP UART init done"); + hp_uart_setup_cfg(hp_cfg); /* HP deliberately uses the wrong word length */ + unity_send_signal("HP UART init done"); + + /* Collect whatever the HP receives within a bounded window */ + uint8_t rx_data[UART_BUF_SIZE] = {0}; + int recv_idx = 0; + int idle_count = 0; + while (recv_idx < TEST_DATA_LEN && idle_count < 20) { + int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, + TEST_DATA_LEN - recv_idx, 10 / portTICK_PERIOD_MS); + if (n > 0) { + recv_idx += n; + idle_count = 0; + } else { + idle_count++; + vTaskDelay(10); + } + } + + uint8_t expected[TEST_DATA_LEN]; + setup_test_data_nbits(expected, lp_cfg->uart_proto_cfg.data_bits); + + /* + * With mismatched framing the HP cannot receive the same sequence the LP + * sent. Either fewer bytes arrive (HP sees framing errors) or the values + * are reinterpreted across different bit boundaries. Either way the data + * must differ from the expected 5-bit pattern. + */ + bool all_match = (recv_idx == TEST_DATA_LEN) && + (memcmp(rx_data, expected, TEST_DATA_LEN) == 0); + ESP_LOGI(TAG, "Received %d bytes; all_match=%d (expected: false)", recv_idx, (int)all_match); + TEST_ASSERT_FALSE_MESSAGE(all_match, + "HP received correct data despite word-length mismatch"); + + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +/* ========================================================================= + * Print test helpers (standalone: not parameterisable over word length) + * ========================================================================= */ + static void hp_uart_read_print(void) { - /* Wait for LP UART to be initialized first */ unity_wait_for_signal("LP UART init done"); - /* Configure HP UART driver */ uart_config_t hp_uart_cfg = { - .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, - .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, - .parity = lp_uart_cfg.uart_proto_cfg.parity, - .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, - .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, .source_clk = UART_SCLK_DEFAULT, }; int intr_alloc_flags = 0; - #if CONFIG_UART_ISR_IN_IRAM intr_alloc_flags = ESP_INTR_FLAG_IRAM; #endif - /* Install HP UART driver */ ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, + lp_uart_cfg.uart_pin_cfg.rx_io_num, + lp_uart_cfg.uart_pin_cfg.tx_io_num, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - /* Cross-connect the HP UART pins and the LP UART pins for the test */ - ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); - - /* Notify the LP UART that the HP UART is initialized */ unity_send_signal("HP UART init done"); - /* Setup test data */ setup_test_print_data(); - /* Receive data from LP UART */ uint8_t rx_data[UART_BUF_SIZE]; - int recv_idx = 0; + int recv_idx = 0; int idle_count = 0; while (1) { - int bytes_received = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); - if (bytes_received < 0) { + int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, + UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); + if (n < 0) { TEST_FAIL_MESSAGE("HP UART read error"); - } else if (bytes_received > 0) { - recv_idx += bytes_received; - } else if (bytes_received == 0) { + } else if (n > 0) { + recv_idx += n; + } else { idle_count++; vTaskDelay(10); if (idle_count > 10) { @@ -672,8 +627,6 @@ static void hp_uart_read_print(void) } rx_data[UART_BUF_SIZE - 1] = '\0'; - /* Parse the rx_data to verify the printed data */ - /* Search for expected_string in rx_data. Report test pass if the string is found */ char expected_string[TEST_DATA_LEN]; snprintf(expected_string, TEST_DATA_LEN, "%s", test_string); TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); @@ -693,64 +646,264 @@ static void hp_uart_read_print(void) snprintf(expected_string, TEST_DATA_LEN, "Test printf character %c", test_character); TEST_ASSERT_NOT_NULL(strstr((const char *)rx_data, expected_string)); - /* Uninstall the HP UART driver */ uart_driver_delete(UART_NUM_1); vTaskDelay(1); } static void test_lp_uart_print(void) { - /* Setup LP UART with default configuration */ TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); - - /* Notify HP UART once LP UART is initialized */ unity_send_signal("LP UART init done"); - - /* Wait for the HP UART device to be initialized */ unity_wait_for_signal("HP UART init done"); - /* Load and Run the LP core firmware */ ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, }; - load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + load_and_start_lp_core_firmware(&lp_cfg, + lp_core_main_uart_bin_start, + lp_core_main_uart_bin_end); - /* Setup test data */ setup_test_print_data(); strcpy((char *)&ulp_test_string, test_string); strcpy((char *)&ulp_test_long_string, test_long_string); - ulp_test_signed_integer = test_signed_integer; + ulp_test_signed_integer = test_signed_integer; ulp_test_unsigned_integer = test_unsigned_integer; - ulp_test_hex = test_hex; - ulp_test_character = test_character; + ulp_test_hex = test_hex; + ulp_test_character = test_character; - /* Configure ULP wakeup source */ #if SOC_LIGHT_SLEEP_SUPPORTED esp_sleep_enable_ulp_wakeup(); + /* Same safety-net timer as in test_lp_uart_write_cfg: handles the race + * where the LP core fires the edge-triggered wakeup pulse before the HP + * core has entered light sleep. */ + esp_sleep_enable_timer_wakeup(3 * 1000 * 1000ULL); #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - /* Start the test */ ESP_LOGI(TAG, "LP Core print test start"); ulp_test_cmd = LP_CORE_LP_UART_PRINT_TEST; #if SOC_LIGHT_SLEEP_SUPPORTED - /* Enter light sleep */ esp_light_sleep_start(); #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ - vTaskDelay(10); - TEST_ASSERT_EQUAL(ulp_test_cmd_reply, LP_CORE_COMMAND_OK); + TEST_ASSERT_EQUAL(LP_CORE_COMMAND_OK, ulp_test_cmd_reply); } -/* Test LP UART write operation with default LP UART initialization configuration */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - default config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_write, hp_uart_read); -/* Test LP UART write operation with updated LP UART initialization configuration */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - optional config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_write_options, hp_uart_read_options); -/* Test LP UART read operation with default LP UART initialization configuration */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - default config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read, hp_uart_write); -/* Test LP UART read operation with updated LP UART initialization configuration */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - optional config", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read_options, hp_uart_write_options); -/* Test LP UART multi-byte read operation */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART multi-byte read test", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_read_multi_byte, hp_uart_write); -/* Test LP Core print */ -TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART print test", "[lp_core][test_env=generic_multi_device][timeout=150]", test_lp_uart_print, hp_uart_read_print); +/* ========================================================================= + * Concrete (no-argument) wrappers — required by TEST_CASE_MULTIPLE_DEVICES + * ========================================================================= */ + +/* --- Default config (115200 / 8N1) --- */ +static void test_lp_uart_write_default(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg); +} +static void hp_uart_read_default(void) +{ + hp_uart_read_cfg(&lp_uart_cfg); +} +static void test_lp_uart_read_default(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg); +} +static void hp_uart_write_default(void) +{ + hp_uart_write_cfg(&lp_uart_cfg); +} + +/* --- Non-default config (9600 / 8 / odd / 2-stop) --- */ +static void test_lp_uart_write_options(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg1); +} +static void hp_uart_read_options(void) +{ + hp_uart_read_cfg(&lp_uart_cfg1); +} +static void test_lp_uart_read_options(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg1); +} +static void hp_uart_write_options(void) +{ + hp_uart_write_cfg(&lp_uart_cfg1); +} + +/* --- 5-bit word length --- */ +static void test_lp_uart_write_5bit(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_5bit); +} +static void hp_uart_read_5bit(void) +{ + hp_uart_read_cfg(&lp_uart_cfg_5bit); +} +static void test_lp_uart_read_5bit(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg_5bit); +} +static void hp_uart_write_5bit(void) +{ + hp_uart_write_cfg(&lp_uart_cfg_5bit); +} + +/* --- 6-bit word length --- */ +static void test_lp_uart_write_6bit(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_6bit); +} +static void hp_uart_read_6bit(void) +{ + hp_uart_read_cfg(&lp_uart_cfg_6bit); +} +static void test_lp_uart_read_6bit(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg_6bit); +} +static void hp_uart_write_6bit(void) +{ + hp_uart_write_cfg(&lp_uart_cfg_6bit); +} + +/* --- 7-bit word length --- */ +static void test_lp_uart_write_7bit(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_7bit); +} +static void hp_uart_read_7bit(void) +{ + hp_uart_read_cfg(&lp_uart_cfg_7bit); +} +static void test_lp_uart_read_7bit(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg_7bit); +} +static void hp_uart_write_7bit(void) +{ + hp_uart_write_cfg(&lp_uart_cfg_7bit); +} + +/* --- Even parity (115200 / 8E1) --- */ +static void test_lp_uart_write_even_parity(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_even_parity); +} +static void hp_uart_read_even_parity(void) +{ + hp_uart_read_cfg(&lp_uart_cfg_even_parity); +} +static void test_lp_uart_read_even_parity(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg_even_parity); +} +static void hp_uart_write_even_parity(void) +{ + hp_uart_write_cfg(&lp_uart_cfg_even_parity); +} + +#if SOC_LP_GPIO_MATRIX_SUPPORTED +/* --- LP GPIO Matrix path (non-default pins, swapped TX/RX GPIOs) --- */ +static void test_lp_uart_write_matrix(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_matrix); +} +static void hp_uart_read_matrix(void) +{ + hp_uart_read_cfg(&lp_uart_cfg_matrix); +} +static void test_lp_uart_read_matrix(void) +{ + test_lp_uart_read_cfg(&lp_uart_cfg_matrix); +} +static void hp_uart_write_matrix(void) +{ + hp_uart_write_cfg(&lp_uart_cfg_matrix); +} +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + +/* --- Negative: 5-bit LP RX, 8-bit HP TX (FRAM_ERR recovery) --- */ +static void test_lp_uart_read_mismatch_wl(void) +{ + test_lp_uart_read_mismatch_cfg(&lp_uart_cfg_5bit); +} +static void hp_uart_write_mismatch_wl(void) +{ + hp_uart_write_mismatch_cfg(&lp_uart_cfg_5bit, &lp_uart_cfg); +} + +/* --- Negative: 5-bit LP TX, 8-bit HP RX (garbled data on HP) --- */ +static void test_lp_uart_write_mismatch_wl(void) +{ + test_lp_uart_write_cfg(&lp_uart_cfg_5bit); +} +static void hp_uart_read_mismatch_wl(void) +{ + hp_uart_read_mismatch_cfg(&lp_uart_cfg_5bit, &lp_uart_cfg); +} + +/* ========================================================================= + * Test case registrations + * ========================================================================= */ + +/* Write tests (LP → HP) */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - default config", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_default, hp_uart_read_default); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - optional config", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_options, hp_uart_read_options); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - 5-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_5bit, hp_uart_read_5bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - 6-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_6bit, hp_uart_read_6bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - 7-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_7bit, hp_uart_read_7bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - even parity", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_even_parity, hp_uart_read_even_parity); + +/* Read tests (HP → LP) */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - default config", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_default, hp_uart_write_default); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - optional config", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_options, hp_uart_write_options); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - 5-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_5bit, hp_uart_write_5bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - 6-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_6bit, hp_uart_write_6bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - 7-bit data", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_7bit, hp_uart_write_7bit); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - even parity", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_even_parity, hp_uart_write_even_parity); + +/* Negative tests */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - word length mismatch FRAM_ERR recovery", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_mismatch_wl, hp_uart_write_mismatch_wl); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - word length mismatch garbled HP receive", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_mismatch_wl, hp_uart_read_mismatch_wl); + +/* LP GPIO Matrix path tests (SOC_LP_GPIO_MATRIX_SUPPORTED chips only) */ +#if SOC_LP_GPIO_MATRIX_SUPPORTED +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART write test - LP GPIO Matrix routing", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_write_matrix, hp_uart_read_matrix); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - LP GPIO Matrix routing", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_matrix, hp_uart_write_matrix); +#endif /* SOC_LP_GPIO_MATRIX_SUPPORTED */ + +/* Print test */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART print test", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_print, hp_uart_read_print); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py index 373fdd1f764..f4773a5ed39 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -62,8 +62,28 @@ def test_lp_vad(dut: Dut) -> None: indirect=True, ) @idf_parametrize('target', ['esp32c6'], indirect=['target']) -def test_lp_core_multi_device(case_tester) -> None: # type: ignore - case_tester.run_all_multi_dev_cases(reset=True) +def test_lp_core_multi_device(case_tester: CaseTester) -> None: + # Run only non-UART multi-device cases (e.g. LP I2C); LP UART is covered + # by test_lp_uart_multi_device which targets all LP_CORE_SUPPORTED chips. + non_uart_cases = [case for case in case_tester.test_menu if 'uart' not in case.groups] + for case in non_uart_cases: + case_tester.run_multi_dev_case(case=case, reset=True) + + +@pytest.mark.generic_multi_device +@pytest.mark.parametrize('count', [2], indirect=True) +@pytest.mark.parametrize( + 'config', + [ + 'defaults', + ], + indirect=True, +) +@idf_parametrize('target', soc_filtered_targets('SOC_ULP_LP_UART_SUPPORTED == 1'), indirect=['target']) +def test_lp_uart_multi_device(case_tester: CaseTester) -> None: + uart_cases = [case for case in case_tester.test_menu if 'uart' in case.groups and 'wakeup' not in case.groups] + for case in uart_cases: + case_tester.run_multi_dev_case(case=case, reset=True) @pytest.mark.generic_multi_device From 359cec50bbd02e96c545fd9867d1bfdce4812f1e Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 13 Apr 2026 12:23:59 +0200 Subject: [PATCH 5/8] test(lp_core): use soc_filtered_targets for all LP core pytest parametrization Several LP core pytest files were either hardcoded to specific chip lists or using a less-precise SOC capability filter: - test_lp_core_multi_device: was locked to ['esp32c6'] pending a workaround for LP I2C on esp32p4; all three active LP core chips now have SOC_LP_I2C_SUPPORTED=1, so switch to soc_filtered_targets. - test_lp_uart_wakeup_modes: was using SOC_LP_CORE_SUPPORTED which is semantically wrong for a UART test; change to SOC_ULP_LP_UART_SUPPORTED. - LP core example pytests (build_system, interrupt, gpio_intr_pulse_counter, lp_timer_interrupt): replace hardcoded ['esp32c5', 'esp32c6', 'esp32p4'] with soc_filtered_targets('SOC_LP_CORE_SUPPORTED == 1') so that new chips automatically get coverage when their SOC cap is enabled. --- .../lp_core_basic_tests/pytest_lp_core_basic.py | 5 ++--- .../lp_core/build_system/pytest_lp_core_build_sys.py | 6 ++++-- .../gpio_intr_pulse_counter/pytest_lp_core_pcnt.py | 11 +++++++++-- .../ulp/lp_core/interrupt/pytest_lp_core_intr.py | 3 ++- .../lp_timer_interrupt/pytest_lp_timer_interrupt.py | 3 ++- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py index f4773a5ed39..888d153239e 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py @@ -50,7 +50,6 @@ def test_lp_vad(dut: Dut) -> None: dut.run_all_single_board_cases(group='lp_vad') -# TODO: Support LP I2C test for esp32p4 (IDF-9581) @pytest.mark.generic_multi_device @pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='TODO IDF-15572 Enable ULP multi device tests for ESP32-S31') @pytest.mark.parametrize('count', [2], indirect=True) @@ -61,7 +60,7 @@ def test_lp_vad(dut: Dut) -> None: ], indirect=True, ) -@idf_parametrize('target', ['esp32c6'], indirect=['target']) +@idf_parametrize('target', soc_filtered_targets('SOC_LP_I2C_SUPPORTED == 1'), indirect=['target']) def test_lp_core_multi_device(case_tester: CaseTester) -> None: # Run only non-UART multi-device cases (e.g. LP I2C); LP UART is covered # by test_lp_uart_multi_device which targets all LP_CORE_SUPPORTED chips. @@ -90,7 +89,7 @@ def test_lp_uart_multi_device(case_tester: CaseTester) -> None: @pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='TODO IDF-15572 Enable ULP multi device tests for ESP32-S31') @pytest.mark.parametrize( 'target', - soc_filtered_targets('SOC_LP_CORE_SUPPORTED == 1'), + soc_filtered_targets('SOC_ULP_LP_UART_SUPPORTED == 1'), indirect=True, ) @pytest.mark.parametrize( diff --git a/examples/system/ulp/lp_core/build_system/pytest_lp_core_build_sys.py b/examples/system/ulp/lp_core/build_system/pytest_lp_core_build_sys.py index f311e22ee1c..7dafa40bcbc 100644 --- a/examples/system/ulp/lp_core/build_system/pytest_lp_core_build_sys.py +++ b/examples/system/ulp/lp_core/build_system/pytest_lp_core_build_sys.py @@ -1,11 +1,13 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 + import pytest from pytest_embedded_idf.dut import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets @pytest.mark.generic -@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) +@idf_parametrize('target', soc_filtered_targets('SOC_LP_CORE_SUPPORTED == 1'), indirect=['target']) def test_lp_core_build_sys(dut: IdfDut) -> None: dut.expect('Sum calculated by ULP using external library func: 11') diff --git a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py index 53f2eb1a404..c162225aa5f 100644 --- a/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py +++ b/examples/system/ulp/lp_core/gpio_intr_pulse_counter/pytest_lp_core_pcnt.py @@ -1,14 +1,21 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import logging import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets @pytest.mark.generic -@idf_parametrize('target', ['esp32c6', 'esp32p4', 'esp32c5'], indirect=['target']) +@idf_parametrize( + 'target', + soc_filtered_targets( + 'SOC_LP_CORE_SUPPORTED == 1 and SOC_ULP_LP_UART_SUPPORTED == 1 and SOC_DEEP_SLEEP_SUPPORTED == 1' + ), + indirect=['target'], +) def test_lp_core_pcnt(dut: Dut) -> None: res = dut.expect(r'ULP will wake up processor after every (\d+) pulses') wakeup_limit = res.group(1).decode('utf-8') diff --git a/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py b/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py index ed33de4c290..8445700b68b 100644 --- a/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py +++ b/examples/system/ulp/lp_core/interrupt/pytest_lp_core_intr.py @@ -3,9 +3,10 @@ import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets @pytest.mark.generic -@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) +@idf_parametrize('target', soc_filtered_targets('SOC_LP_CORE_SUPPORTED == 1'), indirect=['target']) def test_lp_core_intr(dut: Dut) -> None: dut.expect('Triggered 10 interrupts on the LP-Core, LP-Core received 10 interrupts') diff --git a/examples/system/ulp/lp_core/lp_timer_interrupt/pytest_lp_timer_interrupt.py b/examples/system/ulp/lp_core/lp_timer_interrupt/pytest_lp_timer_interrupt.py index 92f07652d42..0f7297e6f2a 100644 --- a/examples/system/ulp/lp_core/lp_timer_interrupt/pytest_lp_timer_interrupt.py +++ b/examples/system/ulp/lp_core/lp_timer_interrupt/pytest_lp_timer_interrupt.py @@ -5,10 +5,11 @@ import time import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets @pytest.mark.generic -@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4'], indirect=['target']) +@idf_parametrize('target', soc_filtered_targets('SOC_LP_CORE_SUPPORTED == 1'), indirect=['target']) def test_lp_timer_interrupt(dut: Dut) -> None: # Wait for LP core to be loaded and running dut.expect_exact('LP core loaded with firmware and running successfully') From 3b36e9d0942c6e67c12a3c99df9e9392c173c081 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Mon, 20 Apr 2026 15:00:39 +0200 Subject: [PATCH 6/8] fix(lp_core): fix multi-device LP UART test failures on esp32p4 The multi-device LP UART tests were failing on esp32p4 due to several issues in the test harness: - LP ROM boot banner: On chips with LP ROM (esp32p4), the LP core emits a ROM banner on LP UART during startup, corrupting the first bytes of test data. Set skip_lp_rom_boot=true in the ULP config for write, read and mismatch tests to suppress this. - Stale FIFO data: The HP UART RX FIFO could accumulate garbage during pin mux setup. Add uart_flush_input() after HP UART driver installation and before each read phase. Call lp_core_uart_clear_buf() before LP-side read tests to flush the LP UART RX FIFO as well. - Missing synchronization: The HP reader could start listening before the LP transmitter was ready (or vice versa), causing data loss at higher baud rates. Add signal exchange (unity_send_signal / unity_wait_for_signal) to coordinate LP-to-HP data transfers. - Short read timeout: The uart_read_bytes() timeout of 10 ms was too aggressive for slower baud rates. Increase to 100 ms. Made-with: Cursor --- .../main/test_lp_core_uart.c | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c index 39a3517b886..deb764707c3 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c @@ -20,6 +20,7 @@ #include "driver/uart.h" #include "driver/rtc_io.h" #include "soc/soc_caps.h" +#include "ulp_lp_core_lp_uart_shared.h" #if SOC_LIGHT_SLEEP_SUPPORTED #include "esp_sleep.h" #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ @@ -287,6 +288,9 @@ static void hp_uart_setup_cfg(const lp_core_uart_cfg_t *cfg) cfg->uart_pin_cfg.rx_io_num, cfg->uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + /* Discard any stale bytes that arrived while the pin mux was settling */ + uart_flush_input(UART_NUM_1); } /* @@ -300,12 +304,18 @@ static void hp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) hp_uart_setup_cfg(cfg); unity_send_signal("HP UART init done"); + /* Wait for the LP side to finish loading firmware and preparing data, + * then flush the HP UART RX FIFO before signalling readiness. */ + unity_wait_for_signal("LP UART tx ready"); + uart_flush_input(UART_NUM_1); + unity_send_signal("HP UART rx ready"); + uint8_t rx_data[UART_BUF_SIZE] = {0}; int bytes_remaining = TEST_DATA_LEN; int recv_idx = 0; while (bytes_remaining > 0) { int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, - bytes_remaining, 10 / portTICK_PERIOD_MS); + bytes_remaining, 100 / portTICK_PERIOD_MS); if (n < 0) { TEST_FAIL_MESSAGE("HP UART read error"); } else if (n > 0) { @@ -357,6 +367,9 @@ static void test_lp_uart_write_cfg(const lp_core_uart_cfg_t *cfg) ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, +#if ESP_ROM_HAS_LP_ROM + .skip_lp_rom_boot = true, +#endif }; load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, @@ -377,6 +390,11 @@ static void test_lp_uart_write_cfg(const lp_core_uart_cfg_t *cfg) esp_sleep_enable_timer_wakeup(3 * 1000 * 1000ULL); #endif /* SOC_LIGHT_SLEEP_SUPPORTED */ + /* Tell the HP side we are ready to transmit so it can flush its RX FIFO + * just before we begin, then wait for its acknowledgement. */ + unity_send_signal("LP UART tx ready"); + unity_wait_for_signal("HP UART rx ready"); + ESP_LOGI(TAG, "Write test start"); ulp_test_cmd = LP_CORE_LP_UART_WRITE_TEST; @@ -401,6 +419,9 @@ static void test_lp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) ulp_lp_core_cfg_t lp_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, +#if ESP_ROM_HAS_LP_ROM + .skip_lp_rom_boot = true, +#endif }; load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, @@ -410,6 +431,10 @@ static void test_lp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) setup_test_data_nbits(expected, cfg->uart_proto_cfg.data_bits); ulp_rx_len = TEST_DATA_LEN; + /* Flush any garbage that accumulated in the LP UART RX FIFO during the + HP-side pin muxing / UART driver installation. */ + lp_core_uart_clear_buf(); + ESP_LOGI(TAG, "Read test start"); ulp_test_cmd = LP_CORE_LP_UART_MULTI_BYTE_READ_TEST; vTaskDelay(10); @@ -421,7 +446,8 @@ static void test_lp_uart_read_cfg(const lp_core_uart_cfg_t *cfg) } ESP_LOGI(TAG, "Verify Rx data"); - TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, (uint8_t *)&ulp_rx_data, TEST_DATA_LEN); + const volatile uint8_t *rx_ptr = (const volatile uint8_t *)&ulp_rx_data; + TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, (const void *)rx_ptr, TEST_DATA_LEN); unity_send_signal("LP UART recv data done"); } @@ -490,6 +516,9 @@ static void test_lp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg) ulp_lp_core_cfg_t lp_core_cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, +#if ESP_ROM_HAS_LP_ROM + .skip_lp_rom_boot = true, +#endif }; load_and_start_lp_core_firmware(&lp_core_cfg, lp_core_main_uart_bin_start, @@ -499,6 +528,8 @@ static void test_lp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg) setup_test_data_nbits(expected, lp_cfg->uart_proto_cfg.data_bits); ulp_rx_len = TEST_DATA_LEN; + lp_core_uart_clear_buf(); + ESP_LOGI(TAG, "Mismatch read test start (expect FRAM_ERR, driver must recover)"); ulp_test_cmd = LP_CORE_LP_UART_MULTI_BYTE_READ_TEST; vTaskDelay(10); @@ -520,7 +551,8 @@ static void test_lp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg) * A single differing byte is enough to confirm the error was detected. */ ESP_LOGI(TAG, "Verify Rx buffer is corrupt (FRAM_ERR aborted read early)"); - bool data_matches = (memcmp((uint8_t *)&ulp_rx_data, expected, TEST_DATA_LEN) == 0); + const volatile uint8_t *rx_ptr = (const volatile uint8_t *)&ulp_rx_data; + bool data_matches = (memcmp((const void *)rx_ptr, expected, TEST_DATA_LEN) == 0); TEST_ASSERT_FALSE_MESSAGE(data_matches, "LP UART received correct data despite word-length mismatch"); @@ -540,13 +572,18 @@ static void hp_uart_read_mismatch_cfg(const lp_core_uart_cfg_t *lp_cfg, hp_uart_setup_cfg(hp_cfg); /* HP deliberately uses the wrong word length */ unity_send_signal("HP UART init done"); + /* Wait for LP side to be ready, then flush before receiving */ + unity_wait_for_signal("LP UART tx ready"); + uart_flush_input(UART_NUM_1); + unity_send_signal("HP UART rx ready"); + /* Collect whatever the HP receives within a bounded window */ uint8_t rx_data[UART_BUF_SIZE] = {0}; int recv_idx = 0; int idle_count = 0; while (recv_idx < TEST_DATA_LEN && idle_count < 20) { int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, - TEST_DATA_LEN - recv_idx, 10 / portTICK_PERIOD_MS); + TEST_DATA_LEN - recv_idx, 100 / portTICK_PERIOD_MS); if (n > 0) { recv_idx += n; idle_count = 0; @@ -603,6 +640,7 @@ static void hp_uart_read_print(void) lp_uart_cfg.uart_pin_cfg.tx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + uart_flush_input(UART_NUM_1); unity_send_signal("HP UART init done"); setup_test_print_data(); @@ -612,7 +650,7 @@ static void hp_uart_read_print(void) int idle_count = 0; while (1) { int n = uart_read_bytes(UART_NUM_1, rx_data + recv_idx, - UART_BUF_SIZE, 10 / portTICK_PERIOD_MS); + UART_BUF_SIZE, 100 / portTICK_PERIOD_MS); if (n < 0) { TEST_FAIL_MESSAGE("HP UART read error"); } else if (n > 0) { From 57b301f036f14b3252711cfccebf56328a72619a Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 22 Apr 2026 11:16:59 +0200 Subject: [PATCH 7/8] test(lp_core): add LP UART read_bytes multi-device tests Made-with: Cursor --- .../main/lp_core/test_main_uart.c | 43 ++++++- .../main/lp_core/test_shared.h | 36 ++++++ .../main/test_lp_core_uart.c | 118 ++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c index dbfb69096aa..b37550f8ade 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart.c @@ -1,11 +1,14 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include #include +#include #include "hal/uart_types.h" +#include "hal/uart_ll.h" #include "test_shared.h" #include "ulp_lp_core_utils.h" #include "ulp_lp_core_uart.h" @@ -28,6 +31,12 @@ uint8_t rx_data[LP_UART_BUFFER_LEN] = {}; volatile uint8_t tx_len = 0; volatile uint8_t rx_len = 0; +/* Last lp_core_uart_read_bytes return value for LP UART multi-device tests */ +volatile int32_t read_return_value = 0; + +/* Guarded buffer for read_bytes buffer-bounds test (pre + user + post) */ +lp_uart_read_bounds_guard_t read_bounds_guard_region; + /* LP Core print test variables */ volatile char test_string[25]; volatile char test_long_string[200]; @@ -36,6 +45,17 @@ volatile uint32_t test_unsigned_integer; volatile int test_hex; volatile char test_character; +/* Wait until the HP peer has filled the LP UART RX FIFO with the full test burst, + * then allow the RX timeout condition to settle. This makes read_bytes behaviour + * deterministic for multi-device tests that depend on a single large FIFO drain. */ +static void lp_uart_wait_rx_burst_ready(void) +{ + uart_dev_t *dev = (uart_dev_t *)UART_LL_GET_HW(LP_UART_PORT_NUM); + while ((size_t)uart_ll_get_rxfifo_len(dev) < LP_UART_READ_RETURN_VALUE_BURST_LEN) { + } + ulp_lp_core_delay_us(400); +} + int main(void) { /* Enable interrupts. @@ -89,6 +109,27 @@ int main(void) } } + if (test_cmd == LP_CORE_LP_UART_READ_BYTES_RETURN_VALUE_TEST) { + /* Read a burst that fits in hardware RX FIFO; HP asserts return value + * equals requested length (see test_lp_uart_read_bytes_return_value). */ + lp_uart_wait_rx_burst_ready(); + read_return_value = lp_core_uart_read_bytes(LP_UART_PORT_NUM, rx_data, + LP_UART_READ_RETURN_VALUE_BURST_LEN, + LP_UART_TRANS_WAIT_FOREVER); + } + + if (test_cmd == LP_CORE_LP_UART_READ_BYTES_BOUNDS_TEST) { + /* Request a small user buffer while the link partner sends a larger burst; + * read_bytes must not corrupt guard regions past user_buf. */ + memset(&read_bounds_guard_region, LP_UART_READ_BOUNDS_GUARD_PATTERN, + sizeof(read_bounds_guard_region)); + lp_uart_wait_rx_burst_ready(); + read_return_value = lp_core_uart_read_bytes(LP_UART_PORT_NUM, + read_bounds_guard_region.user_buf, + LP_UART_READ_BOUNDS_USER_BUF_LEN, + LP_UART_TRANS_WAIT_FOREVER); + } + if (test_cmd == LP_CORE_LP_UART_PRINT_TEST) { /* Write various cases to test lp_core_printf to test various format specifiers */ lp_core_printf("%s\r\n", test_string); diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h index d1d07d93276..68f0eb8676d 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h @@ -5,6 +5,9 @@ */ #pragma once +#include +#include "soc/soc_caps.h" + #define XOR_MASK 0xDEADBEEF /* I2C test params */ @@ -15,6 +18,37 @@ /* LP UART test param */ #define UART_BUF_SIZE 1024 +/* + * LP UART read_bytes return-value test (HP + LP): + * The burst must fit in the hardware RX FIFO yet typically exceed the LP UART + * driver's default RX FIFO full threshold (LP_UART_FULL_THRESH_DEFAULT in + * components/ulp/lp_core/lp_core/lp_core_uart.c). If either value changes, + * revisit this guard. + */ +#if (SOC_LP_UART_FIFO_LEN) < 12 +#error "SOC_LP_UART_FIFO_LEN too small for LP UART read_bytes return-value test" +#endif +#define LP_UART_READ_RETURN_VALUE_BURST_LEN ((SOC_LP_UART_FIFO_LEN) - 1) + +/* Small user buffer for read_bytes buffer-bounds test; HP sends RETURN_VALUE_BURST_LEN. */ +#define LP_UART_READ_BOUNDS_USER_BUF_LEN ((SOC_LP_UART_FIFO_LEN) / 4) + +/* Guard memory on each side of the user buffer (worst case = full FIFO depth per side). */ +#define LP_UART_READ_BOUNDS_GUARD_LEN (2 * (SOC_LP_UART_FIFO_LEN)) + +#define LP_UART_READ_BOUNDS_GUARD_PATTERN 0xAA + +typedef struct __attribute__((packed)) +{ + uint8_t pre_guard[LP_UART_READ_BOUNDS_GUARD_LEN]; + uint8_t user_buf[LP_UART_READ_BOUNDS_USER_BUF_LEN]; + uint8_t post_guard[LP_UART_READ_BOUNDS_GUARD_LEN]; +} lp_uart_read_bounds_guard_t; + +_Static_assert(sizeof(lp_uart_read_bounds_guard_t) == + (LP_UART_READ_BOUNDS_GUARD_LEN + LP_UART_READ_BOUNDS_USER_BUF_LEN + LP_UART_READ_BOUNDS_GUARD_LEN), + "lp_uart_read_bounds_guard_t layout must match LP/HP shared memory view"); + typedef enum { LP_CORE_READ_WRITE_TEST = 1, LP_CORE_DELAY_TEST, @@ -26,6 +60,8 @@ typedef enum { LP_CORE_LP_UART_READ_TEST, LP_CORE_LP_UART_MULTI_BYTE_READ_TEST, LP_CORE_LP_UART_PRINT_TEST, + LP_CORE_LP_UART_READ_BYTES_RETURN_VALUE_TEST, + LP_CORE_LP_UART_READ_BYTES_BOUNDS_TEST, LP_CORE_LP_SPI_WRITE_READ_TEST, LP_CORE_NO_COMMAND, } lp_core_test_commands_t; diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c index deb764707c3..73b4b583b1f 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_uart.c @@ -945,3 +945,121 @@ TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read test - LP GPIO Matrix routing", TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART print test", "[lp_core][uart][test_env=generic_multi_device][timeout=150]", test_lp_uart_print, hp_uart_read_print); + +/* HP peer: send one UART burst sized for LP read_bytes return-value / bounds tests. */ + +static void hp_uart_send_lp_read_test_burst(void) +{ + unity_wait_for_signal("LP UART init done"); + + uart_config_t hp_uart_cfg = { + .baud_rate = lp_uart_cfg.uart_proto_cfg.baud_rate, + .data_bits = lp_uart_cfg.uart_proto_cfg.data_bits, + .parity = lp_uart_cfg.uart_proto_cfg.parity, + .stop_bits = lp_uart_cfg.uart_proto_cfg.stop_bits, + .flow_ctrl = lp_uart_cfg.uart_proto_cfg.flow_ctrl, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; +#if CONFIG_UART_ISR_IN_IRAM + intr_alloc_flags = ESP_INTR_FLAG_IRAM; +#endif + ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, UART_BUF_SIZE, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &hp_uart_cfg)); + ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, lp_uart_cfg.uart_pin_cfg.rx_io_num, + lp_uart_cfg.uart_pin_cfg.tx_io_num, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + + unity_send_signal("HP UART init done"); + unity_wait_for_signal("LP UART recv ready"); + + ESP_ERROR_CHECK(uart_flush_input(UART_NUM_1)); + + uint8_t tx_buf[LP_UART_READ_RETURN_VALUE_BURST_LEN]; + for (int i = 0; i < LP_UART_READ_RETURN_VALUE_BURST_LEN; i++) { + tx_buf[i] = (uint8_t)(0x30 + i); + } + uart_write_bytes(UART_NUM_1, (const char *)tx_buf, LP_UART_READ_RETURN_VALUE_BURST_LEN); + ESP_ERROR_CHECK(uart_wait_tx_done(UART_NUM_1, pdMS_TO_TICKS(500))); + + unity_wait_for_signal("LP UART recv data done"); + + uart_driver_delete(UART_NUM_1); + vTaskDelay(1); +} + +/* LP requests lp_core_uart_read_bytes(..., len = LP_UART_READ_RETURN_VALUE_BURST_LEN, ...). + * Asserts the return value matches len after the HP sends the same byte count. */ +static void test_lp_uart_read_bytes_return_value(void) +{ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + unity_send_signal("LP UART init done"); + unity_wait_for_signal("HP UART init done"); + + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + ulp_test_cmd = LP_CORE_LP_UART_READ_BYTES_RETURN_VALUE_TEST; + vTaskDelay(10); + + unity_send_signal("LP UART recv ready"); + + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(10); + } + + int32_t ret_val = (int32_t)ulp_read_return_value; + ESP_LOGI(TAG, "read_bytes returned %ld, expected %d", (long)ret_val, LP_UART_READ_RETURN_VALUE_BURST_LEN); + TEST_ASSERT_EQUAL(LP_UART_READ_RETURN_VALUE_BURST_LEN, ret_val); + + unity_send_signal("LP UART recv data done"); +} + +/* LP reads into a short user buffer while the HP sends LP_UART_READ_RETURN_VALUE_BURST_LEN + * bytes. Asserts return length is within the user buffer and guard memory is intact. */ +static void test_lp_uart_read_bytes_buffer_bounds(void) +{ + TEST_ASSERT(ESP_OK == lp_core_uart_init(&lp_uart_cfg)); + unity_send_signal("LP UART init done"); + unity_wait_for_signal("HP UART init done"); + + ulp_lp_core_cfg_t lp_cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&lp_cfg, lp_core_main_uart_bin_start, lp_core_main_uart_bin_end); + + ulp_test_cmd = LP_CORE_LP_UART_READ_BYTES_BOUNDS_TEST; + vTaskDelay(10); + + unity_send_signal("LP UART recv ready"); + + while (ulp_test_cmd_reply != LP_CORE_COMMAND_OK) { + vTaskDelay(10); + } + + int32_t ret_val = (int32_t)ulp_read_return_value; + ESP_LOGI(TAG, "read_bytes returned %ld, max allowed %d", (long)ret_val, LP_UART_READ_BOUNDS_USER_BUF_LEN); + TEST_ASSERT_LESS_OR_EQUAL(LP_UART_READ_BOUNDS_USER_BUF_LEN, ret_val); + TEST_ASSERT_GREATER_THAN(0, ret_val); + + lp_uart_read_bounds_guard_t *region = (lp_uart_read_bounds_guard_t *)&ulp_read_bounds_guard_region; + for (int i = 0; i < LP_UART_READ_BOUNDS_GUARD_LEN; i++) { + TEST_ASSERT_EQUAL_HEX8_MESSAGE(LP_UART_READ_BOUNDS_GUARD_PATTERN, region->pre_guard[i], + "pre-guard memory corrupted"); + } + for (int i = 0; i < LP_UART_READ_BOUNDS_GUARD_LEN; i++) { + TEST_ASSERT_EQUAL_HEX8_MESSAGE(LP_UART_READ_BOUNDS_GUARD_PATTERN, region->post_guard[i], + "post-guard memory corrupted"); + } + + unity_send_signal("LP UART recv data done"); +} +/* read_bytes: return value and buffer bounds (LP UART driver) */ +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read_bytes return value test", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_bytes_return_value, hp_uart_send_lp_read_test_burst); +TEST_CASE_MULTIPLE_DEVICES("LP-Core LP-UART read_bytes buffer bounds test", + "[lp_core][uart][test_env=generic_multi_device][timeout=150]", + test_lp_uart_read_bytes_buffer_bounds, hp_uart_send_lp_read_test_burst); From 4155acbdd1412d2d226bb127d6fefc87f2373e31 Mon Sep 17 00:00:00 2001 From: Sudeep Mohanty Date: Wed, 22 Apr 2026 11:17:10 +0200 Subject: [PATCH 8/8] fix(lp_core): repair LP UART read_bytes RX handling Made-with: Cursor --- components/ulp/lp_core/lp_core/lp_core_uart.c | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/components/ulp/lp_core/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core/lp_core_uart.c index b3643bab9ad..3a70b8fc970 100644 --- a/components/ulp/lp_core/lp_core/lp_core_uart.c +++ b/components/ulp/lp_core/lp_core/lp_core_uart.c @@ -180,45 +180,49 @@ int lp_core_uart_read_bytes(uart_port_t lp_uart_num, void *buf, size_t size, int uint32_t timeout_start = ulp_lp_core_get_cpu_cycles(); while (remaining_bytes > 0) { - /* Read from the Rx FIFO - * We set rx_len to -1 to read all bytes in the Rx FIFO - */ - rx_len = -1; - uart_hal_read_rxfifo(&hal, (uint8_t *)(buf + bytes_rcvd), &rx_len); + /* Drain only as many bytes as fit in the remaining buffer space */ + int fifo_len = uart_ll_get_rxfifo_len(hal.dev); + rx_len = (fifo_len < remaining_bytes) ? fifo_len : remaining_bytes; - if (rx_len) { - /* We have some data to read from the Rx FIFO. Check Rx interrupt status */ - intr_status = uart_hal_get_intraw_mask(&hal); - if ((intr_status & UART_INTR_RXFIFO_FULL) || - (intr_status & UART_INTR_RXFIFO_TOUT)) { - /* This is expected. Clear interrupt status and break */ - uart_hal_clr_intsts_mask(&hal, intr_mask); + if (rx_len > 0) { + uart_hal_read_rxfifo(&hal, (uint8_t *)(buf + bytes_rcvd), &rx_len); + bytes_rcvd += rx_len; + remaining_bytes -= rx_len; + + /* RXFIFO_FULL / RXFIFO_TOUT raw bits are sticky; acknowledge them + * so they do not short-circuit the next iteration. */ + uart_hal_clr_intsts_mask(&hal, LP_UART_RX_INT_FLAG); + + if (remaining_bytes <= 0) { break; - } else if ((intr_status & UART_INTR_RXFIFO_OVF)) { - /* We reset the Rx FIFO if it overflows */ + } + + /* FIFO overflow and parity/framing errors are terminal */ + intr_status = uart_hal_get_intraw_mask(&hal); + if (intr_status & UART_INTR_RXFIFO_OVF) { uart_hal_clr_intsts_mask(&hal, intr_mask); uart_hal_rxfifo_rst(&hal); break; - } else if ((intr_status & LP_UART_ERR_INT_FLAG)) { - /* Transaction error. Abort */ + } else if (intr_status & LP_UART_ERR_INT_FLAG) { uart_hal_clr_intsts_mask(&hal, intr_mask); return -1; } - /* Update the byte counters */ - bytes_rcvd += rx_len; - remaining_bytes -= rx_len; + /* Progress was made; restart the timeout window so callers with + * a finite timeout can tolerate gaps between bursts. */ + timeout_start = ulp_lp_core_get_cpu_cycles(); } else { - /* We have no data to read from the Rx FIFO. Check for transaction timeout */ + /* FIFO empty. Honour the caller's timeout. */ ret = lp_core_uart_check_timeout(intr_mask, timeout, timeout_start); if (ret == ESP_ERR_TIMEOUT) { - /* Timeout. Clear interrupt status and break */ uart_hal_clr_intsts_mask(&hal, intr_mask); break; } } } - /* Return the number of bytes received */ - return bytes_rcvd; + if (bytes_rcvd > size) { + bytes_rcvd = size; + } + return (int)bytes_rcvd; }