bluetooth: samples: Add hci_uart_async

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 <aleksander.wasaznik@nordicsemi.no>
This commit is contained in:
Aleksander Wasaznik 2023-01-10 10:11:09 +01:00 committed by Johan Hedberg
commit 347ce7aa7f
14 changed files with 955 additions and 0 deletions

View file

@ -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_async.c
src/main.c
)

View file

@ -0,0 +1,158 @@
.. _bluetooth-hci-uart-async-sample:
Bluetooth: HCI UART based on ASYNC UART
#######################################
Expose a Zephyr Bluetooth Controller over a standard Bluetooth HCI UART interface.
This sample performs the same basic function as the HCI UART sample, but it uses the UART_ASYNC_API
instead of UART_INTERRUPT_DRIVEN API. Not all boards implement both UART APIs, so the board support
of the HCI UART sample may be different.
Requirements
************
* A board with BLE support
Default UART settings
*********************
By default the controller builds use the following settings:
* Baudrate: 1Mbit/s
* 8 bits, no parity, 1 stop bit
* Hardware Flow Control (RTS/CTS) enabled
Building and Running
********************
This sample can be found under :zephyr_file:`samples/bluetooth/hci_uart_async`
in the Zephyr tree and is built as a standard Zephyr application.
Using the controller with emulators and BlueZ
*********************************************
The instructions below show how to use a Nordic nRF5x device as a Zephyr BLE
controller and expose it to Linux's BlueZ.
First, make sure you have a recent BlueZ version installed by following the
instructions in the :ref:`bluetooth_bluez` section.
Now build and flash the sample for the Nordic nRF5x board of your choice.
All of the Nordic Development Kits come with a Segger IC that provides a
debugger interface and a CDC ACM serial port bridge. More information can be
found in :ref:`nordic_segger`.
For example, to build for the nRF52832 Development Kit:
.. zephyr-app-commands::
:zephyr-app: samples/bluetooth/hci_uart_async
:board: nrf52dk_nrf52832
:goals: build flash
.. _bluetooth-hci-uart-async-qemu-posix:
Using the controller with QEMU and Native POSIX
===============================================
In order to use the HCI UART controller with QEMU or Native POSIX you will need
to attach it to the Linux Host first. To do so simply build the sample and
connect the UART to the Linux machine, and then attach it with this command:
.. code-block:: console
sudo btattach -B /dev/ttyACM0 -S 1000000 -R
.. note::
Depending on the serial port you are using you will need to modify the
``/dev/ttyACM0`` string to point to the serial device your controller is
connected to.
.. note::
The ``-R`` flag passed to ``btattach`` instructs the kernel to avoid
interacting with the controller and instead just be aware of it in order
to proxy it to QEMU later.
If you are running :file:`btmon` you should see a brief log showing how the
Linux kernel identifies the attached controller.
Once the controller is attached follow the instructions in the
:ref:`bluetooth_qemu_posix` section to use QEMU with it.
.. _bluetooth-hci-uart-async-bluez:
Using the controller with BlueZ
===============================
In order to use the HCI UART controller with BlueZ you will need to attach it
to the Linux Host first. To do so simply build the sample and connect the
UART to the Linux machine, and then attach it with this command:
.. code-block:: console
sudo btattach -B /dev/ttyACM0 -S 1000000
.. note::
Depending on the serial port you are using you will need to modify the
``/dev/ttyACM0`` string to point to the serial device your controller is
connected to.
If you are running :file:`btmon` you should see a comprehensive log showing how
BlueZ loads and initializes the attached controller.
Once the controller is attached follow the instructions in the
:ref:`bluetooth_ctlr_bluez` section to use BlueZ with it.
Debugging the controller
========================
The sample can be debugged using RTT since the UART is reserved used by this
application. To enable debug over RTT the debug configuration file can be used.
.. code-block:: console
west build samples/bluetooth/hci_uart_async -- -DEXTRA_CONFIG='debug.mixin.conf'
Then attach RTT as described here: :ref:`Using Segger J-Link <Using Segger J-Link>`
Using the controller with the Zephyr host
=========================================
This describes how to hook up a board running this sample to a board running
an application that uses the Zephyr host.
On the controller side, the `zephyr,bt-c2h-uart` DTS property (in the `chosen`
block) is used to select which uart device to use. For example if we want to
keep the console logs, we can keep console on uart0 and the HCI on uart1 like
so:
.. code-block:: dts
/ {
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,bt-c2h-uart = &uart1;
};
};
On the host application, some config options need to be used to select the H4
driver instead of the built-in controller:
.. code-block:: kconfig
CONFIG_BT_HCI=y
CONFIG_BT_CTLR=n
CONFIG_BT_H4=y
Similarly, the `zephyr,bt-uart` DTS property selects which uart to use:
.. code-block:: dts
/ {
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,bt-uart = &uart1;
};
};

