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:
parent
4d926ac041
commit
347ce7aa7f
14 changed files with 955 additions and 0 deletions
10
samples/bluetooth/hci_uart_async/CMakeLists.txt
Normal file
10
samples/bluetooth/hci_uart_async/CMakeLists.txt
Normal 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
|
||||
)
|
158
samples/bluetooth/hci_uart_async/README.rst
Normal file
158
samples/bluetooth/hci_uart_async/README.rst
Normal 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;
|
||||
};
|
||||
};
|
31
samples/bluetooth/hci_uart_async/app.overlay
Normal file
31
samples/bluetooth/hci_uart_async/app.overlay
Normal 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;
|
||||
};
|
||||
};
|
15
samples/bluetooth/hci_uart_async/debug.mixin.conf
Normal file
15
samples/bluetooth/hci_uart_async/debug.mixin.conf
Normal 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
|
25
samples/bluetooth/hci_uart_async/prj.conf
Normal file
25
samples/bluetooth/hci_uart_async/prj.conf
Normal 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
|
19
samples/bluetooth/hci_uart_async/sample.yaml
Normal file
19
samples/bluetooth/hci_uart_async/sample.yaml
Normal 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
|
403
samples/bluetooth/hci_uart_async/src/hci_uart_async.c
Normal file
403
samples/bluetooth/hci_uart_async/src/hci_uart_async.c
Normal 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();
|
||||
}
|
11
samples/bluetooth/hci_uart_async/src/main.c
Normal file
11
samples/bluetooth/hci_uart_async/src/main.c
Normal 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;
|
||||
}
|
15
tests/bluetooth/hci_uart_async/CMakeLists.txt
Normal file
15
tests/bluetooth/hci_uart_async/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
set(EXTRA_CONF_FILE
|
||||
../../../samples/bluetooth/hci_uart_async/prj.conf
|
||||
)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
|
||||
project(test_samples_bluetooth_hci_uart_async)
|
||||
target_sources(app PRIVATE
|
||||
../../../samples/bluetooth/hci_uart_async/src/hci_uart_async.c
|
||||
src/test_hci_uart_async.c
|
||||
)
|
11
tests/bluetooth/hci_uart_async/app.overlay
Normal file
11
tests/bluetooth/hci_uart_async/app.overlay
Normal file
|
@ -0,0 +1,11 @@
|
|||
/ {
|
||||
chosen {
|
||||
zephyr,bt-c2h-uart = &test_uart;
|
||||
};
|
||||
|
||||
test_uart: test_uart {
|
||||
compatible = "vnd,serial";
|
||||
status = "okay";
|
||||
buffer-size = <100>;
|
||||
};
|
||||
};
|
3
tests/bluetooth/hci_uart_async/boards/native_posix.conf
Normal file
3
tests/bluetooth/hci_uart_async/boards/native_posix.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Print logs and test results on stdout. For some reason, this not the
|
||||
# default when SERIAL=y.
|
||||
CONFIG_LOG_BACKEND_NATIVE_POSIX=y
|
10
tests/bluetooth/hci_uart_async/prj.conf
Normal file
10
tests/bluetooth/hci_uart_async/prj.conf
Normal file
|
@ -0,0 +1,10 @@
|
|||
CONFIG_BT_NO_DRIVER=y
|
||||
|
||||
CONFIG_RING_BUFFER=y
|
||||
|
||||
CONFIG_ASSERT=y
|
||||
CONFIG_LOG=y
|
||||
CONFIG_TEST=y
|
||||
|
||||
CONFIG_ZTEST=y
|
||||
CONFIG_ZTEST_NEW_API=y
|
238
tests/bluetooth/hci_uart_async/src/test_hci_uart_async.c
Normal file
238
tests/bluetooth/hci_uart_async/src/test_hci_uart_async.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/kernel/thread.h>
|
||||
#include <zephyr/net/buf.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/ztest_assert.h>
|
||||
#include <zephyr/ztest_test_new.h>
|
||||
|
||||
#include <zephyr/drivers/bluetooth/hci_driver.h>
|
||||
#include <zephyr/drivers/uart/serial_test.h>
|
||||
|
||||
LOG_MODULE_REGISTER(test, LOG_LEVEL_DBG);
|
||||
|
||||
/* This is a mock UART. Using `serial_vnd_...` on this simulates
|
||||
* traffic from the external Host.
|
||||
*/
|
||||
static const struct device *const zephyr_bt_c2h_uart = DEVICE_DT_GET(DT_CHOSEN(zephyr_bt_c2h_uart));
|
||||
|
||||
/* The DUT is Sandwiched between the mock serial interface and a mock
|
||||
* controller. {{{
|
||||
*/
|
||||
static void serial_vnd_data_callback(const struct device *dev, void *user_data);
|
||||
static int drv_send(struct net_buf *buf);
|
||||
static int drv_open(void);
|
||||
static const struct bt_hci_driver drv = {
|
||||
.name = "Mock Controller",
|
||||
.bus = BT_HCI_DRIVER_BUS_VIRTUAL,
|
||||
.open = drv_open,
|
||||
.send = drv_send,
|
||||
};
|
||||
static int sys_init_hci_driver_register(void)
|
||||
{
|
||||
serial_vnd_set_callback(zephyr_bt_c2h_uart, serial_vnd_data_callback, NULL);
|
||||
bt_hci_driver_register(&drv);
|
||||
return 0;
|
||||
}
|
||||
SYS_INIT(sys_init_hci_driver_register, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
|
||||
/* }}} */
|
||||
|
||||
/* Start the DUT "main thread". The settings for this thread are selected as
|
||||
* true as possible to the real main thread. {{{
|
||||
*/
|
||||
static struct k_thread hci_uart_thread;
|
||||
static K_THREAD_PINNED_STACK_DEFINE(hci_uart_thread_stack, CONFIG_MAIN_STACK_SIZE);
|
||||
static void hci_uart_thread_entry(void *p1, void *p2, void *p3)
|
||||
{
|
||||
extern void hci_uart_main(void);
|
||||
hci_uart_main();
|
||||
}
|
||||
static int sys_init_spawn_hci_uart(void)
|
||||
{
|
||||
k_thread_name_set(&hci_uart_thread, "hci_uart_main");
|
||||
k_thread_create(&hci_uart_thread, hci_uart_thread_stack,
|
||||
K_THREAD_STACK_SIZEOF(hci_uart_thread_stack), hci_uart_thread_entry, NULL,
|
||||
NULL, NULL, CONFIG_MAIN_THREAD_PRIORITY, 0, K_NO_WAIT);
|
||||
return 0;
|
||||
}
|
||||
SYS_INIT(sys_init_spawn_hci_uart, POST_KERNEL, 64);
|
||||
/* }}} */
|
||||
|
||||
/* Mock controller callbacks. {{{ */
|
||||
|
||||
static int drv_open(void)
|
||||
{
|
||||
LOG_DBG("drv_open");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** This FIFO holds the references to all h2c packets the DUT has sent
|
||||
* to the controller using #bt_send.
|
||||
*
|
||||
* Each test should mock a controller by calling #net_buf_get on this
|
||||
* FIFO and simulate a controller's #bt_hci_driver::drv_send. The mocks
|
||||
* should use #bt_recv to send c2h packets to the DUT.
|
||||
*/
|
||||
K_FIFO_DEFINE(drv_send_fifo); /* elem T: net_buf */
|
||||
static int drv_send(struct net_buf *buf)
|
||||
{
|
||||
LOG_DBG("buf %p type %d len %u", buf, bt_buf_get_type(buf), buf->len);
|
||||
LOG_HEXDUMP_DBG(buf->data, buf->len, "buf");
|
||||
|
||||
__ASSERT_NO_MSG(buf);
|
||||
net_buf_put(&drv_send_fifo, buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* Mock UART c2h TX handler. {{{ */
|
||||
|
||||
static void serial_vnd_data_callback(const struct device *dev, void *user_data)
|
||||
{
|
||||
uint32_t size = serial_vnd_out_data_size_get(dev);
|
||||
uint8_t data[size];
|
||||
|
||||
serial_vnd_read_out_data(dev, data, size);
|
||||
LOG_HEXDUMP_DBG(data, size, "uart tx");
|
||||
|
||||
/* If a test needs to look at the c2h UART traffic, it can be
|
||||
* captured here.
|
||||
*/
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
#define HCI_NORMAL_CMD_BUF_COUNT (CONFIG_BT_BUF_CMD_TX_COUNT - 1)
|
||||
#define TEST_PARAM_HOST_COMPLETE_COUNT 10
|
||||
#define TIMEOUT_PRESUME_STUCK K_SECONDS(1)
|
||||
|
||||
/** Corresponds to:
|
||||
* - #bt_hci_cmd_hdr
|
||||
*/
|
||||
const uint8_t h4_msg_cmd_dummy1[] = {
|
||||
0x01, /* H4: opcode = CMD */
|
||||
0x01, 0x00, /* H4: CMD: opcode = 1 */
|
||||
0x00, /* H4: CMD: len = 0 */
|
||||
};
|
||||
|
||||
/** Corresponds to:
|
||||
* - #bt_hci_cmd_hdr
|
||||
* - #bt_hci_cp_host_num_completed_packets
|
||||
*/
|
||||
const uint8_t h4_msg_cmd_host_num_complete[] = {
|
||||
0x01, /* H4: opcode = CMD */
|
||||
0x35, 0x0c, /* H4: CMD: opcode = BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS */
|
||||
0x05, /* H4: CMD: len = 5 */
|
||||
0x01, /* H4: CMD: num_handles = 1 */
|
||||
0x00, 0x00, /* H4: CMD: connection_handle = 0 */
|
||||
0x01, 0x00, /* H4: CMD: num_complete = 1 */
|
||||
};
|
||||
|
||||
/** Corresponds to:
|
||||
* - #bt_hci_evt_hdr
|
||||
* - #bt_hci_evt_cmd_complete
|
||||
*/
|
||||
const uint8_t hci_msg_rx_evt_cmd_complete[] = {
|
||||
BT_HCI_EVT_CMD_COMPLETE, /* EVT: opcode */
|
||||
0x03, /* EVT: len */
|
||||
0x01, /* EVT: CMDC: ncmd = 1 */
|
||||
/* EVT: CMDC: opcode */
|
||||
0x00,
|
||||
0x00,
|
||||
};
|
||||
|
||||
ZTEST_SUITE(hci_uart, NULL, NULL, NULL, NULL, NULL);
|
||||
ZTEST(hci_uart, test_h2c_cmd_flow_control)
|
||||
{
|
||||
/* This test assumes the DUT does not care about the contents of
|
||||
* the HCI messages, other than the HCI type/endpoint and the
|
||||
* size. This allows the test to cheat and skip the HCI Reset,
|
||||
* connection setup etc and use dummy command-packets.
|
||||
*/
|
||||
|
||||
/* Send commands, saturating the controller's command pipeline. */
|
||||
for (uint16_t i = 0; i < HCI_NORMAL_CMD_BUF_COUNT; i++) {
|
||||
int write_size = serial_vnd_queue_in_data(zephyr_bt_c2h_uart, h4_msg_cmd_dummy1,
|
||||
sizeof(h4_msg_cmd_dummy1));
|
||||
__ASSERT_NO_MSG(write_size == sizeof(h4_msg_cmd_dummy1));
|
||||
}
|
||||
|
||||
/* At this point, the HCI flow control limit for the cmd
|
||||
* endpoint has been reached. It will remain so until the
|
||||
* controller mock has sent a 'HCI Command Complete' event.
|
||||
*
|
||||
* But the 'HCI Host Number of Completed Packets' command is
|
||||
* exempt from HCI flow control. (It's like it has its own
|
||||
* endpoint, that has no flow control.)
|
||||
*
|
||||
* We now send several 'HCI Host Number of Completed Packets'
|
||||
* packets before handling any commands in the controller. This
|
||||
* tests whether the DUT is able to engage the lower transport
|
||||
* flow controller (i.e. UART flow-control) or somehow handle
|
||||
* the special packets out-of-order in real-time.
|
||||
*/
|
||||
for (uint16_t i = 0; i < TEST_PARAM_HOST_COMPLETE_COUNT; i++) {
|
||||
int write_size =
|
||||
serial_vnd_queue_in_data(zephyr_bt_c2h_uart, h4_msg_cmd_host_num_complete,
|
||||
sizeof(h4_msg_cmd_host_num_complete));
|
||||
__ASSERT_NO_MSG(write_size == sizeof(h4_msg_cmd_host_num_complete));
|
||||
}
|
||||
|
||||
LOG_DBG("All h2c packets queued on UART");
|
||||
|
||||
/* Then, we check that all packets are delivered without loss. */
|
||||
|
||||
/* Expect all the normal commands first. */
|
||||
for (uint16_t i = 0; i < HCI_NORMAL_CMD_BUF_COUNT; i++) {
|
||||
/* The mock controller processes a command. */
|
||||
{
|
||||
struct net_buf *buf = net_buf_get(&drv_send_fifo, TIMEOUT_PRESUME_STUCK);
|
||||
|
||||
zassert_not_null(buf);
|
||||
zassert_equal(buf->len, sizeof(h4_msg_cmd_dummy1) - 1, "Wrong length");
|
||||
zassert_mem_equal(buf->data, &h4_msg_cmd_dummy1[1],
|
||||
sizeof(h4_msg_cmd_dummy1) - 1);
|
||||
net_buf_unref(buf);
|
||||
}
|
||||
|
||||
/* The controller sends a HCI Command Complete response. */
|
||||
{
|
||||
int err;
|
||||
struct net_buf *buf = bt_buf_get_rx(BT_BUF_EVT, K_NO_WAIT);
|
||||
|
||||
zassert_not_null(buf);
|
||||
net_buf_add_mem(buf, hci_msg_rx_evt_cmd_complete,
|
||||
sizeof(hci_msg_rx_evt_cmd_complete));
|
||||
err = bt_recv(buf);
|
||||
zassert_equal(err, 0, "bt_recv failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* Expect all the 'HCI Host Number of Completed Packets'. */
|
||||
for (uint16_t i = 0; i < TEST_PARAM_HOST_COMPLETE_COUNT; i++) {
|
||||
/* The mock controller processes a 'HCI Host Number of Completed Packets'. */
|
||||
{
|
||||
struct net_buf *buf = net_buf_get(&drv_send_fifo, TIMEOUT_PRESUME_STUCK);
|
||||
|
||||
zassert_not_null(buf);
|
||||
zassert_equal(buf->len, sizeof(h4_msg_cmd_host_num_complete) - 1,
|
||||
"Wrong length");
|
||||
zassert_mem_equal(buf->data, &h4_msg_cmd_host_num_complete[1],
|
||||
sizeof(h4_msg_cmd_dummy1) - 2);
|
||||
net_buf_unref(buf);
|
||||
}
|
||||
|
||||
/* There is no response to 'HCI Host Number of Completed Packets'. */
|
||||
}
|
||||
|
||||
LOG_DBG("All h2c packets received by controller.");
|
||||
}
|
6
tests/bluetooth/hci_uart_async/testcase.yaml
Normal file
6
tests/bluetooth/hci_uart_async/testcase.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
tests:
|
||||
samples.bluetooth.hci_uart_async:
|
||||
tags: bluetooth uart
|
||||
harness: ztest
|
||||
platform_allow:
|
||||
- native_posix
|
Loading…
Add table
Add a link
Reference in a new issue