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