• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Copyright (c) 2018, 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 HDLC interface to radio (RCP).
32  */
33 
34 #include "hdlc_interface.hpp"
35 
36 #include "platform-posix.h"
37 
38 #include <assert.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
42 #if defined(__APPLE__) || defined(__NetBSD__)
43 #include <util.h>
44 #elif defined(__FreeBSD__)
45 #include <libutil.h>
46 #else
47 #include <pty.h>
48 #endif
49 #endif
50 #include <stdarg.h>
51 #include <stdlib.h>
52 #include <sys/resource.h>
53 #include <sys/stat.h>
54 #include <sys/time.h>
55 #include <sys/wait.h>
56 #include <syslog.h>
57 #include <termios.h>
58 #include <unistd.h>
59 
60 #include <openthread/logging.h>
61 
62 #include "common/code_utils.hpp"
63 
64 #ifdef __APPLE__
65 
66 #ifndef B230400
67 #define B230400 230400
68 #endif
69 
70 #ifndef B460800
71 #define B460800 460800
72 #endif
73 
74 #ifndef B500000
75 #define B500000 500000
76 #endif
77 
78 #ifndef B576000
79 #define B576000 576000
80 #endif
81 
82 #ifndef B921600
83 #define B921600 921600
84 #endif
85 
86 #ifndef B1000000
87 #define B1000000 1000000
88 #endif
89 
90 #ifndef B1152000
91 #define B1152000 1152000
92 #endif
93 
94 #ifndef B1500000
95 #define B1500000 1500000
96 #endif
97 
98 #ifndef B2000000
99 #define B2000000 2000000
100 #endif
101 
102 #ifndef B2500000
103 #define B2500000 2500000
104 #endif
105 
106 #ifndef B3000000
107 #define B3000000 3000000
108 #endif
109 
110 #ifndef B3500000
111 #define B3500000 3500000
112 #endif
113 
114 #ifndef B4000000
115 #define B4000000 4000000
116 #endif
117 
118 #endif // __APPLE__
119 
120 #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
121 
122 using ot::Spinel::SpinelInterface;
123 
124 namespace ot {
125 namespace Posix {
126 
HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,void * aCallbackContext,SpinelInterface::RxFrameBuffer & aFrameBuffer)127 HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,
128                              void *                                aCallbackContext,
129                              SpinelInterface::RxFrameBuffer &      aFrameBuffer)
130     : mReceiveFrameCallback(aCallback)
131     , mReceiveFrameContext(aCallbackContext)
132     , mReceiveFrameBuffer(aFrameBuffer)
133     , mSockFd(-1)
134     , mBaudRate(0)
135     , mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this)
136     , mRadioUrl(nullptr)
137 {
138     memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics));
139     mInterfaceMetrics.mRcpInterfaceType = OT_POSIX_RCP_BUS_UART;
140 }
141 
OnRcpReset(void)142 void HdlcInterface::OnRcpReset(void)
143 {
144     mHdlcDecoder.Reset();
145 }
146 
Init(const Url::Url & aRadioUrl)147 otError HdlcInterface::Init(const Url::Url &aRadioUrl)
148 {
149     otError     error = OT_ERROR_NONE;
150     struct stat st;
151 
152     VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY);
153 
154     VerifyOrDie(stat(aRadioUrl.GetPath(), &st) == 0, OT_EXIT_INVALID_ARGUMENTS);
155 
156     if (S_ISCHR(st.st_mode))
157     {
158         mSockFd = OpenFile(aRadioUrl);
159         VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
160     }
161 #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
162     else if (S_ISREG(st.st_mode))
163     {
164         mSockFd = ForkPty(aRadioUrl);
165         VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
166     }
167 #endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
168     else
169     {
170         otLogCritPlat("Radio file '%s' not supported", aRadioUrl.GetPath());
171         ExitNow(error = OT_ERROR_INVALID_ARGS);
172     }
173 
174     mRadioUrl = &aRadioUrl;
175 
176 exit:
177     return error;
178 }
179 
~HdlcInterface(void)180 HdlcInterface::~HdlcInterface(void)
181 {
182     Deinit();
183 }
184 
Deinit(void)185 void HdlcInterface::Deinit(void)
186 {
187     CloseFile();
188 }
189 
Read(void)190 void HdlcInterface::Read(void)
191 {
192     uint8_t buffer[kMaxFrameSize];
193     ssize_t rval;
194 
195     rval = read(mSockFd, buffer, sizeof(buffer));
196 
197     if (rval > 0)
198     {
199         Decode(buffer, static_cast<uint16_t>(rval));
200     }
201     else if ((rval < 0) && (errno != EAGAIN) && (errno != EINTR))
202     {
203         DieNow(OT_EXIT_ERROR_ERRNO);
204     }
205 }
206 
Decode(const uint8_t * aBuffer,uint16_t aLength)207 void HdlcInterface::Decode(const uint8_t *aBuffer, uint16_t aLength)
208 {
209     mHdlcDecoder.Decode(aBuffer, aLength);
210 }
211 
SendFrame(const uint8_t * aFrame,uint16_t aLength)212 otError HdlcInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength)
213 {
214     otError                          error = OT_ERROR_NONE;
215     Hdlc::FrameBuffer<kMaxFrameSize> encoderBuffer;
216     Hdlc::Encoder                    hdlcEncoder(encoderBuffer);
217 
218     SuccessOrExit(error = hdlcEncoder.BeginFrame());
219     SuccessOrExit(error = hdlcEncoder.Encode(aFrame, aLength));
220     SuccessOrExit(error = hdlcEncoder.EndFrame());
221 
222     error = Write(encoderBuffer.GetFrame(), encoderBuffer.GetLength());
223 
224 exit:
225     return error;
226 }
227 
Write(const uint8_t * aFrame,uint16_t aLength)228 otError HdlcInterface::Write(const uint8_t *aFrame, uint16_t aLength)
229 {
230     otError error = OT_ERROR_NONE;
231 #if OPENTHREAD_POSIX_VIRTUAL_TIME
232     virtualTimeSendRadioSpinelWriteEvent(aFrame, aLength);
233 #else
234     while (aLength)
235     {
236         ssize_t rval = write(mSockFd, aFrame, aLength);
237 
238         if (rval == aLength)
239         {
240             break;
241         }
242         else if (rval > 0)
243         {
244             aLength -= static_cast<uint16_t>(rval);
245             aFrame += static_cast<uint16_t>(rval);
246         }
247         else if (rval < 0)
248         {
249             VerifyOrDie((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR), OT_EXIT_ERROR_ERRNO);
250         }
251 
252         SuccessOrExit(error = WaitForWritable());
253     }
254 
255 exit:
256 #endif // OPENTHREAD_POSIX_VIRTUAL_TIME
257 
258     mInterfaceMetrics.mTransferredFrameCount++;
259     if (error == OT_ERROR_NONE)
260     {
261         mInterfaceMetrics.mTxFrameCount++;
262         mInterfaceMetrics.mTxFrameByteCount += aLength;
263         mInterfaceMetrics.mTransferredValidFrameCount++;
264     }
265     else
266     {
267         mInterfaceMetrics.mTransferredGarbageFrameCount++;
268     }
269 
270     return error;
271 }
272 
WaitForFrame(uint64_t aTimeoutUs)273 otError HdlcInterface::WaitForFrame(uint64_t aTimeoutUs)
274 {
275     otError        error = OT_ERROR_NONE;
276     struct timeval timeout;
277 #if OPENTHREAD_POSIX_VIRTUAL_TIME
278     struct VirtualTimeEvent event;
279 
280     timeout.tv_sec  = static_cast<time_t>(aTimeoutUs / US_PER_S);
281     timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
282 
283     virtualTimeSendSleepEvent(&timeout);
284     virtualTimeReceiveEvent(&event);
285 
286     switch (event.mEvent)
287     {
288     case OT_SIM_EVENT_RADIO_SPINEL_WRITE:
289         Decode(event.mData, event.mDataLength);
290         break;
291 
292     case OT_SIM_EVENT_ALARM_FIRED:
293         VerifyOrExit(event.mDelay <= aTimeoutUs, error = OT_ERROR_RESPONSE_TIMEOUT);
294         break;
295 
296     default:
297         assert(false);
298         break;
299     }
300 #else  // OPENTHREAD_POSIX_VIRTUAL_TIME
301     timeout.tv_sec = static_cast<time_t>(aTimeoutUs / US_PER_S);
302     timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
303 
304     fd_set read_fds;
305     fd_set error_fds;
306     int rval;
307 
308     FD_ZERO(&read_fds);
309     FD_ZERO(&error_fds);
310     FD_SET(mSockFd, &read_fds);
311     FD_SET(mSockFd, &error_fds);
312 
313     rval = select(mSockFd + 1, &read_fds, nullptr, &error_fds, &timeout);
314 
315     if (rval > 0)
316     {
317         if (FD_ISSET(mSockFd, &read_fds))
318         {
319             Read();
320         }
321         else if (FD_ISSET(mSockFd, &error_fds))
322         {
323             DieNowWithMessage("NCP error", OT_EXIT_FAILURE);
324         }
325         else
326         {
327             DieNow(OT_EXIT_FAILURE);
328         }
329     }
330     else if (rval == 0)
331     {
332         ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
333     }
334     else if (errno != EINTR)
335     {
336         DieNowWithMessage("wait response", OT_EXIT_FAILURE);
337     }
338 #endif // OPENTHREAD_POSIX_VIRTUAL_TIME
339 
340 exit:
341     return error;
342 }
343 
UpdateFdSet(fd_set & aReadFdSet,fd_set & aWriteFdSet,int & aMaxFd,struct timeval & aTimeout)344 void HdlcInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
345 {
346     OT_UNUSED_VARIABLE(aWriteFdSet);
347     OT_UNUSED_VARIABLE(aTimeout);
348 
349     FD_SET(mSockFd, &aReadFdSet);
350 
351     if (aMaxFd < mSockFd)
352     {
353         aMaxFd = mSockFd;
354     }
355 }
356 
Process(const RadioProcessContext & aContext)357 void HdlcInterface::Process(const RadioProcessContext &aContext)
358 {
359     if (FD_ISSET(mSockFd, aContext.mReadFdSet))
360     {
361         Read();
362     }
363 }
364 
WaitForWritable(void)365 otError HdlcInterface::WaitForWritable(void)
366 {
367     otError        error   = OT_ERROR_NONE;
368     struct timeval timeout = {kMaxWaitTime / 1000, (kMaxWaitTime % 1000) * 1000};
369     uint64_t       now     = otPlatTimeGet();
370     uint64_t       end     = now + kMaxWaitTime * US_PER_MS;
371     fd_set         writeFds;
372     fd_set         errorFds;
373     int            rval;
374 
375     while (true)
376     {
377         FD_ZERO(&writeFds);
378         FD_ZERO(&errorFds);
379         FD_SET(mSockFd, &writeFds);
380         FD_SET(mSockFd, &errorFds);
381 
382         rval = select(mSockFd + 1, nullptr, &writeFds, &errorFds, &timeout);
383 
384         if (rval > 0)
385         {
386             if (FD_ISSET(mSockFd, &writeFds))
387             {
388                 ExitNow();
389             }
390             else if (FD_ISSET(mSockFd, &errorFds))
391             {
392                 DieNow(OT_EXIT_FAILURE);
393             }
394             else
395             {
396                 assert(false);
397             }
398         }
399         else if ((rval < 0) && (errno != EINTR))
400         {
401             DieNow(OT_EXIT_ERROR_ERRNO);
402         }
403 
404         now = otPlatTimeGet();
405 
406         if (end > now)
407         {
408             uint64_t remain = end - now;
409 
410             timeout.tv_sec  = static_cast<time_t>(remain / US_PER_S);
411             timeout.tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
412         }
413         else
414         {
415             break;
416         }
417     }
418 
419     error = OT_ERROR_FAILED;
420 
421 exit:
422     return error;
423 }
424 
OpenFile(const Url::Url & aRadioUrl)425 int HdlcInterface::OpenFile(const Url::Url &aRadioUrl)
426 {
427     int fd   = -1;
428     int rval = 0;
429 
430     fd = open(aRadioUrl.GetPath(), O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
431     if (fd == -1)
432     {
433         perror("open uart failed");
434         ExitNow();
435     }
436 
437     if (isatty(fd))
438     {
439         struct termios tios;
440         const char *   value;
441         speed_t        speed;
442 
443         int      stopBit  = 1;
444         uint32_t baudrate = 115200;
445 
446         VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0);
447 
448         cfmakeraw(&tios);
449 
450         tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
451 
452         if ((value = aRadioUrl.GetValue("uart-parity")) != nullptr)
453         {
454             if (strncmp(value, "odd", 3) == 0)
455             {
456                 tios.c_cflag |= PARENB;
457                 tios.c_cflag |= PARODD;
458             }
459             else if (strncmp(value, "even", 4) == 0)
460             {
461                 tios.c_cflag |= PARENB;
462             }
463             else
464             {
465                 DieNow(OT_EXIT_INVALID_ARGUMENTS);
466             }
467         }
468 
469         if ((value = aRadioUrl.GetValue("uart-stop")) != nullptr)
470         {
471             stopBit = atoi(value);
472         }
473 
474         switch (stopBit)
475         {
476         case 1:
477             tios.c_cflag &= static_cast<unsigned long>(~CSTOPB);
478             break;
479         case 2:
480             tios.c_cflag |= CSTOPB;
481             break;
482         default:
483             DieNow(OT_EXIT_INVALID_ARGUMENTS);
484             break;
485         }
486 
487         if ((value = aRadioUrl.GetValue("uart-baudrate")))
488         {
489             baudrate = static_cast<uint32_t>(atoi(value));
490         }
491 
492         switch (baudrate)
493         {
494         case 9600:
495             speed = B9600;
496             break;
497         case 19200:
498             speed = B19200;
499             break;
500         case 38400:
501             speed = B38400;
502             break;
503         case 57600:
504             speed = B57600;
505             break;
506         case 115200:
507             speed = B115200;
508             break;
509 #ifdef B230400
510         case 230400:
511             speed = B230400;
512             break;
513 #endif
514 #ifdef B460800
515         case 460800:
516             speed = B460800;
517             break;
518 #endif
519 #ifdef B500000
520         case 500000:
521             speed = B500000;
522             break;
523 #endif
524 #ifdef B576000
525         case 576000:
526             speed = B576000;
527             break;
528 #endif
529 #ifdef B921600
530         case 921600:
531             speed = B921600;
532             break;
533 #endif
534 #ifdef B1000000
535         case 1000000:
536             speed = B1000000;
537             break;
538 #endif
539 #ifdef B1152000
540         case 1152000:
541             speed = B1152000;
542             break;
543 #endif
544 #ifdef B1500000
545         case 1500000:
546             speed = B1500000;
547             break;
548 #endif
549 #ifdef B2000000
550         case 2000000:
551             speed = B2000000;
552             break;
553 #endif
554 #ifdef B2500000
555         case 2500000:
556             speed = B2500000;
557             break;
558 #endif
559 #ifdef B3000000
560         case 3000000:
561             speed = B3000000;
562             break;
563 #endif
564 #ifdef B3500000
565         case 3500000:
566             speed = B3500000;
567             break;
568 #endif
569 #ifdef B4000000
570         case 4000000:
571             speed = B4000000;
572             break;
573 #endif
574         default:
575             DieNow(OT_EXIT_INVALID_ARGUMENTS);
576             break;
577         }
578 
579         mBaudRate = baudrate;
580 
581         if (aRadioUrl.GetValue("uart-flow-control") != nullptr)
582         {
583             tios.c_cflag |= CRTSCTS;
584         }
585 
586         VerifyOrExit((rval = cfsetspeed(&tios, static_cast<speed_t>(speed))) == 0, perror("cfsetspeed"));
587         VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr"));
588         VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0);
589     }
590 
591 exit:
592     if (rval != 0)
593     {
594         DieNow(OT_EXIT_FAILURE);
595     }
596 
597     return fd;
598 }
599 
CloseFile(void)600 void HdlcInterface::CloseFile(void)
601 {
602     VerifyOrExit(mSockFd != -1);
603 
604     VerifyOrExit(0 == close(mSockFd), perror("close RCP"));
605     VerifyOrExit(-1 != wait(nullptr) || errno == ECHILD, perror("wait RCP"));
606 
607     mSockFd = -1;
608 
609 exit:
610     return;
611 }
612 
613 #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
ForkPty(const Url::Url & aRadioUrl)614 int HdlcInterface::ForkPty(const Url::Url &aRadioUrl)
615 {
616     int fd   = -1;
617     int pid  = -1;
618     int rval = -1;
619 
620     {
621         struct termios tios;
622 
623         memset(&tios, 0, sizeof(tios));
624         cfmakeraw(&tios);
625         tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
626 
627         VerifyOrDie((pid = forkpty(&fd, nullptr, &tios, nullptr)) != -1, OT_EXIT_ERROR_ERRNO);
628     }
629 
630     if (0 == pid)
631     {
632         constexpr int kMaxArguments = 32;
633         char *        argv[kMaxArguments + 1];
634         size_t        index = 0;
635 
636         argv[index++] = const_cast<char *>(aRadioUrl.GetPath());
637 
638         for (const char *arg = nullptr;
639              index < OT_ARRAY_LENGTH(argv) && (arg = aRadioUrl.GetValue("forkpty-arg", arg)) != nullptr;
640              argv[index++] = const_cast<char *>(arg))
641         {
642         }
643 
644         if (index < OT_ARRAY_LENGTH(argv))
645         {
646             argv[index] = nullptr;
647         }
648         else
649         {
650             DieNowWithMessage("Too many arguments!", OT_EXIT_INVALID_ARGUMENTS);
651         }
652 
653         VerifyOrDie((rval = execvp(argv[0], argv)) != -1, OT_EXIT_ERROR_ERRNO);
654     }
655     else
656     {
657         VerifyOrDie((rval = fcntl(fd, F_GETFL)) != -1, OT_EXIT_ERROR_ERRNO);
658         VerifyOrDie((rval = fcntl(fd, F_SETFL, rval | O_NONBLOCK | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
659     }
660 
661     return fd;
662 }
663 #endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
664 
HandleHdlcFrame(void * aContext,otError aError)665 void HdlcInterface::HandleHdlcFrame(void *aContext, otError aError)
666 {
667     static_cast<HdlcInterface *>(aContext)->HandleHdlcFrame(aError);
668 }
669 
HandleHdlcFrame(otError aError)670 void HdlcInterface::HandleHdlcFrame(otError aError)
671 {
672     mInterfaceMetrics.mTransferredFrameCount++;
673 
674     if (aError == OT_ERROR_NONE)
675     {
676         mInterfaceMetrics.mRxFrameCount++;
677         mInterfaceMetrics.mRxFrameByteCount += mReceiveFrameBuffer.GetLength();
678         mInterfaceMetrics.mTransferredValidFrameCount++;
679         mReceiveFrameCallback(mReceiveFrameContext);
680     }
681     else
682     {
683         mInterfaceMetrics.mTransferredGarbageFrameCount++;
684         mReceiveFrameBuffer.DiscardFrame();
685         otLogWarnPlat("Error decoding hdlc frame: %s", otThreadErrorToString(aError));
686     }
687 }
688 
ResetConnection(void)689 otError HdlcInterface::ResetConnection(void)
690 {
691     otError  error = OT_ERROR_NONE;
692     uint64_t end;
693 
694     if (mRadioUrl->GetValue("uart-reset") != nullptr)
695     {
696         usleep(static_cast<useconds_t>(kRemoveRcpDelay) * US_PER_MS);
697         CloseFile();
698 
699         end = otPlatTimeGet() + kResetTimeout * US_PER_MS;
700         do
701         {
702             mSockFd = OpenFile(*mRadioUrl);
703             if (mSockFd != -1)
704             {
705                 ExitNow();
706             }
707             usleep(static_cast<useconds_t>(kOpenFileDelay) * US_PER_MS);
708         } while (end > otPlatTimeGet());
709 
710         otLogCritPlat("Failed to reopen UART connection after resetting the RCP device.");
711         error = OT_ERROR_FAILED;
712     }
713 
714 exit:
715     return error;
716 }
717 
718 } // namespace Posix
719 } // namespace ot
720 #endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
721