| /* |
| * 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 <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) |
| { |
| } |
| |
| void SpiInterface::OnRcpReset(void) |
| { |
| mSpiValidFrameCount = 0; |
| mSpiTxIsReady = false; |
| mSpiTxRefusedCount = 0; |
| mSpiTxPayloadSize = 0; |
| mDidPrintRateLimitLog = false; |
| mSpiSlaveDataLen = 0; |
| memset(mSpiTxFrameBuffer, 0, sizeof(mSpiTxFrameBuffer)); |
| |
| TriggerReset(); |
| usleep(static_cast<useconds_t>(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<uint8_t>(atoi(value)); |
| } |
| else |
| { |
| DieNow(OT_EXIT_INVALID_ARGUMENTS); |
| } |
| if ((value = aRadioUrl.GetValue("gpio-reset-line"))) |
| { |
| spiGpioResetLine = static_cast<uint8_t>(atoi(value)); |
| } |
| else |
| { |
| DieNow(OT_EXIT_INVALID_ARGUMENTS); |
| } |
| if ((value = aRadioUrl.GetValue("spi-mode"))) |
| { |
| spiMode = static_cast<uint8_t>(atoi(value)); |
| } |
| if ((value = aRadioUrl.GetValue("spi-speed"))) |
| { |
| spiSpeed = static_cast<uint32_t>(atoi(value)); |
| } |
| if ((value = aRadioUrl.GetValue("spi-reset-delay"))) |
| { |
| spiResetDelay = static_cast<uint32_t>(atoi(value)); |
| } |
| if ((value = aRadioUrl.GetValue("spi-cs-delay"))) |
| { |
| spiCsDelay = static_cast<uint16_t>(atoi(value)); |
| } |
| if ((value = aRadioUrl.GetValue("spi-align-allowance"))) |
| { |
| spiAlignAllowance = static_cast<uint8_t>(atoi(value)); |
| } |
| if ((value = aRadioUrl.GetValue("spi-small-packet"))) |
| { |
| spiSmallPacketSize = static_cast<uint8_t>(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<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 != 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<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)); |
| |
| // 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<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) |
| { |
| // 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) |
| { |
| 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) |
| { |
| // 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 |
| mSpiGarbageFrameCount++; |
| |
| otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], |
| spiRxFrame[2], spiRxFrame[3], spiRxFrame[4]); |
| otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, spiTransferBytes); |
| otDumpDebg(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]); |
| otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, spiTransferBytes); |
| otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-RX", spiRxFrameBuffer, spiTransferBytes); |
| |
| ExitNow(); |
| } |
| |
| mSpiValidFrameCount++; |
| |
| 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())) |
| { |
| mSpiRxFrameByteCount += mSpiSlaveDataLen; |
| mSpiSlaveDataLen = 0; |
| mSpiRxFrameCount++; |
| 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; |
| |
| 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++; |
| |
| mSpiTxFrameCount++; |
| mSpiTxFrameByteCount += 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) |
| { |
| if (FD_ISSET(mIntGpioValueFd, aContext.mReadFdSet)) |
| { |
| 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; |
| 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())) |
| { |
| // Interrupt pin is asserted, set the timeout to be 0. |
| spiTimeout.tv_sec = 0; |
| spiTimeout.tv_usec = 0; |
| } |
| 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, &readFdSet); |
| } |
| } |
| else |
| { |
| // In this case we don't have an interrupt, so we revert to SPI polling. |
| spiTimeout.tv_sec = 0; |
| spiTimeout.tv_usec = kSpiPollPeriodUs; |
| } |
| |
| if (timercmp(&spiTimeout, &timeout, <)) |
| { |
| timeout = spiTimeout; |
| } |
| |
| ret = select(mIntGpioValueFd + 1, &readFdSet, nullptr, nullptr, &timeout); |
| |
| if (ret > 0 && FD_ISSET(mIntGpioValueFd, &readFdSet)) |
| { |
| struct gpioevent_data event; |
| |
| // Read event data to clear interrupt. |
| 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); |
| } |
| } // namespace Posix |
| } // namespace ot |
| |
| #endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_SPI |