From 92019b1dacabd55a49b1b31f0c121b3fbb93d6e1 Mon Sep 17 00:00:00 2001 From: Pieter De Gendt Date: Thu, 11 Jul 2024 16:23:26 +0200 Subject: [PATCH] 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 --- drivers/serial/Kconfig.emul | 8 ++ drivers/serial/uart_emul.c | 74 +++++++++++++++--- include/zephyr/drivers/emul.h | 32 ++++---- include/zephyr/drivers/uart_emul.h | 76 +++++++++++++++++++ subsys/emul/emul.c | 8 ++ tests/drivers/console_switching/prj.conf | 2 +- tests/drivers/uart/uart_emul/prj.conf | 1 + .../subsys/logging/log_backend_uart/prj.conf | 2 + .../subsys/shell/shell_backend_uart/prj.conf | 1 + 9 files changed, 178 insertions(+), 26 deletions(-) create mode 100644 include/zephyr/drivers/uart_emul.h diff --git a/drivers/serial/Kconfig.emul b/drivers/serial/Kconfig.emul index dd243bade5f..8427f41a2c0 100644 --- a/drivers/serial/Kconfig.emul +++ b/drivers/serial/Kconfig.emul @@ -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 diff --git a/drivers/serial/uart_emul.c b/drivers/serial/uart_emul.c index 2dc8029c7f9..f0207377737 100644 --- a/drivers/serial/uart_emul.c +++ b/drivers/serial/uart_emul.c @@ -9,8 +9,9 @@ #include -#include +#include #include +#include #include #include #include @@ -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); diff --git a/include/zephyr/drivers/emul.h b/include/zephyr/drivers/emul.h index a41b48b8925..be4522c7964 100644 --- a/include/zephyr/drivers/emul.h +++ b/include/zephyr/drivers/emul.h @@ -28,6 +28,7 @@ struct emul; #include #include #include +#include #include /** @@ -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), \ }; diff --git a/include/zephyr/drivers/uart_emul.h b/include/zephyr/drivers/uart_emul.h new file mode 100644 index 00000000000..c2f16bd441d --- /dev/null +++ b/include/zephyr/drivers/uart_emul.h @@ -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 +#include +#include +#include +#include + +/** + * @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_ */ diff --git a/subsys/emul/emul.c b/subsys/emul/emul.c index 8918fd27ca1..03368a2b301 100644 --- a/subsys/emul/emul.c +++ b/subsys/emul/emul.c @@ -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", diff --git a/tests/drivers/console_switching/prj.conf b/tests/drivers/console_switching/prj.conf index 0c30f023843..c2c231fb506 100644 --- a/tests/drivers/console_switching/prj.conf +++ b/tests/drivers/console_switching/prj.conf @@ -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 diff --git a/tests/drivers/uart/uart_emul/prj.conf b/tests/drivers/uart/uart_emul/prj.conf index 79043ef148e..738ce8b6fe5 100644 --- a/tests/drivers/uart/uart_emul/prj.conf +++ b/tests/drivers/uart/uart_emul/prj.conf @@ -1,2 +1,3 @@ CONFIG_ZTEST=y CONFIG_SERIAL=y +CONFIG_EMUL=y diff --git a/tests/subsys/logging/log_backend_uart/prj.conf b/tests/subsys/logging/log_backend_uart/prj.conf index c8615a65adb..46e0a2322f5 100644 --- a/tests/subsys/logging/log_backend_uart/prj.conf +++ b/tests/subsys/logging/log_backend_uart/prj.conf @@ -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 diff --git a/tests/subsys/shell/shell_backend_uart/prj.conf b/tests/subsys/shell/shell_backend_uart/prj.conf index 7b71e09687f..0b87be611d8 100644 --- a/tests/subsys/shell/shell_backend_uart/prj.conf +++ b/tests/subsys/shell/shell_backend_uart/prj.conf @@ -6,3 +6,4 @@ CONFIG_SHELL_PRINTF_BUFF_SIZE=15 CONFIG_SHELL_METAKEYS=n CONFIG_LOG=n CONFIG_ZTEST=y +CONFIG_EMUL=y