From 10bd2de2353dfab00d952ae1ba3950e270a18fc2 Mon Sep 17 00:00:00 2001 From: Markus Lassila Date: Fri, 7 Mar 2025 14:51:45 +0200 Subject: [PATCH] modem: backend: uart: Add hw-flow-control for UART Add Asynchronous UART implementation, which does not drop data when automatic hardware-flow-control is set in the device tree. With automatic hardware flow control, the CTS pin will be automatically deactivated when there are no more asynchronous UART RX buffers available. After buffer space becomes available, and UART RX is restarted, the CTS pin will be activated. Signed-off-by: Markus Lassila --- include/zephyr/modem/backend/uart.h | 35 +- subsys/modem/backends/CMakeLists.txt | 5 +- subsys/modem/backends/Kconfig | 15 +- subsys/modem/backends/modem_backend_uart.c | 4 +- .../modem/backends/modem_backend_uart_async.c | 65 +-- .../modem/backends/modem_backend_uart_async.h | 4 +- .../backends/modem_backend_uart_async_hwfc.c | 439 ++++++++++++++++++ subsys/modem/modem_cmux.c | 6 +- .../subsys/modem/backends/uart/testcase.yaml | 17 +- 9 files changed, 544 insertions(+), 46 deletions(-) create mode 100644 subsys/modem/backends/modem_backend_uart_async_hwfc.c diff --git a/include/zephyr/modem/backend/uart.h b/include/zephyr/modem/backend/uart.h index f68f411c6e2..d3e7e9a9881 100644 --- a/include/zephyr/modem/backend/uart.h +++ b/include/zephyr/modem/backend/uart.h @@ -30,17 +30,42 @@ struct modem_backend_uart_isr { uint32_t transmit_buf_put_limit; }; +struct modem_backend_uart_async_common { + uint8_t *transmit_buf; + uint32_t transmit_buf_size; + struct k_work rx_disabled_work; + atomic_t state; +}; + +#ifdef CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC + +struct rx_queue_event { + uint8_t *buf; + size_t len; +}; + struct modem_backend_uart_async { + struct modem_backend_uart_async_common common; + struct k_mem_slab rx_slab; + struct k_msgq rx_queue; + struct rx_queue_event rx_event; + struct rx_queue_event rx_queue_buf[CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC_BUFFER_COUNT]; + uint32_t rx_buf_size; + uint8_t rx_buf_count; +}; + +#else + +struct modem_backend_uart_async { + struct modem_backend_uart_async_common common; uint8_t *receive_bufs[2]; uint32_t receive_buf_size; struct ring_buf receive_rb; struct k_spinlock receive_rb_lock; - uint8_t *transmit_buf; - uint32_t transmit_buf_size; - struct k_work rx_disabled_work; - atomic_t state; }; +#endif /* CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC */ + struct modem_backend_uart { const struct device *uart; struct modem_pipe pipe; @@ -60,7 +85,7 @@ struct modem_backend_uart { struct modem_backend_uart_config { const struct device *uart; - uint8_t *receive_buf; + uint8_t *receive_buf __aligned(sizeof(uint32_t)); uint32_t receive_buf_size; uint8_t *transmit_buf; uint32_t transmit_buf_size; diff --git a/subsys/modem/backends/CMakeLists.txt b/subsys/modem/backends/CMakeLists.txt index 471452eebbc..e2247a8a09d 100644 --- a/subsys/modem/backends/CMakeLists.txt +++ b/subsys/modem/backends/CMakeLists.txt @@ -6,4 +6,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_TTY modem_backend_tty.c) zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART modem_backend_uart.c) zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ISR modem_backend_uart_isr.c) -zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ASYNC modem_backend_uart_async.c) +if(CONFIG_MODEM_BACKEND_UART_ASYNC) + zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC modem_backend_uart_async_hwfc.c) + zephyr_library_sources_ifndef(CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC modem_backend_uart_async.c) +endif() diff --git a/subsys/modem/backends/Kconfig b/subsys/modem/backends/Kconfig index 6bbadc2c588..2ce81c6dce1 100644 --- a/subsys/modem/backends/Kconfig +++ b/subsys/modem/backends/Kconfig @@ -48,6 +48,19 @@ config MODEM_BACKEND_UART_ASYNC_RECEIVE_IDLE_TIMEOUT_MS int "Modem async UART receive idle timeout in milliseconds" default 30 -endif +config MODEM_BACKEND_UART_ASYNC_HWFC + bool "Hardware flow control (HWFC) for the modem async UART backend" + select EXPERIMENTAL + +if MODEM_BACKEND_UART_ASYNC_HWFC + +config MODEM_BACKEND_UART_ASYNC_HWFC_BUFFER_COUNT + int "Modem async UART HWFC buffer count" + range 2 4 + default 3 + +endif # MODEM_BACKEND_UART_ASYNC_HWFC + +endif # MODEM_BACKEND_UART_ASYNC endif # MODEM_BACKEND_UART diff --git a/subsys/modem/backends/modem_backend_uart.c b/subsys/modem/backends/modem_backend_uart.c index e182a3acd81..82242a76c01 100644 --- a/subsys/modem/backends/modem_backend_uart.c +++ b/subsys/modem/backends/modem_backend_uart.c @@ -45,7 +45,9 @@ struct modem_pipe *modem_backend_uart_init(struct modem_backend_uart *backend, #ifdef CONFIG_MODEM_BACKEND_UART_ASYNC if (modem_backend_uart_async_is_supported(backend)) { - modem_backend_uart_async_init(backend, config); + if (modem_backend_uart_async_init(backend, config)) { + return NULL; + } return &backend->pipe; } #endif /* CONFIG_MODEM_BACKEND_UART_ASYNC */ diff --git a/subsys/modem/backends/modem_backend_uart_async.c b/subsys/modem/backends/modem_backend_uart_async.c index 1b18420f7ed..d96f513d606 100644 --- a/subsys/modem/backends/modem_backend_uart_async.c +++ b/subsys/modem/backends/modem_backend_uart_async.c @@ -22,13 +22,13 @@ enum { static bool modem_backend_uart_async_is_uart_stopped(struct modem_backend_uart *backend) { - if (!atomic_test_bit(&backend->async.state, + if (!atomic_test_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT) && - !atomic_test_bit(&backend->async.state, + !atomic_test_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT) && - !atomic_test_bit(&backend->async.state, + !atomic_test_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT) && - !atomic_test_bit(&backend->async.state, + !atomic_test_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT)) { return true; } @@ -38,7 +38,7 @@ static bool modem_backend_uart_async_is_uart_stopped(struct modem_backend_uart * static bool modem_backend_uart_async_is_open(struct modem_backend_uart *backend) { - return atomic_test_bit(&backend->async.state, + return atomic_test_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); } @@ -56,7 +56,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, switch (evt->type) { case UART_TX_DONE: - atomic_clear_bit(&backend->async.state, + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT); k_work_submit(&backend->transmit_idle_work); break; @@ -65,14 +65,14 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, if (modem_backend_uart_async_is_open(backend)) { LOG_WRN("Transmit aborted (%zu sent)", evt->data.tx.len); } - atomic_clear_bit(&backend->async.state, + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT); k_work_submit(&backend->transmit_idle_work); break; case UART_RX_BUF_REQUEST: - if (!atomic_test_and_set_bit(&backend->async.state, + if (!atomic_test_and_set_bit(&backend->async.common.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); @@ -80,7 +80,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, break; } - if (!atomic_test_and_set_bit(&backend->async.state, + if (!atomic_test_and_set_bit(&backend->async.common.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); @@ -93,14 +93,14 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, case UART_RX_BUF_RELEASED: if (evt->data.rx_buf.buf == backend->async.receive_bufs[0]) { - atomic_clear_bit(&backend->async.state, + atomic_clear_bit(&backend->async.common.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, + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT); break; @@ -131,7 +131,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, break; case UART_RX_DISABLED: - atomic_clear_bit(&backend->async.state, + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT); break; @@ -144,7 +144,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, } if (modem_backend_uart_async_is_uart_stopped(backend)) { - k_work_submit(&backend->async.rx_disabled_work); + k_work_submit(&backend->async.common.rx_disabled_work); } } @@ -153,12 +153,13 @@ static int modem_backend_uart_async_open(void *data) struct modem_backend_uart *backend = (struct modem_backend_uart *)data; int ret; - atomic_clear(&backend->async.state); + atomic_clear(&backend->async.common.state); ring_buf_reset(&backend->async.receive_rb); - atomic_set_bit(&backend->async.state, MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT); - atomic_set_bit(&backend->async.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT); - atomic_set_bit(&backend->async.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); + atomic_set_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT); + atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT); + atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); /* Receive buffers are used internally by UART, receive ring buffer is * used to store received data. @@ -167,7 +168,7 @@ static int modem_backend_uart_async_open(void *data) backend->async.receive_buf_size, CONFIG_MODEM_BACKEND_UART_ASYNC_RECEIVE_IDLE_TIMEOUT_MS * 1000L); if (ret < 0) { - atomic_clear(&backend->async.state); + atomic_clear(&backend->async.common.state); return ret; } @@ -197,7 +198,7 @@ static void advertise_receive_buf_stats(struct modem_backend_uart *backend) static uint32_t get_transmit_buf_size(struct modem_backend_uart *backend) { - return backend->async.transmit_buf_size; + return backend->async.common.transmit_buf_size; } static int modem_backend_uart_async_transmit(void *data, const uint8_t *buf, size_t size) @@ -207,7 +208,7 @@ static int modem_backend_uart_async_transmit(void *data, const uint8_t *buf, siz uint32_t bytes_to_transmit; int ret; - transmitting = atomic_test_and_set_bit(&backend->async.state, + transmitting = atomic_test_and_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT); if (transmitting) { return 0; @@ -217,9 +218,9 @@ static int modem_backend_uart_async_transmit(void *data, const uint8_t *buf, siz bytes_to_transmit = MIN(size, get_transmit_buf_size(backend)); /* Copy buf to transmit buffer which is passed to UART */ - memcpy(backend->async.transmit_buf, buf, bytes_to_transmit); + memcpy(backend->async.common.transmit_buf, buf, bytes_to_transmit); - ret = uart_tx(backend->uart, backend->async.transmit_buf, bytes_to_transmit, + ret = uart_tx(backend->uart, backend->async.common.transmit_buf, bytes_to_transmit, CONFIG_MODEM_BACKEND_UART_ASYNC_TRANSMIT_TIMEOUT_MS * 1000L); #if CONFIG_MODEM_STATS @@ -263,7 +264,7 @@ static int modem_backend_uart_async_close(void *data) { struct modem_backend_uart *backend = (struct modem_backend_uart *)data; - atomic_clear_bit(&backend->async.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); uart_tx_abort(backend->uart); uart_rx_disable(backend->uart); return 0; @@ -284,8 +285,11 @@ bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend) static void modem_backend_uart_async_notify_closed(struct k_work *item) { + struct modem_backend_uart_async_common *common = + CONTAINER_OF(item, struct modem_backend_uart_async_common, rx_disabled_work); + struct modem_backend_uart_async *async = - CONTAINER_OF(item, struct modem_backend_uart_async, rx_disabled_work); + CONTAINER_OF(common, struct modem_backend_uart_async, common); struct modem_backend_uart *backend = CONTAINER_OF(async, struct modem_backend_uart, async); @@ -310,8 +314,8 @@ static void init_stats(struct modem_backend_uart *backend) } #endif -void modem_backend_uart_async_init(struct modem_backend_uart *backend, - const struct modem_backend_uart_config *config) +int 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; @@ -324,12 +328,15 @@ void modem_backend_uart_async_init(struct modem_backend_uart *backend, ring_buf_init(&backend->async.receive_rb, (receive_buf_size_quarter * 2), &config->receive_buf[receive_buf_size_quarter * 2]); - 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); + backend->async.common.transmit_buf = config->transmit_buf; + backend->async.common.transmit_buf_size = config->transmit_buf_size; + k_work_init(&backend->async.common.rx_disabled_work, + modem_backend_uart_async_notify_closed); modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_async_api); #if CONFIG_MODEM_STATS init_stats(backend); #endif + + return 0; } diff --git a/subsys/modem/backends/modem_backend_uart_async.h b/subsys/modem/backends/modem_backend_uart_async.h index dce7e2b71e8..9d271655f11 100644 --- a/subsys/modem/backends/modem_backend_uart_async.h +++ b/subsys/modem/backends/modem_backend_uart_async.h @@ -15,8 +15,8 @@ extern "C" { bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend); -void modem_backend_uart_async_init(struct modem_backend_uart *backend, - const struct modem_backend_uart_config *config); +int modem_backend_uart_async_init(struct modem_backend_uart *backend, + const struct modem_backend_uart_config *config); #ifdef __cplusplus } diff --git a/subsys/modem/backends/modem_backend_uart_async_hwfc.c b/subsys/modem/backends/modem_backend_uart_async_hwfc.c new file mode 100644 index 00000000000..5bb7d395a45 --- /dev/null +++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "modem_backend_uart_async.h" + +#include +LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL); + +#include +#include + +struct rx_buf_t { + atomic_t ref_counter; + uint8_t buf[]; +}; + +static inline struct rx_buf_t *block_start_get(struct modem_backend_uart_async *async, uint8_t *buf) +{ + size_t block_num; + + /* Find the correct block. */ + block_num = (((size_t)buf - sizeof(struct rx_buf_t) - (size_t)async->rx_slab.buffer) / + async->rx_buf_size); + + return (struct rx_buf_t *) &async->rx_slab.buffer[block_num * async->rx_buf_size]; +} + +static struct rx_buf_t *rx_buf_alloc(struct modem_backend_uart_async *async) +{ + struct rx_buf_t *buf; + + if (k_mem_slab_alloc(&async->rx_slab, (void **) &buf, K_NO_WAIT)) { + return NULL; + } + atomic_set(&buf->ref_counter, 1); + + return buf; +} + +static void rx_buf_ref(struct modem_backend_uart_async *async, void *buf) +{ + atomic_inc(&(block_start_get(async, buf)->ref_counter)); +} + +static void rx_buf_unref(struct modem_backend_uart_async *async, void *buf) +{ + struct rx_buf_t *uart_buf = block_start_get(async, buf); + atomic_t ref_counter = atomic_dec(&uart_buf->ref_counter); + + if (ref_counter == 1) { + k_mem_slab_free(&async->rx_slab, (void *)uart_buf); + } +} + +enum { + MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT, + MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT, +}; + +static int modem_backend_uart_async_hwfc_rx_enable(struct modem_backend_uart *backend) +{ + int ret; + struct rx_buf_t *buf = rx_buf_alloc(&backend->async); + + if (!buf) { + return -ENOMEM; + } + + ret = uart_rx_enable(backend->uart, buf->buf, + backend->async.rx_buf_size - sizeof(struct rx_buf_t), + CONFIG_MODEM_BACKEND_UART_ASYNC_RECEIVE_IDLE_TIMEOUT_MS * 1000); + if (ret) { + rx_buf_unref(&backend->async, buf->buf); + return ret; + } + + return 0; +} + +static void modem_backend_uart_async_hwfc_rx_recovery(struct modem_backend_uart *backend) +{ + int err; + + if (!atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT)) { + return; + } + + err = modem_backend_uart_async_hwfc_rx_enable(backend); + if (err) { + LOG_DBG("RX recovery failed: %d", err); + return; + } + + if (!atomic_test_and_clear_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT)) { + /* Closed during recovery. */ + uart_rx_disable(backend->uart); + } else { + LOG_DBG("RX recovery success"); + } +} + +static bool modem_backend_uart_async_hwfc_is_uart_stopped(struct modem_backend_uart *backend) +{ + if (!atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT) && + !atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT) && + !atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT)) { + return true; + } + + return false; +} + +static bool modem_backend_uart_async_hwfc_is_open(struct modem_backend_uart *backend) +{ + return atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); +} + +static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev, + struct uart_event *evt, void *user_data) +{ + struct modem_backend_uart *backend = (struct modem_backend_uart *) user_data; + struct rx_queue_event rx_event; + int err; + + switch (evt->type) { + case UART_TX_DONE: + atomic_clear_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT); + k_work_submit(&backend->transmit_idle_work); + break; + + case UART_TX_ABORTED: + if (modem_backend_uart_async_hwfc_is_open(backend)) { + LOG_WRN("Transmit aborted (%zu sent)", evt->data.tx.len); + } + atomic_clear_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT); + k_work_submit(&backend->transmit_idle_work); + + break; + + case UART_RX_BUF_REQUEST: + struct rx_buf_t *buf = rx_buf_alloc(&backend->async); + + if (!buf) { + LOG_DBG("No receive buffer, disabling RX"); + break; + } + err = uart_rx_buf_rsp(backend->uart, buf->buf, + backend->async.rx_buf_size - sizeof(struct rx_buf_t)); + if (err) { + LOG_ERR("uart_rx_buf_rsp: %d", err); + rx_buf_unref(&backend->async, buf->buf); + } + break; + + case UART_RX_BUF_RELEASED: + if (evt->data.rx_buf.buf) { + rx_buf_unref(&backend->async, evt->data.rx_buf.buf); + } + break; + + case UART_RX_RDY: + if (evt->data.rx.buf) { + rx_buf_ref(&backend->async, evt->data.rx.buf); + rx_event.buf = &evt->data.rx.buf[evt->data.rx.offset]; + rx_event.len = evt->data.rx.len; + err = k_msgq_put(&backend->async.rx_queue, &rx_event, K_NO_WAIT); + if (err) { + LOG_WRN("RX queue overflow: %d (dropped %u)", err, + evt->data.rx.len); + rx_buf_unref(&backend->async, evt->data.rx.buf); + break; + } + k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + } + break; + + case UART_RX_DISABLED: + if (atomic_test_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT)) { + if (!atomic_test_and_set_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT)) { + k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + LOG_DBG("RX recovery started"); + } + } + break; + + case UART_RX_STOPPED: + LOG_WRN("Receive stopped for reasons: %u", (uint8_t)evt->data.rx_stop.reason); + break; + + default: + break; + } + + if (modem_backend_uart_async_hwfc_is_uart_stopped(backend)) { + k_work_submit(&backend->async.common.rx_disabled_work); + } +} + +static int modem_backend_uart_async_hwfc_open(void *data) +{ + struct modem_backend_uart *backend = (struct modem_backend_uart *)data; + struct rx_buf_t *buf = rx_buf_alloc(&backend->async); + int ret; + + if (!buf) { + return -ENOMEM; + } + + atomic_clear(&backend->async.common.state); + atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); + + ret = uart_rx_enable(backend->uart, buf->buf, + backend->async.rx_buf_size - sizeof(struct rx_buf_t), + CONFIG_MODEM_BACKEND_UART_ASYNC_RECEIVE_IDLE_TIMEOUT_MS * 1000L); + if (ret < 0) { + rx_buf_unref(&backend->async, buf->buf); + atomic_clear(&backend->async.common.state); + return ret; + } + + modem_pipe_notify_opened(&backend->pipe); + return 0; +} + +#if CONFIG_MODEM_STATS +static uint32_t get_receive_buf_size(struct modem_backend_uart *backend) +{ + return (backend->async.rx_buf_size - sizeof(struct rx_buf_t)) * backend->async.rx_buf_count; +} + +static void advertise_transmit_buf_stats(struct modem_backend_uart *backend, uint32_t length) +{ + modem_stats_buffer_advertise_length(&backend->transmit_buf_stats, length); +} + +static void advertise_receive_buf_stats(struct modem_backend_uart *backend, uint32_t reserved) +{ + modem_stats_buffer_advertise_length(&backend->receive_buf_stats, reserved); +} +#endif + +static uint32_t get_transmit_buf_size(struct modem_backend_uart *backend) +{ + return backend->async.common.transmit_buf_size; +} + +static int modem_backend_uart_async_hwfc_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.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT); + if (transmitting) { + return 0; + } + + /* Determine amount of bytes to transmit */ + bytes_to_transmit = MIN(size, get_transmit_buf_size(backend)); + + /* Copy buf to transmit buffer which is passed to UART */ + memcpy(backend->async.common.transmit_buf, buf, bytes_to_transmit); + + ret = uart_tx(backend->uart, backend->async.common.transmit_buf, bytes_to_transmit, + CONFIG_MODEM_BACKEND_UART_ASYNC_TRANSMIT_TIMEOUT_MS * 1000L); + +#if CONFIG_MODEM_STATS + advertise_transmit_buf_stats(backend, bytes_to_transmit); +#endif + + if (ret != 0) { + LOG_ERR("Failed to %s %u bytes. (%d)", + "start async transmit for", bytes_to_transmit, ret); + return ret; + } + + return (int)bytes_to_transmit; +} + +static int modem_backend_uart_async_hwfc_receive(void *data, uint8_t *buf, size_t size) +{ + struct modem_backend_uart *backend = (struct modem_backend_uart *)data; + size_t received = 0; + size_t copy_size = 0; + +#if CONFIG_MODEM_STATS + struct rx_queue_event rx_event; + size_t reserved = backend->async.rx_event.len; + + for (int i = 0; i < k_msgq_num_used_get(&backend->async.rx_queue); i++) { + if (k_msgq_peek_at(&backend->async.rx_queue, &rx_event, i)) { + break; + } + reserved += rx_event.len; + } + advertise_receive_buf_stats(backend, reserved); +#endif + while (size > received) { + /* Keeping track of the async.rx_event allows us to receive less than what the event + * indicates. + */ + if (backend->async.rx_event.len == 0) { + if (k_msgq_get(&backend->async.rx_queue, &backend->async.rx_event, + K_NO_WAIT)) { + break; + } + } + copy_size = MIN(size - received, backend->async.rx_event.len); + memcpy(buf, backend->async.rx_event.buf, copy_size); + buf += copy_size; + received += copy_size; + backend->async.rx_event.buf += copy_size; + backend->async.rx_event.len -= copy_size; + + if (backend->async.rx_event.len == 0) { + rx_buf_unref(&backend->async, backend->async.rx_event.buf); + } + } + + if (backend->async.rx_event.len != 0 || + k_msgq_num_used_get(&backend->async.rx_queue) != 0) { + k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + } + + modem_backend_uart_async_hwfc_rx_recovery(backend); + + return (int)received; +} + +static int modem_backend_uart_async_hwfc_close(void *data) +{ + struct modem_backend_uart *backend = (struct modem_backend_uart *)data; + + atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); + uart_tx_abort(backend->uart); + + if (!atomic_test_and_clear_bit(&backend->async.common.state, + MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT)) { + /* Disable the RX, if recovery is not ongoing. */ + uart_rx_disable(backend->uart); + } + + return 0; +} + +static const struct modem_pipe_api modem_backend_uart_async_api = { + .open = modem_backend_uart_async_hwfc_open, + .transmit = modem_backend_uart_async_hwfc_transmit, + .receive = modem_backend_uart_async_hwfc_receive, + .close = modem_backend_uart_async_hwfc_close, +}; + +bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend) +{ + return uart_callback_set(backend->uart, modem_backend_uart_async_hwfc_event_handler, + backend) == 0; +} + +static void modem_backend_uart_async_hwfc_notify_closed(struct k_work *item) +{ + struct modem_backend_uart_async_common *common = + CONTAINER_OF(item, struct modem_backend_uart_async_common, rx_disabled_work); + + struct modem_backend_uart_async *async = + CONTAINER_OF(common, struct modem_backend_uart_async, common); + + struct modem_backend_uart *backend = + CONTAINER_OF(async, struct modem_backend_uart, async); + + modem_pipe_notify_closed(&backend->pipe); +} + +#if CONFIG_MODEM_STATS +static void init_stats(struct modem_backend_uart *backend) +{ + char name[CONFIG_MODEM_STATS_BUFFER_NAME_SIZE]; + uint32_t receive_buf_size; + uint32_t transmit_buf_size; + + receive_buf_size = get_receive_buf_size(backend); + transmit_buf_size = get_transmit_buf_size(backend); + + snprintk(name, sizeof(name), "%s_%s", backend->uart->name, "rx"); + modem_stats_buffer_init(&backend->receive_buf_stats, name, receive_buf_size); + snprintk(name, sizeof(name), "%s_%s", backend->uart->name, "tx"); + modem_stats_buffer_init(&backend->transmit_buf_stats, name, transmit_buf_size); +} +#endif + +int modem_backend_uart_async_init(struct modem_backend_uart *backend, + const struct modem_backend_uart_config *config) +{ + int32_t buf_size = (int32_t)config->receive_buf_size; + int err; + + backend->async.rx_buf_count = CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC_BUFFER_COUNT; + + /* Make sure all the buffers will be aligned. */ + buf_size -= (config->receive_buf_size % (sizeof(uint32_t) * backend->async.rx_buf_count)); + backend->async.rx_buf_size = buf_size / backend->async.rx_buf_count; + __ASSERT_NO_MSG(backend->async.rx_buf_size > sizeof(struct rx_buf_t)); + + /* Initialize the RX buffers and event queue. */ + err = k_mem_slab_init(&backend->async.rx_slab, config->receive_buf, + backend->async.rx_buf_size, backend->async.rx_buf_count); + if (err) { + return err; + } + k_msgq_init(&backend->async.rx_queue, (char *)&backend->async.rx_queue_buf, + sizeof(struct rx_queue_event), CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC_BUFFER_COUNT); + + backend->async.common.transmit_buf = config->transmit_buf; + backend->async.common.transmit_buf_size = config->transmit_buf_size; + k_work_init(&backend->async.common.rx_disabled_work, + modem_backend_uart_async_hwfc_notify_closed); + + modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_async_api); + +#if CONFIG_MODEM_STATS + init_stats(backend); +#endif + return 0; +} diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 48498f5b72c..d5547e03da2 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -1108,7 +1108,7 @@ static int modem_cmux_dlci_pipe_api_transmit(void *data, const uint8_t *buf, siz { struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data; struct modem_cmux *cmux = dlci->cmux; - int ret; + int ret = 0; K_SPINLOCK(&cmux->work_lock) { if (!cmux->attached) { @@ -1345,7 +1345,7 @@ int modem_cmux_connect(struct modem_cmux *cmux) int modem_cmux_connect_async(struct modem_cmux *cmux) { - int ret; + int ret = 0; if (k_event_test(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT)) { return -EALREADY; @@ -1360,8 +1360,6 @@ int modem_cmux_connect_async(struct modem_cmux *cmux) if (k_work_delayable_is_pending(&cmux->connect_work) == false) { k_work_schedule(&cmux->connect_work, K_NO_WAIT); } - - ret = 0; } return ret; diff --git a/tests/subsys/modem/backends/uart/testcase.yaml b/tests/subsys/modem/backends/uart/testcase.yaml index be53728c724..84a4c18c3d1 100644 --- a/tests/subsys/modem/backends/uart/testcase.yaml +++ b/tests/subsys/modem/backends/uart/testcase.yaml @@ -5,15 +5,26 @@ common: harness: ztest harness_config: fixture: gpio_loopback - platform_allow: - - b_u585i_iot02a - - nrf5340dk/nrf5340/cpuapp tests: modem.backends.uart.async: extra_configs: - CONFIG_UART_ASYNC_API=y + platform_allow: + - b_u585i_iot02a + - nrf5340dk/nrf5340/cpuapp + + modem.backends.uart.async.hwfc: + extra_configs: + - CONFIG_UART_ASYNC_API=y + - CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC=y + - CONFIG_TEST_HW_FLOW_CONTROL=y + platform_allow: + - nrf5340dk/nrf5340/cpuapp modem.backends.uart.isr: extra_configs: - CONFIG_UART_INTERRUPT_DRIVEN=y + platform_allow: + - b_u585i_iot02a + - nrf5340dk/nrf5340/cpuapp