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..0e1dbe626dac7 100644 --- a/drivers/serial/uart_native_pty.c +++ b/drivers/serial/uart_native_pty.c @@ -16,6 +16,7 @@ #include #include #include +#include #include /* native_sim command line options header */ #include #include @@ -43,6 +44,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,8 +66,24 @@ 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; + char char_store; + bool char_ready; + atomic_t thread_started; + /* 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 int np_uart_poll_out_n(struct native_pty_status *d, const unsigned char *buf, size_t len); static void np_uart_poll_out(const struct device *dev, unsigned char out_char); static int np_uart_poll_in(const struct device *dev, unsigned char *p_char); static int np_uart_init(const struct device *dev); @@ -81,6 +99,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 +126,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) \ @@ -163,88 +211,93 @@ static int np_uart_init(const struct device *dev) } /* - * @brief Output a character towards the serial port + * @brief Output len characters towards the serial port * * @param dev UART device struct - * @param out_char Character to send. + * @param buf Pointer to the characters to send. + * @param len Number of characters to send */ -static void np_uart_poll_out(const struct device *dev, unsigned char out_char) +static int np_uart_poll_out_n(struct native_pty_status *d, const unsigned char *buf, size_t len) { int ret; - struct native_pty_status *d = (struct native_pty_status *)dev->data; if (d->wait_pts) { while (1) { - int rc = np_uart_slave_connected(d->out_fd); + ret = np_uart_slave_connected(d->out_fd); - if (rc == 1) { + if (ret == 1) { break; } k_sleep(K_MSEC(100)); } } - /* The return value of write() cannot be ignored (there is a warning) - * but we do not need the return value for anything. - */ - ret = nsi_host_write(d->out_fd, &out_char, 1); - (void) ret; + ret = nsi_host_write(d->out_fd, buf, len); + + return ret; } -/** - * @brief Poll the device for input. - * - * @param dev UART device structure. - * @param p_char Pointer to character. +/* + * @brief Output a character towards the serial port * - * @retval 0 If a character arrived and was stored in p_char - * @retval -1 If no character was available to read + * @param dev UART device struct + * @param out_char Character to send. */ -static int np_uart_stdin_poll_in(const struct device *dev, unsigned char *p_char) +static void np_uart_poll_out(const struct device *dev, unsigned char out_char) { - int in_f = ((struct native_pty_status *)dev->data)->in_fd; - static bool disconnected; - int rc; - - if (disconnected == true) { - return -1; - } - - rc = np_uart_stdin_poll_in_bottom(in_f, p_char, 1); - if (rc == -2) { - disconnected = true; - } - return rc == 1 ? 0 : -1; + (void)np_uart_poll_out_n((struct native_pty_status *)dev->data, &out_char, 1); } /** - * @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 -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_read_n(struct native_pty_status *data, unsigned char *p_char, int len) { - int n = -1; - int in_f = ((struct native_pty_status *)dev->data)->in_fd; + int rc = -1; + int in_f = data->in_fd; - n = nsi_host_read(in_f, p_char, 1); - if (n == -1) { + if (len <= 0) { return -1; } - return 0; + + if (data->on_stdinout) { + if (data->stdin_disconnected) { + return -1; + } + rc = np_uart_stdin_read_bottom(in_f, p_char, len); + + if (rc == -2) { + data->stdin_disconnected = true; + return -1; + } + + } else { + rc = nsi_host_read(in_f, p_char, len); + } + + if (rc > 0) { + return rc; + } + + return -1; } 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); + struct native_pty_status *data = dev->data; + + int ret = np_uart_read_n(data, p_char, 1); + + if (ret == -1) { + return -1; } + return 0; } #ifdef CONFIG_UART_ASYNC_API @@ -337,8 +390,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_read_n(data, data->async.rx_buf, data->async.rx_len); if (rc > 0) { /* Data received */ evt.type = UART_RX_RDY; @@ -403,6 +455,222 @@ 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 { + ERROR("%s: No callback registered\n", __func__); + } +} + +static void np_uart_irq_read_1_ahead(struct native_pty_status *data) +{ + int ret = np_uart_read_n(data, &data->irq.char_store, 1); + + if (ret == 1) { + data->irq.char_ready = true; + } + + if (data->stdin_disconnected) { + /* There won't be any more data ever */ + data->irq.rx_enabled = false; + } +} + +/* + * 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) { + if (!data->irq.char_ready) { + np_uart_irq_read_1_ahead(data); + } + + if (data->irq.char_ready) { + np_uart_irq_handler(dev); + } + } + if (data->irq.tx_enabled) { + np_uart_irq_handler(dev); + } + + if ((data->irq.tx_enabled) || + ((data->irq.rx_enabled) && (data->irq.char_ready))) { + /* There is pending work. Let's handle it right away */ + continue; + } + + k_timeout_t wait = K_FOREVER; + + if (data->irq.rx_enabled) { + wait = K_MSEC(10); + } + (void)k_sleep(wait); + } +} + +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 int np_uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) +{ + return np_uart_poll_out_n((struct native_pty_status *)dev->data, tx_data, size); +} + +static int np_uart_fifo_read(const struct device *dev, uint8_t *rx_data, int size) +{ + uint32_t len = 0; + int ret; + struct native_pty_status *data = dev->data; + + if ((size <= 0) || data->stdin_disconnected) { + return 0; + } + + if (data->irq.char_ready) { + rx_data[0] = data->irq.char_store; + rx_data++; + size--; + len = 1; + data->irq.char_ready = false; + /* Note this native_sim driver code cannot be interrupted, + * so there is no race with np_uart_irq_thread() + */ + } + + ret = np_uart_read_n(data, rx_data, size); + + if (ret > 0) { + len += ret; + np_uart_irq_read_1_ahead(data); + } + + return len; +} + +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 kick_thread = !data->irq.tx_enabled; + + data->irq.tx_enabled = true; + + if (!atomic_set(&data->irq.thread_started, 1)) { + np_uart_irq_thread_start(dev); + } + + if (kick_thread) { + /* Let's ensure the thread wakes to allow the Tx right away */ + k_wakeup(&data->irq.poll_thread); + } +} + +static void np_uart_irq_tx_disable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + data->irq.tx_enabled = false; +} + +static void np_uart_irq_rx_enable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + if (data->stdin_disconnected) { + /* There won't ever be data => we ignore the request */ + return; + } + + bool kick_thread = !data->irq.rx_enabled; + + data->irq.rx_enabled = true; + + if (!atomic_set(&data->irq.thread_started, 1)) { + np_uart_irq_thread_start(dev); + } + + if (kick_thread) { + /* Let's ensure the thread wakes to try to check for data */ + k_wakeup(&data->irq.poll_thread); + } +} + +static void np_uart_irq_rx_disable(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + data->irq.rx_enabled = false; +} + +static int np_uart_irq_rx_ready(const struct device *dev) +{ + struct native_pty_status *data = dev->data; + + if (data->irq.rx_enabled && data->irq.char_ready) { + 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..7b5144a111ce1 100644 --- a/drivers/serial/uart_native_pty_bottom.c +++ b/drivers/serial/uart_native_pty_bottom.c @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -38,39 +38,35 @@ * @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 np_uart_stdin_read_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; - fd_set readfds; - static struct timeval timeout; /* just zero */ - FD_ZERO(&readfds); - FD_SET(in_f, &readfds); + struct pollfd fds = {.fd = in_f, .events = POLLIN}; - ready = select(in_f+1, &readfds, NULL, NULL, &timeout); + ready = poll(&fds, 1, 0); if (ready == 0) { return -1; } else if (ready == -1) { - ERROR("%s: Error on select ()\n", __func__); + ERROR("%s: Error on poll ()\n", __func__); } - n = read(in_f, p_char, len); - if ((n == -1) || (n == 0)) { - return -1; + if (len == 0) { + return 0; } - return n; + n = read(in_f, p_char, len); + + if (n == 0) { + /* Attempting to read > 0 but getting 0 characters back + * indicates we reached EOF + */ + return -2; + } else { + return n; + } } /** diff --git a/drivers/serial/uart_native_pty_bottom.h b/drivers/serial/uart_native_pty_bottom.h index 69af6d3fa8d0f..cc9d596219c96 100644 --- a/drivers/serial/uart_native_pty_bottom.h +++ b/drivers/serial/uart_native_pty_bottom.h @@ -20,7 +20,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_stdin_read_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); diff --git a/subsys/shell/backends/Kconfig.backends b/subsys/shell/backends/Kconfig.backends index 013de5ba91afd..c68ec201197c2 100644 --- a/subsys/shell/backends/Kconfig.backends +++ b/subsys/shell/backends/Kconfig.backends @@ -41,7 +41,7 @@ config SHELL_PROMPT_UART config SHELL_BACKEND_SERIAL_INTERRUPT_DRIVEN bool "Interrupt driven" - default y + default y if !UART_NATIVE_PTY depends on SERIAL_SUPPORT_INTERRUPT config SHELL_ASYNC_API