diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index 62336a8980e..be0cbfd394b 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -91,3 +91,4 @@ if(CONFIG_UART_NATIVE_TTY) endif() zephyr_library_sources_ifdef(CONFIG_SERIAL_TEST serial_test.c) +zephyr_library_sources_ifdef(CONFIG_UART_ASYNC_RX_HELPER uart_async_rx.c) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 4a42a81aadd..22916527ca1 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -125,6 +125,14 @@ config UART_PIPE data (as contrary to console UART driver) and all aspects of received protocol data are handled by application provided callback. +config UART_ASYNC_RX_HELPER + bool "Helper for UART asynchronous reception" + help + Module implements handling of reception of variable length data using + Asynchronous UART API. It can be used in cases where received data processing + is delayed. Module implements zero-copy approach with multiple reception + buffers. + comment "Serial Drivers" source "drivers/serial/Kconfig.b91" diff --git a/drivers/serial/uart_async_rx.c b/drivers/serial/uart_async_rx.c new file mode 100644 index 00000000000..68e17691c3e --- /dev/null +++ b/drivers/serial/uart_async_rx.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +static uint8_t inc(struct uart_async_rx *rx_data, uint8_t val) +{ + return (val + 1) & (rx_data->config->buf_cnt - 1); +} + +static struct uart_async_rx_buf *get_buf(struct uart_async_rx *rx_data, uint8_t idx) +{ + uint8_t *p = rx_data->config->buffer; + + p += idx * (rx_data->buf_len + sizeof(struct uart_async_rx_buf)); + + return (struct uart_async_rx_buf *)p; +} + +uint8_t *uart_async_rx_buf_req(struct uart_async_rx *rx_data) +{ + uint8_t *data = NULL; + + if (rx_data->free_buf_cnt != 0) { + struct uart_async_rx_buf *buf = get_buf(rx_data, rx_data->drv_buf_idx); + + data = buf->buffer; + rx_data->drv_buf_idx = inc(rx_data, rx_data->drv_buf_idx); + + atomic_dec(&rx_data->free_buf_cnt); + } + + return data; +} + +void uart_async_rx_on_rdy(struct uart_async_rx *rx_data, uint8_t *buffer, size_t length) +{ + /* Cannot use CONTAINER_OF because validation fails due to type mismatch: + * uint8_t * vs uint8_t []. + */ + struct uart_async_rx_buf *rx_buf = + (struct uart_async_rx_buf *)(buffer - offsetof(struct uart_async_rx_buf, buffer)); + + rx_buf->wr_idx += length; + __ASSERT_NO_MSG(rx_buf->wr_idx <= rx_data->buf_len); + + atomic_add(&rx_data->pending_bytes, length); +} + +static void buf_reset(struct uart_async_rx_buf *buf) +{ + buf->rd_idx = 0; + buf->wr_idx = 0; + buf->completed = 0; +} +static void usr_rx_buf_release(struct uart_async_rx *rx_data, struct uart_async_rx_buf *buf) +{ + buf_reset(buf); + rx_data->rd_buf_idx = inc(rx_data, rx_data->rd_buf_idx); + atomic_inc(&rx_data->free_buf_cnt); + __ASSERT_NO_MSG(rx_data->free_buf_cnt <= rx_data->config->buf_cnt); +} + +void uart_async_rx_on_buf_rel(struct uart_async_rx *rx_data, uint8_t *buffer) +{ + /* Cannot use CONTAINER_OF because validation fails due to type mismatch: + * uint8_t * vs uint8_t []. + */ + struct uart_async_rx_buf *rx_buf = + (struct uart_async_rx_buf *)(buffer - offsetof(struct uart_async_rx_buf, buffer)); + + rx_buf->completed = 1; + rx_data->wr_buf_idx = inc(rx_data, rx_data->wr_buf_idx); +} + +size_t uart_async_rx_data_claim(struct uart_async_rx *rx_data, uint8_t **data, size_t length) +{ + struct uart_async_rx_buf *buf; + int rem; + + if ((rx_data->pending_bytes == 0) || (length == 0)) { + return 0; + } + + do { + buf = get_buf(rx_data, rx_data->rd_buf_idx); + if ((buf->rd_idx == buf->wr_idx) && (buf->completed == 1)) { + usr_rx_buf_release(rx_data, buf); + } else { + break; + } + } while (1); + + *data = &buf->buffer[buf->rd_idx]; + rem = buf->wr_idx - buf->rd_idx; + + return MIN(length, rem); +} + +void uart_async_rx_data_consume(struct uart_async_rx *rx_data, size_t length) +{ + struct uart_async_rx_buf *buf = get_buf(rx_data, rx_data->rd_buf_idx); + + buf->rd_idx += length; + + atomic_sub(&rx_data->pending_bytes, length); + + __ASSERT_NO_MSG(buf->rd_idx <= buf->wr_idx); +} + +void uart_async_rx_reset(struct uart_async_rx *rx_data) +{ + rx_data->free_buf_cnt = rx_data->config->buf_cnt; + for (uint8_t i = 0; i < rx_data->config->buf_cnt; i++) { + buf_reset(get_buf(rx_data, i)); + } +} + +int uart_async_rx_init(struct uart_async_rx *rx_data, + const struct uart_async_rx_config *config) +{ + __ASSERT_NO_MSG(config->length / config->buf_cnt <= UINT8_MAX); + memset(rx_data, 0, sizeof(*rx_data)); + rx_data->config = config; + rx_data->buf_len = (config->length / config->buf_cnt) - UART_ASYNC_RX_BUF_OVERHEAD; + uart_async_rx_reset(rx_data); + + return 0; +} diff --git a/include/zephyr/drivers/serial/uart_async_rx.h b/include/zephyr/drivers/serial/uart_async_rx.h new file mode 100644 index 00000000000..646a2befc94 --- /dev/null +++ b/include/zephyr/drivers/serial/uart_async_rx.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Helper module for receiving using UART Asynchronous API. + */ + +#ifndef ZEPHYR_DRIVERS_SERIAL_UART_ASYNC_RX_H_ +#define ZEPHYR_DRIVERS_SERIAL_UART_ASYNC_RX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* @brief RX buffer structure which holds the buffer and its state. */ +struct uart_async_rx_buf { + /* Write index which is incremented whenever new data is reported to be + * received to that buffer. + */ + uint8_t wr_idx; + + /* Read index which is incremented whenever data is consumed from the buffer. + * Read index cannot be higher than the write index. + */ + uint8_t rd_idx; + + /* Set to one if buffer is released by the driver. */ + uint8_t completed; + + /* Location which is passed to the UART driver. */ + uint8_t buffer[]; +}; + +/** @brief UART asynchronous RX helper structure. */ +struct uart_async_rx { + /* Pointer to the configuration structure. Structure must be persistent. */ + const struct uart_async_rx_config *config; + + /* Total amount of pending bytes. Bytes may be spread across multiple RX buffers. */ + atomic_t pending_bytes; + + /* Number of buffers which are free. */ + atomic_t free_buf_cnt; + + /* Single buffer size. */ + uint8_t buf_len; + + /* Index of the next buffer to be provided to the driver. */ + uint8_t drv_buf_idx; + + /* Current buffer to which data is written. */ + uint8_t wr_buf_idx; + + /* Current buffer from which data is being consumed. */ + uint8_t rd_buf_idx; +}; + +/** @brief UART asynchronous RX helper configuration structure. */ +struct uart_async_rx_config { + /* Pointer to the buffer. */ + uint8_t *buffer; + + /* Buffer length. */ + size_t length; + + /* Number of buffers into provided space shall be split. */ + uint8_t buf_cnt; +}; + +/** @brief Get RX buffer length. + * + * @param async_rx Pointer to the helper instance. + * + * @return Buffer length. + */ +static inline uint8_t uart_async_rx_get_buf_len(struct uart_async_rx *async_rx) +{ + return async_rx->buf_len; +} + +/** @brief Get amount of space dedicated for managing each buffer state. + * + * User buffer provided during the initialization is split into chunks and each + * chunk has overhead. This overhead can be used to calculate actual space used + * for UART data. + * + * @return Overhead space in bytes. + */ +#define UART_ASYNC_RX_BUF_OVERHEAD offsetof(struct uart_async_rx_buf, buffer) + +/** @brief Initialize the helper instance. + * + * @param async_rx Pointer to the helper instance. + * @param config Configuration. Must be persistent. + * + * @retval 0 on successful initialization. + */ +int uart_async_rx_init(struct uart_async_rx *async_rx, + const struct uart_async_rx_config *config); + +/** @brief Reset state of the helper instance. + * + * Helper can be reset after RX abort to discard all received data and bring + * the helper to its initial state. + * + * @param async_rx Pointer to the helper instance. + */ +void uart_async_rx_reset(struct uart_async_rx *async_rx); + +/** @brief Indicate received data. + * + * Function shall be called from @ref UART_RX_RDY context. + * + * @param async_rx Pointer to the helper instance. + * @param buffer Buffer received in the UART driver event. + * @param length Length received in the UART driver event. + */ +void uart_async_rx_on_rdy(struct uart_async_rx *async_rx, uint8_t *buffer, size_t length); + +/** @brief Get next RX buffer. + * + * Returned pointer shall be provided to @ref uart_rx_buf_rsp or @ref uart_rx_enable. + * If null is returned that indicates that there are no available buffers since all + * buffers are used by the driver or contain not consumed data. + * + * @param async_rx Pointer to the helper instance. + * + * @return Pointer to the next RX buffer or null if no buffer available. + */ +uint8_t *uart_async_rx_buf_req(struct uart_async_rx *async_rx); + +/** @brief Indicate that buffer is no longer used by the UART driver. + * + * Function shall be called on @ref UART_RX_BUF_RELEASED event. + * + * @param async_rx Pointer to the helper instance. + * @param buf Buffer pointer received in the UART driver event. + */ +void uart_async_rx_on_buf_rel(struct uart_async_rx *async_rx, uint8_t *buf); + +/** @brief Claim received data for processing. + * + * Helper module works in the zero copy mode. It provides a pointer to the buffer + * that was directly used by the UART driver. Since received data is spread across + * multiple buffers there is no possibility to read all data at once. It can only be + * consumed in chunks. After data is processed, @ref uart_async_rx_data_consume is + * used to indicate that data is consumed. + * + * @param async_rx Pointer to the helper instance. + * @param data Location where address to the buffer is written. Untouched if no data to claim. + * @param length Amount of requested data. + * + * @return Amount valid of data in the @p data buffer. 0 is returned when there is no data. + */ +size_t uart_async_rx_data_claim(struct uart_async_rx *async_rx, uint8_t **data, size_t length); + +/** @brief Consume claimed data. + * + * It pairs with @ref uart_async_rx_data_claim. + * + * @param async_rx Pointer to the helper instance. + * @param length Amount of data to consume. It must be less or equal than amount of claimed data. + */ +void uart_async_rx_data_consume(struct uart_async_rx *async_rx, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_SERIAL_UART_ASYNC_RX_H_ */