| /* 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."); |
| } |