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