blob: 08ea8bc1a79bc026637bb2d6313eb59232f80729 [file] [log] [blame]
/* 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.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.");
}