drivers: serial: bt: Add UART over NUS Bluetooth driver

Enables usage Bluetooth LE GATT as a serial endpoint to exchange data
using UART APIs. This implementation is compatible with UART Interrupt
Driven APIs and uses the nus-uart device-tree node properties to
configure FIFO buffers for transmitting and receiving. Defining
multiple instances of the driver is possible and it allows implementing
multiple GATT NUS service instances to exchange data as separate serial
endpoints.

Signed-off-by: Luis Ubieda <luisf@croxel.com>
This commit is contained in:
Luis Ubieda 2024-03-06 13:12:51 -05:00 committed by Carles Cufí
parent d834ec875e
commit 205994b87b
6 changed files with 358 additions and 1 deletions

View file

@ -98,6 +98,8 @@ if(CONFIG_UART_NATIVE_TTY)
endif()
endif()
zephyr_library_sources_ifdef(CONFIG_UART_BT uart_bt.c)
zephyr_library_sources_ifdef(CONFIG_SERIAL_TEST serial_test.c)
zephyr_library_sources_ifdef(CONFIG_UART_ASYNC_RX_HELPER uart_async_rx.c)
zephyr_library_sources_ifdef(CONFIG_UART_ASYNC_TO_INT_DRIVEN_API uart_async_to_irq.c)

View file

@ -218,6 +218,8 @@ source "drivers/serial/Kconfig.litex"
source "drivers/serial/Kconfig.rtt"
source "drivers/serial/Kconfig.bt"
source "drivers/serial/Kconfig.xlnx"
source "drivers/serial/Kconfig.xmc4xxx"

13
drivers/serial/Kconfig.bt Normal file
View file

@ -0,0 +1,13 @@
# Copyright (c) 2024 Croxel, Inc.
# SPDX-License-Identifier: Apache-2.0
config UART_BT
bool "UART over NUS Bluetooth LE"
depends on BT_NUS
depends on DT_HAS_ZEPHYR_NUS_UART_ENABLED
select UART_INTERRUPT_DRIVEN
select RING_BUFFER
select EXPERIMENTAL
help
Enable the UART over NUS Bluetooth driver, which can be used to pipe
serial data over Bluetooth LE GATT using NUS (Nordic UART Service).

321
drivers/serial/uart_bt.c Normal file
View file

