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