blob: 0e1dbe626dac73846e54ab2b887b0cda77b13734 [file] [log] [blame]
/*
* Copyright (c) 2018, Oticon A/S
* Copyright (c) 2025, Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/devicetree.h>
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_native_posix_uart)
#define DT_DRV_COMPAT zephyr_native_posix_uart
#warning "zephyr,native-posix-uart is deprecated in favor of zephyr,native-pty-uart"
#else
#define DT_DRV_COMPAT zephyr_native_pty_uart
#endif
#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>
#include <nsi_tracing.h>
#include "uart_native_pty_bottom.h"
#define ERROR posix_print_error_and_exit
#define WARN posix_print_warning
/*
* UART driver for native simulator based boards.
* It can support a configurable number of UARTs.
*
* One (and only one) of this can be connected to the process STDIN+STDOUT otherwise, they are
* connected to a dedicated pseudo terminal.
*
* Connecting to a dedicated PTY is the recommended option for interactive use, as the pseudo
* terminal driver will be configured in "raw" mode and will therefore behave more like a real UART.
*
* When connected to its own pseudo terminal, it may also auto attach a terminal emulator to it,
* if set so from command line.
*/
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 */
bool wait_pts; /* Hold writes to the uart/pts until a client is connected/ready */
bool cmd_request_stdinout; /* User requested to connect this UART to the stdin/out */
#ifdef CONFIG_UART_ASYNC_API
struct {
const struct device *dev;
struct k_work_delayable tx_done;
uart_callback_t user_callback;
void *user_data;
const uint8_t *tx_buf;
size_t tx_len;
uint8_t *rx_buf;
size_t rx_len;
/* Instance-specific RX thread. */
struct k_thread rx_thread;
/* Stack for RX thread */
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);
#ifdef CONFIG_UART_ASYNC_API
static void np_uart_tx_done_work(struct k_work *work);
static int np_uart_callback_set(const struct device *dev, uart_callback_t callback,
void *user_data);
static int np_uart_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout);
static int np_uart_tx_abort(const struct device *dev);
static int np_uart_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len);
static int np_uart_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout);
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,
#ifdef CONFIG_UART_ASYNC_API
.callback_set = np_uart_callback_set,
.tx = np_uart_tx,
.tx_abort = np_uart_tx_abort,
.rx_buf_rsp = np_uart_rx_buf_rsp,
.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) \
static struct native_pty_status native_pty_status_##inst; \
\
DEVICE_DT_INST_DEFINE(inst, np_uart_init, NULL, \
(void *)&native_pty_status_##inst, NULL, \
PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \
&np_uart_driver_api);
DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_INSTANCE);
/**
* @brief Initialize a native_pty serial port
*
* @param dev uart device struct
*
* @return 0 (if it fails catastrophically, the execution is terminated)
*/
static int np_uart_init(const struct device *dev)
{
static bool stdinout_used;
struct native_pty_status *d;
d = (struct native_pty_status *)dev->data;
if (IS_ENABLED(CONFIG_UART_NATIVE_PTY_0_ON_STDINOUT)) {
static bool first_node = true;
if (first_node) {
d->on_stdinout = true;
}
first_node = false;
}
if (d->cmd_request_stdinout) {
if (stdinout_used) {
nsi_print_warning("%s requested to connect to STDIN/OUT, but another UART"
" is already connected to it => ignoring request.\n",
dev->name);
} else {
d->on_stdinout = true;
}
}
if (d->on_stdinout == true) {
d->in_fd = np_uart_pty_get_stdin_fileno();
d->out_fd = np_uart_pty_get_stdout_fileno();
stdinout_used = true;
} else {
if (d->auto_attach_cmd == NULL) {
d->auto_attach_cmd = CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD;
} else { /* Running with --attach_uart_cmd, implies --attach_uart */
d->auto_attach = true;
}
int tty_fn = np_uart_open_pty(dev->name, d->auto_attach_cmd, d->auto_attach,
d->wait_pts);
d->in_fd = tty_fn;
d->out_fd = tty_fn;
}
#ifdef CONFIG_UART_ASYNC_API
k_work_init_delayable(&d->async.tx_done, np_uart_tx_done_work);
d->async.dev = dev;
#endif
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
*
* @param dev UART device struct
* @param out_char Character to send.
*/
static void np_uart_poll_out(const struct device *dev, unsigned char out_char)
{
(void)np_uart_poll_out_n((struct native_pty_status *)dev->data, &out_char, 1);
}
/**
* @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 (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);
}
if (rc > 0) {
return rc;
}
return -1;
}
static int np_uart_poll_in(const struct device *dev, unsigned char *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
static int np_uart_callback_set(const struct device *dev, uart_callback_t callback, void *user_data)
{
struct native_pty_status *data = dev->data;
data->async.user_callback = callback;
data->async.user_data = user_data;
return 0;
}
static void np_uart_tx_done_work(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct native_pty_status *data =
CONTAINER_OF(dwork, struct native_pty_status, async.tx_done);
struct uart_event evt;
unsigned int key = irq_lock();
evt.type = UART_TX_DONE;
evt.data.tx.buf = data->async.tx_buf;
evt.data.tx.len = data->async.tx_len;
(void)nsi_host_write(data->out_fd, evt.data.tx.buf, evt.data.tx.len);
data->async.tx_buf = NULL;
if (data->async.user_callback) {
data->async.user_callback(data->async.dev, &evt, data->async.user_data);
}
irq_unlock(key);
}
static int np_uart_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout)
{
struct native_pty_status *data = dev->data;
if (data->async.tx_buf) {
/* Port is busy */
return -EBUSY;
}
data->async.tx_buf = buf;
data->async.tx_len = len;
/* Run the callback on the next tick to give the caller time to use the return value */
k_work_reschedule(&data->async.tx_done, K_TICKS(1));
return 0;
}
static int np_uart_tx_abort(const struct device *dev)
{
struct native_pty_status *data = dev->data;
struct k_work_sync sync;
struct uart_event evt;
bool not_idle;
/* Cancel the callback */
not_idle = k_work_cancel_delayable_sync(&data->async.tx_done, &sync);
if (!not_idle) {
return -EFAULT;
}
/* Generate TX_DONE event with number of bytes transmitted */
evt.type = UART_TX_DONE;
evt.data.tx.buf = data->async.tx_buf;
evt.data.tx.len = 0;
if (data->async.user_callback) {
data->async.user_callback(data->async.dev, &evt, data->async.user_data);
}
/* Reset state */
data->async.tx_buf = NULL;
return 0;
}
/*
* Emulate async interrupts using a polling thread
*/
static void native_pty_uart_async_poll_function(void *arg1, void *arg2, void *arg3)
{
const struct device *dev = arg1;
struct native_pty_status *data = dev->data;
struct uart_event evt;
int rc;
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
while (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;
evt.data.rx.buf = data->async.rx_buf;
evt.data.rx.offset = 0;
evt.data.rx.len = rc;
/* User callback */
if (data->async.user_callback) {
data->async.user_callback(data->async.dev, &evt,
data->async.user_data);
}
}
if ((data->async.rx_len != 0) && (rc < 0)) {
/* Sleep if RX not disabled and last read didn't result in any data */
k_sleep(K_MSEC(10));
}
}
}
static int np_uart_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len)
{
/* Driver never requests additional buffers */
return -ENOTSUP;
}
static int np_uart_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout)
{
struct native_pty_status *data = dev->data;
ARG_UNUSED(timeout);
if (data->async.rx_buf != NULL) {
return -EBUSY;
}
data->async.rx_buf = buf;
data->async.rx_len = len;
/* Create a thread which will wait for data - replacement for IRQ */
k_thread_create(&data->async.rx_thread, data->async.rx_stack,
K_KERNEL_STACK_SIZEOF(data->async.rx_stack),
native_pty_uart_async_poll_function,
(void *)dev, NULL, NULL,
K_HIGHEST_THREAD_PRIO, 0, K_NO_WAIT);
return 0;
}
static int np_uart_rx_disable(const struct device *dev)
{
struct native_pty_status *data = dev->data;
if (data->async.rx_buf == NULL) {
return -EFAULT;
}
data->async.rx_len = 0;
data->async.rx_buf = NULL;
/* Wait for RX thread to terminate */
return k_thread_join(&data->async.rx_thread, K_FOREVER);
}
#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;
#define NATIVE_PTY_SET_AUTO_ATTACH(inst, value) \
native_pty_status_##inst.auto_attach = value;
#define NATIVE_PTY_SET_WAIT_PTS(inst, value) \
native_pty_status_##inst.wait_pts = value;
static void auto_attach_cmd_cb(char *argv, int offset)
{
DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH_CMD, &argv[offset]);
DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH, true);
}
static void auto_attach_cb(char *argv, int offset)
{
DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH, true);
}
static void wait_pts_cb(char *argv, int offset)
{
DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_WAIT_PTS, true);
}
#define INST_NAME(inst) DEVICE_DT_NAME(DT_DRV_INST(inst))
#define NATIVE_PTY_COMMAND_LINE_OPTS(inst) \
{ \
.is_switch = true, \
.option = INST_NAME(inst) "_stdinout", \
.type = 'b', \
.dest = &native_pty_status_##inst.cmd_request_stdinout, \
.descript = "Connect "INST_NAME(inst)" to STDIN/OUT instead of a PTY" \
" (can only be done for one UART)" \
}, \
{ \
.is_switch = true, \
.option = INST_NAME(inst) "_attach_uart", \
.type = 'b', \
.dest = &native_pty_status_##inst.auto_attach, \
.descript = "Automatically attach "INST_NAME(inst)" to a terminal emulator." \
" (only applicable when connected to PTYs)" \
}, \
{ \
.option = INST_NAME(inst) "_attach_uart_cmd", \
.name = "\"cmd\"", \
.type = 's', \
.dest = &native_pty_status_##inst.auto_attach_cmd, \
.descript = "Command used to automatically attach to the terminal "INST_NAME(inst) \
" (implies "INST_NAME(inst)"_auto_attach), by default: " \
"'" CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD "'" \
" (only applicable when connected to PTYs)" \
}, \
{ \
.is_switch = true, \
.option = INST_NAME(inst) "_wait_uart", \
.type = 'b', \
.dest = &native_pty_status_##inst.wait_pts, \
.descript = "Hold writes to "INST_NAME(inst)" until a client is connected/ready" \
" (only applicable when connected to PTYs)" \
},
static void np_add_uart_options(void)
{
static struct args_struct_t uart_options[] = {
/* Set of parameters that apply to all PTY UARTs: */
{
.is_switch = true,
.option = "attach_uart",
.type = 'b',
.call_when_found = auto_attach_cb,
.descript = "Automatically attach all PTY UARTs to a terminal emulator."
" (only applicable when connected to PTYs)"
},
{
.option = "attach_uart_cmd",
.name = "\"cmd\"",
.type = 's',
.call_when_found = auto_attach_cmd_cb,
.descript = "Command used to automatically attach all PTY UARTs to a terminal "
"emulator (implies auto_attach), by default: "
"'" CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD "'"
" (only applicable when connected to PTYs)"
},
{
.is_switch = true,
.option = "wait_uart",
.type = 'b',
.call_when_found = wait_pts_cb,
.descript = "Hold writes to all PTY UARTs until a client is connected/ready"
" (only applicable when connected to PTYs)"
},
/* Set of parameters that apply to each individual PTY UART: */
DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_COMMAND_LINE_OPTS)
ARG_TABLE_ENDMARKER
};
native_add_command_line_opts(uart_options);
}
#define NATIVE_PTY_CLEANUP(inst) \
if ((!native_pty_status_##inst.on_stdinout) && (native_pty_status_##inst.in_fd != 0)) { \
nsi_host_close(native_pty_status_##inst.in_fd); \
native_pty_status_##inst.in_fd = 0; \
}
static void np_cleanup_uart(void)
{
DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_CLEANUP);
}
NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);