/* * Copyright (c) 2016, The OpenThread Authors. * 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 a SPI interface to the OpenThread stack. */ #include "ncp_spi.hpp" #include #include #include #include #include "openthread-core-config.h" #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/instance.hpp" #include "common/new.hpp" #include "net/ip6.hpp" #if OPENTHREAD_CONFIG_NCP_SPI_ENABLE #if OPENTHREAD_CONFIG_DIAG_ENABLE static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE - ot::Ncp::NcpBase::kSpinelCmdHeaderSize - ot::Ncp::NcpBase::kSpinelPropIdSize - ot::Ncp::SpiFrame::kHeaderSize, "diag output should be smaller than NCP SPI tx buffer"); static_assert(OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE, "diag command line should be smaller than NCP SPI rx buffer"); #endif namespace ot { namespace Ncp { #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0 static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpSpi), uint64_t); extern "C" void otNcpSpiInit(otInstance *aInstance) { NcpSpi *ncpSpi = nullptr; Instance *instance = static_cast(aInstance); ncpSpi = new (&sNcpRaw) NcpSpi(instance); if (ncpSpi == nullptr || ncpSpi != NcpBase::GetNcpInstance()) { OT_ASSERT(false); } } #endif // OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0 NcpSpi::NcpSpi(Instance *aInstance) : NcpBase(aInstance) , mTxState(kTxStateIdle) , mHandlingRxFrame(false) , mResetFlag(true) , mPrepareTxFrameTask(*aInstance, NcpSpi::PrepareTxFrame) , mSendFrameLength(0) { SpiFrame sendFrame(mSendFrame); SpiFrame emptyFullAccept(mEmptySendFrameFullAccept); SpiFrame emptyZeroAccept(mEmptySendFrameZeroAccept); sendFrame.SetHeaderFlagByte(/* aResetFlag */ true); sendFrame.SetHeaderAcceptLen(0); sendFrame.SetHeaderDataLen(0); emptyFullAccept.SetHeaderFlagByte(/* aResetFlag */ true); emptyFullAccept.SetHeaderAcceptLen(kSpiBufferSize - kSpiHeaderSize); emptyFullAccept.SetHeaderDataLen(0); emptyZeroAccept.SetHeaderFlagByte(/* aResetFlag */ true); emptyZeroAccept.SetHeaderAcceptLen(0); emptyZeroAccept.SetHeaderDataLen(0); mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToTxBuffer, this); IgnoreError(otPlatSpiSlaveEnable(&NcpSpi::SpiTransactionComplete, &NcpSpi::SpiTransactionProcess, this)); // We signal an interrupt on this first transaction to // make sure that the host processor knows that our // reset flag was set. IgnoreError(otPlatSpiSlavePrepareTransaction(mEmptySendFrameZeroAccept, kSpiHeaderSize, mEmptyReceiveFrame, kSpiHeaderSize, /* aRequestTransactionFlag */ true)); } bool NcpSpi::SpiTransactionComplete(void *aContext, uint8_t *aOutputBuf, uint16_t aOutputLen, uint8_t *aInputBuf, uint16_t aInputLen, uint16_t aTransLen) { NcpSpi *ncp = reinterpret_cast(aContext); return ncp->SpiTransactionComplete(aOutputBuf, aOutputLen, aInputBuf, aInputLen, aTransLen); } bool NcpSpi::SpiTransactionComplete(uint8_t *aOutputBuf, uint16_t aOutputLen, uint8_t *aInputBuf, uint16_t aInputLen, uint16_t aTransLen) { // This can be executed from an interrupt context, therefore we cannot // use any of OpenThread APIs here. If further processing is needed, // returned value `shouldProcess` is set to `true` to indicate to // platform SPI slave driver to invoke `SpiTransactionProcess()` callback // which unlike this callback must be called from the same OS context // that OpenThread APIs/callbacks are executed. uint16_t transDataLen; bool shouldProcess = false; SpiFrame outputFrame(aOutputBuf); SpiFrame inputFrame(aInputBuf); SpiFrame sendFrame(mSendFrame); VerifyOrExit((aTransLen >= kSpiHeaderSize) && (aInputLen >= kSpiHeaderSize) && (aOutputLen >= kSpiHeaderSize)); VerifyOrExit(inputFrame.IsValid() && outputFrame.IsValid()); transDataLen = aTransLen - kSpiHeaderSize; if (!mHandlingRxFrame) { uint16_t rxDataLen = inputFrame.GetHeaderDataLen(); // A new frame is successfully received if input frame // indicates that there is data and the "data len" is not // larger than than the "accept len" we provided in the // exchanged output frame. if ((rxDataLen > 0) && (rxDataLen <= transDataLen) && (rxDataLen <= outputFrame.GetHeaderAcceptLen())) { mHandlingRxFrame = true; shouldProcess = true; } } if (mTxState == kTxStateSending) { uint16_t txDataLen = outputFrame.GetHeaderDataLen(); // Frame transmission is successful if master indicates // in the input frame that it could accept the frame // length that was exchanged, i.e., the "data len" in // the output frame is smaller than or equal to "accept // len" in the received input frame from master. if ((txDataLen > 0) && (txDataLen <= transDataLen) && (txDataLen <= inputFrame.GetHeaderAcceptLen())) { mTxState = kTxStateHandlingSendDone; shouldProcess = true; } } exit: // Determine the input and output frames to prepare a new transaction. if (mResetFlag && (aTransLen > 0) && (aOutputLen > 0)) { mResetFlag = false; sendFrame.SetHeaderFlagByte(/*aResetFlag */ false); SpiFrame(mEmptySendFrameFullAccept).SetHeaderFlagByte(/*aResetFlag */ false); SpiFrame(mEmptySendFrameZeroAccept).SetHeaderFlagByte(/*aResetFlag */ false); } if (mTxState == kTxStateSending) { aOutputBuf = mSendFrame; aOutputLen = mSendFrameLength; } else { aOutputBuf = mHandlingRxFrame ? mEmptySendFrameZeroAccept : mEmptySendFrameFullAccept; aOutputLen = kSpiHeaderSize; } if (mHandlingRxFrame) { aInputBuf = mEmptyReceiveFrame; aInputLen = kSpiHeaderSize; } else { aInputBuf = mReceiveFrame; aInputLen = kSpiBufferSize; } sendFrame.SetHeaderAcceptLen(aInputLen - kSpiHeaderSize); IgnoreError( otPlatSpiSlavePrepareTransaction(aOutputBuf, aOutputLen, aInputBuf, aInputLen, (mTxState == kTxStateSending))); return shouldProcess; } void NcpSpi::SpiTransactionProcess(void *aContext) { reinterpret_cast(aContext)->SpiTransactionProcess(); } void NcpSpi::SpiTransactionProcess(void) { if (mTxState == kTxStateHandlingSendDone) { mPrepareTxFrameTask.Post(); } if (mHandlingRxFrame) { HandleRxFrame(); } } void NcpSpi::HandleFrameAddedToTxBuffer(void *aContext, Spinel::Buffer::FrameTag aTag, Spinel::Buffer::Priority aPriority, Spinel::Buffer *aBuffer) { OT_UNUSED_VARIABLE(aBuffer); OT_UNUSED_VARIABLE(aTag); OT_UNUSED_VARIABLE(aPriority); static_cast(aContext)->mPrepareTxFrameTask.Post(); } void NcpSpi::PrepareNextSpiSendFrame(void) { otError error = OT_ERROR_NONE; uint16_t frameLength; uint16_t readLength; SpiFrame sendFrame(mSendFrame); VerifyOrExit(!mTxFrameBuffer.IsEmpty()); if (ShouldWakeHost()) { otPlatWakeHost(); } SuccessOrExit(error = mTxFrameBuffer.OutFrameBegin()); frameLength = mTxFrameBuffer.OutFrameGetLength(); OT_ASSERT(frameLength <= kSpiBufferSize - kSpiHeaderSize); // The "accept length" in `mSendFrame` is already updated based // on current state of receive. It is changed either from the // `SpiTransactionComplete()` callback or from `HandleRxFrame()`. readLength = mTxFrameBuffer.OutFrameRead(frameLength, sendFrame.GetData()); OT_ASSERT(readLength == frameLength); // Suppress the warning when assertions are disabled OT_UNUSED_VARIABLE(readLength); sendFrame.SetHeaderDataLen(frameLength); mSendFrameLength = frameLength + kSpiHeaderSize; mTxState = kTxStateSending; // Prepare new transaction by using `mSendFrame` as the output // frame while keeping the input frame unchanged. error = otPlatSpiSlavePrepareTransaction(mSendFrame, mSendFrameLength, nullptr, 0, /* aRequestTrans */ true); if (error == OT_ERROR_BUSY) { // Being busy is OK. We will get the transaction set up // properly when the current transaction is completed. error = OT_ERROR_NONE; } if (error != OT_ERROR_NONE) { mTxState = kTxStateIdle; mPrepareTxFrameTask.Post(); ExitNow(); } IgnoreError(mTxFrameBuffer.OutFrameRemove()); exit: return; } void NcpSpi::PrepareTxFrame(Tasklet &aTasklet) { OT_UNUSED_VARIABLE(aTasklet); static_cast(GetNcpInstance())->PrepareTxFrame(); } void NcpSpi::PrepareTxFrame(void) { switch (mTxState) { case kTxStateHandlingSendDone: mTxState = kTxStateIdle; OT_FALL_THROUGH; // to next case to prepare the next frame (if any). case kTxStateIdle: PrepareNextSpiSendFrame(); break; case kTxStateSending: // The next frame in queue (if any) will be prepared when the // current frame is successfully sent and this task is posted // again from the `SpiTransactionComplete()` callback. break; } } void NcpSpi::HandleRxFrame(void) { SpiFrame recvFrame(mReceiveFrame); SpiFrame sendFrame(mSendFrame); // Pass the received frame to base class to process. HandleReceive(recvFrame.GetData(), recvFrame.GetHeaderDataLen()); // The order of operations below is important. We should clear // the `mHandlingRxFrame` before checking `mTxState` and possibly // preparing the next transaction. Note that the callback // `SpiTransactionComplete()` can be invoked from ISR at any point. // // If we switch the order, we have the following race situation: // We check `mTxState` and it is in `kTxStateSending`, so we skip // preparing the transaction here. But before we set the // `mHandlingRxFrame` to `false`, the `SpiTransactionComplete()` // happens and prepares the next transaction and sets the accept // length to zero on `mSendFrame` (since it assumes we are still // handling the previous received frame). mHandlingRxFrame = false; // If tx state is in `kTxStateSending`, we wait for the callback // `SpiTransactionComplete()` which will then set up everything // and prepare the next transaction. if (mTxState != kTxStateSending) { sendFrame.SetHeaderAcceptLen(kSpiBufferSize - kSpiHeaderSize); IgnoreError(otPlatSpiSlavePrepareTransaction(mEmptySendFrameFullAccept, kSpiHeaderSize, mReceiveFrame, kSpiBufferSize, /* aRequestTrans */ false)); // No need to check the error status. Getting `OT_ERROR_BUSY` // is OK as everything will be set up properly from callback when // the current transaction is completed. } } } // namespace Ncp } // namespace ot #endif // OPENTHREAD_CONFIG_NCP_SPI_ENABLE