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; } diff --git a/components/ulp/lp_core/lp_core_uart.c b/components/ulp/lp_core/lp_core_uart.c index 5bd7bc97263..95933304b6d 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 */ @@ -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; } @@ -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 */ @@ -145,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; } 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 bb2744406f7..83b5d5747fb 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, @@ -27,6 +61,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 25234f94419..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 @@ -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 */ @@ -18,7 +18,9 @@ #include "esp_log.h" #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 */ @@ -26,36 +28,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,38 +71,149 @@ 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); } -/* LP UART default config */ +TEST_CASE("LP-Core LP-UART data_bits parameter validation", "[lp_core]") +{ + /* 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, + }; + 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 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 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; @@ -105,544 +221,441 @@ 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)); + /* Discard any stale bytes that arrived while the pin mux was settling */ + uart_flush_input(UART_NUM_1); +} - /* 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]; + /* 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 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, 100 / 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, +#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, 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 */ + /* 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; #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"); + TEST_ASSERT_EQUAL(ESP_OK, lp_core_uart_init(cfg)); + unity_send_signal("LP UART init done"); + unity_wait_for_signal("HP 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; + 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 - - /* 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 */ - 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 */ + /* 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(); - /* 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 */ + 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"); } +/* ========================================================================= + * 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, +#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, + 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; + + 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); + + 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)"); + 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"); + + 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"); + + /* 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, 100 / 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 */ + uart_flush_input(UART_NUM_1); 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, 100 / 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) { @@ -652,8 +665,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)); @@ -673,64 +684,382 @@ 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); + + 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_unsigned_integer = test_unsigned_integer; + ulp_test_hex = test_hex; + ulp_test_character = test_character; + +#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 */ + + ESP_LOGI(TAG, "LP Core print test start"); + ulp_test_cmd = LP_CORE_LP_UART_PRINT_TEST; + +#if SOC_LIGHT_SLEEP_SUPPORTED + esp_light_sleep_start(); +#endif /* SOC_LIGHT_SLEEP_SUPPORTED */ + + TEST_ASSERT_EQUAL(LP_CORE_COMMAND_OK, ulp_test_cmd_reply); +} + +/* ========================================================================= + * 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); + +/* 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); - /* 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_unsigned_integer = test_unsigned_integer; - ulp_test_hex = test_hex; - ulp_test_character = test_character; - - /* 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, "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 */ - + ulp_test_cmd = LP_CORE_LP_UART_READ_BYTES_RETURN_VALUE_TEST; vTaskDelay(10); - TEST_ASSERT_EQUAL(ulp_test_cmd_reply, LP_CORE_COMMAND_OK); + + 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"); } -/* 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); +/* 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); 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..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 @@ -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 @@ -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,16 +60,36 @@ 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) +@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. + 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 @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')