198 lines
5.0 KiB
C
198 lines
5.0 KiB
C
/*
|
|
* Copyright (c) 2022, Commonwealth Scientific and Industrial Research
|
|
* Organisation (CSIRO) ABN 41 687 119 230.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/drivers/uart.h>
|
|
|
|
#include "modem_context.h"
|
|
#include "modem_iface_uart.h"
|
|
|
|
LOG_MODULE_REGISTER(modem_iface_uart_async, CONFIG_MODEM_LOG_LEVEL);
|
|
|
|
#define RX_BUFFER_SIZE CONFIG_MODEM_IFACE_UART_ASYNC_RX_BUFFER_SIZE
|
|
#define RX_BUFFER_NUM CONFIG_MODEM_IFACE_UART_ASYNC_RX_NUM_BUFFERS
|
|
|
|
K_MEM_SLAB_DEFINE(uart_modem_async_rx_slab, RX_BUFFER_SIZE, RX_BUFFER_NUM, 1);
|
|
|
|
static void iface_uart_async_callback(const struct device *dev,
|
|
struct uart_event *evt,
|
|
void *user_data)
|
|
{
|
|
struct modem_iface *iface = user_data;
|
|
struct modem_iface_uart_data *data = iface->iface_data;
|
|
uint32_t written;
|
|
void *buf;
|
|
int rc;
|
|
|
|
switch (evt->type) {
|
|
case UART_TX_DONE:
|
|
k_sem_give(&data->tx_sem);
|
|
break;
|
|
case UART_RX_BUF_REQUEST:
|
|
/* Allocate next RX buffer for UART driver */
|
|
rc = k_mem_slab_alloc(&uart_modem_async_rx_slab, (void **)&buf, K_NO_WAIT);
|
|
if (rc < 0) {
|
|
/* Major problems, UART_RX_BUF_RELEASED event is not being generated, or
|
|
* CONFIG_MODEM_IFACE_UART_ASYNC_RX_NUM_BUFFERS is not large enough.
|
|
*/
|
|
LOG_ERR("RX buffer starvation");
|
|
break;
|
|
}
|
|
/* Provide the buffer to the UART driver */
|
|
uart_rx_buf_rsp(dev, buf, RX_BUFFER_SIZE);
|
|
break;
|
|
case UART_RX_BUF_RELEASED:
|
|
/* UART driver is done with memory, free it */
|
|
k_mem_slab_free(&uart_modem_async_rx_slab, (void *)evt->data.rx_buf.buf);
|
|
break;
|
|
case UART_RX_RDY:
|
|
/* Place received data on the ring buffer */
|
|
written = ring_buf_put(&data->rx_rb,
|
|
evt->data.rx.buf + evt->data.rx.offset,
|
|
evt->data.rx.len);
|
|
if (written != evt->data.rx.len) {
|
|
LOG_WRN("Received bytes dropped from ring buf");
|
|
}
|
|
/* Notify upper layer that new data has arrived */
|
|
k_sem_give(&data->rx_sem);
|
|
break;
|
|
case UART_RX_STOPPED:
|
|
break;
|
|
case UART_RX_DISABLED:
|
|
/* RX stopped (likely due to line error), re-enable it */
|
|
rc = k_mem_slab_alloc(&uart_modem_async_rx_slab, (void **)&buf, K_FOREVER);
|
|
if (rc < 0) {
|
|
LOG_ERR("RX disabled and buffer starvation");
|
|
break;
|
|
}
|
|
rc = uart_rx_enable(dev, buf, RX_BUFFER_SIZE,
|
|
CONFIG_MODEM_IFACE_UART_ASYNC_RX_TIMEOUT_US);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to re-enable UART");
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int modem_iface_uart_async_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;
|
|
}
|
|
|
|
/* Pull data off the ring buffer */
|
|
data = iface->iface_data;
|
|
*bytes_read = ring_buf_get(&data->rx_rb, buf, size);
|
|
return 0;
|
|
}
|
|
|
|
static int modem_iface_uart_async_write(struct modem_iface *iface,
|
|
const uint8_t *buf, size_t size)
|
|
{
|
|
struct modem_iface_uart_data *data;
|
|
int rc;
|
|
|
|
if (!iface || !iface->iface_data) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/* Start the transmission */
|
|
rc = uart_tx(iface->dev, buf, size, SYS_FOREVER_MS);
|
|
if (rc >= 0) {
|
|
/* Wait until the transmission completes */
|
|
data = iface->iface_data;
|
|
k_sem_take(&data->tx_sem, K_FOREVER);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int modem_iface_uart_init_dev(struct modem_iface *iface,
|
|
const struct device *dev)
|
|
{
|
|
struct modem_iface_uart_data *data;
|
|
void *buf;
|
|
int rc;
|
|
|
|
if (!device_is_ready(dev)) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Check if there's already a device inited to this iface. If so,
|
|
* interrupts needs to be disabled on that too before switching to avoid
|
|
* race conditions with modem_iface_uart_isr.
|
|
*/
|
|
if (iface->dev) {
|
|
LOG_WRN("Device %s already inited", iface->dev->name);
|
|
uart_rx_disable(iface->dev);
|
|
}
|
|
|
|
iface->dev = dev;
|
|
data = iface->iface_data;
|
|
|
|
/* Configure async UART callback */
|
|
rc = uart_callback_set(dev, iface_uart_async_callback, iface);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to set UART callback");
|
|
return rc;
|
|
}
|
|
/* Enable reception permanently on the interface */
|
|
k_mem_slab_alloc(&uart_modem_async_rx_slab, (void **)&buf, K_FOREVER);
|
|
rc = uart_rx_enable(dev, buf, RX_BUFFER_SIZE, CONFIG_MODEM_IFACE_UART_ASYNC_RX_TIMEOUT_US);
|
|
if (rc < 0) {
|
|
LOG_ERR("Failed to enable UART RX");
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int modem_iface_uart_init(struct modem_iface *iface, struct modem_iface_uart_data *data,
|
|
const struct modem_iface_uart_config *config)
|
|
{
|
|
int ret;
|
|
|
|
if (iface == NULL || data == NULL || config == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
iface->iface_data = data;
|
|
iface->read = modem_iface_uart_async_read;
|
|
iface->write = modem_iface_uart_async_write;
|
|
|
|
ring_buf_init(&data->rx_rb, config->rx_rb_buf_len, config->rx_rb_buf);
|
|
k_sem_init(&data->rx_sem, 0, 1);
|
|
k_sem_init(&data->tx_sem, 0, 1);
|
|
|
|
/* Configure hardware flow control */
|
|
data->hw_flow_control = config->hw_flow_control;
|
|
|
|
/* Get UART device */
|
|
ret = modem_iface_uart_init_dev(iface, config->dev);
|
|
if (ret < 0) {
|
|
iface->iface_data = NULL;
|
|
iface->read = NULL;
|
|
iface->write = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|