blob: bf9bf98a653fe6b7be820ce4200ef0b02ccf8a3c [file] [log] [blame]
/*
* 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, 20);
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)