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:
parent
38a14b894d
commit
8cdcb2c167
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue