commit 4c27e81df55924dd448c2b65659fb3f3d7612e4a Author: Timo Date: Fri Aug 21 16:35:54 2026 +0200 added xiao nrf54l15 support for otbr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3d59b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Zephyr/west build output +build/ +build-*/ + +# west workspace metadata, if this repo is ever used as a manifest repo +.west/ + +# Editor / OS droppings +._* +.DS_Store +*.swp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a2e646a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +set(OT_NCP_VENDOR_HOOK_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(openthread_coprocessor) + +# NORDIC SDK APP START +target_sources(app PRIVATE src/main.c src/nrf_802154_radio_wrapper.c) +target_sources_ifdef(CONFIG_RCP_SAMPLE_HCI app PRIVATE src/rcp_hci.c) +# NORDIC SDK APP END diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..4c4e606 --- /dev/null +++ b/Kconfig @@ -0,0 +1,22 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Set product string for USB device +config CDC_ACM_SERIAL_PRODUCT_STRING + default "Thread Co-Processor" + depends on USB_DEVICE_STACK_NEXT + +menu "Zephyr Kernel" +source "Kconfig.zephyr" +endmenu + +module = OT_COPROCESSOR +module-str = ot_coprocessor +source "$(ZEPHYR_BASE)/subsys/logging/Kconfig.template.log_config" + +config RCP_SAMPLE_HCI + bool "Bluetooth HCI USB support for the coprocessor sample" + select EXPERIMENTAL diff --git a/Kconfig.sysbuild b/Kconfig.sysbuild new file mode 100644 index 0000000..c02a068 --- /dev/null +++ b/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config PARTITION_MANAGER + default n + +source "share/sysbuild/Kconfig" diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..60b5ca5 --- /dev/null +++ b/README.rst @@ -0,0 +1,202 @@ +.. _ot_coprocessor_sample: + +Thread: Co-processor +#################### + +.. contents:: + :local: + :depth: 2 + +The :ref:`Thread ` Co-processor sample demonstrates how to implement OpenThread's :ref:`thread_architectures_designs_cp` inside the Zephyr environment. +The sample uses the :ref:`thread_architectures_designs_cp_rcp` architecture. + +The sample is based on Zephyr's :zephyr:code-sample:`openthread-coprocessor` sample. +However, it customizes Zephyr's sample to fulfill the |NCS| requirements (for example, by increasing the stack size dedicated for the user application), and also extends it with features such as: + +* Increased Mbed TLS heap size. +* Lowered main stack size to increase user application space. +* No obsolete configuration options. +* Vendor hooks for co-processor architecture allowing users to extend handled properties by their own, customized functionalities. +* Thread 1.4 features, including support for Thread 1.3 and Thread 1.2. + +This sample supports optional :ref:`logging extension `, which can be turned on or off independently. +To enable logging extension, use the ``ot-debug`` snippet. + +.. _ot_coprocessor_sample_requirements: + +Requirements +************ + +The sample supports the following development kits for testing the network status: + +.. table-from-sample-yaml:: + +To test the sample, you need at least one development kit. +You can use additional development kits programmed with the Co-processor sample for testing network joining. + +Moreover, the sample requires a Userspace higher layer process running on your device to communicate with the MCU co-processor part. +This sample uses ``ot-cli`` as reference. + + +Overview +******** + +The sample demonstrates using a co-processor target on the MCU to communicate with `ot-cli` on Unix-like operating system. +According to the co-processor architecture, the MCU part must cooperate with user higher layer process to establish the complete full stack application. +The sample shows how to set up the connection between the co-processor and the host. + +By default, this sample comes with the :ref:`RCP set of OpenThread functionalities ` enabled (:kconfig:option:`CONFIG_OPENTHREAD_NORDIC_LIBRARY_RCP`). + +Application architecture options +================================ + +.. include:: ../cli/README.rst + :start-after: ot_cli_sample_architecture_options_start + :end-before: ot_cli_sample_architecture_options_end + +.. _ot_coprocessor_sample_logging: + +Logging extension +================= + +By default, this sample uses :ref:`Spinel logging backend ` for sending log messages to the host device using the Spinel protocol. +This is a useful feature, because it does not require separate interfaces to communicate with the co-processor through the Spinel protocol and collect log messages. +Moreover, using the Spinel logging backend (by setting :kconfig:option:`CONFIG_LOG_BACKEND_SPINEL`) does not exclude using another backend like UART or RTT at the same time. + +By default, the log levels for all modules are set to critical to not engage the microprocessor in unnecessary activities. +To make the solution flexible, you can change independently the log levels for your modules, for the whole Zephyr system, and for OpenThread. +Use the ``ot-debug`` snippet as reference for this purpose. + +For example: + +.. code-block:: none + + west build -b nrf54l15dk_nrf54l15_cpuapp -p -- -Dcoprocessor_SNIPPET=ot-debug + +User interface +************** + +All the interactions with the application are handled using serial communication. + +You can interact with the sample through ``ot-daemon`` or ``ot-cli`` with commands listed in `OpenThread CLI Reference`_. +See :ref:`ug_thread_tools_ot_apps` for more information. + +You can also use your own application, provided that it supports the Spinel communication protocol. + +.. note:: + |thread_hwfc_enabled| + In addition, the Co-processor sample reconfigures the baud rate to 1000000 bit/s by default. + +Diagnostic module +================= + +The Co-processor sample enables a diagnostic module in a similar way as described in the :ref:`ot_cli_sample_diag_module` section of the :ref:`ot_cli_sample` sample documentation. +However, the Co-processor and CLI samples use different commands for the module, as described in the :ref:`ot_coprocessor_testing` section. + +Rebooting to bootloader +======================= + +The Co-processor sample enables rebooting to bootloader for the ``nrf52840dongle/nrf52840`` board target, similar to what is described in the :ref:`ot_cli_sample_bootloader` section of the :ref:`ot_cli_sample` sample documentation. +However, the Co-processor and CLI samples use different commands, as described in the :ref:`ot_coprocessor_testing` section. +Additionally, the :ref:`ug_thread_tools_ot_apps` should be built with ``-DOT_PLATFORM_BOOTLOADER_MODE=ON`` option. + +Configuration +************* + +|config| + +Check and configure the following library option that is used by the sample: + +* :kconfig:option:`CONFIG_OPENTHREAD_COPROCESSOR_RCP` - Selects the RCP architecture for the sample. + + +.. include:: /includes/sample_fem_support.txt + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/openthread/coprocessor` + +|enable_thread_before_testing| + +.. include:: /includes/build_and_run.txt + +.. _ot_coprocessor_testing: + +HCI support +=========== + +The sample supports Bluetooth® Low Energy (LE) Controller functionality using HCI over UART. +To enable HCI support, complete the following steps: + +.. tabs:: + + .. tab:: nRF54L15, nRF54L10, and nRF54L05 DKs + + HCI communication is handled over a dedicated UART peripheral on the nRF54L Series System-on-Chip (SoC). + After connecting the board using the SEGGER J-Link USB port, two serial ports will be created. + The first port is used for HCI and the second for the Thread co-processor (Spinel). + + To enable HCI support, run the following command: + + .. parsed-literal:: + :class: highlight + + west build -b *board_target* -p -- -DEXTRA_CONF_FILE=extra_conf/rcp_hci_nrf54l_05_10_15.conf -DEXTRA_DTC_OVERLAY_FILE=extra_conf/rcp_hci_nrf54l_05_10_15.overlay + + .. tab:: Other supported boards + + For a list of supported boards, see the :ref:`ot_coprocessor_sample_requirements` section. + + HCI communication is handled over a USB CDC ACM interface. + After connecting the board using the nRF USB port, two serial ports will be created. + Usually, the first port is used for HCI and the second for the Thread co-processor. + + To enable HCI support, run the following command: + + .. parsed-literal:: + :class: highlight + + west build -b *board_target* -p -- -DEXTRA_CONF_FILE=extra_conf/rcp_hci.conf -DEXTRA_DTC_OVERLAY_FILE=extra_conf/rcp_hci.overlay + +Vendor hooks +============ + +The sample supports vendor hooks for co-processor architecture allowing you to extend handled properties by your own, customized functionalities. + +To enable vendor hooks, set the :kconfig:option:`CONFIG_OPENTHREAD_COPROCESSOR_VENDOR_HOOK_SOURCE` Kconfig option to the path of the vendor hook source file and run the following command with *board_target* replaced with the board target name: + +.. parsed-literal:: + :class: highlight + + west build -b *board_target* -p -- -DCONFIG_OPENTHREAD_COPROCESSOR_VENDOR_HOOK_SOURCE="/src/user_vendor_hook.cpp" + +Testing +======= + +After building the sample and programming it to your development kit, complete the following steps to test it: + +1. Connect the development kit's SEGGER J-Link USB port to the PC USB port with a USB cable. + If you are using HCI for boards other than nRF54L15, nRF54L10, and nRF54L05 DKs, connect the kit's nRF USB port to the PC USB port instead. +#. Get the kit's serial port name (for example, :file:`/dev/ttyACM0`). +#. Run and configure ot-cli as described in :ref:`ug_thread_tools_ot_apps`. +#. From this point, you can follow the :ref:`ot_cli_sample_testing` instructions in the CLI sample by removing the `ot` prefix for each command. + If you are using HCI, follow the instructions for the :zephyr:code-sample:`bluetooth_hci_uart` sample in the Zephyr documentation. + You can follow these instead of or in addition to the CLI sample instructions. + +Dependencies +************ + +This sample uses the following Zephyr libraries: + +* :ref:`zephyr:kernel_api`: + + * ``include/kernel.h`` + +* :ref:`zephyr:thread_protocol_interface` + +* :ref:`zephyr:logging_api`: + + * ``include/logging/log.h`` + +* :ref:`zephyr:bluetooth-hci` diff --git a/boards/xiao_nrf54l15_nrf54l15_cpuapp.conf b/boards/xiao_nrf54l15_nrf54l15_cpuapp.conf new file mode 100644 index 0000000..0004273 --- /dev/null +++ b/boards/xiao_nrf54l15_nrf54l15_cpuapp.conf @@ -0,0 +1,18 @@ +# OpenThread RCP on the Seeed XIAO nRF54L15. + +# The XIAO has no external SPI flash populated. +CONFIG_SPI_NOR=n + +# Nordic uses this on the nRF54L15 DK to keep the main stack clear of CRACEN +# (the hardware crypto engine) stack usage. +CONFIG_MAIN_STACK_SIZE=2048 + +# Nothing may write to uart20 except spinel. The boot banner and the UART log +# backend would both land mid-stream and desynchronise otbr-agent's HDLC +# decoder. Logs ride in-band over spinel instead, where otbr-agent decodes them. +CONFIG_BOOT_BANNER=n +CONFIG_LOG=y +CONFIG_LOG_MAX_LEVEL=1 +CONFIG_LOG_BACKEND_UART=n +CONFIG_LOG_BACKEND_SPINEL=y +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 diff --git a/boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay b/boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay new file mode 100644 index 0000000..aaff0b2 --- /dev/null +++ b/boards/xiao_nrf54l15_nrf54l15_cpuapp.overlay @@ -0,0 +1,36 @@ +/* + * OpenThread RCP on the Seeed XIAO nRF54L15. + * + * uart20 (P1.09 TX / P1.08 RX) is wired to the onboard CMSIS-DAP USB-serial + * bridge, so it is both the board's default console and the only UART that + * reaches the host over USB. The RCP needs that link for spinel, so: + * + * - zephyr,ot-uart -> uart20 (spinel to otbr-agent on the host) + * - zephyr,console -> uart21 (moved off, or console output would corrupt + * the spinel byte stream) + * + * uart21 (P2.08 TX / P2.07 RX) is exposed on the XIAO edge pads. Nothing needs + * to be attached; hook up a USB-UART adapter there if you want boot logs. + * + * Unlike Nordic's nrf54l15dk overlay, hw-flow-control is NOT set: the XIAO's + * pinctrl only defines TX and RX for uart20, and the CMSIS-DAP bridge does not + * carry RTS/CTS. See docs/ for the baud rate this was validated at. + */ + +/ { + chosen { + zephyr,ot-uart = &uart20; + zephyr,console = &uart21; + zephyr,shell-uart = &uart21; + }; +}; + +&uart20 { + status = "okay"; + current-speed = <1000000>; +}; + +&uart21 { + status = "okay"; + current-speed = <115200>; +}; diff --git a/prj.conf b/prj.conf new file mode 100644 index 0000000..ea93eb5 --- /dev/null +++ b/prj.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2020 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Enable OpenThread +CONFIG_OPENTHREAD=y + +# Set OpenThread NCP architecture +CONFIG_OPENTHREAD_COPROCESSOR=y +CONFIG_OPENTHREAD_COPROCESSOR_RCP=y + +# Nordic feature set +CONFIG_OPENTHREAD_NORDIC_LIBRARY_RCP=y + +# Increase Settings storage size +CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE=0x8000 + +# Disable RTT +CONFIG_USE_SEGGER_RTT=n + +# Disable GPIO +CONFIG_GPIO=n diff --git a/scripts/ncs-toolchain.sh b/scripts/ncs-toolchain.sh new file mode 100755 index 0000000..705129b --- /dev/null +++ b/scripts/ncs-toolchain.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# nRF Connect SDK toolchain environment. +# +# Normally you do not source this directly. It is wired into Zephyr's own +# entry point via ~/.zephyrrc (see scripts/install-zephyrrc.sh), so that +# +# source /opt/ncs/v3.4.0/zephyr/zephyr-env.sh +# +# sets up ZEPHYR_BASE *and* the toolchain in one step. Sourcing zephyr-env.sh +# without this leaves you on the system cmake and python, and the build fails: +# +# Could NOT find Python3: Found unsuitable version "3.10.6", +# but required is at least "3.12" +# +# because /usr/local/bin/python3.10 (and the host's 3.14) are found before the +# Python 3.12 that the SDK toolchain bundle ships. +# +# Nothing is hardcoded: the bundle directory is discovered, and the variables +# are read from the bundle's own environment.json, so toolchain or SDK updates +# are picked up instead of silently rotting. +# +# Overrides, if the defaults guess wrong: +# NCS_ROOT SDK install root (default /opt/ncs) +# NCS_VERSION version dir under NCS_ROOT (default: newest vX.Y.Z) +# NCS_TOOLCHAIN toolchain bundle directory (default: most recent) + +_ncs_warn() { echo "ncs-toolchain: $*" >&2; } + +: "${NCS_ROOT:=/opt/ncs}" + +if [ ! -d "$NCS_ROOT" ]; then + _ncs_warn "SDK root not found: $NCS_ROOT (set NCS_ROOT)" +else + +# --- locate the SDK version directory --------------------------------------- +# Prefer the tree that zephyr-env.sh is actually being sourced from, so the +# toolchain always matches the SDK in use rather than guessing. +if [ -z "${NCS_VERSION:-}" ] && [ -n "${ZEPHYR_BASE:-}" ]; then + case "$ZEPHYR_BASE" in + "$NCS_ROOT"/*) NCS_VERSION="${ZEPHYR_BASE#"$NCS_ROOT"/}"; NCS_VERSION="${NCS_VERSION%%/*}" ;; + esac +fi +if [ -z "${NCS_VERSION:-}" ]; then + NCS_VERSION="$( + for d in "$NCS_ROOT"/v*/; do + [ -d "$d/zephyr" ] && basename "$d" + done | sort -V | tail -1 + )" +fi + +# --- locate the toolchain bundle -------------------------------------------- +if [ -z "${NCS_TOOLCHAIN:-}" ]; then + # Bundles are keyed by an opaque id and manifest.json carries no plain SDK + # version to match on, so take the most recently installed. + NCS_TOOLCHAIN="$( + for d in "$NCS_ROOT"/toolchains/*/; do + [ -f "$d/environment.json" ] && printf '%s %s\n' "$(stat -c %Y "$d")" "${d%/}" + done | sort -n | tail -1 | cut -d' ' -f2- + )" +fi + +if [ -z "${NCS_TOOLCHAIN:-}" ] || [ ! -f "$NCS_TOOLCHAIN/environment.json" ]; then + _ncs_warn "no toolchain bundle with environment.json under $NCS_ROOT/toolchains (set NCS_TOOLCHAIN)" +elif [ "${_NCS_TOOLCHAIN_APPLIED:-}" = "$NCS_TOOLCHAIN" ]; then + : # already applied in this shell; re-prepending PATH would just pile up +else + +# --- apply the bundle's declared environment -------------------------------- +# environment.json declares each variable as either a "string" or a list of +# paths relative to the bundle root, plus how to combine with any existing +# value ("prepend_to" or "overwrite"). Honour what it declares rather than +# copying today's values, so added or changed variables are picked up too. +# +# Parsed with the host python: the bundle's python is only usable once +# PYTHONHOME points at it, and a PYTHONHOME/PYTHONPATH left over from an +# earlier sourcing would break it -- hence `env -u`. +_ncs_env="$(env -u PYTHONHOME -u PYTHONPATH python3 - "$NCS_TOOLCHAIN" <<'PY' +import json, os, shlex, sys + +root = sys.argv[1] +with open(os.path.join(root, "environment.json")) as fh: + spec = json.load(fh) + +for var in spec.get("env_vars", []): + key = var["key"] + if var["type"] == "string": + value = var["value"] + elif var["type"] == "relative_paths": + value = os.pathsep.join(os.path.join(root, v) for v in var["values"]) + else: + continue + + if var.get("existing_value_treatment") == "prepend_to": + existing = os.environ.get(key, "") + if existing: + value = value + os.pathsep + existing + print(f"export {key}={shlex.quote(value)}") +PY +)" + +if [ -z "$_ncs_env" ]; then + _ncs_warn "failed to read $NCS_TOOLCHAIN/environment.json" +else + eval "$_ncs_env" + + # Some bundles declare this as "zephyr/gnu", which the Zephyr build does + # not accept; the SDK variant is plain "zephyr". + case "${ZEPHYR_TOOLCHAIN_VARIANT:-}" in + zephyr/*) export ZEPHYR_TOOLCHAIN_VARIANT=zephyr ;; + esac + + export NCS_ROOT NCS_TOOLCHAIN + [ -n "${NCS_VERSION:-}" ] && export NCS_VERSION NCS_WORKSPACE="$NCS_ROOT/$NCS_VERSION" + + # west resolves projects from the nearest .west found walking up from the + # current directory. A repo outside the SDK tree has none, so `west build` + # must run from inside the workspace with absolute -d and source paths. + [ -n "${NCS_WORKSPACE:-}" ] && export WEST_TOPDIR="$NCS_WORKSPACE" + + export _NCS_TOOLCHAIN_APPLIED="$NCS_TOOLCHAIN" +fi +unset _ncs_env + +fi +fi + +unset -f _ncs_warn diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e69b757 --- /dev/null +++ b/src/main.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#include +#include + +#if defined(CONFIG_RCP_SAMPLE_HCI) +#include "rcp_hci.h" +#endif + +LOG_MODULE_REGISTER(coprocessor_sample, CONFIG_OT_COPROCESSOR_LOG_LEVEL); + +#define WELCOME_TEXT \ + "\n\r" \ + "\n\r" \ + "=========================================================\n\r" \ + "OpenThread Coprocessor application is now running on NCS.\n\r" \ + "=========================================================\n\r" + +int main(void) +{ + LOG_INF(WELCOME_TEXT); + +#if defined(CONFIG_RCP_SAMPLE_HCI) + run_hci(); +#endif + + return 0; +} diff --git a/src/nrf_802154_radio_wrapper.c b/src/nrf_802154_radio_wrapper.c new file mode 100644 index 0000000..fa782a9 --- /dev/null +++ b/src/nrf_802154_radio_wrapper.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ +#include "nrf_802154_radio_wrapper.h" +#include + +#ifdef CONFIG_NETWORKING +#include +#include + +static const struct device *const radio_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_ieee802154)); +static struct ieee802154_radio_api *radio_api; + +#else +static uint16_t get_capabilities(void) +{ + nrf_802154_capabilities_t caps = nrf_802154_capabilities_get(); + + /* Convert nrf_802154_capabilities_t to ieee802154_hw_caps to keep backwards compatibility + * See @ref ieee802154_hw_caps in zephyr/net/ieee802154_radio.h for more details. + */ + return /* IEEE802154_HW_FCS */ BIT(1) | + /* IEEE802154_HW_PROMISC */ BIT(3) | + /* IEEE802154_HW_FILTER */ BIT(2) | + /* IEEE802154_HW_CSMA */ ((caps & NRF_802154_CAPABILITY_CSMA) ? BIT(4) : 0UL) | + /* IEEE802154_HW_TX_RX_ACK */ BIT(5) | + /* IEEE802154_HW_RX_TX_ACK */ BIT(7) | + /* IEEE802154_HW_ENERGY_SCAN */ BIT(0) | + /* IEEE802154_HW_TXTIME */ + ((caps & NRF_802154_CAPABILITY_DELAYED_TX) ? BIT(8) : 0UL) | + /* IEEE802154_HW_RXTIME */ + ((caps & NRF_802154_CAPABILITY_DELAYED_RX) ? BIT(10) : 0UL) | + /* IEEE802154_HW_SLEEP_TO_TX */ BIT(9) | + /* IEEE802154_HW_TX_SEC */ BIT(12) | + ((caps & NRF_802154_CAPABILITY_SECURITY) ? BIT(11) : 0UL) +#if defined(CONFIG_IEEE802154_NRF5_MULTIPLE_CCA) + | /* IEEE802154_OPENTHREAD_HW_CAPS_BITS_START */ BIT(14) +#endif +#if defined(CONFIG_IEEE802154_SELECTIVE_TXCHANNEL) + | /* IEEE802154_HW_SELECTIVE_TXCHANNEL */ BIT(13) +#endif +#if defined(CONFIG_IEEE802154_NRF5_CST_ENDPOINT) + | /* IEEE802154_OPENTHREAD_HW_CAPS_BITS_START + 1*/ BIT(15) +#endif + ; +} + +#endif + +bool nrf_802154_radio_wrapper_auto_ack_get(void) +{ + return nrf_802154_auto_ack_get(); +} + +void nrf_802154_radio_wrapper_auto_ack_set(bool enabled) +{ + nrf_802154_auto_ack_set(enabled); +} + +uint16_t nrf_802154_radio_wrapper_hw_capabilities_get(void) +{ +#ifdef CONFIG_NETWORKING + __ASSERT_NO_MSG(device_is_ready(radio_dev)); + + radio_api = (struct ieee802154_radio_api *)radio_dev->api; + __ASSERT_NO_MSG(radio_api != NULL); + + return radio_api->get_capabilities(radio_dev); +#else + return get_capabilities(); +#endif +} diff --git a/src/nrf_802154_radio_wrapper.h b/src/nrf_802154_radio_wrapper.h new file mode 100644 index 0000000..1d2adf6 --- /dev/null +++ b/src/nrf_802154_radio_wrapper.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef __NRF_802154_RADIO_WRAPPER_H__ +#define __NRF_802154_RADIO_WRAPPER_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gets current automatic acknowledgments mode state. + * + * @return true when auto ACK is set, false otherwise + */ +bool nrf_802154_radio_wrapper_auto_ack_get(void); + +/** + * Enables or disables the automatic acknowledgments mode (auto ACK). + * + * @param[in] enabled Value setting (true) or clearing (false) + * auto ACK radio mode. + */ +void nrf_802154_radio_wrapper_auto_ack_set(bool enabled); + +/** + * Gets the device hardware capabilities + * + * @return 0 on failure; nonzero hw capabilities value + * on success (@ref ieee802154_hw_caps). + */ +uint16_t nrf_802154_radio_wrapper_hw_capabilities_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __NRF_802154_RADIO_WRAPPER_H__ */ diff --git a/src/rcp_hci.c b/src/rcp_hci.c new file mode 100644 index 0000000..cf13233 --- /dev/null +++ b/src/rcp_hci.c @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(rcp_hci_module, CONFIG_OT_COPROCESSOR_LOG_LEVEL); + +static const struct device *const hci_uart_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_bt_c2h_uart)); +static K_THREAD_STACK_DEFINE(tx_thread_stack, CONFIG_BT_HCI_TX_STACK_SIZE); +static struct k_thread tx_thread_data; +static K_FIFO_DEFINE(tx_queue); + +/* RX in terms of bluetooth communication */ +static K_FIFO_DEFINE(uart_tx_queue); + +#define H4_CMD 0x01 +#define H4_ACL 0x02 +#define H4_SCO 0x03 +#define H4_EVT 0x04 +#define H4_ISO 0x05 + +/* Receiver states. */ +#define ST_IDLE 0 /* Waiting for packet type. */ +#define ST_HDR 1 /* Receiving packet header. */ +#define ST_PAYLOAD 2 /* Receiving packet payload. */ +#define ST_DISCARD 3 /* Dropping packet. */ + +#define H4_DISCARD_LEN 33 + +static int h4_read(const struct device *uart, uint8_t *buf, size_t len) +{ + int rx = uart_fifo_read(uart, buf, len); + + LOG_DBG("read %d req %d", rx, len); + + return rx; +} + +static bool valid_type(uint8_t type) +{ + return (type == H4_CMD) | (type == H4_ACL) | (type == H4_ISO); +} + +/* Function expects that type is validated and only CMD, ISO or ACL will be used. */ +static uint32_t get_len(const uint8_t *hdr_buf, uint8_t type) +{ + switch (type) { + case H4_CMD: + return ((const struct bt_hci_cmd_hdr *)hdr_buf)->param_len; + case H4_ISO: + return bt_iso_hdr_len( + sys_le16_to_cpu(((const struct bt_hci_iso_hdr *)hdr_buf)->len)); + case H4_ACL: + return sys_le16_to_cpu(((const struct bt_hci_acl_hdr *)hdr_buf)->len); + default: + LOG_ERR("Invalid type: %u", type); + return 0; + } +} + +/* Function expects that type is validated and only CMD, ISO or ACL will be used. */ +static int hdr_len(uint8_t type) +{ + switch (type) { + case H4_CMD: + return sizeof(struct bt_hci_cmd_hdr); + case H4_ISO: + return sizeof(struct bt_hci_iso_hdr); + case H4_ACL: + return sizeof(struct bt_hci_acl_hdr); + default: + LOG_ERR("Invalid type: %u", type); + return 0; + } +} + +static void rx_isr(void) +{ + static struct net_buf *buf; + static int remaining; + static uint8_t state; + static uint8_t type; + static uint8_t hdr_buf[MAX(sizeof(struct bt_hci_cmd_hdr), sizeof(struct bt_hci_acl_hdr))]; + int read; + + do { + switch (state) { + case ST_IDLE: + /* Get packet type */ + read = h4_read(hci_uart_dev, &type, sizeof(type)); + /* since we read in loop until no data is in the fifo, + * it is possible that read = 0. + */ + if (read) { + if (valid_type(type)) { + /* Get expected header size and switch + * to receiving header. + */ + remaining = hdr_len(type); + state = ST_HDR; + } else { + LOG_WRN("Unknown header %d", type); + } + } + break; + case ST_HDR: + read = h4_read(hci_uart_dev, &hdr_buf[hdr_len(type) - remaining], + remaining); + remaining -= read; + if (remaining == 0) { + /* Header received. Allocate buffer and get + * payload length. If allocation fails leave + * interrupt. On failed allocation state machine + * is reset. + */ + buf = bt_buf_get_tx(bt_buf_type_from_h4(type, BT_BUF_OUT), + K_NO_WAIT, NULL, 0); + if (!buf) { + LOG_ERR("No available command buffers!"); + state = ST_IDLE; + return; + } + + remaining = get_len(hdr_buf, type); + + net_buf_add_mem(buf, hdr_buf, hdr_len(type)); + if (remaining > net_buf_tailroom(buf)) { + LOG_ERR("Not enough space in buffer"); + net_buf_unref(buf); + state = ST_DISCARD; + } else { + state = ST_PAYLOAD; + } + } + break; + case ST_PAYLOAD: + read = h4_read(hci_uart_dev, net_buf_tail(buf), remaining); + buf->len += read; + remaining -= read; + if (remaining == 0) { + /* Packet received */ + LOG_DBG("putting RX packet in queue."); + k_fifo_put(&tx_queue, buf); + state = ST_IDLE; + } + break; + case ST_DISCARD: { + uint8_t discard[H4_DISCARD_LEN]; + size_t to_read = MIN(remaining, sizeof(discard)); + + read = h4_read(hci_uart_dev, discard, to_read); + remaining -= read; + if (remaining == 0) { + state = ST_IDLE; + } + + break; + } + default: + read = 0; + __ASSERT_NO_MSG(0); + break; + } + } while (read); +} + +static void tx_isr(void) +{ + static struct net_buf *buf; + int len; + + if (!buf) { + buf = k_fifo_get(&uart_tx_queue, K_NO_WAIT); + if (!buf) { + uart_irq_tx_disable(hci_uart_dev); + return; + } + } + + len = uart_fifo_fill(hci_uart_dev, buf->data, buf->len); + net_buf_pull(buf, len); + if (!buf->len) { + net_buf_unref(buf); + buf = NULL; + } +} + +static void bt_uart_isr(const struct device *unused, void *user_data) +{ + ARG_UNUSED(unused); + ARG_UNUSED(user_data); + + if (!(uart_irq_rx_ready(hci_uart_dev) || uart_irq_tx_ready(hci_uart_dev))) { + LOG_DBG("spurious interrupt"); + } + + if (uart_irq_tx_ready(hci_uart_dev)) { + tx_isr(); + } + + if (uart_irq_rx_ready(hci_uart_dev)) { + rx_isr(); + } +} + +static void tx_thread(void *p1, void *p2, void *p3) +{ + while (1) { + struct net_buf *buf; + int err; + + /* Wait until a buffer is available */ + buf = k_fifo_get(&tx_queue, K_FOREVER); + /* Pass buffer to the stack */ + err = bt_send(buf); + if (err) { + LOG_ERR("Unable to send (err %d)", err); + net_buf_unref(buf); + } + + /* Give other threads a chance to run if tx_queue keeps getting + * new data all the time. + */ + k_yield(); + } +} + +static int h4_send(struct net_buf *buf) +{ + LOG_DBG("buf %p type %u len %u", buf, buf->data[0], buf->len); + + k_fifo_put(&uart_tx_queue, buf); + uart_irq_tx_enable(hci_uart_dev); + + return 0; +} + +#if defined(CONFIG_BT_CTLR_ASSERT_HANDLER) +void bt_ctlr_assert_handle(char *file, uint32_t line) +{ + uint32_t len = 0U, pos = 0U; + + /* Disable interrupts, this is unrecoverable */ + (void)irq_lock(); + + uart_irq_rx_disable(hci_uart_dev); + uart_irq_tx_disable(hci_uart_dev); + + if (file) { + while (file[len] != '\0') { + if (file[len] == '/') { + pos = len + 1; + } + len++; + } + file += pos; + len -= pos; + } + + uart_poll_out(hci_uart_dev, H4_EVT); + /* Vendor-Specific debug event */ + uart_poll_out(hci_uart_dev, 0xff); + /* 0xAA + strlen + \0 + 32-bit line number */ + uart_poll_out(hci_uart_dev, 1 + len + 1 + 4); + uart_poll_out(hci_uart_dev, 0xAA); + + if (len) { + while (*file != '\0') { + uart_poll_out(hci_uart_dev, *file); + file++; + } + uart_poll_out(hci_uart_dev, 0x00); + } + + uart_poll_out(hci_uart_dev, line >> 0 & 0xff); + uart_poll_out(hci_uart_dev, line >> 8 & 0xff); + uart_poll_out(hci_uart_dev, line >> 16 & 0xff); + uart_poll_out(hci_uart_dev, line >> 24 & 0xff); + + while (1) { + } +} +#endif /* CONFIG_BT_CTLR_ASSERT_HANDLER */ + +static int hci_uart_init(void) +{ + LOG_DBG(""); + + if (!device_is_ready(hci_uart_dev)) { + LOG_ERR("HCI UART %s is not ready", hci_uart_dev->name); + return -EINVAL; + } + + uart_irq_rx_disable(hci_uart_dev); + uart_irq_tx_disable(hci_uart_dev); + + uart_irq_callback_set(hci_uart_dev, bt_uart_isr); + + uart_irq_rx_enable(hci_uart_dev); + + return 0; +} + +void run_hci(void) +{ + int err = 0; + + __ASSERT(hci_uart_dev, "UART device is NULL"); + /* incoming events and data from the controller */ + static K_FIFO_DEFINE(rx_queue); + + /* Enable the raw interface, this will in turn open the HCI driver */ + bt_enable_raw(&rx_queue); + + if (IS_ENABLED(CONFIG_BT_WAIT_NOP)) { + /* Issue a Command Complete with NOP */ + int i; + + const struct { + const uint8_t h4; + const struct bt_hci_evt_hdr hdr; + const struct bt_hci_evt_cmd_complete cc; + } __packed cc_evt = { + .h4 = H4_EVT, + .hdr = { + .evt = BT_HCI_EVT_CMD_COMPLETE, + .len = sizeof(struct bt_hci_evt_cmd_complete), + }, + .cc = { + .ncmd = 1, + .opcode = sys_cpu_to_le16(BT_OP_NOP), + }, + }; + + for (i = 0; i < sizeof(cc_evt); i++) { + uart_poll_out(hci_uart_dev, *(((const uint8_t *)&cc_evt) + i)); + } + } + + /* Spawn the TX thread and start feeding commands and data to the + * controller + */ + k_thread_create(&tx_thread_data, tx_thread_stack, K_THREAD_STACK_SIZEOF(tx_thread_stack), + tx_thread, NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + k_thread_name_set(&tx_thread_data, "HCI uart TX"); + + while (1) { + struct net_buf *buf; + + buf = k_fifo_get(&rx_queue, K_FOREVER); + err = h4_send(buf); + if (err) { + LOG_ERR("Failed to send"); + } + } +} + +SYS_INIT(hci_uart_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/src/rcp_hci.h b/src/rcp_hci.h new file mode 100644 index 0000000..fa969d4 --- /dev/null +++ b/src/rcp_hci.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifndef __RCP_HCI_H__ +#define __RCP_HCI_H__ + +void run_hci(void); + +#endif diff --git a/src/user_vendor_hook.cpp b/src/user_vendor_hook.cpp new file mode 100644 index 0000000..a1aa2e6 --- /dev/null +++ b/src/user_vendor_hook.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/** + * @file + * This file shows how to implement the NCP vendor hook. + */ +#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK +#include "nrf_802154_radio_wrapper.h" +#include +#include +#include +#include + +#define VENDOR_SPINEL_PROP_VENDOR_NAME SPINEL_PROP_VENDOR__BEGIN +#define VENDOR_SPINEL_PROP_AUTO_ACK_ENABLED SPINEL_PROP_VENDOR__BEGIN + 1 +#define VENDOR_SPINEL_PROP_HW_CAPABILITIES SPINEL_PROP_VENDOR__BEGIN + 2 + +LOG_MODULE_REGISTER(ncp_sample_vendor_hook, CONFIG_OT_COPROCESSOR_LOG_LEVEL); + +namespace ot { +namespace Ncp { + +otError NcpBase::VendorCommandHandler(uint8_t aHeader, unsigned int aCommand) +{ + otError error = OT_ERROR_NONE; + + LOG_DBG("VendorCommandHandler"); + + switch (aCommand) + { + // TODO: Implement your command handlers here. + + default: + error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_INVALID_COMMAND); + } + + return error; +} + +void NcpBase::VendorHandleFrameRemovedFromNcpBuffer(Spinel::Buffer::FrameTag aFrameTag) +{ + // This method is a callback which mirrors `NcpBase::HandleFrameRemovedFromNcpBuffer()`. + // It is called when a spinel frame is sent and removed from NCP buffer. + // + // (a) This can be used to track and verify that a vendor spinel frame response is + // delivered to the host (tracking the frame using its tag). + // + // (b) It indicates that NCP buffer space is now available (since a spinel frame is + // removed). This can be used to implement reliability mechanisms to re-send + // a failed spinel command response (or an async spinel frame) transmission + // (failed earlier due to NCP buffer being full). + + OT_UNUSED_VARIABLE(aFrameTag); +} + +otError NcpBase::VendorGetPropertyHandler(spinel_prop_key_t aPropKey) +{ + otError error = OT_ERROR_NONE; + + switch (aPropKey) + { + case VENDOR_SPINEL_PROP_VENDOR_NAME: + LOG_DBG("Got NAME get property request"); + error = mEncoder.WriteUtf8("Nordic Seminconductor"); + break; + case VENDOR_SPINEL_PROP_AUTO_ACK_ENABLED: + LOG_DBG("Got VENDOR_SPINEL_PROP_AUTO_ACK_ENABLED get property request"); + error = mEncoder.WriteUint8(nrf_802154_radio_wrapper_auto_ack_get()); + break; + case VENDOR_SPINEL_PROP_HW_CAPABILITIES: + LOG_DBG("Got VENDOR_SPINEL_PROP_HW_CAPABILITIES get property request"); + error = mEncoder.WriteUint16(nrf_802154_radio_wrapper_hw_capabilities_get()); + break; + + // TODO: Implement your get properties handlers here. + default: + error = OT_ERROR_NOT_FOUND; + break; + } + + return error; +} + +otError NcpBase::VendorSetPropertyHandler(spinel_prop_key_t aPropKey) +{ + otError error = OT_ERROR_NONE; + + switch (aPropKey) + { + case VENDOR_SPINEL_PROP_AUTO_ACK_ENABLED: { + LOG_DBG("Got VENDOR_SPINEL_PROP_AUTO_ACK_ENABLED set property request"); + + uint8_t mode = 0; + SuccessOrExit(error = mDecoder.ReadUint8(mode)); + nrf_802154_radio_wrapper_auto_ack_set(mode); + } + break; + + // TODO: Implement your set properties handlers here. + default: + error = OT_ERROR_NOT_FOUND; + break; + } + +exit: + return error; +} + +} // namespace Ncp +} // namespace ot + +//------------------------------------------------------------------ +// When OPENTHREAD_ENABLE_NCP_VENDOR_HOOK is enabled, vendor code is +// expected to provide the `otNcpInit()` function. The reason behind +// this is to enable vendor code to define its own sub-class of +// `NcpBase` or `NcpHdlc`/`NcpSpi`. +// +// Example below show how to add a vendor sub-class over `NcpHdlc`. + +class NcpVendorUart : public ot::Ncp::NcpHdlc +{ + + static int SendHdlc(const uint8_t *aBuf, uint16_t aBufLength) + { + return mSendCallback(aBuf, aBufLength); + } + +public: + NcpVendorUart(ot::Instance *aInstance, otNcpHdlcSendCallback aSendCallback) + : ot::Ncp::NcpHdlc(aInstance, &NcpVendorUart::SendHdlc) + { + OT_ASSERT(aSendCallback); + mSendCallback = aSendCallback; + } + + // Add public/private methods or member variables +private: + static otNcpHdlcSendCallback mSendCallback; +}; + +otNcpHdlcSendCallback NcpVendorUart::mSendCallback; + +static OT_DEFINE_ALIGNED_VAR(sNcpVendorRaw, sizeof(NcpVendorUart), uint64_t); + + +extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback) +{ + NcpVendorUart *ncpVendor = NULL; + ot::Instance *instance = static_cast(aInstance); + + ncpVendor = new (&sNcpVendorRaw) NcpVendorUart(instance, aSendCallback); +} +#endif