| /* |
| * 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 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_USEC(aTimeoutUs)); |
| 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(const 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(const 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 */ |