diff --git a/drivers/serial/serial_test.c b/drivers/serial/serial_test.c index 95e036e5992ed08..bfd4894f310dfcb 100644 --- a/drivers/serial/serial_test.c +++ b/drivers/serial/serial_test.c @@ -9,12 +9,21 @@ * devices for the "vnd,serial" devicetree compatible used in test code. */ +#include + +#include #include #include #include #include +#include #include +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 @@ -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 @@ -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) @@ -130,6 +300,124 @@ 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; + } + + if (callback == NULL && data->read_buf) { + LOG_ERR("Setting callback to NULL while asynchronous API is in use."); + } + + 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->read_buf) { + return; + } + + __ASSERT_NO_MSG(data->async_cb); + + 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, @@ -138,6 +426,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) \ diff --git a/dts/bindings/test/vnd,serial.yaml b/dts/bindings/test/vnd,serial.yaml index dc2327b4827991d..c2d931b0ed73354 100644 --- a/dts/bindings/test/vnd,serial.yaml +++ b/dts/bindings/test/vnd,serial.yaml @@ -5,9 +5,6 @@ compatible: "vnd,serial" include: uart-controller.yaml properties: - reg: - required: true - baud-rate: type: int diff --git a/include/zephyr/drivers/uart/serial_test.h b/include/zephyr/drivers/uart/serial_test.h index 5e011d40321714d..ff1a2ad61814ea5 100644 --- a/include/zephyr/drivers/uart/serial_test.h +++ b/include/zephyr/drivers/uart/serial_test.h @@ -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. diff --git a/samples/bluetooth/hci_uart_async/CMakeLists.txt b/samples/bluetooth/hci_uart_async/CMakeLists.txt new file mode 100644 index 000000000000000..054f5b85b425227 --- /dev/null +++ b/samples/bluetooth/hci_uart_async/CMakeLists.txt @@ -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 +) diff --git a/samples/bluetooth/hci_uart_async/README.rst b/samples/bluetooth/hci_uart_async/README.rst new file mode 100644 index 000000000000000..05cc83c725e72a2 --- /dev/null +++ b/samples/bluetooth/hci_uart_async/README.rst @@ -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-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-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 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; + }; + }; diff --git a/samples/bluetooth/hci_uart_async/app.overlay b/samples/bluetooth/hci_uart_async/app.overlay new file mode 100644 index 000000000000000..9794e5cccb3721a --- /dev/null +++ b/samples/bluetooth/hci_uart_async/app.overlay @@ -0,0 +1,11 @@ +/ { + chosen { + zephyr,bt-c2h-uart = &uart0; + }; +}; + +&uart0 { + /* compatible = uart-controller; */ + current-speed = <1000000>; + hw-flow-control; +}; diff --git a/samples/bluetooth/hci_uart_async/debug.mixin.conf b/samples/bluetooth/hci_uart_async/debug.mixin.conf new file mode 100644 index 000000000000000..774a2a1c45bc4f2 --- /dev/null +++ b/samples/bluetooth/hci_uart_async/debug.mixin.conf @@ -0,0 +1,5 @@ +CONFIG_LOG=y + +CONFIG_ASSERT=y +CONFIG_DEBUG_INFO=y +CONFIG_EXCEPTION_STACK_TRACE=y diff --git a/samples/bluetooth/hci_uart_async/prj.conf b/samples/bluetooth/hci_uart_async/prj.conf new file mode 100644 index 000000000000000..a4d85732a6283dc --- /dev/null +++ b/samples/bluetooth/hci_uart_async/prj.conf @@ -0,0 +1,23 @@ +# Not implemented yet in hci_uart_async +#CONFIG_BT_CTLR_ASSERT_HANDLER=y + +# hci_uart +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 +CONFIG_BT_CTLR_DTM_HCI=n + +# Send an initial HCI_Command_Complete event on boot without waiting for +# HCI_Reset. +CONFIG_BT_WAIT_NOP=y diff --git a/samples/bluetooth/hci_uart_async/src/hci_uart.c b/samples/bluetooth/hci_uart_async/src/hci_uart.c new file mode 100644 index 000000000000000..7e4ff92099aa623 --- /dev/null +++ b/samples/bluetooth/hci_uart_async/src/hci_uart.c @@ -0,0 +1,377 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE_NAME hci_uart +LOG_MODULE_REGISTER(LOG_MODULE_NAME, 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); + +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; + } + + k_poll(done, ARRAY_SIZE(done), K_FOREVER); + + 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; + } +} + +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); + memset(dst, 0, size); + return err; + } + + k_poll(done, ARRAY_SIZE(done), K_FOREVER); + return sig->result; +} + +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)); + bt_recv(buf); + + /* HW error is on c2h queue. Delivery is async. */ +} + +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) +{ + 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 { + uint8_t *discard_dst; + uint16_t discard_size; + + /* Discard oversize packet. */ + LOG_WRN("h2c: Discarding oversize h4_type %d payload_size %d.", h4_type, + payload_size); + + 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) { + k_poll_signal_raise(&uart_h2c_rx_sig, 0); + } + if (evt->type == UART_RX_STOPPED) { + k_poll_signal_raise(&uart_h2c_rx_sig, evt->data.rx_stop.reason); + } + if (evt->type == UART_TX_DONE) { + 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 (IS_ENABLED(CONFIG_USB_CDC_ACM)) { + if (usb_enable(NULL)) { + LOG_ERR("Failed to enable USB"); + return -EINVAL; + } + } + + 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); + + /* Is CONFIG_UART_ASYNC_API not implemented/enabled for the + * selected `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, /* TODO: Should this value come from a config? */ + .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) +{ + LOG_DBG("Start"); + __ASSERT(hci_uart_dev, "UART device is NULL"); + + bt_enable_raw(&c2h_queue); + + /* 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(); +} diff --git a/samples/bluetooth/hci_uart_async/src/main.c b/samples/bluetooth/hci_uart_async/src/main.c new file mode 100644 index 000000000000000..2d38d83e45c986f --- /dev/null +++ b/samples/bluetooth/hci_uart_async/src/main.c @@ -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; +} diff --git a/tests/bluetooth/samples/hci_uart_async/CMakeLists.txt b/tests/bluetooth/samples/hci_uart_async/CMakeLists.txt new file mode 100644 index 000000000000000..d1e9aa2fdd2bb9f --- /dev/null +++ b/tests/bluetooth/samples/hci_uart_async/CMakeLists.txt @@ -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_bluetooth_samples_hci_uart_async) +target_sources(app PRIVATE + ../../../../samples/bluetooth/hci_uart_async/src/hci_uart.c + src/test_hci_uart_async.c +) diff --git a/tests/bluetooth/samples/hci_uart_async/boards/native_posix.conf b/tests/bluetooth/samples/hci_uart_async/boards/native_posix.conf new file mode 100644 index 000000000000000..e638cd6a0efa62a --- /dev/null +++ b/tests/bluetooth/samples/hci_uart_async/boards/native_posix.conf @@ -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 diff --git a/tests/bluetooth/samples/hci_uart_async/prj.conf b/tests/bluetooth/samples/hci_uart_async/prj.conf new file mode 100644 index 000000000000000..b038c7c4312e9da --- /dev/null +++ b/tests/bluetooth/samples/hci_uart_async/prj.conf @@ -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 diff --git a/tests/bluetooth/samples/hci_uart_async/src/test_hci_uart_async.c b/tests/bluetooth/samples/hci_uart_async/src/test_hci_uart_async.c new file mode 100644 index 000000000000000..1996aba177ac34f --- /dev/null +++ b/tests/bluetooth/samples/hci_uart_async/src/test_hci_uart_async.c @@ -0,0 +1,238 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +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."); +} diff --git a/tests/bluetooth/samples/hci_uart_async/testcase.yaml b/tests/bluetooth/samples/hci_uart_async/testcase.yaml new file mode 100644 index 000000000000000..9d158307ef977dc --- /dev/null +++ b/tests/bluetooth/samples/hci_uart_async/testcase.yaml @@ -0,0 +1,6 @@ +tests: + bluetooth.samples.hci_uart_async: + tags: bluetooth uart + harness: ztest + platform_allow: + - native_posix