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