From 0b0db37a13c45196e30b373ad0a9b82a5d89ccb2 Mon Sep 17 00:00:00 2001 From: Mason Tran Date: Fri, 27 Oct 2023 18:54:15 -0400 Subject: [PATCH] [api] add API to reset to bootloader mode (#9523) --- etc/cmake/options.cmake | 1 + examples/platforms/simulation/misc.c | 9 +++++++++ include/openthread/instance.h | 16 ++++++++++++++- include/openthread/platform/misc.h | 14 ++++++++++++++ script/test | 18 ++++++++++++++++- src/cli/README.md | 11 +++++++++++ src/cli/cli.cpp | 29 +++++++++++++++++++++++++--- src/core/api/instance_api.cpp | 4 ++++ src/core/instance/instance.cpp | 4 ++++ src/core/instance/instance.hpp | 12 ++++++++++++ src/lib/spinel/radio_spinel.cpp | 11 +++++++++++ src/lib/spinel/radio_spinel.hpp | 12 +++++++----- src/lib/spinel/spinel.c | 2 ++ src/lib/spinel/spinel.h | 12 ++++++++---- src/ncp/ncp_base.cpp | 12 ++++++++++++ src/posix/platform/radio.cpp | 9 +++++++++ tests/scripts/expect/cli-reset.exp | 3 +++ tests/unit/test_platform.cpp | 2 ++ 18 files changed, 167 insertions(+), 14 deletions(-) diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 0c667d0eed5..227e0faa160 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -225,6 +225,7 @@ ot_option(OT_NETDIAG_CLIENT OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE "Network ot_option(OT_OPERATIONAL_DATASET_AUTO_INIT OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT "operational dataset auto init") ot_option(OT_OTNS OPENTHREAD_CONFIG_OTNS_ENABLE "OTNS") ot_option(OT_PING_SENDER OPENTHREAD_CONFIG_PING_SENDER_ENABLE "ping sender" ${OT_APP_CLI}) +ot_option(OT_PLATFORM_BOOTLOADER_MODE OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE "platform bootloader mode") ot_option(OT_PLATFORM_NETIF OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE "platform netif") ot_option(OT_PLATFORM_UDP OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE "platform UDP") ot_option(OT_REFERENCE_DEVICE OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE "test harness reference device") diff --git a/examples/platforms/simulation/misc.c b/examples/platforms/simulation/misc.c index aefbca5def1..07739a2fef8 100644 --- a/examples/platforms/simulation/misc.c +++ b/examples/platforms/simulation/misc.c @@ -60,6 +60,15 @@ void otPlatReset(otInstance *aInstance) #endif // OPENTHREAD_PLATFORM_USE_PSEUDO_RESET } +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE +otError otPlatResetToBootloader(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return OT_ERROR_NOT_CAPABLE; +} +#endif + otPlatResetReason otPlatGetResetReason(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); diff --git a/include/openthread/instance.h b/include/openthread/instance.h index b9c0b82b1b0..03f5c623ad6 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (369) +#define OPENTHREAD_API_VERSION (370) /** * @addtogroup api-instance @@ -255,6 +255,20 @@ void otRemoveStateChangeCallback(otInstance *aInstance, otStateChangedCallback a */ void otInstanceReset(otInstance *aInstance); +/** + * Triggers a platform reset to bootloader mode, if supported. + * + * Requires `OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval OT_ERROR_NONE Reset to bootloader successfully. + * @retval OT_ERROR_BUSY Failed due to another operation is ongoing. + * @retval OT_ERROR_NOT_CAPABLE Not capable of resetting to bootloader. + * + */ +otError otInstanceResetToBootloader(otInstance *aInstance); + /** * Deletes all the settings stored on non-volatile memory, and then triggers a platform reset. * diff --git a/include/openthread/platform/misc.h b/include/openthread/platform/misc.h index 7fbee54b1fd..b1b20211112 100644 --- a/include/openthread/platform/misc.h +++ b/include/openthread/platform/misc.h @@ -61,6 +61,20 @@ extern "C" { */ void otPlatReset(otInstance *aInstance); +/** + * Performs a hardware reset on the platform to launch bootloader mode, if supported. + * + * Used when `OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE` is enabled. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Reset to bootloader successfully. + * @retval OT_ERROR_BUSY Failed due to another operation is ongoing. + * @retval OT_ERROR_NOT_CAPABLE Not capable of resetting to bootloader. + * + */ +otError otPlatResetToBootloader(otInstance *aInstance); + /** * Enumeration of possible reset reason codes. * diff --git a/script/test b/script/test index e716f5abb61..a9a71b3b55c 100755 --- a/script/test +++ b/script/test @@ -132,6 +132,17 @@ build_simulation() options+=("-DOT_LINK_METRICS_MANAGER=ON") fi + if [[ ${OT_NODE_TYPE} == cli* ]]; then + # Only enable OT_PLATFORM_BOOTLOADER_MODE when testing cli. + # This is intended to test that the "reset bootloader" CLI command returns a "NotCapable" error + + # Note: Setting this option to ON for all OT_NODE_TYPEs will cause the posix/expects CI check to fail. + # This is because the simulation RCP will have the SPINEL_CAP_RCP_RESET_TO_BOOTLOADER capability, + # causing the ot-cli POSIX app to send the reset to simulation RCP successfully instead of printing + # the expected error. + options+=("-DOT_PLATFORM_BOOTLOADER_MODE=ON") + fi + if [[ ${ot_extra_options[*]+x} ]]; then options+=("${ot_extra_options[@]}") fi @@ -158,7 +169,12 @@ build_simulation() build_posix() { local version="$1" - local options=("-DOT_MESSAGE_USE_HEAP=ON" "-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON") + local options=( + "-DBUILD_TESTING=ON" + "-DOT_MESSAGE_USE_HEAP=ON" + "-DOT_PLATFORM_BOOTLOADER_MODE=ON" + "-DOT_THREAD_VERSION=${version}" + ) if [[ ${version} != "1.1" ]]; then options+=("-DOT_DUA=ON") diff --git a/src/cli/README.md b/src/cli/README.md index e13f03204e8..9bc0ffb3e3d 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -3075,6 +3075,17 @@ Signal a platform reset. > reset ``` +### reset bootloader + +Signal a platform reset to bootloader mode, if supported. + +Requires `OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE`. + +```bash +> reset bootloader +Done +``` + ### rloc16 Get the Thread RLOC16 value. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 845127a4a32..e4e62620165 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -276,11 +276,34 @@ template <> otError Interpreter::Process(Arg aArgs[]) template <> otError Interpreter::Process(Arg aArgs[]) { - OT_UNUSED_VARIABLE(aArgs); + otError error = OT_ERROR_NONE; - otInstanceReset(GetInstancePtr()); + if (aArgs[0].IsEmpty()) + { + otInstanceReset(GetInstancePtr()); + } - return OT_ERROR_NONE; +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE + /** + * @cli reset bootloader + * @code + * reset bootloader + * @endcode + * @cparam reset bootloader + * @par api_copy + * #otInstanceResetToBootloader + */ + else if (aArgs[0] == "bootloader") + { + error = otInstanceResetToBootloader(GetInstancePtr()); + } +#endif + else + { + error = OT_ERROR_INVALID_COMMAND; + } + + return error; } void Interpreter::ProcessLine(char *aBuf) diff --git a/src/core/api/instance_api.cpp b/src/core/api/instance_api.cpp index ad2a89e16ce..6d51d3c3986 100644 --- a/src/core/api/instance_api.cpp +++ b/src/core/api/instance_api.cpp @@ -86,6 +86,10 @@ void otInstanceFinalize(otInstance *aInstance) { AsCoreType(aInstance).Finalize( void otInstanceReset(otInstance *aInstance) { AsCoreType(aInstance).Reset(); } +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE +otError otInstanceResetToBootloader(otInstance *aInstance) { return AsCoreType(aInstance).ResetToBootloader(); } +#endif + #if OPENTHREAD_CONFIG_UPTIME_ENABLE uint64_t otInstanceGetUptime(otInstance *aInstance) { return AsCoreType(aInstance).Get().GetUptime(); } diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 6dcc3e8927c..872718fafc0 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -312,6 +312,10 @@ Instance *Instance::Init(void *aBuffer, size_t *aBufferSize) void Instance::Reset(void) { otPlatReset(this); } +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE +Error Instance::ResetToBootloader(void) { return otPlatResetToBootloader(this); } +#endif + #if OPENTHREAD_RADIO void Instance::ResetRadioStack(void) { diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 6b26825abdb..1d5f5b5a553 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -236,6 +236,18 @@ class Instance : public otInstance, private NonCopyable */ void Reset(void); +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE + /** + * Triggers a platform reset to bootloader mode, if supported. + * + * @retval kErrorNone Reset to bootloader successfully. + * @retval kErrorBusy Failed due to another operation is ongoing. + * @retval kErrorNotCapable Not capable of resetting to bootloader. + * + */ + Error ResetToBootloader(void); +#endif + #if OPENTHREAD_RADIO /** * Resets the internal states of the radio. diff --git a/src/lib/spinel/radio_spinel.cpp b/src/lib/spinel/radio_spinel.cpp index a592c3f1636..ab031ae66d5 100644 --- a/src/lib/spinel/radio_spinel.cpp +++ b/src/lib/spinel/radio_spinel.cpp @@ -72,6 +72,7 @@ RadioSpinel::RadioSpinel(void) , mIsPromiscuous(false) , mIsReady(false) , mSupportsLogStream(false) + , mSupportsResetToBootloader(false) , mIsTimeSynced(false) #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 , mRcpFailureCount(0) @@ -236,6 +237,11 @@ bool RadioSpinel::IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostA aSupportsRcpApiVersion = true; } + if (capability == SPINEL_CAP_RCP_RESET_TO_BOOTLOADER) + { + mSupportsResetToBootloader = true; + } + if (capability == SPINEL_PROP_RCP_MIN_HOST_API_VERSION) { aSupportsRcpMinHostApiVersion = true; @@ -1469,6 +1475,11 @@ otError RadioSpinel::SendReset(uint8_t aResetType) uint8_t buffer[kMaxSpinelFrame]; spinel_ssize_t packed; + if ((aResetType == SPINEL_RESET_BOOTLOADER) && !mSupportsResetToBootloader) + { + ExitNow(error = OT_ERROR_NOT_CAPABLE); + } + // Pack the header, command and key packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_COMMAND_S SPINEL_DATATYPE_UINT8_S, SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_RESET, aResetType); diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index c85e2be7b8c..a1249854507 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -808,10 +808,11 @@ class RadioSpinel /** * Tries to reset the co-processor. * - * @prarm[in] aResetType The reset type, SPINEL_RESET_PLATFORM or SPINEL_RESET_STACK. + * @prarm[in] aResetType The reset type, SPINEL_RESET_PLATFORM, SPINEL_RESET_STACK, or SPINEL_RESET_BOOTLOADER. * * @retval OT_ERROR_NONE Successfully removed item from the property. * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_NOT_CAPABLE Requested reset type is not supported by the co-processor * */ otError SendReset(uint8_t aResetType); @@ -1065,10 +1066,11 @@ class RadioSpinel otExtAddress mIeeeEui64; State mState; - bool mIsPromiscuous : 1; ///< Promiscuous mode. - bool mIsReady : 1; ///< NCP ready. - bool mSupportsLogStream : 1; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. - bool mIsTimeSynced : 1; ///< Host has calculated the time difference between host and RCP. + bool mIsPromiscuous : 1; ///< Promiscuous mode. + bool mIsReady : 1; ///< NCP ready. + bool mSupportsLogStream : 1; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. + bool mSupportsResetToBootloader : 1; ///< RCP supports resetting into bootloader mode. + bool mIsTimeSynced : 1; ///< Host has calculated the time difference between host and RCP. #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index 5cf3836b7a6..af95176dd5f 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1606,6 +1606,8 @@ const char *spinel_capability_to_cstr(spinel_capability_t capability) {SPINEL_CAP_NET_THREAD_1_1, "NET_THREAD_1_1"}, {SPINEL_CAP_NET_THREAD_1_2, "NET_THREAD_1_2"}, {SPINEL_CAP_RCP_API_VERSION, "RCP_API_VERSION"}, + {SPINEL_CAP_RCP_MIN_HOST_API_VERSION, "RCP_MIN_HOST_API_VERSION"}, + {SPINEL_CAP_RCP_RESET_TO_BOOTLOADER, "RCP_RESET_TO_BOOTLOADER"}, {SPINEL_CAP_MAC_ALLOWLIST, "MAC_ALLOWLIST"}, {SPINEL_CAP_MAC_RAW, "MAC_RAW"}, {SPINEL_CAP_OOB_STEERING_DATA, "OOB_STEERING_DATA"}, diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index ffa370786bf..770cc96e1c9 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -897,8 +897,9 @@ enum enum { - SPINEL_RESET_PLATFORM = 1, - SPINEL_RESET_STACK = 2, + SPINEL_RESET_PLATFORM = 1, + SPINEL_RESET_STACK = 2, + SPINEL_RESET_BOOTLOADER = 3, }; enum @@ -928,8 +929,10 @@ enum * `PROP_LAST_STATUS` has been set to `STATUS_RESET_SOFTWARE`. * * The optional command payload specifies the reset type, can be - * `SPINEL_RESET_PLATFORM` or `SPINEL_RESET_STACK`. Defaults to stack - * reset if unspecified. + * `SPINEL_RESET_PLATFORM`, `SPINEL_RESET_STACK`, or + * `SPINEL_RESET_BOOTLOADER`. + * + * Defaults to stack reset if unspecified. * * If an error occurs, the value of `PROP_LAST_STATUS` will be emitted * instead with the value set to the generated status code for the error. @@ -1284,6 +1287,7 @@ enum SPINEL_CAP_RCP__BEGIN = 64, SPINEL_CAP_RCP_API_VERSION = (SPINEL_CAP_RCP__BEGIN + 0), SPINEL_CAP_RCP_MIN_HOST_API_VERSION = (SPINEL_CAP_RCP__BEGIN + 1), + SPINEL_CAP_RCP_RESET_TO_BOOTLOADER = (SPINEL_CAP_RCP__BEGIN + 2), SPINEL_CAP_RCP__END = 80, SPINEL_CAP_OPENTHREAD__BEGIN = 512, diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 78077287990..acff7845e22 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -1211,6 +1211,14 @@ otError NcpBase::CommandHandler_RESET(uint8_t aHeader) SuccessOrAssert( error = WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_RESET_POWER_ON)); } +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE + else if (reset_type == SPINEL_RESET_BOOTLOADER) + { + // Signal a platform reset to bootloader mode. + // If implemented, this function shouldn't return. + error = otInstanceResetToBootloader(mInstance); + } +#endif else #endif { @@ -1843,6 +1851,10 @@ template <> otError NcpBase::HandlePropertyGet(void) SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_MIN_HOST_API_VERSION)); #endif +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE + SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER)); +#endif + #if OPENTHREAD_PLATFORM_POSIX SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_POSIX)); #endif diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index aafe9b50200..86901dd7df1 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -978,6 +978,15 @@ otError otPlatRadioReceiveAt(otInstance *aInstance, uint8_t aChannel, uint32_t a return OT_ERROR_NOT_IMPLEMENTED; } +#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE +otError otPlatResetToBootloader(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return GetRadioSpinel().SendReset(SPINEL_RESET_BOOTLOADER); +} +#endif + const otRadioSpinelMetrics *otSysGetRadioSpinelMetrics(void) { return GetRadioSpinel().GetRadioSpinelMetrics(); } const otRcpInterfaceMetrics *otSysGetRcpInterfaceMetrics(void) diff --git a/tests/scripts/expect/cli-reset.exp b/tests/scripts/expect/cli-reset.exp index b4306971708..9e828ef925c 100755 --- a/tests/scripts/expect/cli-reset.exp +++ b/tests/scripts/expect/cli-reset.exp @@ -71,4 +71,7 @@ send "panid\n" expect "0xffff" expect_line "Done" +send "reset bootloader\n" +expect_line "Error 27: NotCapable" + dispose_all diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 5487643140e..237c5eb0137 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -223,6 +223,8 @@ OT_TOOL_WEAK void otPlatUartReceived(const uint8_t *, uint16_t) {} OT_TOOL_WEAK void otPlatReset(otInstance *) {} +OT_TOOL_WEAK otError otPlatResetToBootloader(otInstance *) { return OT_ERROR_NOT_CAPABLE; } + OT_TOOL_WEAK otPlatResetReason otPlatGetResetReason(otInstance *) { return OT_PLAT_RESET_REASON_POWER_ON; } OT_TOOL_WEAK void otPlatLog(otLogLevel, otLogRegion, const char *, ...) {}