You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
10 KiB
373 lines
10 KiB
/* |
|
* Copyright (c) 2024 Croxel, Inc. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#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); |
|
|
|
K_THREAD_STACK_DEFINE(nus_work_queue_stack, CONFIG_UART_BT_WORKQUEUE_STACK_SIZE); |
|
static struct k_work_q nus_work_queue; |
|
|
|
#define UART_BT_MTU_INVALID 0xFFFF |
|
|
|
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_for_queue(&nus_work_queue, &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_to_queue(&nus_work_queue, &dev_data->uart.cb_work); |
|
} |
|
|
|
static void foreach_conn_handler_get_att_mtu(struct bt_conn *conn, void *data) |
|
{ |
|
uint16_t *min_att_mtu = (uint16_t *)data; |
|
uint16_t conn_att_mtu = 0; |
|
struct bt_conn_info conn_info; |
|
int err; |
|
|
|
err = bt_conn_get_info(conn, &conn_info); |
|
if (!err && conn_info.state == BT_CONN_STATE_CONNECTED) { |
|
conn_att_mtu = bt_gatt_get_uatt_mtu(conn); |
|
|
|
if (conn_att_mtu > 0) { |
|
*min_att_mtu = MIN(*min_att_mtu, conn_att_mtu); |
|
} |
|
} |
|
} |
|
|
|
static inline uint16_t get_max_chunk_size(void) |
|
{ |
|
uint16_t min_att_mtu = UART_BT_MTU_INVALID; |
|
|
|
bt_conn_foreach(BT_CONN_TYPE_LE, foreach_conn_handler_get_att_mtu, &min_att_mtu); |
|
|
|
if (min_att_mtu == UART_BT_MTU_INVALID) { |
|
/** Default ATT MTU */ |
|
min_att_mtu = 23; |
|
} |
|
|
|
/** ATT NTF Payload overhead: opcode (1 octet) + attribute (2 octets) */ |
|
return (min_att_mtu - 1 - 2); |
|
} |
|
|
|
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); |
|
|
|
uint16_t chunk_size = get_max_chunk_size(); |
|
do { |
|
/** The chunk size is based on the smallest MTU among all |
|
* peers, and the same chunk is sent to everyone. This avoids |
|
* managing separate read pointers: one per connection. |
|
*/ |
|
len = ring_buf_get_claim(dev_data->uart.tx_ringbuf, &data, chunk_size); |
|
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_to_queue(&nus_work_queue, &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_for_queue(&nus_work_queue, &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_WRN_ONCE("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_schedule_for_queue(&nus_work_queue, &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_to_queue(&nus_work_queue, &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_to_queue(&nus_work_queue, &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 DEVICE_API(uart, 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_workqueue_init(void) |
|
{ |
|
k_work_queue_init(&nus_work_queue); |
|
k_work_queue_start(&nus_work_queue, nus_work_queue_stack, |
|
K_THREAD_STACK_SIZEOF(nus_work_queue_stack), |
|
CONFIG_UART_BT_WORKQUEUE_PRIORITY, NULL); |
|
k_thread_name_set(&nus_work_queue.thread, "uart_bt"); |
|
|
|
return 0; |
|
} |
|
|
|
/** The work-queue is shared across all instances, hence we initialize it separatedly */ |
|
SYS_INIT(uart_bt_workqueue_init, POST_KERNEL, CONFIG_SERIAL_INIT_PRIORITY); |
|
|
|
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)
|
|
|