View file

@ -0,0 +1,31 @@
/* This is the default app device tree overlay. This file is ignored if
* there is a board-specific overlay in `./boards`.
*
* Most boards define a convenient `&uart0`. It's used here to make the
* sample 'just work' automatically for those boards.
*/
bt_c2h_uart: &uart0 {
status = "okay";
current-speed = <1000000>;
hw-flow-control;
};
/ {
chosen {
zephyr,bt-c2h-uart = &bt_c2h_uart;
};
};
/* Some boards are by default assigning the &uart0 to these other
* functions. Removing the assignments will ensure a compilation error
* instead of accidental interference.
*/
/ {
chosen {
/delete-property/ zephyr,console;
/delete-property/ zephyr,shell-uart;
/delete-property/ zephyr,uart-mcumgr;
/delete-property/ zephyr,bt-mon-uart;
};
};

View file

@ -0,0 +1,15 @@
CONFIG_ASSERT_ON_ERRORS=y
CONFIG_ASSERT=y
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_OPTIMIZATIONS=y
CONFIG_DEBUG_THREAD_INFO=y
# Enable RTT console
CONFIG_RTT_CONSOLE=y
CONFIG_LOG=y
CONFIG_LOG_BUFFER_SIZE=10000
# This outputs all HCI traffic to a separate RTT channel. Use `btmon
# --jlink` to read it out. Add `--priority 7` for debug logs.
CONFIG_BT_DEBUG_MONITOR_RTT=y

View file

@ -0,0 +1,25 @@
# hci_uart_async
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y
# hci_raw (dependency of hci_uart)
CONFIG_BT=y
CONFIG_BT_HCI_RAW=y
CONFIG_BT_HCI_RAW_H4=y
CONFIG_BT_HCI_RAW_H4_ENABLE=y
# Controller configuration. Modify these for your application's needs.
CONFIG_BT_MAX_CONN=16
CONFIG_BT_BUF_ACL_RX_SIZE=255
CONFIG_BT_BUF_CMD_TX_SIZE=255
CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE=255
# Send an initial HCI_Command_Complete event on boot without waiting for
# HCI_Reset. Make sure to use the same value for this setting in your
# host application.
#CONFIG_BT_WAIT_NOP=y
# See `overlay.app`. The 'zephyr,console' chosen node is deleted there
# in case it has a interfering default. Those same boards set this
# config and it must be undone or the build will fail.
CONFIG_UART_CONSOLE=n

View file

@ -0,0 +1,19 @@
sample:
name: Bluetooth HCI UART Async
description:
This sample is a batteries-included example of a Bluetooth HCI UART
connectivity chip.
It demonstrates a possible implementation of an HCI UART (H4)
interface on top of Zephyr's Bluetooth Raw API, and how to expose it
over a UART.
This implementation is based on the Zephyr Asynchoronous UART API.
tests:
sample.bluetooth.hci_uart_async.nrf5:
harness: bluetooth
platform_allow:
- nrf52dk_nrf52832
tags:
- uart
- bluetooth

View file

