| /* |
| * 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 <openthread/config.h> |
| |
| #include "ncp_spi.hpp" |
| |
| #include <openthread/ncp.h> |
| #include <openthread/platform/spi-slave.h> |
| #include <openthread/platform/misc.h> |
| |
| #include "openthread-core-config.h" |
| #include "openthread-instance.h" |
| #include "common/code_utils.hpp" |
| #include "common/debug.hpp" |
| #include "common/new.hpp" |
| #include "net/ip6.hpp" |
| |
| #define SPI_RESET_FLAG 0x80 |
| #define SPI_CRC_FLAG 0x40 |
| #define SPI_PATTERN_VALUE 0x02 |
| #define SPI_PATTERN_MASK 0x03 |
| |
| #if OPENTHREAD_ENABLE_NCP_SPI |
| |
| namespace ot { |
| |
| static otDEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpSpi), uint64_t); |
| |
| extern "C" void otNcpInit(otInstance *aInstance) |
| { |
| NcpSpi *ncpSpi = NULL; |
| |
| ncpSpi = new(&sNcpRaw) NcpSpi(aInstance); |
| |
| if (ncpSpi == NULL || ncpSpi != NcpBase::GetNcpInstance()) |
| { |
| assert(false); |
| } |
| } |
| |
| static void spi_header_set_flag_byte(uint8_t *header, uint8_t value) |
| { |
| header[0] = value; |
| } |
| |
| static void spi_header_set_accept_len(uint8_t *header, uint16_t len) |
| { |
| header[1] = ((len >> 0) & 0xff); |
| header[2] = ((len >> 8) & 0xff); |
| } |
| |
| static void spi_header_set_data_len(uint8_t *header, uint16_t len) |
| { |
| header[3] = ((len >> 0) & 0xff); |
| header[4] = ((len >> 8) & 0xff); |
| } |
| |
| static uint8_t spi_header_get_flag_byte(const uint8_t *header) |
| { |
| return header[0]; |
| } |
| |
| static uint16_t spi_header_get_accept_len(const uint8_t *header) |
| { |
| return ( header[1] + static_cast<uint16_t>(header[2] << 8) ); |
| } |
| |
| static uint16_t spi_header_get_data_len(const uint8_t *header) |
| { |
| return ( header[3] + static_cast<uint16_t>(header[4] << 8) ); |
| } |
| |
| NcpSpi::NcpSpi(otInstance *aInstance) : |
| NcpBase(aInstance), |
| mTxState(kTxStateIdle), |
| mHandlingRxFrame(false), |
| mResetFlag(true), |
| mPrepareTxFrameTask(aInstance->mIp6.mTaskletScheduler, &NcpSpi::PrepareTxFrame, this), |
| mSendFrameLen(0) |
| { |
| memset(mSendFrame, 0, kSpiHeaderLength); |
| memset(mEmptySendFrameZeroAccept, 0, kSpiHeaderLength); |
| memset(mEmptySendFrameFullAccept, 0, kSpiHeaderLength); |
| |
| mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToTxBuffer, this); |
| |
| spi_header_set_flag_byte(mSendFrame, SPI_RESET_FLAG | SPI_PATTERN_VALUE); |
| spi_header_set_flag_byte(mEmptySendFrameZeroAccept, SPI_RESET_FLAG | SPI_PATTERN_VALUE); |
| spi_header_set_flag_byte(mEmptySendFrameFullAccept, SPI_RESET_FLAG | SPI_PATTERN_VALUE); |
| |
| spi_header_set_accept_len(mSendFrame, sizeof(mReceiveFrame) - kSpiHeaderLength); |
| spi_header_set_accept_len(mEmptySendFrameFullAccept, sizeof(mReceiveFrame) - kSpiHeaderLength); |
| spi_header_set_accept_len(mEmptySendFrameZeroAccept, 0); |
| |
| 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. |
| |
| otPlatSpiSlavePrepareTransaction(mEmptySendFrameZeroAccept, kSpiHeaderLength, mEmptyReceiveFrame, kSpiHeaderLength, |
| true); |
| } |
| |
| bool NcpSpi::SpiTransactionComplete(void *aContext, uint8_t *aOutputBuf, uint16_t aOutputBufLen, uint8_t *aInputBuf, |
| uint16_t aInputBufLen, uint16_t aTransactionLength) |
| { |
| return static_cast<NcpSpi *>(aContext)->SpiTransactionComplete(aOutputBuf, aOutputBufLen, aInputBuf, aInputBufLen, |
| aTransactionLength); |
| } |
| |
| bool NcpSpi::SpiTransactionComplete(uint8_t *aOutputBuf, uint16_t aOutputBufLen, uint8_t *aInputBuf, |
| uint16_t aInputBufLen, uint16_t aTransactionLength) |
| { |
| // This may be executed from an interrupt context. |
| // Must return as quickly as possible. |
| |
| uint16_t rx_data_len = 0; |
| uint16_t rx_accept_len = 0; |
| uint16_t tx_data_len = 0; |
| uint16_t tx_accept_len = 0; |
| bool shouldProcess = false; |
| |
| // TODO: Check `PATTERN` bits of `HDR` and ignore frame if not set. |
| // Holding off on implementing this so as to not cause immediate |
| // compatibility problems, even though it is required by the spec. |
| |
| if (aTransactionLength >= kSpiHeaderLength) |
| { |
| if (aOutputBufLen >= kSpiHeaderLength) |
| { |
| rx_accept_len = spi_header_get_accept_len(aOutputBuf); |
| tx_data_len = spi_header_get_data_len(aOutputBuf); |
| (void)spi_header_get_flag_byte(aOutputBuf); |
| } |
| |
| if (aInputBufLen >= kSpiHeaderLength) |
| { |
| rx_data_len = spi_header_get_data_len(aInputBuf); |
| tx_accept_len = spi_header_get_accept_len(aInputBuf); |
| } |
| |
| if (!mHandlingRxFrame && |
| (rx_data_len > 0) && |
| (rx_data_len <= aTransactionLength - kSpiHeaderLength) && |
| (rx_data_len <= rx_accept_len)) |
| { |
| mHandlingRxFrame = true; |
| shouldProcess = true; |
| } |
| |
| if ((mTxState == kTxStateSending) && |
| (tx_data_len > 0) && |
| (tx_data_len <= aTransactionLength - kSpiHeaderLength) && |
| (tx_data_len <= tx_accept_len)) |
| { |
| mTxState = kTxStateHandlingSendDone; |
| shouldProcess = true; |
| } |
| } |
| |
| if (mResetFlag && (aTransactionLength > 0) && (aOutputBufLen > 0)) |
| { |
| // Clear the reset flag. |
| mResetFlag = false; |
| spi_header_set_flag_byte(mSendFrame, SPI_PATTERN_VALUE); |
| spi_header_set_flag_byte(mEmptySendFrameZeroAccept, SPI_PATTERN_VALUE); |
| spi_header_set_flag_byte(mEmptySendFrameFullAccept, SPI_PATTERN_VALUE); |
| } |
| |
| if (mTxState == kTxStateSending) |
| { |
| aOutputBuf = mSendFrame; |
| aOutputBufLen = mSendFrameLen; |
| } |
| else |
| { |
| aOutputBuf = (mHandlingRxFrame) ? mEmptySendFrameZeroAccept : mEmptySendFrameFullAccept; |
| aOutputBufLen = kSpiHeaderLength; |
| } |
| |
| if (mHandlingRxFrame) |
| { |
| aInputBuf = mEmptyReceiveFrame; |
| aInputBufLen = kSpiHeaderLength; |
| spi_header_set_accept_len(mSendFrame, 0); |
| } |
| else |
| { |
| aInputBuf = mReceiveFrame; |
| aInputBufLen = sizeof(mReceiveFrame); |
| spi_header_set_accept_len(mSendFrame, sizeof(mReceiveFrame) - kSpiHeaderLength); |
| } |
| |
| otPlatSpiSlavePrepareTransaction(aOutputBuf, aOutputBufLen, aInputBuf, aInputBufLen, |
| (mTxState == kTxStateSending)); |
| |
| return shouldProcess; |
| } |
| |
| void NcpSpi::SpiTransactionProcess(void *aContext) |
| { |
| static_cast<NcpSpi *>(aContext)->SpiTransactionProcess(); |
| } |
| |
| void NcpSpi::SpiTransactionProcess(void) |
| { |
| if (mTxState == kTxStateHandlingSendDone) |
| { |
| mPrepareTxFrameTask.Post(); |
| } |
| |
| if (mHandlingRxFrame) |
| { |
| HandleRxFrame(); |
| } |
| } |
| |
| void NcpSpi::HandleFrameAddedToTxBuffer(void *aContext, NcpFrameBuffer::FrameTag aTag, NcpFrameBuffer *aNcpFrameBuffer) |
| { |
| OT_UNUSED_VARIABLE(aNcpFrameBuffer); |
| OT_UNUSED_VARIABLE(aTag); |
| |
| static_cast<NcpSpi *>(aContext)->mPrepareTxFrameTask.Post(); |
| } |
| |
| otError NcpSpi::PrepareNextSpiSendFrame(void) |
| { |
| otError errorCode = OT_ERROR_NONE; |
| uint16_t frameLength; |
| uint16_t readLength; |
| |
| VerifyOrExit(!mTxFrameBuffer.IsEmpty()); |
| |
| if (ShouldWakeHost()) |
| { |
| otPlatWakeHost(); |
| } |
| |
| SuccessOrExit(errorCode = mTxFrameBuffer.OutFrameBegin()); |
| |
| frameLength = mTxFrameBuffer.OutFrameGetLength(); |
| assert(frameLength <= sizeof(mSendFrame) - kSpiHeaderLength); |
| |
| spi_header_set_data_len(mSendFrame, frameLength); |
| |
| // 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, mSendFrame + kSpiHeaderLength); |
| assert(readLength == frameLength); |
| |
| mSendFrameLen = frameLength + kSpiHeaderLength; |
| |
| mTxState = kTxStateSending; |
| |
| // Prepare new transaction by using `mSendFrame` as the output |
| // buffer while keeping the input buffer unchanged. |
| |
| errorCode = otPlatSpiSlavePrepareTransaction(mSendFrame, mSendFrameLen, NULL, 0, true); |
| |
| if (errorCode == OT_ERROR_BUSY) |
| { |
| // Being busy is OK. We will get the transaction |
| // set up properly when the current transaction |
| // is completed. |
| errorCode = OT_ERROR_NONE; |
| } |
| |
| if (errorCode != OT_ERROR_NONE) |
| { |
| mTxState = kTxStateIdle; |
| mPrepareTxFrameTask.Post(); |
| ExitNow(); |
| } |
| |
| mTxFrameBuffer.OutFrameRemove(); |
| |
| exit: |
| return errorCode; |
| } |
| |
| void NcpSpi::PrepareTxFrame(Tasklet &aTasklet) |
| { |
| OT_UNUSED_VARIABLE(aTasklet); |
| static_cast<NcpSpi *>(GetNcpInstance())->PrepareTxFrame(); |
| } |
| |
| void NcpSpi::PrepareTxFrame(void) |
| { |
| switch (mTxState) |
| { |
| case kTxStateHandlingSendDone: |
| mTxState = kTxStateIdle; |
| |
| // 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) |
| { |
| // Pass the received frame to base class to process. |
| HandleReceive(mReceiveFrame + kSpiHeaderLength, spi_header_get_data_len(mReceiveFrame)); |
| |
| // 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) |
| { |
| spi_header_set_accept_len(mSendFrame, sizeof(mReceiveFrame) - kSpiHeaderLength); |
| |
| otPlatSpiSlavePrepareTransaction(mEmptySendFrameFullAccept, kSpiHeaderLength, mReceiveFrame, |
| sizeof(mReceiveFrame), 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 ot |
| |
| #endif // OPENTHREAD_ENABLE_NCP_SPI |