uart_native_tty: Emulate an interrupt driven uart

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 <bjorn@haxx.se>
This commit is contained in:
Björn Stenberg 2024-02-12 11:03:20 +01:00 committed by Alberto Escolar
parent 38a14b894d
commit 8cdcb2c167
5 changed files with 203 additions and 2 deletions

View file

@ -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:

View file

@ -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

View file

@ -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);

View file

@ -11,6 +11,7 @@
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <poll.h>
#include <termios.h>
#include <unistd.h>
@ -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);

View file

@ -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
*