serial: add an uart-bridge driver
Add a reusable uart-bridge driver that echoes data between two serial devices. It's mainly meant to be used with one of the devices being an USB CDC-ACM, the data is buffered, there's a pause feature to stop the USB endpoint when the hardware UART is filling up to avoid overrun and there's a helper function used to propagate the USB uart configuration to the hardware one. Signed-off-by: Fabio Baltieri <fabiobaltieri@google.com>
This commit is contained in:
parent
6e9cfcdcd5
commit
3cb8f745a6
@ -23,6 +23,7 @@ zephyr_library_sources_ifdef(CONFIG_UART_ALTERA_JTAG uart_altera_jtag.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_APBUART uart_apbuart.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_BCM2711_MU uart_bcm2711.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_BFLB uart_bflb.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_BRIDGE uart_bridge.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_BT uart_bt.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_CC13XX_CC26XX uart_cc13xx_cc26xx.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_UART_CC23X0 uart_cc23x0.c)
|
||||
|
||||
@ -164,6 +164,7 @@ rsource "Kconfig.apbuart"
|
||||
rsource "Kconfig.b91"
|
||||
rsource "Kconfig.bcm2711"
|
||||
rsource "Kconfig.bflb"
|
||||
rsource "Kconfig.bridge"
|
||||
rsource "Kconfig.bt"
|
||||
rsource "Kconfig.cc13xx_cc26xx"
|
||||
rsource "Kconfig.cc23x0"
|
||||
|
||||
18
drivers/serial/Kconfig.bridge
Normal file
18
drivers/serial/Kconfig.bridge
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright 2025 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config UART_BRIDGE
|
||||
bool "UART bridge"
|
||||
default y
|
||||
depends on DT_HAS_ZEPHYR_UART_BRIDGE_ENABLED
|
||||
select UART_INTERRUPT_DRIVEN
|
||||
select RING_BUFFER
|
||||
help
|
||||
Enable the UART bridge driver.
|
||||
|
||||
config UART_BRIDGE_BUF_SIZE
|
||||
int "UART bridge buffer size"
|
||||
default 256
|
||||
depends on UART_BRIDGE
|
||||
help
|
||||
Size of the bridge ring buffers in bytes.
|
||||
229
drivers/serial/uart_bridge.c
Normal file
229
drivers/serial/uart_bridge.c
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/ring_buffer.h>
|
||||
#include <zephyr/drivers/uart/uart_bridge.h>
|
||||
|
||||
#define DT_DRV_COMPAT zephyr_uart_bridge
|
||||
|
||||
LOG_MODULE_REGISTER(uart_bridge, LOG_LEVEL_INF);
|
||||
|
||||
#define RING_BUF_SIZE CONFIG_UART_BRIDGE_BUF_SIZE
|
||||
#define RING_BUF_FULL_THRESHOLD (RING_BUF_SIZE / 3)
|
||||
|
||||
struct uart_bridge_config {
|
||||
const struct device *peer_dev[2];
|
||||
};
|
||||
|
||||
struct uart_bridge_peer_data {
|
||||
uint8_t buf[RING_BUF_SIZE];
|
||||
struct ring_buf rb;
|
||||
bool paused;
|
||||
};
|
||||
|
||||
struct uart_bridge_data {
|
||||
struct uart_bridge_peer_data peer[2];
|
||||
};
|
||||
|
||||
static const struct device *uart_bridge_get_peer(const struct device *dev,
|
||||
const struct device *bridge_dev)
|
||||
{
|
||||
const struct uart_bridge_config *cfg = bridge_dev->config;
|
||||
|
||||
if (dev == cfg->peer_dev[0]) {
|
||||
return cfg->peer_dev[1];
|
||||
} else if (dev == cfg->peer_dev[1]) {
|
||||
return cfg->peer_dev[0];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void uart_bridge_settings_update(const struct device *dev,
|
||||
const struct device *bridge_dev)
|
||||
{
|
||||
struct uart_config cfg;
|
||||
const struct device *peer_dev = uart_bridge_get_peer(dev, bridge_dev);
|
||||
int ret;
|
||||
|
||||
if (peer_dev == NULL) {
|
||||
LOG_DBG("%s: not a bridge dev", dev->name);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("update settings: dev=%s bridge=%s peer=%s", dev->name,
|
||||
bridge_dev->name, peer_dev->name);
|
||||
|
||||
ret = uart_config_get(dev, &cfg);
|
||||
if (ret) {
|
||||
LOG_WRN("%s: failed to get the uart config: %d", dev->name,
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = uart_configure(peer_dev, &cfg);
|
||||
if (ret) {
|
||||
LOG_WRN("%s: failed to set the uart config: %d", peer_dev->name,
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INF("uart settings: baudrate=%d parity=%d", cfg.baudrate,
|
||||
cfg.parity);
|
||||
}
|
||||
|
||||
static uint8_t uart_bridge_get_idx(const struct device *dev,
|
||||
const struct device *bridge_dev, bool own)
|
||||
{
|
||||
const struct uart_bridge_config *cfg = bridge_dev->config;
|
||||
|
||||
if (dev == cfg->peer_dev[0]) {
|
||||
return own ? 0 : 1;
|
||||
} else {
|
||||
return own ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_bridge_handle_rx(const struct device *dev,
|
||||
const struct device *bridge_dev)
|
||||
{
|
||||
const struct uart_bridge_config *cfg = bridge_dev->config;
|
||||
struct uart_bridge_data *data = bridge_dev->data;
|
||||
|
||||
const struct device *peer_dev =
|
||||
cfg->peer_dev[uart_bridge_get_idx(dev, bridge_dev, false)];
|
||||
struct uart_bridge_peer_data *own_data =
|
||||
&data->peer[uart_bridge_get_idx(dev, bridge_dev, true)];
|
||||
|
||||
uint8_t *recv_buf;
|
||||
int rb_len, recv_len;
|
||||
int ret;
|
||||
|
||||
if (ring_buf_space_get(&own_data->rb) < RING_BUF_FULL_THRESHOLD) {
|
||||
LOG_DBG("%s: buffer full: pause", dev->name);
|
||||
uart_irq_rx_disable(dev);
|
||||
own_data->paused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
rb_len = ring_buf_put_claim(&own_data->rb, &recv_buf, RING_BUF_SIZE);
|
||||
if (rb_len == 0) {
|
||||
LOG_WRN("%s: ring_buf full", dev->name);
|
||||
return;
|
||||
}
|
||||
|
||||
recv_len = uart_fifo_read(dev, recv_buf, rb_len);
|
||||
if (recv_len < 0) {
|
||||
ring_buf_put_finish(&own_data->rb, 0);
|
||||
LOG_ERR("%s: rx error: %d", dev->name, recv_len);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ring_buf_put_finish(&own_data->rb, recv_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("%s: ring_buf_put_finish error: %d", dev->name, rb_len);
|
||||
return;
|
||||
}
|
||||
|
||||
uart_irq_tx_enable(peer_dev);
|
||||
}
|
||||
|
||||
static void uart_bridge_handle_tx(const struct device *dev,
|
||||
const struct device *bridge_dev)
|
||||
{
|
||||
const struct uart_bridge_config *cfg = bridge_dev->config;
|
||||
struct uart_bridge_data *data = bridge_dev->data;
|
||||
|
||||
const struct device *peer_dev =
|
||||
cfg->peer_dev[uart_bridge_get_idx(dev, bridge_dev, false)];
|
||||
struct uart_bridge_peer_data *peer_data =
|
||||
&data->peer[uart_bridge_get_idx(dev, bridge_dev, false)];
|
||||
|
||||
uint8_t *send_buf;
|
||||
int rb_len, sent_len;
|
||||
int ret;
|
||||
|
||||
rb_len = ring_buf_get_claim(&peer_data->rb, &send_buf, RING_BUF_SIZE);
|
||||
if (rb_len == 0) {
|
||||
LOG_DBG("%s: buffer empty, disable tx irq", dev->name);
|
||||
uart_irq_tx_disable(dev);
|
||||
return;
|
||||
}
|
||||
|
||||
sent_len = uart_fifo_fill(dev, send_buf, rb_len);
|
||||
if (sent_len < 0) {
|
||||
ring_buf_get_finish(&peer_data->rb, 0);
|
||||
LOG_ERR("%s: tx error: %d", dev->name, sent_len);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = ring_buf_get_finish(&peer_data->rb, sent_len);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("ring_buf_get_finish error: %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (peer_data->paused &&
|
||||
ring_buf_space_get(&peer_data->rb) > RING_BUF_FULL_THRESHOLD) {
|
||||
LOG_DBG("%s: buffer free: resume", dev->name);
|
||||
uart_irq_rx_enable(peer_dev);
|
||||
peer_data->paused = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void interrupt_handler(const struct device *dev, void *user_data)
|
||||
{
|
||||
const struct device *bridge_dev = user_data;
|
||||
|
||||
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
uart_bridge_handle_rx(dev, bridge_dev);
|
||||
}
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
uart_bridge_handle_tx(dev, bridge_dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int uart_bridge_init(const struct device *dev)
|
||||
{
|
||||
const struct uart_bridge_config *cfg = dev->config;
|
||||
struct uart_bridge_data *data = dev->data;
|
||||
|
||||
ring_buf_init(&data->peer[0].rb, RING_BUF_SIZE, data->peer[0].buf);
|
||||
ring_buf_init(&data->peer[1].rb, RING_BUF_SIZE, data->peer[1].buf);
|
||||
|
||||
uart_irq_callback_user_data_set(cfg->peer_dev[0], interrupt_handler,
|
||||
(void *)dev);
|
||||
uart_irq_callback_user_data_set(cfg->peer_dev[1], interrupt_handler,
|
||||
(void *)dev);
|
||||
uart_irq_rx_enable(cfg->peer_dev[0]);
|
||||
uart_irq_rx_enable(cfg->peer_dev[1]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define UART_BRIDGE_INIT(n) \
|
||||
BUILD_ASSERT(DT_INST_PROP_LEN(n, peers) == 2, \
|
||||
"uart-bridge peers property must have exactly 2 members"); \
|
||||
\
|
||||
static const struct uart_bridge_config uart_bridge_cfg_##n = { \
|
||||
.peer_dev = {DT_INST_FOREACH_PROP_ELEM_SEP( \
|
||||
n, peers, DEVICE_DT_GET_BY_IDX, (,))}, \
|
||||
}; \
|
||||
\
|
||||
static struct uart_bridge_data uart_bridge_data_##n; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, uart_bridge_init, NULL(n), \
|
||||
&uart_bridge_data_##n, &uart_bridge_cfg_##n, \
|
||||
POST_KERNEL, CONFIG_SERIAL_INIT_PRIORITY, NULL);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(UART_BRIDGE_INIT)
|
||||
23
dts/bindings/serial/zephyr,uart-bridge.yaml
Normal file
23
dts/bindings/serial/zephyr,uart-bridge.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
# Copyright 2025 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
title: UART bridge
|
||||
|
||||
description: |
|
||||
Bridges data between two serial devices, for example a USB CDC-ACM serial
|
||||
port and an hardware UART.
|
||||
|
||||
The device node must specify exactly two peer phandles for the two serial
|
||||
devices to be bridged together. Example configuration:
|
||||
|
||||
uart-bridge {
|
||||
compatible = "zephyr,uart-bridge";
|
||||
peers = <&cdc_acm_uart0 &uart1>;
|
||||
};
|
||||
|
||||
compatible: "zephyr,uart-bridge"
|
||||
|
||||
properties:
|
||||
peers:
|
||||
type: phandles
|
||||
description: Peer device nodes, must contain two phandles
|
||||
19
include/zephyr/drivers/uart/uart_bridge.h
Normal file
19
include/zephyr/drivers/uart/uart_bridge.h
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 Google LLC
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
|
||||
/**
|
||||
* @brief Update the hardware port settings on a uart bridge
|
||||
*
|
||||
* If dev is part bridge_dev, then the dev uart configuration are applied to
|
||||
* the other device in the uart bridge. This allows propagating the settings
|
||||
* from a USB CDC-ACM port to a hardware UART.
|
||||
*
|
||||
* If dev is not part of bridge_dev then the function is a no-op.
|
||||
*/
|
||||
void uart_bridge_settings_update(const struct device *dev,
|
||||
const struct device *bridge_dev);
|
||||
Loading…
Reference in New Issue
Block a user