• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2019, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file includes the implementation for the SPI interface to radio (RCP).
32  */
33 
34 #include "spi_interface.hpp"
35 
36 #include "platform-posix.h"
37 
38 #include <assert.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <getopt.h>
42 #include <inttypes.h>
43 #include <signal.h>
44 #include <stdint.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <syslog.h>
49 #include <unistd.h>
50 
51 #include <sys/file.h>
52 #include <sys/ioctl.h>
53 #include <sys/select.h>
54 #include <sys/types.h>
55 #include <sys/ucontext.h>
56 
57 #include "common/code_utils.hpp"
58 
59 #if OPENTHREAD_POSIX_CONFIG_SPINEL_SPI_INTERFACE_ENABLE
60 #include <linux/gpio.h>
61 #include <linux/ioctl.h>
62 #include <linux/spi/spidev.h>
63 
64 namespace ot {
65 namespace Posix {
66 
67 const char SpiInterface::kLogModuleName[] = "SpiIntface";
68 
SpiInterface(const Url::Url & aRadioUrl)69 SpiInterface::SpiInterface(const Url::Url &aRadioUrl)
70     : mReceiveFrameCallback(nullptr)
71     , mReceiveFrameContext(nullptr)
72     , mRxFrameBuffer(nullptr)
73     , mRadioUrl(aRadioUrl)
74     , mSpiDevFd(-1)
75     , mResetGpioValueFd(-1)
76     , mIntGpioValueFd(-1)
77     , mSlaveResetCount(0)
78     , mSpiDuplexFrameCount(0)
79     , mSpiUnresponsiveFrameCount(0)
80     , mSpiTxIsReady(false)
81     , mSpiTxRefusedCount(0)
82     , mSpiTxPayloadSize(0)
83     , mDidPrintRateLimitLog(false)
84     , mSpiSlaveDataLen(0)
85     , mDidRxFrame(false)
86 {
87 }
88 
ResetStates(void)89 void SpiInterface::ResetStates(void)
90 {
91     mSpiTxIsReady         = false;
92     mSpiTxRefusedCount    = 0;
93     mSpiTxPayloadSize     = 0;
94     mDidPrintRateLimitLog = false;
95     mSpiSlaveDataLen      = 0;
96     memset(mSpiTxFrameBuffer, 0, sizeof(mSpiTxFrameBuffer));
97     memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics));
98     mInterfaceMetrics.mRcpInterfaceType = kSpinelInterfaceTypeSpi;
99 }
100 
HardwareReset(void)101 otError SpiInterface::HardwareReset(void)
102 {
103     ResetStates();
104     TriggerReset();
105 
106     // If the `INT` pin is set to low during the restart of the RCP chip, which triggers continuous invalid SPI
107     // transactions by the host, it will cause the function `PushPullSpi()` to output lots of invalid warn log
108     // messages. Adding the delay here is used to wait for the RCP chip starts up to avoid outputting invalid
109     // log messages.
110     usleep(static_cast<useconds_t>(mSpiResetDelay) * kUsecPerMsec);
111 
112     return OT_ERROR_NONE;
113 }
114 
Init(ReceiveFrameCallback aCallback,void * aCallbackContext,RxFrameBuffer & aFrameBuffer)115 otError SpiInterface::Init(ReceiveFrameCallback aCallback, void *aCallbackContext, RxFrameBuffer &aFrameBuffer)
116 {
117     const char *spiGpioIntDevice;
118     const char *spiGpioResetDevice;
119     uint8_t     spiGpioIntLine     = 0;
120     uint8_t     spiGpioResetLine   = 0;
121     uint8_t     spiMode            = OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE;
122     uint32_t    spiSpeed           = SPI_IOC_WR_MAX_SPEED_HZ;
123     uint32_t    spiResetDelay      = OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS;
124     uint16_t    spiCsDelay         = OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US;
125     uint8_t     spiAlignAllowance  = OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE;
126     uint8_t     spiSmallPacketSize = OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE;
127 
128     spiGpioIntDevice   = mRadioUrl.GetValue("gpio-int-device");
129     spiGpioResetDevice = mRadioUrl.GetValue("gpio-reset-device");
130 
131     VerifyOrDie(spiGpioIntDevice, OT_EXIT_INVALID_ARGUMENTS);
132     SuccessOrDie(mRadioUrl.ParseUint8("gpio-int-line", spiGpioIntLine));
133     VerifyOrDie(mRadioUrl.ParseUint8("spi-mode", spiMode) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
134     VerifyOrDie(mRadioUrl.ParseUint32("spi-speed", spiSpeed) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
135     VerifyOrDie(mRadioUrl.ParseUint32("spi-reset-delay", spiResetDelay) != OT_ERROR_INVALID_ARGS,
136                 OT_EXIT_INVALID_ARGUMENTS);
137     VerifyOrDie(mRadioUrl.ParseUint16("spi-cs-delay", spiCsDelay) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
138     VerifyOrDie(mRadioUrl.ParseUint8("spi-align-allowance", spiAlignAllowance) != OT_ERROR_INVALID_ARGS,
139                 OT_EXIT_INVALID_ARGUMENTS);
140     VerifyOrDie(mRadioUrl.ParseUint8("spi-small-packet", spiSmallPacketSize) != OT_ERROR_INVALID_ARGS,
141                 OT_EXIT_INVALID_ARGUMENTS);
142     VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_INVALID_ARGUMENTS);
143 
144     mSpiResetDelay      = spiResetDelay;
145     mSpiCsDelayUs       = spiCsDelay;
146     mSpiSmallPacketSize = spiSmallPacketSize;
147     mSpiAlignAllowance  = spiAlignAllowance;
148 
149     InitIntPin(spiGpioIntDevice, spiGpioIntLine);
150 
151     if (spiGpioResetDevice)
152     {
153         SuccessOrDie(mRadioUrl.ParseUint8("gpio-reset-line", spiGpioResetLine));
154         InitResetPin(spiGpioResetDevice, spiGpioResetLine);
155     }
156     else
157     {
158         LogNote("gpio-reset-device is not given.");
159     }
160 
161     InitSpiDev(mRadioUrl.GetPath(), spiMode, spiSpeed);
162 
163     mReceiveFrameCallback = aCallback;
164     mReceiveFrameContext  = aCallbackContext;
165     mRxFrameBuffer        = &aFrameBuffer;
166 
167     return OT_ERROR_NONE;
168 }
169 
~SpiInterface(void)170 SpiInterface::~SpiInterface(void) { Deinit(); }
171 
Deinit(void)172 void SpiInterface::Deinit(void)
173 {
174     if (mSpiDevFd >= 0)
175     {
176         close(mSpiDevFd);
177         mSpiDevFd = -1;
178     }
179 
180     if (mResetGpioValueFd >= 0)
181     {
182         close(mResetGpioValueFd);
183         mResetGpioValueFd = -1;
184     }
185 
186     if (mIntGpioValueFd >= 0)
187     {
188         close(mIntGpioValueFd);
189         mIntGpioValueFd = -1;
190     }
191 
192     mReceiveFrameCallback = nullptr;
193     mReceiveFrameContext  = nullptr;
194     mRxFrameBuffer        = nullptr;
195 }
196 
SetupGpioHandle(int aFd,uint8_t aLine,uint32_t aHandleFlags,const char * aLabel)197 int SpiInterface::SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel)
198 {
199     struct gpiohandle_request req;
200     int                       ret;
201 
202     assert(strlen(aLabel) < sizeof(req.consumer_label));
203 
204     req.flags             = aHandleFlags;
205     req.lines             = 1;
206     req.lineoffsets[0]    = aLine;
207     req.default_values[0] = 1;
208 
209     snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
210 
211     VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEHANDLE_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
212 
213     return req.fd;
214 }
215 
SetupGpioEvent(int aFd,uint8_t aLine,uint32_t aHandleFlags,uint32_t aEventFlags,const char * aLabel)216 int SpiInterface::SetupGpioEvent(int         aFd,
217                                  uint8_t     aLine,
218                                  uint32_t    aHandleFlags,
219                                  uint32_t    aEventFlags,
220                                  const char *aLabel)
221 {
222     struct gpioevent_request req;
223     int                      ret;
224 
225     assert(strlen(aLabel) < sizeof(req.consumer_label));
226 
227     req.lineoffset  = aLine;
228     req.handleflags = aHandleFlags;
229     req.eventflags  = aEventFlags;
230     snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
231 
232     VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEEVENT_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
233 
234     return req.fd;
235 }
236 
SetGpioValue(int aFd,uint8_t aValue)237 void SpiInterface::SetGpioValue(int aFd, uint8_t aValue)
238 {
239     struct gpiohandle_data data;
240 
241     data.values[0] = aValue;
242     VerifyOrDie(ioctl(aFd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
243 }
244 
GetGpioValue(int aFd)245 uint8_t SpiInterface::GetGpioValue(int aFd)
246 {
247     struct gpiohandle_data data;
248 
249     VerifyOrDie(ioctl(aFd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
250     return data.values[0];
251 }
252 
InitResetPin(const char * aCharDev,uint8_t aLine)253 void SpiInterface::InitResetPin(const char *aCharDev, uint8_t aLine)
254 {
255     char label[] = "SOC_THREAD_RESET";
256     int  fd;
257 
258     LogDebg("InitResetPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
259 
260     VerifyOrDie(aCharDev != nullptr, OT_EXIT_INVALID_ARGUMENTS);
261     VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
262     mResetGpioValueFd = SetupGpioHandle(fd, aLine, GPIOHANDLE_REQUEST_OUTPUT, label);
263 
264     close(fd);
265 }
266 
InitIntPin(const char * aCharDev,uint8_t aLine)267 void SpiInterface::InitIntPin(const char *aCharDev, uint8_t aLine)
268 {
269     char label[] = "THREAD_SOC_INT";
270     int  fd;
271 
272     LogDebg("InitIntPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
273 
274     VerifyOrDie(aCharDev != nullptr, OT_EXIT_INVALID_ARGUMENTS);
275     VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
276 
277     mIntGpioValueFd = SetupGpioEvent(fd, aLine, GPIOHANDLE_REQUEST_INPUT, GPIOEVENT_REQUEST_FALLING_EDGE, label);
278 
279     close(fd);
280 }
281 
InitSpiDev(const char * aPath,uint8_t aMode,uint32_t aSpeed)282 void SpiInterface::InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed)
283 {
284     const uint8_t wordBits = kSpiBitsPerWord;
285     int           fd;
286 
287     LogDebg("InitSpiDev: path=%s, mode=%" PRIu8 ", speed=%" PRIu32, aPath, aMode, aSpeed);
288 
289     VerifyOrDie((aPath != nullptr) && (aMode <= kSpiModeMax), OT_EXIT_INVALID_ARGUMENTS);
290     VerifyOrDie((fd = open(aPath, O_RDWR | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
291     VerifyOrExit(ioctl(fd, SPI_IOC_WR_MODE, &aMode) != -1, LogError("ioctl(SPI_IOC_WR_MODE)"));
292     VerifyOrExit(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &aSpeed) != -1, LogError("ioctl(SPI_IOC_WR_MAX_SPEED_HZ)"));
293     VerifyOrExit(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &wordBits) != -1, LogError("ioctl(SPI_IOC_WR_BITS_PER_WORD)"));
294     VerifyOrExit(flock(fd, LOCK_EX | LOCK_NB) != -1, LogError("flock"));
295 
296     mSpiDevFd   = fd;
297     mSpiMode    = aMode;
298     mSpiSpeedHz = aSpeed;
299     fd          = -1;
300 
301 exit:
302     if (fd >= 0)
303     {
304         close(fd);
305     }
306 }
307 
TriggerReset(void)308 void SpiInterface::TriggerReset(void)
309 {
310     VerifyOrDie(mResetGpioValueFd >= 0, OT_EXIT_RCP_RESET_REQUIRED);
311 
312     // Set Reset pin to low level.
313     SetGpioValue(mResetGpioValueFd, 0);
314 
315     usleep(kResetHoldOnUsec);
316 
317     // Set Reset pin to high level.
318     SetGpioValue(mResetGpioValueFd, 1);
319 
320     LogNote("Triggered hardware reset");
321 }
322 
GetRealRxFrameStart(uint8_t * aSpiRxFrameBuffer,uint8_t aAlignAllowance,uint16_t & aSkipLength)323 uint8_t *SpiInterface::GetRealRxFrameStart(uint8_t *aSpiRxFrameBuffer, uint8_t aAlignAllowance, uint16_t &aSkipLength)
324 {
325     uint8_t       *start = aSpiRxFrameBuffer;
326     const uint8_t *end   = aSpiRxFrameBuffer + aAlignAllowance;
327 
328     for (; start != end && ((start[0] == 0xff) || (start[0] == 0x00)); start++)
329         ;
330 
331     aSkipLength = static_cast<uint16_t>(start - aSpiRxFrameBuffer);
332 
333     return start;
334 }
335 
DoSpiTransfer(uint8_t * aSpiRxFrameBuffer,uint32_t aTransferLength)336 otError SpiInterface::DoSpiTransfer(uint8_t *aSpiRxFrameBuffer, uint32_t aTransferLength)
337 {
338     int                     ret;
339     struct spi_ioc_transfer transfer[2];
340 
341     memset(&transfer[0], 0, sizeof(transfer));
342 
343     // This part is the delay between C̅S̅ being asserted and the SPI clock
344     // starting. This is not supported by all Linux SPI drivers.
345     transfer[0].tx_buf        = 0;
346     transfer[0].rx_buf        = 0;
347     transfer[0].len           = 0;
348     transfer[0].speed_hz      = mSpiSpeedHz;
349     transfer[0].delay_usecs   = mSpiCsDelayUs;
350     transfer[0].bits_per_word = kSpiBitsPerWord;
351     transfer[0].cs_change     = false;
352 
353     // This part is the actual SPI transfer.
354     transfer[1].tx_buf        = reinterpret_cast<uintptr_t>(mSpiTxFrameBuffer);
355     transfer[1].rx_buf        = reinterpret_cast<uintptr_t>(aSpiRxFrameBuffer);
356     transfer[1].len           = aTransferLength;
357     transfer[1].speed_hz      = mSpiSpeedHz;
358     transfer[1].delay_usecs   = 0;
359     transfer[1].bits_per_word = kSpiBitsPerWord;
360     transfer[1].cs_change     = false;
361 
362     if (mSpiCsDelayUs > 0)
363     {
364         // A C̅S̅ delay has been specified. Start transactions with both parts.
365         ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(2), &transfer[0]);
366     }
367     else
368     {
369         // No C̅S̅ delay has been specified, so we skip the first part because it causes some SPI drivers to croak.
370         ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(1), &transfer[1]);
371     }
372 
373     if (ret != -1)
374     {
375         otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, static_cast<uint16_t>(transfer[1].len));
376         otDumpDebgPlat("SPI-RX", aSpiRxFrameBuffer, static_cast<uint16_t>(transfer[1].len));
377 
378         mInterfaceMetrics.mTransferredFrameCount++;
379     }
380 
381     return (ret < 0) ? OT_ERROR_FAILED : OT_ERROR_NONE;
382 }
383 
PushPullSpi(void)384 otError SpiInterface::PushPullSpi(void)
385 {
386     otError          error               = OT_ERROR_FAILED;
387     uint16_t         spiTransferBytes    = 0;
388     uint8_t          successfulExchanges = 0;
389     bool             discardRxFrame      = true;
390     uint8_t         *spiRxFrameBuffer;
391     uint8_t         *spiRxFrame;
392     uint8_t          slaveHeader;
393     uint16_t         slaveAcceptLen;
394     Spinel::SpiFrame txFrame(mSpiTxFrameBuffer);
395     uint16_t         skipAlignAllowanceLength;
396 
397     VerifyOrExit((mReceiveFrameCallback != nullptr) && (mRxFrameBuffer != nullptr), error = OT_ERROR_INVALID_STATE);
398 
399     if (mInterfaceMetrics.mTransferredValidFrameCount == 0)
400     {
401         // Set the reset flag to indicate to our slave that we are coming up from scratch.
402         txFrame.SetHeaderFlagByte(true);
403     }
404     else
405     {
406         txFrame.SetHeaderFlagByte(false);
407     }
408 
409     // Zero out our rx_accept and our data_len for now.
410     txFrame.SetHeaderAcceptLen(0);
411     txFrame.SetHeaderDataLen(0);
412 
413     // Sanity check.
414     if (mSpiSlaveDataLen > kMaxFrameSize)
415     {
416         mSpiSlaveDataLen = 0;
417     }
418 
419     if (mSpiTxIsReady)
420     {
421         // Go ahead and try to immediately send a frame if we have it queued up.
422         txFrame.SetHeaderDataLen(mSpiTxPayloadSize);
423 
424         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiTxPayloadSize);
425     }
426 
427     if (mSpiSlaveDataLen != 0)
428     {
429         // In a previous transaction the slave indicated it had something to send us. Make sure our transaction
430         // is large enough to handle it.
431         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSlaveDataLen);
432     }
433     else
434     {
435         // Set up a minimum transfer size to allow small frames the slave wants to send us to be handled in a
436         // single transaction.
437         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSmallPacketSize);
438     }
439 
440     txFrame.SetHeaderAcceptLen(spiTransferBytes);
441 
442     // Set skip length to make MultiFrameBuffer to reserve a space in front of the frame buffer.
443     SuccessOrExit(error = mRxFrameBuffer->SetSkipLength(kSpiFrameHeaderSize));
444 
445     // Check whether the remaining frame buffer has enough space to store the data to be received.
446     VerifyOrExit(mRxFrameBuffer->GetFrameMaxLength() >= spiTransferBytes + mSpiAlignAllowance);
447 
448     // Point to the start of the reserved buffer.
449     spiRxFrameBuffer = mRxFrameBuffer->GetFrame() - kSpiFrameHeaderSize;
450 
451     // Set the total number of bytes to be transmitted.
452     spiTransferBytes += kSpiFrameHeaderSize + mSpiAlignAllowance;
453 
454     // Perform the SPI transaction.
455     error = DoSpiTransfer(spiRxFrameBuffer, spiTransferBytes);
456 
457     if (error != OT_ERROR_NONE)
458     {
459         LogCrit("PushPullSpi:DoSpiTransfer: errno=%s", strerror(errno));
460 
461         // Print out a helpful error message for a common error.
462         if ((mSpiCsDelayUs != 0) && (errno == EINVAL))
463         {
464             LogWarn("SPI ioctl failed with EINVAL. Try adding `--spi-cs-delay=0` to command line arguments.");
465         }
466 
467         LogStats();
468         DieNow(OT_EXIT_FAILURE);
469     }
470 
471     // Account for misalignment (0xFF or 0x00 bytes at the start)
472     spiRxFrame = GetRealRxFrameStart(spiRxFrameBuffer, mSpiAlignAllowance, skipAlignAllowanceLength);
473 
474     {
475         Spinel::SpiFrame rxFrame(spiRxFrame);
476 
477         LogDebg("spi_transfer TX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, txFrame.GetHeaderFlagByte(),
478                 txFrame.GetHeaderAcceptLen(), txFrame.GetHeaderDataLen());
479         LogDebg("spi_transfer RX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, rxFrame.GetHeaderFlagByte(),
480                 rxFrame.GetHeaderAcceptLen(), rxFrame.GetHeaderDataLen());
481 
482         slaveHeader = rxFrame.GetHeaderFlagByte();
483         if ((slaveHeader == 0xFF) || (slaveHeader == 0x00))
484         {
485             if ((slaveHeader == spiRxFrame[1]) && (slaveHeader == spiRxFrame[2]) && (slaveHeader == spiRxFrame[3]) &&
486                 (slaveHeader == spiRxFrame[4]))
487             {
488                 // Device is off or in a bad state. In some cases may be induced by flow control.
489                 if (mSpiSlaveDataLen == 0)
490                 {
491                     LogDebg("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
492                 }
493                 else
494                 {
495                     LogWarn("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
496                 }
497 
498                 mSpiUnresponsiveFrameCount++;
499             }
500             else
501             {
502                 // Header is full of garbage
503                 mInterfaceMetrics.mTransferredGarbageFrameCount++;
504 
505                 LogWarn("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2],
506                         spiRxFrame[3], spiRxFrame[4]);
507                 otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
508                 otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes);
509             }
510 
511             mSpiTxRefusedCount++;
512             ExitNow();
513         }
514 
515         slaveAcceptLen   = rxFrame.GetHeaderAcceptLen();
516         mSpiSlaveDataLen = rxFrame.GetHeaderDataLen();
517 
518         if (!rxFrame.IsValid() || (slaveAcceptLen > kMaxFrameSize) || (mSpiSlaveDataLen > kMaxFrameSize))
519         {
520             mInterfaceMetrics.mTransferredGarbageFrameCount++;
521             mSpiTxRefusedCount++;
522             mSpiSlaveDataLen = 0;
523 
524             LogWarn("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2],
525                     spiRxFrame[3], spiRxFrame[4]);
526             otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
527             otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes);
528 
529             ExitNow();
530         }
531 
532         mInterfaceMetrics.mTransferredValidFrameCount++;
533 
534         if (rxFrame.IsResetFlagSet())
535         {
536             mSlaveResetCount++;
537 
538             LogNote("Slave did reset (%" PRIu64 " resets so far)", mSlaveResetCount);
539             LogStats();
540         }
541 
542         // Handle received packet, if any.
543         if ((mSpiSlaveDataLen != 0) && (mSpiSlaveDataLen <= txFrame.GetHeaderAcceptLen()))
544         {
545             mInterfaceMetrics.mRxFrameByteCount += mSpiSlaveDataLen;
546             mSpiSlaveDataLen = 0;
547             mInterfaceMetrics.mRxFrameCount++;
548             successfulExchanges++;
549 
550             // Set the skip length to skip align bytes and SPI frame header.
551             SuccessOrExit(error = mRxFrameBuffer->SetSkipLength(skipAlignAllowanceLength + kSpiFrameHeaderSize));
552             // Set the received frame length.
553             SuccessOrExit(error = mRxFrameBuffer->SetLength(rxFrame.GetHeaderDataLen()));
554 
555             // Upper layer will free the frame buffer.
556             discardRxFrame = false;
557 
558             mDidRxFrame = true;
559             mReceiveFrameCallback(mReceiveFrameContext);
560         }
561     }
562 
563     // Handle transmitted packet, if any.
564     if (mSpiTxIsReady && (mSpiTxPayloadSize == txFrame.GetHeaderDataLen()))
565     {
566         if (txFrame.GetHeaderDataLen() <= slaveAcceptLen)
567         {
568             // Our outbound packet has been successfully transmitted. Clear mSpiTxPayloadSize and mSpiTxIsReady so
569             // that uplayer can pull another packet for us to send.
570             successfulExchanges++;
571 
572             mInterfaceMetrics.mTxFrameCount++;
573             mInterfaceMetrics.mTxFrameByteCount += mSpiTxPayloadSize;
574 
575             // Clear tx buffer after usage
576             memset(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], 0, mSpiTxPayloadSize);
577             mSpiTxIsReady      = false;
578             mSpiTxPayloadSize  = 0;
579             mSpiTxRefusedCount = 0;
580         }
581         else
582         {
583             // The slave wasn't ready for what we had to send them. Incrementing this counter will turn on rate
584             // limiting so that we don't waste a ton of CPU bombarding them with useless SPI transfers.
585             mSpiTxRefusedCount++;
586         }
587     }
588 
589     if (!mSpiTxIsReady)
590     {
591         mSpiTxRefusedCount = 0;
592     }
593 
594     if (successfulExchanges == 2)
595     {
596         mSpiDuplexFrameCount++;
597     }
598 
599 exit:
600     if (discardRxFrame)
601     {
602         mRxFrameBuffer->DiscardFrame();
603     }
604 
605     return error;
606 }
607 
CheckInterrupt(void)608 bool SpiInterface::CheckInterrupt(void) { return (GetGpioValue(mIntGpioValueFd) == kGpioIntAssertState); }
609 
UpdateFdSet(void * aMainloopContext)610 void SpiInterface::UpdateFdSet(void *aMainloopContext)
611 {
612     struct timeval        timeout = {kSecPerDay, 0};
613     otSysMainloopContext *context = reinterpret_cast<otSysMainloopContext *>(aMainloopContext);
614 
615     assert(context != nullptr);
616 
617     if (mSpiTxIsReady)
618     {
619         // We have data to send to the slave.
620         timeout.tv_sec  = 0;
621         timeout.tv_usec = 0;
622     }
623 
624     if (context->mMaxFd < mIntGpioValueFd)
625     {
626         context->mMaxFd = mIntGpioValueFd;
627     }
628 
629     if (CheckInterrupt())
630     {
631         // Interrupt pin is asserted, set the timeout to be 0.
632         timeout.tv_sec  = 0;
633         timeout.tv_usec = 0;
634         LogDebg("UpdateFdSet(): Interrupt.");
635     }
636     else
637     {
638         // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the
639         // read set.
640         FD_SET(mIntGpioValueFd, &context->mReadFdSet);
641     }
642 
643     if (mSpiTxRefusedCount)
644     {
645         struct timeval minTimeout = {0, 0};
646 
647         // We are being rate-limited by the slave. This is fairly normal behavior. Based on number of times slave has
648         // refused a transmission, we apply a minimum timeout.
649         if (mSpiTxRefusedCount < kImmediateRetryCount)
650         {
651             minTimeout.tv_usec = kImmediateRetryTimeoutUs;
652         }
653         else if (mSpiTxRefusedCount < kFastRetryCount)
654         {
655             minTimeout.tv_usec = kFastRetryTimeoutUs;
656         }
657         else
658         {
659             minTimeout.tv_usec = kSlowRetryTimeoutUs;
660         }
661 
662         if (timercmp(&timeout, &minTimeout, <))
663         {
664             timeout = minTimeout;
665         }
666 
667         if (mSpiTxIsReady && !mDidPrintRateLimitLog && (mSpiTxRefusedCount > 1))
668         {
669             // To avoid printing out this message over and over, we only print it out once the refused count is at two
670             // or higher when we actually have something to send the slave. And then, we only print it once.
671             LogInfo("Slave is rate limiting transactions");
672 
673             mDidPrintRateLimitLog = true;
674         }
675 
676         if (mSpiTxRefusedCount == kSpiTxRefuseWarnCount)
677         {
678             // Ua-oh. The slave hasn't given us a chance to send it anything for over thirty frames. If this ever
679             // happens, print out a warning to the logs.
680             LogWarn("Slave seems stuck.");
681         }
682         else if (mSpiTxRefusedCount == kSpiTxRefuseExitCount)
683         {
684             // Double ua-oh. The slave hasn't given us a chance to send it anything for over a hundred frames.
685             // This almost certainly means that the slave has locked up or gotten into an unrecoverable state.
686             DieNowWithMessage("Slave seems REALLY stuck.", OT_EXIT_FAILURE);
687         }
688     }
689     else
690     {
691         mDidPrintRateLimitLog = false;
692     }
693 
694     if (timercmp(&timeout, &context->mTimeout, <))
695     {
696         context->mTimeout = timeout;
697     }
698 }
699 
Process(const void * aMainloopContext)700 void SpiInterface::Process(const void *aMainloopContext)
701 {
702     const otSysMainloopContext *context = reinterpret_cast<const otSysMainloopContext *>(aMainloopContext);
703 
704     assert(context != nullptr);
705 
706     if (FD_ISSET(mIntGpioValueFd, &context->mReadFdSet))
707     {
708         struct gpioevent_data event;
709 
710         LogDebg("Process(): Interrupt.");
711 
712         // Read event data to clear interrupt.
713         VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_ERROR_ERRNO);
714     }
715 
716     // Service the SPI port if we can receive a packet or we have a packet to be sent.
717     if (mSpiTxIsReady || CheckInterrupt())
718     {
719         // We guard this with the above check because we don't want to overwrite any previously received frames.
720         IgnoreError(PushPullSpi());
721     }
722 }
723 
WaitForFrame(uint64_t aTimeoutUs)724 otError SpiInterface::WaitForFrame(uint64_t aTimeoutUs)
725 {
726     otError  error = OT_ERROR_NONE;
727     uint64_t now   = otPlatTimeGet();
728     uint64_t end   = now + aTimeoutUs;
729 
730     mDidRxFrame = false;
731 
732     while (now < end)
733     {
734         otSysMainloopContext context;
735         int                  ret;
736 
737         context.mMaxFd           = -1;
738         context.mTimeout.tv_sec  = static_cast<time_t>((end - now) / OT_US_PER_S);
739         context.mTimeout.tv_usec = static_cast<suseconds_t>((end - now) % OT_US_PER_S);
740 
741         FD_ZERO(&context.mReadFdSet);
742         FD_ZERO(&context.mWriteFdSet);
743 
744         UpdateFdSet(&context);
745 
746         ret = select(context.mMaxFd + 1, &context.mReadFdSet, &context.mWriteFdSet, nullptr, &context.mTimeout);
747 
748         if (ret >= 0)
749         {
750             Process(&context);
751 
752             if (mDidRxFrame)
753             {
754                 ExitNow();
755             }
756         }
757         else if (errno != EINTR)
758         {
759             DieNow(OT_EXIT_ERROR_ERRNO);
760         }
761 
762         now = otPlatTimeGet();
763     }
764 
765     error = OT_ERROR_RESPONSE_TIMEOUT;
766 
767 exit:
768     return error;
769 }
770 
SendFrame(const uint8_t * aFrame,uint16_t aLength)771 otError SpiInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength)
772 {
773     otError error = OT_ERROR_NONE;
774 
775     VerifyOrExit(aLength < (kMaxFrameSize - kSpiFrameHeaderSize), error = OT_ERROR_NO_BUFS);
776 
777     if (IsSpinelResetCommand(aFrame, aLength))
778     {
779         ResetStates();
780     }
781 
782     VerifyOrExit(!mSpiTxIsReady, error = OT_ERROR_BUSY);
783 
784     memcpy(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], aFrame, aLength);
785 
786     mSpiTxIsReady     = true;
787     mSpiTxPayloadSize = aLength;
788 
789     IgnoreError(PushPullSpi());
790 
791 exit:
792     return error;
793 }
794 
LogError(const char * aString)795 void SpiInterface::LogError(const char *aString)
796 {
797     OT_UNUSED_VARIABLE(aString);
798     LogWarn("%s: %s", aString, strerror(errno));
799 }
800 
LogStats(void)801 void SpiInterface::LogStats(void)
802 {
803     LogInfo("INFO: SlaveResetCount=%" PRIu64, mSlaveResetCount);
804     LogInfo("INFO: SpiDuplexFrameCount=%" PRIu64, mSpiDuplexFrameCount);
805     LogInfo("INFO: SpiUnresponsiveFrameCount=%" PRIu64, mSpiUnresponsiveFrameCount);
806     LogInfo("INFO: TransferredFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredFrameCount);
807     LogInfo("INFO: TransferredValidFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredValidFrameCount);
808     LogInfo("INFO: TransferredGarbageFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredGarbageFrameCount);
809     LogInfo("INFO: RxFrameCount=%" PRIu64, mInterfaceMetrics.mRxFrameCount);
810     LogInfo("INFO: RxFrameByteCount=%" PRIu64, mInterfaceMetrics.mRxFrameByteCount);
811     LogInfo("INFO: TxFrameCount=%" PRIu64, mInterfaceMetrics.mTxFrameCount);
812     LogInfo("INFO: TxFrameByteCount=%" PRIu64, mInterfaceMetrics.mTxFrameByteCount);
813 }
814 } // namespace Posix
815 } // namespace ot
816 #endif // OPENTHREAD_POSIX_CONFIG_SPINEL_SPI_INTERFACE_ENABLE
817