modem: backends: uart_isr: improve the reception of bytes

Add a configurable delay between when a byte is received and
MODEM_PIPE_EVENT_RECEIVE_READY is sent.

This fixes data reception at baud rates above 460800, and
most likely also reduces the workload at any baud rate
when receiving bytes by not going through the work item
and callbacks for every single byte.

Signed-off-by: Tomi Fontanilles <tomi.fontanilles@nordicsemi.no>
This commit is contained in:
Tomi Fontanilles 2024-03-19 09:31:40 +02:00 committed by Fabio Baltieri
parent 9cea822cc4
commit 419a398c01
5 changed files with 44 additions and 13 deletions

View file

@ -42,7 +42,7 @@ struct modem_backend_uart_async {
struct modem_backend_uart {
const struct device *uart;
struct modem_pipe pipe;
struct k_work receive_ready_work;
struct k_work_delayable receive_ready_work;
struct k_work transmit_idle_work;
union {

View file

@ -22,14 +22,30 @@ config MODEM_BACKEND_UART_ASYNC
bool "Modem UART backend module async implementation"
default y if UART_ASYNC_API
if MODEM_BACKEND_UART_ISR
config MODEM_BACKEND_UART_ISR_RECEIVE_IDLE_TIMEOUT_MS
int "Modem ISR UART delay between first byte received and RECEIVE_READY pipe event"
default 20
help
This defines the delay, in milliseconds, that the backend waits
when receiving a byte before sending the RECEIVE_READY pipe event.
The backend will anyway send the event before this delay if buffer space runs out.
A good value is ~90% the time it takes to fill half the receive buffer.
It can be calculated as follows:
(<UART receive_buf_size> / 2) / (<UART baud rate> / <UART bits per byte>) * <ms per sec>
By default (for the modem_cellular driver): (512 / 2) / (115200 / 10) * 1000 = 22,222 => 20
endif
if MODEM_BACKEND_UART_ASYNC
config MODEM_BACKEND_UART_ASYNC_TRANSMIT_TIMEOUT_MS
int "Modem UART async transmit timeout in milliseconds"
int "Modem async UART transmit timeout in milliseconds"
default 1000
config MODEM_BACKEND_UART_ASYNC_RECEIVE_IDLE_TIMEOUT_MS
int "Modem UART async receive idle timeout in milliseconds"
int "Modem async UART receive idle timeout in milliseconds"
default 30
endif

View file

@ -13,8 +13,8 @@
static void modem_backend_uart_receive_ready_handler(struct k_work *item)
{
struct modem_backend_uart *backend =
CONTAINER_OF(item, struct modem_backend_uart, receive_ready_work);
struct modem_backend_uart *backend = CONTAINER_OF(
k_work_delayable_from_work(item), struct modem_backend_uart, receive_ready_work);
modem_pipe_notify_receive_ready(&backend->pipe);
}
@ -39,7 +39,8 @@ struct modem_pipe *modem_backend_uart_init(struct modem_backend_uart *backend,
memset(backend, 0x00, sizeof(*backend));
backend->uart = config->uart;
k_work_init(&backend->receive_ready_work, modem_backend_uart_receive_ready_handler);
k_work_init_delayable(&backend->receive_ready_work,
modem_backend_uart_receive_ready_handler);
k_work_init(&backend->transmit_idle_work, modem_backend_uart_transmit_idle_handler);
#ifdef CONFIG_MODEM_BACKEND_UART_ASYNC

View file

@ -123,7 +123,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev,
}
k_spin_unlock(&backend->async.receive_rb_lock, key);
k_work_submit(&backend->receive_ready_work);
k_work_schedule(&backend->receive_ready_work, K_NO_WAIT);
break;
case UART_RX_DISABLED:
@ -215,7 +215,7 @@ static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t siz
k_spin_unlock(&backend->async.receive_rb_lock, key);
if (!empty) {
k_work_submit(&backend->receive_ready_work);
k_work_schedule(&backend->receive_ready_work, K_NO_WAIT);
}
return (int)received;

View file

@ -30,6 +30,11 @@ static void modem_backend_uart_isr_irq_handler_receive_ready(struct modem_backen
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);
@ -37,14 +42,23 @@ static void modem_backend_uart_isr_irq_handler_receive_ready(struct modem_backen
}
ret = uart_fifo_read(backend->uart, buffer, size);
if (ret < 0) {
if (ret <= 0) {
ring_buf_put_finish(receive_rb, 0);
} else {
ring_buf_put_finish(receive_rb, (uint32_t)ret);
return;
}
ring_buf_put_finish(receive_rb, (uint32_t)ret);
if (ret > 0) {
k_work_submit(&backend->receive_ready_work);
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);
}
}