mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-02 03:22:30 +00:00
So far all received bytes over UART where blindly drained and pushed to ring_buf. This approach is okay for UART devices without configured HW flow control, as it basically decouples data processing from ISR handler and gives more time before data overrun. However when HW flow control is enabled, such behavior somehow suppresses UART flow control advantage, because data can overrun when pushing to ring_buf. Allow drivers utilizing modem_context framework to pass information about whether HW flow control is enabled or not. If it is enabled, then read data from UART FIFO to the point when RX ring_buf is totally filled and follow such situation by disabling RX interrupt. Incoming data will be paused on HW level, so there is lots of time for RX thread to process ring_buf content. Reenable RX interrupts after all data in ring_buf is processed, so that number of context switches is kept at minimum level. Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
193 lines
3.8 KiB
C
193 lines
3.8 KiB
C
/** @file
|
|
* @brief interface for modem context
|
|
*
|
|
* UART-based modem interface implementation for modem context driver.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2019 Foundries.io
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(modem_iface_uart, CONFIG_MODEM_LOG_LEVEL);
|
|
|
|
#include <kernel.h>
|
|
#include <drivers/uart.h>
|
|
|
|
#include "modem_context.h"
|
|
#include "modem_iface_uart.h"
|
|
|
|
/**
|
|
* @brief Drains UART.
|
|
*
|
|
* @note Discards remaining data.
|
|
*
|
|
* @param *iface: modem interface.
|
|
*
|
|
* @retval None.
|
|
*/
|
|
static void modem_iface_uart_flush(struct modem_iface *iface)
|
|
{
|
|
uint8_t c;
|
|
|
|
while (uart_fifo_read(iface->dev, &c, 1) > 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Modem interface interrupt handler.
|
|
*
|
|
* @note Fills interfaces ring buffer with received data.
|
|
* When ring buffer is full the data is discarded.
|
|
*
|
|
* @param *uart_dev: uart device.
|
|
*
|
|
* @retval None.
|
|
*/
|
|
static void modem_iface_uart_isr(const struct device *uart_dev,
|
|
void *user_data)
|
|
{
|
|
struct modem_context *ctx;
|
|
struct modem_iface_uart_data *data;
|
|
int rx = 0, ret;
|
|
uint8_t *dst;
|
|
uint32_t partial_size = 0;
|
|
uint32_t total_size = 0;
|
|
|
|
ARG_UNUSED(user_data);
|
|
|
|
/* lookup the modem context */
|
|
ctx = modem_context_from_iface_dev(uart_dev);
|
|
if (!ctx || !ctx->iface.iface_data) {
|
|
return;
|
|
}
|
|
|
|
data = (struct modem_iface_uart_data *)(ctx->iface.iface_data);
|
|
/* get all of the data off UART as fast as we can */
|
|
while (uart_irq_update(ctx->iface.dev) &&
|
|
uart_irq_rx_ready(ctx->iface.dev)) {
|
|
if (!partial_size) {
|
|
partial_size = ring_buf_put_claim(&data->rx_rb, &dst,
|
|
UINT32_MAX);
|
|
}
|
|
if (!partial_size) {
|
|
if (data->hw_flow_control) {
|
|
uart_irq_rx_disable(ctx->iface.dev);
|
|
} else {
|
|
LOG_ERR("Rx buffer doesn't have enough space");
|
|
modem_iface_uart_flush(&ctx->iface);
|
|
}
|
|
break;
|
|
}
|
|
|
|
rx = uart_fifo_read(ctx->iface.dev, dst, partial_size);
|
|
if (rx <= 0) {
|
|
continue;
|
|
}
|
|
|
|
dst += rx;
|
|
total_size += rx;
|
|
partial_size -= rx;
|
|
}
|
|
|
|
ret = ring_buf_put_finish(&data->rx_rb, total_size);
|
|
__ASSERT_NO_MSG(ret == 0);
|
|
|
|
if (total_size > 0) {
|
|
k_sem_give(&data->rx_sem);
|
|
}
|
|
}
|
|
|
|
static int modem_iface_uart_read(struct modem_iface *iface,
|
|
uint8_t *buf, size_t size, size_t *bytes_read)
|
|
{
|
|
struct modem_iface_uart_data *data;
|
|
|
|
if (!iface || !iface->iface_data) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size == 0) {
|
|
*bytes_read = 0;
|
|
return 0;
|
|
}
|
|
|
|
data = (struct modem_iface_uart_data *)(iface->iface_data);
|
|
*bytes_read = ring_buf_get(&data->rx_rb, buf, size);
|
|
|
|
if (data->hw_flow_control && *bytes_read == 0) {
|
|
uart_irq_rx_enable(iface->dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int modem_iface_uart_write(struct modem_iface *iface,
|
|
const uint8_t *buf, size_t size)
|
|
{
|
|
if (!iface || !iface->iface_data) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
do {
|
|
uart_poll_out(iface->dev, *buf++);
|
|
} while (--size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int modem_iface_uart_init_dev(struct modem_iface *iface,
|
|
const char *dev_name)
|
|
{
|
|
/* get UART device */
|
|
iface->dev = device_get_binding(dev_name);
|
|
if (!iface->dev) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
uart_irq_rx_disable(iface->dev);
|
|
uart_irq_tx_disable(iface->dev);
|
|
modem_iface_uart_flush(iface);
|
|
uart_irq_callback_set(iface->dev, modem_iface_uart_isr);
|
|
uart_irq_rx_enable(iface->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int modem_iface_uart_init(struct modem_iface *iface,
|
|
struct modem_iface_uart_data *data,
|
|
const char *dev_name)
|
|
{
|
|
int ret;
|
|
|
|
if (!iface || !data) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
iface->iface_data = data;
|
|
iface->read = modem_iface_uart_read;
|
|
iface->write = modem_iface_uart_write;
|
|
|
|
ring_buf_init(&data->rx_rb, data->rx_rb_buf_len, data->rx_rb_buf);
|
|
k_sem_init(&data->rx_sem, 0, 1);
|
|
|
|
/* get UART device */
|
|
ret = modem_iface_uart_init_dev(iface, dev_name);
|
|
if (ret < 0) {
|
|
iface->iface_data = NULL;
|
|
iface->read = NULL;
|
|
iface->write = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|