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