From df0a515b86764c86c5b29b234482b4654a6b49e6 Mon Sep 17 00:00:00 2001 From: Tim Pambor Date: Mon, 11 Aug 2025 11:16:16 +0200 Subject: [PATCH 1/2] serial: uart_native_pty: IRQ support Add support for the interrupt-driven API. Interrupts are emulated using a polling thread. Signed-off-by: Tim Pambor --- boards/native/native_sim/doc/index.rst | 3 +- drivers/serial/Kconfig.native_pty | 1 + drivers/serial/uart_native_pty.c | 275 ++++++++++++++++++++---- drivers/serial/uart_native_pty_bottom.c | 90 ++++++-- drivers/serial/uart_native_pty_bottom.h | 1 + 5 files changed, 309 insertions(+), 61 deletions(-) diff --git a/boards/native/native_sim/doc/index.rst b/boards/native/native_sim/doc/index.rst index 1c085cc09c196..d7b6cea697a14 100644 --- a/boards/native/native_sim/doc/index.rst +++ b/boards/native/native_sim/doc/index.rst @@ -564,8 +564,7 @@ program ends, one could do: $ zephyr.exe --uart_attach_uart_cmd='ln -s %s /tmp/somename' ; rm /tmp/somename -This driver supports poll mode or async mode with :kconfig:option:`CONFIG_UART_ASYNC_API`. -Interrupt mode is not supported. +This driver supports poll mode, interrupt mode and async mode. Neither runtime configuration or line control are supported. .. _native_tty_uart: diff --git a/drivers/serial/Kconfig.native_pty b/drivers/serial/Kconfig.native_pty index 9420ce4e13fcc..29acd627e2816 100644 --- a/drivers/serial/Kconfig.native_pty +++ b/drivers/serial/Kconfig.native_pty @@ -7,6 +7,7 @@ config UART_NATIVE_PTY depends on (DT_HAS_ZEPHYR_NATIVE_PTY_UART_ENABLED || DT_HAS_ZEPHYR_NATIVE_POSIX_UART_ENABLED) select SERIAL_HAS_DRIVER select SERIAL_SUPPORT_ASYNC + select SERIAL_SUPPORT_INTERRUPT help This enables a PTY based UART driver for the POSIX ARCH with up to 2 UARTs. For the first UART port, the driver can be configured diff --git a/drivers/serial/uart_native_pty.c b/drivers/serial/uart_native_pty.c index 84dacf5c253cf..ae81b5e133daf 100644 --- a/drivers/serial/uart_native_pty.c +++ b/drivers/serial/uart_native_pty.c @@ -43,6 +43,7 @@ struct native_pty_status { int out_fd; /* File descriptor used for output */ int in_fd; /* File descriptor used for input */ bool on_stdinout; /* This UART is connected to a PTY and not STDIN/OUT */ + bool stdin_disconnected; bool auto_attach; /* For PTY, attach a terminal emulator automatically */ char *auto_attach_cmd; /* If auto_attach, which command to launch the terminal emulator */ @@ -64,6 +65,18 @@ struct native_pty_status { K_KERNEL_STACK_MEMBER(rx_stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE); } async; #endif /* CONFIG_UART_ASYNC_API */ +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + struct { + bool tx_enabled; + bool rx_enabled; + uart_irq_callback_user_data_t callback; + void *cb_data; + /* Instance-specific IRQ emulation thread. */ + struct k_thread poll_thread; + /* Stack for IRQ emulation thread */ + K_KERNEL_STACK_MEMBER(poll_stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE); + } irq; +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; static void np_uart_poll_out(const struct device *dev, unsigned char out_char); @@ -81,6 +94,22 @@ static int np_uart_rx_enable(const struct device *dev, uint8_t *buf, size_t len, static int np_uart_rx_disable(const struct device *dev); #endif /* CONFIG_UART_ASYNC_API */ +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static int np_uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size); +static int np_uart_fifo_read(const struct device *dev, uint8_t *rx_data, const int size); +static void np_uart_irq_tx_enable(const struct device *dev); +static void np_uart_irq_tx_disable(const struct device *dev); +static int np_uart_irq_tx_ready(const struct device *dev); +static int np_uart_irq_tx_complete(const struct device *dev); +static void np_uart_irq_rx_enable(const struct device *dev); +static void np_uart_irq_rx_disable(const struct device *dev); +static int np_uart_irq_rx_ready(const struct device *dev); +static int np_uart_irq_is_pending(const struct device *dev); +static int np_uart_irq_update(const struct device *dev); +static void np_uart_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, + void *cb_data); +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + static DEVICE_API(uart, np_uart_driver_api) = { .poll_out = np_uart_poll_out, .poll_in = np_uart_poll_in, @@ -92,6 +121,20 @@ static DEVICE_API(uart, np_uart_driver_api) = { .rx_enable = np_uart_rx_enable, .rx_disable = np_uart_rx_disable, #endif /* CONFIG_UART_ASYNC_API */ +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = np_uart_fifo_fill, + .fifo_read = np_uart_fifo_read, + .irq_tx_enable = np_uart_irq_tx_enable, + .irq_tx_disable = np_uart_irq_tx_disable, + .irq_tx_ready = np_uart_irq_tx_ready, + .irq_tx_complete = np_uart_irq_tx_complete, + .irq_rx_enable = np_uart_irq_rx_enable, + .irq_rx_disable = np_uart_irq_rx_disable, + .irq_rx_ready = np_uart_irq_rx_ready, + .irq_is_pending = np_uart_irq_is_pending, + .irq_update = np_uart_irq_update, + .irq_callback_set = np_uart_irq_callback_set, +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ }; #define NATIVE_PTY_INSTANCE(inst) \ @@ -192,61 +235,49 @@ static void np_uart_poll_out(const struct device *dev, unsigned char out_char) } /** - * @brief Poll the device for input. + * @brief Poll the device for up to len input characters * * @param dev UART device structure. * @param p_char Pointer to character. * - * @retval 0 If a character arrived and was stored in p_char + * @retval > 0 If a character arrived and was stored in p_char + * @retval == 0 If a character arrived but len was 0 * @retval -1 If no character was available to read */ -static int np_uart_stdin_poll_in(const struct device *dev, unsigned char *p_char) +static int np_uart_poll_in_n(const struct device *dev, unsigned char *p_char, int len) { - int in_f = ((struct native_pty_status *)dev->data)->in_fd; - static bool disconnected; - int rc; + int rc = -1; + struct native_pty_status *data = (struct native_pty_status *)dev->data; + int in_f = data->in_fd; - if (disconnected == true) { + if (data->stdin_disconnected) { return -1; } - rc = np_uart_stdin_poll_in_bottom(in_f, p_char, 1); - if (rc == -2) { - disconnected = true; + if (data->on_stdinout) { + rc = np_uart_stdin_poll_in_bottom(in_f, p_char, len); + + if (rc == -2) { + data->stdin_disconnected = true; + return -1; + } + } else { + rc = np_uart_pty_poll_in_bottom(in_f, p_char, len); } - return rc == 1 ? 0 : -1; + + return rc; } -/** - * @brief Poll the device for input. - * - * @param dev UART device structure. - * @param p_char Pointer to character. - * - * @retval 0 If a character arrived and was stored in p_char - * @retval -1 If no character was available to read - */ -static int np_uart_pty_poll_in(const struct device *dev, unsigned char *p_char) +static int np_uart_poll_in(const struct device *dev, unsigned char *p_char) { - int n = -1; - int in_f = ((struct native_pty_status *)dev->data)->in_fd; + int ret = np_uart_poll_in_n(dev, p_char, 1); - n = nsi_host_read(in_f, p_char, 1); - if (n == -1) { + if (ret == -1) { return -1; } return 0; } -static int np_uart_poll_in(const struct device *dev, unsigned char *p_char) -{ - if (((struct native_pty_status *)dev->data)->on_stdinout) { - return np_uart_stdin_poll_in(dev, p_char); - } else { - return np_uart_pty_poll_in(dev, p_char); - } -} - #ifdef CONFIG_UART_ASYNC_API static int np_uart_callback_set(const struct device *dev, uart_callback_t callback, void *user_data) @@ -337,8 +368,7 @@ static void native_pty_uart_async_poll_function(void *arg1, void *arg2, void *ar ARG_UNUSED(arg3); while (data->async.rx_len) { - rc = np_uart_stdin_poll_in_bottom(data->in_fd, data->async.rx_buf, - data->async.rx_len); + rc = np_uart_poll_in_n(dev, data->async.rx_buf, data->async.rx_len); if (rc > 0) { /* Data received */ evt.type = UART_RX_RDY; @@ -403,6 +433,179 @@ static int np_uart_rx_disable(const struct device *dev) #endif /* CONFIG_UART_ASYNC_API */ +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static void np_uart_irq_handler(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + if (data->irq.callback) { + data->irq.callback(dev, data->irq.cb_data); + } else { + WARN("No callback!\n"); + } +} + +/* + * Emulate uart interrupts using a polling thread + */ +static void np_uart_irq_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + struct device *dev = (struct device *)arg1; + struct native_pty_status *data = dev->data; + + while (1) { + if (data->irq.rx_enabled) { + int ret = np_uart_poll_in_n(dev, NULL, 0); + + if (ret == 0) { + np_uart_irq_handler(dev); + } else if (ret == -1) { + k_sleep(K_MSEC(1)); + } else { + WARN("Poll returned error %d\n", ret); + } + } + if (data->irq.tx_enabled) { + np_uart_irq_handler(dev); + } + if (!data->irq.tx_enabled && !data->irq.rx_enabled) { + break; /* No IRQs enabled, exit the thread */ + } + } +} + +static void np_uart_irq_thread_start(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + /* Create a thread which will wait for data - replacement for IRQ */ + k_thread_create(&data->irq.poll_thread, data->irq.poll_stack, + K_KERNEL_STACK_SIZEOF(data->irq.poll_stack), + np_uart_irq_thread, + (void *)dev, NULL, NULL, + K_HIGHEST_THREAD_PRIO, 0, K_NO_WAIT); +} + +static void np_uart_irq_thread_stop(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + /* Wait for IRQ thread to terminate */ + k_thread_join(&data->irq.poll_thread, K_FOREVER); +} + +static int np_uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) +{ + for (int i = 0; i < size; i++) { + np_uart_poll_out(dev, tx_data[i]); + } + + return size; +} + +static int np_uart_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) +{ + return np_uart_poll_in_n(dev, rx_data, size); +} + +static int np_uart_irq_tx_ready(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + return data->irq.tx_enabled ? 1 : 0; +} + +static int np_uart_irq_tx_complete(const struct device *dev) +{ + ARG_UNUSED(dev); + + return 1; +} + +static void np_uart_irq_tx_enable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + bool start_thread = !data->irq.rx_enabled && !data->irq.tx_enabled; + + data->irq.tx_enabled = true; + + if (start_thread) { + np_uart_irq_thread_start(dev); + } +} + +static void np_uart_irq_tx_disable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + data->irq.tx_enabled = false; + + if (!data->irq.rx_enabled && !data->irq.tx_enabled) { + np_uart_irq_thread_stop(dev); + } +} + +static void np_uart_irq_rx_enable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + bool start_thread = !data->irq.rx_enabled && !data->irq.tx_enabled; + + data->irq.rx_enabled = true; + + if (start_thread) { + np_uart_irq_thread_start(dev); + } +} + +static void np_uart_irq_rx_disable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + data->irq.rx_enabled = false; + + if (!data->irq.rx_enabled && !data->irq.tx_enabled) { + np_uart_irq_thread_stop(dev); + } +} + +static int np_uart_irq_rx_ready(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + if (data->irq.rx_enabled && np_uart_poll_in_n(dev, NULL, 0) == 0) { + return 1; + } + return 0; +} + +static int np_uart_irq_is_pending(const struct device *dev) +{ + return np_uart_irq_rx_ready(dev) || + np_uart_irq_tx_ready(dev); +} + +static int np_uart_irq_update(const struct device *dev) +{ + ARG_UNUSED(dev); + + return 1; +} + +static void np_uart_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, + void *cb_data) +{ + struct native_pty_status *data = dev->data; + + data->irq.callback = cb; + data->irq.cb_data = cb_data; +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + #define NATIVE_PTY_SET_AUTO_ATTACH_CMD(inst, cmd) \ native_pty_status_##inst.auto_attach_cmd = cmd; diff --git a/drivers/serial/uart_native_pty_bottom.c b/drivers/serial/uart_native_pty_bottom.c index 3ff7ca59479b0..f86b7c542b82a 100644 --- a/drivers/serial/uart_native_pty_bottom.c +++ b/drivers/serial/uart_native_pty_bottom.c @@ -27,27 +27,8 @@ #define ERROR nsi_print_error_and_exit #define WARN nsi_print_warning -/** - * @brief Poll the device for input. - * - * @param in_f Input file descriptor - * @param p_char Pointer to character. - * @param len Maximum number of characters to read. - * - * @retval >0 Number of characters actually read - * @retval -1 If no character was available to read - * @retval -2 if the stdin is disconnected - */ -int np_uart_stdin_poll_in_bottom(int in_f, unsigned char *p_char, int len) +static int np_uart_poll_in_bottom(int in_f, unsigned char *p_char, int len) { - if (feof(stdin)) { - /* - * The stdinput is fed from a file which finished or the user - * pressed Ctrl+D - */ - return -2; - } - int n = -1; int ready; @@ -65,14 +46,77 @@ int np_uart_stdin_poll_in_bottom(int in_f, unsigned char *p_char, int len) ERROR("%s: Error on select ()\n", __func__); } - n = read(in_f, p_char, len); - if ((n == -1) || (n == 0)) { - return -1; + if (len <= 0) { + return 0; } + n = read(in_f, p_char, len); + return n; } +/** + * @brief Poll the device for input. + * + * Note: When called with len == 0, the function checks if any + * data is available or if the input was disconnected. + * However, it cannot distinguish between these two cases. + * In both scenarios, the function will return 0. + * To determine if new data is available or the input was + * disconnected, this function must be called with len > 0. + * + * @param in_f Input file descriptor + * @param p_char Pointer to character. + * @param len Maximum number of characters to read. + * + * @retval >0 Number of characters actually read + * @retval == 0 If a character is available to read but len was 0 + * @retval -1 If no character was available to read + * @retval -2 if the stdin is disconnected + */ +int np_uart_stdin_poll_in_bottom(int in_f, unsigned char *p_char, int len) +{ + int rc = np_uart_poll_in_bottom(in_f, p_char, len); + + if ((len > 0) && (rc == 0)) { + /* EOF: stdin was disconnected. + * + * When len > 0, we attempt to read at least one character. + * If rc == 0, it means no bytes were read, which in this + * context indicates that the input stream (stdin) has been + * disconnected. + * + * Note: If len == 0 and the input is disconnected, + * np_uart_poll_in_bottom will still return 0, + * making it indistinguishable from the case where + * a character is available but not read due to + * len == 0. + */ + return -2; + } + + return rc; +} + +/** + * @brief Poll the device for input. + * + * Note: The function can be called with len == 0, to check if + * any data is available. + * + * @param in_f Input file descriptor + * @param p_char Pointer to character. + * @param len Maximum number of characters to read. + * + * @retval >0 Number of characters actually read + * @retval == 0 If a character is available to read but len was 0 + * @retval -1 If no character was available to read + */ +int np_uart_pty_poll_in_bottom(int in_f, unsigned char *p_char, int len) +{ + return np_uart_poll_in_bottom(in_f, p_char, len); +} + /** * @brief Check if the output descriptor has something connected to the slave side * diff --git a/drivers/serial/uart_native_pty_bottom.h b/drivers/serial/uart_native_pty_bottom.h index 69af6d3fa8d0f..71e578f0fbe3f 100644 --- a/drivers/serial/uart_native_pty_bottom.h +++ b/drivers/serial/uart_native_pty_bottom.h @@ -21,6 +21,7 @@ extern "C" { /* Note: None of these functions are public interfaces. But internal to the native ptty driver */ int np_uart_stdin_poll_in_bottom(int in_f, unsigned char *p_char, int len); +int np_uart_pty_poll_in_bottom(int in_f, unsigned char *p_char, int len); int np_uart_slave_connected(int fd); int np_uart_open_pty(const char *uart_name, const char *auto_attach_cmd, bool do_auto_attach, bool wait_pts); From b2de0dc9fae63b78826c97f942c0c03101db6eff Mon Sep 17 00:00:00 2001 From: Tim Pambor Date: Mon, 11 Aug 2025 11:16:16 +0200 Subject: [PATCH 2/2] samples: sensor_shell: Wait for prompt before sending command Wait for prompt before sending command to ensure shell is ready. Signed-off-by: Tim Pambor --- samples/sensor/sensor_shell/pytest/test_sensor_shell.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/samples/sensor/sensor_shell/pytest/test_sensor_shell.py b/samples/sensor/sensor_shell/pytest/test_sensor_shell.py index 49627d6f4a2b9..276d038ce0876 100644 --- a/samples/sensor/sensor_shell/pytest/test_sensor_shell.py +++ b/samples/sensor/sensor_shell/pytest/test_sensor_shell.py @@ -11,6 +11,7 @@ def test_sensor_shell_info(shell: Shell): logger.info('send "sensor info" command') + shell.wait_for_prompt() lines = shell.exec_command('sensor info') assert any(['device name: sensor@0' in line for line in lines]), 'expected response not found' assert any(['device name: sensor@1' in line for line in lines]), 'expected response not found' @@ -25,6 +26,7 @@ def test_sensor_shell_get(shell: Shell): # for-loop in `parse_named_int()` will go through everything for channel in range(59): logger.info(f'channel {channel}') + shell.wait_for_prompt() lines = shell.exec_command(f'sensor get sensor@0 {channel}') assert any([f'channel type={channel}' in line for line in lines]), 'expected response not found' @@ -34,9 +36,11 @@ def test_sensor_shell_get(shell: Shell): def test_sensor_shell_attr_get(shell: Shell): logger.info('send "sensor attr_get" command') + shell.wait_for_prompt() lines = shell.exec_command('sensor attr_get sensor@0 co2 sampling_frequency') assert any(['sensor@0(channel=co2, attr=sampling_frequency)' in line for line in lines]), 'expected response not found' + shell.wait_for_prompt() lines = shell.exec_command('sensor attr_get sensor@1 54 3') assert any(['sensor@1(channel=gauge_state_of_health, attr=slope_th)' in line for line in lines]), 'expected response not found' @@ -46,10 +50,12 @@ def test_sensor_shell_attr_get(shell: Shell): def test_sensor_shell_attr_set(shell: Shell): logger.info('send "sensor attr_set" command') + shell.wait_for_prompt() lines = shell.exec_command('sensor attr_set sensor@0 co2 sampling_frequency 1') expected_line = 'sensor@0 channel=co2, attr=sampling_frequency set to value=1' assert any([expected_line in line for line in lines]), 'expected response not found' + shell.wait_for_prompt() lines = shell.exec_command('sensor attr_set sensor@1 54 3 1') expected_line = 'sensor@1 channel=gauge_state_of_health, attr=slope_th set to value=1' assert any([expected_line in line for line in lines]), 'expected response not found' @@ -60,10 +66,12 @@ def test_sensor_shell_attr_set(shell: Shell): def test_sensor_shell_trig(shell: Shell): logger.info('send "sensor trig" command') + shell.wait_for_prompt() lines = shell.exec_command('sensor trig sensor@0 on data_ready') expected_line = 'Enabled trigger idx=1 data_ready on device sensor@0' assert any([expected_line in line for line in lines]), 'expected response not found' + shell.wait_for_prompt() lines = shell.exec_command('sensor trig sensor@0 off data_ready') expected_line = 'Disabled trigger idx=1 data_ready on device sensor@0' assert any([expected_line in line for line in lines]), 'expected response not found'