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