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     if (!spiGpioIntDevice || !spiGpioResetDevice)
131     {
132         DieNow(OT_EXIT_INVALID_ARGUMENTS);
133     }
134 
135     SuccessOrDie(mRadioUrl.ParseUint8("gpio-int-line", spiGpioIntLine));
136     SuccessOrDie(mRadioUrl.ParseUint8("gpio-reset-line", spiGpioResetLine));
137     VerifyOrDie(mRadioUrl.ParseUint8("spi-mode", spiMode) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
138     VerifyOrDie(mRadioUrl.ParseUint32("spi-speed", spiSpeed) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
139     VerifyOrDie(mRadioUrl.ParseUint32("spi-reset-delay", spiResetDelay) != OT_ERROR_INVALID_ARGS,
140                 OT_EXIT_INVALID_ARGUMENTS);
141     VerifyOrDie(mRadioUrl.ParseUint16("spi-cs-delay", spiCsDelay) != OT_ERROR_INVALID_ARGS, OT_EXIT_INVALID_ARGUMENTS);
142     VerifyOrDie(mRadioUrl.ParseUint8("spi-align-allowance", spiAlignAllowance) != OT_ERROR_INVALID_ARGS,
143                 OT_EXIT_INVALID_ARGUMENTS);
144     VerifyOrDie(mRadioUrl.ParseUint8("spi-small-packet", spiSmallPacketSize) != OT_ERROR_INVALID_ARGS,
145                 OT_EXIT_INVALID_ARGUMENTS);
146     VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_INVALID_ARGUMENTS);
147 
148     mSpiResetDelay      = spiResetDelay;
149     mSpiCsDelayUs       = spiCsDelay;
150     mSpiSmallPacketSize = spiSmallPacketSize;
151     mSpiAlignAllowance  = spiAlignAllowance;
152 
153     if (spiGpioIntDevice != nullptr)
154     {
155         // If the interrupt pin is not set, SPI interface will use polling mode.
156         InitIntPin(spiGpioIntDevice, spiGpioIntLine);
157     }
158     else
159     {
160         LogNote("SPI interface enters polling mode.");
161     }
162 
163     InitResetPin(spiGpioResetDevice, spiGpioResetLine);
164     InitSpiDev(mRadioUrl.GetPath(), spiMode, spiSpeed);
165 
166     mReceiveFrameCallback = aCallback;
167     mReceiveFrameContext  = aCallbackContext;
168     mRxFrameBuffer        = &aFrameBuffer;
169 
170     return OT_ERROR_NONE;
171 }
172 
~SpiInterface(void)173 SpiInterface::~SpiInterface(void) { Deinit(); }
174 
Deinit(void)175 void SpiInterface::Deinit(void)
176 {
177     if (mSpiDevFd >= 0)
178     {
179         close(mSpiDevFd);
180         mSpiDevFd = -1;
181     }
182 
183     if (mResetGpioValueFd >= 0)
184     {
185         close(mResetGpioValueFd);
186         mResetGpioValueFd = -1;
187     }
188 
189     if (mIntGpioValueFd >= 0)
190     {
191         close(mIntGpioValueFd);
192         mIntGpioValueFd = -1;
193     }
194 
195     mReceiveFrameCallback = nullptr;
196     mReceiveFrameContext  = nullptr;
197     mRxFrameBuffer        = nullptr;
198 }
199 
SetupGpioHandle(int aFd,uint8_t aLine,uint32_t aHandleFlags,const char * aLabel)200 int SpiInterface::SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel)
201 {
202     struct gpiohandle_request req;
203     int                       ret;
204 
205     assert(strlen(aLabel) < sizeof(req.consumer_label));
206 
207     req.flags             = aHandleFlags;
208     req.lines             = 1;
209     req.lineoffsets[0]    = aLine;
210     req.default_values[0] = 1;
211 
212     snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
213 
214     VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEHANDLE_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
215 
216     return req.fd;
217 }
218 
SetupGpioEvent(int aFd,uint8_t aLine,uint32_t aHandleFlags,uint32_t aEventFlags,const char * aLabel)219 int SpiInterface::SetupGpioEvent(int         aFd,
220                                  uint8_t     aLine,
221                                  uint32_t    aHandleFlags,
222                                  uint32_t    aEventFlags,
223                                  const char *aLabel)
224 {
225     struct gpioevent_request req;
226     int                      ret;
227 
228     assert(strlen(aLabel) < sizeof(req.consumer_label));
229 
230     req.lineoffset  = aLine;
231     req.handleflags = aHandleFlags;
232     req.eventflags  = aEventFlags;
233     snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel);
234 
235     VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEEVENT_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO);
236 
237     return req.fd;
238 }
239 
SetGpioValue(int aFd,uint8_t aValue)240 void SpiInterface::SetGpioValue(int aFd, uint8_t aValue)
241 {
242     struct gpiohandle_data data;
243 
244     data.values[0] = aValue;
245     VerifyOrDie(ioctl(aFd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
246 }
247 
GetGpioValue(int aFd)248 uint8_t SpiInterface::GetGpioValue(int aFd)
249 {
250     struct gpiohandle_data data;
251 
252     VerifyOrDie(ioctl(aFd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO);
253     return data.values[0];
254 }
255 
InitResetPin(const char * aCharDev,uint8_t aLine)256 void SpiInterface::InitResetPin(const char *aCharDev, uint8_t aLine)
257 {
258     char label[] = "SOC_THREAD_RESET";
259     int  fd;
260 
261     LogDebg("InitResetPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
262 
263     VerifyOrDie(aCharDev != nullptr, OT_EXIT_INVALID_ARGUMENTS);
264     VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
265     mResetGpioValueFd = SetupGpioHandle(fd, aLine, GPIOHANDLE_REQUEST_OUTPUT, label);
266 
267     close(fd);
268 }
269 
InitIntPin(const char * aCharDev,uint8_t aLine)270 void SpiInterface::InitIntPin(const char *aCharDev, uint8_t aLine)
271 {
272     char label[] = "THREAD_SOC_INT";
273     int  fd;
274 
275     LogDebg("InitIntPin: charDev=%s, line=%" PRIu8, aCharDev, aLine);
276 
277     VerifyOrDie(aCharDev != nullptr, OT_EXIT_INVALID_ARGUMENTS);
278     VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO);
279 
280     mIntGpioValueFd = SetupGpioEvent(fd, aLine, GPIOHANDLE_REQUEST_INPUT, GPIOEVENT_REQUEST_FALLING_EDGE, label);
281 
282     close(fd);
283 }
284 
InitSpiDev(const char * aPath,uint8_t aMode,uint32_t aSpeed)285 void SpiInterface::InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed)
286 {
287     const uint8_t wordBits = kSpiBitsPerWord;
288     int           fd;
289 
290     LogDebg("InitSpiDev: path=%s, mode=%" PRIu8 ", speed=%" PRIu32, aPath, aMode, aSpeed);
291 
292     VerifyOrDie((aPath != nullptr) && (aMode <= kSpiModeMax), OT_EXIT_INVALID_ARGUMENTS);
293     VerifyOrDie((fd = open(aPath, O_RDWR | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
294     VerifyOrExit(ioctl(fd, SPI_IOC_WR_MODE, &aMode) != -1, LogError("ioctl(SPI_IOC_WR_MODE)"));
295     VerifyOrExit(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &aSpeed) != -1, LogError("ioctl(SPI_IOC_WR_MAX_SPEED_HZ)"));
296     VerifyOrExit(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &wordBits) != -1, LogError("ioctl(SPI_IOC_WR_BITS_PER_WORD)"));
297     VerifyOrExit(flock(fd, LOCK_EX | LOCK_NB) != -1, LogError("flock"));
298 
299     mSpiDevFd   = fd;
300     mSpiMode    = aMode;
301     mSpiSpeedHz = aSpeed;
302     fd          = -1;
303 
304 exit:
305     if (fd >= 0)
306     {
307         close(fd);
308     }
309 }
310 
TriggerReset(void)311 void SpiInterface::TriggerReset(void)
312 {
313     // Set Reset pin to low level.
314     SetGpioValue(mResetGpioValueFd, 0);
315 
316     usleep(kResetHoldOnUsec);
317 
318     // Set Reset pin to high level.
319     SetGpioValue(mResetGpioValueFd, 1);
320 
321     LogNote("Triggered hardware reset");
322 }
323 
GetRealRxFrameStart(uint8_t * aSpiRxFrameBuffer,uint8_t aAlignAllowance,uint16_t & aSkipLength)324 uint8_t *SpiInterface::GetRealRxFrameStart(uint8_t *aSpiRxFrameBuffer, uint8_t aAlignAllowance, uint16_t &aSkipLength)
325 {
326     uint8_t       *start = aSpiRxFrameBuffer;
327     const uint8_t *end   = aSpiRxFrameBuffer + aAlignAllowance;
328 
329     for (; start != end && start[0] == 0xff; start++)
330         ;
331 
332     aSkipLength = static_cast<uint16_t>(start - aSpiRxFrameBuffer);
333 
334     return start;
335 }
336 
DoSpiTransfer(uint8_t * aSpiRxFrameBuffer,uint32_t aTransferLength)337 otError SpiInterface::DoSpiTransfer(uint8_t *aSpiRxFrameBuffer, uint32_t aTransferLength)
338 {
339     int                     ret;
340     struct spi_ioc_transfer transfer[2];
341 
342     memset(&transfer[0], 0, sizeof(transfer));
343 
344     // This part is the delay between C̅S̅ being asserted and the SPI clock
345     // starting. This is not supported by all Linux SPI drivers.
346     transfer[0].tx_buf        = 0;
347     transfer[0].rx_buf        = 0;
348     transfer[0].len           = 0;
349     transfer[0].speed_hz      = mSpiSpeedHz;
350     transfer[0].delay_usecs   = mSpiCsDelayUs;
351     transfer[0].bits_per_word = kSpiBitsPerWord;
352     transfer[0].cs_change     = false;
353 
354     // This part is the actual SPI transfer.
355     transfer[1].tx_buf        = reinterpret_cast<uintptr_t>(mSpiTxFrameBuffer);
356     transfer[1].rx_buf        = reinterpret_cast<uintptr_t>(aSpiRxFrameBuffer);
357     transfer[1].len           = aTransferLength;
358     transfer[1].speed_hz      = mSpiSpeedHz;
359     transfer[1].delay_usecs   = 0;
360     transfer[1].bits_per_word = kSpiBitsPerWord;
361     transfer[1].cs_change     = false;
362 
363     if (mSpiCsDelayUs > 0)
364     {
365         // A C̅S̅ delay has been specified. Start transactions with both parts.
366         ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(2), &transfer[0]);
367     }
368     else
369     {
370         // No C̅S̅ delay has been specified, so we skip the first part because it causes some SPI drivers to croak.
371         ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(1), &transfer[1]);
372     }
373 
374     if (ret != -1)
375     {
376         otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, static_cast<uint16_t>(transfer[1].len));
377         otDumpDebgPlat("SPI-RX", aSpiRxFrameBuffer, static_cast<uint16_t>(transfer[1].len));
378 
379         mInterfaceMetrics.mTransferredFrameCount++;
380     }
381 
382     return (ret < 0) ? OT_ERROR_FAILED : OT_ERROR_NONE;
383 }
384 
PushPullSpi(void)385 otError SpiInterface::PushPullSpi(void)
386 {
387     otError          error               = OT_ERROR_FAILED;
388     uint16_t         spiTransferBytes    = 0;
389     uint8_t          successfulExchanges = 0;
390     bool             discardRxFrame      = true;
391     uint8_t         *spiRxFrameBuffer;
392     uint8_t         *spiRxFrame;
393     uint8_t          slaveHeader;
394     uint16_t         slaveAcceptLen;
395     Spinel::SpiFrame txFrame(mSpiTxFrameBuffer);
396     uint16_t         skipAlignAllowanceLength;
397 
398     VerifyOrExit((mReceiveFrameCallback != nullptr) && (mRxFrameBuffer != nullptr), error = OT_ERROR_INVALID_STATE);
399 
400     if (mInterfaceMetrics.mTransferredValidFrameCount == 0)
401     {
402         // Set the reset flag to indicate to our slave that we are coming up from scratch.
403         txFrame.SetHeaderFlagByte(true);
404     }
405     else
406     {
407         txFrame.SetHeaderFlagByte(false);
408     }
409 
410     // Zero out our rx_accept and our data_len for now.
411     txFrame.SetHeaderAcceptLen(0);
412     txFrame.SetHeaderDataLen(0);
413 
414     // Sanity check.
415     if (mSpiSlaveDataLen > kMaxFrameSize)
416     {
417         mSpiSlaveDataLen = 0;
418     }
419 
420     if (mSpiTxIsReady)
421     {
422         // Go ahead and try to immediately send a frame if we have it queued up.
423         txFrame.SetHeaderDataLen(mSpiTxPayloadSize);
424 
425         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiTxPayloadSize);
426     }
427 
428     if (mSpiSlaveDataLen != 0)
429     {
430         // In a previous transaction the slave indicated it had something to send us. Make sure our transaction
431         // is large enough to handle it.
432         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSlaveDataLen);
433     }
434     else
435     {
436         // Set up a minimum transfer size to allow small frames the slave wants to send us to be handled in a
437         // single transaction.
438         spiTransferBytes = OT_MAX(spiTransferBytes, mSpiSmallPacketSize);
439     }
440 
441     txFrame.SetHeaderAcceptLen(spiTransferBytes);
442 
443     // Set skip length to make MultiFrameBuffer to reserve a space in front of the frame buffer.
444     SuccessOrExit(error = mRxFrameBuffer->SetSkipLength(kSpiFrameHeaderSize));
445 
446     // Check whether the remaining frame buffer has enough space to store the data to be received.
447     VerifyOrExit(mRxFrameBuffer->GetFrameMaxLength() >= spiTransferBytes + mSpiAlignAllowance);
448 
449     // Point to the start of the reserved buffer.
450     spiRxFrameBuffer = mRxFrameBuffer->GetFrame() - kSpiFrameHeaderSize;
451 
452     // Set the total number of bytes to be transmitted.
453     spiTransferBytes += kSpiFrameHeaderSize + mSpiAlignAllowance;
454 
455     // Perform the SPI transaction.
456     error = DoSpiTransfer(spiRxFrameBuffer, spiTransferBytes);
457 
458     if (error != OT_ERROR_NONE)
459     {
460         LogCrit("PushPullSpi:DoSpiTransfer: errno=%s", strerror(errno));
461 
462         // Print out a helpful error message for a common error.
463         if ((mSpiCsDelayUs != 0) && (errno == EINVAL))
464         {
465             LogWarn("SPI ioctl failed with EINVAL. Try adding `--spi-cs-delay=0` to command line arguments.");
466         }
467 
468         LogStats();
469         DieNow(OT_EXIT_FAILURE);
470     }
471 
472     // Account for misalignment (0xFF bytes at the start)
473     spiRxFrame = GetRealRxFrameStart(spiRxFrameBuffer, mSpiAlignAllowance, skipAlignAllowanceLength);
474 
475     {
476         Spinel::SpiFrame rxFrame(spiRxFrame);
477 
478         LogDebg("spi_transfer TX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, txFrame.GetHeaderFlagByte(),
479                 txFrame.GetHeaderAcceptLen(), txFrame.GetHeaderDataLen());
480         LogDebg("spi_transfer RX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, rxFrame.GetHeaderFlagByte(),
481                 rxFrame.GetHeaderAcceptLen(), rxFrame.GetHeaderDataLen());
482 
483         slaveHeader = rxFrame.GetHeaderFlagByte();
484         if ((slaveHeader == 0xFF) || (slaveHeader == 0x00))
485         {
486             if ((slaveHeader == spiRxFrame[1]) && (slaveHeader == spiRxFrame[2]) && (slaveHeader == spiRxFrame[3]) &&
487                 (slaveHeader == spiRxFrame[4]))
488             {
489                 // Device is off or in a bad state. In some cases may be induced by flow control.
490                 if (mSpiSlaveDataLen == 0)
491                 {
492                     LogDebg("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
493                 }
494                 else
495                 {
496                     LogWarn("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader);
497                 }
498 
499                 mSpiUnresponsiveFrameCount++;
500             }
501             else
502             {
503                 // Header is full of garbage
504                 mInterfaceMetrics.mTransferredGarbageFrameCount++;
505 
506                 LogWarn("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2],
507                         spiRxFrame[3], spiRxFrame[4]);
508                 otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
509                 otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes);
510             }
511 
512             mSpiTxRefusedCount++;
513             ExitNow();
514         }
515 
516         slaveAcceptLen   = rxFrame.GetHeaderAcceptLen();
517         mSpiSlaveDataLen = rxFrame.GetHeaderDataLen();
518 
519         if (!rxFrame.IsValid() || (slaveAcceptLen > kMaxFrameSize) || (mSpiSlaveDataLen > kMaxFrameSize))
520         {
521             mInterfaceMetrics.mTransferredGarbageFrameCount++;
522             mSpiTxRefusedCount++;
523             mSpiSlaveDataLen = 0;
524 
525             LogWarn("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrame[0], spiRxFrame[1], spiRxFrame[2],
526                     spiRxFrame[3], spiRxFrame[4]);
527             otDumpDebgPlat("SPI-TX", mSpiTxFrameBuffer, spiTransferBytes);
528             otDumpDebgPlat("SPI-RX", spiRxFrameBuffer, spiTransferBytes);
529 
530             ExitNow();
531         }
532 
533         mInterfaceMetrics.mTransferredValidFrameCount++;
534 
535         if (rxFrame.IsResetFlagSet())
536         {
537             mSlaveResetCount++;
538 
539             LogNote("Slave did reset (%" PRIu64 " resets so far)", mSlaveResetCount);
540             LogStats();
541         }
542 
543         // Handle received packet, if any.
544         if ((mSpiSlaveDataLen != 0) && (mSpiSlaveDataLen <= txFrame.GetHeaderAcceptLen()))
545         {
546             mInterfaceMetrics.mRxFrameByteCount += mSpiSlaveDataLen;
547             mSpiSlaveDataLen = 0;
548             mInterfaceMetrics.mRxFrameCount++;
549             successfulExchanges++;
550 
551             // Set the skip length to skip align bytes and SPI frame header.
552             SuccessOrExit(error = mRxFrameBuffer->SetSkipLength(skipAlignAllowanceLength + kSpiFrameHeaderSize));
553             // Set the received frame length.
554             SuccessOrExit(error = mRxFrameBuffer->SetLength(rxFrame.GetHeaderDataLen()));
555 
556             // Upper layer will free the frame buffer.
557             discardRxFrame = false;
558 
559             mDidRxFrame = true;
560             mReceiveFrameCallback(mReceiveFrameContext);
561         }
562     }
563 
564     // Handle transmitted packet, if any.
565     if (mSpiTxIsReady && (mSpiTxPayloadSize == txFrame.GetHeaderDataLen()))
566     {
567         if (txFrame.GetHeaderDataLen() <= slaveAcceptLen)
568         {
569             // Our outbound packet has been successfully transmitted. Clear mSpiTxPayloadSize and mSpiTxIsReady so
570             // that uplayer can pull another packet for us to send.
571             successfulExchanges++;
572 
573             mInterfaceMetrics.mTxFrameCount++;
574             mInterfaceMetrics.mTxFrameByteCount += mSpiTxPayloadSize;
575 
576             // Clear tx buffer after usage
577             memset(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], 0, mSpiTxPayloadSize);
578             mSpiTxIsReady      = false;
579             mSpiTxPayloadSize  = 0;
580             mSpiTxRefusedCount = 0;
581         }
582         else
583         {
584             // The slave wasn't ready for what we had to send them. Incrementing this counter will turn on rate
585             // limiting so that we don't waste a ton of CPU bombarding them with useless SPI transfers.
586             mSpiTxRefusedCount++;
587         }
588     }
589 
590     if (!mSpiTxIsReady)
591     {
592         mSpiTxRefusedCount = 0;
593     }
594 
595     if (successfulExchanges == 2)
596     {
597         mSpiDuplexFrameCount++;
598     }
599 
600 exit:
601     if (discardRxFrame)
602     {
603         mRxFrameBuffer->DiscardFrame();
604     }
605 
606     return error;
607 }
608 
CheckInterrupt(void)609 bool SpiInterface::CheckInterrupt(void)
610 {
611     return (mIntGpioValueFd >= 0) ? (GetGpioValue(mIntGpioValueFd) == kGpioIntAssertState) : true;
612 }
613 
UpdateFdSet(void * aMainloopContext)614 void SpiInterface::UpdateFdSet(void *aMainloopContext)
615 {
616     struct timeval        timeout        = {kSecPerDay, 0};
617     struct timeval        pollingTimeout = {0, kSpiPollPeriodUs};
618     otSysMainloopContext *context        = reinterpret_cast<otSysMainloopContext *>(aMainloopContext);
619 
620     assert(context != nullptr);
621 
622     if (mSpiTxIsReady)
623     {
624         // We have data to send to the slave.
625         timeout.tv_sec  = 0;
626         timeout.tv_usec = 0;
627     }
628 
629     if (mIntGpioValueFd >= 0)
630     {
631         if (context->mMaxFd < mIntGpioValueFd)
632         {
633             context->mMaxFd = mIntGpioValueFd;
634         }
635 
636         if (CheckInterrupt())
637         {
638             // Interrupt pin is asserted, set the timeout to be 0.
639             timeout.tv_sec  = 0;
640             timeout.tv_usec = 0;
641             LogDebg("UpdateFdSet(): Interrupt.");
642         }
643         else
644         {
645             // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the
646             // read set.
647             FD_SET(mIntGpioValueFd, &context->mReadFdSet);
648         }
649     }
650     else if (timercmp(&pollingTimeout, &timeout, <))
651     {
652         // In this case we don't have an interrupt, so we revert to SPI polling.
653         timeout = pollingTimeout;
654     }
655 
656     if (mSpiTxRefusedCount)
657     {
658         struct timeval minTimeout = {0, 0};
659 
660         // We are being rate-limited by the slave. This is fairly normal behavior. Based on number of times slave has
661         // refused a transmission, we apply a minimum timeout.
662         if (mSpiTxRefusedCount < kImmediateRetryCount)
663         {
664             minTimeout.tv_usec = kImmediateRetryTimeoutUs;
665         }
666         else if (mSpiTxRefusedCount < kFastRetryCount)
667         {
668             minTimeout.tv_usec = kFastRetryTimeoutUs;
669         }
670         else
671         {
672             minTimeout.tv_usec = kSlowRetryTimeoutUs;
673         }
674 
675         if (timercmp(&timeout, &minTimeout, <))
676         {
677             timeout = minTimeout;
678         }
679 
680         if (mSpiTxIsReady && !mDidPrintRateLimitLog && (mSpiTxRefusedCount > 1))
681         {
682             // To avoid printing out this message over and over, we only print it out once the refused count is at two
683             // or higher when we actually have something to send the slave. And then, we only print it once.
684             LogInfo("Slave is rate limiting transactions");
685 
686             mDidPrintRateLimitLog = true;
687         }
688 
689         if (mSpiTxRefusedCount == kSpiTxRefuseWarnCount)
690         {
691             // Ua-oh. The slave hasn't given us a chance to send it anything for over thirty frames. If this ever
692             // happens, print out a warning to the logs.
693             LogWarn("Slave seems stuck.");
694         }
695         else if (mSpiTxRefusedCount == kSpiTxRefuseExitCount)
696         {
697             // Double ua-oh. The slave hasn't given us a chance to send it anything for over a hundred frames.
698             // This almost certainly means that the slave has locked up or gotten into an unrecoverable state.
699             DieNowWithMessage("Slave seems REALLY stuck.", OT_EXIT_FAILURE);
700         }
701     }
702     else
703     {
704         mDidPrintRateLimitLog = false;
705     }
706 
707     if (timercmp(&timeout, &context->mTimeout, <))
708     {
709         context->mTimeout = timeout;
710     }
711 }
712 
Process(const void * aMainloopContext)713 void SpiInterface::Process(const void *aMainloopContext)
714 {
715     const otSysMainloopContext *context = reinterpret_cast<const otSysMainloopContext *>(aMainloopContext);
716 
717     assert(context != nullptr);
718 
719     if (FD_ISSET(mIntGpioValueFd, &context->mReadFdSet))
720     {
721         struct gpioevent_data event;
722 
723         LogDebg("Process(): Interrupt.");
724 
725         // Read event data to clear interrupt.
726         VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_ERROR_ERRNO);
727     }
728 
729     // Service the SPI port if we can receive a packet or we have a packet to be sent.
730     if (mSpiTxIsReady || CheckInterrupt())
731     {
732         // We guard this with the above check because we don't want to overwrite any previously received frames.
733         IgnoreError(PushPullSpi());
734     }
735 }
736 
WaitForFrame(uint64_t aTimeoutUs)737 otError SpiInterface::WaitForFrame(uint64_t aTimeoutUs)
738 {
739     otError  error = OT_ERROR_NONE;
740     uint64_t now   = otPlatTimeGet();
741     uint64_t end   = now + aTimeoutUs;
742 
743     mDidRxFrame = false;
744 
745     while (now < end)
746     {
747         otSysMainloopContext context;
748         int                  ret;
749 
750         context.mMaxFd           = -1;
751         context.mTimeout.tv_sec  = static_cast<time_t>((end - now) / US_PER_S);
752         context.mTimeout.tv_usec = static_cast<suseconds_t>((end - now) % US_PER_S);
753 
754         FD_ZERO(&context.mReadFdSet);
755         FD_ZERO(&context.mWriteFdSet);
756 
757         UpdateFdSet(&context);
758 
759         ret = select(context.mMaxFd + 1, &context.mReadFdSet, &context.mWriteFdSet, nullptr, &context.mTimeout);
760 
761         if (ret >= 0)
762         {
763             Process(&context);
764 
765             if (mDidRxFrame)
766             {
767                 ExitNow();
768             }
769         }
770         else if (errno != EINTR)
771         {
772             DieNow(OT_EXIT_ERROR_ERRNO);
773         }
774 
775         now = otPlatTimeGet();
776     }
777 
778     error = OT_ERROR_RESPONSE_TIMEOUT;
779 
780 exit:
781     return error;
782 }
783 
SendFrame(const uint8_t * aFrame,uint16_t aLength)784 otError SpiInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength)
785 {
786     otError error = OT_ERROR_NONE;
787 
788     VerifyOrExit(aLength < (kMaxFrameSize - kSpiFrameHeaderSize), error = OT_ERROR_NO_BUFS);
789 
790     if (IsSpinelResetCommand(aFrame, aLength))
791     {
792         ResetStates();
793     }
794 
795     VerifyOrExit(!mSpiTxIsReady, error = OT_ERROR_BUSY);
796 
797     memcpy(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], aFrame, aLength);
798 
799     mSpiTxIsReady     = true;
800     mSpiTxPayloadSize = aLength;
801 
802     IgnoreError(PushPullSpi());
803 
804 exit:
805     return error;
806 }
807 
LogError(const char * aString)808 void SpiInterface::LogError(const char *aString)
809 {
810     OT_UNUSED_VARIABLE(aString);
811     LogWarn("%s: %s", aString, strerror(errno));
812 }
813 
LogStats(void)814 void SpiInterface::LogStats(void)
815 {
816     LogInfo("INFO: SlaveResetCount=%" PRIu64, mSlaveResetCount);
817     LogInfo("INFO: SpiDuplexFrameCount=%" PRIu64, mSpiDuplexFrameCount);
818     LogInfo("INFO: SpiUnresponsiveFrameCount=%" PRIu64, mSpiUnresponsiveFrameCount);
819     LogInfo("INFO: TransferredFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredFrameCount);
820     LogInfo("INFO: TransferredValidFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredValidFrameCount);
821     LogInfo("INFO: TransferredGarbageFrameCount=%" PRIu64, mInterfaceMetrics.mTransferredGarbageFrameCount);
822     LogInfo("INFO: RxFrameCount=%" PRIu64, mInterfaceMetrics.mRxFrameCount);
823     LogInfo("INFO: RxFrameByteCount=%" PRIu64, mInterfaceMetrics.mRxFrameByteCount);
824     LogInfo("INFO: TxFrameCount=%" PRIu64, mInterfaceMetrics.mTxFrameCount);
825     LogInfo("INFO: TxFrameByteCount=%" PRIu64, mInterfaceMetrics.mTxFrameByteCount);
826 }
827 } // namespace Posix
828 } // namespace ot
829 #endif // OPENTHREAD_POSIX_CONFIG_SPINEL_SPI_INTERFACE_ENABLE
830