Browse Source
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
6 changed files with 1187 additions and 0 deletions
@ -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 */ |
@ -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_
|
@ -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…
Reference in new issue