diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 7043034c64e..c07d18413dc 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -319,7 +319,10 @@ Bluetooth samples * :ref:`direct_test_mode` sample: - * Added support for the nRF52840 DK. + * Added: + + * Support for the nRF52840 DK. + * Experimental support for the HCI interface. * Updated: diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 1a8593c043e..d6a8247945b 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -61,6 +61,15 @@ if (CONFIG_NCS_SAMPLE_REMOTE_SHELL_CHILD_IMAGE) ) endif() +if (CONFIG_NCS_SAMPLE_DTM_REMOTE_HCI_CHILD_IMAGE) + add_child_image( + NAME "remote_hci" + SOURCE_DIR "${ZEPHYR_NRF_MODULE_DIR}/samples/bluetooth/direct_test_mode/remote_hci" + DOMAIN "CPUAPP" + BOARD ${CONFIG_DOMAIN_CPUAPP_BOARD} + ) +endif() + if (CONFIG_NCS_INCLUDE_RPMSG_CHILD_IMAGE OR CONFIG_NCS_SAMPLE_EMPTY_NET_CORE_CHILD_IMAGE) if (CONFIG_NCS_SAMPLE_EMPTY_NET_CORE_CHILD_IMAGE) diff --git a/samples/Kconfig b/samples/Kconfig index 921966365b5..f030b64f3b6 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -60,6 +60,14 @@ config NCS_SAMPLE_REMOTE_SHELL_CHILD_IMAGE Add remote_shell as a child image (application core). Used by samples that run on the network core only. +config NCS_SAMPLE_DTM_REMOTE_HCI_CHILD_IMAGE + bool "Add remote_hci child image for DTM" + depends on SOC_NRF5340_CPUNET + select PARTITION_MANAGER_ENABLED + help + Add remote_hci as a child image (application core). + Used by the DTM sample to pass HCI commands from + application core UART peripheral. if NCS_INCLUDE_RPMSG_CHILD_IMAGE diff --git a/samples/bluetooth/direct_test_mode/CMakeLists.txt b/samples/bluetooth/direct_test_mode/CMakeLists.txt index 1ca0c10056e..d3cdf039086 100644 --- a/samples/bluetooth/direct_test_mode/CMakeLists.txt +++ b/samples/bluetooth/direct_test_mode/CMakeLists.txt @@ -28,12 +28,30 @@ if(CONFIG_DTM_USB) set(remote_shell_CONF_FILE ${CMAKE_CURRENT_LIST_DIR}/conf/remote_shell/prj_usb.conf) endif() +if("${BOARD}" STREQUAL "nrf5340dk_nrf5340_cpunet") + if(NOT "${EXTRA_CONF_FILE}" STREQUAL "overlay-hci-nrf53.conf") + set(EXTRA_DTC_OVERLAY_FILE "${CMAKE_CURRENT_LIST_DIR}/conf/remote_shell_nrf53.overlay") + endif() +endif() + find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(NONE) target_include_directories(app PRIVATE ./src) -target_sources_ifdef(CONFIG_DTM_TRANSPORT_TWOWIRE app PRIVATE src/transport/dtm_uart_twowire.c) +target_sources_ifdef(CONFIG_DTM_TRANSPORT_TWOWIRE app PRIVATE + src/transport/dtm_uart_twowire.c + src/transport/dtm_uart_wait.c +) + +target_sources_ifdef(CONFIG_DTM_TRANSPORT_HCI app PRIVATE src/transport/dtm_hci.c) + +if(CONFIG_NCS_SAMPLE_DTM_REMOTE_HCI_CHILD_IMAGE) + target_sources(app PRIVATE src/transport/hci_uart_remote.c) + target_include_directories(app PRIVATE ./rpc) +else() + target_sources_ifdef(CONFIG_DTM_TRANSPORT_HCI app PRIVATE src/transport/hci_uart.c) +endif() # NORDIC SDK APP START target_sources(app PRIVATE diff --git a/samples/bluetooth/direct_test_mode/Kconfig b/samples/bluetooth/direct_test_mode/Kconfig index 07533af3f24..e928d741e99 100644 --- a/samples/bluetooth/direct_test_mode/Kconfig +++ b/samples/bluetooth/direct_test_mode/Kconfig @@ -36,7 +36,7 @@ config ANOMALY_172_TIMER_IRQ_PRIORITY config DTM_USB bool "DTM over USB CDC ACM class" - depends on SOC_NRF5340_CPUNET + depends on SOC_NRF5340_CPUNET && DTM_TRANSPORT_TWOWIRE select EXPERIMENTAL help Use USB instead of UART as the DTM interface. For nRF5340 the USB from application core @@ -54,14 +54,50 @@ config DTM_TRANSPORT_TWOWIRE help Use the Two Wire transport interface as the DTM transport layer. +config DTM_TRANSPORT_HCI + bool "DTM over HCI UART [EXPERIMENTAL]" + depends on SERIAL + depends on NET_BUF + select EXPERIMENTAL + help + Use the HCI UART transport interface as the DTM transport layer. + endchoice # DTM_TRANSPORT +if DTM_TRANSPORT_HCI + +config DTM_HCI_QUEUE_COUNT + int "Count of HCI RX/TX queues" + default 16 + help + Maximum depth of the HCI RX/TX queues. + +config DTM_HCI_QUEUE_SIZE + int "Size of HCI RX/TX queue buffer" + default 1024 + help + Maximum size of the HCI RX/TX queue element. + +config DTM_HCI_TX_THREAD_STACK_SIZE + int "Stack size of TX thread" + default 2048 + help + Stack size of the TX thread. + +config DTM_HCI_TX_THREAD_PRIORITY + int "TX thread priority" + default 7 + help + Priority of the TX thread. + +endif # DTM_TRANSPORT_HCI + config DTM_POWER_CONTROL_AUTOMATIC bool "Automatic power control" depends on FEM default y help - Set the SoC output power and the front-end module gain to achieve the Tx output power + Set the SoC output power and the front-end module gain to achieve the TX output power requested by user. If the exact value cannot be achieved, power is set to the closest possible value. If this option is disabled, user can set the SoC output power and the front-end module gain with the separate vendor specific commands. diff --git a/samples/bluetooth/direct_test_mode/README.rst b/samples/bluetooth/direct_test_mode/README.rst index c00d720b470..047d6d054c4 100644 --- a/samples/bluetooth/direct_test_mode/README.rst +++ b/samples/bluetooth/direct_test_mode/README.rst @@ -50,8 +50,10 @@ The DTM sample includes two parts: You can find the source code of both parts in :file:`samples/bluetooth/direct_test_mode/src`. -The DTM sample contains a driver for a 2-wire UART interface. -The driver maps two-octet commands and events to the DTM library, as specified by the Bluetooth Low Energy DTM specification. +This sample contains a driver for a 2-wire UART interface and an experimental HCI UART interface. +Unless otherwise stated, all following commands and references describe the 2-wire interface. +The 2-wire driver maps two-octet commands and events to the DTM library, as specified by the Bluetooth Low Energy DTM specification. +The HCI driver accepts HCI commands in H4 format and implements a minimal set of HCI commands usually sufficient to run DTM testing. .. figure:: /images/bt_dtm_dut.svg :alt: nRF5 with DTM as a DUT @@ -60,8 +62,9 @@ The implementation is self-contained and requires no Bluetooth Low Energy protoc The MPU is initialized in the standard way. The DTM library function :c:func:`dtm_init` configures all interrupts, timers, and the radio. -The :file:`main.c` file may be extended with other interface implementations, such as an HCI interface, USB, or another interface required by the Upper Tester. +This sample may be extended with other interface implementations, such as an HCI interface, USB, or another interface required by the Upper Tester. The extension should be done by adding an appropriate interface implementation in the :file:`transport` directory. +The transports must conform to the interface specified in the :file:`transport/dtm_transport.h` file. The interface to the Lower Tester uses the antenna connector of the chosen development kit. While in principle an aerial connection might be used, conformance tests cover the reading of the transmission power delivered by the DUT. @@ -316,9 +319,12 @@ The DTM-to-Serial adaptation layer The :file:`dtm_uart_twowire.c` file is an implementation of the UART interface specified in the `Bluetooth Core Specification`_, Volume 6, Part F, Chapter 3. +The :file:`dtm_hci.c` and :file:`hci_uart.c` files are an implementation of the HCI UART interface specified in the `Bluetooth Core Specification`_, Volume 4, Part A (the flow control can be configured by an overlay file). + The default selection of UART pins is defined in :file:`zephyr/boards/arm/board_name/board_name.dts`. You can change the defaults using the symbols ``tx-pin`` and ``rx-pin`` in the DTS overlay file of the child image at the project level. The overlay files for the :ref:`nrf5340_remote_shell` child image are located in the :file:`child_image/remote_shell` directory. +The HCI interface allows for a custom ``remote_hci`` image to be used with |nRF5340DKnoref|. .. note:: On the nRF5340 development kit, the physical UART interface of the application core is used for communication with the tester device. @@ -362,6 +368,21 @@ Building and running The Remote IPC shell sample is built and programmed automatically by default. If you want to program your custom solution for the application core, unset the :kconfig:option:`CONFIG_NCS_SAMPLE_REMOTE_SHELL_CHILD_IMAGE` Kconfig option. +Experimental HCI interface +========================== + +To build the sample with an HCI interface, use the following command: + +.. code-block:: console + + west build samples/bluetooth/direct_test_mode -b board_name -- --DEXTRA_CONF_FILE=overlay-hci.conf + +On the |nRF5340DKnoref| the sample with HCI interface can also be built with the `remote_hci` image using the following command: + +.. code-block:: console + + west build samples/bluetooth/direct_test_mode -b board_name -- --DEXTRA_CONF_FILE=overlay-hci-nrf53.conf + USB CDC ACM transport variant ============================= @@ -455,7 +476,7 @@ The Bluetooth Low Energy DTM UART interface standard specifies the following con .. note:: The default bit rate of the DTM UART driver is 19200 bps, which is supported by most certified testers. -You must send all commands as two-byte HEX numbers. +When using a 2-wire interface, you must send all commands as two-byte HEX numbers. The responses must have the same format. Connect with RealTerm (Windows) diff --git a/samples/bluetooth/direct_test_mode/boards/nrf5340dk_nrf5340_cpunet.overlay b/samples/bluetooth/direct_test_mode/boards/nrf5340dk_nrf5340_cpunet.overlay index 51db31d9c39..373e538c5fd 100644 --- a/samples/bluetooth/direct_test_mode/boards/nrf5340dk_nrf5340_cpunet.overlay +++ b/samples/bluetooth/direct_test_mode/boards/nrf5340dk_nrf5340_cpunet.overlay @@ -1,16 +1,8 @@ -/ { - chosen { - ncs,dtm-uart = &uart0; - }; -}; - -&uart0 { - status = "okay"; - compatible = "nordic,nrf-ipc-uart"; - ipc = <&ipc0>; - ept-name = "remote shell"; - current-speed = <19200>; -}; +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ &radio { status = "okay"; diff --git a/samples/bluetooth/direct_test_mode/conf/remote_shell_nrf53.overlay b/samples/bluetooth/direct_test_mode/conf/remote_shell_nrf53.overlay new file mode 100644 index 00000000000..05eb2d5d9fe --- /dev/null +++ b/samples/bluetooth/direct_test_mode/conf/remote_shell_nrf53.overlay @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + ncs,dtm-uart = &uart0; + }; +}; + +&uart0 { + status = "okay"; + compatible = "nordic,nrf-ipc-uart"; + ipc = <&ipc0>; + ept-name = "remote shell"; + current-speed = <19200>; +}; diff --git a/samples/bluetooth/direct_test_mode/overlay-hci-nrf53.conf b/samples/bluetooth/direct_test_mode/overlay-hci-nrf53.conf new file mode 100644 index 00000000000..3dda4d3c8e8 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/overlay-hci-nrf53.conf @@ -0,0 +1,15 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_NCS_SAMPLE_REMOTE_SHELL_CHILD_IMAGE=n +CONFIG_IPC_UART=n + +CONFIG_NCS_SAMPLE_DTM_REMOTE_HCI_CHILD_IMAGE=y +CONFIG_NRF_RPC=y +CONFIG_NRF_RPC_CBOR=y + +CONFIG_DTM_TRANSPORT_HCI=y +CONFIG_NET_BUF=y diff --git a/samples/bluetooth/direct_test_mode/overlay-hci.conf b/samples/bluetooth/direct_test_mode/overlay-hci.conf new file mode 100644 index 00000000000..3c07dad787f --- /dev/null +++ b/samples/bluetooth/direct_test_mode/overlay-hci.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_DTM_TRANSPORT_HCI=y +CONFIG_NET_BUF=y +CONFIG_UART_ASYNC_API=y diff --git a/samples/bluetooth/direct_test_mode/remote_hci/CMakeLists.txt b/samples/bluetooth/direct_test_mode/remote_hci/CMakeLists.txt new file mode 100644 index 00000000000..26988ce7cdf --- /dev/null +++ b/samples/bluetooth/direct_test_mode/remote_hci/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(remote_hci) + +target_include_directories(app PRIVATE ./../rpc) +target_include_directories(app PRIVATE ./../src/transport) + +# NORDIC SDK APP START +target_sources(app PRIVATE + src/main.c + ../src/transport/hci_uart.c +) +# NORDIC SDK APP END diff --git a/samples/bluetooth/direct_test_mode/remote_hci/Kconfig b/samples/bluetooth/direct_test_mode/remote_hci/Kconfig new file mode 100644 index 00000000000..f4e870470b3 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/remote_hci/Kconfig @@ -0,0 +1,53 @@ +# +# Copyright (c) 2023 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +mainmenu "Nordic Remote HCI DTM" + +config DTM_REMOTE_HCI_CHILD + bool + default y + +config DTM_PUT_THREAD_STACK_SIZE + int "dtm_put thread stack size" + default 2048 + help + Stack size of the dtm_put thread. + +config DTM_PUT_THREAD_PRIORITY + int "dtm_put thread priority" + default 7 + help + Priority of the dtm_put thread. + +config REMOTE_HCI_QUEUE_COUNT + int "Count of HCI RX/TX queues" + default 16 + help + Maximum depth of the HCI RX/TX queues. + +config REMOTE_HCI_QUEUE_SIZE + int "Size of HCI RX/TX queue buffer" + default 1024 + help + Maximum size of the HCI RX/TX queue element. + +config REMOTE_HCI_TX_THREAD_STACK_SIZE + int "Stack size of TX thread" + default 2048 + help + Stack size of the TX thread. + +config REMOTE_HCI_TX_THREAD_PRIORITY + int "TX thread priority" + default 7 + help + Priority of the TX thread. + +module = DTM_REMOTE_HCI +module-str = "DTM_remote_hci" +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +source "Kconfig.zephyr" diff --git a/samples/bluetooth/direct_test_mode/remote_hci/boards/nrf5340dk_nrf5340_cpuapp.overlay b/samples/bluetooth/direct_test_mode/remote_hci/boards/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000..22f5975d377 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/remote_hci/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + ncs,dtm-uart = &uart0; + }; +}; + +&uart0 { + status = "okay"; + current-speed = <115200>; +}; + +&gpio_fwd { + dfe-radio { + gpios = <&gpio0 4 0>, + <&gpio0 5 0>, + <&gpio0 6 0>, + <&gpio0 7 0>; + }; +}; diff --git a/samples/bluetooth/direct_test_mode/remote_hci/prj.conf b/samples/bluetooth/direct_test_mode/remote_hci/prj.conf new file mode 100644 index 00000000000..68f4ffb3e17 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/remote_hci/prj.conf @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_ASSERT=y +CONFIG_ASSERT_NO_COND_INFO=y +CONFIG_ASSERT_NO_MSG_INFO=y + +CONFIG_HW_STACK_PROTECTION=y + +CONFIG_CONSOLE=n +CONFIG_UART_CONSOLE=n +CONFIG_LOG=y +CONFIG_LOG_PRINTK=y +CONFIG_USE_SEGGER_RTT=y +CONFIG_LOG_BACKEND_RTT=y +CONFIG_UART_ASYNC_API=y + +CONFIG_NRF_RPC=y +CONFIG_NRF_RPC_CBOR=y +CONFIG_IDLE_STACK_SIZE=2048 + +CONFIG_NET_BUF=y + +CONFIG_BOARD_ENABLE_CPUNET=y diff --git a/samples/bluetooth/direct_test_mode/remote_hci/src/main.c b/samples/bluetooth/direct_test_mode/remote_hci/src/main.c new file mode 100644 index 00000000000..2503255e402 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/remote_hci/src/main.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" +#include "dtm_serialization.h" + +LOG_MODULE_REGISTER(serialization_layer, CONFIG_DTM_REMOTE_HCI_LOG_LEVEL); + +NRF_RPC_IPC_TRANSPORT(hci_group_tr, DEVICE_DT_GET(DT_NODELABEL(ipc0)), "dtm_ept"); +NRF_RPC_GROUP_DEFINE(hci_group, "hci_remote", &hci_group_tr, NULL, NULL, NULL); + +static K_FIFO_DEFINE(dtm_put_queue); + +static void dtm_hci_put_wrapper(struct net_buf *buf); + +static void rsp_error_code_send(const struct nrf_rpc_group *group, int err_code) +{ + struct nrf_rpc_cbor_ctx ctx; + size_t buffer_size_max = 5; + + NRF_RPC_CBOR_ALLOC(group, ctx, buffer_size_max); + + if (!zcbor_int32_put(ctx.zs, err_code)) { + goto error_exit; + } + + nrf_rpc_cbor_rsp_no_err(group, &ctx); + + return; + +error_exit: + __ASSERT_NO_MSG(false); +} + +/* Outgoing event dtm_hci_put to network core (DTM). */ +static void dtm_hci_put_remote(struct net_buf *buf) +{ + struct nrf_rpc_cbor_ctx ctx; + size_t buffer_size_max = 10; + + LOG_DBG("Call to dtm_hci_put"); + + buffer_size_max += buf->len; + + NRF_RPC_CBOR_ALLOC(&hci_group, ctx, buffer_size_max); + + if (!zcbor_uint_encode(ctx.zs, &buf->user_data[0], sizeof(buf->user_data[0]))) { + goto error_exit; + } + + if (!zcbor_bstr_encode_ptr(ctx.zs, buf->data, buf->len)) { + goto error_exit; + } + + nrf_rpc_cbor_evt(&hci_group, RPC_DTM_HCI_PUT_EVT, &ctx); + + return; + +error_exit: + __ASSERT_NO_MSG(false); +} + +/* Incoming hci_uart_write command from network core (DTM) */ +static void hci_uart_write_handler(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, + void *handler_data) +{ + int err; + uint8_t type; + struct zcbor_string hdr; + struct zcbor_string pld; + + LOG_DBG("Call from hci_uart_write"); + + if (!zcbor_uint_decode(ctx->zs, &type, sizeof(type))) { + goto error_exit; + } + + if (!zcbor_bstr_decode(ctx->zs, &hdr)) { + goto error_exit; + } + + if (!zcbor_bstr_decode(ctx->zs, &pld)) { + goto error_exit; + } + + err = hci_uart_write(type, hdr.value, hdr.len, pld.value, pld.len); + nrf_rpc_cbor_decoding_done(group, ctx); + rsp_error_code_send(group, err); + + return; + +error_exit: + __ASSERT_NO_MSG(false); +} + +NRF_RPC_CBOR_CMD_DECODER(hci_group, hci_uart_write, RPC_HCI_UART_WRITE_CMD, + hci_uart_write_handler, NULL); + +/* Incoming command hci_uart_init from network core (DTM). */ +static void hci_uart_init_handler(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, + void *handler_data) +{ + int err; + + LOG_DBG("Call from hci_uart_init"); + nrf_rpc_cbor_decoding_done(group, ctx); + + err = hci_uart_init(dtm_hci_put_wrapper); + rsp_error_code_send(group, err); +} + +NRF_RPC_CBOR_CMD_DECODER(hci_group, hci_uart_init, RPC_HCI_UART_INIT_CMD, + hci_uart_init_handler, NULL); + +static void dtm_hci_put_wrapper(struct net_buf *buf) +{ + net_buf_put(&dtm_put_queue, buf); +} + +static void dtm_put_thread(void) +{ + struct net_buf *buf; + + for (;;) { + buf = net_buf_get(&dtm_put_queue, K_FOREVER); + + __ASSERT_NO_MSG(buf != NULL); + + dtm_hci_put_remote(buf); + net_buf_unref(buf); + } +} + +K_THREAD_DEFINE(dtm_put_thread_id, CONFIG_DTM_PUT_THREAD_STACK_SIZE, dtm_put_thread, + NULL, NULL, NULL, + CONFIG_DTM_PUT_THREAD_PRIORITY, 0, 0); + +static void err_handler(const struct nrf_rpc_err_report *report) +{ + LOG_ERR("nRF RPC error %d ocurred. See nRF RPC logs for more details.", report->code); + k_oops(); +} + +int main(void) +{ + int err; + + LOG_INF("RPC init begin"); + + err = nrf_rpc_init(err_handler); + if (err) { + LOG_ERR("nrf_rpc_init failed: %d", err); + return -EIO; + } + + LOG_INF("RPC init done"); + + return 0; +} diff --git a/samples/bluetooth/direct_test_mode/rpc/dtm_serialization.h b/samples/bluetooth/direct_test_mode/rpc/dtm_serialization.h new file mode 100644 index 00000000000..48d23452a75 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/rpc/dtm_serialization.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DTM_SETIALIZATION_H_ +#define DTM_SETIALIZATION_H_ + +#define RPC_HCI_UART_INIT_CMD 0 +#define RPC_HCI_UART_WRITE_CMD 1 +#define RPC_DTM_HCI_PUT_EVT 2 + +#endif /* DTM_SETIALIZATION_H_ */ diff --git a/samples/bluetooth/direct_test_mode/src/dtm.c b/samples/bluetooth/direct_test_mode/src/dtm.c index e2f1b898b3f..735f0489696 100644 --- a/samples/bluetooth/direct_test_mode/src/dtm.c +++ b/samples/bluetooth/direct_test_mode/src/dtm.c @@ -78,6 +78,9 @@ BUILD_ASSERT(NRFX_TIMER_CONFIG_LABEL(ANOMALY_172_TIMER_INSTANCE) == 1, #define RFPHY_TEST_0X0F_REF_PATTERN 0x0F #define RFPHY_TEST_0X55_REF_PATTERN 0x55 #define RFPHY_TEST_0XFF_REF_PATTERN 0xFF +#define RFPHY_TEST_0X00_REF_PATTERN 0x00 +#define RFPHY_TEST_0XF0_REF_PATTERN 0xF0 +#define RFPHY_TEST_0XAA_REF_PATTERN 0xAA /* Time between start of TX packets (in us). */ #define TX_INTERVAL 625 @@ -135,7 +138,7 @@ BUILD_ASSERT(NRFX_TIMER_CONFIG_LABEL(ANOMALY_172_TIMER_INSTANCE) == 1, /* Base address length in bytes. */ #define PACKET_BA_LEN 3 /* CTE IQ sample data size. */ -#define DTM_CTE_SAMPLE_DATA_SIZE 128 +#define DTM_CTE_SAMPLE_DATA_SIZE 0x52 /* Vendor specific packet type for internal use. */ #define DTM_PKT_TYPE_VENDORSPECIFIC 0xFE /* 1111111 bit pattern packet type for internal use. */ @@ -233,8 +236,20 @@ enum dtm_pdu_type { /* 10101010 bit pattern (LSB is the leftmost bit). */ DTM_PDU_TYPE_0X55 = 0x02, - /* 11111111 bit pattern (Used only for coded PHY). */ + /* PRBS15 bit pattern */ + DTM_PDU_TYPE_PRBS15 = 0x03, + + /* 11111111 bit pattern */ DTM_PDU_TYPE_0XFF = 0x04, + + /* 00000000 bit pattern */ + DTM_PDU_TYPE_0X00 = 0x05, + + /* 00001111 bit pattern (LSB is the leftmost bit). */ + DTM_PDU_TYPE_0XF0 = 0x06, + + /* 01010101 bit pattern (LSB is the leftmost bit). */ + DTM_PDU_TYPE_0XAA = 0x07 }; /* Vendor Specific DTM subcommand for Transmitter Test command. @@ -298,6 +313,9 @@ struct dtm_cte_info { /* CTEInfo. */ uint8_t info; + + /* IQ Report callback */ + dtm_iq_report_callback_t iq_rep_cb; }; struct fem_parameters { @@ -389,7 +407,7 @@ static struct dtm_instance { * in the array is reverse of that found by running the PRBS9 algorithm. * This is because of the endianness of the nRF5 radio. */ -static uint8_t const dtm_prbs_content[] = { +static uint8_t const dtm_prbs9_content[] = { 0xFF, 0xC1, 0xFB, 0xE8, 0x4C, 0x90, 0x72, 0x8B, 0xE7, 0xB3, 0x51, 0x89, 0x63, 0xAB, 0x23, 0x23, 0x02, 0x84, 0x18, 0x72, 0xAA, 0x61, 0x2F, 0x3B, @@ -424,6 +442,41 @@ static uint8_t const dtm_prbs_content[] = { 0x8A, 0x84, 0x39, 0xF4, 0x36, 0x0B, 0xF7 }; +static uint8_t const dtm_prbs15_content[] = { + 0xFF, 0x7F, 0x00, 0x20, 0x00, 0x18, 0x00, 0x0A, + 0x80, 0x07, 0x20, 0x02, 0x98, 0x01, 0xAA, 0x80, + 0x7F, 0x20, 0x20, 0x18, 0x18, 0x0A, 0x8A, 0x87, + 0x27, 0x22, 0x9A, 0x99, 0xAB, 0x2A, 0xFF, 0x5F, + 0x00, 0x38, 0x00, 0x12, 0x80, 0x0D, 0xA0, 0x05, + 0xB8, 0x03, 0x32, 0x81, 0xD5, 0xA0, 0x5F, 0x38, + 0x38, 0x12, 0x92, 0x8D, 0xAD, 0xA5, 0xBD, 0xBB, + 0x31, 0xB3, 0x54, 0x75, 0xFF, 0x67, 0x00, 0x2A, + 0x80, 0x1F, 0x20, 0x08, 0x18, 0x06, 0x8A, 0x82, + 0xE7, 0x21, 0x8A, 0x98, 0x67, 0x2A, 0xAA, 0x9F, + 0x3F, 0x28, 0x10, 0x1E, 0x8C, 0x08, 0x65, 0xC6, + 0xAB, 0x12, 0xFF, 0x4D, 0x80, 0x35, 0xA0, 0x17, + 0x38, 0x0E, 0x92, 0x84, 0x6D, 0xA3, 0x6D, 0xB9, + 0xED, 0xB2, 0xCD, 0xB5, 0x95, 0xB7, 0x2F, 0x36, + 0x9C, 0x16, 0xE9, 0xCE, 0xCE, 0xD4, 0x54, 0x5F, + 0x7F, 0x78, 0x20, 0x22, 0x98, 0x19, 0xAA, 0x8A, + 0xFF, 0x27, 0x00, 0x1A, 0x80, 0x0B, 0x20, 0x07, + 0x58, 0x02, 0xBA, 0x81, 0xB3, 0x20, 0x75, 0xD8, + 0x27, 0x1A, 0x9A, 0x8B, 0x2B, 0x27, 0x5F, 0x5A, + 0xB8, 0x3B, 0x32, 0x93, 0x55, 0xAD, 0xFF, 0x3D, + 0x80, 0x11, 0xA0, 0x0C, 0x78, 0x05, 0xE2, 0x83, + 0x09, 0xA1, 0xC6, 0xF8, 0x52, 0xC2, 0xBD, 0x91, + 0xB1, 0xAC, 0x74, 0x7D, 0xE7, 0x61, 0x8A, 0xA8, + 0x67, 0x3E, 0xAA, 0x90, 0x7F, 0x2C, 0x20, 0x1D, + 0xD8, 0x09, 0x9A, 0x86, 0xEB, 0x22, 0xCF, 0x59, + 0x94, 0x3A, 0xEF, 0x53, 0x0C, 0x3D, 0xC5, 0xD1, + 0x93, 0x1C, 0x6D, 0xC9, 0xED, 0x96, 0xCD, 0xAE, + 0xD5, 0xBC, 0x5F, 0x31, 0xF8, 0x14, 0x42, 0x8F, + 0x71, 0xA4, 0x24, 0x7B, 0x5B, 0x63, 0x7B, 0x69, + 0xE3, 0x6E, 0xC9, 0xEC, 0x56, 0xCD, 0xFE, 0xD5, + 0x80, 0x5F, 0x20, 0x38, 0x18, 0x12, 0x8A, 0x8D, + 0xA7, 0x25, 0xBA, 0x9B, 0x33, 0x2B, 0x55 +}; + static const struct dtm_supp_features supported_features = { .data_len_ext = true, .phy_2m = true, @@ -833,7 +886,7 @@ static int radio_init(void) return 0; } -int dtm_init(void) +int dtm_init(dtm_iq_report_callback_t callback) { int err; @@ -890,10 +943,57 @@ int dtm_init(void) dtm_inst.state = STATE_IDLE; dtm_inst.packet_len = 0; + dtm_inst.cte_info.iq_rep_cb = callback; return 0; } +#if DIRECTION_FINDING_SUPPORTED +static void report_iq(void) +{ + struct dtm_iq_data iq_data; + + iq_data.channel = dtm_inst.phys_ch; + iq_data.rssi = -nrf_radio_rssi_sample_get(NRF_RADIO); + + nrf_radio_event_clear(NRF_RADIO, NRF_RADIO_EVENT_RSSIEND); + + iq_data.rssi_ant = dtm_hw_radio_pdu_antenna_get(); + + if (dtm_inst.cte_info.mode == DTM_CTE_MODE_AOD) { + if (dtm_inst.cte_info.slot == DTM_CTE_SLOT_1US) { + iq_data.type = DTM_CTE_TYPE_AOD_1US; + } else if (dtm_inst.cte_info.slot == DTM_CTE_SLOT_2US) { + iq_data.type = DTM_CTE_TYPE_AOD_2US; + } else { + /* Not possible - invalid value */ + __ASSERT_NO_MSG(false); + } + } else if (dtm_inst.cte_info.mode == DTM_CTE_MODE_AOA) { + iq_data.type = DTM_CTE_TYPE_AOA; + } else { + /* Not possible - invalid value */ + __ASSERT_NO_MSG(false); + } + + if (dtm_inst.cte_info.slot == DTM_CTE_SLOT_1US) { + iq_data.slot = DTM_CTE_SLOT_DURATION_1US; + } else if (dtm_inst.cte_info.slot == DTM_CTE_SLOT_2US) { + iq_data.slot = DTM_CTE_SLOT_DURATION_2US; + } else { + /* Not possible - invalid value */ + __ASSERT_NO_MSG(false); + } + + /* There is no requirement to report iq samples with invalid CRC */ + iq_data.status = DTM_PACKET_STATUS_CRC_OK; + iq_data.sample_cnt = nrf_radio_dfe_amount_get(NRF_RADIO); + iq_data.samples = (struct dtm_iq_sample *)dtm_inst.cte_info.data; + + dtm_inst.cte_info.iq_rep_cb(&iq_data); +} +#endif /* DIRECTION_FINDING_SUPPORTED */ + /* Function for verifying that a received PDU has the expected structure and * content. */ @@ -905,6 +1005,7 @@ static bool check_pdu(const struct dtm_pdu *pdu) uint32_t pdu_packet_type; uint32_t length = 0; uint8_t header_len; + const uint8_t *payload; pdu_packet_type = (uint32_t) (pdu->content[DTM_HEADER_OFFSET] & 0x0F); @@ -913,6 +1014,8 @@ static bool check_pdu(const struct dtm_pdu *pdu) header_len = (dtm_inst.cte_info.mode != DTM_CTE_MODE_OFF) ? DTM_HEADER_WITH_CTE_SIZE : DTM_HEADER_SIZE; + payload = pdu->content + header_len; + /* Check that the length is valid. */ if (length > DTM_PAYLOAD_MAX_SIZE) { return false; @@ -935,25 +1038,37 @@ static bool check_pdu(const struct dtm_pdu *pdu) return false; } - if (pdu_packet_type == DTM_PDU_TYPE_PRBS9) { - /* Payload does not consist of one repeated octet; must - * compare it with entire block. - */ - const uint8_t *payload = pdu->content + header_len; - - return (memcmp(payload, dtm_prbs_content, length) == 0); - } - switch (pdu_packet_type) { + case DTM_PDU_TYPE_PRBS9: + return (memcmp(payload, dtm_prbs9_content, length) == 0); + case DTM_PDU_TYPE_0X0F: pattern = RFPHY_TEST_0X0F_REF_PATTERN; break; + case DTM_PDU_TYPE_0X55: pattern = RFPHY_TEST_0X55_REF_PATTERN; break; + + case DTM_PDU_TYPE_PRBS15: + return (memcmp(payload, dtm_prbs15_content, length) == 0); + case DTM_PDU_TYPE_0XFF: pattern = RFPHY_TEST_0XFF_REF_PATTERN; break; + + case DTM_PDU_TYPE_0X00: + pattern = RFPHY_TEST_0X00_REF_PATTERN; + break; + + case DTM_PDU_TYPE_0XF0: + pattern = RFPHY_TEST_0XF0_REF_PATTERN; + break; + + case DTM_PDU_TYPE_0XAA: + pattern = RFPHY_TEST_0XAA_REF_PATTERN; + break; + default: /* No valid packet type set. */ return false; @@ -981,6 +1096,10 @@ static bool check_pdu(const struct dtm_pdu *pdu) ((dtm_inst.cte_info.slot == DTM_CTE_SLOT_1US) ? 2 : 4); cte_sample_cnt = NRF_RADIO->DFEPACKET.AMOUNT; + if (dtm_inst.cte_info.iq_rep_cb) { + report_iq(); + } + memset(dtm_inst.cte_info.data, 0, sizeof(dtm_inst.cte_info.data)); @@ -1216,6 +1335,8 @@ static void radio_prepare(bool rx) #if DIRECTION_FINDING_SUPPORTED nrf_radio_shorts_set(NRF_RADIO, NRF_RADIO_SHORT_READY_START_MASK | + (dtm_inst.cte_info.iq_rep_cb ? + NRF_RADIO_SHORT_ADDRESS_RSSISTART_MASK : 0) | (dtm_inst.cte_info.mode == DTM_CTE_MODE_OFF ? NRF_RADIO_SHORT_END_DISABLE_MASK : NRF_RADIO_SHORT_PHYEND_DISABLE_MASK)); @@ -1241,6 +1362,7 @@ static void radio_prepare(bool rx) nrf_radio_int_enable(NRF_RADIO, NRF_RADIO_INT_READY_MASK | NRF_RADIO_INT_ADDRESS_MASK | + NRF_RADIO_INT_RSSIEND_MASK | NRF_RADIO_INT_END_MASK); if (rx) { @@ -1863,7 +1985,7 @@ int dtm_test_transmit(uint8_t channel, uint8_t length, enum dtm_packet pkt) DTM_PDU_TYPE_PRBS9; /* Non-repeated, must copy entire pattern to PDU */ memcpy(dtm_inst.current_pdu->content + header_len, - dtm_prbs_content, dtm_inst.packet_len); + dtm_prbs9_content, dtm_inst.packet_len); break; case DTM_PACKET_0F: @@ -1884,17 +2006,51 @@ int dtm_test_transmit(uint8_t channel, uint8_t length, enum dtm_packet pkt) dtm_inst.packet_len); break; + case DTM_PACKET_PRBS15: + dtm_inst.current_pdu->content[DTM_HEADER_OFFSET] = + DTM_PDU_TYPE_PRBS15; + /* Non-repeated, must copy entire pattern to PDU */ + memcpy(dtm_inst.current_pdu->content + header_len, + dtm_prbs15_content, dtm_inst.packet_len); + break; + break; + case DTM_PACKET_FF: dtm_inst.current_pdu->content[DTM_HEADER_OFFSET] = DTM_PDU_TYPE_0XFF; - /* Bit pattern 11111111 repeated. Only available in - * coded PHY (Long range). - */ + /* Bit pattern 11111111 repeated. */ memset(dtm_inst.current_pdu->content + header_len, RFPHY_TEST_0XFF_REF_PATTERN, dtm_inst.packet_len); break; + case DTM_PACKET_00: + dtm_inst.current_pdu->content[DTM_HEADER_OFFSET] = + DTM_PDU_TYPE_0X00; + /* Bit pattern 00000000 repeated */ + memset(dtm_inst.current_pdu->content + header_len, + RFPHY_TEST_0X00_REF_PATTERN, + dtm_inst.packet_len); + break; + + case DTM_PACKET_F0: + dtm_inst.current_pdu->content[DTM_HEADER_OFFSET] = + DTM_PDU_TYPE_0XF0; + /* Bit pattern 11110000 repeated */ + memset(dtm_inst.current_pdu->content + header_len, + RFPHY_TEST_0XF0_REF_PATTERN, + dtm_inst.packet_len); + break; + + case DTM_PACKET_AA: + dtm_inst.current_pdu->content[DTM_HEADER_OFFSET] = + DTM_PDU_TYPE_0XAA; + /* Bit pattern 10101010 repeated */ + memset(dtm_inst.current_pdu->content + header_len, + RFPHY_TEST_0XAA_REF_PATTERN, + dtm_inst.packet_len); + break; + case DTM_PACKET_VENDOR: /* The length field is for indicating the vendor * specific command to execute. The channel field @@ -1955,11 +2111,6 @@ int dtm_test_transmit(uint8_t channel, uint8_t length, enum dtm_packet pkt) int dtm_test_end(uint16_t *pack_cnt) { - if (dtm_inst.state == STATE_IDLE) { - /* Sequencing error, only rx or tx test may be ended */ - return -EIO; - } - if (!pack_cnt) { return -EINVAL; } @@ -2064,6 +2215,10 @@ static void radio_handler(const void *context) } #endif /* NRF52_ERRATA_172_PRESENT */ } + + if (nrf_radio_event_check(NRF_RADIO, NRF_RADIO_EVENT_RSSIEND)) { + nrf_radio_event_clear(NRF_RADIO, NRF_RADIO_EVENT_RSSIEND); + } } static void dtm_timer_handler(nrf_timer_event_t event_type, void *context) diff --git a/samples/bluetooth/direct_test_mode/src/dtm.h b/samples/bluetooth/direct_test_mode/src/dtm.h index e624f58e9d2..258fb39fa4c 100644 --- a/samples/bluetooth/direct_test_mode/src/dtm.h +++ b/samples/bluetooth/direct_test_mode/src/dtm.h @@ -15,7 +15,9 @@ extern "C" { #endif -/** @brief DTM PHY mode. */ +#define NRF_IQ_SAMPLE_INVALID -32768 + +/** @brief DTM PHY mode */ enum dtm_phy { /** Bluetooth Low Energy 1 Mbps PHY. */ DTM_PHY_1M, @@ -104,13 +106,25 @@ enum dtm_packet { /** Packet with 0x55 bytes as payload. */ DTM_PACKET_55, + /** Packet filled with PRBS15 stream as payload. */ + DTM_PACKET_PRBS15, + /** Packet with 0xFF bytes as payload or vendor specific packet. */ DTM_PACKET_FF_OR_VENDOR, /** Packet with 0xFF bytes as payload. */ DTM_PACKET_FF, - /** Vendor specific packet. */ + /** Packet with 0x00 bytes as payload. */ + DTM_PACKET_00, + + /** Packet with 0xF0 bytes as payload. */ + DTM_PACKET_F0, + + /** Packet with 0xAA bytes as payload. */ + DTM_PACKET_AA, + + /** Vendor-specific packet. */ DTM_PACKET_VENDOR }; @@ -156,11 +170,81 @@ struct dtm_tx_power { bool max; }; -/** @brief Function for initializing DTM module. +/** @brief DTM Packet status for IQ Sample report. */ +enum dtm_packet_status { + /** Packet received with proper CRC. */ + DTM_PACKET_STATUS_CRC_OK, + + /** Packet received with invalid CRC. + * The Length and CTEInfo was used to calculate sampling points. + */ + DTM_PACKET_STATUS_CRC_ERR_TIME, + + /** Packet received with invalid CRC. + * The sampling points were calculated in another way. + */ + DTM_PACKET_STATUS_CRC_ERR_OTHER, + + /** Packet received with invalid CRC. + * Insufficient resources to sample. + */ + DTM_PACKET_STATUS_CRC_ERR_INSUFFICIENT +}; + +/** @brief IQ sample format. */ +struct dtm_iq_sample { + /** I sample value. */ + int16_t i; + + /** Q sample value. */ + int16_t q; +}; + +/** @brief DTM IQ sampling data with additional information. */ +struct dtm_iq_data { + /** Channel number. */ + uint8_t channel; + + /** RSSI value of received packet. */ + int16_t rssi; + + /** Antenna number used to measure RSSI. */ + uint8_t rssi_ant; + + /** CTE type. */ + enum dtm_cte_type type; + + /** CTE slot duration. */ + enum dtm_cte_slot_duration slot; + + /** Packet status. */ + enum dtm_packet_status status; + + /** IQ sample count. */ + uint8_t sample_cnt; + + /** IQ samples. */ + struct dtm_iq_sample *samples; +}; + +/** @brief Callback to report received IQ samples. + * + * @note The callback is used only with direction finding. + * + * @param[in] data Pointer to dtm_iq_data structure. + */ +typedef void (*dtm_iq_report_callback_t)(struct dtm_iq_data *data); + +/** @brief Initialize the DTM module. + * + * This function initializes the DTM module and registers the IQ sampling callback. + * If the callback is not needed, the pointer can be NULL. + * + * @param[in] iq_callback Function pointer to the IQ report callback, can be NULL. * * @return 0 in case of success or negative value in case of error. */ -int dtm_init(void); +int dtm_init(dtm_iq_report_callback_t callback); /** @brief Prepare DTM for setup. * diff --git a/samples/bluetooth/direct_test_mode/src/main.c b/samples/bluetooth/direct_test_mode/src/main.c index 75860cf0fb2..bdecc2f21e9 100644 --- a/samples/bluetooth/direct_test_mode/src/main.c +++ b/samples/bluetooth/direct_test_mode/src/main.c @@ -7,26 +7,26 @@ #include #include -#include "transport/dtm_uart_twowire.h" +#include "transport/dtm_transport.h" int main(void) { int err; - uint16_t cmd; + union dtm_tr_packet cmd; printk("Starting Direct Test Mode example\n"); - err = dtm_uart_twowire_init(); + err = dtm_tr_init(); if (err) { - printk("Error initializing DTM Two Wire Uart transport: %d\n", err); + printk("Error initializing DTM transport: %d\n", err); return err; } for (;;) { - cmd = dtm_uart_twowire_get(); - err = dtm_uart_twowire_process(cmd); + cmd = dtm_tr_get(); + err = dtm_tr_process(cmd); if (err) { - printk("Error processing command(%x): %d\n", cmd, err); + printk("Error processing command: %d\n", err); return err; } } diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_hci.c b/samples/bluetooth/direct_test_mode/src/transport/dtm_hci.c new file mode 100644 index 00000000000..a37f90cf09f --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/dtm_hci.c @@ -0,0 +1,772 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" +#include "dtm_transport.h" + +LOG_MODULE_REGISTER(dtm_hci_tr, CONFIG_DTM_TRANSPORT_LOG_LEVEL); + +#define BT_LE_FEAT_SET(feat, n) (feat[(n) >> 3] |= BIT((n) & 7)) + +#define MAX_ANT_PATTERN_LENGTH 0x4B +#define SYNC_HANDLE_RECEIVER_TEST 0x0FFF + +#define CONNECTIONLESS_IQ_REPORT_MAX_SIZE (sizeof(struct hci_connectionless_iq_report_evt) + \ + (B_HCI_LE_CTE_REPORT_SAMPLE_COUNT_MAX * sizeof(struct bt_hci_le_iq_sample))) + +/* HCI_Command_Complete with status only */ +struct hci_base_cc_evt { + struct bt_hci_evt_cmd_complete evt; + struct bt_hci_evt_cc_status ret; +} __packed; + +/* HCI_Command_Complete for Test End */ +struct hci_test_end_cc_evt { + struct bt_hci_evt_cmd_complete evt; + struct bt_hci_rp_le_test_end ret; +} __packed; + +/* HCI_Command_Complete for Read BD Addr */ +struct hci_read_bd_addr_evt { + struct bt_hci_evt_cmd_complete evt; + struct bt_hci_rp_read_bd_addr ret; +} __packed; + +/* HCI_Command_Complete for Read Local Features */ +struct hci_read_local_feat_evt { + struct bt_hci_evt_cmd_complete evt; + struct bt_hci_rp_le_read_local_features ret; +} __packed; + +/* HCI Connectionless IQ report */ +struct hci_connectionless_iq_report_evt { + struct bt_hci_evt_le_meta_event evt; + struct bt_hci_evt_le_connectionless_iq_report report; +} __packed; + +/* HCI RX Test all versions params */ +union rx_params { + struct bt_hci_cp_le_rx_test v1; + struct bt_hci_cp_le_enh_rx_test v2; + struct bt_hci_cp_le_rx_test_v3 v3; +}; + +/* HCI TX Test all versions params */ +union tx_params { + struct bt_hci_cp_le_tx_test v1; + struct bt_hci_cp_le_enh_tx_test v2; + struct bt_hci_cp_le_tx_test_v3 v3; + struct bt_hci_cp_le_tx_test_v4 v4; +}; + +static K_FIFO_DEFINE(hci_rx_queue); + +static int hci_to_dtm_payload(uint8_t hci_pld, enum dtm_packet *dtm_pld) +{ + if (!dtm_pld) { + return -EINVAL; + } + + switch (hci_pld) { + case BT_HCI_TEST_PKT_PAYLOAD_PRBS9: + *dtm_pld = DTM_PACKET_PRBS9; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_11110000: + *dtm_pld = DTM_PACKET_0F; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_10101010: + *dtm_pld = DTM_PACKET_55; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_PRBS15: + *dtm_pld = DTM_PACKET_PRBS15; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_11111111: + *dtm_pld = DTM_PACKET_FF; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_00000000: + *dtm_pld = DTM_PACKET_00; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_00001111: + *dtm_pld = DTM_PACKET_F0; + break; + + case BT_HCI_TEST_PKT_PAYLOAD_01010101: + *dtm_pld = DTM_PACKET_AA; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int phy_set(uint8_t phy) +{ + switch (phy) { + case BT_HCI_LE_TX_PHY_1M: + return dtm_setup_set_phy(DTM_PHY_1M); + + case BT_HCI_LE_TX_PHY_2M: + return dtm_setup_set_phy(DTM_PHY_2M); + + case BT_HCI_LE_TX_PHY_CODED_S8: + return dtm_setup_set_phy(DTM_PHY_CODED_S8); + + case BT_HCI_LE_TX_PHY_CODED_S2: + return dtm_setup_set_phy(DTM_PHY_CODED_S2); + + default: + return -EINVAL; + } +} + +static int mod_set(uint8_t mod) +{ + switch (mod) { + case BT_HCI_LE_MOD_INDEX_STANDARD: + return dtm_setup_set_modulation(DTM_MODULATION_STANDARD); + + case BT_HCI_LE_MOD_INDEX_STABLE: + return dtm_setup_set_modulation(DTM_MODULATION_STABLE); + + default: + return -EINVAL; + } +} + +static int cte_set(uint8_t cte_len, uint8_t cte_type, + uint8_t pattern_len, uint8_t *pattern) +{ + static uint8_t cur_pattern[MAX_ANT_PATTERN_LENGTH]; + int err; + + /* Check if CTE is used at all */ + if (!cte_len) { + return dtm_setup_set_cte_mode(DTM_CTE_TYPE_NONE, 0); + } + + if (pattern_len > MAX_ANT_PATTERN_LENGTH) { + return -EINVAL; + } + + switch (cte_type) { + case BT_HCI_LE_AOA_CTE: + err = dtm_setup_set_cte_mode(DTM_CTE_TYPE_AOA, cte_len); + break; + + case BT_HCI_LE_AOD_CTE_1US: + err = dtm_setup_set_cte_mode(DTM_CTE_TYPE_AOD_1US, cte_len); + break; + + case BT_HCI_LE_AOD_CTE_2US: + err = dtm_setup_set_cte_mode(DTM_CTE_TYPE_AOD_2US, cte_len); + break; + + default: + err = -EINVAL; + break; + } + + if (err) { + return err; + } + + memcpy(cur_pattern, pattern, pattern_len); + + return dtm_setup_set_antenna_params(0, cur_pattern, pattern_len); +} + +static int tx_power_set(int8_t power, uint8_t channel) +{ + switch (power) { + case BT_HCI_TX_TEST_POWER_MIN_SET: + dtm_setup_set_transmit_power(DTM_TX_POWER_REQUEST_MIN, 0, channel); + break; + + case BT_HCI_TX_TEST_POWER_MAX_SET: + dtm_setup_set_transmit_power(DTM_TX_POWER_REQUEST_MAX, 0, channel); + break; + + case BT_HCI_TX_TEST_POWER_MIN ... BT_HCI_TX_TEST_POWER_MAX: + dtm_setup_set_transmit_power(DTM_TX_POWER_REQUEST_VAL, power, channel); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int base_cc_evt(uint16_t opcode, uint8_t status) +{ + struct hci_base_cc_evt tmp; + struct bt_hci_evt_hdr hdr; + + hdr.evt = BT_HCI_EVT_CMD_COMPLETE; + hdr.len = sizeof(tmp); + + tmp.evt.ncmd = 1; + sys_put_le16(opcode, (uint8_t *)&tmp.evt.opcode); + + tmp.ret.status = status; + + LOG_INF("Responding to opcode %x, with status %d", opcode, status); + return hci_uart_write(H4_TYPE_EVT, (uint8_t *)&hdr, sizeof(hdr), (uint8_t *)&tmp, hdr.len); +} + +static int test_end_cc_evt(uint8_t status, uint16_t cnt) +{ + struct hci_test_end_cc_evt tmp; + struct bt_hci_evt_hdr hdr; + + hdr.evt = BT_HCI_EVT_CMD_COMPLETE; + hdr.len = sizeof(tmp); + + tmp.evt.ncmd = 1; + sys_put_le16(BT_HCI_OP_LE_TEST_END, (uint8_t *)&tmp.evt.opcode); + + tmp.ret.status = status; + sys_put_le16(cnt, (uint8_t *)&tmp.ret.rx_pkt_count); + + LOG_INF("Responding to test end, with status %d and count %d", status, cnt); + return hci_uart_write(H4_TYPE_EVT, (uint8_t *)&hdr, sizeof(hdr), (uint8_t *)&tmp, hdr.len); +} + +static int read_bd_addr_cc_evt(uint8_t status) +{ + struct hci_read_bd_addr_evt tmp; + struct bt_hci_evt_hdr hdr; + bt_addr_t bt_addr_zero = { { 0, 0, 0, 0, 0, 0 } }; + + hdr.evt = BT_HCI_EVT_CMD_COMPLETE; + hdr.len = sizeof(tmp); + + tmp.evt.ncmd = 1; + sys_put_le16(BT_HCI_OP_READ_BD_ADDR, (uint8_t *)&tmp.evt.opcode); + + tmp.ret.status = status; + memcpy(&tmp.ret.bdaddr, &bt_addr_zero, sizeof(tmp.ret.bdaddr)); + + LOG_INF("Responding to address query with status %d", status); + return hci_uart_write(H4_TYPE_EVT, (uint8_t *)&hdr, sizeof(hdr), (uint8_t *)&tmp, hdr.len); +} + +static int read_local_feat_cc_evt(uint8_t status, uint8_t *features) +{ + struct hci_read_local_feat_evt tmp; + struct bt_hci_evt_hdr hdr; + + hdr.evt = BT_HCI_EVT_CMD_COMPLETE; + hdr.len = sizeof(tmp); + + tmp.evt.ncmd = 1; + sys_put_le16(BT_HCI_OP_LE_READ_LOCAL_FEATURES, (uint8_t *)&tmp.evt.opcode); + + tmp.ret.status = status; + memcpy(&tmp.ret.features, features, sizeof(tmp.ret.features)); + + LOG_INF("Responding to features query with status %d", status); + return hci_uart_write(H4_TYPE_EVT, (uint8_t *)&hdr, sizeof(hdr), (uint8_t *)&tmp, hdr.len); +} + +static void iq_report_evt(struct dtm_iq_data *iq_data) +{ + uint8_t buf[CONNECTIONLESS_IQ_REPORT_MAX_SIZE]; + struct hci_connectionless_iq_report_evt *tmp = + (struct hci_connectionless_iq_report_evt *)buf; + struct bt_hci_evt_hdr hdr; + size_t i; + int err; + + hdr.evt = BT_HCI_EVT_LE_META_EVENT; + hdr.len = sizeof(*tmp); + hdr.len += sizeof(struct bt_hci_le_iq_sample) * iq_data->sample_cnt; + + if (hdr.len > CONNECTIONLESS_IQ_REPORT_MAX_SIZE) { + LOG_ERR("Invalid sample count in IQ report callback."); + return; + } + + tmp->evt.subevent = BT_HCI_EVT_LE_CONNECTIONLESS_IQ_REPORT; + + tmp->report.sync_handle = sys_cpu_to_le16(SYNC_HANDLE_RECEIVER_TEST); + tmp->report.chan_idx = iq_data->channel; + tmp->report.rssi = iq_data->rssi; + tmp->report.rssi_ant_id = iq_data->rssi_ant; + + switch (iq_data->type) { + case DTM_CTE_TYPE_AOA: + tmp->report.cte_type = BT_HCI_LE_AOA_CTE; + break; + + case DTM_CTE_TYPE_AOD_1US: + tmp->report.cte_type = BT_HCI_LE_AOD_CTE_1US; + break; + + case DTM_CTE_TYPE_AOD_2US: + tmp->report.cte_type = BT_HCI_LE_AOD_CTE_2US; + break; + + default: + LOG_ERR("Invalid CTE type in IQ report callback."); + return; + } + + switch (iq_data->slot) { + case DTM_CTE_SLOT_DURATION_1US: + tmp->report.slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_1US; + break; + + case DTM_CTE_SLOT_DURATION_2US: + tmp->report.slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US; + break; + + default: + LOG_ERR("Invalid CTE slot duration in IQ report callback."); + return; + } + + switch (iq_data->status) { + case DTM_PACKET_STATUS_CRC_OK: + tmp->report.packet_status = BT_HCI_LE_CTE_CRC_OK; + break; + + case DTM_PACKET_STATUS_CRC_ERR_TIME: + tmp->report.packet_status = BT_HCI_LE_CTE_CRC_ERR_CTE_BASED_TIME; + break; + + case DTM_PACKET_STATUS_CRC_ERR_OTHER: + tmp->report.packet_status = BT_HCI_LE_CTE_CRC_ERR_CTE_BASED_OTHER; + break; + + case DTM_PACKET_STATUS_CRC_ERR_INSUFFICIENT: + tmp->report.packet_status = BT_HCI_LE_CTE_INSUFFICIENT_RESOURCES; + break; + + default: + LOG_ERR("Invalid status in IQ report callback."); + return; + } + + tmp->report.per_evt_counter = 0; + + tmp->report.sample_count = iq_data->sample_cnt; + for (i = 0; i < tmp->report.sample_count; i++) { + if (iq_data->samples[i].i == NRF_IQ_SAMPLE_INVALID) { + tmp->report.sample[i].i = BT_HCI_LE_CTE_REPORT_NO_VALID_SAMPLE; + } else { + tmp->report.sample[i].i = (int8_t)(iq_data->samples[i].i >> 4); + } + + if (iq_data->samples[i].q == NRF_IQ_SAMPLE_INVALID) { + tmp->report.sample[i].q = BT_HCI_LE_CTE_REPORT_NO_VALID_SAMPLE; + } else { + tmp->report.sample[i].q = (int8_t)(iq_data->samples[i].q >> 4); + } + } + + err = hci_uart_write(H4_TYPE_EVT, (uint8_t *)&hdr, sizeof(hdr), (uint8_t *)&buf, hdr.len); + if (err) { + LOG_ERR("Error writing LE Connectionless IQ Report event."); + } +} + +static int hci_reset(void) +{ + int err; + + err = dtm_setup_reset(); + if (err) { + base_cc_evt(BT_HCI_OP_RESET, BT_HCI_ERR_HW_FAILURE); + } + + return base_cc_evt(BT_HCI_OP_RESET, BT_HCI_ERR_SUCCESS); +} + +static int hci_read_bd_addr(void) +{ + return read_bd_addr_cc_evt(BT_HCI_ERR_SUCCESS); +} + +static int hci_read_local_features(void) +{ + uint8_t hci_features[8] = { 0 }; + struct dtm_supp_features features; + + features = dtm_setup_read_features(); + + if (features.data_len_ext) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_DLE); + } + + if (features.phy_2m) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_PHY_2M); + } + + if (features.stable_mod) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_SMI_TX); + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_SMI_RX); + } + + if (features.coded_phy) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_PHY_CODED); + } + + if (features.cte) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_RX_CTE); + } + + if (features.ant_switching) { + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_ANT_SWITCH_TX_AOD); + BT_LE_FEAT_SET(hci_features, BT_LE_FEAT_BIT_ANT_SWITCH_RX_AOA); + } + + return read_local_feat_cc_evt(BT_HCI_ERR_SUCCESS, hci_features); +} + +static int hci_rx_test(uint16_t opcode, const uint8_t *data) +{ + union rx_params *params = (union rx_params *)data; + uint8_t def_pattern[2] = {0, 0}; + int err; + + uint8_t chan; + uint8_t phy = 0x01; + uint8_t mod = 0x00; + uint8_t cte_len = 0x00; + uint8_t cte_type = 0x00; + uint8_t slot_durations = 0x01; + uint8_t pattern_len = 0x02; + uint8_t *pattern = def_pattern; + + switch (opcode) { + case BT_HCI_OP_LE_RX_TEST: + chan = params->v1.rx_ch; + LOG_DBG("RX Test command: v1, chan: %d.", chan); + break; + + case BT_HCI_OP_LE_ENH_RX_TEST: + chan = params->v2.rx_ch; + phy = params->v2.phy; + mod = params->v2.mod_index; + LOG_DBG("RX Test command: v2, chan: %d, phy: %d, mod: %d.", chan, phy, mod); + break; + + case BT_HCI_OP_LE_RX_TEST_V3: + chan = params->v3.rx_ch; + phy = params->v3.phy; + mod = params->v3.mod_index; + cte_len = params->v3.expected_cte_len; + cte_type = params->v3.expected_cte_type; + slot_durations = params->v3.slot_durations; + pattern_len = params->v3.switch_pattern_len; + pattern = params->v3.ant_ids; + LOG_DBG("RX Test command: v3, chan: %d, phy: %d, mod: %d, cte_len: %d," + " cte_type: %d, slot_durations: %d, pattern_len: %d.", + chan, phy, mod, cte_len, cte_type, slot_durations, pattern_len); + break; + + default: + return -EINVAL; + } + + dtm_setup_prepare(); + + err = phy_set(phy); + if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } + + err = mod_set(mod); + if (err == -ENOTSUP) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } else if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + + if ((cte_len != 0) && + (phy != BT_HCI_LE_TX_PHY_CODED_S8) && (phy != BT_HCI_LE_TX_PHY_CODED_S2)) { + return base_cc_evt(opcode, BT_HCI_ERR_CMD_DISALLOWED); + } + + err = cte_set(cte_len, cte_type, pattern_len, pattern); + if (err == -ENOTSUP) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } else if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + + if (cte_len != 0) { + err = dtm_setup_set_cte_slot(slot_durations); + if (err == -ENOTSUP) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } else if (err == -EINVAL) { + return base_cc_evt(opcode, BT_HCI_ERR_INVALID_PARAM); + } else if (err != 0) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + } + + err = dtm_test_receive(chan); + if (err == -EINVAL) { + return base_cc_evt(opcode, BT_HCI_ERR_INVALID_PARAM); + } else if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + + return base_cc_evt(opcode, BT_HCI_ERR_SUCCESS); +} + +static int hci_tx_test(uint16_t opcode, const uint8_t *data) +{ + union tx_params *params = (union tx_params *)data; + uint8_t def_pattern[2] = {0, 0}; + + uint8_t chan; + uint8_t data_len; + uint8_t payload; + uint8_t phy = BT_HCI_LE_TX_PHY_1M; + uint8_t cte_len = 0x00; + uint8_t cte_type = 0x00; + uint8_t pattern_len = 0x02; + uint8_t *pattern = def_pattern; + int8_t power = 0x7F; + + enum dtm_packet pld; + int err; + + switch (opcode) { + case BT_HCI_OP_LE_TX_TEST: + chan = params->v1.tx_ch; + data_len = params->v1.test_data_len; + payload = params->v1.pkt_payload; + LOG_DBG("TX Test command: v1, chan: %d, data_len: %d, payload %d.", + chan, data_len, payload); + break; + + case BT_HCI_OP_LE_ENH_TX_TEST: + chan = params->v2.tx_ch; + data_len = params->v2.test_data_len; + payload = params->v2.pkt_payload; + phy = params->v2.phy; + LOG_DBG("TX Test command: v2, chan: %d, data_len: %d, payload: %d, phy: %d.", + chan, data_len, payload, phy); + break; + + case BT_HCI_OP_LE_TX_TEST_V3: + chan = params->v3.tx_ch; + data_len = params->v3.test_data_len; + payload = params->v3.pkt_payload; + phy = params->v3.phy; + cte_len = params->v3.cte_len; + cte_type = params->v3.cte_type; + pattern_len = params->v3.switch_pattern_len; + pattern = params->v3.ant_ids; + LOG_DBG("TX Test command: v3, chan: %d, data_len: %d, payload: %d, phy: %d," + " cte_len: %d, cte_type: %d, pattern_len: %d.", + chan, data_len, payload, phy, cte_len, cte_type, pattern_len); + break; + + case BT_HCI_OP_LE_TX_TEST_V4: + struct bt_hci_cp_le_tx_test_v4_tx_power *tmp; + + chan = params->v4.tx_ch; + data_len = params->v4.test_data_len; + payload = params->v4.pkt_payload; + phy = params->v4.phy; + cte_len = params->v4.cte_len; + cte_type = params->v4.cte_type; + pattern_len = params->v4.switch_pattern_len; + pattern = params->v4.ant_ids; + tmp = (struct bt_hci_cp_le_tx_test_v4_tx_power *)&(params->v4.ant_ids[pattern_len]); + power = tmp->tx_power; + LOG_DBG("TX Test command: v3, chan: %d, data_len: %d, payload: %d, phy: %d," + " cte_len: %d, cte_type: %d, pattern_len: %d, power: %d.", + chan, data_len, payload, phy, cte_len, cte_type, pattern_len, power); + break; + + default: + return -EINVAL; + } + + dtm_setup_prepare(); + + err = hci_to_dtm_payload(payload, &pld); + if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } + + err = phy_set(phy); + if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } + + if ((cte_len != 0) && + (phy != BT_HCI_LE_TX_PHY_CODED_S8) && (phy != BT_HCI_LE_TX_PHY_CODED_S2)) { + return base_cc_evt(opcode, BT_HCI_ERR_CMD_DISALLOWED); + } + + err = cte_set(cte_len, cte_type, pattern_len, pattern); + if (err == -ENOTSUP) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } else if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + + err = tx_power_set(power, chan); + if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + } + + err = dtm_test_transmit(chan, data_len, pld); + if (err == -EINVAL) { + return base_cc_evt(opcode, BT_HCI_ERR_INVALID_PARAM); + } else if (err) { + return base_cc_evt(opcode, BT_HCI_ERR_HW_FAILURE); + } + + return base_cc_evt(opcode, BT_HCI_ERR_SUCCESS); +} + +static int hci_test_end(void) +{ + uint16_t cnt; + int err; + + err = dtm_test_end(&cnt); + + if (err == -EINVAL) { + return test_end_cc_evt(BT_HCI_ERR_INVALID_PARAM, cnt); + } else if (err) { + return test_end_cc_evt(BT_HCI_ERR_HW_FAILURE, cnt); + } + + return test_end_cc_evt(BT_HCI_ERR_SUCCESS, cnt); +} + +static int hci_cmd(const struct bt_hci_cmd_hdr *hdr, const uint8_t *data) +{ + uint16_t cmd; + + cmd = sys_le16_to_cpu(hdr->opcode); + + LOG_INF("Processing HCI command opcode: 0x%04x", cmd); + + switch (cmd) { + case BT_HCI_OP_RESET: + LOG_INF("Executing HCI reset command."); + return hci_reset(); + + case BT_HCI_OP_READ_BD_ADDR: + LOG_INF("Executing HCI Read BD_ADDR command."); + return hci_read_bd_addr(); + + case BT_HCI_OP_LE_READ_LOCAL_FEATURES: + LOG_INF("Executing HCI LE Read Local Supported Features command."); + return hci_read_local_features(); + + case BT_HCI_OP_LE_RX_TEST: + case BT_HCI_OP_LE_ENH_RX_TEST: + case BT_HCI_OP_LE_RX_TEST_V3: + LOG_INF("Executing HCI LE Receiver Test command."); + return hci_rx_test(cmd, data); + + case BT_HCI_OP_LE_TX_TEST: + case BT_HCI_OP_LE_ENH_TX_TEST: + case BT_HCI_OP_LE_TX_TEST_V3: + case BT_HCI_OP_LE_TX_TEST_V4: + LOG_INF("Executing HCI LE Transmitter Test command."); + return hci_tx_test(cmd, data); + + case BT_HCI_OP_LE_TEST_END: + LOG_INF("Executing HCI LE Test End command."); + return hci_test_end(); + + default: + LOG_ERR("Unknown HCI command opcode: 0x%04x", cmd); + base_cc_evt(cmd, BT_HCI_ERR_UNKNOWN_CMD); + return -ENOTSUP; + } +} + +static void dtm_hci_put(struct net_buf *buf) +{ + net_buf_put(&hci_rx_queue, buf); +} + +int dtm_tr_init(void) +{ + int err; + + err = hci_uart_init(dtm_hci_put); + if (err) { + LOG_ERR("Failed to initialize HCI over UART: %d", err); + return err; + } + + err = dtm_init(iq_report_evt); + if (err) { + LOG_ERR("Failed to initialize DTM: %d", err); + return err; + } + + return 0; +} + +union dtm_tr_packet dtm_tr_get(void) +{ + union dtm_tr_packet tmp; + + tmp.hci = net_buf_get(&hci_rx_queue, K_FOREVER); + return tmp; +} + +int dtm_tr_process(union dtm_tr_packet cmd) +{ + struct net_buf *buf = cmd.hci; + const struct bt_hci_cmd_hdr *hdr; + const uint8_t *data; + uint8_t type; + int err; + + if (!buf) { + LOG_ERR("Command pointer is NULL."); + return -EINVAL; + } + + type = *(uint8_t *)net_buf_user_data(buf); + + switch (type) { + case H4_TYPE_CMD: + hdr = (const struct bt_hci_cmd_hdr *)buf->data; + data = buf->data + sizeof(*hdr); + err = hci_cmd(hdr, data); + net_buf_unref(buf); + return err; + + default: + LOG_ERR("Tried to process unsupported HCI type."); + return -ENOTSUP; + } +} diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_transport.h b/samples/bluetooth/direct_test_mode/src/transport/dtm_transport.h new file mode 100644 index 00000000000..ee348c83d92 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/dtm_transport.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DTM_TRANSPORT_H_ +#define DTM_TRANSPORT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if (CONFIG_DTM_TRANSPORT_HCI || CONFIG_DTM_REMOTE_HCI_CHILD) +#define H4_TYPE_CMD 0x01 +#define H4_TYPE_ACL 0x02 +#define H4_TYPE_EVT 0x04 +#define H4_TYPE_ISO 0x05 +#endif + +/** @brief DTM transport packet. */ +union dtm_tr_packet { + /** HCI packet buffer. */ + struct net_buf *hci; + + /** Two-wire uart 2-octet packet. */ + uint16_t twowire; +}; + +/** @brief Initialize DTM transport layer. + * + * @note This function also initializes the DTM module. + * + * @return 0 in case of success or negative value in case of error. + */ +int dtm_tr_init(void); + +/** @brief Poll for DTM command. + * + * This function polls for DTM command. + * + * @return DTM command. + */ +union dtm_tr_packet dtm_tr_get(void); + +/** @brief Process DTM command and respond. + * + * This function processes the DTM command + * and responds to the tester. + * + * @param[in] cmd DTM command. + * + * @return 0 in case of success or negative value in case of error. + */ +int dtm_tr_process(union dtm_tr_packet cmd); + +#ifdef __cplusplus +} +#endif + +#endif /* DTM_TRANSPORT_H_ */ diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.c b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.c index 80a67a028aa..3b9da97678a 100644 --- a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.c +++ b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.c @@ -8,10 +8,10 @@ #include #include #include -#include #include -#include "dtm_uart_twowire.h" +#include "dtm_uart_wait.h" +#include "dtm_transport.h" LOG_MODULE_REGISTER(dtm_tw_tr, CONFIG_DTM_TRANSPORT_LOG_LEVEL); @@ -67,38 +67,9 @@ LOG_MODULE_REGISTER(dtm_tw_tr, CONFIG_DTM_TRANSPORT_LOG_LEVEL); #define DTM_LE_AOD_1US_RECEPTION BIT(8) #define DTM_LE_AOA_1US_RECEPTION BIT(9) -/* Timer used for measuring UART poll cycle wait time. */ -#define WAIT_TIMER_INSTANCE 1 -#define WAIT_TIMER_IRQ NRFX_CONCAT_3(TIMER, \ - WAIT_TIMER_INSTANCE, \ - _IRQn) -#define WAIT_TIMER_IRQ_HANDLER NRFX_CONCAT_3(nrfx_timer_, \ - WAIT_TIMER_INSTANCE, \ - _irq_handler) - -BUILD_ASSERT(NRFX_CONCAT_3(CONFIG_, NRFX_TIMER, WAIT_TIMER_INSTANCE) == 1, - "Wait DTM timer needs additional KConfig configuration"); - -#define DTM_UART DT_CHOSEN(ncs_dtm_uart) - /* The DTM maximum wait time in milliseconds for the UART command second byte. */ #define DTM_UART_SECOND_BYTE_MAX_DELAY 5 -#if DT_NODE_HAS_PROP(DTM_UART, current_speed) -/* UART Baudrate used to communicate with the DTM library. */ -#define DTM_UART_BAUDRATE DT_PROP(DTM_UART, current_speed) - -/* The UART poll cycle in micro seconds. - * A baud rate of e.g. 19200 bits / second, and 8 data bits, 1 start/stop bit, - * no flow control, give the time to transmit a byte: - * 10 bits * 1/19200 = approx: 520 us. To ensure no loss of bytes, - * the UART should be polled every 260 us. - */ -#define DTM_UART_POLL_CYCLE ((uint32_t) (10 * 1e6 / DTM_UART_BAUDRATE / 2)) -#else -#error "DTM UART node not found" -#endif /* DT_NODE_HAS_PROP(DTM_UART, currrent_speed) */ - static const struct device *dtm_uart = DEVICE_DT_GET(DTM_UART); /* DTM command codes */ @@ -364,12 +335,6 @@ enum dtm_evt { /** Upper bits of packet length */ static uint8_t upper_len; -/* Timer to be used for measuring UART poll cycle wait time. */ -static const nrfx_timer_t wait_timer = NRFX_TIMER_INSTANCE(WAIT_TIMER_INSTANCE); - -/* Semaphore for synchronizing UART poll cycle wait time.*/ -static K_SEM_DEFINE(wait_sem, 0, 1); - static int reset_dtm(uint8_t parameter) { if (parameter > LE_RESET_MAX_RANGE) { @@ -749,53 +714,7 @@ static uint16_t dtm_cmd_put(uint16_t cmd) } } -static void wait_timer_handler(nrf_timer_event_t event_type, void *context) -{ - nrfx_timer_disable(&wait_timer); - nrfx_timer_clear(&wait_timer); - - k_sem_give(&wait_sem); -} - -static int wait_timer_init(void) -{ - nrfx_err_t err; - nrfx_timer_config_t timer_cfg = { - .frequency = NRFX_MHZ_TO_HZ(1), - .mode = NRF_TIMER_MODE_TIMER, - .bit_width = NRF_TIMER_BIT_WIDTH_16, - }; - - err = nrfx_timer_init(&wait_timer, &timer_cfg, wait_timer_handler); - if (err != NRFX_SUCCESS) { - LOG_ERR("nrfx_timer_init failed with: %d", err); - return -EAGAIN; - } - - IRQ_CONNECT(WAIT_TIMER_IRQ, CONFIG_DTM_TIMER_IRQ_PRIORITY, - WAIT_TIMER_IRQ_HANDLER, NULL, 0); - - nrfx_timer_compare(&wait_timer, - NRF_TIMER_CC_CHANNEL0, - nrfx_timer_us_to_ticks(&wait_timer, DTM_UART_POLL_CYCLE), - true); - - return 0; -} - -static void uart_wait(void) -{ - int err; - - nrfx_timer_enable(&wait_timer); - - err = k_sem_take(&wait_sem, K_FOREVER); - if (err) { - LOG_ERR("UART wait error: %d", err); - } -} - -int dtm_uart_twowire_init(void) +int dtm_tr_init(void) { int err; @@ -804,13 +723,13 @@ int dtm_uart_twowire_init(void) return -EIO; } - err = dtm_init(); + err = dtm_init(NULL); if (err) { LOG_ERR("Error during DTM initialization: %d", err); return err; } - err = wait_timer_init(); + err = dtm_uart_wait_init(); if (err) { return err; } @@ -818,16 +737,17 @@ int dtm_uart_twowire_init(void) return 0; } -uint16_t dtm_uart_twowire_get(void) +union dtm_tr_packet dtm_tr_get(void) { bool is_msb_read = false; + union dtm_tr_packet tmp; uint8_t rx_byte; uint16_t dtm_cmd = 0; int64_t msb_time = 0; int err; for (;;) { - uart_wait(); + dtm_uart_wait(); err = uart_poll_in(dtm_uart, &rx_byte); if (err) { @@ -866,18 +786,20 @@ uint16_t dtm_uart_twowire_get(void) } else { dtm_cmd |= rx_byte; LOG_INF("Received 0x%04x command", dtm_cmd); - return dtm_cmd; + tmp.twowire = dtm_cmd; + return tmp; } } } -int dtm_uart_twowire_process(uint16_t cmd) +int dtm_tr_process(union dtm_tr_packet cmd) { + uint16_t tmp = cmd.twowire; uint16_t ret; - LOG_INF("Processing 0x%04x command", cmd); + LOG_INF("Processing 0x%04x command", tmp); - ret = dtm_cmd_put(cmd); + ret = dtm_cmd_put(tmp); LOG_INF("Sending 0x%04x response", ret); uart_poll_out(dtm_uart, (ret >> 8) & 0xFF); diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.h b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.h deleted file mode 100644 index 0ea6ef40820..00000000000 --- a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_twowire.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2023 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -#ifndef DTM_UART_TWOWIRE_H_ -#define DTM_UART_TWOWIRE_H_ - -#include - -/** @brief Initialize DTM over Two Wire UART interface. - * - * @note This function also initializes the DTM module. - * - * @return 0 in case of success or negative value in case of error. - */ -int dtm_uart_twowire_init(void); - -/** @brief Poll for DTM command. - * - * This function polls for Two Wire UART command. - * - * @return 16-bit command. - */ -uint16_t dtm_uart_twowire_get(void); - -/** @brief Process DTM command and respond. - * - * This function processes the DTM command - * and responds to the tester. - * - * @return 0 in case of success or negative value in case of error. - */ -int dtm_uart_twowire_process(uint16_t cmd); - -#endif diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.c b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.c new file mode 100644 index 00000000000..37677098449 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include "dtm_uart_wait.h" + +LOG_MODULE_REGISTER(dtm_wait, CONFIG_DTM_TRANSPORT_LOG_LEVEL); + +/* Timer used for measuring UART poll cycle wait time. */ +#define WAIT_TIMER_INSTANCE 1 +#define WAIT_TIMER_IRQ NRFX_CONCAT_3(TIMER, \ + WAIT_TIMER_INSTANCE, \ + _IRQn) +#define WAIT_TIMER_IRQ_HANDLER NRFX_CONCAT_3(nrfx_timer_, \ + WAIT_TIMER_INSTANCE, \ + _irq_handler) + +BUILD_ASSERT(NRFX_CONCAT_3(CONFIG_, NRFX_TIMER, WAIT_TIMER_INSTANCE) == 1, + "Wait DTM timer needs additional KConfig configuration"); + +#if DT_NODE_HAS_PROP(DTM_UART, current_speed) +/* UART Baudrate used to communicate with the DTM library. */ +#define DTM_UART_BAUDRATE DT_PROP(DTM_UART, current_speed) + +/* The UART poll cycle in micro seconds. + * A baud rate of e.g. 19200 bits / second, and 8 data bits, 1 start/stop bit, + * no flow control, give the time to transmit a byte: + * 10 bits * 1/19200 = approx: 520 us. To ensure no loss of bytes, + * the UART should be polled every 260 us. + */ +#define DTM_UART_POLL_CYCLE ((uint32_t) (10 * 1e6 / DTM_UART_BAUDRATE / 2)) +#else +#error "DTM UART node not found" +#endif /* DT_NODE_HAS_PROP(DTM_UART, currrent_speed) */ + +/* Timer to be used for measuring UART poll cycle wait time. */ +static const nrfx_timer_t wait_timer = NRFX_TIMER_INSTANCE(WAIT_TIMER_INSTANCE); + +/* Semaphore for synchronizing UART poll cycle wait time.*/ +static K_SEM_DEFINE(wait_sem, 0, 1); + +static void wait_timer_handler(nrf_timer_event_t event_type, void *context) +{ + nrfx_timer_disable(&wait_timer); + nrfx_timer_clear(&wait_timer); + + k_sem_give(&wait_sem); +} + +int dtm_uart_wait_init(void) +{ + nrfx_err_t err; + nrfx_timer_config_t timer_cfg = { + .frequency = NRFX_MHZ_TO_HZ(1), + .mode = NRF_TIMER_MODE_TIMER, + .bit_width = NRF_TIMER_BIT_WIDTH_16, + }; + + err = nrfx_timer_init(&wait_timer, &timer_cfg, wait_timer_handler); + if (err != NRFX_SUCCESS) { + LOG_ERR("nrfx_timer_init failed with: %d", err); + return -EAGAIN; + } + + IRQ_CONNECT(WAIT_TIMER_IRQ, CONFIG_DTM_TIMER_IRQ_PRIORITY, + WAIT_TIMER_IRQ_HANDLER, NULL, 0); + + nrfx_timer_compare(&wait_timer, + NRF_TIMER_CC_CHANNEL0, + nrfx_timer_us_to_ticks(&wait_timer, DTM_UART_POLL_CYCLE), + true); + + return 0; +} + +void dtm_uart_wait(void) +{ + int err; + + nrfx_timer_enable(&wait_timer); + + err = k_sem_take(&wait_sem, K_FOREVER); + if (err) { + LOG_ERR("UART wait error: %d", err); + } +} diff --git a/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.h b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.h new file mode 100644 index 00000000000..0d1a445be77 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/dtm_uart_wait.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef DTM_UART_WAIT_H_ +#define DTM_UART_WAIT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define DTM_UART DT_CHOSEN(ncs_dtm_uart) + +/** @brief Initialize wait function. + * + * @return 0 in case of success or negative value in case of error. + */ +int dtm_uart_wait_init(void); + +/** @brief Wait for UART poll cycle. + * + * Wait for half of the UART period used by the DTM. + */ +void dtm_uart_wait(void); + +#ifdef __cplusplus +} +#endif + +#endif /* DTM_UART_WAIT_H_ */ diff --git a/samples/bluetooth/direct_test_mode/src/transport/hci_uart.c b/samples/bluetooth/direct_test_mode/src/transport/hci_uart.c new file mode 100644 index 00000000000..99eaceb2591 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/hci_uart.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "dtm_transport.h" +#include "hci_uart.h" + +#ifndef CONFIG_DTM_TRANSPORT_LOG_LEVEL +#define CONFIG_DTM_TRANSPORT_LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#endif + +#ifdef CONFIG_DTM_TRANSPORT_HCI +#define QUEUE_COUNT CONFIG_DTM_HCI_QUEUE_COUNT +#define QUEUE_SIZE CONFIG_DTM_HCI_QUEUE_SIZE +#define TX_THREAD_STACK_SIZE CONFIG_DTM_HCI_TX_THREAD_STACK_SIZE +#define TX_THREAD_PRIORITY CONFIG_DTM_HCI_TX_THREAD_PRIORITY +#else +#define QUEUE_COUNT CONFIG_REMOTE_HCI_QUEUE_COUNT +#define QUEUE_SIZE CONFIG_REMOTE_HCI_QUEUE_SIZE +#define TX_THREAD_STACK_SIZE CONFIG_REMOTE_HCI_TX_THREAD_STACK_SIZE +#define TX_THREAD_PRIORITY CONFIG_REMOTE_HCI_TX_THREAD_PRIORITY +#endif + +#define UART_DMA_BUF_SIZE 128 +#define UART_TIMEOUT_US 10000 + +LOG_MODULE_REGISTER(dtm_hci_uart, CONFIG_DTM_TRANSPORT_LOG_LEVEL); + +#define DTM_UART DT_CHOSEN(ncs_dtm_uart) +static const struct device *hci_uart_dev = DEVICE_DT_GET(DTM_UART); + +NET_BUF_POOL_DEFINE(hci_tx_buf, QUEUE_COUNT, QUEUE_SIZE, 0, NULL); +static K_FIFO_DEFINE(hci_tx_queue); + +NET_BUF_POOL_DEFINE(hci_rx_buf, QUEUE_COUNT, QUEUE_SIZE, sizeof(uint8_t), NULL); + +enum h4_state { + S_TYPE, + S_HEADER, + S_PAYLOAD +}; + +static hci_uart_read_cb dtm_hci_put; + +static size_t hci_hdr_len(uint8_t type) +{ + switch (type) { + case H4_TYPE_CMD: + return sizeof(struct bt_hci_cmd_hdr); + + case H4_TYPE_ACL: + return sizeof(struct bt_hci_acl_hdr); + + case H4_TYPE_EVT: + return sizeof(struct bt_hci_evt_hdr); + + case H4_TYPE_ISO: + return sizeof(struct bt_hci_iso_hdr); + + default: + return 0; + } +} + +static size_t hci_pld_len(uint8_t type, uint8_t *hdr) +{ + switch (type) { + case H4_TYPE_CMD: + return ((struct bt_hci_cmd_hdr *)hdr)->param_len; + + case H4_TYPE_ACL: + return sys_le16_to_cpu(((struct bt_hci_acl_hdr *)hdr)->len); + + case H4_TYPE_ISO: + return sys_le16_to_cpu(((struct bt_hci_iso_hdr *)hdr)->len); + + default: + return 0; + } +} + +static bool h4_rx_type(uint8_t type) +{ + return ((type == H4_TYPE_CMD) | (type == H4_TYPE_ACL) | (type == H4_TYPE_ISO)); +} + +static size_t buf_read(struct net_buf *buf, const uint8_t *src, size_t src_len, size_t req_len) +{ + size_t len; + + if (req_len > src_len) { + len = src_len; + } else { + len = req_len; + } + + if (net_buf_tailroom(buf) < len) { + __ASSERT_NO_MSG(false); + } + net_buf_add_mem(buf, src, len); + + return len; +} + +static void h4_read(const uint8_t *data, size_t offset, size_t len) +{ + static enum h4_state state = S_TYPE; + static struct net_buf *buf; + static uint8_t type; + static size_t rem; + size_t read; + + while (len > 0) { + switch (state) { + case S_TYPE: + type = data[offset]; + offset += sizeof(type); + len -= sizeof(type); + + if (h4_rx_type(type)) { + rem = hci_hdr_len(type); + buf = net_buf_alloc(&hci_rx_buf, K_NO_WAIT); + if (!buf) { + __ASSERT_NO_MSG(false); + /* Out of buffers condition */ + } + + *(uint8_t *)net_buf_user_data(buf) = type; + state = S_HEADER; + } else { + /* TODO: Sync failure */ + } + break; + + case S_HEADER: + read = buf_read(buf, &data[offset], len, rem); + offset += read; + len -= read; + rem -= read; + if (rem == 0) { + rem = hci_pld_len(type, buf->data); + state = S_PAYLOAD; + if (rem == 0) { + if (dtm_hci_put) { + dtm_hci_put(buf); + } else { + LOG_ERR("Callback dtm_hci_put is not assigned."); + } + state = S_TYPE; + } + } + break; + + case S_PAYLOAD: + read = buf_read(buf, &data[offset], len, rem); + offset += read; + len -= read; + rem -= read; + if (rem == 0) { + if (dtm_hci_put) { + dtm_hci_put(buf); + } else { + LOG_ERR("Callback dtm_hci_put is not assigned."); + } + state = S_TYPE; + } + break; + + default: + state = S_TYPE; + break; + } + } +} + +static uint8_t *uart_buf(void) +{ + static uint32_t cur; + static uint8_t buf1[UART_DMA_BUF_SIZE]; + static uint8_t buf2[UART_DMA_BUF_SIZE]; + + cur = (cur + 1) % 2; + + if (cur == 0) { + return buf1; + } else { + return buf2; + } +} + +static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data) +{ + struct net_buf *buf; + + switch (evt->type) { + case UART_TX_DONE: + LOG_DBG("Uart TX done"); + /* The pointer to net_buf is the first datum before the uart TX pointer */ + buf = *(struct net_buf **)(evt->data.tx.buf - sizeof(buf)); + net_buf_unref(buf); + break; + + case UART_TX_ABORTED: + LOG_DBG("Uart TX aborted"); + /* The pointer to net_buf is the first datum before the uart TX pointer */ + buf = *(struct net_buf **)(evt->data.tx.buf - sizeof(buf)); + net_buf_unref(buf); + break; + + case UART_RX_RDY: + LOG_DBG("Uart RX ready"); + h4_read(evt->data.rx.buf, evt->data.rx.offset, evt->data.rx.len); + break; + + case UART_RX_BUF_REQUEST: + LOG_DBG("Uart rx buf request"); + uart_rx_buf_rsp(dev, uart_buf(), UART_DMA_BUF_SIZE); + break; + + case UART_RX_BUF_RELEASED: + LOG_DBG("Uart rx buf released"); + break; + + case UART_RX_DISABLED: + LOG_DBG("Uart rx disabled"); + break; + + case UART_RX_STOPPED: + LOG_DBG("Uart rx stopped"); + break; + } +} + +static void tx_thread(void) +{ + struct net_buf *buf; + + for (;;) { + /* The first pointer is discarded as it serves internal purpose + * and it's not supposed to be sent over uart. + * The pointer is an address to the associated net_buf. + */ + buf = net_buf_get(&hci_tx_queue, K_FOREVER); + uart_tx(hci_uart_dev, &buf->data[sizeof(buf)], + buf->len - sizeof(buf), SYS_FOREVER_US); + } +} +K_THREAD_DEFINE(tx_thread_id, TX_THREAD_STACK_SIZE, tx_thread, + NULL, NULL, NULL, + TX_THREAD_PRIORITY, 0, 0); + +int hci_uart_init(hci_uart_read_cb cb) +{ + int err; + + dtm_hci_put = cb; + + if (!device_is_ready(hci_uart_dev)) { + LOG_ERR("UART device not ready"); + return -EIO; + } + + err = uart_callback_set(hci_uart_dev, uart_cb, NULL); + if (err) { + LOG_ERR("UART callback not set %d", err); + return err; + } + + err = uart_rx_enable(hci_uart_dev, uart_buf(), UART_DMA_BUF_SIZE, UART_TIMEOUT_US); + if (err) { + LOG_ERR("UART rx not enabled %d", err); + return err; + } + + return 0; +} + +int hci_uart_write(uint8_t type, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t len) +{ + struct net_buf *buf; + + buf = net_buf_alloc(&hci_tx_buf, K_NO_WAIT); + if (!buf) { + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < (len + hdr_len + sizeof(type) + sizeof(buf))) { + net_buf_unref(buf); + return -ENOMEM; + } + + /* Pointer to net_buf is saved in order to unref it in uart callback. */ + net_buf_add_mem(buf, (uint8_t *)&buf, sizeof(buf)); + net_buf_add_u8(buf, type); + net_buf_add_mem(buf, hdr, hdr_len); + net_buf_add_mem(buf, pld, len); + + net_buf_put(&hci_tx_queue, buf); + return 0; +} diff --git a/samples/bluetooth/direct_test_mode/src/transport/hci_uart.h b/samples/bluetooth/direct_test_mode/src/transport/hci_uart.h new file mode 100644 index 00000000000..27c5402467d --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/hci_uart.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef HCI_UART_H_ +#define HCI_UART_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Put read HCI packet into the internal DTM buffer. + * + * This callback puts the HCI packet into the internal buffer + * of the DTM module. + * + * @param[in] buf Buffer containing the HCI packet. + */ +typedef void (*hci_uart_read_cb)(struct net_buf *buf); + +/** @brief Initialize the HCI UART interface. + * + * @param[in] cb Pointer to the callback function for + * saving the HCI packet into the internal buffer. + * + * @return 0 in case of success or negative value in case of error. + */ +int hci_uart_init(hci_uart_read_cb cb); + +/** @brief Transmit an HCI packet. + * + * This function schedules transmission of an HCI packet. + * + * @param[in] type Packet type. + * @param[in] hdr Packet header. + * @param[in] hdr_len Length of the header. + * @param[in] pld Packet payload. + * @param[in] len Length of the payload. + * + * @return 0 in case of success or negative value in case of error. + */ +int hci_uart_write(uint8_t type, const uint8_t *hdr, size_t hdr_len, + const uint8_t *pld, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* HCI_UART_H_ */ diff --git a/samples/bluetooth/direct_test_mode/src/transport/hci_uart_remote.c b/samples/bluetooth/direct_test_mode/src/transport/hci_uart_remote.c new file mode 100644 index 00000000000..20ed71e5699 --- /dev/null +++ b/samples/bluetooth/direct_test_mode/src/transport/hci_uart_remote.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "hci_uart.h" +#include "dtm_serialization.h" + +LOG_MODULE_REGISTER(serialize_layer); + +NRF_RPC_IPC_TRANSPORT(hci_group_tr, DEVICE_DT_GET(DT_NODELABEL(ipc0)), "dtm_ept"); +NRF_RPC_GROUP_DEFINE(hci_group, "hci_remote", &hci_group_tr, NULL, NULL, NULL); + +NET_BUF_POOL_DEFINE(tx_buf, CONFIG_DTM_HCI_QUEUE_COUNT, CONFIG_DTM_HCI_QUEUE_SIZE, 0, NULL); + +static hci_uart_read_cb callback; + +static void rsp_error_code_handle(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, + void *handler_data) +{ + int32_t val; + + if (zcbor_int32_decode(ctx->zs, &val)) { + *(int *)handler_data = (int)val; + } else { + *(int *)handler_data = -NRF_EINVAL; + } +} + +/* Incoming event from application core (uart) */ +static void dtm_hci_put_handler(const struct nrf_rpc_group *group, struct nrf_rpc_cbor_ctx *ctx, + void *handler_data) +{ + struct net_buf *buf; + uint8_t type; + struct zcbor_string tmp; + + LOG_DBG("Call from dtm_hci_put"); + + if (!zcbor_uint_decode(ctx->zs, &type, sizeof(type))) { + goto error_exit; + } + + if (!zcbor_bstr_decode(ctx->zs, &tmp)) { + goto error_exit; + } + + buf = net_buf_alloc(&tx_buf, K_NO_WAIT); + net_buf_add_mem(buf, tmp.value, tmp.len); + buf->user_data[0] = type; + + nrf_rpc_cbor_decoding_done(group, ctx); + + callback(buf); + + return; + +error_exit: + __ASSERT_NO_MSG(false); +} + +NRF_RPC_CBOR_EVT_DECODER(hci_group, dtm_hci_put, RPC_DTM_HCI_PUT_EVT, dtm_hci_put_handler, NULL); + +/* Outgoing to application core (uart), save callback locally. */ +int hci_uart_init(hci_uart_read_cb cb) +{ + int result; + int err; + struct nrf_rpc_cbor_ctx ctx; + size_t buffer_size_max = 0; + + LOG_DBG("Call to hci_init"); + callback = cb; + + NRF_RPC_CBOR_ALLOC(&hci_group, ctx, buffer_size_max); + + err = nrf_rpc_cbor_cmd(&hci_group, RPC_HCI_UART_INIT_CMD, &ctx, + rsp_error_code_handle, &result); + if (err < 0) { + return err; + } + + return result; +} + +/* Outgoing to application core (uart) */ +int hci_uart_write(uint8_t type, const uint8_t *hdr, size_t hdr_len, const uint8_t *pld, size_t len) +{ + int result; + int err; + struct nrf_rpc_cbor_ctx ctx; + size_t buffer_size_max = 20; + + LOG_DBG("Call to hci_uart_write"); + + buffer_size_max += hdr_len + len; + + NRF_RPC_CBOR_ALLOC(&hci_group, ctx, buffer_size_max); + + if (!zcbor_uint_encode(ctx.zs, &type, sizeof(type))) { + goto error_exit; + } + + if (!zcbor_bstr_encode_ptr(ctx.zs, hdr, hdr_len)) { + goto error_exit; + } + + if (!zcbor_bstr_encode_ptr(ctx.zs, pld, len)) { + goto error_exit; + } + + err = nrf_rpc_cbor_cmd(&hci_group, RPC_HCI_UART_WRITE_CMD, &ctx, + rsp_error_code_handle, &result); + if (err < 0) { + return err; + } + + return result; + +error_exit: + __ASSERT_NO_MSG(false); +} + +static void err_handler(const struct nrf_rpc_err_report *report) +{ + LOG_ERR("nRF RPC error %d ocurred. See nRF RPC logs for more details.", + report->code); + k_oops(); +} + +static int serialization_init(void) +{ + int err; + + LOG_INF("RPC init begin\n"); + + err = nrf_rpc_init(err_handler); + if (err) { + return -NRF_EINVAL; + } + + LOG_INF("RPC init done\n"); + + return 0; +} + +SYS_INIT(serialization_init, POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY);