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 #if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION
51 #include <libudev.h>
52 #endif
53 #include <stdarg.h>
54 #include <stdlib.h>
55 #include <sys/resource.h>
56 #include <sys/stat.h>
57 #include <sys/time.h>
58 #include <sys/wait.h>
59 #include <syslog.h>
60 #include <termios.h>
61 #include <unistd.h>
62 
63 #include "common/code_utils.hpp"
64 #include "common/logging.hpp"
65 
66 #ifdef __APPLE__
67 
68 #ifndef B230400
69 #define B230400 230400
70 #endif
71 
72 #ifndef B460800
73 #define B460800 460800
74 #endif
75 
76 #ifndef B500000
77 #define B500000 500000
78 #endif
79 
80 #ifndef B576000
81 #define B576000 576000
82 #endif
83 
84 #ifndef B921600
85 #define B921600 921600
86 #endif
87 
88 #ifndef B1000000
89 #define B1000000 1000000
90 #endif
91 
92 #ifndef B1152000
93 #define B1152000 1152000
94 #endif
95 
96 #ifndef B1500000
97 #define B1500000 1500000
98 #endif
99 
100 #ifndef B2000000
101 #define B2000000 2000000
102 #endif
103 
104 #ifndef B2500000
105 #define B2500000 2500000
106 #endif
107 
108 #ifndef B3000000
109 #define B3000000 3000000
110 #endif
111 
112 #ifndef B3500000
113 #define B3500000 3500000
114 #endif
115 
116 #ifndef B4000000
117 #define B4000000 4000000
118 #endif
119 
120 #endif // __APPLE__
121 
122 #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
123 
124 using ot::Spinel::SpinelInterface;
125 
126 namespace ot {
127 namespace Posix {
128 
HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,void * aCallbackContext,SpinelInterface::RxFrameBuffer & aFrameBuffer)129 HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,
130                              void *                                aCallbackContext,
131                              SpinelInterface::RxFrameBuffer &      aFrameBuffer)
132     : mReceiveFrameCallback(aCallback)
133     , mReceiveFrameContext(aCallbackContext)
134     , mReceiveFrameBuffer(aFrameBuffer)
135     , mSockFd(-1)
136     , mBaudRate(0)
137     , mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this)
138     , mRadioUrl(nullptr)
139 {
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     return error;
258 }
259 
WaitForFrame(uint64_t aTimeoutUs)260 otError HdlcInterface::WaitForFrame(uint64_t aTimeoutUs)
261 {
262     otError        error = OT_ERROR_NONE;
263     struct timeval timeout;
264 #if OPENTHREAD_POSIX_VIRTUAL_TIME
265     struct VirtualTimeEvent event;
266 
267     timeout.tv_sec  = static_cast<time_t>(aTimeoutUs / US_PER_S);
268     timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
269 
270     virtualTimeSendSleepEvent(&timeout);
271     virtualTimeReceiveEvent(&event);
272 
273     switch (event.mEvent)
274     {
275     case OT_SIM_EVENT_RADIO_SPINEL_WRITE:
276         Decode(event.mData, event.mDataLength);
277         break;
278 
279     case OT_SIM_EVENT_ALARM_FIRED:
280         VerifyOrExit(event.mDelay <= aTimeoutUs, error = OT_ERROR_RESPONSE_TIMEOUT);
281         break;
282 
283     default:
284         assert(false);
285         break;
286     }
287 #else  // OPENTHREAD_POSIX_VIRTUAL_TIME
288     timeout.tv_sec = static_cast<time_t>(aTimeoutUs / US_PER_S);
289     timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
290 
291     fd_set read_fds;
292     fd_set error_fds;
293     int rval;
294 
295     FD_ZERO(&read_fds);
296     FD_ZERO(&error_fds);
297     FD_SET(mSockFd, &read_fds);
298     FD_SET(mSockFd, &error_fds);
299 
300     rval = select(mSockFd + 1, &read_fds, nullptr, &error_fds, &timeout);
301 
302     if (rval > 0)
303     {
304         if (FD_ISSET(mSockFd, &read_fds))
305         {
306             Read();
307         }
308         else if (FD_ISSET(mSockFd, &error_fds))
309         {
310             DieNowWithMessage("NCP error", OT_EXIT_FAILURE);
311         }
312         else
313         {
314             DieNow(OT_EXIT_FAILURE);
315         }
316     }
317     else if (rval == 0)
318     {
319         ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
320     }
321     else if (errno != EINTR)
322     {
323         DieNowWithMessage("wait response", OT_EXIT_FAILURE);
324     }
325 #endif // OPENTHREAD_POSIX_VIRTUAL_TIME
326 
327 exit:
328     return error;
329 }
330 
UpdateFdSet(fd_set & aReadFdSet,fd_set & aWriteFdSet,int & aMaxFd,struct timeval & aTimeout)331 void HdlcInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
332 {
333     OT_UNUSED_VARIABLE(aWriteFdSet);
334     OT_UNUSED_VARIABLE(aTimeout);
335 
336     FD_SET(mSockFd, &aReadFdSet);
337 
338     if (aMaxFd < mSockFd)
339     {
340         aMaxFd = mSockFd;
341     }
342 }
343 
Process(const RadioProcessContext & aContext)344 void HdlcInterface::Process(const RadioProcessContext &aContext)
345 {
346     if (FD_ISSET(mSockFd, aContext.mReadFdSet))
347     {
348         Read();
349     }
350 }
351 
WaitForWritable(void)352 otError HdlcInterface::WaitForWritable(void)
353 {
354     otError        error   = OT_ERROR_NONE;
355     struct timeval timeout = {kMaxWaitTime / 1000, (kMaxWaitTime % 1000) * 1000};
356     uint64_t       now     = otPlatTimeGet();
357     uint64_t       end     = now + kMaxWaitTime * US_PER_MS;
358     fd_set         writeFds;
359     fd_set         errorFds;
360     int            rval;
361 
362     while (true)
363     {
364         FD_ZERO(&writeFds);
365         FD_ZERO(&errorFds);
366         FD_SET(mSockFd, &writeFds);
367         FD_SET(mSockFd, &errorFds);
368 
369         rval = select(mSockFd + 1, nullptr, &writeFds, &errorFds, &timeout);
370 
371         if (rval > 0)
372         {
373             if (FD_ISSET(mSockFd, &writeFds))
374             {
375                 ExitNow();
376             }
377             else if (FD_ISSET(mSockFd, &errorFds))
378             {
379                 DieNow(OT_EXIT_FAILURE);
380             }
381             else
382             {
383                 assert(false);
384             }
385         }
386         else if ((rval < 0) && (errno != EINTR))
387         {
388             DieNow(OT_EXIT_ERROR_ERRNO);
389         }
390 
391         now = otPlatTimeGet();
392 
393         if (end > now)
394         {
395             uint64_t remain = end - now;
396 
397             timeout.tv_sec  = static_cast<time_t>(remain / US_PER_S);
398             timeout.tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
399         }
400         else
401         {
402             break;
403         }
404     }
405 
406     error = OT_ERROR_FAILED;
407 
408 exit:
409     return error;
410 }
411 
OpenFile(const Url::Url & aRadioUrl)412 int HdlcInterface::OpenFile(const Url::Url &aRadioUrl)
413 {
414     int fd   = -1;
415     int rval = 0;
416 
417     fd = open(aRadioUrl.GetPath(), O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
418     if (fd == -1)
419     {
420         perror("open uart failed");
421         ExitNow();
422     }
423 
424     if (isatty(fd))
425     {
426         struct termios tios;
427         const char *   value;
428         speed_t        speed;
429 
430         int      stopBit  = 1;
431         uint32_t baudrate = 115200;
432 
433         VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0);
434 
435         cfmakeraw(&tios);
436 
437         tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
438 
439         if ((value = aRadioUrl.GetValue("uart-parity")) != nullptr)
440         {
441             if (strncmp(value, "odd", 3) == 0)
442             {
443                 tios.c_cflag |= PARENB;
444                 tios.c_cflag |= PARODD;
445             }
446             else if (strncmp(value, "even", 4) == 0)
447             {
448                 tios.c_cflag |= PARENB;
449             }
450             else
451             {
452                 DieNow(OT_EXIT_INVALID_ARGUMENTS);
453             }
454         }
455 
456         if ((value = aRadioUrl.GetValue("uart-stop")) != nullptr)
457         {
458             stopBit = atoi(value);
459         }
460 
461         switch (stopBit)
462         {
463         case 1:
464             tios.c_cflag &= static_cast<unsigned long>(~CSTOPB);
465             break;
466         case 2:
467             tios.c_cflag |= CSTOPB;
468             break;
469         default:
470             DieNow(OT_EXIT_INVALID_ARGUMENTS);
471             break;
472         }
473 
474         if ((value = aRadioUrl.GetValue("uart-baudrate")))
475         {
476             baudrate = static_cast<uint32_t>(atoi(value));
477         }
478 
479         switch (baudrate)
480         {
481         case 9600:
482             speed = B9600;
483             break;
484         case 19200:
485             speed = B19200;
486             break;
487         case 38400:
488             speed = B38400;
489             break;
490         case 57600:
491             speed = B57600;
492             break;
493         case 115200:
494             speed = B115200;
495             break;
496 #ifdef B230400
497         case 230400:
498             speed = B230400;
499             break;
500 #endif
501 #ifdef B460800
502         case 460800:
503             speed = B460800;
504             break;
505 #endif
506 #ifdef B500000
507         case 500000:
508             speed = B500000;
509             break;
510 #endif
511 #ifdef B576000
512         case 576000:
513             speed = B576000;
514             break;
515 #endif
516 #ifdef B921600
517         case 921600:
518             speed = B921600;
519             break;
520 #endif
521 #ifdef B1000000
522         case 1000000:
523             speed = B1000000;
524             break;
525 #endif
526 #ifdef B1152000
527         case 1152000:
528             speed = B1152000;
529             break;
530 #endif
531 #ifdef B1500000
532         case 1500000:
533             speed = B1500000;
534             break;
535 #endif
536 #ifdef B2000000
537         case 2000000:
538             speed = B2000000;
539             break;
540 #endif
541 #ifdef B2500000
542         case 2500000:
543             speed = B2500000;
544             break;
545 #endif
546 #ifdef B3000000
547         case 3000000:
548             speed = B3000000;
549             break;
550 #endif
551 #ifdef B3500000
552         case 3500000:
553             speed = B3500000;
554             break;
555 #endif
556 #ifdef B4000000
557         case 4000000:
558             speed = B4000000;
559             break;
560 #endif
561         default:
562             DieNow(OT_EXIT_INVALID_ARGUMENTS);
563             break;
564         }
565 
566         mBaudRate = baudrate;
567 
568         if (aRadioUrl.GetValue("uart-flow-control") != nullptr)
569         {
570             tios.c_cflag |= CRTSCTS;
571         }
572 
573         VerifyOrExit((rval = cfsetspeed(&tios, static_cast<speed_t>(speed))) == 0, perror("cfsetspeed"));
574         VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr"));
575         VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0);
576     }
577 
578 exit:
579     if (rval != 0)
580     {
581         DieNow(OT_EXIT_FAILURE);
582     }
583 
584     return fd;
585 }
586 
CloseFile(void)587 void HdlcInterface::CloseFile(void)
588 {
589     VerifyOrExit(mSockFd != -1);
590 
591     VerifyOrExit(0 == close(mSockFd), perror("close RCP"));
592     VerifyOrExit(-1 != wait(nullptr) || errno == ECHILD, perror("wait RCP"));
593 
594     mSockFd = -1;
595 
596 exit:
597     return;
598 }
599 
600 #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
ForkPty(const Url::Url & aRadioUrl)601 int HdlcInterface::ForkPty(const Url::Url &aRadioUrl)
602 {
603     int fd   = -1;
604     int pid  = -1;
605     int rval = -1;
606 
607     {
608         struct termios tios;
609 
610         memset(&tios, 0, sizeof(tios));
611         cfmakeraw(&tios);
612         tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
613 
614         VerifyOrDie((pid = forkpty(&fd, nullptr, &tios, nullptr)) != -1, OT_EXIT_ERROR_ERRNO);
615     }
616 
617     if (0 == pid)
618     {
619         constexpr int kMaxArguments = 32;
620         char *        argv[kMaxArguments + 1];
621         size_t        index = 0;
622 
623         argv[index++] = const_cast<char *>(aRadioUrl.GetPath());
624 
625         for (const char *arg = nullptr;
626              index < OT_ARRAY_LENGTH(argv) && (arg = aRadioUrl.GetValue("forkpty-arg", arg)) != nullptr;
627              argv[index++] = const_cast<char *>(arg))
628         {
629         }
630 
631         if (index < OT_ARRAY_LENGTH(argv))
632         {
633             argv[index] = nullptr;
634         }
635         else
636         {
637             DieNowWithMessage("Too many arguments!", OT_EXIT_INVALID_ARGUMENTS);
638         }
639 
640         VerifyOrDie((rval = execvp(argv[0], argv)) != -1, OT_EXIT_ERROR_ERRNO);
641     }
642     else
643     {
644         VerifyOrDie((rval = fcntl(fd, F_GETFL)) != -1, OT_EXIT_ERROR_ERRNO);
645         VerifyOrDie((rval = fcntl(fd, F_SETFL, rval | O_NONBLOCK | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
646     }
647 
648     return fd;
649 }
650 #endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
651 
HandleHdlcFrame(void * aContext,otError aError)652 void HdlcInterface::HandleHdlcFrame(void *aContext, otError aError)
653 {
654     static_cast<HdlcInterface *>(aContext)->HandleHdlcFrame(aError);
655 }
656 
HandleHdlcFrame(otError aError)657 void HdlcInterface::HandleHdlcFrame(otError aError)
658 {
659     if (aError == OT_ERROR_NONE)
660     {
661         mReceiveFrameCallback(mReceiveFrameContext);
662     }
663     else
664     {
665         mReceiveFrameBuffer.DiscardFrame();
666         otLogWarnPlat("Error decoding hdlc frame: %s", otThreadErrorToString(aError));
667     }
668 }
669 
670 #if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION
ResetConnection(void)671 otError HdlcInterface::ResetConnection(void)
672 {
673     otError error = OT_ERROR_NONE;
674 
675     VerifyOrExit(mRadioUrl != nullptr, error = OT_ERROR_FAILED);
676     SuccessOrExit(error = WaitForUsbDevice(mRadioUrl->GetPath()));
677 
678     CloseFile();
679 
680     mSockFd = OpenFile(*mRadioUrl);
681     VerifyOrExit(mSockFd != -1, error = OT_ERROR_FAILED);
682 
683 exit:
684     return error;
685 }
686 
WaitForUsbDevice(const char * aRadioUrlPath)687 otError HdlcInterface::WaitForUsbDevice(const char *aRadioUrlPath)
688 {
689     int                  fd;
690     uint64_t             end;
691     struct udev_monitor *mon;
692     otError              error = OT_ERROR_NONE;
693 
694     struct udev *udev = udev_new();
695     VerifyOrExit(udev != nullptr, error = OT_ERROR_FAILED);
696 
697     mon = udev_monitor_new_from_netlink(udev, "udev");
698     udev_monitor_filter_add_match_subsystem_devtype(mon, "tty", NULL);
699     udev_monitor_enable_receiving(mon);
700     fd = udev_monitor_get_fd(mon);
701 
702     // wait maximally 10 seconds
703     end = otPlatTimeGet() + 10 * US_PER_S;
704     do
705     {
706         int            ret;
707         fd_set         fds;
708         struct timeval tv;
709 
710         tv.tv_sec  = 0;
711         tv.tv_usec = 100 * US_PER_MS;
712 
713         FD_ZERO(&fds);
714         FD_SET(fd, &fds);
715 
716         ret = select(fd + 1, &fds, NULL, NULL, &tv);
717         if (ret > 0 && FD_ISSET(fd, &fds))
718         {
719             struct udev_device *dev = udev_monitor_receive_device(mon);
720             if (dev)
721             {
722                 const char *action = udev_device_get_action(dev);
723                 VerifyOrExit(action != nullptr, error = OT_ERROR_FAILED);
724                 if (strcmp(action, "add") == 0)
725                 {
726                     struct udev_list_entry *entry;
727                     udev_list_entry_foreach(entry, udev_device_get_devlinks_list_entry(dev))
728                     {
729                         const char *name = udev_list_entry_get_name(entry);
730                         VerifyOrExit(name != nullptr, error = OT_ERROR_FAILED);
731                         if (strcmp(name, aRadioUrlPath) == 0)
732                         {
733                             udev_device_unref(dev);
734                             ExitNow();
735                         }
736                     }
737                 }
738                 udev_device_unref(dev);
739             }
740         }
741     } while (end > otPlatTimeGet());
742 
743     error = OT_ERROR_FAILED;
744 
745 exit:
746     if (udev)
747     {
748         udev_unref(udev);
749     }
750 
751     return error;
752 }
753 #endif // OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION
754 
755 } // namespace Posix
756 } // namespace ot
757 #endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
758