Skip to content

Commit

Permalink
Bluetooth: Samples: Add hci_uart_async
Browse files Browse the repository at this point in the history
This sample is an alternative implementation of hci_uart. The new sample
differs from the existing sample in that it uses the async UART API
instead of the interrupt driven API.

Included in this commit is a new test for HCI UART flow control. It's
enabled for hci_uart_async. The test can excercise also the existing
hci_uart sample (with some minimal changes to allow compiling in the
mock controller and test suite). The existing hci_uart sample currently
fails the flow control test.

Signed-off-by: Aleksander Wasaznik <[email protected]>
  • Loading branch information
alwa-nordic committed Jun 27, 2023
1 parent 14573fc commit 7bfa2ee
Show file tree
Hide file tree
Showing 15 changed files with 1,171 additions and 6 deletions.
305 changes: 303 additions & 2 deletions drivers/serial/serial_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@
* devices for the "vnd,serial" devicetree compatible used in test code.
*/

#include <stdbool.h>

#include <zephyr/sys/__assert.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/uart/serial_test.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/ring_buffer.h>

LOG_MODULE_REGISTER(mock_serial, CONFIG_LOG_DEFAULT_LEVEL);

BUILD_ASSERT(!IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN) || IS_ENABLED(CONFIG_RING_BUFFER));
BUILD_ASSERT(!IS_ENABLED(CONFIG_UART_ASYNC_API) || IS_ENABLED(CONFIG_RING_BUFFER));

#define DT_DRV_COMPAT vnd_serial
struct serial_vnd_data {
#ifdef CONFIG_RING_BUFFER
Expand All @@ -23,8 +32,149 @@ struct serial_vnd_data {
#endif
serial_vnd_write_cb_t callback;
void *callback_data;
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
uart_irq_callback_user_data_t irq_isr;
bool irq_rx_enabled;
bool irq_tx_enabled;
#endif
#ifdef CONFIG_UART_ASYNC_API
uart_callback_t async_cb;
void *async_cb_user_data;
uint8_t *read_buf;
size_t read_size;
size_t read_position;
#endif
};

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static bool is_irq_rx_pending(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

return !ring_buf_is_empty(data->read_queue);
}

static bool is_irq_tx_pending(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

return ring_buf_space_get(data->written) != 0;
}

static void irq_process(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

for (;;) {
bool rx_rdy = is_irq_rx_pending(dev);
bool tx_rdy = is_irq_tx_pending(dev);
bool rx_int = rx_rdy && data->irq_rx_enabled;
bool tx_int = tx_rdy && data->irq_tx_enabled;

LOG_DBG("rx_rdy %d tx_rdy %d", rx_rdy, tx_rdy);
LOG_DBG("rx_int %d tx_int %d", rx_int, tx_int);

if (!(rx_int || tx_int)) {
break;
}

LOG_DBG("isr");
if (!data->irq_isr) {
LOG_ERR("no isr registered");
break;
}
data->irq_isr(dev, NULL);
};
}

static void irq_rx_enable(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

data->irq_rx_enabled = true;
LOG_DBG("rx enabled");
irq_process(dev);
}

static void irq_rx_disable(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

data->irq_rx_enabled = false;
LOG_DBG("rx disabled");
}

static int irq_rx_ready(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;
bool ready = !ring_buf_is_empty(data->read_queue);

LOG_DBG("rx ready: %d", ready);
return ready;
}

static void irq_tx_enable(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

LOG_DBG("tx enabled");
data->irq_tx_enabled = true;
irq_process(dev);
}

static void irq_tx_disable(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;

data->irq_tx_enabled = false;
LOG_DBG("tx disabled");
}

static int irq_tx_ready(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;
bool ready = (ring_buf_space_get(data->written) != 0);

LOG_DBG("tx ready: %d", ready);
return ready;
}

static void irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb,
void *user_data)
{
struct serial_vnd_data *data = dev->data;

/* It seems like user_data is only used for the async interface.
* Until async is implemented in this mock driver, we expect it
* to be NULL.
*/
__ASSERT_NO_MSG(user_data == NULL);

data->irq_isr = cb;
LOG_DBG("callback set");
}

static int fifo_fill(const struct device *dev, const uint8_t *tx_data, int size)
{
struct serial_vnd_data *data = dev->data;
uint32_t write_len = ring_buf_put(data->written, tx_data, size);

if (data->callback) {
data->callback(dev, data->callback_data);
}
return write_len;
}

static int fifo_read(const struct device *dev, uint8_t *rx_data, const int size)
{
struct serial_vnd_data *data = dev->data;
int read_len = ring_buf_get(data->read_queue, rx_data, size);

LOG_HEXDUMP_DBG(rx_data, read_len, "");
return read_len;
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */

static int serial_vnd_poll_in(const struct device *dev, unsigned char *c)
{
#ifdef CONFIG_RING_BUFFER
Expand Down Expand Up @@ -59,15 +209,35 @@ static void serial_vnd_poll_out(const struct device *dev, unsigned char c)
}
}

#ifdef CONFIG_UART_ASYNC_API
static void async_rx_run(const struct device *dev);
#endif

