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