This commit delegates the modem_pipe_notify_closed() call resulting from the UART async API UART_RX_DISABLED event to the workqueue. This is neccesary as the async UART callback may be called from ISR context. modem_pipe_notify_closed() must be called from outside of the ISR context as it takes a mutex. The commit also adds a missing break to the async UART callback, and adds a missing dependency to the Kconfig for the UART backends, RING_BUFFER=y Signed-off-by: Bjarki Arge Andreasen <bjarkix123@gmail.com>
282 lines
8.1 KiB
C
282 lines
8.1 KiB
C
/*
|
|
* Copyright (c) 2023 Trackunit Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "modem_backend_uart_async.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_DECLARE(modem_backend_uart);
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <string.h>
|
|
|
|
#define MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT (0)
|
|
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT (1)
|
|
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT (2)
|
|
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT (3)
|
|
|
|
static void modem_backend_uart_async_flush(struct modem_backend_uart *backend)
|
|
{
|
|
uint8_t c;
|
|
|
|
while (uart_fifo_read(backend->uart, &c, 1) > 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
static uint8_t modem_backend_uart_async_rx_rbuf_used_index(struct modem_backend_uart *backend)
|
|
{
|
|
return atomic_test_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
|
|
}
|
|
|
|
static void modem_backend_uart_async_rx_rbuf_used_swap(struct modem_backend_uart *backend)
|
|
{
|
|
uint8_t rx_rbuf_index = modem_backend_uart_async_rx_rbuf_used_index(backend);
|
|
|
|
if (rx_rbuf_index) {
|
|
atomic_clear_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
|
|
} else {
|
|
atomic_set_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
|
|
}
|
|
}
|
|
|
|
static void modem_backend_uart_async_event_handler(const struct device *dev,
|
|
struct uart_event *evt, void *user_data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *) user_data;
|
|
|
|
uint8_t receive_rb_used_index;
|
|
uint32_t received;
|
|
|
|
switch (evt->type) {
|
|
case UART_TX_DONE:
|
|
atomic_clear_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT);
|
|
|
|
break;
|
|
|
|
case UART_TX_ABORTED:
|
|
LOG_WRN("Transmit aborted");
|
|
atomic_clear_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT);
|
|
|
|
break;
|
|
|
|
case UART_RX_BUF_REQUEST:
|
|
if (!atomic_test_and_set_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT)) {
|
|
uart_rx_buf_rsp(backend->uart, backend->async.receive_bufs[0],
|
|
backend->async.receive_buf_size);
|
|
|
|
break;
|
|
}
|
|
|
|
if (!atomic_test_and_set_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT)) {
|
|
uart_rx_buf_rsp(backend->uart, backend->async.receive_bufs[1],
|
|
backend->async.receive_buf_size);
|
|
|
|
break;
|
|
}
|
|
|
|
LOG_WRN("No receive buffer available");
|
|
break;
|
|
|
|
case UART_RX_BUF_RELEASED:
|
|
if (evt->data.rx_buf.buf == backend->async.receive_bufs[0]) {
|
|
atomic_clear_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT);
|
|
|
|
break;
|
|
}
|
|
|
|
if (evt->data.rx_buf.buf == backend->async.receive_bufs[1]) {
|
|
atomic_clear_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT);
|
|
|
|
break;
|
|
}
|
|
|
|
LOG_WRN("Unknown receive buffer released");
|
|
break;
|
|
|
|
case UART_RX_RDY:
|
|
receive_rb_used_index = modem_backend_uart_async_rx_rbuf_used_index(backend);
|
|
|
|
received = ring_buf_put(&backend->async.receive_rdb[receive_rb_used_index],
|
|
&evt->data.rx.buf[evt->data.rx.offset],
|
|
evt->data.rx.len);
|
|
|
|
if (received < evt->data.rx.len) {
|
|
ring_buf_reset(&backend->async.receive_rdb[receive_rb_used_index]);
|
|
LOG_WRN("Receive buffer overrun");
|
|
break;
|
|
}
|
|
|
|
k_work_submit(&backend->receive_ready_work);
|
|
break;
|
|
|
|
case UART_RX_DISABLED:
|
|
k_work_submit(&backend->async.rx_disabled_work);
|
|
break;
|
|
|
|
case UART_RX_STOPPED:
|
|
LOG_WRN("Receive stopped for reasons: %u", (uint8_t)evt->data.rx_stop.reason);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int modem_backend_uart_async_open(void *data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
int ret;
|
|
|
|
atomic_set(&backend->async.state, 0);
|
|
modem_backend_uart_async_flush(backend);
|
|
ring_buf_reset(&backend->async.receive_rdb[0]);
|
|
ring_buf_reset(&backend->async.receive_rdb[1]);
|
|
|
|
/* Reserve receive buffer 0 */
|
|
atomic_set_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT);
|
|
|
|
/*
|
|
* Receive buffer 0 is used internally by UART, receive ring buffer 0 is
|
|
* used to store received data.
|
|
*/
|
|
ret = uart_rx_enable(backend->uart, backend->async.receive_bufs[0],
|
|
backend->async.receive_buf_size, 3000);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
modem_pipe_notify_opened(&backend->pipe);
|
|
return 0;
|
|
}
|
|
|
|
static int modem_backend_uart_async_transmit(void *data, const uint8_t *buf, size_t size)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
bool transmitting;
|
|
uint32_t bytes_to_transmit;
|
|
int ret;
|
|
|
|
transmitting = atomic_test_and_set_bit(&backend->async.state,
|
|
MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT);
|
|
|
|
if (transmitting) {
|
|
return 0;
|
|
}
|
|
|
|
/* Determine amount of bytes to transmit */
|
|
bytes_to_transmit = (size < backend->async.transmit_buf_size)
|
|
? size
|
|
: backend->async.transmit_buf_size;
|
|
|
|
/* Copy buf to transmit buffer which is passed to UART */
|
|
memcpy(backend->async.transmit_buf, buf, bytes_to_transmit);
|
|
|
|
ret = uart_tx(backend->uart, backend->async.transmit_buf, bytes_to_transmit,
|
|
CONFIG_MODEM_BACKEND_UART_ASYNC_TRANSMIT_TIMEOUT_MS * 1000L);
|
|
|
|
if (ret < 0) {
|
|
LOG_WRN("Failed to start async transmit");
|
|
return ret;
|
|
}
|
|
|
|
return (int)bytes_to_transmit;
|
|
}
|
|
|
|
static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t size)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
|
|
uint32_t received;
|
|
uint8_t receive_rdb_unused;
|
|
|
|
received = 0;
|
|
receive_rdb_unused = modem_backend_uart_async_rx_rbuf_used_index(backend) ? 0 : 1;
|
|
|
|
/* Read data from unused ring double buffer first */
|
|
received += ring_buf_get(&backend->async.receive_rdb[receive_rdb_unused], buf, size);
|
|
|
|
if (ring_buf_is_empty(&backend->async.receive_rdb[receive_rdb_unused]) == false) {
|
|
return (int)received;
|
|
}
|
|
|
|
/* Swap receive ring double buffer */
|
|
modem_backend_uart_async_rx_rbuf_used_swap(backend);
|
|
|
|
/* Read data from previously used buffer */
|
|
receive_rdb_unused = modem_backend_uart_async_rx_rbuf_used_index(backend) ? 0 : 1;
|
|
|
|
received += ring_buf_get(&backend->async.receive_rdb[receive_rdb_unused],
|
|
&buf[received], (size - received));
|
|
|
|
return (int)received;
|
|
}
|
|
|
|
static int modem_backend_uart_async_close(void *data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
|
|
uart_rx_disable(backend->uart);
|
|
return 0;
|
|
}
|
|
|
|
struct modem_pipe_api modem_backend_uart_async_api = {
|
|
.open = modem_backend_uart_async_open,
|
|
.transmit = modem_backend_uart_async_transmit,
|
|
.receive = modem_backend_uart_async_receive,
|
|
.close = modem_backend_uart_async_close,
|
|
};
|
|
|
|
bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend)
|
|
{
|
|
return uart_callback_set(backend->uart, modem_backend_uart_async_event_handler,
|
|
backend) == 0;
|
|
}
|
|
|
|
static void modem_backend_uart_async_notify_closed(struct k_work *item)
|
|
{
|
|
struct modem_backend_uart_async *async =
|
|
CONTAINER_OF(item, struct modem_backend_uart_async, rx_disabled_work);
|
|
|
|
struct modem_backend_uart *backend =
|
|
CONTAINER_OF(async, struct modem_backend_uart, async);
|
|
|
|
modem_pipe_notify_closed(&backend->pipe);
|
|
}
|
|
|
|
void modem_backend_uart_async_init(struct modem_backend_uart *backend,
|
|
const struct modem_backend_uart_config *config)
|
|
{
|
|
uint32_t receive_buf_size_quarter = config->receive_buf_size / 4;
|
|
|
|
/* Split receive buffer into 4 buffers, use 2 parts for UART receive double buffer */
|
|
backend->async.receive_buf_size = receive_buf_size_quarter;
|
|
backend->async.receive_bufs[0] = &config->receive_buf[0];
|
|
backend->async.receive_bufs[1] = &config->receive_buf[receive_buf_size_quarter];
|
|
|
|
/* Use remaining 2 parts for receive double ring buffer */
|
|
ring_buf_init(&backend->async.receive_rdb[0], receive_buf_size_quarter,
|
|
&config->receive_buf[receive_buf_size_quarter * 2]);
|
|
|
|
ring_buf_init(&backend->async.receive_rdb[1], receive_buf_size_quarter,
|
|
&config->receive_buf[receive_buf_size_quarter * 3]);
|
|
|
|
backend->async.transmit_buf = config->transmit_buf;
|
|
backend->async.transmit_buf_size = config->transmit_buf_size;
|
|
k_work_init(&backend->async.rx_disabled_work, modem_backend_uart_async_notify_closed);
|
|
modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_async_api);
|
|
}
|