@ -0,0 +1,321 @@
/*
* Copyright (c) 2024 Croxel, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/bluetooth/services/nus.h>
#define DT_DRV_COMPAT zephyr_nus_uart
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(uart_nus, CONFIG_UART_LOG_LEVEL);
struct uart_bt_data {
struct {
struct bt_nus_inst *inst;
struct bt_nus_cb cb;
atomic_t enabled;
} bt;
struct {
struct ring_buf *rx_ringbuf;
struct ring_buf *tx_ringbuf;
struct k_work cb_work;
struct k_work_delayable tx_work;
bool rx_irq_ena;
bool tx_irq_ena;
struct {
const struct device *dev;
uart_irq_callback_user_data_t cb;
void *cb_data;
} callback;
} uart;
};
static void bt_notif_enabled(bool enabled, void *ctx)
{
__ASSERT_NO_MSG(ctx);
const struct device *dev = (const struct device *)ctx;
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
(void)atomic_set(&dev_data->bt.enabled, enabled ? 1 : 0);
LOG_DBG("%s() - %s", __func__, enabled ? "enabled" : "disabled");
if (!ring_buf_is_empty(dev_data->uart.tx_ringbuf)) {
k_work_reschedule(&dev_data->uart.tx_work, K_NO_WAIT);
}
}
static void bt_received(struct bt_conn *conn, const void *data, uint16_t len, void *ctx)
{
__ASSERT_NO_MSG(conn);
__ASSERT_NO_MSG(ctx);
__ASSERT_NO_MSG(data);
__ASSERT_NO_MSG(len > 0);
const struct device *dev = (const struct device *)ctx;
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
struct ring_buf *ringbuf = dev_data->uart.rx_ringbuf;
uint32_t put_len;
LOG_DBG("%s() - len: %d, rx_ringbuf space %d", __func__, len, ring_buf_space_get(ringbuf));
LOG_HEXDUMP_DBG(data, len, "data");
put_len = ring_buf_put(ringbuf, (const uint8_t *)data, len);
if (put_len < len) {
LOG_ERR("RX Ring buffer full. received: %d, added to queue: %d", len, put_len);
}
k_work_submit(&dev_data->uart.cb_work);
}
static void cb_work_handler(struct k_work *work)
{
struct uart_bt_data *dev_data = CONTAINER_OF(work, struct uart_bt_data, uart.cb_work);
if (dev_data->uart.callback.cb) {
dev_data->uart.callback.cb(
dev_data->uart.callback.dev,
dev_data->uart.callback.cb_data);
}
}
static void tx_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct uart_bt_data *dev_data = CONTAINER_OF(dwork, struct uart_bt_data, uart.tx_work);
uint8_t *data = NULL;
size_t len;
int err;
__ASSERT_NO_MSG(dev_data);
do {
/** Using Minimum MTU at this point to guarantee all connected
* peers will receive the data, without keeping track of MTU
* size per-connection. This has the trade-off of limiting
* throughput but allows multi-connection support.
*/
len = ring_buf_get_claim(dev_data->uart.tx_ringbuf, &data, 23);
if (len > 0) {
err = bt_nus_inst_send(NULL, dev_data->bt.inst, data, len);
if (err) {
LOG_ERR("Failed to send data over BT: %d", err);
}
}
ring_buf_get_finish(dev_data->uart.tx_ringbuf, len);
} while (len > 0 && !err);
if ((ring_buf_space_get(dev_data->uart.tx_ringbuf) > 0) && dev_data->uart.tx_irq_ena) {
k_work_submit(&dev_data->uart.cb_work);
}
}
static int uart_bt_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
size_t wrote;
wrote = ring_buf_put(dev_data->uart.tx_ringbuf, tx_data, len);
if (wrote < len) {
LOG_WRN("Ring buffer full, drop %zd bytes", len - wrote);
}
if (atomic_get(&dev_data->bt.enabled)) {
k_work_reschedule(&dev_data->uart.tx_work, K_NO_WAIT);
}
return wrote;
}
static int uart_bt_fifo_read(const struct device *dev, uint8_t *rx_data, const int size)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
return ring_buf_get(dev_data->uart.rx_ringbuf, rx_data, size);
}
static int uart_bt_poll_in(const struct device *dev, unsigned char *c)
{
int err = uart_bt_fifo_read(dev, c, 1);
return err == 1 ? 0 : -1;
}
static void uart_bt_poll_out(const struct device *dev, unsigned char c)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
struct ring_buf *ringbuf = dev_data->uart.tx_ringbuf;
/** Right now we're discarding data if ring-buf is full. */
while (!ring_buf_put(ringbuf, &c, 1)) {
if (k_is_in_isr() || !atomic_get(&dev_data->bt.enabled)) {
LOG_INF("Ring buffer full, discard %c", c);
break;
}
k_sleep(K_MSEC(1));
}
/** Don't flush the data until notifications are enabled. */
if (atomic_get(&dev_data->bt.enabled)) {
/** Delay will allow buffering some characters before transmitting
* data, so more than one byte is transmitted (e.g: when poll_out is
* called inside a for-loop).
*/
k_work_reschedule(&dev_data->uart.tx_work, K_MSEC(1));
}
}
static int uart_bt_irq_tx_ready(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
if ((ring_buf_space_get(dev_data->uart.tx_ringbuf) > 0) && dev_data->uart.tx_irq_ena) {
return 1;
}
return 0;
}
static void uart_bt_irq_tx_enable(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
dev_data->uart.tx_irq_ena = true;
if (uart_bt_irq_tx_ready(dev)) {
k_work_submit(&dev_data->uart.cb_work);
}
}
static void uart_bt_irq_tx_disable(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
dev_data->uart.tx_irq_ena = false;
}
static int uart_bt_irq_rx_ready(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
if (!ring_buf_is_empty(dev_data->uart.rx_ringbuf) && dev_data->uart.rx_irq_ena) {
return 1;
}
return 0;
}
static void uart_bt_irq_rx_enable(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
dev_data->uart.rx_irq_ena = true;
k_work_submit(&dev_data->uart.cb_work);
}
static void uart_bt_irq_rx_disable(const struct device *dev)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
dev_data->uart.rx_irq_ena = false;
}
static int uart_bt_irq_is_pending(const struct device *dev)
{
return uart_bt_irq_rx_ready(dev);
}
static int uart_bt_irq_update(const struct device *dev)
{
ARG_UNUSED(dev);
return 1;
}
static void uart_bt_irq_callback_set(const struct device *dev,
uart_irq_callback_user_data_t cb,
void *cb_data)
{
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
dev_data->uart.callback.cb = cb;
dev_data->uart.callback.cb_data = cb_data;
}
static const struct uart_driver_api uart_bt_driver_api = {
.poll_in = uart_bt_poll_in,
.poll_out = uart_bt_poll_out,
.fifo_fill = uart_bt_fifo_fill,
.fifo_read = uart_bt_fifo_read,
.irq_tx_enable = uart_bt_irq_tx_enable,
.irq_tx_disable = uart_bt_irq_tx_disable,
.irq_tx_ready = uart_bt_irq_tx_ready,
.irq_rx_enable = uart_bt_irq_rx_enable,
.irq_rx_disable = uart_bt_irq_rx_disable,
.irq_rx_ready = uart_bt_irq_rx_ready,
.irq_is_pending = uart_bt_irq_is_pending,
.irq_update = uart_bt_irq_update,
.irq_callback_set = uart_bt_irq_callback_set,
};
static int uart_bt_init(const struct device *dev)
{
int err;
struct uart_bt_data *dev_data = (struct uart_bt_data *)dev->data;
/** As a way to backtrace the device handle from uart_bt_data.
* Used in cb_work_handler.
*/
dev_data->uart.callback.dev = dev;
k_work_init_delayable(&dev_data->uart.tx_work, tx_work_handler);
k_work_init(&dev_data->uart.cb_work, cb_work_handler);
err = bt_nus_inst_cb_register(dev_data->bt.inst, &dev_data->bt.cb, (void *)dev);
if (err) {
return err;
}
return 0;
}
#define UART_BT_RX_FIFO_SIZE(inst) (DT_INST_PROP(inst, rx_fifo_size))
#define UART_BT_TX_FIFO_SIZE(inst) (DT_INST_PROP(inst, tx_fifo_size))
#define UART_BT_INIT(n) \
\
BT_NUS_INST_DEFINE(bt_nus_inst_##n); \
\
RING_BUF_DECLARE(bt_nus_rx_rb_##n, UART_BT_RX_FIFO_SIZE(n)); \
RING_BUF_DECLARE(bt_nus_tx_rb_##n, UART_BT_TX_FIFO_SIZE(n)); \
\
static struct uart_bt_data uart_bt_data_##n = { \
.bt = { \
.inst = &bt_nus_inst_##n, \
.enabled = ATOMIC_INIT(0), \
.cb = { \
.notif_enabled = bt_notif_enabled, \
.received = bt_received, \
}, \
}, \
.uart = { \
.rx_ringbuf = &bt_nus_rx_rb_##n, \
.tx_ringbuf = &bt_nus_tx_rb_##n, \
}, \
}; \
\
DEVICE_DT_INST_DEFINE(n, uart_bt_init, NULL, &uart_bt_data_##n, \
NULL, PRE_KERNEL_1, \
CONFIG_SERIAL_INIT_PRIORITY, \
&uart_bt_driver_api);
DT_INST_FOREACH_STATUS_OKAY(UART_BT_INIT)

View file

@ -0,0 +1,19 @@
# Copyright (c) 2024 Croxel, Inc.
# SPDX-License-Identifier: Apache-2.0
description: UART over NUS (Bluetooth LE)
compatible: "zephyr,nus-uart"
properties:
tx-fifo-size:
type: int
default: 1024
description: |
Size of the virtual UART TX FIFO
rx-fifo-size:
type: int
default: 1024
description: |
Size of the virtual UART RX FIFO

View file

@ -8,7 +8,7 @@ if BT_NUS
config BT_NUS_DEFAULT_INSTANCE
bool "Use default NUS Service instance"
default y
default y if !UART_BT
help
Enable default Nordic UART Service Instance. Allows using the NUS as
the other services, where the Service is hosted by the subsystem itself.