219 lines
6.4 KiB
C
219 lines
6.4 KiB
C
/*
|
|
* Copyright (c) 2023 Trackunit Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include "modem_backend_uart_isr.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(modem_backend_uart_isr, CONFIG_MODEM_MODULES_LOG_LEVEL);
|
|
|
|
#include <string.h>
|
|
|
|
static void modem_backend_uart_isr_flush(struct modem_backend_uart *backend)
|
|
{
|
|
uint8_t c;
|
|
|
|
while (uart_fifo_read(backend->uart, &c, 1) > 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
static void modem_backend_uart_isr_irq_handler_receive_ready(struct modem_backend_uart *backend)
|
|
{
|
|
uint32_t size;
|
|
struct ring_buf *receive_rb;
|
|
uint8_t *buffer;
|
|
int ret;
|
|
|
|
receive_rb = &backend->isr.receive_rdb[backend->isr.receive_rdb_used];
|
|
size = ring_buf_put_claim(receive_rb, &buffer, UINT32_MAX);
|
|
if (size == 0) {
|
|
/* This can be caused by
|
|
* - a too long CONFIG_MODEM_BACKEND_UART_ISR_RECEIVE_IDLE_TIMEOUT_MS
|
|
* - or a too small receive_buf_size
|
|
* relatively to the (too high) baud rate and amount of incoming data.
|
|
*/
|
|
LOG_WRN("Receive buffer overrun");
|
|
ring_buf_put_finish(receive_rb, 0);
|
|
ring_buf_reset(receive_rb);
|
|
size = ring_buf_put_claim(receive_rb, &buffer, UINT32_MAX);
|
|
}
|
|
|
|
ret = uart_fifo_read(backend->uart, buffer, size);
|
|
if (ret <= 0) {
|
|
ring_buf_put_finish(receive_rb, 0);
|
|
return;
|
|
}
|
|
ring_buf_put_finish(receive_rb, (uint32_t)ret);
|
|
|
|
if (ring_buf_space_get(receive_rb) > ring_buf_capacity_get(receive_rb) / 20) {
|
|
/*
|
|
* Avoid having the receiver call modem_pipe_receive() too often (e.g. every byte).
|
|
* It temporarily disables the UART RX IRQ when swapping buffers
|
|
* which can cause byte loss at higher baud rates.
|
|
*/
|
|
k_work_schedule(&backend->receive_ready_work,
|
|
K_MSEC(CONFIG_MODEM_BACKEND_UART_ISR_RECEIVE_IDLE_TIMEOUT_MS));
|
|
} else {
|
|
/* The buffer is getting full. Run the work item immediately to free up space. */
|
|
k_work_reschedule(&backend->receive_ready_work, K_NO_WAIT);
|
|
}
|
|
}
|
|
|
|
static void modem_backend_uart_isr_irq_handler_transmit_ready(struct modem_backend_uart *backend)
|
|
{
|
|
uint32_t size;
|
|
uint8_t *buffer;
|
|
int ret;
|
|
|
|
if (ring_buf_is_empty(&backend->isr.transmit_rb) == true) {
|
|
uart_irq_tx_disable(backend->uart);
|
|
k_work_submit(&backend->transmit_idle_work);
|
|
return;
|
|
}
|
|
|
|
size = ring_buf_get_claim(&backend->isr.transmit_rb, &buffer, UINT32_MAX);
|
|
ret = uart_fifo_fill(backend->uart, buffer, size);
|
|
if (ret < 0) {
|
|
ring_buf_get_finish(&backend->isr.transmit_rb, 0);
|
|
} else {
|
|
ring_buf_get_finish(&backend->isr.transmit_rb, (uint32_t)ret);
|
|
|
|
/* Update transmit buf capacity tracker */
|
|
atomic_sub(&backend->isr.transmit_buf_len, (uint32_t)ret);
|
|
}
|
|
}
|
|
|
|
static void modem_backend_uart_isr_irq_handler(const struct device *uart, void *user_data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)user_data;
|
|
|
|
if (uart_irq_update(uart) < 1) {
|
|
return;
|
|
}
|
|
|
|
if (uart_irq_rx_ready(uart)) {
|
|
modem_backend_uart_isr_irq_handler_receive_ready(backend);
|
|
}
|
|
|
|
if (uart_irq_tx_ready(uart)) {
|
|
modem_backend_uart_isr_irq_handler_transmit_ready(backend);
|
|
}
|
|
}
|
|
|
|
static int modem_backend_uart_isr_open(void *data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
|
|
ring_buf_reset(&backend->isr.receive_rdb[0]);
|
|
ring_buf_reset(&backend->isr.receive_rdb[1]);
|
|
ring_buf_reset(&backend->isr.transmit_rb);
|
|
atomic_set(&backend->isr.transmit_buf_len, 0);
|
|
modem_backend_uart_isr_flush(backend);
|
|
uart_irq_rx_enable(backend->uart);
|
|
uart_irq_tx_enable(backend->uart);
|
|
modem_pipe_notify_opened(&backend->pipe);
|
|
return 0;
|
|
}
|
|
|
|
static bool modem_backend_uart_isr_transmit_buf_above_limit(struct modem_backend_uart *backend)
|
|
{
|
|
return backend->isr.transmit_buf_put_limit < atomic_get(&backend->isr.transmit_buf_len);
|
|
}
|
|
|
|
static int modem_backend_uart_isr_transmit(void *data, const uint8_t *buf, size_t size)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
int written;
|
|
|
|
if (modem_backend_uart_isr_transmit_buf_above_limit(backend) == true) {
|
|
return 0;
|
|
}
|
|
|
|
uart_irq_tx_disable(backend->uart);
|
|
written = ring_buf_put(&backend->isr.transmit_rb, buf, size);
|
|
uart_irq_tx_enable(backend->uart);
|
|
|
|
/* Update transmit buf capacity tracker */
|
|
atomic_add(&backend->isr.transmit_buf_len, written);
|
|
return written;
|
|
}
|
|
|
|
static int modem_backend_uart_isr_receive(void *data, uint8_t *buf, size_t size)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
|
|
uint32_t read_bytes;
|
|
uint8_t receive_rdb_unused;
|
|
|
|
read_bytes = 0;
|
|
receive_rdb_unused = (backend->isr.receive_rdb_used == 1) ? 0 : 1;
|
|
|
|
/* Read data from unused ring double buffer first */
|
|
read_bytes += ring_buf_get(&backend->isr.receive_rdb[receive_rdb_unused], buf, size);
|
|
|
|
if (ring_buf_is_empty(&backend->isr.receive_rdb[receive_rdb_unused]) == false) {
|
|
return (int)read_bytes;
|
|
}
|
|
|
|
/* Swap receive ring double buffer */
|
|
uart_irq_rx_disable(backend->uart);
|
|
backend->isr.receive_rdb_used = receive_rdb_unused;
|
|
uart_irq_rx_enable(backend->uart);
|
|
|
|
/* Read data from previously used buffer */
|
|
receive_rdb_unused = (backend->isr.receive_rdb_used == 1) ? 0 : 1;
|
|
|
|
read_bytes += ring_buf_get(&backend->isr.receive_rdb[receive_rdb_unused],
|
|
&buf[read_bytes], (size - read_bytes));
|
|
|
|
return (int)read_bytes;
|
|
}
|
|
|
|
static int modem_backend_uart_isr_close(void *data)
|
|
{
|
|
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
|
|
|
|
uart_irq_rx_disable(backend->uart);
|
|
uart_irq_tx_disable(backend->uart);
|
|
modem_pipe_notify_closed(&backend->pipe);
|
|
return 0;
|
|
}
|
|
|
|
struct modem_pipe_api modem_backend_uart_isr_api = {
|
|
.open = modem_backend_uart_isr_open,
|
|
.transmit = modem_backend_uart_isr_transmit,
|
|
.receive = modem_backend_uart_isr_receive,
|
|
.close = modem_backend_uart_isr_close,
|
|
};
|
|
|
|
void modem_backend_uart_isr_init(struct modem_backend_uart *backend,
|
|
const struct modem_backend_uart_config *config)
|
|
{
|
|
uint32_t receive_double_buf_size;
|
|
|
|
backend->isr.transmit_buf_put_limit =
|
|
config->transmit_buf_size - (config->transmit_buf_size / 4);
|
|
|
|
receive_double_buf_size = config->receive_buf_size / 2;
|
|
|
|
ring_buf_init(&backend->isr.receive_rdb[0], receive_double_buf_size,
|
|
&config->receive_buf[0]);
|
|
|
|
ring_buf_init(&backend->isr.receive_rdb[1], receive_double_buf_size,
|
|
&config->receive_buf[receive_double_buf_size]);
|
|
|
|
ring_buf_init(&backend->isr.transmit_rb, config->transmit_buf_size,
|
|
config->transmit_buf);
|
|
|
|
atomic_set(&backend->isr.transmit_buf_len, 0);
|
|
uart_irq_rx_disable(backend->uart);
|
|
uart_irq_tx_disable(backend->uart);
|
|
uart_irq_callback_user_data_set(backend->uart, modem_backend_uart_isr_irq_handler,
|
|
backend);
|
|
|
|
modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_isr_api);
|
|
}
|