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>
364 lines
11 KiB
C++
364 lines
11 KiB
C++
/*
|
|
* 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 */
|