drivers: serial: Add uart_async_rx module
Add module which can handle RX path of UART asynchronous RX API. Module can be utilized in cases where processing of received data is not performed directly in the event context but it is delayed. At least two use cases has been identified (shell async UART backend, asynchronous to interrupt driven adaptation layer). Signed-off-by: Krzysztof Chruściński <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
parent
e74676223a
commit
165bd2a780
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
134
drivers/serial/uart_async_rx.c
Normal file
134
drivers/serial/uart_async_rx.c
Normal file
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/drivers/serial/uart_async_rx.h>
|
||||
|
||||
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;
|
||||
}
|
176
include/zephyr/drivers/serial/uart_async_rx.h
Normal file
176
include/zephyr/drivers/serial/uart_async_rx.h
Normal file
|
@ -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 <zephyr/kernel.h>
|
||||
|
||||
/* @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_ */
|
Loading…
Reference in a new issue