#ifdef CONFIG_RING_BUFFER
int serial_vnd_queue_in_data(const struct device *dev, unsigned char *c, uint32_t size)
int serial_vnd_queue_in_data(const struct device *dev, const unsigned char *c, uint32_t size)
{
struct serial_vnd_data *data = dev->data;
int write_size;

if (data == NULL || data->read_queue == NULL) {
return -ENOTSUP;
}
return ring_buf_put(data->read_queue, c, size);
write_size = ring_buf_put(data->read_queue, c, size);

LOG_DBG("size %u write_size %u", size, write_size);
LOG_HEXDUMP_DBG(c, write_size, "");

#ifdef CONFIG_UART_INTERRUPT_DRIVEN
if (write_size > 0) {
irq_process(dev);
}
#endif

#ifdef CONFIG_UART_ASYNC_API
async_rx_run(dev);
#endif

return write_size;
}

uint32_t serial_vnd_out_data_size_get(const struct device *dev)
Expand Down Expand Up @@ -130,6 +300,121 @@ static int serial_vnd_config_get(const struct device *dev, struct uart_config *c
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */

#ifdef CONFIG_UART_ASYNC_API
static int serial_vnd_callback_set(const struct device *dev, uart_callback_t callback,
void *user_data)
{
struct serial_vnd_data *data = dev->data;

if (data == NULL) {
return -ENOTSUP;
}
data->async_cb = callback;
data->async_cb_user_data = user_data;

return 0;
}

static int serial_vnd_api_tx(const struct device *dev, const uint8_t *tx_data, size_t len,
int32_t timeout)
{
struct serial_vnd_data *data = dev->data;
struct uart_event evt;
uint32_t write_len;

if (data == NULL) {
return -ENOTSUP;
}

if (data->async_cb == NULL) {
return -EINVAL;
}

write_len = ring_buf_put(data->written, tx_data, len);
if (data->callback) {
data->callback(dev, data->callback_data);
}

__ASSERT(write_len == len, "Ring buffer full. Async wait not implemented.");

evt = (struct uart_event){
.type = UART_TX_DONE,
.data.tx.buf = tx_data,
.data.tx.len = len,
};
data->async_cb(dev, &evt, data->async_cb_user_data);

return 0;
}

static void async_rx_run(const struct device *dev)
{
struct serial_vnd_data *data = dev->data;
struct uart_event evt;
uint32_t read_len;
uint32_t read_remaining;

if (data->async_cb == NULL) {
return;
}

if (!data->read_buf) {
return;
}

read_remaining = data->read_size - data->read_position;

read_len = ring_buf_get(data->read_queue, &data->read_buf[data->read_position],
read_remaining);

if (read_len != 0) {
evt = (struct uart_event){
.type = UART_RX_RDY,
.data.rx.buf = data->read_buf,
.data.rx.len = read_len,
.data.rx.offset = data->read_position,
};
data->async_cb(dev, &evt, data->async_cb_user_data);
}

data->read_position += read_len;

if (data->read_position == data->read_size) {
data->read_buf = NULL;
evt = (struct uart_event){
.type = UART_RX_DISABLED,
};
data->async_cb(dev, &evt, data->async_cb_user_data);
}
}

static int serial_vnd_rx_enable(const struct device *dev, uint8_t *read_buf, size_t read_size,
int32_t timeout)
{
struct serial_vnd_data *data = dev->data;

LOG_WRN("read_size %d", read_size);

if (data == NULL) {
return -ENOTSUP;
}

if (data->async_cb == NULL) {
return -EINVAL;
}

__ASSERT(timeout == SYS_FOREVER_MS, "Async timeout not implemented.");

data->read_buf = read_buf;
data->read_size = read_size;
data->read_position = 0;

async_rx_run(dev);

return 0;
}
#endif /* CONFIG_UART_ASYNC_API */

static const struct uart_driver_api serial_vnd_api = {
.poll_in = serial_vnd_poll_in,
.poll_out = serial_vnd_poll_out,
Expand All @@ -138,6 +423,22 @@ static const struct uart_driver_api serial_vnd_api = {
.configure = serial_vnd_configure,
.config_get = serial_vnd_config_get,
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
.irq_callback_set = irq_callback_set,
.irq_rx_enable = irq_rx_enable,
.irq_rx_disable = irq_rx_disable,
.irq_rx_ready = irq_rx_ready,
.irq_tx_enable = irq_tx_enable,
.irq_tx_disable = irq_tx_disable,
.irq_tx_ready = irq_tx_ready,
.fifo_read = fifo_read,
.fifo_fill = fifo_fill,
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
#ifdef CONFIG_UART_ASYNC_API
.callback_set = serial_vnd_callback_set,
.tx = serial_vnd_api_tx,
.rx_enable = serial_vnd_rx_enable,
#endif /* CONFIG_UART_ASYNC_API */
};

#define VND_SERIAL_DATA_BUFFER(n) \
Expand Down
3 changes: 0 additions & 3 deletions dts/bindings/test/vnd,serial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ compatible: "vnd,serial"
include: uart-controller.yaml

properties:
reg:
required: true

baud-rate:
type: int

Expand Down
2 changes: 1 addition & 1 deletion include/zephyr/drivers/uart/serial_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extern "C" {
*
* @retval Number of bytes written.
*/
int serial_vnd_queue_in_data(const struct device *dev, unsigned char *data, uint32_t size);
int serial_vnd_queue_in_data(const struct device *dev, const unsigned char *data, uint32_t size);

/**
* @brief Returns size of unread written data.
Expand Down
10 changes: 10 additions & 0 deletions samples/bluetooth/hci_uart_async/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(hci_uart_async)
target_sources(app PRIVATE
src/hci_uart.c
src/main.c
)
Loading

0 comments on commit 7bfa2ee

Please sign in to comment.