From fa997aead671be737e3d36df1fecef6117b54949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Maciejo=C5=84czyk?= <32327281+lmaciejonczyk@users.noreply.github.com> Date: Fri, 14 May 2021 20:20:32 +0200 Subject: [PATCH] [posix] RCP(USB CDC ACM) hard reset support (#6454) This commit handles the situation when RCP(USB CDC) performs hard reset. This commit brings an alternative to pseudo reset by handling the connection with RCP(USB CDC ACM) by reset the socket connection after the device is reset. When RCP is connected by USB CDC and we reset it, the RCP device is numerated in host operating system. So far for RCP(USB CDC) pseudo reset has been performing meaning only specific parts of firmware were reset thanks to results the connection hasn't been broken. For hard resetting RCP(USB CDC) device the host needs to handle the connection properly. It uses udev library to check if device enumeration has ended if libudev-dev package is present in the host system otherwise it waits few seconds. It works based on symlink to the RCP device created by udev in /dev/serial/by-id/ directory. --- configure.ac | 15 +++ examples/README.md | 1 + src/lib/spinel/openthread-spinel-config.h | 10 ++ src/lib/spinel/radio_spinel_impl.hpp | 6 ++ src/posix/Makefile-posix | 6 ++ src/posix/platform/CMakeLists.txt | 12 +++ src/posix/platform/hdlc_interface.cpp | 114 ++++++++++++++++++++-- src/posix/platform/hdlc_interface.hpp | 46 ++++++++- src/posix/platform/system.cpp | 6 +- 9 files changed, 202 insertions(+), 14 deletions(-) diff --git a/configure.ac b/configure.ac index be48617e7..41983a512 100644 --- a/configure.ac +++ b/configure.ac @@ -614,6 +614,21 @@ AC_ARG_ENABLE(ncp, AC_MSG_RESULT(${enable_ncp}) AM_CONDITIONAL([OPENTHREAD_ENABLE_NCP], [test "${enable_ncp}" = "yes"]) +# +# Udev - udev library to use for POSIX NCP +# + +AC_ARG_WITH([udev], + AS_HELP_STRING([--with-udev], [Use udev library for monitoring tty events])) + +AS_IF([test "x$with_udev" = "xyes"], [ + AC_CHECK_LIB([udev], [udev_new]) + AC_CHECK_HEADER([libudev.h]) +]) + +AS_IF([test "x$ac_cv_lib_udev_udev_new" == "xno" || test "x$ac_cv_header_libudev_h" == "xno"], + [AC_MSG_ERROR([--with-udev was given, but test for udev failed])]) + # # Readline - readline library to use for POSIX CLI # diff --git a/examples/README.md b/examples/README.md index f88c4b6c2..1bdd98097 100644 --- a/examples/README.md +++ b/examples/README.md @@ -52,6 +52,7 @@ This page lists the available common switches with description. Unless stated ot | OTNS | OT_OTNS | Enables support for [OpenThread Network Simulator](https://github.com/openthread/ot-ns). Enable this switch if you are building OpenThread for OpenThread Network Simulator. | | PLATFORM_UDP | OT_PLATFORM_UDP | Enables platform UDP support. | | REFERENCE_DEVICE | OT_REFERENCE_DEVICE | Enables support for Thread Test Harness reference device. Enable this switch on the reference device during certification. | +| RESET_CONNECTION | OT_SPINEL_RESET_CONNECTION | Enables resetting connection with RCP device. Enable this switch if RCP uses USB transport and performs hard reset. Use symlink instead of file name pointing the RCP device i.e. /dev/serial/by-id/usb-device-name instead of /dev/ttyUSB0 in a connection configuration. | | SERVICE | OT_SERVICE | Enables support for injecting Service entries into the Thread Network Data. | | SETTINGS_RAM | OT_SETTINGS_RAM | Enables volatile-only storage of settings. | | SLAAC | OT_SLAAC | Enables support for adding auto-configured SLAAC addresses by OpenThread. This feature is enabled by default. | diff --git a/src/lib/spinel/openthread-spinel-config.h b/src/lib/spinel/openthread-spinel-config.h index 3cd91f243..dd6c2253a 100644 --- a/src/lib/spinel/openthread-spinel-config.h +++ b/src/lib/spinel/openthread-spinel-config.h @@ -55,4 +55,14 @@ #define OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT 0 #endif +/** + * @def OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + * + * Define 1 to reset connection after hard resetting RCP(USB CDC ACM) device. + * + */ +#ifndef OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION +#define OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION 0 +#endif + #endif // OPENTHREAD_SPINEL_CONFIG_H_ diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index 246b7b92a..b497b7d92 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -234,6 +234,9 @@ void RadioSpinel::Init(bool aResetRadio, if (aResetRadio) { SuccessOrExit(error = SendReset()); +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + SuccessOrDie(mSpinelInterface.ResetConnection()); +#endif } SuccessOrExit(error = WaitResponse()); @@ -2227,6 +2230,9 @@ void RadioSpinel::RecoverFromRcpFailure(void) if (mResetRadioOnStartup) { SuccessOrDie(SendReset()); +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + SuccessOrDie(mSpinelInterface.ResetConnection()); +#endif } SuccessOrDie(WaitResponse()); diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index 2b8430be6..57d7c5cff 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -66,6 +66,7 @@ NEIGHBOR_DISCOVERY_AGENT ?= 1 PING_SENDER ?= 1 READLINE ?= readline REFERENCE_DEVICE ?= 1 +RESET_CONNECTION ?= 0 SERVICE ?= 1 SNTP_CLIENT ?= 1 SRP_CLIENT ?= 1 @@ -125,6 +126,11 @@ ifeq ($(VIRTUAL_TIME),1) COMMONCFLAGS += -DOPENTHREAD_POSIX_VIRTUAL_TIME=1 endif +ifeq ($(RESET_CONNECTION),1) +COMMONCFLAGS += -DOPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION=1 +configure_OPTIONS += --with-udev +endif + include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../examples/common-switches.mk CPPFLAGS += \ diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index d379a318c..0c9deb401 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -53,6 +53,17 @@ if(NOT OT_CONFIG) set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE) endif() +option(OT_SPINEL_RESET_CONNECTION "reset connection after hard resetting RCP(USB CDC ACM) device" OFF) +if (OT_SPINEL_RESET_CONNECTION) + list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION=1") + find_library(LIBUDEV "udev") + if (LIBUDEV) + set(UDEV_LINK_LIBRARIES "${LIBUDEV}") + else() + message(FATAL_ERROR "test for udev failed") + endif() +endif() + set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE) list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"") @@ -86,6 +97,7 @@ target_link_libraries(openthread-posix openthread-url ot-config util + ${UDEV_LINK_LIBRARIES} $<$:rt> ) diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp index 888b05846..484e29fd6 100644 --- a/src/posix/platform/hdlc_interface.cpp +++ b/src/posix/platform/hdlc_interface.cpp @@ -47,6 +47,9 @@ #include #endif #endif +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION +#include +#endif #include #include #include @@ -132,6 +135,7 @@ HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback, , mSockFd(-1) , mBaudRate(0) , mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this) + , mRadioUrl(nullptr) { } @@ -167,6 +171,8 @@ otError HdlcInterface::Init(const Url::Url &aRadioUrl) ExitNow(error = OT_ERROR_INVALID_ARGS); } + mRadioUrl = &aRadioUrl; + exit: return error; } @@ -178,15 +184,7 @@ HdlcInterface::~HdlcInterface(void) void HdlcInterface::Deinit(void) { - VerifyOrExit(mSockFd != -1); - - VerifyOrExit(0 == close(mSockFd), perror("close RCP")); - VerifyOrExit(-1 != wait(nullptr) || errno == ECHILD, perror("wait RCP")); - - mSockFd = -1; - -exit: - return; + CloseFile(); } void HdlcInterface::Read(void) @@ -586,6 +584,19 @@ exit: return fd; } +void HdlcInterface::CloseFile(void) +{ + VerifyOrExit(mSockFd != -1); + + VerifyOrExit(0 == close(mSockFd), perror("close RCP")); + VerifyOrExit(-1 != wait(nullptr) || errno == ECHILD, perror("wait RCP")); + + mSockFd = -1; + +exit: + return; +} + #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE int HdlcInterface::ForkPty(const Url::Url &aRadioUrl) { @@ -656,6 +667,91 @@ void HdlcInterface::HandleHdlcFrame(otError aError) } } +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION +otError HdlcInterface::ResetConnection(void) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(mRadioUrl != nullptr, error = OT_ERROR_FAILED); + SuccessOrExit(error = WaitForUsbDevice(mRadioUrl->GetPath())); + + CloseFile(); + + mSockFd = OpenFile(*mRadioUrl); + VerifyOrExit(mSockFd != -1, error = OT_ERROR_FAILED); + +exit: + return error; +} + +otError HdlcInterface::WaitForUsbDevice(const char *aRadioUrlPath) +{ + int fd; + uint64_t end; + struct udev_monitor *mon; + otError error = OT_ERROR_NONE; + + struct udev *udev = udev_new(); + VerifyOrExit(udev != nullptr, error = OT_ERROR_FAILED); + + mon = udev_monitor_new_from_netlink(udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(mon, "tty", NULL); + udev_monitor_enable_receiving(mon); + fd = udev_monitor_get_fd(mon); + + // wait maximally 10 seconds + end = otPlatTimeGet() + 10 * US_PER_S; + do + { + int ret; + fd_set fds; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 100 * US_PER_MS; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + ret = select(fd + 1, &fds, NULL, NULL, &tv); + if (ret > 0 && FD_ISSET(fd, &fds)) + { + struct udev_device *dev = udev_monitor_receive_device(mon); + if (dev) + { + const char *action = udev_device_get_action(dev); + VerifyOrExit(action != nullptr, error = OT_ERROR_FAILED); + if (strcmp(action, "add") == 0) + { + struct udev_list_entry *entry; + udev_list_entry_foreach(entry, udev_device_get_devlinks_list_entry(dev)) + { + const char *name = udev_list_entry_get_name(entry); + VerifyOrExit(name != nullptr, error = OT_ERROR_FAILED); + if (strcmp(name, aRadioUrlPath) == 0) + { + udev_device_unref(dev); + ExitNow(); + } + } + } + udev_device_unref(dev); + } + } + } while (end > otPlatTimeGet()); + + error = OT_ERROR_FAILED; + +exit: + if (udev) + { + udev_unref(udev); + } + + return error; +} +#endif // OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + } // namespace Posix } // namespace ot #endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp index a9d5ece98..92a7d07c7 100644 --- a/src/posix/platform/hdlc_interface.hpp +++ b/src/posix/platform/hdlc_interface.hpp @@ -37,6 +37,7 @@ #include "openthread-posix-config.h" #include "platform-posix.h" #include "lib/hdlc/hdlc.hpp" +#include "lib/spinel/openthread-spinel-config.h" #include "lib/spinel/spinel_interface.hpp" #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART @@ -162,6 +163,14 @@ public: */ void OnRcpReset(void); +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + /** + * This method is called when RCP is reset to recreate the connection with it. + * + */ + otError ResetConnection(void); +#endif + private: /** * This method instructs `HdlcInterface` to read and decode data from radio over the socket. @@ -212,7 +221,37 @@ private: static void HandleHdlcFrame(void *aContext, otError aError); void HandleHdlcFrame(otError aError); + /** + * This method opens file specified by aRadioUrl. + * + * @param[in] aRadioUrl A reference to object containing path to file and data for configuring + * the connection with tty type file. + * + * @retval The file descriptor of newly opened file. + */ int OpenFile(const Url::Url &aRadioUrl); + + /** + * This method closes file associated with the file descriptor. + * + */ + void CloseFile(void); + +#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION + /** + * This method waits until enumeration of RCP(USB CDC ACM) device ends. + * + * This is blocking call, this method waits for up to 10 seconds. + * + * @param[in] aRadioUrlPath A path to RCP device. + * + * @retval OT_ERROR_NONE The RCP device has been added to the host OS before timeout ends. + * @retval OT_ERROR_FAILED The RCP device has not been added to the host OS before timeout ends. + * + */ + otError WaitForUsbDevice(const char *aRadioUrlPath); +#endif + #if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE static int ForkPty(const Url::Url &aRadioUrl); #endif @@ -227,9 +266,10 @@ private: void * mReceiveFrameContext; Spinel::SpinelInterface::RxFrameBuffer & mReceiveFrameBuffer; - int mSockFd; - uint32_t mBaudRate; - Hdlc::Decoder mHdlcDecoder; + int mSockFd; + uint32_t mBaudRate; + Hdlc::Decoder mHdlcDecoder; + const Url::Url *mRadioUrl; // Non-copyable, intentionally not implemented. HdlcInterface(const HdlcInterface &); diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index 416156358..58a89a83c 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -110,8 +110,10 @@ static const char *getTrelRadioUrl(otPlatformConfig *aPlatformConfig) otInstance *otSysInit(otPlatformConfig *aPlatformConfig) { - otInstance * instance = nullptr; - ot::Posix::RadioUrl radioUrl(get802154RadioUrl(aPlatformConfig)); + otInstance *instance = nullptr; + // radioUrl must be static to prevent from having dangling pointers after this function call ends + // and we still need to refer to the radioUrl from HdlcInterface object + static ot::Posix::RadioUrl radioUrl(get802154RadioUrl(aPlatformConfig)); #if OPENTHREAD_POSIX_VIRTUAL_TIME // The last argument must be the node id