#include "spi_interface.hpp"
#include "platform-posix.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/ucontext.h>
#if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI
#include <linux/gpio.h>
#include <linux/ioctl.h>
#include <linux/spi/spidev.h>
using ot::Spinel::SpinelInterface;
namespace ot {
namespace Posix {
SpiInterface::SpiInterface(SpinelInterface::ReceiveFrameCallback aCallback,
void * aCallbackContext,
SpinelInterface::RxFrameBuffer & aFrameBuffer)
: mReceiveFrameCallback(aCallback)
, mReceiveFrameContext(aCallbackContext)
, mRxFrameBuffer(aFrameBuffer)
, mSpiDevFd(-1)
, mResetGpioValueFd(-1)
, mIntGpioValueFd(-1)
, mSlaveResetCount(0)
, mSpiFrameCount(0)
, mSpiValidFrameCount(0)
, mSpiGarbageFrameCount(0)
, mSpiDuplexFrameCount(0)
, mSpiUnresponsiveFrameCount(0)
, mSpiRxFrameCount(0)
, mSpiRxFrameByteCount(0)
, mSpiTxFrameCount(0)
, mSpiTxFrameByteCount(0)
, mSpiTxIsReady(false)
, mSpiTxRefusedCount(0)
, mSpiTxPayloadSize(0)
, mDidPrintRateLimitLog(false)
, mSpiSlaveDataLen(0)
{
}
otError SpiInterface::Init(Arguments &aArguments)
{
const char *spiGpioIntDevice;
const char *spiGpioResetDevice;
uint8_t spiGpioIntLine = 0;
uint8_t spiGpioResetLine = 0;
uint8_t spiMode = OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE;
uint32_t spiSpeed = SPI_IOC_WR_MAX_SPEED_HZ;
uint32_t spiResetDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS;
uint16_t spiCsDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US;
uint8_t spiAlignAllowance = OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE;
uint8_t spiSmallPacketSize = OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE;
spiGpioIntDevice = aArguments.GetValue("gpio-int-device");
spiGpioResetDevice = aArguments.GetValue("gpio-reset-device");
if (!spiGpioIntDevice || !spiGpioResetDevice)
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aArguments.GetValue("gpio-int-line"))
{
spiGpioIntLine = static_cast<uint8_t>(atoi(aArguments.GetValue("gpio-int-line")));
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aArguments.GetValue("gpio-reset-line"))
{
spiGpioResetLine = static_cast<uint8_t>(atoi(aArguments.GetValue("gpio-reset-line")));
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
if (aArguments.GetValue("spi-mode"))
{
spiMode = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-mode")));
}
if (aArguments.GetValue("spi-speed"))
{
spiSpeed = static_cast<uint32_t>(atoi(aArguments.GetValue("spi-speed")));
}
if (aArguments.GetValue("spi-reset-delay"))
{
spiResetDelay = static_cast<uint32_t>(atoi(aArguments.GetValue("spi-reset-delay")));
}
if (aArguments.GetValue("spi-cs-delay"))
{
spiCsDelay = static_cast<uint16_t>(atoi(aArguments.GetValue("spi-cs-delay")));
}
if (aArguments.GetValue("spi-align-allowance"))
{
spiAlignAllowance = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-align-allowance")));
}
if (aArguments.GetValue("spi-small-packet"))
{
spiSmallPacketSize = static_cast<uint8_t>(atoi(aArguments.GetValue("spi-small-packet")));
}
VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE);
mSpiCsDelayUs = spiCsDelay;
mSpiSmallPacketSize = spiSmallPacketSize;
mSpiAlignAllowance = spiAlignAllowance;
if (spiGpioIntDevice != NULL)
{
InitIntPin(spiGpioIntDevice, spiGpioIntLine);
}
else
{
otLogNotePlat("SPI interface enters polling mode.");
}
InitResetPin(spiGpioResetDevice, spiGpioResetLine);
InitSpiDev(aArguments.GetPath(), spiMode, spiSpeed);
TrigerReset();
usleep(static_cast<useconds_t>(spiResetDelay) * kUsecPerMsec);
return OT_ERROR_NONE;
}
SpiInterface::~SpiInterface(void)
{
Deinit();
}
void SpiInterface::Deinit(void)
{
if (mSpiDevFd >= 0)
{
close(mSpiDevFd);
mSpiDevFd = -1;
}
if (mResetGpioValueFd >= 0)
{
close(mResetGpioValueFd);
mResetGpioValueFd = -1;
}
if (mIntGpioValueFd >= 0)
{
close(mIntGpioValueFd);
mIntGpioValueFd = -1;
}
}
int SpiInterface::SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel)
{
struct gpiohandle_request req;
int ret;
assert(strlen(aLabel) < sizeof(req.consumer_label));
req.flags = aHandleFlags;
req.lines = 1;
req.lineoffsets[0] = aLine;
req.default_values[0] = 1;
snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEHANDLE_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
return req.fd;
}
int SpiInterface::SetupGpioEvent(int aFd,
uint8_t aLine,
uint32_t aHandleFlags,
uint32_t aEventFlags,
const char *aLabel)
{
struct gpioevent_request req;
int ret;
assert(strlen(aLabel) < sizeof(req.consumer_label));
req.lineoffset = aLine;
req.handleflags = aHandleFlags;
req.eventflags = aEventFlags;
snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEEVENT_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
return req.fd;
}
void SpiInterface::SetGpioValue(int aFd, uint8_t aValue)
{
struct gpiohandle_data data;
data.values[0] = aValue;
VerifyOrDie(ioctl(aFd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
}
uint8_t SpiInterface::GetGpioValue(int aFd)
{
struct gpiohandle_data data;
VerifyOrDie(ioctl(aFd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
return data.values[0];
}
void SpiInterface::InitResetPin(const char *aCharDev, uint8_t aLine)
{
char label[] = "SOC_THREAD_RESET";
int fd;
otLogDebgPlat("InitResetPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
VerifyOrDie((aCharDev != NULL) && (aLine < GPIOHANDLES_MAX), OT_EXIT_INVALID_ARGUMENTS);
VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
mResetGpioValueFd = SetupGpioHandle(fd, aLine, GPIOHANDLE_REQUEST_OUTPUT, label);
close(fd);
}
void SpiInterface::InitIntPin(const char *aCharDev, uint8_t aLine)
{
char label[] = "THREAD_SOC_INT";
int fd;
otLogDebgPlat("InitIntPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
VerifyOrDie((aCharDev != NULL) && (aLine < GPIOHANDLES_MAX), OT_EXIT_INVALID_ARGUMENTS);
VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
mIntGpioValueFd = SetupGpioEvent(fd, aLine, GPIOHANDLE_REQUEST_INPUT, GPIOEVENT_REQUEST_FALLING_EDGE, label);
close(fd);
}
void SpiInterface::InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed)
{
const uint8_t wordBits = kSpiBitsPerWord;
int fd;
otLogDebgPlat("InitSpiDev: path=%s, mode=%" PRIu8 ", speed=%" PRIu32, aPath, aMode, aSpeed);
VerifyOrDie((aPath != NULL) && (aMode <= kSpiModeMax), OT_EXIT_INVALID_ARGUMENTS);
VerifyOrDie((fd = open(aPath, O_RDWR | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
VerifyOrExit(ioctl(fd, SPI_IOC_WR_MODE, &aMode) != -1, LogError("ioctl(SPI_IOC_WR_MODE)"));
VerifyOrExit(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &aSpeed) != -1, LogError("ioctl(SPI_IOC_WR_MAX_SPEED_HZ)"));
VerifyOrExit(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &wordBits) != -1, LogError("ioctl(SPI_IOC_WR_BITS_PER_WORD)"));
VerifyOrExit(flock(fd, LOCK_EX | LOCK_NB) != -1, LogError("flock"));
mSpiDevFd = fd;
mSpiMode = aMode;
mSpiSpeedHz = aSpeed;
fd = -1;
exit:
if (fd >= 0)
{
close(fd);
}
}
void SpiInterface::TrigerReset(void)
{
SetGpioValue(mResetGpioValueFd, 0);
usleep(kResetHoldOnUsec);
SetGpioValue(mResetGpioValueFd, 1);
otLogNotePlat("Triggered hardware reset");
}
uint8_t *SpiInterface::GetRealRxFrameStart(uint8_t *aSpiRxFrameBuffer, uint8_t aAlignAllowance, uint16_t &aSkipLength)
{
uint8_t * start = aSpiRxFrameBuffer;
const uint8_t *end = aSpiRxFrameBuffer + aAlignAllowance;
for (; start != end && start[0] == 0xff; start++)
;
aSkipLength = static_cast<uint16_t>(start - aSpiRxFrameBuffer);
return start;
}
otError SpiInterface::DoSpiTransfer(uint8_t *aSpiRxFrameBuffer, uint32_t aTransferLength)
{
int ret;
struct spi_ioc_transfer transfer[2];
memset(&transfer[0], 0, sizeof(transfer));
transfer[0].tx_buf = 0;
transfer[0].rx_buf = 0;
transfer[0].len = 0;
transfer[0].speed_hz = mSpiSpeedHz;
transfer[0].delay_usecs = mSpiCsDelayUs;
transfer[0].bits_per_word = kSpiBitsPerWord;
transfer[0].cs_change = false;
transfer[1].tx_buf = reinterpret_cast<uintptr_t>(mSpiTxFrameBuffer);
transfer[1].rx_buf = reinterpret_cast<uintptr_t>(aSpiRxFrameBuffer);
transfer[1].len = aTransferLength;
transfer[1].speed_hz = mSpiSpeedHz;
transfer[1].delay_usecs = 0;
transfer[1].bits_per_word = kSpiBitsPerWord;
transfer[1].cs_change = false;
if (mSpiCsDelayUs > 0)
{
ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(2), &transfer[0]);
}
else
{
ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(1), &transfer[1]);
}
if (ret != -1)
{
otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, transfer[1].len);
otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-RX", aSpiRxFrameBuffer, transfer[1].len);
mSpiFrameCount++;
}
return (ret < 0) ? OT_ERROR_FAILED : OT_ERROR_NONE;
}
otError SpiInterface::PushPullSpi(void)
{
otError error = OT_ERROR_FAILED;
uint16_t spiTransferBytes = 0;
uint8_t successfulExchanges = 0;
bool discardRxFrame = true;
uint8_t * spiRxFrameBuffer;
uint8_t * spiRxFrame;
uint8_t slaveHeader;
uint16_t slaveAcceptLen;
Ncp::SpiFrame txFrame(mSpiTxFrameBuffer);
uint16_t skipAlignAllowanceLength;
if (mSpiValidFrameCount == 0)
{
txFrame.SetHeaderFlagByte(true);
}
else
{
txFrame.SetHeaderFlagByte(false);
}
txFrame.SetHeaderAcceptLen(0);
txFrame.SetHeaderDataLen(0);
if (mSpiSlaveDataLen > kMaxFrameSize)
{
mSpiSlaveDataLen = 0;
}
if (mSpiTxIsReady)
{
txFrame.SetHeaderDataLen(mSpiTxPayloadSize);
if (mSpiTxPayloadSize > spiTransferBytes)
{
spiTransferBytes = mSpiTxPayloadSize;
}
}
if (mSpiSlaveDataLen != 0)
{
if (mSpiSlaveDataLen > spiTransferBytes)
{
spiTransferBytes = mSpiSlaveDataLen;
}
}
else
{
if (spiTransferBytes < mSpiSmallPacketSize)
{
spiTransferBytes = mSpiSmallPacketSize;
}
}
txFrame.SetHeaderAcceptLen(spiTransferBytes);
SuccessOrExit(error = mRxFrameBuffer.SetSkipLength(kSpiFrameHeaderSize));
VerifyOrExit(mRxFrameBuffer.GetFrameMaxLength() >= spiTransferBytes + mSpiAlignAllowance, OT_NOOP);
spiRxFrameBuffer = mRxFrameBuffer.GetFrame() - kSpiFrameHeaderSize;
spiTransferBytes += kSpiFrameHeaderSize + mSpiAlignAllowance;
error = DoSpiTransfer(spiRxFrameBuffer, spiTransferBytes);
if (error != OT_ERROR_NONE)
{
otLogCritPlat("PushPullSpi:DoSpiTransfer: errno=%s", strerror(errno));
if ((mSpiCsDelayUs != 0) && (errno == EINVAL))
{
otLogWarnPlat("SPI ioctl failed with EINVAL. Try adding `--spi-cs-delay=0` to command line arguments.");
}
LogStats();
DieNow(OT_EXIT_FAILURE);
}
spiRxFrame = GetRealRxFrameStart(spiRxFrameBuffer, mSpiAlignAllowance, skipAlignAllowanceLength);
{
Ncp::SpiFrame rxFrame(spiRxFrame);
otLogDebgPlat("spi_transfer TX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, txFrame.GetHeaderFlagByte(),
txFrame.GetHeaderAcceptLen(), txFrame.GetHeaderDataLen());
otLogDebgPlat("spi_transfer RX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, rxFrame.GetHeaderFlagByte(),
rxFrame.GetHeaderAcceptLen(), rxFrame.GetHeaderDataLen());
slaveHeader = rxFrame.GetHeaderFlagByte();
if ((slaveHeader == 0xFF) || (slaveHeader == 0x00))
{
if ((slaveHeader == spiRxFrame[1]) && (slaveHeader == spiRxFrame[2]) && (slaveHeader == spiRxFrame[3]) &&
(slaveHeader == spiRxFrame[4]))
{
if (mSpiSlaveDataLen == 0)
{
otLogDebgPlat("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
}
else
{
otLogWarnPlat("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
}
mSpiUnresponsiveFrameCount++;
}
else
{
mSpiGarbageFrameCount++;
otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1],
spiRxFrame[2], spiRxFrame[3], spiRxFrame[4]);
otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-RX", spiRxFrameBuffer, spiTransferBytes);
}
mSpiTxRefusedCount++;
ExitNow();
}
slaveAcceptLen = rxFrame.GetHeaderAcceptLen();
mSpiSlaveDataLen = rxFrame.GetHeaderDataLen();
if (!rxFrame.IsValid() || (slaveAcceptLen > kMaxFrameSize) || (mSpiSlaveDataLen > kMaxFrameSize))
{
mSpiGarbageFrameCount++;
mSpiTxRefusedCount++;
mSpiSlaveDataLen = 0;
otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2],
spiRxFrame[3], spiRxFrame[4]);
otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-RX", spiRxFrameBuffer, spiTransferBytes);
ExitNow();
}
mSpiValidFrameCount++;
if (rxFrame.IsResetFlagSet())
{
mSlaveResetCount++;
otLogNotePlat("Slave did reset (%" PRIu64 " resets so far)", mSlaveResetCount);
LogStats();
}
if ((mSpiSlaveDataLen != 0) && (mSpiSlaveDataLen <= txFrame.GetHeaderAcceptLen()))
{
mSpiRxFrameByteCount += mSpiSlaveDataLen;
mSpiSlaveDataLen = 0;
mSpiRxFrameCount++;
successfulExchanges++;
SuccessOrExit(error = mRxFrameBuffer.SetSkipLength(skipAlignAllowanceLength + kSpiFrameHeaderSize));
SuccessOrExit(error = mRxFrameBuffer.SetLength(rxFrame.GetHeaderDataLen()));
discardRxFrame = false;
mReceiveFrameCallback(mReceiveFrameContext);
}
}
if (mSpiTxIsReady && (mSpiTxPayloadSize == txFrame.GetHeaderDataLen()))
{
if (txFrame.GetHeaderDataLen() <= slaveAcceptLen)
{
successfulExchanges++;
mSpiTxFrameCount++;
mSpiTxFrameByteCount += mSpiTxPayloadSize;
mSpiTxIsReady = false;
mSpiTxPayloadSize = 0;
mSpiTxRefusedCount = 0;
}
else
{
mSpiTxRefusedCount++;
}
}
if (!mSpiTxIsReady)
{
mSpiTxRefusedCount = 0;
}
if (successfulExchanges == 2)
{
mSpiDuplexFrameCount++;
}
exit:
if (discardRxFrame)
{
mRxFrameBuffer.DiscardFrame();
}
return error;
}
bool SpiInterface::CheckInterrupt(void)
{
return (mIntGpioValueFd >= 0) ? (GetGpioValue(mIntGpioValueFd) == kGpioIntAssertState) : true;
}
void SpiInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
{
struct timeval timeout = {kSecPerDay, 0};
struct timeval pollingTimeout = {0, kSpiPollPeriodUs};
OT_UNUSED_VARIABLE(aWriteFdSet);
if (mSpiTxIsReady)
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
}
if (mIntGpioValueFd >= 0)
{
if (aMaxFd < mIntGpioValueFd)
{
aMaxFd = mIntGpioValueFd;
}
if (CheckInterrupt())
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
otLogDebgPlat("UpdateFdSet(): Interrupt.");
}
else
{
FD_SET(mIntGpioValueFd, &aReadFdSet);
}
}
else if (timercmp(&pollingTimeout, &timeout, <))
{
timeout = pollingTimeout;
}
if (mSpiTxRefusedCount)
{
struct timeval minTimeout = {0, 0};
if (mSpiTxRefusedCount < kImmediateRetryCount)
{
minTimeout.tv_usec = kImmediateRetryTimeoutUs;
}
else if (mSpiTxRefusedCount < kFastRetryCount)
{
minTimeout.tv_usec = kFastRetryTimeoutUs;
}
else
{
minTimeout.tv_usec = kSlowRetryTimeoutUs;
}
if (timercmp(&timeout, &minTimeout, <))
{
timeout = minTimeout;
}
if (mSpiTxIsReady && !mDidPrintRateLimitLog && (mSpiTxRefusedCount > 1))
{
otLogInfoPlat("Slave is rate limiting transactions");
mDidPrintRateLimitLog = true;
}
if (mSpiTxRefusedCount == kSpiTxRefuseWarnCount)
{
otLogWarnPlat("Slave seems stuck.");
}
else if (mSpiTxRefusedCount == kSpiTxRefuseExitCount)
{
DieNowWithMessage("Slave seems REALLY stuck.", OT_EXIT_FAILURE);
}
}
else
{
mDidPrintRateLimitLog = false;
}
if (timercmp(&timeout, &aTimeout, <))
{
aTimeout = timeout;
}
}
void SpiInterface::Process(const RadioProcessContext &aContext)
{
if (FD_ISSET(mIntGpioValueFd, aContext.mReadFdSet))
{
struct gpioevent_data event;
otLogDebgPlat("Process(): Interrupt.");
VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_ERROR_ERRNO);
}
if (mSpiTxIsReady || CheckInterrupt())
{
IgnoreError(PushPullSpi());
}
}
otError SpiInterface::WaitForFrame(uint64_t aTimeoutUs)
{
otError error = OT_ERROR_NONE;
struct timeval spiTimeout = {kSecPerDay, 0};
struct timeval timeout;
fd_set readFdSet;
int ret;
bool isDataReady = false;
timeout.tv_sec = static_cast<time_t>(aTimeoutUs / US_PER_S);
timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
FD_ZERO(&readFdSet);
if (mIntGpioValueFd >= 0)
{
if ((isDataReady = CheckInterrupt()))
{
spiTimeout.tv_sec = 0;
spiTimeout.tv_usec = 0;
}
else
{
FD_SET(mIntGpioValueFd, &readFdSet);
}
}
else
{
spiTimeout.tv_sec = 0;
spiTimeout.tv_usec = kSpiPollPeriodUs;
}
if (timercmp(&spiTimeout, &timeout, <))
{
timeout = spiTimeout;
}
ret = select(mIntGpioValueFd + 1, &readFdSet, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(mIntGpioValueFd, &readFdSet))
{
struct gpioevent_data event;
VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_FAILURE);
isDataReady = true;
}
if (isDataReady)
{
IgnoreError(PushPullSpi());
}
else if (ret == 0)
{
ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
}
else if (errno != EINTR)
{
DieNow(OT_EXIT_ERROR_ERRNO);
}
exit:
return error;
}
otError SpiInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(aLength < (kMaxFrameSize - kSpiFrameHeaderSize), error = OT_ERROR_NO_BUFS);
VerifyOrExit(!mSpiTxIsReady, error = OT_ERROR_BUSY);
memcpy(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], aFrame, aLength);
mSpiTxIsReady = true;
mSpiTxPayloadSize = aLength;
IgnoreError(PushPullSpi());
exit:
return error;
}
void SpiInterface::LogError(const char *aString)
{
OT_UNUSED_VARIABLE(aString);
otLogWarnPlat("%s: %s", aString, strerror(errno));
}
void SpiInterface::LogStats(void)
{
otLogInfoPlat("INFO: mSlaveResetCount=%" PRIu64, mSlaveResetCount);
otLogInfoPlat("INFO: mSpiFrameCount=%" PRIu64, mSpiFrameCount);
otLogInfoPlat("INFO: mSpiValidFrameCount=%" PRIu64, mSpiValidFrameCount);
otLogInfoPlat("INFO: mSpiDuplexFrameCount=%" PRIu64, mSpiDuplexFrameCount);
otLogInfoPlat("INFO: mSpiUnresponsiveFrameCount=%" PRIu64, mSpiUnresponsiveFrameCount);
otLogInfoPlat("INFO: mSpiGarbageFrameCount=%" PRIu64, mSpiGarbageFrameCount);
otLogInfoPlat("INFO: mSpiRxFrameCount=%" PRIu64, mSpiRxFrameCount);
otLogInfoPlat("INFO: mSpiRxFrameByteCount=%" PRIu64, mSpiRxFrameByteCount);
otLogInfoPlat("INFO: mSpiTxFrameCount=%" PRIu64, mSpiTxFrameCount);
otLogInfoPlat("INFO: mSpiTxFrameByteCount=%" PRIu64, mSpiTxFrameByteCount);
}
} }
#endif