@ -0,0 +1,403 @@
/* Copyright (c) 2023 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/time_units.h>
#include <zephyr/toolchain/common.h>
#include <zephyr/drivers/bluetooth/hci_driver.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/net/buf.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/buf.h>
#include <zephyr/bluetooth/hci_raw.h>
LOG_MODULE_REGISTER(hci_uart_async, LOG_LEVEL_DBG);
static const struct device *const hci_uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_bt_c2h_uart));
static K_THREAD_STACK_DEFINE(h2c_thread_stack, CONFIG_BT_HCI_TX_STACK_SIZE);
static struct k_thread h2c_thread;
enum h4_type {
H4_CMD = 0x01,
H4_ACL = 0x02,
H4_SCO = 0x03,
H4_EVT = 0x04,
H4_ISO = 0x05,
};
struct k_poll_signal uart_h2c_rx_sig;
struct k_poll_signal uart_c2h_tx_sig;
static K_FIFO_DEFINE(c2h_queue);
/** Send raw data on c2h UART.
*
* Blocks until completion. Not thread-safe.
*
* @retval 0 on success
* @retval -EBUSY Another transmission is in progress. This a
* thread-safety violation.
* @retval -errno @ref uart_tx error.
*/
static int uart_c2h_tx(const uint8_t *data, size_t size)
{
int err;
struct k_poll_signal *sig = &uart_c2h_tx_sig;
struct k_poll_event done[] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, sig),
};
k_poll_signal_reset(sig);
err = uart_tx(hci_uart_dev, data, size, SYS_FOREVER_US);
if (err) {
LOG_ERR("uart c2h tx: err %d", err);
return err;
}
err = k_poll(done, ARRAY_SIZE(done), K_FOREVER);
__ASSERT_NO_MSG(err == 0);
return 0;
}
/* Function expects that type is validated and only CMD, ISO or ACL will be used. */
static uint32_t hci_payload_size(const uint8_t *hdr_buf, enum h4_type type)
{
switch (type) {
case H4_CMD:
return ((const struct bt_hci_cmd_hdr *)hdr_buf)->param_len;
case H4_ACL:
return sys_le16_to_cpu(((const struct bt_hci_acl_hdr *)hdr_buf)->len);
case H4_ISO:
return bt_iso_hdr_len(
sys_le16_to_cpu(((const struct bt_hci_iso_hdr *)hdr_buf)->len));
default:
LOG_ERR("Invalid type: %u", type);
return 0;
}
}
static uint8_t hci_hdr_size(enum h4_type type)
{
switch (type) {
case H4_CMD:
return sizeof(struct bt_hci_cmd_hdr);
case H4_ACL:
return sizeof(struct bt_hci_acl_hdr);
case H4_ISO:
return sizeof(struct bt_hci_iso_hdr);
default:
LOG_ERR("Unexpected h4 type: %u", type);
return 0;
}
}
/** Send raw data on c2h UART.
*
* Blocks until either @p size has been received or special UART
* condition occurs on the UART RX line, like an UART break or parity
* error.
*
* Not thread-safe.
*
* @retval 0 on success
* @retval -EBUSY Another transmission is in progress. This a
* thread-safety violation.
* @retval -errno @ref uart_rx_enable error.
* @retval +stop_reason Special condition @ref uart_rx_stop_reason.
*/
static int uart_h2c_rx(uint8_t *dst, size_t size)
{
int err;
struct k_poll_signal *sig = &uart_h2c_rx_sig;
struct k_poll_event done[] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, sig),
};
k_poll_signal_reset(sig);
err = uart_rx_enable(hci_uart_dev, dst, size, SYS_FOREVER_US);
if (err) {
LOG_ERR("uart h2c rx: err %d", err);
return err;
}
k_poll(done, ARRAY_SIZE(done), K_FOREVER);
return sig->result;
}
/** Inject a HCI EVT Hardware error into the c2h packet stream.
*
* This uses `bt_recv`, just as if the controller is sending the error.
*/
static void send_hw_error(void)
{
const uint8_t err_code = 0;
const uint8_t hci_evt_hw_err[] = {BT_HCI_EVT_HARDWARE_ERROR,
sizeof(struct bt_hci_evt_hardware_error), err_code};
struct net_buf *buf = bt_buf_get_rx(BT_BUF_EVT, K_FOREVER);
net_buf_add_mem(buf, hci_evt_hw_err, sizeof(hci_evt_hw_err));
/* Inject the message into the c2h queue. */
bt_recv(buf);
/* The c2h thread will send the message at some point. The host
* will receive it and reset the controller.
*/
}
static void recover_sync_by_reset_pattern(void)
{
/* { H4_CMD, le_16(HCI_CMD_OP_RESET), len=0 } */
const uint8_t h4_cmd_reset[] = {0x01, 0x03, 0x0C, 0x00};
const uint32_t reset_pattern = sys_get_be32(h4_cmd_reset);
int err;
struct net_buf *h2c_cmd_reset;
uint32_t shift_register = 0;
LOG_DBG("Looking for reset pattern");
while (shift_register != reset_pattern) {
uint8_t read_byte;
uart_h2c_rx(&read_byte, sizeof(uint8_t));
LOG_DBG("h2c: 0x%02x", read_byte);
shift_register = (shift_register * 0x100) + read_byte;
}
LOG_DBG("Pattern found");
h2c_cmd_reset = bt_buf_get_tx(BT_BUF_H4, K_FOREVER, h4_cmd_reset, sizeof(h4_cmd_reset));
LOG_DBG("Fowarding reset");
err = bt_send(h2c_cmd_reset);
__ASSERT(!err, "Failed to send reset: %d", err);
}
static void h2c_h4_transport(void)
{
/* When entering this function, the h2c stream should be
* 'synchronized'. I.e. The stream should be at a H4 packet
* boundary.
*
* This function returns to signal a desynchronization.
* When this happens, the caller should resynchronize before
* entering this function again. It's up to the caller to decide
* how to resynchronize.
*/
for (;;) {
int err;
struct net_buf *buf;
uint8_t h4_type;
uint8_t hdr_size;
uint8_t *hdr_buf;
uint16_t payload_size;
LOG_DBG("h2c: listening");
/* Read H4 type. */
err = uart_h2c_rx(&h4_type, sizeof(uint8_t));
if (err) {
return;
}
LOG_DBG("h2c: h4_type %d", h4_type);
/* Allocate buf. */
buf = bt_buf_get_tx(BT_BUF_H4, K_FOREVER, &h4_type, sizeof(h4_type));
LOG_DBG("h2c: buf %p", buf);
if (!buf) {
/* `h4_type` was invalid. */
__ASSERT_NO_MSG(hci_hdr_size(h4_type) == 0);
LOG_WRN("bt_buf_get_tx failed h4_type %d", h4_type);
return;
}
/* Read HCI header. */
hdr_size = hci_hdr_size(h4_type);
hdr_buf = net_buf_add(buf, hdr_size);
err = uart_h2c_rx(hdr_buf, hdr_size);
if (err) {
net_buf_unref(buf);
return;
}
LOG_HEXDUMP_DBG(hdr_buf, hdr_size, "h2c: hci hdr");
/* Read HCI payload. */
payload_size = hci_payload_size(hdr_buf, h4_type);
LOG_DBG("h2c: payload_size %u", payload_size);
if (payload_size <= net_buf_tailroom(buf)) {
uint8_t *payload_dst = net_buf_add(buf, payload_size);
err = uart_h2c_rx(payload_dst, payload_size);
if (err) {
net_buf_unref(buf);
return;
}
LOG_HEXDUMP_DBG(payload_dst, payload_size, "h2c: hci payload");
} else {
/* Discard oversize packet. */
uint8_t *discard_dst;
uint16_t discard_size;
LOG_WRN("h2c: Discarding oversize h4_type %d payload_size %d.", h4_type,
payload_size);
/* Reset `buf` so all of it is available. */
net_buf_reset(buf);
discard_dst = net_buf_tail(buf);
discard_size = net_buf_max_len(buf);
while (payload_size) {
uint16_t read_size = MIN(payload_size, discard_size);
err = uart_h2c_rx(discard_dst, read_size);
if (err) {
net_buf_unref(buf);
return;
}
payload_size -= read_size;
}
net_buf_unref(buf);
buf = NULL;
}
LOG_DBG("h2c: packet done");
/* Route buf to Controller. */
if (buf) {
err = bt_send(buf);
if (err) {
/* This is not a transport error. */
LOG_ERR("bt_send err %d", err);
net_buf_unref(buf);
buf = NULL;
}
}
k_yield();
}
}
static void h2c_thread_entry(void *p1, void *p2, void *p3)
{
k_thread_name_set(k_current_get(), "HCI TX (h2c)");
for (;;) {
LOG_DBG("Synchronized");
h2c_h4_transport();
LOG_WRN("Desynchronized");
send_hw_error();
recover_sync_by_reset_pattern();
}
}
void callback(const struct device *dev, struct uart_event *evt, void *user_data)
{
ARG_UNUSED(user_data);
if (evt->type == UART_RX_DISABLED) {
(void)k_poll_signal_raise(&uart_h2c_rx_sig, 0);
} else if (evt->type == UART_RX_STOPPED) {
(void)k_poll_signal_raise(&uart_h2c_rx_sig, evt->data.rx_stop.reason);
} else if (evt->type == UART_TX_DONE) {
(void)k_poll_signal_raise(&uart_c2h_tx_sig, 0);
}
}
static int hci_uart_init(void)
{
int err;
k_poll_signal_init(&uart_h2c_rx_sig);
k_poll_signal_init(&uart_c2h_tx_sig);
LOG_DBG("");
if (!device_is_ready(hci_uart_dev)) {
LOG_ERR("HCI UART %s is not ready", hci_uart_dev->name);
return -EINVAL;
}
BUILD_ASSERT(IS_ENABLED(CONFIG_UART_ASYNC_API));
err = uart_callback_set(hci_uart_dev, callback, NULL);
/* Note: Asserts if CONFIG_UART_ASYNC_API is not enabled for `hci_uart_dev`. */
__ASSERT(!err, "err %d", err);
return 0;
}
SYS_INIT(hci_uart_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
const struct {
uint8_t h4;
struct bt_hci_evt_hdr hdr;
struct bt_hci_evt_cmd_complete cc;
} __packed cc_evt = {
.h4 = H4_EVT,
.hdr = {.evt = BT_HCI_EVT_CMD_COMPLETE, .len = sizeof(struct bt_hci_evt_cmd_complete)},
.cc = {.ncmd = 1, .opcode = sys_cpu_to_le16(BT_OP_NOP)},
};
static void c2h_thread_entry(void)
{
k_thread_name_set(k_current_get(), "HCI RX (c2h)");
if (IS_ENABLED(CONFIG_BT_WAIT_NOP)) {
uart_c2h_tx((char *)&cc_evt, sizeof(cc_evt));
}
for (;;) {
struct net_buf *buf;
buf = net_buf_get(&c2h_queue, K_FOREVER);
uart_c2h_tx(buf->data, buf->len);
net_buf_unref(buf);
}
}
void hci_uart_main(void)
{
int err;
err = bt_enable_raw(&c2h_queue);
__ASSERT_NO_MSG(!err);
/* TX thread. */
k_thread_create(&h2c_thread, h2c_thread_stack, K_THREAD_STACK_SIZEOF(h2c_thread_stack),
h2c_thread_entry, NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
/* Reuse current thread as RX thread. */
c2h_thread_entry();
}

View file

@ -0,0 +1,11 @@
/* Copyright (c) 2023 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
extern int hci_uart_main(void);
int main(void)
{
hci_uart_main();
return 0;
}