emul: Support UART device emulation

Add support to the existing UART emulated bus, to have a client device
emulator.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
This commit is contained in:
Pieter De Gendt 2024-07-11 16:23:26 +02:00 committed by Fabio Baltieri
commit 92019b1dac
9 changed files with 178 additions and 26 deletions

View file

@ -7,6 +7,8 @@ config UART_EMUL
bool "Emulated UART driver [EXPERIMENTAL]"
default y
depends on DT_HAS_ZEPHYR_UART_EMUL_ENABLED
depends on EMUL
select SERIAL_HAS_DRIVER
select SERIAL_SUPPORT_INTERRUPT
select SERIAL_SUPPORT_ASYNC
select RING_BUFFER
@ -16,6 +18,12 @@ config UART_EMUL
if UART_EMUL
config UART_EMUL_DEVICE_INIT_PRIORITY
int "UART emulated devices' init priority"
default 0
help
The init priority of emulated driver on the UART bus.
config UART_EMUL_WORK_Q_STACK_SIZE
int "UART emulator work queue stack size"
default 2048

View file

@ -9,8 +9,9 @@
#include <errno.h>
#include <zephyr/drivers/serial/uart_emul.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/serial/uart_emul.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/ring_buffer.h>
@ -19,12 +20,20 @@
LOG_MODULE_REGISTER(uart_emul, CONFIG_UART_LOG_LEVEL);
struct uart_emul_config {
/* emul_list has to be the first member */
struct emul_list_for_bus emul_list;
bool loopback;
size_t latch_buffer_size;
};
BUILD_ASSERT(offsetof(struct uart_emul_config, emul_list) == 0);
/* Device run time data */
struct uart_emul_data {
/* List of struct uart_emul associated with the device */
sys_slist_t emuls;
const struct device *dev;
struct uart_config cfg;
@ -98,6 +107,24 @@ int uart_emul_init_work_q(void)
SYS_INIT(uart_emul_init_work_q, POST_KERNEL, 0);
static void uart_emul_tx_data_ready(const struct device *dev)
{
struct uart_emul_data *data = dev->data;
sys_snode_t *node;
if (data->tx_data_ready_cb) {
(data->tx_data_ready_cb)(dev, ring_buf_size_get(data->tx_rb), data->user_data);
}
SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) {
struct uart_emul *emul = CONTAINER_OF(node, struct uart_emul, node);
__ASSERT_NO_MSG(emul->api != NULL);
__ASSERT_NO_MSG(emul->api->tx_data_ready != NULL);
emul->api->tx_data_ready(dev, ring_buf_size_get(data->tx_rb), emul->target);
}
}
static int uart_emul_poll_in(const struct device *dev, unsigned char *p_char)
{
struct uart_emul_data *drv_data = dev->data;
@ -135,10 +162,8 @@ static void uart_emul_poll_out(const struct device *dev, unsigned char out_char)
if (drv_cfg->loopback) {
uart_emul_put_rx_data(dev, &out_char, 1);
}
if (drv_data->tx_data_ready_cb) {
(drv_data->tx_data_ready_cb)(dev, ring_buf_size_get(drv_data->tx_rb),
drv_data->user_data);
}
uart_emul_tx_data_ready(dev);
}
static int uart_emul_err_check(const struct device *dev)
@ -183,9 +208,8 @@ static int uart_emul_fifo_fill(const struct device *dev, const uint8_t *tx_data,
if (config->loopback) {
uart_emul_put_rx_data(dev, (uint8_t *)tx_data, put_size);
}
if (data->tx_data_ready_cb) {
data->tx_data_ready_cb(dev, ring_buf_size_get(data->tx_rb), data->user_data);
}
uart_emul_tx_data_ready(dev);
return ret;
}
@ -597,9 +621,8 @@ static void uart_emul_async_tx_handler(struct k_work *work)
LOG_WRN("Lost %" PRIu32 " bytes on loopback", written - loop_written);
}
}
if (data->tx_data_ready_cb) {
(data->tx_data_ready_cb)(dev, ring_buf_size_get(data->tx_rb), data->user_data);
}
uart_emul_tx_data_ready(dev);
if ((config->loopback && written) || !written) {
/* When using the loopback fixture, just allow to drop all bytes in the ring buffer
@ -973,19 +996,40 @@ void uart_emul_set_release_buffer_on_timeout(const struct device *dev, bool rele
IF_ENABLED(CONFIG_UART_ASYNC_API, (drv_data->rx_release_on_timeout = release_on_timeout;));
}
int uart_emul_register(const struct device *dev, struct uart_emul *emul)
{
struct uart_emul_data *data = dev->data;
sys_slist_append(&data->emuls, &emul->node);
return 0;
}
#define UART_EMUL_RX_FIFO_SIZE(inst) (DT_INST_PROP(inst, rx_fifo_size))
#define UART_EMUL_TX_FIFO_SIZE(inst) (DT_INST_PROP(inst, tx_fifo_size))
#define EMUL_LINK_AND_COMMA(node_id) \
{ \
.dev = DEVICE_DT_GET(node_id), \
},
#define DEFINE_UART_EMUL(inst) \
static const struct emul_link_for_bus emuls_##inst[] = { \
DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(inst), EMUL_LINK_AND_COMMA)}; \
\
RING_BUF_DECLARE(uart_emul_##inst##_rx_rb, UART_EMUL_RX_FIFO_SIZE(inst)); \
RING_BUF_DECLARE(uart_emul_##inst##_tx_rb, UART_EMUL_TX_FIFO_SIZE(inst)); \
\
static struct uart_emul_config uart_emul_cfg_##inst = { \
static const struct uart_emul_config uart_emul_cfg_##inst = { \
.loopback = DT_INST_PROP(inst, loopback), \
.latch_buffer_size = DT_INST_PROP(inst, latch_buffer_size), \
.emul_list = { \
.children = emuls_##inst, \
.num_children = ARRAY_SIZE(emuls_##inst), \
}, \
}; \
static struct uart_emul_data uart_emul_data_##inst = { \
.emuls = SYS_SLIST_STATIC_INIT(&_CONCAT(uart_emul_data_, inst).emuls), \
.dev = DEVICE_DT_INST_GET(inst), \
.rx_rb = &uart_emul_##inst##_rx_rb, \
.tx_rb = &uart_emul_##inst##_tx_rb, \
@ -1000,6 +1044,12 @@ void uart_emul_set_release_buffer_on_timeout(const struct device *dev, bool rele
uart_emul_async_rx_disable_handler),)) \
}; \
\
static int uart_emul_post_init_##inst(void) \
{ \
return emul_init_for_bus(DEVICE_DT_INST_GET(inst)); \
} \
SYS_INIT(uart_emul_post_init_##inst, POST_KERNEL, CONFIG_UART_EMUL_DEVICE_INIT_PRIORITY); \
\
DEVICE_DT_INST_DEFINE(inst, NULL, NULL, &uart_emul_data_##inst, &uart_emul_cfg_##inst, \
PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &uart_emul_api);

View file

@ -28,6 +28,7 @@ struct emul;
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/drivers/spi_emul.h>
#include <zephyr/drivers/mspi_emul.h>
#include <zephyr/drivers/uart_emul.h>
#include <zephyr/sys/iterable_sections.h>
/**
@ -38,6 +39,7 @@ enum emul_bus_type {
EMUL_BUS_TYPE_ESPI,
EMUL_BUS_TYPE_SPI,
EMUL_BUS_TYPE_MSPI,
EMUL_BUS_TYPE_UART,
EMUL_BUS_TYPE_NONE,
};
@ -94,6 +96,7 @@ struct emul {
struct espi_emul *espi;
struct spi_emul *spi;
struct mspi_emul *mspi;
struct uart_emul *uart;
struct no_bus_emul *none;
} bus;
/** Address of the API structure exposed by the emulator instance */
@ -114,12 +117,14 @@ struct emul {
#define Z_EMUL_REG_BUS_IDENTIFIER(_dev_node_id) (_CONCAT(_CONCAT(__emulreg_, _dev_node_id), _bus))
/* Conditionally places text based on what bus _dev_node_id is on. */
#define Z_EMUL_BUS(_dev_node_id, _i2c, _espi, _spi, _mspi, _none) \
#define Z_EMUL_BUS(_dev_node_id, _i2c, _espi, _spi, _mspi, _uart, _none) \
COND_CODE_1(DT_ON_BUS(_dev_node_id, i2c), (_i2c), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, espi), (_espi), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, spi), (_spi), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, mspi), (_mspi), \
(_none))))))))
(COND_CODE_1(DT_ON_BUS(_dev_node_id, espi), (_espi), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, spi), (_spi), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, mspi), (_mspi), \
(COND_CODE_1(DT_ON_BUS(_dev_node_id, uart), (_uart), \
(_none))))))))))
/**
* @brief Define a new emulator
*
@ -135,20 +140,21 @@ struct emul {
* @param _backend_api emulator-specific backend api
*/
#define EMUL_DT_DEFINE(node_id, init_fn, data_ptr, cfg_ptr, bus_api, _backend_api) \
static struct Z_EMUL_BUS(node_id, i2c_emul, espi_emul, spi_emul, mspi_emul, no_bus_emul) \
Z_EMUL_REG_BUS_IDENTIFIER(node_id) = { \
.api = bus_api, \
.Z_EMUL_BUS(node_id, addr, chipsel, chipsel, dev_idx, addr) = \
DT_REG_ADDR(node_id), \
}; \
static struct Z_EMUL_BUS(node_id, i2c_emul, espi_emul, spi_emul, mspi_emul, uart_emul, \
no_bus_emul) Z_EMUL_REG_BUS_IDENTIFIER(node_id) = { \
.api = bus_api, \
IF_ENABLED(DT_NODE_HAS_PROP(node_id, reg), \
(.Z_EMUL_BUS(node_id, addr, chipsel, chipsel, dev_idx, dummy, addr) = \
DT_REG_ADDR(node_id),))}; \
const STRUCT_SECTION_ITERABLE(emul, EMUL_DT_NAME_GET(node_id)) __used = { \
.init = (init_fn), \
.dev = DEVICE_DT_GET(node_id), \
.cfg = (cfg_ptr), \
.data = (data_ptr), \
.bus_type = Z_EMUL_BUS(node_id, EMUL_BUS_TYPE_I2C, EMUL_BUS_TYPE_ESPI, \
EMUL_BUS_TYPE_SPI, EMUL_BUS_TYPE_MSPI, EMUL_BUS_TYPE_NONE), \
.bus = {.Z_EMUL_BUS(node_id, i2c, espi, spi, mspi, none) = \
EMUL_BUS_TYPE_SPI, EMUL_BUS_TYPE_MSPI, EMUL_BUS_TYPE_UART, \
EMUL_BUS_TYPE_NONE), \
.bus = {.Z_EMUL_BUS(node_id, i2c, espi, spi, mspi, uart, none) = \
&(Z_EMUL_REG_BUS_IDENTIFIER(node_id))}, \
.backend_api = (_backend_api), \
};

