From ec4e0192fb83cdefb0e58f5f6a262a296061ac6c Mon Sep 17 00:00:00 2001 From: Jamel Arbi Date: Tue, 16 Jul 2024 11:29:20 +0200 Subject: [PATCH] modules: openthread: Add of spinel hdlc rcp host interface Add a spinel support to an RCP design, the core of OpenThread lives on the host processor connected to an RCP radio controller over a HDLC interface. Signed-off-by: Jamel Arbi --- modules/openthread/CMakeLists.txt | 11 + modules/openthread/platform/CMakeLists.txt | 8 + modules/openthread/platform/alarm.c | 8 + .../openthread/platform/hdlc_interface.cpp | 363 +++++++++++ .../openthread/platform/hdlc_interface.hpp | 206 ++++++ modules/openthread/platform/radio_spinel.cpp | 591 ++++++++++++++++++ 6 files changed, 1187 insertions(+) create mode 100644 modules/openthread/platform/hdlc_interface.cpp create mode 100644 modules/openthread/platform/hdlc_interface.hpp create mode 100644 modules/openthread/platform/radio_spinel.cpp diff --git a/modules/openthread/CMakeLists.txt b/modules/openthread/CMakeLists.txt index 3900521ad85..941c8f1f219 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 29e827c1584..bedd51c84ee 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 3cbf1d033e3..8d41c1a66f9 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 00000000000..0d51366dbe2 --- /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 00000000000..2e65a81c135 --- /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 00000000000..95c05e88580 --- /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); +}