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