From 8cdcb2c1677a666a59727d3f75083b6318c07ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Stenberg?= Date: Mon, 12 Feb 2024 11:03:20 +0100 Subject: [PATCH] uart_native_tty: Emulate an interrupt driven uart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emulate SERIAL_SUPPORT_INTERRUPT for UART_NATIVE_TTY, using a thread that polls the tty and invokes the callback. This allows interrupt-driven subsystems such as modbus to use a native tty, which is useful for testing purposes. Signed-off-by: Björn Stenberg --- boards/posix/native_sim/doc/index.rst | 5 +- drivers/serial/Kconfig.native_tty | 1 + drivers/serial/uart_native_tty.c | 180 +++++++++++++++++++++++- drivers/serial/uart_native_tty_bottom.c | 8 ++ drivers/serial/uart_native_tty_bottom.h | 11 ++ 5 files changed, 203 insertions(+), 2 deletions(-) diff --git a/boards/posix/native_sim/doc/index.rst b/boards/posix/native_sim/doc/index.rst index 9621ba5d2c..9c77269169 100644 --- a/boards/posix/native_sim/doc/index.rst +++ b/boards/posix/native_sim/doc/index.rst @@ -567,13 +567,16 @@ Interaction with serial ports can be configured in several different ways: options override values from the devicetree. * The rest of the configuration options such as number of data and stop bits, parity, as well as baud rate can be set at runtime with ``uart_configure``. +* This driver can emulate an interrupt-driven UART by enabling + :kconfig:option:`CONFIG_UART_INTERRUPT_DRIVEN`. Multiple instances of such uart drivers are supported. The :zephyr:code-sample:`uart-native-tty` sample app provides a working example of the driver. -This driver only supports poll mode. Interrupt and async mode are not supported. +This driver only supports poll mode and interrupt mode. Async mode is not +supported. It has runtime configuration support, but no line control support. .. _native_sim_backends: diff --git a/drivers/serial/Kconfig.native_tty b/drivers/serial/Kconfig.native_tty index a8bcce360e..c0bf6429f6 100644 --- a/drivers/serial/Kconfig.native_tty +++ b/drivers/serial/Kconfig.native_tty @@ -5,3 +5,4 @@ config UART_NATIVE_TTY default y depends on DT_HAS_ZEPHYR_NATIVE_TTY_UART_ENABLED select SERIAL_HAS_DRIVER + select SERIAL_SUPPORT_INTERRUPT diff --git a/drivers/serial/uart_native_tty.c b/drivers/serial/uart_native_tty.c index 5f6de6e0cb..dc19f2a10d 100644 --- a/drivers/serial/uart_native_tty.c +++ b/drivers/serial/uart_native_tty.c @@ -39,12 +39,30 @@ struct native_tty_data { int cmd_baudrate; /* Serial port set from the command line. If NULL, it was not set. */ char *cmd_serial_port; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + /* Emulated tx irq is enabled. */ + bool tx_irq_enabled; + /* Emulated rx irq is enabled. */ + bool rx_irq_enabled; + /* IRQ callback */ + uart_irq_callback_user_data_t callback; + /* IRQ callback data */ + void *cb_data; +#endif }; struct native_tty_config { struct uart_config uart_config; }; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static struct k_thread rx_thread; +static K_KERNEL_STACK_DEFINE(rx_stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE); +#define NATIVE_TTY_INIT_LEVEL POST_KERNEL +#else +#define NATIVE_TTY_INIT_LEVEL PRE_KERNEL_1 +#endif + /** * @brief Convert from uart_config to native_tty_bottom_cfg eqvivalent struct * @@ -157,6 +175,148 @@ static int native_tty_configure(const struct device *dev, const struct uart_conf return native_tty_configure_bottom(fd, &bottom_cfg); } +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static int native_tty_uart_fifo_fill(const struct device *dev, + const uint8_t *tx_data, + int size) +{ + struct native_tty_data *data = dev->data; + + return nsi_host_write(data->fd, (void *)tx_data, size); +} + +static int native_tty_uart_fifo_read(const struct device *dev, + uint8_t *rx_data, + const int size) +{ + struct native_tty_data *data = dev->data; + + return nsi_host_read(data->fd, rx_data, size); +} + +static int native_tty_uart_irq_tx_ready(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + return data->tx_irq_enabled ? 1 : 0; +} + +static int native_tty_uart_irq_tx_complete(const struct device *dev) +{ + ARG_UNUSED(dev); + return 1; +} + +static void native_tty_uart_irq_tx_enable(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + data->tx_irq_enabled = true; +} + +static void native_tty_uart_irq_tx_disable(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + data->tx_irq_enabled = false; +} + +static void native_tty_uart_irq_rx_enable(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + data->rx_irq_enabled = true; +} + +static void native_tty_uart_irq_rx_disable(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + data->rx_irq_enabled = false; +} + +static int native_tty_uart_irq_rx_ready(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + if (data->rx_irq_enabled && native_tty_poll_bottom(data->fd) == 1) { + return 1; + } + return 0; +} + +static int native_tty_uart_irq_is_pending(const struct device *dev) +{ + return native_tty_uart_irq_rx_ready(dev) || + native_tty_uart_irq_tx_ready(dev); +} + +static int native_tty_uart_irq_update(const struct device *dev) +{ + ARG_UNUSED(dev); + return 1; +} + +static void native_tty_uart_irq_handler(const struct device *dev) +{ + struct native_tty_data *data = dev->data; + + if (data->callback) { + data->callback(dev, data->cb_data); + } else { + WARN("No callback!\n"); + } +} + +/* + * Emulate uart interrupts using a polling thread + */ +void native_tty_uart_irq_function(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + struct device *dev = (struct device *)arg1; + struct native_tty_data *data = dev->data; + + while (1) { + if (data->rx_irq_enabled) { + int ret = native_tty_poll_bottom(data->fd); + + if (ret == 1) { + native_tty_uart_irq_handler(dev); + } else if (ret < 0) { + WARN("Poll returned error %d\n", ret); + } else { + k_sleep(K_MSEC(1)); + } + } else if (data->tx_irq_enabled) { + native_tty_uart_irq_handler(dev); + } else { + k_sleep(K_MSEC(10)); + } + } +} + +static void native_tty_uart_irq_callback_set(const struct device *dev, + uart_irq_callback_user_data_t cb, + void *cb_data) +{ + struct native_tty_data *data = dev->data; + + data->callback = cb; + data->cb_data = cb_data; +} + +static void native_tty_irq_init(const struct device *dev) +{ + /* Create a thread which will wait for data - replacement for IRQ */ + k_thread_create(&rx_thread, rx_stack, K_KERNEL_STACK_SIZEOF(rx_stack), + native_tty_uart_irq_function, + (void *)dev, NULL, NULL, + K_HIGHEST_THREAD_PRIO, 0, K_NO_WAIT); +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + static int native_tty_serial_init(const struct device *dev) { struct native_tty_data *data = dev->data; @@ -197,6 +357,10 @@ static int native_tty_serial_init(const struct device *dev) posix_print_trace("%s connected to the serial port: %s\n", dev->name, data->serial_port); +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + /* Start irq emulation thread */ + native_tty_irq_init(dev); +#endif return 0; } @@ -206,6 +370,20 @@ static struct uart_driver_api native_tty_uart_driver_api = { #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE .configure = native_tty_configure, #endif +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = native_tty_uart_fifo_fill, + .fifo_read = native_tty_uart_fifo_read, + .irq_tx_enable = native_tty_uart_irq_tx_enable, + .irq_tx_disable = native_tty_uart_irq_tx_disable, + .irq_tx_ready = native_tty_uart_irq_tx_ready, + .irq_tx_complete = native_tty_uart_irq_tx_complete, + .irq_rx_enable = native_tty_uart_irq_rx_enable, + .irq_rx_disable = native_tty_uart_irq_rx_disable, + .irq_rx_ready = native_tty_uart_irq_rx_ready, + .irq_is_pending = native_tty_uart_irq_is_pending, + .irq_update = native_tty_uart_irq_update, + .irq_callback_set = native_tty_uart_irq_callback_set, +#endif }; #define NATIVE_TTY_INSTANCE(inst) \ @@ -225,7 +403,7 @@ static struct uart_driver_api native_tty_uart_driver_api = { }; \ \ DEVICE_DT_INST_DEFINE(inst, native_tty_serial_init, NULL, &native_tty_##inst##_data, \ - &native_tty_##inst##_cfg, PRE_KERNEL_1, 55, \ + &native_tty_##inst##_cfg, NATIVE_TTY_INIT_LEVEL, 55, \ &native_tty_uart_driver_api); DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_INSTANCE); diff --git a/drivers/serial/uart_native_tty_bottom.c b/drivers/serial/uart_native_tty_bottom.c index 21a2f272f1..cb8e4162a6 100644 --- a/drivers/serial/uart_native_tty_bottom.c +++ b/drivers/serial/uart_native_tty_bottom.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -174,6 +175,13 @@ static inline void native_tty_data_bits_set(struct termios *ter, ter->c_cflag |= data_bits_to_set; } +int native_tty_poll_bottom(int fd) +{ + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + + return poll(&pfd, 1, 0); +} + int native_tty_open_tty_bottom(const char *pathname) { int fd = open(pathname, O_RDWR | O_NOCTTY); diff --git a/drivers/serial/uart_native_tty_bottom.h b/drivers/serial/uart_native_tty_bottom.h index 08ed367e9e..aa5b819c0a 100644 --- a/drivers/serial/uart_native_tty_bottom.h +++ b/drivers/serial/uart_native_tty_bottom.h @@ -53,6 +53,17 @@ struct native_tty_bottom_cfg { /* Note: None of these functions are public interfaces. They are internal to the native tty driver. */ +/** + * @brief Check for available input on tty file descriptor + * + * @param fd + * + * @retval 1 if data is available + * @retval 0 if data is not available + * @retval <0 on error + */ +int native_tty_poll_bottom(int fd); + /** * @brief Opens tty port on the given pathname *