diff --git a/boards/nxp/frdm_rw612/frdm_rw612.dts b/boards/nxp/frdm_rw612/frdm_rw612.dts index 47a7bbd10f85a..af0aac2372a2e 100644 --- a/boards/nxp/frdm_rw612/frdm_rw612.dts +++ b/boards/nxp/frdm_rw612/frdm_rw612.dts @@ -73,6 +73,32 @@ erase-block-size = <4096>; write-block-size = <1>; spi-max-frequency = <104000000>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 DT_SIZE_K(128)>; + }; + /* The MCUBoot swap-move algorithm uses the last 2 sectors + * of the primary slot0 for swap status and move. + */ + slot0_partition: partition@20000 { + label = "image-0"; + reg = <0x00020000 (DT_SIZE_M(3) + DT_SIZE_K(2 * 4))>; + }; + slot1_partition: partition@323000 { + label = "image-1"; + reg = <0x00323000 DT_SIZE_M(3)>; + }; + storage_partition: partition@623000 { + label = "storage"; + reg = <0x00623000 (DT_SIZE_M(58) - DT_SIZE_K(136))>; + }; + }; }; aps6404l: aps6404l@2 { compatible = "nxp,imx-flexspi-aps6404l"; diff --git a/doc/LICENSING.rst b/doc/LICENSING.rst index 6585581e9f59f..a16750cd3c3f9 100644 --- a/doc/LICENSING.rst +++ b/doc/LICENSING.rst @@ -41,3 +41,11 @@ licensing in this document. *Origin:* ThreadX *Licensing:* `MIT License`_ + +.. _BSD-3-clause: + https://opensource.org/license/bsd-3-clause + +*modules/openthread/platform/{hdlc_interface.cpp,hdlc_interface.hpp,radio_spinel.cpp}* + *Origin:* OpenThread + + *Licensing:* `BSD-3-clause`_ diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 05be8c3fd01c4..2536e2011710c 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -45,6 +45,7 @@ add_subdirectory_ifdef(CONFIG_FUEL_GAUGE fuel_gauge) add_subdirectory_ifdef(CONFIG_GNSS gnss) add_subdirectory_ifdef(CONFIG_GPIO gpio) add_subdirectory_ifdef(CONFIG_HAPTICS haptics) +add_subdirectory_ifdef(CONFIG_HDLC_RCP_IF hdlc_rcp_if) add_subdirectory_ifdef(CONFIG_HWINFO hwinfo) add_subdirectory_ifdef(CONFIG_HWSPINLOCK hwspinlock) add_subdirectory_ifdef(CONFIG_I2C i2c) diff --git a/drivers/Kconfig b/drivers/Kconfig index db80ba39a66fa..65f097f2acc97 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -38,6 +38,7 @@ source "drivers/fuel_gauge/Kconfig" source "drivers/gnss/Kconfig" source "drivers/gpio/Kconfig" source "drivers/haptics/Kconfig" +source "drivers/hdlc_rcp_if/Kconfig" source "drivers/hwinfo/Kconfig" source "drivers/hwspinlock/Kconfig" source "drivers/i2c/Kconfig" diff --git a/drivers/hdlc_rcp_if/CMakeLists.txt b/drivers/hdlc_rcp_if/CMakeLists.txt new file mode 100644 index 0000000000000..abae0200ad9b1 --- /dev/null +++ b/drivers/hdlc_rcp_if/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_HDLC_RCP_IF_NXP hdlc_rcp_if_nxp.c) diff --git a/drivers/hdlc_rcp_if/Kconfig b/drivers/hdlc_rcp_if/Kconfig new file mode 100644 index 0000000000000..c2b77cd42a65e --- /dev/null +++ b/drivers/hdlc_rcp_if/Kconfig @@ -0,0 +1,29 @@ +# Configuration options for HDLC RCP communication Interface + +# Copyright (c) 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +# +# HDLC communication Interface used by Zephyr running Openthread RCP host +# + +menuconfig HDLC_RCP_IF + bool "HDLC interface for a Zephyr Openthread RCP host" + depends on NET_L2_OPENTHREAD && !OPENTHREAD_COPROCESSOR + +if HDLC_RCP_IF + +source "drivers/hdlc_rcp_if/Kconfig.nxp" + +config HDLC_RCP_IF_DRV_NAME + string "HDLC RCP Interface Driver's name" + default "hdlc_rcp_if" + help + This option sets the driver name + +module = HDLC_RCP_IF_DRIVER +module-str = HDLC driver for Openthread RCP host +module-help = Sets log level for Openthread HDLC RCP host interface Device Drivers. +source "subsys/logging/Kconfig.template.log_config" + +endif # HDLC_RCP_IF diff --git a/drivers/hdlc_rcp_if/Kconfig.nxp b/drivers/hdlc_rcp_if/Kconfig.nxp new file mode 100644 index 0000000000000..743f422ba2399 --- /dev/null +++ b/drivers/hdlc_rcp_if/Kconfig.nxp @@ -0,0 +1,13 @@ +# Configuration options for NXP HDLC RCP communication Interface + +# Copyright (c) 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +# +# HDLC communication Interface used by Zephyr running Openthread RCP host +# + +config HDLC_RCP_IF_NXP + bool "NXP HDLC interface for Zephyr Openthread RCP host" + default y + depends on DT_HAS_NXP_HDLC_RCP_IF_ENABLED diff --git a/drivers/hdlc_rcp_if/hdlc_rcp_if_nxp.c b/drivers/hdlc_rcp_if/hdlc_rcp_if_nxp.c new file mode 100644 index 0000000000000..fb81b20337761 --- /dev/null +++ b/drivers/hdlc_rcp_if/hdlc_rcp_if_nxp.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2024, NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * IEEE 802.15.4 HDLC RCP interface. This is meant for network connectivity + * between a host and a RCP radio device. + */ + +/* -------------------------------------------------------------------------- */ +/* Includes */ +/* -------------------------------------------------------------------------- */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ +/* Definitions */ +/* -------------------------------------------------------------------------- */ + +#define DT_DRV_COMPAT nxp_hdlc_rcp_if + +#define LOG_MODULE_NAME hdlc_rcp_if_nxp +#define LOG_LEVEL CONFIG_HDLC_RCP_IF_DRIVER_LOG_LEVEL +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#define HDLC_RCP_IF_IRQ_N DT_INST_IRQ_BY_NAME(0, hdlc_rcp_if_int, irq) +#define HDLC_RCP_IF_IRQ_P DT_INST_IRQ_BY_NAME(0, hdlc_rcp_if_int, priority) +#define HDLC_RCP_IF_WAKEUP_IRQ_N DT_INST_IRQ_BY_NAME(0, wakeup_int, irq) +#define HDLC_RCP_IF_WAKEUP_IRQ_P DT_INST_IRQ_BY_NAME(0, wakeup_int, priority) + +struct ot_hdlc_rcp_context { + struct net_if *iface; + struct openthread_context *ot_context; +}; + +/* -------------------------------------------------------------------------- */ +/* Private prototypes */ +/* -------------------------------------------------------------------------- */ +extern int32_t hdlc_rcp_if_handler(void); +extern int32_t hdlc_rcp_if_wakeup_done_handler(void); + +/* -------------------------------------------------------------------------- */ +/* Private functions */ +/* -------------------------------------------------------------------------- */ + +static void hdlc_iface_init(struct net_if *iface) +{ + struct ot_hdlc_rcp_context *ctx = net_if_get_device(iface)->data; + otExtAddress eui64; + + /* HDLC RCP interface Interrupt */ + IRQ_CONNECT(HDLC_RCP_IF_IRQ_N, HDLC_RCP_IF_IRQ_P, hdlc_rcp_if_handler, 0, 0); + irq_enable(HDLC_RCP_IF_IRQ_N); + + /* Wake up done interrupt */ + IRQ_CONNECT(HDLC_RCP_IF_WAKEUP_IRQ_N, HDLC_RCP_IF_WAKEUP_IRQ_P, + hdlc_rcp_if_wakeup_done_handler, 0, 0); + irq_enable(HDLC_RCP_IF_WAKEUP_IRQ_N); + + ctx->iface = iface; + + ieee802154_init(iface); + + ctx->ot_context = net_if_l2_data(iface); + + otPlatRadioGetIeeeEui64(ctx->ot_context->instance, eui64.m8); + net_if_set_link_addr(iface, eui64.m8, OT_EXT_ADDRESS_SIZE, NET_LINK_IEEE802154); +} + +static int hdlc_register_rx_cb(hdlc_rx_callback_t hdlc_rx_callback, void *param) +{ + int ret = 0; + + ret = PLATFORM_InitHdlcInterface(hdlc_rx_callback, param); + if (ret < 0) { + LOG_ERR("HDLC RX callback registration failed"); + } + + return ret; +} + +static int hdlc_send(const uint8_t *frame, uint16_t length) +{ + int ret = 0; + + ret = PLATFORM_SendHdlcMessage((uint8_t *)frame, length); + if (ret < 0) { + LOG_ERR("HDLC send frame failed"); + } + + return ret; +} + +static int hdlc_deinit(void) +{ + int ret = 0; + + ret = PLATFORM_TerminateHdlcInterface(); + if (ret < 0) { + LOG_ERR("Failed to shutdown OpenThread controller"); + } + + return ret; +} + +static const struct hdlc_api nxp_hdlc_api = { + .iface_api.init = hdlc_iface_init, + .register_rx_cb = hdlc_register_rx_cb, + .send = hdlc_send, + .deinit = hdlc_deinit, +}; + +#define L2_CTX_TYPE NET_L2_GET_CTX_TYPE(OPENTHREAD_L2) + +#define MTU 1280 + +NET_DEVICE_DT_INST_DEFINE(0, NULL, /* Initialization Function */ + NULL, /* No PM API support */ + NULL, /* No context data */ + NULL, /* Configuration info */ + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, /* Initial priority */ + &nxp_hdlc_api, /* API interface functions */ + OPENTHREAD_L2, /* Openthread L2 */ + NET_L2_GET_CTX_TYPE(OPENTHREAD_L2), /* Openthread L2 context type */ + MTU); /* MTU size */ diff --git a/dts/arm/nxp/nxp_rw6xx_common.dtsi b/dts/arm/nxp/nxp_rw6xx_common.dtsi index b0e1e3781ac7d..2d436173c4d29 100644 --- a/dts/arm/nxp/nxp_rw6xx_common.dtsi +++ b/dts/arm/nxp/nxp_rw6xx_common.dtsi @@ -17,6 +17,7 @@ chosen { zephyr,entropy = &trng; zephyr,bt-hci = &hci; + zephyr,hdlc-rcp-if = &hdlc_rcp_if; }; cpus { @@ -521,6 +522,14 @@ interrupt-names = "hci_int", "wakeup_int"; }; + hdlc_rcp_if: hdlc_rcp_if { + compatible = "nxp,hdlc-rcp-if"; + /* first index is the hdlc_rcp_if interrupt */ + /* the second is the wake up done interrupt */ + interrupts = <90 2>, <82 2>; + interrupt-names = "hdlc_rcp_if_int", "wakeup_int"; + }; + enet: enet@138000 { compatible = "nxp,enet"; reg = <0x138000 0x700>; diff --git a/dts/bindings/hdlc_rcp_if/nxp,hdlc-rcp-if.yaml b/dts/bindings/hdlc_rcp_if/nxp,hdlc-rcp-if.yaml new file mode 100644 index 0000000000000..226368063af0b --- /dev/null +++ b/dts/bindings/hdlc_rcp_if/nxp,hdlc-rcp-if.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024, NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP HDLC RCP interface node + +compatible: "nxp,hdlc-rcp-if" + +include: base.yaml diff --git a/include/zephyr/net/hdlc_rcp_if/hdlc_rcp_if.h b/include/zephyr/net/hdlc_rcp_if/hdlc_rcp_if.h new file mode 100644 index 0000000000000..d26bf12baccd0 --- /dev/null +++ b/include/zephyr/net/hdlc_rcp_if/hdlc_rcp_if.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2024, NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public APIs of HDLC RCP communication Interface + * + * This file provide the HDLC APIs to be used by an RCP host + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief OT RCP HDLC RX callback function. + * + * @note This function is called in the radio spinel HDLC level + */ +typedef void (*hdlc_rx_callback_t)(uint8_t *data, uint16_t len, void *param); + +/** HDLC interface configuration data. */ +struct hdlc_api { + /** + * @brief HDLC interface API + */ + struct net_if_api iface_api; + + /** + * @brief Register the Spinel HDLC RX callback. + * + * @param hdlc_rx_callback pointer to the spinel HDLC RX callback + * @param param pointer to the spinel HDLC interface + * + * @retval 0 The callback was successfully registered. + * @retval -EIO The callback could not be registered. + */ + int (*register_rx_cb)(hdlc_rx_callback_t hdlc_rx_callback, void *param); + + /** + * @brief Transmit a HDLC frame + * + * + * @param frame pointer to the HDLC frame to be transmitted. + * @param length length of the HDLC frame to be transmitted. + + * @retval 0 The frame was successfully sent. + * @retval -EIO The frame could not be sent due to some unspecified + * interface error (e.g. the interface being busy). + */ + int (*send)(const uint8_t *frame, uint16_t length); + + /** + * @brief Deinitialize the device. + * + * @param none + * + * @retval 0 The interface was successfully stopped. + * @retval -EIO The interface could not be stopped. + */ + int (*deinit)(void); +}; + +/* Make sure that the interface API is properly setup inside + * HDLC interface API struct (it is the first one). + */ +BUILD_ASSERT(offsetof(struct hdlc_api, iface_api) == 0); + +#ifdef __cplusplus +} +#endif diff --git a/modules/openthread/CMakeLists.txt b/modules/openthread/CMakeLists.txt index 3900521ad8502..941c8f1f219db 100644 --- a/modules/openthread/CMakeLists.txt +++ b/modules/openthread/CMakeLists.txt @@ -249,6 +249,17 @@ list(APPEND ot_libs openthread-mtd) endif() endif() +if(CONFIG_HDLC_RCP_IF) +list(APPEND ot_libs + ot-config + openthread-platform + openthread-radio-spinel + openthread-spinel-ncp + openthread-url + openthread-hdlc +) +endif() + if(CONFIG_OPENTHREAD_SETTINGS_RAM) target_compile_options(openthread-platform-utils PRIVATE $ diff --git a/modules/openthread/platform/CMakeLists.txt b/modules/openthread/platform/CMakeLists.txt index 29e827c158491..bedd51c84eefe 100644 --- a/modules/openthread/platform/CMakeLists.txt +++ b/modules/openthread/platform/CMakeLists.txt @@ -6,10 +6,18 @@ zephyr_library_sources( entropy.c misc.c platform.c + ) + +zephyr_library_sources_ifndef(CONFIG_HDLC_RCP_IF radio.c spi.c ) +zephyr_library_sources_ifdef(CONFIG_HDLC_RCP_IF + radio_spinel.cpp + hdlc_interface.cpp + ) + zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_BLE_TCAT ble.c) zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_DIAG diag.c) zephyr_library_sources_ifdef(CONFIG_OPENTHREAD_COPROCESSOR uart.c) diff --git a/modules/openthread/platform/alarm.c b/modules/openthread/platform/alarm.c index 3cbf1d033e367..8d41c1a66f9ab 100644 --- a/modules/openthread/platform/alarm.c +++ b/modules/openthread/platform/alarm.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2024 NXP. * * SPDX-License-Identifier: Apache-2.0 */ @@ -131,3 +132,10 @@ uint16_t otPlatTimeGetXtalAccuracy(void) { return otPlatRadioGetCslAccuracy(NULL); } + +#ifdef CONFIG_HDLC_RCP_IF +uint64_t otPlatTimeGet(void) +{ + return k_ticks_to_us_floor64(k_uptime_ticks()); +} +#endif diff --git a/modules/openthread/platform/hdlc_interface.cpp b/modules/openthread/platform/hdlc_interface.cpp new file mode 100644 index 0000000000000..0d51366dbe269 --- /dev/null +++ b/modules/openthread/platform/hdlc_interface.cpp @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * Copyright (c) 2022-2024, NXP. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include "hdlc_interface.hpp" + +namespace ot +{ + +namespace Hdlc +{ + +HdlcInterface::HdlcInterface(const Url::Url &aRadioUrl) + : mEncoderBuffer(), mHdlcEncoder(mEncoderBuffer), hdlc_rx_callback(nullptr), + mReceiveFrameBuffer(nullptr), mReceiveFrameCallback(nullptr), + mReceiveFrameContext(nullptr), mHdlcSpinelDecoder(), mIsInitialized(false), + mSavedFrame(nullptr), mSavedFrameLen(0), mIsSpinelBufferFull(false), mRadioUrl(aRadioUrl) +{ + bool is_device_ready; + + hdlc_rx_callback = HdlcRxCallback; + + radio_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_hdlc_rcp_if)); + is_device_ready = device_is_ready(radio_dev); + __ASSERT(is_device_ready == true, "Radio device is not ready"); + + hdlc_api = (struct hdlc_api *)radio_dev->api; + __ASSERT(hdlc_api != NULL, "Radio device initialization failed"); +} + +HdlcInterface::~HdlcInterface(void) +{ +} + +otError HdlcInterface::Init(ReceiveFrameCallback aCallback, void *aCallbackContext, + RxFrameBuffer &aFrameBuffer) +{ + int status; + otError error = OT_ERROR_NONE; + + if (!mIsInitialized) { + /* Event initialization */ + k_event_init(&spinel_hdlc_event); + + /* Read/Write semaphores initialization */ + k_mutex_init(&spinel_hdlc_wr_lock); + k_mutex_init(&spinel_hdlc_rd_lock); + + /* Message queue initialization */ + k_msgq_init(&spinel_hdlc_msgq, &spinel_hdlc_msgq_buffer, 1, 1); + + mHdlcSpinelDecoder.Init(mRxSpinelFrameBuffer, HandleHdlcFrame, this); + mReceiveFrameCallback = aCallback; + mReceiveFrameContext = aCallbackContext; + mReceiveFrameBuffer = &aFrameBuffer; + + /* Initialize the HDLC interface */ + status = hdlc_api->register_rx_cb(hdlc_rx_callback, this); + if (status == 0) { + mIsInitialized = true; + } else { + otLogDebgPlat("Failed to initialize HDLC interface %d", status); + error = OT_ERROR_FAILED; + } + } + + return error; +} + +void HdlcInterface::Deinit(void) +{ + int status; + + status = hdlc_api->deinit(); + if (status == 0) { + mIsInitialized = false; + } else { + otLogDebgPlat("Failed to terminate HDLC interface %d", status); + } + + mReceiveFrameCallback = nullptr; + mReceiveFrameContext = nullptr; + mReceiveFrameBuffer = nullptr; +} + +void HdlcInterface::Process(const void *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + TryReadAndDecode(false); +} + +otError HdlcInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + + /* Protect concurrent Send operation to avoid any buffer corruption */ + if (k_mutex_lock(&spinel_hdlc_wr_lock, K_FOREVER) != 0) { + error = OT_ERROR_FAILED; + goto exit; + } + + assert(mEncoderBuffer.IsEmpty()); + + SuccessOrExit(error = mHdlcEncoder.BeginFrame()); + SuccessOrExit(error = mHdlcEncoder.Encode(aFrame, aLength)); + SuccessOrExit(error = mHdlcEncoder.EndFrame()); + otLogDebgPlat("frame len to send = %d/%d", mEncoderBuffer.GetLength(), aLength); + SuccessOrExit(error = Write(mEncoderBuffer.GetFrame(), mEncoderBuffer.GetLength())); + +exit: + + k_mutex_unlock(&spinel_hdlc_wr_lock); + + if (error != OT_ERROR_NONE) { + otLogCritPlat("error = 0x%x", error); + } + + return error; +} + +otError HdlcInterface::WaitForFrame(uint64_t aTimeoutUs) +{ + otError error = OT_ERROR_RESPONSE_TIMEOUT; + uint32_t timeout_ms = (uint32_t)(aTimeoutUs / 1000U); + uint32_t event_bits; + + do { + /* Wait for k_spinel_hdlc_frame_ready_event indicating a frame has been received */ + event_bits = k_event_wait(&spinel_hdlc_event, + HdlcInterface::k_spinel_hdlc_frame_ready_event, false, + K_MSEC(timeout_ms)); + k_event_clear(&spinel_hdlc_event, HdlcInterface::k_spinel_hdlc_frame_ready_event); + if ((event_bits & HdlcInterface::k_spinel_hdlc_frame_ready_event) != 0) { + /* Event is set, it means a frame has been received and can be decoded + * Note: The event is set whenever a frame is received, even when ot task is + * not blocked in WaitForFrame it means the event could have been set for a + * previous frame If TryReadAndDecode returns 0, it means the event was set + * for a previous frame, so we loop and wait again If TryReadAndDecode + * returns anything else, it means there's real data to process, so we can + * exit from WaitForFrame */ + + if (TryReadAndDecode(true) != 0) { + error = OT_ERROR_NONE; + break; + } + } else { + /* The event wasn't set within the timeout range, we exit with a timeout + * error */ + otLogDebgPlat("WaitForFrame timeout"); + break; + } + } while (true); + + return error; +} + +void HdlcInterface::ProcessRxData(uint8_t *data, uint16_t len) +{ + uint8_t event; + uint32_t remainingRxBufferSize = 0; + + k_mutex_lock(&spinel_hdlc_rd_lock, K_FOREVER); + + do { + /* Check if we have enough space to store the frame in the buffer */ + remainingRxBufferSize = + mRxSpinelFrameBuffer.GetFrameMaxLength() - mRxSpinelFrameBuffer.GetLength(); + otLogDebgPlat("remainingRxBufferSize = %u", remainingRxBufferSize); + + if (remainingRxBufferSize >= len) { + mHdlcSpinelDecoder.Decode(data, len); + break; + } else { + mIsSpinelBufferFull = true; + otLogDebgPlat("Spinel buffer full remainingRxLen = %u", + remainingRxBufferSize); + + /* Send a signal to the openthread task to indicate to empty the spinel + * buffer */ + otTaskletsSignalPending(NULL); + + /* Give the mutex */ + k_mutex_unlock(&spinel_hdlc_rd_lock); + + /* Lock the task here until the spinel buffer becomes empty */ + k_msgq_get(&spinel_hdlc_msgq, &event, K_FOREVER); + + /* take the mutex again */ + k_mutex_lock(&spinel_hdlc_rd_lock, K_FOREVER); + } + } while (true); + + k_mutex_unlock(&spinel_hdlc_rd_lock); +} + +otError HdlcInterface::Write(const uint8_t *aFrame, uint16_t aLength) +{ + otError otResult = OT_ERROR_NONE; + int ret; + + otLogDebgPlat("Send tx frame len = %d", aLength); + + ret = hdlc_api->send((uint8_t *)aFrame, aLength); + if (ret != 0) { + otResult = OT_ERROR_FAILED; + otLogCritPlat("Error send %d", ret); + } + + /* Always clear the encoder */ + mEncoderBuffer.Clear(); + return otResult; +} + +uint32_t HdlcInterface::TryReadAndDecode(bool fullRead) +{ + uint32_t totalBytesRead = 0; + uint32_t i = 0; + uint8_t event = 1; + uint8_t *oldFrame = mSavedFrame; + uint16_t oldLen = mSavedFrameLen; + otError savedFrameFound = OT_ERROR_NONE; + + (void)fullRead; + + k_mutex_lock(&spinel_hdlc_rd_lock, K_FOREVER); + + savedFrameFound = mRxSpinelFrameBuffer.GetNextSavedFrame(mSavedFrame, mSavedFrameLen); + + while (savedFrameFound == OT_ERROR_NONE) { + /* Copy the data to the ot frame buffer */ + for (i = 0; i < mSavedFrameLen; i++) { + if (mReceiveFrameBuffer->WriteByte(mSavedFrame[i]) != OT_ERROR_NONE) { + mReceiveFrameBuffer->UndoLastWrites(i); + /* No more space restore the mSavedFrame to the previous frame */ + mSavedFrame = oldFrame; + mSavedFrameLen = oldLen; + /* Signal the ot task to re-try later */ + otTaskletsSignalPending(NULL); + otLogDebgPlat("No more space"); + k_mutex_unlock(&spinel_hdlc_rd_lock); + return totalBytesRead; + } + totalBytesRead++; + } + otLogDebgPlat("Frame len %d consumed", mSavedFrameLen); + mReceiveFrameCallback(mReceiveFrameContext); + oldFrame = mSavedFrame; + oldLen = mSavedFrameLen; + savedFrameFound = + mRxSpinelFrameBuffer.GetNextSavedFrame(mSavedFrame, mSavedFrameLen); + } + + if (savedFrameFound != OT_ERROR_NONE) { + /* No more frame saved clear the buffer */ + mRxSpinelFrameBuffer.ClearSavedFrames(); + /* If the spinel queue was locked */ + if (mIsSpinelBufferFull) { + mIsSpinelBufferFull = false; + /* Send an event to unlock the task */ + k_msgq_put(&spinel_hdlc_msgq, (void *)&event, K_FOREVER); + } + } + + k_mutex_unlock(&spinel_hdlc_rd_lock); + + return totalBytesRead; +} + +void HdlcInterface::HandleHdlcFrame(void *aContext, otError aError) +{ + static_cast(aContext)->HandleHdlcFrame(aError); +} + +void HdlcInterface::HandleHdlcFrame(otError aError) +{ + uint8_t *buf = mRxSpinelFrameBuffer.GetFrame(); + uint16_t bufLength = mRxSpinelFrameBuffer.GetLength(); + + otDumpDebgPlat("RX FRAME", buf, bufLength); + + if (aError == OT_ERROR_NONE && bufLength > 0) { + if ((buf[0] & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG) { + otLogDebgPlat("Frame correctly received %d", bufLength); + /* Save the frame */ + mRxSpinelFrameBuffer.SaveFrame(); + + /* Send a signal to the openthread task to indicate that a spinel data is + * pending */ + otTaskletsSignalPending(NULL); + + /* Notify WaitForFrame that a frame is ready */ + /* TBC: k_event_post or k_event_set */ + k_event_set(&spinel_hdlc_event, + HdlcInterface::k_spinel_hdlc_frame_ready_event); + } else { + /* Give a chance to a child class to process this HDLC content + * The current class treats it as an error case because it's supposed to + * receive only Spinel frames If there's a need to share a same transport + * interface with another protocol, a child class must override this method + */ + HandleUnknownHdlcContent(buf, bufLength); + + /* Not a Spinel frame, discard */ + mRxSpinelFrameBuffer.DiscardFrame(); + } + } else { + otLogCritPlat("Frame will be discarded error = 0x%x", aError); + mRxSpinelFrameBuffer.DiscardFrame(); + } +} + +void HdlcInterface::HdlcRxCallback(uint8_t *data, uint16_t len, void *param) +{ + static_cast(param)->ProcessRxData(data, len); +} + +void HdlcInterface::HandleUnknownHdlcContent(uint8_t *buffer, uint16_t len) +{ + OT_UNUSED_VARIABLE(buffer); + OT_UNUSED_VARIABLE(len); + otLogCritPlat("Unsupported HDLC content received (not Spinel)"); + assert(0); +} + +void HdlcInterface::OnRcpReset(void) +{ + mHdlcSpinelDecoder.Reset(); +} + +} // namespace Hdlc + +} /* namespace ot */ diff --git a/modules/openthread/platform/hdlc_interface.hpp b/modules/openthread/platform/hdlc_interface.hpp new file mode 100644 index 0000000000000..2e65a81c135ee --- /dev/null +++ b/modules/openthread/platform/hdlc_interface.hpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * Copyright (c) 2022-2024, NXP. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OT_SPINEL_HDLC_HPP_ +#define OT_SPINEL_HDLC_HPP_ + +#include +#include + +#include "lib/url/url.hpp" +#include "lib/hdlc/hdlc.hpp" +#include "lib/spinel/spinel.h" +#include "lib/spinel/spinel_interface.hpp" + +namespace ot { + +namespace Hdlc { + +typedef uint8_t HdlcSpinelContext; + +/** + * This class defines an HDLC spinel interface to the Radio Co-processor (RCP). + * + */ +class HdlcInterface : public ot::Spinel::SpinelInterface +{ +public: + /** + * This constructor initializes the object. + * + * @param[in] aRadioUrl Radio url + * + */ + HdlcInterface(const Url::Url &aRadioUrl); + + /** + * This destructor deinitializes the object. + * + */ + virtual ~HdlcInterface(void); + + /** + * This method initializes the HDLC interface. + * + */ + otError Init(ReceiveFrameCallback aCallback, void *aCallbackContext, RxFrameBuffer &aFrameBuffer); + + /** + * This method deinitializes the HDLC interface. + * + */ + void Deinit(void); + + /** + * This method performs radio driver processing. + * + * @param[in] aInstance The ot instance + * + */ + void Process(const void *aInstance); + + /** + * This method encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket. + * + * This is blocking call, i.e., if the socket is not writable, this method waits for it to become writable for + * up to `kMaxWaitTime` interval. + * + * @param[in] aFrame A pointer to buffer containing the spinel frame to send. + * @param[in] aLength The length (number of bytes) in the frame. + * + * @retval OT_ERROR_NONE Successfully encoded and sent the spinel frame. + * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. + * @retval OT_ERROR_FAILED Failed to send due to socket not becoming writable within `kMaxWaitTime`. + * + */ + otError SendFrame(const uint8_t *aFrame, uint16_t aLength); + + /** + * This method waits for receiving part or all of spinel frame within specified timeout. + * + * @param[in] aTimeoutUs The timeout value in microseconds. + * + * @retval OT_ERROR_NONE Part or all of spinel frame is received. + * @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeoutUs. + * + */ + otError WaitForFrame(uint64_t aTimeoutUs); + + /** + * This method is called by the HDLC RX Callback when a HDLC message has been received + * + * It will decode and store the Spinel frames in a temporary Spinel frame buffer (mRxSpinelFrameBuffer) + * This buffer will be then copied to the OpenThread Spinel frame buffer, from the OpenThread task context + * + * @param[in] data A pointer to buffer containing the HDLC message to decode. + * @param[in] len The length (number of bytes) in the message. + */ + void ProcessRxData(uint8_t *data, uint16_t len); + + /** + * This method is called when RCP failure detected and resets internal states of the interface. + * + */ + void OnRcpReset(void); + + /** + * This method is called when RCP is reset to recreate the connection with it. + * Intentionally empty. + * + */ + otError ResetConnection(void) { return OT_ERROR_NONE; } + + /** + * This method hardware resets the RCP. + * + * @retval OT_ERROR_NONE Successfully reset the RCP. + * @retval OT_ERROR_NOT_IMPLEMENTED The hardware reset is not implemented. + * + */ + otError HardwareReset(void) { return OT_ERROR_NOT_IMPLEMENTED; } + +private: + + enum + { + /* HDLC encoder buffer must be larger than the max spinel frame size to be able to handle the HDLC overhead + * As a host, a TX frame will always contain only 1 spinel frame + HDLC overhead + * Sizing the buffer for 2 spinel frames should be large enough to handle this */ + kEncoderBufferSize = SPINEL_FRAME_MAX_SIZE * 2, + kMaxMultiFrameSize = 2048, + k_spinel_hdlc_frame_ready_event = 1 << 0, + }; + + ot::Spinel::FrameBuffer mEncoderBuffer; + ot::Hdlc::Encoder mHdlcEncoder; + hdlc_rx_callback_t hdlc_rx_callback; + ot::Spinel::SpinelInterface::RxFrameBuffer *mReceiveFrameBuffer; + ot::Spinel::SpinelInterface::ReceiveFrameCallback mReceiveFrameCallback; + void *mReceiveFrameContext; + ot::Spinel::MultiFrameBuffer mRxSpinelFrameBuffer; + ot::Hdlc::Decoder mHdlcSpinelDecoder; + bool mIsInitialized; + uint8_t *mSavedFrame; + uint16_t mSavedFrameLen; + bool mIsSpinelBufferFull; + const Url::Url &mRadioUrl; + + /* Spinel HDLC interface */ + const struct device *radio_dev; + struct hdlc_api *hdlc_api; + + struct k_event spinel_hdlc_event; + struct k_mutex spinel_hdlc_wr_lock; + struct k_mutex spinel_hdlc_rd_lock; + struct k_msgq spinel_hdlc_msgq; + char spinel_hdlc_msgq_buffer; + + otError Write(const uint8_t *aFrame, uint16_t aLength); + uint32_t TryReadAndDecode(bool fullRead); + void HandleHdlcFrame(otError aError); + static void HandleHdlcFrame(void *aContext, otError aError); + static void HdlcRxCallback(uint8_t *data, uint16_t len, void *param); + + const otRcpInterfaceMetrics *GetRcpInterfaceMetrics(void) const { return nullptr;} + uint32_t GetBusSpeed(void) const { return 0; } + void UpdateFdSet(void *aMainloopContext) + { + (void)aMainloopContext; + } + +protected: + virtual void HandleUnknownHdlcContent(uint8_t *buffer, uint16_t len); +}; + +} // namespace Zephyr + +} // namespace ot + +#endif // OT_SPINEL_HDLC_HPP_ diff --git a/modules/openthread/platform/radio_spinel.cpp b/modules/openthread/platform/radio_spinel.cpp new file mode 100644 index 0000000000000..95c05e8858087 --- /dev/null +++ b/modules/openthread/platform/radio_spinel.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2021, The OpenThread Authors. + * Copyright (c) 2022-2024, NXP. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements OpenThread platform driver API in openthread/platform/radio.h. + * + */ + +#include +#include +#include +#include +#include +#include "hdlc_interface.hpp" + +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_OPENTHREAD_L2_LOG_LEVEL); +#include +#include +#include + +enum pending_events { + PENDING_EVENT_FRAME_TO_SEND, /* There is a tx frame to send */ + PENDING_EVENT_COUNT /* Keep last */ +}; + +ATOMIC_DEFINE(pending_events, PENDING_EVENT_COUNT); +K_FIFO_DEFINE(tx_pkt_fifo); + +static ot::Spinel::RadioSpinel *psRadioSpinel; +static ot::Url::Url *psRadioUrl; +static ot::Hdlc::HdlcInterface *pSpinelInterface; +static ot::Spinel::SpinelDriver *psSpinelDriver; + +static const otRadioCaps sRequiredRadioCaps = +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 + OT_RADIO_CAPS_TRANSMIT_SEC | OT_RADIO_CAPS_TRANSMIT_TIMING | +#endif + OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_TRANSMIT_RETRIES | OT_RADIO_CAPS_CSMA_BACKOFF; + +static inline bool is_pending_event_set(enum pending_events event) +{ + return atomic_test_bit(pending_events, event); +} + +static void set_pending_event(enum pending_events event) +{ + atomic_set_bit(pending_events, event); + otSysEventSignalPending(); +} + +static void reset_pending_event(enum pending_events event) +{ + atomic_clear_bit(pending_events, event); +} + +static void openthread_handle_frame_to_send(otInstance *instance, struct net_pkt *pkt) +{ + struct net_buf *buf; + otMessage *message; + otMessageSettings settings; + + NET_DBG("Sending Ip6 packet to ot stack"); + + settings.mPriority = OT_MESSAGE_PRIORITY_NORMAL; + settings.mLinkSecurityEnabled = true; + message = otIp6NewMessage(instance, &settings); + if (message == NULL) { + goto exit; + } + + for (buf = pkt->buffer; buf; buf = buf->frags) { + if (otMessageAppend(message, buf->data, buf->len) != OT_ERROR_NONE) { + NET_ERR("Error while appending to otMessage"); + otMessageFree(message); + goto exit; + } + } + + if (otIp6Send(instance, message) != OT_ERROR_NONE) { + NET_ERR("Error while calling otIp6Send"); + goto exit; + } + +exit: + net_pkt_unref(pkt); +} + +void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->GetIeeeEui64(aIeeeEui64)); +} + +void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->SetPanId(panid)); +} + +void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = aAddress->m8[sizeof(addr) - 1 - i]; + } + + SuccessOrDie(psRadioSpinel->SetExtendedAddress(addr)); +} + +void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->SetShortAddress(aAddress)); +} + +void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->SetPromiscuous(aEnable)); +} + +bool otPlatRadioIsEnabled(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->IsEnabled(); +} + +otError otPlatRadioEnable(otInstance *aInstance) +{ + return psRadioSpinel->Enable(aInstance); +} + +otError otPlatRadioDisable(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->Disable(); +} + +otError otPlatRadioSleep(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->Sleep(); +} + +otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->Receive(aChannel); +} + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->Transmit(*aFrame); +} + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return &psRadioSpinel->GetTransmitFrame(); +} + +int8_t otPlatRadioGetRssi(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetRssi(); +} + +otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetRadioCaps(); +} + +const char *otPlatRadioGetVersionString(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetVersion(); +} + +bool otPlatRadioGetPromiscuous(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->IsPromiscuous(); +} + +void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->EnableSrcMatch(aEnable)); +} + +otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->AddSrcMatchShortEntry(aShortAddress); +} + +otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i]; + } + + return psRadioSpinel->AddSrcMatchExtEntry(addr); +} + +otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->ClearSrcMatchShortEntry(aShortAddress); +} + +otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + otExtAddress addr; + + for (size_t i = 0; i < sizeof(addr); i++) { + addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i]; + } + + return psRadioSpinel->ClearSrcMatchExtEntry(addr); +} + +void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->ClearSrcMatchShortEntries()); +} + +void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + SuccessOrDie(psRadioSpinel->ClearSrcMatchExtEntries()); +} + +otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->EnergyScan(aScanChannel, aScanDuration); +} + +otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower) +{ + otError error; + + OT_UNUSED_VARIABLE(aInstance); + VerifyOrExit(aPower != NULL, error = OT_ERROR_INVALID_ARGS); + error = psRadioSpinel->GetTransmitPower(*aPower); + +exit: + return error; +} + +otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->SetTransmitPower(aPower); +} + +otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold) +{ + otError error; + + OT_UNUSED_VARIABLE(aInstance); + VerifyOrExit(aThreshold != NULL, error = OT_ERROR_INVALID_ARGS); + error = psRadioSpinel->GetCcaEnergyDetectThreshold(*aThreshold); + +exit: + return error; +} + +otError otPlatRadioSetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t aThreshold) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->SetCcaEnergyDetectThreshold(aThreshold); +} + +int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetReceiveSensitivity(); +} + +#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE +otError otPlatRadioSetCoexEnabled(otInstance *aInstance, bool aEnabled) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->SetCoexEnabled(aEnabled); +} + +bool otPlatRadioIsCoexEnabled(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->IsCoexEnabled(); +} + +otError otPlatRadioGetCoexMetrics(otInstance *aInstance, otRadioCoexMetrics *aCoexMetrics) +{ + OT_UNUSED_VARIABLE(aInstance); + + otError error = OT_ERROR_NONE; + + VerifyOrExit(aCoexMetrics != NULL, error = OT_ERROR_INVALID_ARGS); + + error = psRadioSpinel->GetCoexMetrics(*aCoexMetrics); + +exit: + return error; +} +#endif + +#if OPENTHREAD_CONFIG_DIAG_ENABLE +otError otPlatDiagProcess(otInstance *aInstance, int argc, char *argv[], char *aOutput, + size_t aOutputMaxLen) +{ + /* Deliver the platform specific diags commands to radio only ncp */ + OT_UNUSED_VARIABLE(aInstance); + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE] = {'\0'}; + char *cur = cmd; + char *end = cmd + sizeof(cmd); + + for (int index = 0; index < argc; index++) { + cur += snprintf(cur, static_cast(end - cur), "%s ", argv[index]); + } + + return psRadioSpinel->PlatDiagProcess(cmd, aOutput, aOutputMaxLen); +} + +void otPlatDiagModeSet(bool aMode) +{ + SuccessOrExit(psRadioSpinel->PlatDiagProcess(aMode ? "start" : "stop", NULL, 0)); + psRadioSpinel->SetDiagEnabled(aMode); + +exit: + return; +} + +bool otPlatDiagModeGet(void) +{ + return psRadioSpinel->IsDiagEnabled(); +} + +void otPlatDiagTxPowerSet(int8_t aTxPower) +{ + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + + snprintf(cmd, sizeof(cmd), "power %d", aTxPower); + SuccessOrExit(psRadioSpinel->PlatDiagProcess(cmd, NULL, 0)); + +exit: + return; +} + +void otPlatDiagChannelSet(uint8_t aChannel) +{ + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + + snprintf(cmd, sizeof(cmd), "channel %d", aChannel); + SuccessOrExit(psRadioSpinel->PlatDiagProcess(cmd, NULL, 0)); + +exit: + return; +} + +void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otError aError) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aFrame); + OT_UNUSED_VARIABLE(aError); +} + +void otPlatDiagAlarmCallback(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); +} +#endif /* OPENTHREAD_CONFIG_DIAG_ENABLE */ + +uint32_t otPlatRadioGetSupportedChannelMask(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetRadioChannelMask(false); +} + +uint32_t otPlatRadioGetPreferredChannelMask(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetRadioChannelMask(true); +} + +otRadioState otPlatRadioGetState(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetState(); +} + +void otPlatRadioSetMacKey(otInstance *aInstance, uint8_t aKeyIdMode, uint8_t aKeyId, + const otMacKeyMaterial *aPrevKey, const otMacKeyMaterial *aCurrKey, + const otMacKeyMaterial *aNextKey, otRadioKeyType aKeyType) +{ + SuccessOrDie(psRadioSpinel->SetMacKey(aKeyIdMode, aKeyId, aPrevKey, aCurrKey, aNextKey)); + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aKeyType); +} + +void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + SuccessOrDie(psRadioSpinel->SetMacFrameCounter(aMacFrameCounter, false)); + OT_UNUSED_VARIABLE(aInstance); +} + +void otPlatRadioSetMacFrameCounterIfLarger(otInstance *aInstance, uint32_t aMacFrameCounter) +{ + SuccessOrDie(psRadioSpinel->SetMacFrameCounter(aMacFrameCounter, true)); + OT_UNUSED_VARIABLE(aInstance); +} + +uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetNow(); +} + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +uint8_t otPlatRadioGetCslAccuracy(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return psRadioSpinel->GetCslAccuracy(); +} +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +uint8_t otPlatRadioGetCslUncertainty(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return psRadioSpinel->GetCslUncertainty(); +} +#endif + +otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, + int8_t aMaxPower) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->SetChannelMaxTransmitPower(aChannel, aMaxPower); +} + +otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->SetRadioRegion(aRegionCode); +} + +otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode) +{ + OT_UNUSED_VARIABLE(aInstance); + return psRadioSpinel->GetRadioRegion(aRegionCode); +} + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE +otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otLinkMetrics aLinkMetrics, + const otShortAddress aShortAddress, + const otExtAddress *aExtAddress) +{ + OT_UNUSED_VARIABLE(aInstance); + + return psRadioSpinel->ConfigureEnhAckProbing(aLinkMetrics, aShortAddress, *aExtAddress); +} +#endif + +otError otPlatRadioReceiveAt(otInstance *aInstance, uint8_t aChannel, uint32_t aStart, + uint32_t aDuration) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aChannel); + OT_UNUSED_VARIABLE(aStart); + OT_UNUSED_VARIABLE(aDuration); + return OT_ERROR_NOT_IMPLEMENTED; +} + +extern "C" void platformRadioInit(void) +{ + spinel_iid_t iidList[ot::Spinel::kSpinelHeaderMaxNumIid]; + struct ot::Spinel::RadioSpinelCallbacks callbacks; + + iidList[0] = 0; + + psRadioSpinel = new ot::Spinel::RadioSpinel(); + psSpinelDriver = new ot::Spinel::SpinelDriver(); + + psRadioUrl = new ot::Url::Url(); + pSpinelInterface = new ot::Hdlc::HdlcInterface(*psRadioUrl); + + OT_UNUSED_VARIABLE(psSpinelDriver->Init(*pSpinelInterface, true /* aSoftwareReset */, + iidList, OT_ARRAY_LENGTH(iidList))); + + memset(&callbacks, 0, sizeof(callbacks)); +#if OPENTHREAD_CONFIG_DIAG_ENABLE + callbacks.mDiagReceiveDone = otPlatDiagRadioReceiveDone; + callbacks.mDiagTransmitDone = otPlatDiagRadioTransmitDone; +#endif /* OPENTHREAD_CONFIG_DIAG_ENABLE */ + callbacks.mEnergyScanDone = otPlatRadioEnergyScanDone; + callbacks.mReceiveDone = otPlatRadioReceiveDone; + callbacks.mTransmitDone = otPlatRadioTxDone; + callbacks.mTxStarted = otPlatRadioTxStarted; + + psRadioSpinel->SetCallbacks(callbacks); + +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && \ + OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + bool aEnableRcpTimeSync = true; +#else + bool aEnableRcpTimeSync = false; +#endif + psRadioSpinel->Init(false /*aSkipRcpCompatibilityCheck*/, true /*aSoftwareReset*/, + psSpinelDriver, sRequiredRadioCaps, aEnableRcpTimeSync); + psRadioSpinel->SetTimeSyncState(true); +} + +extern "C" void platformRadioDeinit(void) +{ + psRadioSpinel->Deinit(); + psSpinelDriver->Deinit(); +} + +extern "C" int notify_new_rx_frame(struct net_pkt *pkt) +{ + /* The RX frame is handled by Openthread stack */ + net_pkt_unref(pkt); + + return 0; +} + +extern "C" int notify_new_tx_frame(struct net_pkt *pkt) +{ + k_fifo_put(&tx_pkt_fifo, pkt); + set_pending_event(PENDING_EVENT_FRAME_TO_SEND); + + return 0; +} + +extern "C" void platformRadioProcess(otInstance *aInstance) +{ + if (is_pending_event_set(PENDING_EVENT_FRAME_TO_SEND)) { + struct net_pkt *evt_pkt; + + reset_pending_event(PENDING_EVENT_FRAME_TO_SEND); + while ((evt_pkt = (struct net_pkt *)k_fifo_get(&tx_pkt_fifo, K_NO_WAIT)) != NULL) { + openthread_handle_frame_to_send(aInstance, evt_pkt); + } + } + + psSpinelDriver->Process(aInstance); + psRadioSpinel->Process(aInstance); +} diff --git a/samples/net/sockets/echo_client/overlay-ot-rcp-host-nxp.conf b/samples/net/sockets/echo_client/overlay-ot-rcp-host-nxp.conf new file mode 100644 index 0000000000000..b1d03bd458344 --- /dev/null +++ b/samples/net/sockets/echo_client/overlay-ot-rcp-host-nxp.conf @@ -0,0 +1,57 @@ +CONFIG_REQUIRES_FULL_LIBC=y + +# CPP library +CONFIG_CPP=y + +# Disable TCP and IPv4 (TCP disabled to avoid heavy traffic) +CONFIG_NET_TCP=n +CONFIG_NET_IPV4=n + +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="" + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_INIT_STACKS=y + + +# Enable OpenThread shell +CONFIG_SHELL=y +CONFIG_OPENTHREAD_SHELL=y +CONFIG_SHELL_STACK_SIZE=3072 + +# IMU & FW loader +CONFIG_NXP_RF_IMU=y +CONFIG_NXP_FW_LOADER=y + +CONFIG_NET_L2_OPENTHREAD=y + +# Enable Openthread rcp host +CONFIG_HDLC_RCP_IF=y + +# Enable OpenThread features set +CONFIG_OPENTHREAD_THREAD_STACK_SIZE=4096 +CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y +CONFIG_OPENTHREAD_DEBUG=y +CONFIG_OPENTHREAD_L2_DEBUG=y +CONFIG_OPENTHREAD_L2_LOG_LEVEL_INF=y + +CONFIG_OPENTHREAD_CHANNEL=26 +CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" + +CONFIG_NET_CONFIG_MY_IPV6_ADDR="fdde:ad00:beef::1" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="fdde:ad00:beef::2" +CONFIG_NET_CONFIG_INIT_TIMEOUT=60 + +# mbedTLS tweaks +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=768 + +# A sample configuration to enable Thread Joiner, uncomment if needed +#CONFIG_OPENTHREAD_JOINER=y +#CONFIG_OPENTHREAD_JOINER_AUTOSTART=y + +# Enable diagnostic module, uncomment if needed +#CONFIG_OPENTHREAD_DIAG=y diff --git a/samples/net/sockets/echo_client/sample.yaml b/samples/net/sockets/echo_client/sample.yaml index 9c4dce656044a..47d0b6364a61e 100644 --- a/samples/net/sockets/echo_client/sample.yaml +++ b/samples/net/sockets/echo_client/sample.yaml @@ -97,6 +97,16 @@ tests: - openthread platform_allow: frdm_kw41z filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC + sample.net.sockets.echo_client.rw612_openthread_rcp_host: + extra_args: OVERLAY_CONFIG="overlay-ot-rcp-host-nxp.conf" + slow: true + tags: + - net + - openthread + platform_allow: + - frdm_rw612 + - rd_rw612_bga + filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC sample.net.sockets.echo_client.userspace: extra_args: - CONFIG_USERSPACE=y diff --git a/samples/net/sockets/echo_server/overlay-ot-rcp-host-nxp.conf b/samples/net/sockets/echo_server/overlay-ot-rcp-host-nxp.conf new file mode 100644 index 0000000000000..ebd1e7f4102ae --- /dev/null +++ b/samples/net/sockets/echo_server/overlay-ot-rcp-host-nxp.conf @@ -0,0 +1,52 @@ +CONFIG_REQUIRES_FULL_LIBC=y + +# Disable TCP and IPv4 (TCP disabled to avoid heavy traffic) +CONFIG_NET_TCP=n +CONFIG_NET_IPV4=n + +CONFIG_NET_IPV6_NBR_CACHE=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_CONFIG_NEED_IPV4=n +CONFIG_NET_CONFIG_MY_IPV4_ADDR="" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="" + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_INIT_STACKS=y + +# Enable OpenThread shell +CONFIG_SHELL=y +CONFIG_OPENTHREAD_SHELL=y +CONFIG_SHELL_STACK_SIZE=3072 + +# IMU & FW loader +CONFIG_NXP_RF_IMU=y +CONFIG_NXP_FW_LOADER=y + +CONFIG_NET_L2_OPENTHREAD=y + +# Enable Openthread rcp host +CONFIG_HDLC_RCP_IF=y + +# Enable OpenThread features set +CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y +CONFIG_OPENTHREAD_THREAD_STACK_SIZE=4096 +CONFIG_OPENTHREAD_DEBUG=y +CONFIG_OPENTHREAD_L2_DEBUG=y +CONFIG_OPENTHREAD_L2_LOG_LEVEL_INF=y + +CONFIG_OPENTHREAD_CHANNEL=26 +CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" + +CONFIG_NET_CONFIG_MY_IPV6_ADDR="fdde:ad00:beef::2" +CONFIG_NET_CONFIG_PEER_IPV6_ADDR="fdde:ad00:beef::1" +CONFIG_NET_CONFIG_INIT_TIMEOUT=60 + +# mbedTLS tweaks +CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=768 + +# A sample configuration to enable Thread Commissioner, uncomment if needed +#CONFIG_OPENTHREAD_COMMISSIONER=y + +# Enable diagnostic module, uncomment if needed +#CONFIG_OPENTHREAD_DIAG=y diff --git a/samples/net/sockets/echo_server/sample.yaml b/samples/net/sockets/echo_server/sample.yaml index 0bd76501d5cdf..65bec0267a391 100644 --- a/samples/net/sockets/echo_server/sample.yaml +++ b/samples/net/sockets/echo_server/sample.yaml @@ -112,6 +112,16 @@ tests: - openthread platform_allow: frdm_kw41z filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC + sample.net.sockets.echo_server.rw612_openthread_rcp_host: + extra_args: OVERLAY_CONFIG="overlay-ot-rcp-host-nxp.conf" + slow: true + tags: + - net + - openthread + platform_allow: + - frdm_rw612 + - rd_rw612_bga + filter: CONFIG_FULL_LIBC_SUPPORTED and not CONFIG_NATIVE_LIBC sample.net.sockets.echo_server.e1000: extra_args: EXTRA_CONF_FILE="overlay-e1000.conf" tags: net diff --git a/soc/nxp/rw/soc.h b/soc/nxp/rw/soc.h index 13f451493e91e..1e29e78cd5314 100644 --- a/soc/nxp/rw/soc.h +++ b/soc/nxp/rw/soc.h @@ -19,6 +19,9 @@ #define ble_hci_handler BLE_MCI_WAKEUP0_DriverIRQHandler #define ble_wakeup_done_handler BLE_MCI_WAKEUP_DONE0_DriverIRQHandler +#define hdlc_rcp_if_handler BLE_MCI_WAKEUP0_DriverIRQHandler +#define hdlc_rcp_if_wakeup_done_handler BLE_MCI_WAKEUP_DONE0_DriverIRQHandler + /* Wrapper Function to deal with SDK differences in power API */ static inline void EnableDeepSleepIRQ(IRQn_Type irq) { @@ -29,5 +32,4 @@ static inline void EnableDeepSleepIRQ(IRQn_Type irq) int flexspi_clock_set_freq(uint32_t clock_name, uint32_t rate); #endif - #endif /* _SOC__H_ */ diff --git a/subsys/net/l2/openthread/openthread.c b/subsys/net/l2/openthread/openthread.c index c70adc0714c01..c70a7e5d4e262 100644 --- a/subsys/net/l2/openthread/openthread.c +++ b/subsys/net/l2/openthread/openthread.c @@ -101,7 +101,7 @@ LOG_MODULE_REGISTER(net_l2_openthread, CONFIG_OPENTHREAD_L2_LOG_LEVEL); #define OT_POLL_PERIOD 0 #endif -#define PACKAGE_NAME "Zephyr" +#define ZEPHYR_PACKAGE_NAME "Zephyr" #define PACKAGE_VERSION KERNEL_VERSION_STRING extern void platformShellInit(otInstance *aInstance); @@ -159,12 +159,14 @@ static int ncp_hdlc_send(const uint8_t *buf, uint16_t len) return len; } +#ifndef CONFIG_HDLC_RCP_IF void otPlatRadioGetIeeeEui64(otInstance *instance, uint8_t *ieee_eui64) { ARG_UNUSED(instance); memcpy(ieee_eui64, ll_addr->addr, ll_addr->len); } +#endif /* CONFIG_HDLC_RCP_IF */ void otTaskletsSignalPending(otInstance *instance) { @@ -451,9 +453,8 @@ int openthread_start(struct openthread_context *ot_context) /* No dataset - initiate network join procedure. */ NET_DBG("Starting OpenThread join procedure."); - error = otJoinerStart(ot_instance, OT_JOINER_PSKD, NULL, - PACKAGE_NAME, OT_PLATFORM_INFO, - PACKAGE_VERSION, NULL, + error = otJoinerStart(ot_instance, OT_JOINER_PSKD, NULL, ZEPHYR_PACKAGE_NAME, + OT_PLATFORM_INFO, PACKAGE_VERSION, NULL, &ot_joiner_start_handler, ot_context); if (error != OT_ERROR_NONE) { diff --git a/west.yml b/west.yml index 3c836008339de..fd76e5b899d68 100644 --- a/west.yml +++ b/west.yml @@ -198,7 +198,7 @@ manifest: groups: - hal - name: hal_nxp - revision: cae40020064894f67b00215dad2baf7c743e1dfb + revision: c42d70dba969de09c0e43d3b26a3ffcb015f6f33 path: modules/hal/nxp groups: - hal