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 <tim.pambor@codewrights.de>
Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
Tim Pambor
2025-08-11 11:16:16 +02:00
committed by Benjamin Cabé
parent 0ad98e9edb
commit 120f5a073c
5 changed files with 353 additions and 89 deletions

View File

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

View File

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

View File

@@ -16,6 +16,7 @@
#include <stdbool.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <cmdline.h> /* native_sim command line options header */
#include <posix_native_task.h>
#include <nsi_host_trampolines.h>
@@ -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) \
@@ -162,6 +210,33 @@ static int np_uart_init(const struct device *dev)
return 0;
}
/*
* @brief Output len characters towards the serial port
*
* @param dev UART device struct
* @param buf Pointer to the characters to send.
* @param len Number of characters to send
*/
static int np_uart_poll_out_n(struct native_pty_status *d, const unsigned char *buf, size_t len)
{
int ret;
if (d->wait_pts) {
while (1) {
ret = np_uart_slave_connected(d->out_fd);
if (ret == 1) {
break;
}
k_sleep(K_MSEC(100));
}
}
ret = nsi_host_write(d->out_fd, buf, len);
return ret;
}
/*
* @brief Output a character towards the serial port
*
@@ -170,81 +245,59 @@ static int np_uart_init(const struct device *dev)
*/
static void np_uart_poll_out(const struct device *dev, unsigned char out_char)
{
int ret;
struct native_pty_status *d = (struct native_pty_status *)dev->data;
(void)np_uart_poll_out_n((struct native_pty_status *)dev->data, &out_char, 1);
}
if (d->wait_pts) {
while (1) {
int rc = np_uart_slave_connected(d->out_fd);
/**
* @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 -1 If no character was available to read
*/
static int np_uart_read_n(struct native_pty_status *data, unsigned char *p_char, int len)
{
int rc = -1;
int in_f = data->in_fd;
if (rc == 1) {
break;
}
k_sleep(K_MSEC(100));
if (len <= 0) {
return -1;
}
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);
}
/* 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;
}
/**
* @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_stdin_poll_in(const struct device *dev, unsigned char *p_char)
{
int in_f = ((struct native_pty_status *)dev->data)->in_fd;
static bool disconnected;
int rc;
if (disconnected == true) {
return -1;
if (rc > 0) {
return rc;
}
rc = np_uart_stdin_poll_in_bottom(in_f, p_char, 1);
if (rc == -2) {
disconnected = true;
}
return rc == 1 ? 0 : -1;
}
/**
* @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)
{
int n = -1;
int in_f = ((struct native_pty_status *)dev->data)->in_fd;
n = nsi_host_read(in_f, p_char, 1);
if (n == -1) {
return -1;
}
return 0;
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;

View File

@@ -19,7 +19,7 @@
#include <string.h>
#include <pty.h>
#include <fcntl.h>
#include <sys/select.h>
#include <poll.h>
#include <unistd.h>
#include <poll.h>
#include <nsi_tracing.h>
@@ -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__);
}
if (len == 0) {
return 0;
}
n = read(in_f, p_char, len);
if ((n == -1) || (n == 0)) {
return -1;
}
return n;
if (n == 0) {
/* Attempting to read > 0 but getting 0 characters back
* indicates we reached EOF
*/
return -2;
} else {
return n;
}
}
/**

View File

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