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