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