mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-01 05:06:20 +00:00
ringbuf claim API returns pointer to contiguous area. In cases when data in ringbuf wraps the end of internal buffer, then single call to claim data is not enough to get all data - there is remaining part on the beginning of internal buffer. Those remaining bytes will need to wait for next ISR handler to trigger workqueue. Theoretically this means that data on the beginning of ringbuf can wait there forever. Consume data from ringbuf in a loop, stopping only when claiming results in empty buffer. This will make sure that there is no stale data in the ringbuf. Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
869 lines
20 KiB
C
869 lines
20 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(uart_mux, CONFIG_UART_MUX_LOG_LEVEL);
|
|
|
|
#include <sys/__assert.h>
|
|
#include <kernel.h>
|
|
#include <init.h>
|
|
#include <syscall_handler.h>
|
|
#include <device.h>
|
|
#include <drivers/uart.h>
|
|
#include <drivers/console/uart_mux.h>
|
|
#include <sys/ring_buffer.h>
|
|
#include <sys/util.h>
|
|
#include <sys/atomic.h>
|
|
|
|
#include "gsm_mux.h"
|
|
|
|
#if CONFIG_UART_MUX_DEVICE_COUNT == 0
|
|
#error "CONFIG_UART_MUX_DEVICE_COUNT tells number of DLCIs to create " \
|
|
"and must be >0"
|
|
#endif
|
|
|
|
#define UART_MUX_WORKQ_PRIORITY CONFIG_UART_MUX_RX_PRIORITY
|
|
#define UART_MUX_WORKQ_STACK_SIZE CONFIG_UART_MUX_RX_STACK_SIZE
|
|
|
|
/* All the RX/TX data is passed via own workqueue. This is done like this
|
|
* as the GSM modem uses global workqueue which causes difficulties if we do
|
|
* the same here. This workqueue is shared between all the DLCI channels.
|
|
*/
|
|
K_KERNEL_STACK_DEFINE(uart_mux_stack, UART_MUX_WORKQ_STACK_SIZE);
|
|
static struct k_work_q uart_mux_workq;
|
|
|
|
/* The UART mux contains information about the real UART. It will synchronize
|
|
* the access to the real UART and pass data between it and GSM muxing API.
|
|
* Usually there is only one instance of these in the system, if we have only
|
|
* one UART connected to modem device.
|
|
*/
|
|
struct uart_mux {
|
|
/* The real UART device that is shared between muxed UARTs */
|
|
struct device *uart;
|
|
|
|
/* GSM mux related to this UART */
|
|
struct gsm_mux *mux;
|
|
|
|
/* Received data is routed from ISR to MUX API via ring buffer */
|
|
struct ring_buf *rx_ringbuf;
|
|
|
|
/* RX worker that passes data from RX ISR to GSM mux API */
|
|
struct k_work rx_work;
|
|
|
|
/* Mutex for accessing the real UART */
|
|
struct k_mutex lock;
|
|
|
|
/* Flag that tells whether this instance is initialized or not */
|
|
atomic_t init_done;
|
|
|
|
/* Temporary buffer when reading data in ISR */
|
|
uint8_t rx_buf[CONFIG_UART_MUX_TEMP_BUF_SIZE];
|
|
};
|
|
|
|
#define DEFINE_UART_MUX(x, _) \
|
|
RING_BUF_DECLARE(uart_rx_ringbuf_##x, \
|
|
CONFIG_UART_MUX_RINGBUF_SIZE); \
|
|
static struct uart_mux uart_mux_##x __used \
|
|
__attribute__((__section__(".uart_mux.data"))) = { \
|
|
.rx_ringbuf = &uart_rx_ringbuf_##x, \
|
|
};
|
|
|
|
UTIL_LISTIFY(CONFIG_UART_MUX_REAL_DEVICE_COUNT, DEFINE_UART_MUX, _)
|
|
|
|
extern struct uart_mux __uart_mux_start[];
|
|
extern struct uart_mux __uart_mux_end[];
|
|
|
|
/* UART Mux Driver Status Codes */
|
|
enum uart_mux_status_code {
|
|
UART_MUX_UNKNOWN, /* Initial connection status */
|
|
UART_MUX_CONFIGURED, /* UART mux configuration done */
|
|
UART_MUX_CONNECTED, /* UART mux connected */
|
|
UART_MUX_DISCONNECTED, /* UART mux connection lost */
|
|
};
|
|
|
|
struct uart_mux_config {
|
|
};
|
|
|
|
#define DEV_DATA(dev) \
|
|
((struct uart_mux_dev_data *)(dev)->data)
|
|
|
|
struct uart_mux_dev_data {
|
|
sys_snode_t node;
|
|
|
|
/* Configuration data */
|
|
struct uart_mux_config cfg;
|
|
|
|
/* This UART mux device */
|
|
struct device *dev;
|
|
|
|
/* The UART device where we are running on top of */
|
|
struct uart_mux *real_uart;
|
|
|
|
/* TX worker that will mux the transmitted data */
|
|
struct k_work tx_work;
|
|
|
|
/* ISR function callback worker */
|
|
struct k_work cb_work;
|
|
|
|
/* ISR function callback */
|
|
uart_irq_callback_user_data_t cb;
|
|
void *cb_user_data;
|
|
|
|
/* Attach callback */
|
|
uart_mux_attach_cb_t attach_cb;
|
|
void *attach_user_data;
|
|
|
|
/* TX data from application is handled via ring buffer */
|
|
struct ring_buf *tx_ringbuf;
|
|
|
|
/* Received data is routed from RX worker to application via ring
|
|
* buffer.
|
|
*/
|
|
struct ring_buf *rx_ringbuf;
|
|
|
|
/* Muxing status */
|
|
enum uart_mux_status_code status;
|
|
|
|
/* DLCI (muxing virtual channel) linked to this muxed UART */
|
|
struct gsm_dlci *dlci;
|
|
|
|
/* Status (enabled / disabled) for RX and TX */
|
|
bool rx_enabled : 1;
|
|
bool tx_enabled : 1;
|
|
bool rx_ready : 1;
|
|
bool tx_ready : 1;
|
|
bool in_use : 1;
|
|
};
|
|
|
|
struct uart_mux_cfg_data {
|
|
};
|
|
|
|
static sys_slist_t uart_mux_data_devlist;
|
|
|
|
static void uart_mux_cb_work(struct k_work *work)
|
|
{
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(work, struct uart_mux_dev_data, cb_work);
|
|
|
|
dev_data->cb(dev_data->cb_user_data);
|
|
}
|
|
|
|
static int uart_mux_consume_ringbuf(struct uart_mux *uart_mux)
|
|
{
|
|
uint8_t *data;
|
|
size_t len;
|
|
int ret;
|
|
|
|
len = ring_buf_get_claim(uart_mux->rx_ringbuf, &data,
|
|
CONFIG_UART_MUX_RINGBUF_SIZE);
|
|
if (len == 0) {
|
|
LOG_DBG("Ringbuf %p is empty!", uart_mux->rx_ringbuf);
|
|
return 0;
|
|
}
|
|
|
|
/* We have now received muxed data. Push that through GSM mux API which
|
|
* will parse it and call proper functions to get the data to the user.
|
|
*/
|
|
|
|
if (IS_ENABLED(CONFIG_UART_MUX_VERBOSE_DEBUG)) {
|
|
char tmp[sizeof("RECV muxed ") + 10];
|
|
|
|
snprintk(tmp, sizeof(tmp), "RECV muxed %s",
|
|
uart_mux->uart->name);
|
|
LOG_HEXDUMP_DBG(data, len, log_strdup(tmp));
|
|
}
|
|
|
|
gsm_mux_recv_buf(uart_mux->mux, data, len);
|
|
|
|
ret = ring_buf_get_finish(uart_mux->rx_ringbuf, len);
|
|
if (ret < 0) {
|
|
LOG_DBG("Cannot flush ring buffer (%d)", ret);
|
|
}
|
|
|
|
return -EAGAIN;
|
|
}
|
|
|
|
static void uart_mux_rx_work(struct k_work *work)
|
|
{
|
|
struct uart_mux *uart_mux =
|
|
CONTAINER_OF(work, struct uart_mux, rx_work);;
|
|
int ret;
|
|
|
|
do {
|
|
ret = uart_mux_consume_ringbuf(uart_mux);
|
|
} while (ret == -EAGAIN);
|
|
}
|
|
|
|
static void uart_mux_tx_work(struct k_work *work)
|
|
{
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(work, struct uart_mux_dev_data, tx_work);
|
|
uint8_t *data;
|
|
size_t len;
|
|
|
|
len = ring_buf_get_claim(dev_data->tx_ringbuf, &data,
|
|
CONFIG_UART_MUX_RINGBUF_SIZE);
|
|
if (!len) {
|
|
LOG_DBG("Ringbuf %p empty!", dev_data->tx_ringbuf);
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("Got %d bytes from ringbuffer send to uart %p", len,
|
|
dev_data->dev);
|
|
|
|
if (IS_ENABLED(CONFIG_UART_MUX_VERBOSE_DEBUG)) {
|
|
char tmp[sizeof("SEND _x") +
|
|
sizeof(CONFIG_UART_MUX_DEVICE_NAME)];
|
|
|
|
snprintk(tmp, sizeof(tmp), "SEND %s",
|
|
dev_data->dev->name);
|
|
LOG_HEXDUMP_DBG(data, len, log_strdup(tmp));
|
|
}
|
|
|
|
(void)gsm_dlci_send(dev_data->dlci, data, len);
|
|
|
|
ring_buf_get_finish(dev_data->tx_ringbuf, len);
|
|
}
|
|
|
|
static int uart_mux_init(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
gsm_mux_init();
|
|
|
|
dev_data->dev = dev;
|
|
dev_data->real_uart = NULL; /* will be set when user attach to it */
|
|
|
|
sys_slist_find_and_remove(&uart_mux_data_devlist, &dev_data->node);
|
|
sys_slist_prepend(&uart_mux_data_devlist, &dev_data->node);
|
|
|
|
k_work_init(&dev_data->tx_work, uart_mux_tx_work);
|
|
k_work_init(&dev_data->cb_work, uart_mux_cb_work);
|
|
|
|
LOG_DBG("Device %s dev %p dev_data %p cfg %p created",
|
|
dev->name, dev, dev_data, dev->config);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This IRQ handler is shared between muxing UARTs. After we have received
|
|
* data from it in uart_mux_rx_work(), we push the data to GSM mux API which
|
|
* will call proper callbacks to pass data to correct recipient.
|
|
*/
|
|
static void uart_mux_isr(struct device *uart, void *user_data)
|
|
{
|
|
struct uart_mux *real_uart = user_data;
|
|
int rx = 0;
|
|
size_t wrote = 0;
|
|
|
|
/* Read all data off UART, and send to RX worker for unmuxing */
|
|
while (uart_irq_update(uart) &&
|
|
uart_irq_rx_ready(uart)) {
|
|
rx = uart_fifo_read(uart, real_uart->rx_buf,
|
|
sizeof(real_uart->rx_buf));
|
|
if (rx <= 0) {
|
|
continue;
|
|
}
|
|
|
|
wrote = ring_buf_put(real_uart->rx_ringbuf,
|
|
real_uart->rx_buf, rx);
|
|
if (wrote < rx) {
|
|
LOG_ERR("Ring buffer full, drop %d bytes",
|
|
rx - wrote);
|
|
}
|
|
|
|
k_work_submit_to_queue(&uart_mux_workq, &real_uart->rx_work);
|
|
}
|
|
}
|
|
|
|
static void uart_mux_flush_isr(struct device *dev)
|
|
{
|
|
uint8_t c;
|
|
|
|
while (uart_fifo_read(dev, &c, 1) > 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
static void dlci_created_cb(struct gsm_dlci *dlci, bool connected,
|
|
void *user_data)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = user_data;
|
|
|
|
if (connected) {
|
|
dev_data->status = UART_MUX_CONNECTED;
|
|
} else {
|
|
dev_data->status = UART_MUX_DISCONNECTED;
|
|
}
|
|
|
|
LOG_DBG("%s %s", dev_data->dev->name,
|
|
dev_data->status == UART_MUX_CONNECTED ? "connected" :
|
|
"disconnected");
|
|
|
|
if (dev_data->attach_cb) {
|
|
dev_data->attach_cb(dev_data->dev,
|
|
dlci ? gsm_dlci_id(dlci) : -1,
|
|
connected,
|
|
dev_data->attach_user_data);
|
|
}
|
|
}
|
|
|
|
static int init_real_uart(struct device *mux, struct device *uart,
|
|
struct uart_mux **mux_uart)
|
|
{
|
|
bool found = false;
|
|
struct uart_mux *real_uart;
|
|
|
|
for (real_uart = __uart_mux_start; real_uart != __uart_mux_end;
|
|
real_uart++) {
|
|
if (real_uart->uart == uart) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == false) {
|
|
for (real_uart = __uart_mux_start; real_uart != __uart_mux_end;
|
|
real_uart++) {
|
|
if (real_uart->uart == NULL) {
|
|
real_uart->uart = uart;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == false) {
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
/* Init the real UART only once */
|
|
if (atomic_cas(&real_uart->init_done, false, true)) {
|
|
real_uart->mux = gsm_mux_create(mux);
|
|
|
|
LOG_DBG("Initializing UART %s and GSM mux %p",
|
|
real_uart->uart->name, real_uart->mux);
|
|
|
|
if (!real_uart->mux) {
|
|
real_uart->uart = NULL;
|
|
atomic_clear(&real_uart->init_done);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
k_work_init(&real_uart->rx_work, uart_mux_rx_work);
|
|
k_mutex_init(&real_uart->lock);
|
|
|
|
uart_irq_rx_disable(real_uart->uart);
|
|
uart_irq_tx_disable(real_uart->uart);
|
|
uart_mux_flush_isr(real_uart->uart);
|
|
uart_irq_callback_user_data_set(
|
|
real_uart->uart, uart_mux_isr,
|
|
real_uart);
|
|
|
|
uart_irq_rx_enable(real_uart->uart);
|
|
}
|
|
|
|
__ASSERT(real_uart->uart, "Real UART not set");
|
|
|
|
*mux_uart = real_uart;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* This will bind the physical (real) UART to this muxed UART */
|
|
static int attach(struct device *mux_uart, struct device *uart,
|
|
int dlci_address, uart_mux_attach_cb_t cb,
|
|
void *user_data)
|
|
{
|
|
sys_snode_t *sn, *sns;
|
|
|
|
if (mux_uart == NULL || uart == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("Attach DLCI %d (%s) to %s", dlci_address,
|
|
mux_uart->name, uart->name);
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&uart_mux_data_devlist, sn, sns) {
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(sn, struct uart_mux_dev_data, node);
|
|
|
|
if (dev_data->dev == mux_uart) {
|
|
struct uart_mux *real_uart;
|
|
int ret;
|
|
|
|
ret = init_real_uart(mux_uart, uart, &real_uart);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
dev_data->real_uart = real_uart;
|
|
dev_data->tx_ready = true;
|
|
dev_data->tx_enabled = true;
|
|
dev_data->rx_enabled = true;
|
|
dev_data->attach_cb = cb;
|
|
dev_data->attach_user_data = user_data;
|
|
dev_data->status = UART_MUX_CONFIGURED;
|
|
|
|
ret = gsm_dlci_create(real_uart->mux,
|
|
mux_uart,
|
|
dlci_address,
|
|
dlci_created_cb,
|
|
dev_data,
|
|
&dev_data->dlci);
|
|
if (ret < 0) {
|
|
LOG_DBG("Cannot create DLCI %d (%d)",
|
|
dlci_address, ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int uart_mux_poll_in(struct device *dev, unsigned char *p_char)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(p_char);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static void uart_mux_poll_out(struct device *dev, unsigned char out_char)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data->dev == NULL) {
|
|
return;
|
|
}
|
|
|
|
(void)gsm_dlci_send(dev_data->dlci, &out_char, 1);
|
|
}
|
|
|
|
static int uart_mux_err_check(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int uart_mux_configure(struct device *dev,
|
|
const struct uart_config *cfg)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(cfg);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int uart_mux_config_get(struct device *dev, struct uart_config *cfg)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
ARG_UNUSED(cfg);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int uart_mux_fifo_fill(struct device *dev, const uint8_t *tx_data, int len)
|
|
{
|
|
struct uart_mux_dev_data *dev_data;
|
|
size_t wrote;
|
|
|
|
if (dev == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_data = DEV_DATA(dev);
|
|
if (dev_data->dev == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
LOG_DBG("dev_data %p len %d tx_ringbuf space %u",
|
|
dev_data, len, ring_buf_space_get(dev_data->tx_ringbuf));
|
|
|
|
if (dev_data->status != UART_MUX_CONNECTED) {
|
|
LOG_WRN("UART mux not connected, drop %d bytes", len);
|
|
return 0;
|
|
}
|
|
|
|
dev_data->tx_ready = false;
|
|
|
|
wrote = ring_buf_put(dev_data->tx_ringbuf, tx_data, len);
|
|
if (wrote < len) {
|
|
LOG_WRN("Ring buffer full, drop %d bytes", len - wrote);
|
|
}
|
|
|
|
k_work_submit_to_queue(&uart_mux_workq, &dev_data->tx_work);
|
|
|
|
return wrote;
|
|
}
|
|
|
|
static int uart_mux_fifo_read(struct device *dev, uint8_t *rx_data, const int size)
|
|
{
|
|
struct uart_mux_dev_data *dev_data;
|
|
uint32_t len;
|
|
|
|
if (dev == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_data = DEV_DATA(dev);
|
|
if (dev_data->dev == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
LOG_DBG("%s size %d rx_ringbuf space %u",
|
|
dev->name, size,
|
|
ring_buf_space_get(dev_data->rx_ringbuf));
|
|
|
|
len = ring_buf_get(dev_data->rx_ringbuf, rx_data, size);
|
|
|
|
if (ring_buf_is_empty(dev_data->rx_ringbuf)) {
|
|
dev_data->rx_ready = false;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static void uart_mux_irq_tx_enable(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL || dev_data->dev == NULL) {
|
|
return;
|
|
}
|
|
|
|
dev_data->tx_enabled = true;
|
|
|
|
if (dev_data->cb && dev_data->tx_ready) {
|
|
k_work_submit_to_queue(&uart_mux_workq, &dev_data->cb_work);
|
|
}
|
|
}
|
|
|
|
static void uart_mux_irq_tx_disable(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL || dev_data->dev == NULL) {
|
|
return;
|
|
}
|
|
|
|
dev_data->tx_enabled = false;
|
|
}
|
|
|
|
static int uart_mux_irq_tx_ready(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev_data->dev == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
return dev_data->tx_ready;
|
|
}
|
|
|
|
static void uart_mux_irq_rx_enable(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL || dev_data->dev == NULL) {
|
|
return;
|
|
}
|
|
|
|
dev_data->rx_enabled = true;
|
|
|
|
if (dev_data->cb && dev_data->rx_ready) {
|
|
k_work_submit_to_queue(&uart_mux_workq, &dev_data->cb_work);
|
|
}
|
|
}
|
|
|
|
static void uart_mux_irq_rx_disable(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL || dev_data->dev == NULL) {
|
|
return;
|
|
}
|
|
|
|
dev_data->rx_enabled = false;
|
|
}
|
|
|
|
static int uart_mux_irq_tx_complete(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int uart_mux_irq_rx_ready(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev_data->dev == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
return dev_data->rx_ready;
|
|
}
|
|
|
|
static void uart_mux_irq_err_enable(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
}
|
|
|
|
static void uart_mux_irq_err_disable(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
}
|
|
|
|
static int uart_mux_irq_is_pending(struct device *dev)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL || dev_data->dev == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (dev_data->tx_ready && dev_data->tx_enabled) {
|
|
return 1;
|
|
}
|
|
|
|
if (dev_data->rx_ready && dev_data->rx_enabled) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int uart_mux_irq_update(struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void uart_mux_irq_callback_set(struct device *dev,
|
|
uart_irq_callback_user_data_t cb,
|
|
void *user_data)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(dev);
|
|
|
|
if (dev_data == NULL) {
|
|
return;
|
|
}
|
|
|
|
dev_data->cb = cb;
|
|
dev_data->cb_user_data = user_data;
|
|
}
|
|
|
|
static struct uart_mux_driver_api uart_mux_driver_api = {
|
|
.uart_api.poll_in = uart_mux_poll_in,
|
|
.uart_api.poll_out = uart_mux_poll_out,
|
|
.uart_api.err_check = uart_mux_err_check,
|
|
.uart_api.configure = uart_mux_configure,
|
|
.uart_api.config_get = uart_mux_config_get,
|
|
.uart_api.fifo_fill = uart_mux_fifo_fill,
|
|
.uart_api.fifo_read = uart_mux_fifo_read,
|
|
.uart_api.irq_tx_enable = uart_mux_irq_tx_enable,
|
|
.uart_api.irq_tx_disable = uart_mux_irq_tx_disable,
|
|
.uart_api.irq_tx_ready = uart_mux_irq_tx_ready,
|
|
.uart_api.irq_rx_enable = uart_mux_irq_rx_enable,
|
|
.uart_api.irq_rx_disable = uart_mux_irq_rx_disable,
|
|
.uart_api.irq_tx_complete = uart_mux_irq_tx_complete,
|
|
.uart_api.irq_rx_ready = uart_mux_irq_rx_ready,
|
|
.uart_api.irq_err_enable = uart_mux_irq_err_enable,
|
|
.uart_api.irq_err_disable = uart_mux_irq_err_disable,
|
|
.uart_api.irq_is_pending = uart_mux_irq_is_pending,
|
|
.uart_api.irq_update = uart_mux_irq_update,
|
|
.uart_api.irq_callback_set = uart_mux_irq_callback_set,
|
|
|
|
.attach = attach,
|
|
};
|
|
|
|
struct device *uart_mux_alloc(void)
|
|
{
|
|
sys_snode_t *sn, *sns;
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&uart_mux_data_devlist, sn, sns) {
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(sn, struct uart_mux_dev_data, node);
|
|
|
|
if (dev_data->in_use) {
|
|
continue;
|
|
}
|
|
|
|
dev_data->in_use = true;
|
|
|
|
return dev_data->dev;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef CONFIG_USERSPACE
|
|
static inline struct device *z_vrfy_uart_mux_find(int dlci_address)
|
|
{
|
|
return z_impl_uart_mux_find(dlci_address);
|
|
}
|
|
#include <syscalls/uart_mux_find_mrsh.c>
|
|
#endif /* CONFIG_USERSPACE */
|
|
|
|
struct device *z_impl_uart_mux_find(int dlci_address)
|
|
{
|
|
sys_snode_t *sn, *sns;
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&uart_mux_data_devlist, sn, sns) {
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(sn, struct uart_mux_dev_data, node);
|
|
|
|
if (!dev_data->in_use) {
|
|
continue;
|
|
}
|
|
|
|
if (dev_data->dlci == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (gsm_dlci_id(dev_data->dlci) == dlci_address) {
|
|
return dev_data->dev;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int uart_mux_send(struct device *uart, const uint8_t *buf, size_t size)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(uart);
|
|
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (atomic_get(&dev_data->real_uart->init_done) == false) {
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_UART_MUX_VERBOSE_DEBUG)) {
|
|
char tmp[sizeof("SEND muxed ") + 10];
|
|
|
|
snprintk(tmp, sizeof(tmp), "SEND muxed %s",
|
|
dev_data->real_uart->uart->name);
|
|
LOG_HEXDUMP_DBG(buf, size, log_strdup(tmp));
|
|
}
|
|
|
|
k_mutex_lock(&dev_data->real_uart->lock, K_FOREVER);
|
|
|
|
do {
|
|
uart_poll_out(dev_data->real_uart->uart, *buf++);
|
|
} while (--size);
|
|
|
|
k_mutex_unlock(&dev_data->real_uart->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int uart_mux_recv(struct device *mux, struct gsm_dlci *dlci, uint8_t *data,
|
|
size_t len)
|
|
{
|
|
struct uart_mux_dev_data *dev_data = DEV_DATA(mux);
|
|
size_t wrote = 0;
|
|
|
|
LOG_DBG("%s: dlci %p data %p len %zd", mux->name, dlci,
|
|
data, len);
|
|
|
|
if (IS_ENABLED(CONFIG_UART_MUX_VERBOSE_DEBUG)) {
|
|
char tmp[sizeof("RECV _x") +
|
|
sizeof(CONFIG_UART_MUX_DEVICE_NAME)];
|
|
|
|
snprintk(tmp, sizeof(tmp), "RECV %s",
|
|
dev_data->dev->name);
|
|
LOG_HEXDUMP_DBG(data, len, log_strdup(tmp));
|
|
}
|
|
|
|
wrote = ring_buf_put(dev_data->rx_ringbuf, data, len);
|
|
if (wrote < len) {
|
|
LOG_ERR("Ring buffer full, drop %d bytes", len - wrote);
|
|
}
|
|
|
|
dev_data->rx_ready = true;
|
|
|
|
if (dev_data->cb && dev_data->rx_enabled) {
|
|
k_work_submit_to_queue(&uart_mux_workq, &dev_data->cb_work);
|
|
}
|
|
|
|
return wrote;
|
|
}
|
|
|
|
void uart_mux_foreach(uart_mux_cb_t cb, void *user_data)
|
|
{
|
|
sys_snode_t *sn, *sns;
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&uart_mux_data_devlist, sn, sns) {
|
|
struct uart_mux_dev_data *dev_data =
|
|
CONTAINER_OF(sn, struct uart_mux_dev_data, node);
|
|
|
|
if (!dev_data->in_use) {
|
|
continue;
|
|
}
|
|
|
|
cb(dev_data->real_uart->uart, dev_data->dev,
|
|
dev_data->dlci ? gsm_dlci_id(dev_data->dlci) : -1,
|
|
user_data);
|
|
}
|
|
}
|
|
|
|
#define DEFINE_UART_MUX_CFG_DATA(x, _) \
|
|
struct uart_mux_cfg_data uart_mux_config_##x = { \
|
|
};
|
|
|
|
#define DEFINE_UART_MUX_DEV_DATA(x, _) \
|
|
RING_BUF_DECLARE(tx_ringbuf_##x, CONFIG_UART_MUX_RINGBUF_SIZE); \
|
|
RING_BUF_DECLARE(rx_ringbuf_##x, CONFIG_UART_MUX_RINGBUF_SIZE); \
|
|
static struct uart_mux_dev_data uart_mux_dev_data_##x = { \
|
|
.tx_ringbuf = &tx_ringbuf_##x, \
|
|
.rx_ringbuf = &rx_ringbuf_##x, \
|
|
};
|
|
|
|
#define DEFINE_UART_MUX_DEVICE(x, _) \
|
|
DEVICE_AND_API_INIT(uart_mux_##x, \
|
|
CONFIG_UART_MUX_DEVICE_NAME "_" #x, \
|
|
&uart_mux_init, \
|
|
&uart_mux_dev_data_##x, \
|
|
&uart_mux_config_##x, \
|
|
POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
&uart_mux_driver_api);
|
|
|
|
UTIL_LISTIFY(CONFIG_UART_MUX_DEVICE_COUNT, DEFINE_UART_MUX_CFG_DATA, _)
|
|
UTIL_LISTIFY(CONFIG_UART_MUX_DEVICE_COUNT, DEFINE_UART_MUX_DEV_DATA, _)
|
|
UTIL_LISTIFY(CONFIG_UART_MUX_DEVICE_COUNT, DEFINE_UART_MUX_DEVICE, _)
|
|
|
|
static int init_uart_mux(struct device *device)
|
|
{
|
|
ARG_UNUSED(device);
|
|
|
|
k_work_q_start(&uart_mux_workq, uart_mux_stack,
|
|
K_KERNEL_STACK_SIZEOF(uart_mux_stack),
|
|
K_PRIO_COOP(UART_MUX_WORKQ_PRIORITY));
|
|
k_thread_name_set(&uart_mux_workq.thread, "uart_mux_workq");
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(init_uart_mux, POST_KERNEL, CONFIG_UART_MUX_INIT_PRIORITY);
|