View file

@ -0,0 +1,76 @@
/*
* Copyright 2024 Basalte bv
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_UART_EMUL_H_
#define ZEPHYR_INCLUDE_DRIVERS_UART_EMUL_H_
#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/slist.h>
#include <zephyr/types.h>
/**
* @file
*
* @brief Public APIs for the UART device emulation drivers.
*/
/**
* @brief UART Emulation Interface
* @defgroup uart_emul_interface UART Emulation Interface
* @ingroup io_emulators
* @{
*/
#ifdef __cplusplus
extern "C" {
#endif
struct uart_emul_device_api;
/**
* @brief Define the emulation callback function signature
*
* @param dev UART device instance
* @param size Number of available bytes in TX buffer
* @param target pointer to emulation context
*/
typedef void (*uart_emul_device_tx_data_ready_t)(const struct device *dev, size_t size,
const struct emul *target);
/** Node in a linked list of emulators for UART devices */
struct uart_emul {
sys_snode_t node;
/** Target emulator - REQUIRED for all emulated bus nodes of any type */
const struct emul *target;
/** API provided for this device */
const struct uart_emul_device_api *api;
};
/** Definition of the emulator API */
struct uart_emul_device_api {
uart_emul_device_tx_data_ready_t tx_data_ready;
};
/**
* Register an emulated device on the controller
*
* @param dev Device that will use the emulator
* @param emul UART emulator to use
* @return 0 indicating success
*/
int uart_emul_register(const struct device *dev, struct uart_emul *emul);
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_DRIVERS_UART_EMUL_H_ */

View file

@ -58,6 +58,9 @@ int emul_init_for_bus(const struct device *dev)
case EMUL_BUS_TYPE_MSPI:
emul->bus.mspi->target = emul;
break;
case EMUL_BUS_TYPE_UART:
emul->bus.uart->target = emul;
break;
case EMUL_BUS_TYPE_NONE:
break;
}
@ -89,6 +92,11 @@ int emul_init_for_bus(const struct device *dev)
rc = mspi_emul_register(dev, emul->bus.mspi);
break;
#endif /* CONFIG_MSPI_EMUL */
#ifdef CONFIG_UART_EMUL
case EMUL_BUS_TYPE_UART:
rc = uart_emul_register(dev, emul->bus.uart);
break;
#endif /* CONFIG_UART_EMUL */
default:
rc = -EINVAL;
LOG_WRN("Found no emulated bus enabled to register emulator %s",

View file

@ -5,4 +5,4 @@ CONFIG_CONSOLE_SUBSYS=y
CONFIG_CONSOLE_GETLINE=y
CONFIG_DEVICE_MUTABLE=y
CONFIG_DEVMUX=y
CONFIG_UART_EMUL=y
CONFIG_EMUL=y

View file

@ -1,2 +1,3 @@
CONFIG_ZTEST=y
CONFIG_SERIAL=y
CONFIG_EMUL=y

View file

@ -4,6 +4,8 @@
CONFIG_ZTEST=y
CONFIG_ASSERT=y
CONFIG_TEST_LOGGING_DEFAULTS=n
CONFIG_SERIAL=y
CONFIG_EMUL=y
CONFIG_LOG=y
CONFIG_LOG_BACKEND_UART=y

View file

@ -6,3 +6,4 @@ CONFIG_SHELL_PRINTF_BUFF_SIZE=15
CONFIG_SHELL_METAKEYS=n
CONFIG_LOG=n
CONFIG_ZTEST=y
CONFIG_EMUL=y