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