Browse Source

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 <jamel.arbi@nxp.com>
pull/82145/head
Jamel Arbi 12 months ago committed by Anas Nashif
parent
commit
ec4e0192fb
  1. 11
      modules/openthread/CMakeLists.txt
  2. 8
      modules/openthread/platform/CMakeLists.txt
  3. 8
      modules/openthread/platform/alarm.c
  4. 363
      modules/openthread/platform/hdlc_interface.cpp
  5. 206
      modules/openthread/platform/hdlc_interface.hpp
  6. 591
      modules/openthread/platform/radio_spinel.cpp

11
modules/openthread/CMakeLists.txt

@ -249,6 +249,17 @@ list(APPEND ot_libs openthread-mtd) @@ -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
$<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_OPTIONS>

8
modules/openthread/platform/CMakeLists.txt

@ -6,10 +6,18 @@ zephyr_library_sources( @@ -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)

8
modules/openthread/platform/alarm.c

@ -1,5 +1,6 @@ @@ -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) @@ -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

363
modules/openthread/platform/hdlc_interface.cpp

@ -0,0 +1,363 @@ @@ -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 <assert.h>
#include <openthread/tasklet.h>
#include <openthread/platform/alarm-milli.h>
#include <common/logging.hpp>
#include <common/code_utils.hpp>
#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<HdlcInterface *>(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<HdlcInterface *>(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 */

206
modules/openthread/platform/hdlc_interface.hpp

@ -0,0 +1,206 @@ @@ -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 <zephyr/kernel.h>
#include <zephyr/net/hdlc_rcp_if/hdlc_rcp_if.h>
#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<kEncoderBufferSize> 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<kMaxMultiFrameSize> 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_

591
modules/openthread/platform/radio_spinel.cpp

@ -0,0 +1,591 @@ @@ -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 <openthread/platform/radio.h>
#include <lib/platform/exit_code.h>
#include <lib/spinel/radio_spinel.hpp>
#include <lib/spinel/spinel.h>
#include <lib/url/url.hpp>
#include "hdlc_interface.hpp"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_OPENTHREAD_L2_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/net/net_pkt.h>
#include <openthread-system.h>
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<size_t>(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);
}
Loading…
Cancel
Save