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