blob: 218deb0002a48b5695d670864a84cece37a1b5b4 [file] [log] [blame]
/*
* Copyright (c) 2016, Nest Labs, Inc.
* 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 <common/code_utils.hpp>
#include <common/new.hpp>
#include <net/ip6.hpp>
#include <ncp/ncp.h>
#include <ncp/ncp_spi.hpp>
#include <platform/spi-slave.h>
#include <core/openthread-core-config.h>
#define SPI_RESET_FLAG 0x80
#define SPI_CRC_FLAG 0x40
#define SPI_PATTERN_VALUE 0x02
#define SPI_PATTERN_MASK 0x03
namespace Thread {
static otDEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpSpi), uint64_t);
static NcpSpi *sNcpSpi;
extern Ip6::Ip6 *sIp6;
extern "C" void otNcpInit(void)
{
sNcpSpi = new(&sNcpRaw) NcpSpi;
}
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():
NcpBase(),
mHandleRxFrameTask(sIp6->mTaskletScheduler, &HandleRxFrame, this),
mPrepareTxFrameTask(sIp6->mTaskletScheduler, &PrepareTxFrame, this),
mTxFrameBuffer(mTxBuffer, sizeof(mTxBuffer))
{
memset(mEmptySendFrame, 0, kSpiHeaderLength);
memset(mSendFrame, 0, kSpiHeaderLength);
mSending = false;
mHandlingSendDone = false;
mHandlingRxFrame = false;
mTxFrameBuffer.SetCallbacks(NULL, TxFrameBufferHasData, this);
spi_header_set_flag_byte(mSendFrame, SPI_RESET_FLAG|SPI_PATTERN_VALUE);
spi_header_set_flag_byte(mEmptySendFrame, SPI_RESET_FLAG|SPI_PATTERN_VALUE);
spi_header_set_accept_len(mSendFrame, sizeof(mReceiveFrame) - kSpiHeaderLength);
otPlatSpiSlaveEnable(&SpiTransactionComplete, (void*)this);
// We signal an interrupt on this first transaction to
// make sure that the host processor knows that our
// reset flag was set.
otPlatSpiSlavePrepareTransaction(
mEmptySendFrame,
kSpiHeaderLength,
mEmptyReceiveFrame,
kSpiHeaderLength,
true
);
}
void
NcpSpi::SpiTransactionComplete(
void *aContext,
uint8_t *anOutputBuf,
uint16_t anOutputBufLen,
uint8_t *anInputBuf,
uint16_t anInputBufLen,
uint16_t aTransactionLength
)
{
static_cast<NcpSpi*>(aContext)->SpiTransactionComplete(
anOutputBuf,
anOutputBufLen,
anInputBuf,
anInputBufLen,
aTransactionLength
);
}
void
NcpSpi::SpiTransactionComplete(
uint8_t *aMISOBuf,
uint16_t aMISOBufLen,
uint8_t *aMOSIBuf,
uint16_t aMOSIBufLen,
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);
// 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 (aMISOBufLen >= kSpiHeaderLength)
{
rx_accept_len = spi_header_get_accept_len(aMISOBuf);
tx_data_len = spi_header_get_data_len(aMISOBuf);
(void)spi_header_get_flag_byte(aMISOBuf);
}
if (aMOSIBufLen >= kSpiHeaderLength)
{
rx_data_len = spi_header_get_data_len(aMOSIBuf);
tx_accept_len = spi_header_get_accept_len(aMOSIBuf);
}
if ( !mHandlingRxFrame
&& (rx_data_len > 0)
&& (rx_data_len <= (aTransactionLength - kSpiHeaderLength))
&& (rx_data_len <= rx_accept_len)
) {
mHandlingRxFrame = true;
mHandleRxFrameTask.Post();
}
if ( mSending
&& !mHandlingSendDone
&& (tx_data_len > 0)
&& (tx_data_len <= (aTransactionLength - kSpiHeaderLength))
&& (tx_data_len <= tx_accept_len)
) {
// Our transmission was successful.
mHandlingSendDone = true;
mPrepareTxFrameTask.Post();
}
}
if ( (aTransactionLength >= 1)
&& (aMISOBufLen >= 1)
) {
// Clear the reset flag.
spi_header_set_flag_byte(mSendFrame, SPI_PATTERN_VALUE);
spi_header_set_flag_byte(mEmptySendFrame, SPI_PATTERN_VALUE);
}
if (mSending && !mHandlingSendDone)
{
aMISOBuf = mSendFrame;
aMISOBufLen = mSendFrameLen;
}
else
{
aMISOBuf = mEmptySendFrame;
aMISOBufLen = kSpiHeaderLength;
}
if (mHandlingRxFrame)
{
aMOSIBuf = mEmptyReceiveFrame;
aMOSIBufLen = kSpiHeaderLength;
spi_header_set_accept_len(aMISOBuf, 0);
}
else
{
aMOSIBuf = mReceiveFrame;
aMOSIBufLen = sizeof(mReceiveFrame);
spi_header_set_accept_len(aMISOBuf, sizeof(mReceiveFrame) - kSpiHeaderLength);
}
otPlatSpiSlavePrepareTransaction(
aMISOBuf,
aMISOBufLen,
aMOSIBuf,
aMOSIBufLen,
mSending && !mHandlingSendDone
);
}
ThreadError NcpSpi::OutboundFrameBegin(void)
{
return mTxFrameBuffer.InFrameBegin();
}
ThreadError NcpSpi::OutboundFrameFeedData(const uint8_t *aDataBuffer, uint16_t aDataBufferLength)
{
return mTxFrameBuffer.InFrameFeedData(aDataBuffer, aDataBufferLength);
}
ThreadError NcpSpi::OutboundFrameFeedMessage(Message &aMessage)
{
return mTxFrameBuffer.InFrameFeedMessage(aMessage);
}
ThreadError NcpSpi::OutboundFrameEnd(void)
{
return mTxFrameBuffer.InFrameEnd();
}
void NcpSpi::TxFrameBufferHasData(void *aContext, NcpFrameBuffer *aNcpFrameBuffer)
{
(void)aContext;
(void)aNcpFrameBuffer;
sNcpSpi->TxFrameBufferHasData();
}
void NcpSpi::TxFrameBufferHasData(void)
{
mPrepareTxFrameTask.Post();
}
ThreadError NcpSpi::PrepareNextSpiSendFrame(void)
{
ThreadError errorCode = kThreadError_None;
uint16_t frameLength;
uint16_t readLength;
VerifyOrExit(!mTxFrameBuffer.IsEmpty(), ;);
SuccessOrExit(errorCode = mTxFrameBuffer.OutFrameBegin());
frameLength = mTxFrameBuffer.OutFrameGetLength();
VerifyOrExit(frameLength <= sizeof(mSendFrame) - kSpiHeaderLength, errorCode = kThreadError_NoBufs);
spi_header_set_data_len(mSendFrame, frameLength);
// Half-duplex to avoid race condition.
spi_header_set_accept_len(mSendFrame, 0);
readLength = mTxFrameBuffer.OutFrameRead(frameLength, mSendFrame + kSpiHeaderLength);
VerifyOrExit(readLength == frameLength, errorCode = kThreadError_Failed);
mSendFrameLen = frameLength + kSpiHeaderLength;
mSending = true;
errorCode = otPlatSpiSlavePrepareTransaction(
mSendFrame,
mSendFrameLen,
mEmptyReceiveFrame,
sizeof(mEmptyReceiveFrame),
true
);
if (errorCode == kThreadError_Busy)
{
// Being busy is OK. We will get the transaction
// set up properly when the current transaction
// is completed.
errorCode = kThreadError_None;
}
if (errorCode != kThreadError_None)
{
mSending = false;
}
// Remove the frame from tx buffer and inform the base
// class that space is now available for a new frame.
mTxFrameBuffer.OutFrameRemove();
super_t::HandleSpaceAvailableInTxBuffer();
exit:
return errorCode;
}
void NcpSpi::PrepareTxFrame(void *aContext)
{
static_cast<NcpSpi*>(aContext)->PrepareTxFrame();
}
void NcpSpi::PrepareTxFrame(void)
{
if (mHandlingSendDone)
{
mSending = false;
PrepareNextSpiSendFrame();
mHandlingSendDone = false;
}
else if (!mSending)
{
PrepareNextSpiSendFrame();
}
}
void NcpSpi::HandleRxFrame(void *aContext)
{
static_cast<NcpSpi*>(aContext)->HandleRxFrame();
}
void NcpSpi::HandleRxFrame(void)
{
uint16_t rx_data_len( spi_header_get_data_len(mReceiveFrame) );
super_t::HandleReceive(mReceiveFrame + kSpiHeaderLength, rx_data_len);
mHandlingRxFrame = false;
}
} // namespace Thread