/* * Copyright (c) 2019, 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 includes the implementation for the SPI interface to radio (RCP). */ #include "spi_interface.hpp" #include "platform-posix.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI #include #include #include 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) , mSpiDuplexFrameCount(0) , mSpiUnresponsiveFrameCount(0) , mSpiTxIsReady(false) , mSpiTxRefusedCount(0) , mSpiTxPayloadSize(0) , mDidPrintRateLimitLog(false) , mSpiSlaveDataLen(0) , mDidRxFrame(false) { } void SpiInterface::OnRcpReset(void) { mSpiTxIsReady = false; mSpiTxRefusedCount = 0; mSpiTxPayloadSize = 0; mDidPrintRateLimitLog = false; mSpiSlaveDataLen = 0; memset(mSpiTxFrameBuffer, 0, sizeof(mSpiTxFrameBuffer)); memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics)); mInterfaceMetrics.mRcpInterfaceType = OT_POSIX_RCP_BUS_SPI; TriggerReset(); usleep(static_cast(mSpiResetDelay) * kUsecPerMsec); } otError SpiInterface::Init(const Url::Url &aRadioUrl) { 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; const char *value; spiGpioIntDevice = aRadioUrl.GetValue("gpio-int-device"); spiGpioResetDevice = aRadioUrl.GetValue("gpio-reset-device"); if (!spiGpioIntDevice || !spiGpioResetDevice) { DieNow(OT_EXIT_INVALID_ARGUMENTS); } if ((value = aRadioUrl.GetValue("gpio-int-line"))) { spiGpioIntLine = static_cast(atoi(value)); } else { DieNow(OT_EXIT_INVALID_ARGUMENTS); } if ((value = aRadioUrl.GetValue("gpio-reset-line"))) { spiGpioResetLine = static_cast(atoi(value)); } else { DieNow(OT_EXIT_INVALID_ARGUMENTS); } if ((value = aRadioUrl.GetValue("spi-mode"))) { spiMode = static_cast(atoi(value)); } if ((value = aRadioUrl.GetValue("spi-speed"))) { spiSpeed = static_cast(atoi(value)); } if ((value = aRadioUrl.GetValue("spi-reset-delay"))) { spiResetDelay = static_cast(atoi(value)); } if ((value = aRadioUrl.GetValue("spi-cs-delay"))) { spiCsDelay = static_cast(atoi(value)); } if ((value = aRadioUrl.GetValue("spi-align-allowance"))) { spiAlignAllowance = static_cast(atoi(value)); } if ((value = aRadioUrl.GetValue("spi-small-packet"))) { spiSmallPacketSize = static_cast(atoi(value)); } VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE); mSpiResetDelay = spiResetDelay; mSpiCsDelayUs = spiCsDelay; mSpiSmallPacketSize = spiSmallPacketSize; mSpiAlignAllowance = spiAlignAllowance; if (spiGpioIntDevice != nullptr) { // If the interrupt pin is not set, SPI interface will use polling mode. InitIntPin(spiGpioIntDevice, spiGpioIntLine); } else { otLogNotePlat("SPI interface enters polling mode."); } InitResetPin(spiGpioResetDevice, spiGpioResetLine); InitSpiDev(aRadioUrl.GetPath(), spiMode, spiSpeed); // Reset RCP chip. TriggerReset(); // Waiting for the RCP chip starts up. usleep(static_cast(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 != nullptr, 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 != nullptr, 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 != nullptr) && (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::TriggerReset(void) { // Set Reset pin to low level. SetGpioValue(mResetGpioValueFd, 0); usleep(kResetHoldOnUsec); // Set Reset pin to high level. 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(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)); // This part is the delay between C̅S̅ being asserted and the SPI clock // starting. This is not supported by all Linux SPI drivers. 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; // This part is the actual SPI transfer. transfer[1].tx_buf = reinterpret_cast(mSpiTxFrameBuffer); transfer[1].rx_buf = reinterpret_cast(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) { // A C̅S̅ delay has been specified. Start transactions with both parts. ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(2), &transfer[0]); } else { // No C̅S̅ delay has been specified, so we skip the first part because it causes some SPI drivers to croak. ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(1), &transfer[1]); } if (ret != -1) { otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, static_cast(transfer[1].len)); otDumpDebgPlat("SPI-RX", aSpiRxFrameBuffer, static_cast(transfer[1].len)); mInterfaceMetrics.mTransferredFrameCount++; } 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 (mInterfaceMetrics.mTransferredValidFrameCount == 0) { // Set the reset flag to indicate to our slave that we are coming up from scratch. txFrame.SetHeaderFlagByte(true); } else { txFrame.SetHeaderFlagByte(false); } // Zero out our rx_accept and our data_len for now. txFrame.SetHeaderAcceptLen(0); txFrame.SetHeaderDataLen(0); // Sanity check. if (mSpiSlaveDataLen > kMaxFrameSize) { mSpiSlaveDataLen = 0; } if (mSpiTxIsReady) { // Go ahead and try to immediately send a frame if we have it queued up. txFrame.SetHeaderDataLen(mSpiTxPayloadSize); spiTransferBytes = OT_MAX(spiTransferBytes, mSpiTxPayloadSize); } if (mSpiSlaveDataLen != 0) { // In a previous transaction the slave indicated it had something to send us. Make sure our transaction // is large enough to handle it. spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSlaveDataLen); } else { // Set up a minimum transfer size to allow small frames the slave wants to send us to be handled in a // single transaction. spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSmallPacketSize); } txFrame.SetHeaderAcceptLen(spiTransferBytes); // Set skip length to make MultiFrameBuffer to reserve a space in front of the frame buffer. SuccessOrExit(error = mRxFrameBuffer.SetSkipLength(kSpiFrameHeaderSize)); // Check whether the remaining frame buffer has enough space to store the data to be received. VerifyOrExit(mRxFrameBuffer.GetFrameMaxLength() >= spiTransferBytes + mSpiAlignAllowance); // Point to the start of the reserved buffer. spiRxFrameBuffer = mRxFrameBuffer.GetFrame() - kSpiFrameHeaderSize; // Set the total number of bytes to be transmitted. spiTransferBytes += kSpiFrameHeaderSize + mSpiAlignAllowance; // Perform the SPI transaction. error = DoSpiTransfer(spiRxFrameBuffer, spiTransferBytes); if (error != OT_ERROR_NONE) { otLogCritPlat("PushPullSpi:DoSpiTransfer: errno=%s", strerror(errno)); // Print out a helpful error message for a common error. 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); } // Account for misalignment (0xFF bytes at the start) 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])) { // Device is off or in a bad state. In some cases may be induced by flow control. 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 { // Header is full of garbage mInterfaceMetrics.mTransferredGarbageFrameCount++; otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2], spiRxFrame[3], spiRxFrame[4]); otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes); otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes); } mSpiTxRefusedCount++; ExitNow(); } slaveAcceptLen = rxFrame.GetHeaderAcceptLen(); mSpiSlaveDataLen = rxFrame.GetHeaderDataLen(); if (!rxFrame.IsValid() || (slaveAcceptLen > kMaxFrameSize) || (mSpiSlaveDataLen > kMaxFrameSize)) { mInterfaceMetrics.mTransferredGarbageFrameCount++; mSpiTxRefusedCount++; mSpiSlaveDataLen = 0; otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2], spiRxFrame[3], spiRxFrame[4]); otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes); otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes); ExitNow(); } mInterfaceMetrics.mTransferredValidFrameCount++; if (rxFrame.IsResetFlagSet()) { mSlaveResetCount++; otLogNotePlat("Slave did reset (%" PRIu64 " resets so far)", mSlaveResetCount); LogStats(); } // Handle received packet, if any. if ((mSpiSlaveDataLen != 0) && (mSpiSlaveDataLen <= txFrame.GetHeaderAcceptLen())) { mInterfaceMetrics.mRxFrameByteCount += mSpiSlaveDataLen; mSpiSlaveDataLen = 0; mInterfaceMetrics.mRxFrameCount++; successfulExchanges++; // Set the skip length to skip align bytes and SPI frame header. SuccessOrExit(error = mRxFrameBuffer.SetSkipLength(skipAlignAllowanceLength + kSpiFrameHeaderSize)); // Set the received frame length. SuccessOrExit(error = mRxFrameBuffer.SetLength(rxFrame.GetHeaderDataLen())); // Upper layer will free the frame buffer. discardRxFrame = false; mDidRxFrame = true; mReceiveFrameCallback(mReceiveFrameContext); } } // Handle transmitted packet, if any. if (mSpiTxIsReady && (mSpiTxPayloadSize == txFrame.GetHeaderDataLen())) { if (txFrame.GetHeaderDataLen() <= slaveAcceptLen) { // Our outbound packet has been successfully transmitted. Clear mSpiTxPayloadSize and mSpiTxIsReady so // that uplayer can pull another packet for us to send. successfulExchanges++; mInterfaceMetrics.mTxFrameCount++; mInterfaceMetrics.mTxFrameByteCount += mSpiTxPayloadSize; mSpiTxIsReady = false; mSpiTxPayloadSize = 0; mSpiTxRefusedCount = 0; } else { // The slave wasn't ready for what we had to send them. Incrementing this counter will turn on rate // limiting so that we don't waste a ton of CPU bombarding them with useless SPI transfers. 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) { // We have data to send to the slave. timeout.tv_sec = 0; timeout.tv_usec = 0; } if (mIntGpioValueFd >= 0) { if (aMaxFd < mIntGpioValueFd) { aMaxFd = mIntGpioValueFd; } if (CheckInterrupt()) { // Interrupt pin is asserted, set the timeout to be 0. timeout.tv_sec = 0; timeout.tv_usec = 0; otLogDebgPlat("UpdateFdSet(): Interrupt."); } else { // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the // read set. FD_SET(mIntGpioValueFd, &aReadFdSet); } } else if (timercmp(&pollingTimeout, &timeout, <)) { // In this case we don't have an interrupt, so we revert to SPI polling. timeout = pollingTimeout; } if (mSpiTxRefusedCount) { struct timeval minTimeout = {0, 0}; // We are being rate-limited by the slave. This is fairly normal behavior. Based on number of times slave has // refused a transmission, we apply a minimum timeout. 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)) { // To avoid printing out this message over and over, we only print it out once the refused count is at two // or higher when we actually have something to send the slave. And then, we only print it once. otLogInfoPlat("Slave is rate limiting transactions"); mDidPrintRateLimitLog = true; } if (mSpiTxRefusedCount == kSpiTxRefuseWarnCount) { // Ua-oh. The slave hasn't given us a chance to send it anything for over thirty frames. If this ever // happens, print out a warning to the logs. otLogWarnPlat("Slave seems stuck."); } else if (mSpiTxRefusedCount == kSpiTxRefuseExitCount) { // Double ua-oh. The slave hasn't given us a chance to send it anything for over a hundred frames. // This almost certainly means that the slave has locked up or gotten into an unrecoverable state. DieNowWithMessage("Slave seems REALLY stuck.", OT_EXIT_FAILURE); } } else { mDidPrintRateLimitLog = false; } if (timercmp(&timeout, &aTimeout, <)) { aTimeout = timeout; } } void SpiInterface::Process(const RadioProcessContext &aContext) { Process(aContext.mReadFdSet, aContext.mWriteFdSet); } void SpiInterface::Process(const fd_set *aReadFdSet, const fd_set *aWriteFdSet) { OT_UNUSED_VARIABLE(aWriteFdSet); if (FD_ISSET(mIntGpioValueFd, aReadFdSet)) { struct gpioevent_data event; otLogDebgPlat("Process(): Interrupt."); // Read event data to clear interrupt. VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_ERROR_ERRNO); } // Service the SPI port if we can receive a packet or we have a packet to be sent. if (mSpiTxIsReady || CheckInterrupt()) { // We guard this with the above check because we don't want to overwrite any previously received frames. IgnoreError(PushPullSpi()); } } otError SpiInterface::WaitForFrame(uint64_t aTimeoutUs) { otError error = OT_ERROR_NONE; uint64_t now = otPlatTimeGet(); uint64_t end = now + aTimeoutUs; mDidRxFrame = false; while (now < end) { fd_set readFdSet; fd_set writeFdSet; int maxFds = -1; struct timeval timeout; int ret; timeout.tv_sec = static_cast((end - now) / US_PER_S); timeout.tv_usec = static_cast((end - now) % US_PER_S); FD_ZERO(&readFdSet); FD_ZERO(&writeFdSet); UpdateFdSet(readFdSet, writeFdSet, maxFds, timeout); ret = select(maxFds + 1, &readFdSet, &writeFdSet, nullptr, &timeout); if (ret >= 0) { Process(&readFdSet, &writeFdSet); if (mDidRxFrame) { ExitNow(); } } else if (errno != EINTR) { DieNow(OT_EXIT_ERROR_ERRNO); } now = otPlatTimeGet(); } error = OT_ERROR_RESPONSE_TIMEOUT; 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: SlaveResetCount=%" PRIu64, mSlaveResetCount); otLogInfoPlat("INFO: SpiDuplexFrameCount=%" PRIu64, mSpiDuplexFrameCount); otLogInfoPlat("INFO: SpiUnresponsiveFrameCount=%" PRIu64, mSpiUnresponsiveFrameCount); otLogInfoPlat("INFO: TransferredFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredFrameCount); otLogInfoPlat("INFO: TransferredValidFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredValidFrameCount); otLogInfoPlat("INFO: TransferredGarbageFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredGarbageFrameCount); otLogInfoPlat("INFO: RxFrameCount=%" PRIu64, mInterfaceMetrics.mRxFrameCount); otLogInfoPlat("INFO: RxFrameByteCount=%" PRIu64, mInterfaceMetrics.mRxFrameByteCount); otLogInfoPlat("INFO: TxFrameCount=%" PRIu64, mInterfaceMetrics.mTxFrameCount); otLogInfoPlat("INFO: TxFrameByteCount=%" PRIu64, mInterfaceMetrics.mTxFrameByteCount); } } // namespace Posix } // namespace ot #endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI