[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.
This commit is contained in:
Łukasz Maciejończyk
2021-05-14 11:20:32 -07:00
committed by GitHub
parent 80487aac36
commit fa997aead6
9 changed files with 202 additions and 14 deletions
+15
View File
@@ -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
#
+1
View File
@@ -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. |
+10
View File
@@ -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_
+6
View File
@@ -234,6 +234,9 @@ void RadioSpinel<InterfaceType, ProcessContextType>::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<InterfaceType, ProcessContextType>::RecoverFromRcpFailure(void)
if (mResetRadioOnStartup)
{
SuccessOrDie(SendReset());
#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION
SuccessOrDie(mSpinelInterface.ResetConnection());
#endif
}
SuccessOrDie(WaitResponse());
+6
View File
@@ -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 += \
+12
View File
@@ -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}
$<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:rt>
)
+105 -9
View File
@@ -47,6 +47,9 @@
#include <pty.h>
#endif
#endif
#if OPENTHREAD_SPINEL_CONFIG_RESET_CONNECTION
#include <libudev.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <sys/resource.h>
@@ -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
+43 -3
View File
@@ -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 &);
+4 -2
View File
@@ -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