From 4d926ac041cea9a1947390c8a34cb7906e3da30f Mon Sep 17 00:00:00 2001 From: Aleksander Wasaznik Date: Wed, 19 Jul 2023 15:43:26 +0200 Subject: [PATCH] drivers: serial_test: Implement interrupt and async APIs This allows testing code that uses the UART interrupt-driven API and UART asynchronous API. Signed-off-by: Aleksander Wasaznik --- drivers/serial/serial_test.c | 319 +++++++++++++++++++++- include/zephyr/drivers/uart/serial_test.h | 2 +- 2 files changed, 318 insertions(+), 3 deletions(-) diff --git a/drivers/serial/serial_test.c b/drivers/serial/serial_test.c index 95e036e5992..2ce4fb50f38 100644 --- a/drivers/serial/serial_test.c +++ b/drivers/serial/serial_test.c @@ -9,12 +9,21 @@ * devices for the "vnd,serial" devicetree compatible used in test code. */ +#include + +#include #include #include #include #include +#include #include +LOG_MODULE_REGISTER(mock_serial, CONFIG_LOG_DEFAULT_LEVEL); + +BUILD_ASSERT(!IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN) || IS_ENABLED(CONFIG_RING_BUFFER)); +BUILD_ASSERT(!IS_ENABLED(CONFIG_UART_ASYNC_API) || IS_ENABLED(CONFIG_RING_BUFFER)); + #define DT_DRV_COMPAT vnd_serial struct serial_vnd_data { #ifdef CONFIG_RING_BUFFER @@ -23,8 +32,156 @@ struct serial_vnd_data { #endif serial_vnd_write_cb_t callback; void *callback_data; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + uart_irq_callback_user_data_t irq_isr; + bool irq_rx_enabled; + bool irq_tx_enabled; +#endif +#ifdef CONFIG_UART_ASYNC_API + uart_callback_t async_cb; + void *async_cb_user_data; + uint8_t *read_buf; + size_t read_size; + size_t read_position; +#endif }; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static bool is_irq_rx_pending(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + return !ring_buf_is_empty(data->read_queue); +} + +static bool is_irq_tx_pending(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + return ring_buf_space_get(data->written) != 0; +} + +static void irq_process(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + for (;;) { + bool rx_rdy = is_irq_rx_pending(dev); + bool tx_rdy = is_irq_tx_pending(dev); + bool rx_int = rx_rdy && data->irq_rx_enabled; + bool tx_int = tx_rdy && data->irq_tx_enabled; + + LOG_DBG("rx_rdy %d tx_rdy %d", rx_rdy, tx_rdy); + LOG_DBG("rx_int %d tx_int %d", rx_int, tx_int); + + if (!(rx_int || tx_int)) { + break; + } + + LOG_DBG("isr"); + if (!data->irq_isr) { + LOG_ERR("no isr registered"); + break; + } + data->irq_isr(dev, NULL); + }; +} + +static void irq_rx_enable(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + data->irq_rx_enabled = true; + LOG_DBG("rx enabled"); + irq_process(dev); +} + +static void irq_rx_disable(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + data->irq_rx_enabled = false; + LOG_DBG("rx disabled"); +} + +static int irq_rx_ready(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + bool ready = !ring_buf_is_empty(data->read_queue); + + LOG_DBG("rx ready: %d", ready); + return ready; +} + +static void irq_tx_enable(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + LOG_DBG("tx enabled"); + data->irq_tx_enabled = true; + irq_process(dev); +} + +static void irq_tx_disable(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + + data->irq_tx_enabled = false; + LOG_DBG("tx disabled"); +} + +static int irq_tx_ready(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + bool ready = (ring_buf_space_get(data->written) != 0); + + LOG_DBG("tx ready: %d", ready); + return ready; +} + +static void irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, + void *user_data) +{ + struct serial_vnd_data *data = dev->data; + + /* Not implemented. Ok because `user_data` is always NULL in the current + * implementation of core UART API. + */ + __ASSERT_NO_MSG(user_data == NULL); + +#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS) && defined(CONFIG_UART_ASYNC_API) + if (data->read_buf) { + LOG_ERR("Setting callback to NULL while asynchronous API is in use."); + } + data->async_cb = NULL; + data->async_cb_user_data = NULL; +#endif + + data->irq_isr = cb; + LOG_DBG("callback set"); +} + +static int fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) +{ + struct serial_vnd_data *data = dev->data; + uint32_t write_len = ring_buf_put(data->written, tx_data, size); + + if (data->callback) { + data->callback(dev, data->callback_data); + } + return write_len; +} + +static int fifo_read(const struct device *dev, uint8_t *rx_data, const int size) +{ + struct serial_vnd_data *data = dev->data; + int read_len = ring_buf_get(data->read_queue, rx_data, size); + + LOG_HEXDUMP_DBG(rx_data, read_len, ""); + return read_len; +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + static int serial_vnd_poll_in(const struct device *dev, unsigned char *c) { #ifdef CONFIG_RING_BUFFER @@ -59,15 +216,35 @@ static void serial_vnd_poll_out(const struct device *dev, unsigned char c) } } +#ifdef CONFIG_UART_ASYNC_API +static void async_rx_run(const struct device *dev); +#endif + #ifdef CONFIG_RING_BUFFER -int serial_vnd_queue_in_data(const struct device *dev, unsigned char *c, uint32_t size) +int serial_vnd_queue_in_data(const struct device *dev, const unsigned char *c, uint32_t size) { struct serial_vnd_data *data = dev->data; + int write_size; if (data == NULL || data->read_queue == NULL) { return -ENOTSUP; } - return ring_buf_put(data->read_queue, c, size); + write_size = ring_buf_put(data->read_queue, c, size); + + LOG_DBG("size %u write_size %u", size, write_size); + LOG_HEXDUMP_DBG(c, write_size, ""); + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + if (write_size > 0) { + irq_process(dev); + } +#endif + +#ifdef CONFIG_UART_ASYNC_API + async_rx_run(dev); +#endif + + return write_size; } uint32_t serial_vnd_out_data_size_get(const struct device *dev) @@ -130,6 +307,128 @@ static int serial_vnd_config_get(const struct device *dev, struct uart_config *c } #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ +#ifdef CONFIG_UART_ASYNC_API +static int serial_vnd_callback_set(const struct device *dev, uart_callback_t callback, + void *user_data) +{ + struct serial_vnd_data *data = dev->data; + + if (data == NULL) { + return -ENOTSUP; + } + +#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS) && defined(CONFIG_UART_INTERRUPT_DRIVEN) + data->irq_isr = cb; +#endif + + if (callback == NULL && data->read_buf) { + LOG_ERR("Setting callback to NULL while asynchronous API is in use."); + } + + data->async_cb = callback; + data->async_cb_user_data = user_data; + + return 0; +} + +static int serial_vnd_api_tx(const struct device *dev, const uint8_t *tx_data, size_t len, + int32_t timeout) +{ + struct serial_vnd_data *data = dev->data; + struct uart_event evt; + uint32_t write_len; + + if (data == NULL) { + return -ENOTSUP; + } + + if (data->async_cb == NULL) { + return -EINVAL; + } + + write_len = ring_buf_put(data->written, tx_data, len); + if (data->callback) { + data->callback(dev, data->callback_data); + } + + __ASSERT(write_len == len, "Ring buffer full. Async wait not implemented."); + + evt = (struct uart_event){ + .type = UART_TX_DONE, + .data.tx.buf = tx_data, + .data.tx.len = len, + }; + data->async_cb(dev, &evt, data->async_cb_user_data); + + return 0; +} + +static void async_rx_run(const struct device *dev) +{ + struct serial_vnd_data *data = dev->data; + struct uart_event evt; + uint32_t read_len; + uint32_t read_remaining; + + if (!data->read_buf) { + return; + } + + __ASSERT_NO_MSG(data->async_cb); + + read_remaining = data->read_size - data->read_position; + + read_len = ring_buf_get(data->read_queue, &data->read_buf[data->read_position], + read_remaining); + + if (read_len != 0) { + evt = (struct uart_event){ + .type = UART_RX_RDY, + .data.rx.buf = data->read_buf, + .data.rx.len = read_len, + .data.rx.offset = data->read_position, + }; + data->async_cb(dev, &evt, data->async_cb_user_data); + } + + data->read_position += read_len; + + if (data->read_position == data->read_size) { + data->read_buf = NULL; + evt = (struct uart_event){ + .type = UART_RX_DISABLED, + }; + data->async_cb(dev, &evt, data->async_cb_user_data); + } +} + +static int serial_vnd_rx_enable(const struct device *dev, uint8_t *read_buf, size_t read_size, + int32_t timeout) +{ + struct serial_vnd_data *data = dev->data; + + LOG_WRN("read_size %d", read_size); + + if (data == NULL) { + return -ENOTSUP; + } + + if (data->async_cb == NULL) { + return -EINVAL; + } + + __ASSERT(timeout == SYS_FOREVER_MS, "Async timeout not implemented."); + + data->read_buf = read_buf; + data->read_size = read_size; + data->read_position = 0; + + async_rx_run(dev); + + return 0; +} +#endif /* CONFIG_UART_ASYNC_API */ + static const struct uart_driver_api serial_vnd_api = { .poll_in = serial_vnd_poll_in, .poll_out = serial_vnd_poll_out, @@ -138,6 +437,22 @@ static const struct uart_driver_api serial_vnd_api = { .configure = serial_vnd_configure, .config_get = serial_vnd_config_get, #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .irq_callback_set = irq_callback_set, + .irq_rx_enable = irq_rx_enable, + .irq_rx_disable = irq_rx_disable, + .irq_rx_ready = irq_rx_ready, + .irq_tx_enable = irq_tx_enable, + .irq_tx_disable = irq_tx_disable, + .irq_tx_ready = irq_tx_ready, + .fifo_read = fifo_read, + .fifo_fill = fifo_fill, +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +#ifdef CONFIG_UART_ASYNC_API + .callback_set = serial_vnd_callback_set, + .tx = serial_vnd_api_tx, + .rx_enable = serial_vnd_rx_enable, +#endif /* CONFIG_UART_ASYNC_API */ }; #define VND_SERIAL_DATA_BUFFER(n) \ diff --git a/include/zephyr/drivers/uart/serial_test.h b/include/zephyr/drivers/uart/serial_test.h index 5e011d40321..ff1a2ad6181 100644 --- a/include/zephyr/drivers/uart/serial_test.h +++ b/include/zephyr/drivers/uart/serial_test.h @@ -29,7 +29,7 @@ extern "C" { * * @retval Number of bytes written. */ -int serial_vnd_queue_in_data(const struct device *dev, unsigned char *data, uint32_t size); +int serial_vnd_queue_in_data(const struct device *dev, const unsigned char *data, uint32_t size); /** * @brief Returns size of unread written data.