diff --git a/doc/spinel-protocol-src/spinel-prop-core.md b/doc/spinel-protocol-src/spinel-prop-core.md index 104451334..2e87f7549 100644 --- a/doc/spinel-protocol-src/spinel-prop-core.md +++ b/doc/spinel-protocol-src/spinel-prop-core.md @@ -141,6 +141,7 @@ Currently defined values are: * 10: `CAP_TRNG`: Support for true random number generation. See (#feature-trng). * 11: `CAP_CMD_MULTI`: Support for `CMD_PROP_VALUE_MULTI_GET` ((#cmd-prop-value-multi-get)), `CMD_PROP_VALUE_MULTI_SET` ((#cmd-prop-value-multi-set), and `CMD_PROP_VALUES_ARE` ((#cmd-prop-values-are)). * 12: `CAP_UNSOL_UPDATE_FILTER`: Support for `PROP_UNSOL_UPDATE_FILTER` ((#prop-unsol-update-filter)) and `PROP_UNSOL_UPDATE_LIST` ((#prop-unsol-update-list)). + * 13: `CAP_MCU_POWER_SAVE`: Support for controlling NCP's MCU power state (`PROP_MCU_POWER_STATE`). * 16: `CAP_802_15_4_2003` * 17: `CAP_802_15_4_2006` * 18: `CAP_802_15_4_2011` @@ -192,7 +193,7 @@ This value is encoded as an unsigned 8-bit integer. The host **MUST** only use this property from NLI 0. Behavior when used from other NLIs is undefined. -### PROP 7: PROP_POWER_STATE {#prop-power-state} +### PROP 7: PROP_POWER_STATE {#prop-power-state} (deprecated) * Type: Read-Write * Packed-Encoding: `C` @@ -201,10 +202,12 @@ Octets: | 1 --------|------------------ Fields: | POWER_STATE +This property is **deprecated**. `MCU_POWER_STATE` provides similar +functionality. + Describes the current power state of the NCP. By writing to this property you can manage the lower state of the NCP. Enumeration is encoded as a single unsigned byte. - Defined values are: * 0: `POWER_STATE_OFFLINE`: NCP is physically powered off. @@ -258,7 +261,7 @@ Unlike most other properties, setting this property to true when the value of the property is already true **MUST** fail with a last status of `STATUS_ALREADY`. -### PROP 10: PROP_HOST_POWER_STATE {#prop-host-power-state} +### PROP 12: PROP_HOST_POWER_STATE {#prop-host-power-state} * Type: Read-Write * Packed-Encoding: `C` @@ -327,6 +330,48 @@ it was entering a low-power state. The host **MUST** only use this property from NLI 0. Behavior when used from other NLIs is undefined. + +### PROP 13: PROP_MCU_POWER_STATE {#prop-mcu-power-state} +* Type: Read-Write +* Packed-Encoding: `C` +* Required capability: CAP_MCU_POWER_SAVE + +This property specifies the desired power state of NCP's micro-controller +(MCU) when the underlying platform's operating system enters idle mode (i.e., +all active tasks/events are processed and the MCU can potentially enter a +energy-saving power state). + +The power state primarily determines how the host should interact with the NCP +and whether the host needs an external trigger (a "poke") to NCP before it can +communicate with the NCP or not. After a reset, the MCU power state MUST be +`SPINEL_MCU_POWER_STATE_ON`. + +Defined values are: + +* 0: `SPINEL_MCU_POWER_STATE_ON`: NCP's MCU stays on and active all the time. + When the NCP's desired power state is set to this value, host can send + messages to NCP without requiring any "poke" or external triggers. MCU is + expected to stay on and active. Note that the `ON` power state only determines + the MCU's power mode and is not related to radio's state. + +* 1: `SPINEL_MCU_POWER_STATE_LOW_POWER`: NCP's MCU can enter low-power + (energy-saving) state. When the NCP's desired power state is set to + `LOW_POWER`, host is expected to "poke" the NCP (e.g., an external trigger + like an interrupt) before it can communicate with the NCP (send a message + to the NCP). The "poke" mechanism is determined by the platform code (based + on NCP's interface to the host). + While power state is set to `LOW_POWER`, NCP can still (at any time) send + messages to host. Note that receiving a message from the NCP does NOT + indicate that the NCP's power state has changed, i.e., host is expected to + continue to "poke" NCP when it wants to talk to the NCP until the power + state is explicitly changed (by setting this property to `ON`). + Note that the `LOW_POWER` power state only determines the MCU's power mode + and is not related to radio's state. + +* 2: `SPINEL_MCU_POWER_STATE_OFF`: NCP is fully powered off. + An NCP hardware reset (via a RESET pin) is required to bring the NCP back + to `SPINEL_MCU_POWER_STATE_ON`. RAM is not retained after reset. + ### PROP 4104: PROP_UNSOL_UPDATE_FILTER {#prop-unsol-update-filter} * Required only if `CAP_UNSOL_UPDATE_FILTER` is set. diff --git a/examples/platforms/posix/misc.c b/examples/platforms/posix/misc.c index ff54095d3..5b995e80a 100644 --- a/examples/platforms/posix/misc.c +++ b/examples/platforms/posix/misc.c @@ -89,3 +89,15 @@ void otPlatWakeHost(void) { // TODO: implement an operation to wake the host from sleep state. } + +otError otPlatSetMcuPowerState(otInstance *aInstance, otPlatMcuPowerState aState) +{ + (void)aInstance; + return (aState == OT_PLAT_MCU_POWER_STATE_ON) ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance) +{ + (void)aInstance; + return OT_PLAT_MCU_POWER_STATE_ON; +} diff --git a/include/openthread/platform/misc.h b/include/openthread/platform/misc.h index f85120338..7a3bb1472 100644 --- a/include/openthread/platform/misc.h +++ b/include/openthread/platform/misc.h @@ -105,6 +105,92 @@ void otPlatAssertFail(const char *aFilename, int aLineNumber); */ void otPlatWakeHost(void); +/** + * Enumeration of micro-controller's power states. + * + * These values are used for NCP configuration when `OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL` is enabled. + * + * The power state specifies the desired power state of NCP's micro-controller (MCU) when the underlying platform's + * operating system enters idle mode (i.e., all active tasks/events are processed and the MCU can potentially enter a + * energy-saving power state). + * + * The power state primarily determines how the host should interact with the NCP and whether the host needs an + * external trigger (a "poke") to NCP before it can communicate with the NCP or not. + * + * After a reset, the MCU power state MUST be `OT_PLAT_POWER_STATE_ON`. + * + */ +typedef enum { + /** + * NCP's MCU stays on and active all the time. + * + * When the NCP's desired power state is set to `ON`, host can send messages to NCP without requiring any "poke" or + * external triggers. + * + * @note The `ON` power state only determines the MCU's power mode and is not related to radio's state. + * + */ + OT_PLAT_MCU_POWER_STATE_ON = 0, + + /** + * NCP's MCU can enter low-power (energy-saving) state. + * + * When the NCP's desired power state is set to `LOW_POWER`, host is expected to "poke" the NCP (e.g., an external + * trigger like an interrupt) before it can communicate with the NCP (send a message to the NCP). The "poke" + * mechanism is determined by the platform code (based on NCP's interface to the host). + * + * While power state is set to `LOW_POWER`, NCP can still (at any time) send messages to host. Note that receiving + * a message from the NCP does NOT indicate that the NCP's power state has changed, i.e., host is expected to + * continue to "poke" when it wants to talk to the NCP until the power state is explicitly changed (by a successful + * call to `otPlatSetMcuPowerState()` changing the state to `ON`). + * + * @note The `LOW_POWER` power state only determines the MCU's power mode and is not related to radio's state + * (radio is managed by OpenThread core and device role, e.g., device being sleepy or not. + * + */ + OT_PLAT_MCU_POWER_STATE_LOW_POWER = 1, + + /** + * NCP is fully off. + * + * An NCP hardware reset (via a RESET pin) is required to bring the NCP back to `SPINEL_MCU_POWER_STATE_ON`. + * RAM is not retained after reset. + * + */ + OT_PLAT_MCU_POWER_STATE_OFF = 2, +} otPlatMcuPowerState; + +/** + * This function sets the desired MCU power state. + * + * This is only applicable and used for NCP configuration when `OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL` + * is enabled. + * + * @param[in] aInstance A pointer to OpenThread instance. + * @param[in] aState The new MCU power state. + * + * @retval OT_ERROR_NONE The power state updated successfully. + * @retval OT_ERROR_FAILED The given MCU power state is not supported by the platform. + * + */ +otError otPlatSetMcuPowerState(otInstance *aInstance, otPlatMcuPowerState aState); + +/** + * This function gets the current desired MCU power state. + * + * This is only applicable and used for NCP configuration when `OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL` + * is enabled. + * + * After a reset, the power state MUST return `OT_PLAT_POWER_STATE_ON`. During operation, power state SHOULD only + * change through an explicit successful call to `otPlatSetMcuPowerState()`. + * + * @param[in] aInstance A pointer to OpenThread instance. + * + * @returns The current power state. + * + */ +otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance); + /** * @} * diff --git a/src/core/openthread-core-default-config.h b/src/core/openthread-core-default-config.h index b87c2e8c2..47b86147a 100644 --- a/src/core/openthread-core-default-config.h +++ b/src/core/openthread-core-default-config.h @@ -1248,6 +1248,27 @@ #define OPENTHREAD_CONFIG_NCP_SPINEL_RESPONSE_QUEUE_SIZE 15 #endif +/** + * @def OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + * + * Define to 1 to enable support controlling of desired power state of NCP's micro-controller. + * + * The power state specifies the desired power state of NCP's micro-controller (MCU) when the underlying platform's + * operating system enters idle mode (i.e., all active tasks/events are processed and the MCU can potentially enter a + * energy-saving power state). + * + * The power state primarily determines how the host should interact with the NCP and whether the host needs an + * external trigger (a "poke") before it can communicate with the NCP or not. + * + * When enabled, the platform is expected to provide `otPlatSetMcuPowerState()` and `otPlatGetMcuPowerState()` + * functions (please see `openthread/platform/misc.h`). Host can then control the power state using + * `SPINEL_PROP_MCU_POWER_STATE`. + * + */ +#ifndef OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL +#define OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL 0 +#endif + /** * @def OPENTHREAD_CONFIG_STAY_AWAKE_BETWEEN_FRAGMENTS * diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index be9e4c2b7..e71027662 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -76,6 +76,7 @@ const NcpBase::PropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = NCP_GET_PROP_HANDLER_ENTRY(PHY_RX_SENSITIVITY), NCP_GET_PROP_HANDLER_ENTRY(PHY_TX_POWER), NCP_GET_PROP_HANDLER_ENTRY(POWER_STATE), + NCP_GET_PROP_HANDLER_ENTRY(MCU_POWER_STATE), NCP_GET_PROP_HANDLER_ENTRY(PROTOCOL_VERSION), NCP_GET_PROP_HANDLER_ENTRY(MAC_15_4_PANID), NCP_GET_PROP_HANDLER_ENTRY(MAC_15_4_LADDR), @@ -261,6 +262,7 @@ const NcpBase::PropertyHandlerEntry NcpBase::mGetPropertyHandlerTable[] = const NcpBase::PropertyHandlerEntry NcpBase::mSetPropertyHandlerTable[] = { NCP_SET_PROP_HANDLER_ENTRY(POWER_STATE), + NCP_SET_PROP_HANDLER_ENTRY(MCU_POWER_STATE), NCP_SET_PROP_HANDLER_ENTRY(UNSOL_UPDATE_FILTER), NCP_SET_PROP_HANDLER_ENTRY(PHY_TX_POWER), NCP_SET_PROP_HANDLER_ENTRY(PHY_CHAN), @@ -1791,6 +1793,10 @@ otError NcpBase::GetPropertyHandler_CAPS(void) SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_COUNTERS)); SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_UNSOL_UPDATE_FILTER)); +#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MCU_POWER_STATE)); +#endif + #if OPENTHREAD_ENABLE_RAW_LINK_API SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_RAW)); #endif @@ -1859,9 +1865,100 @@ otError NcpBase::GetPropertyHandler_INTERFACE_COUNT(void) return mEncoder.WriteUint8(1); // Only one interface for now } +#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + +otError NcpBase::GetPropertyHandler_MCU_POWER_STATE(void) +{ + spinel_mcu_power_state_t state = SPINEL_MCU_POWER_STATE_ON; + + switch (otPlatGetMcuPowerState(mInstance)) + { + case OT_PLAT_MCU_POWER_STATE_ON: + state = SPINEL_MCU_POWER_STATE_ON; + break; + + case OT_PLAT_MCU_POWER_STATE_LOW_POWER: + state = SPINEL_MCU_POWER_STATE_LOW_POWER; + break; + + case OT_PLAT_MCU_POWER_STATE_OFF: + state = SPINEL_MCU_POWER_STATE_OFF; + break; + } + + return mEncoder.WriteUint8(state); +} + +otError NcpBase::SetPropertyHandler_MCU_POWER_STATE(void) +{ + otError error = OT_ERROR_NONE; + otPlatMcuPowerState powerState; + uint8_t state; + + SuccessOrExit(error = mDecoder.ReadUint8(state)); + + switch (state) + { + case SPINEL_MCU_POWER_STATE_ON: + powerState = OT_PLAT_MCU_POWER_STATE_ON; + break; + + case SPINEL_MCU_POWER_STATE_LOW_POWER: + powerState = OT_PLAT_MCU_POWER_STATE_LOW_POWER; + break; + + case SPINEL_MCU_POWER_STATE_OFF: + powerState = OT_PLAT_MCU_POWER_STATE_OFF; + break; + + default: + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + + SuccessOrExit(error = otPlatSetMcuPowerState(mInstance, powerState)); + +#if OPENTHREAD_FTD || OPENTHREAD_MTD + + // If the call `otPlatSetMcuPowerState()` was successful and the desire + // state is `OFF`, ensure to disable Thread (MLE) operation (and stop + // legacy) and also bring the IPv6 interface down. + + if (powerState == OT_PLAT_MCU_POWER_STATE_OFF) + { + if (otThreadGetDeviceRole(mInstance) != OT_DEVICE_ROLE_DISABLED) + { + otThreadSetEnabled(mInstance, false); + StopLegacy(); + } + + if (otIp6IsEnabled(mInstance)) + { + otIp6SetEnabled(mInstance, false); + } + } +#endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD + +exit: + return error; +} + +#else // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + +otError NcpBase::GetPropertyHandler_MCU_POWER_STATE(void) +{ + return mEncoder.WriteUint8(SPINEL_MCU_POWER_STATE_ON); +} + +otError NcpBase::SetPropertyHandler_MCU_POWER_STATE(void) +{ + return OT_ERROR_DISABLED_FEATURE; +} + +#endif // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + otError NcpBase::GetPropertyHandler_POWER_STATE(void) { - return mEncoder.WriteUint8(SPINEL_POWER_STATE_ONLINE); // Always online at the moment + return mEncoder.WriteUint8(SPINEL_POWER_STATE_ONLINE); } otError NcpBase::SetPropertyHandler_POWER_STATE(void) @@ -2162,7 +2259,6 @@ otError otNcpStreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen) return error; } - extern "C" void otNcpPlatLogv(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, va_list aArgs) { char logString[OPENTHREAD_CONFIG_NCP_SPINEL_LOG_MAX_SIZE]; diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index 23e22a074..2eded4876 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -363,6 +363,8 @@ protected: NCP_GET_PROP_HANDLER(INTERFACE_COUNT); NCP_GET_PROP_HANDLER(POWER_STATE); NCP_SET_PROP_HANDLER(POWER_STATE); + NCP_GET_PROP_HANDLER(MCU_POWER_STATE); + NCP_SET_PROP_HANDLER(MCU_POWER_STATE); NCP_GET_PROP_HANDLER(HWADDR); NCP_GET_PROP_HANDLER(LOCK); NCP_GET_PROP_HANDLER(HOST_POWER_STATE); @@ -691,6 +693,15 @@ protected: otError SetPropertyHandler_STREAM_RAW(uint8_t aHeader); #endif +#if OPENTHREAD_ENABLE_LEGACY + void StartLegacy(void); + void StopLegacy(void); +#else + void StartLegacy(void) { } + void StopLegacy(void) { } +#endif + + protected: static NcpBase *sNcpInstance; static spinel_status_t ThreadErrorToSpinelStatus(otError aError); diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp index a2ae4c127..bf63a01fe 100644 --- a/src/ncp/ncp_base_mtd.cpp +++ b/src/ncp/ncp_base_mtd.cpp @@ -284,28 +284,12 @@ otError NcpBase::SetPropertyHandler_NET_STACK_UP(void) if (enabled != false) { error = otThreadSetEnabled(mInstance, true); - -#if OPENTHREAD_ENABLE_LEGACY - mLegacyNodeDidJoin = false; - - if ((mLegacyHandlers != NULL) && (mLegacyHandlers->mStartLegacy != NULL)) - { - mLegacyHandlers->mStartLegacy(); - } -#endif // OPENTHREAD_ENABLE_LEGACY + StartLegacy(); } else { error = otThreadSetEnabled(mInstance, false); - -#if OPENTHREAD_ENABLE_LEGACY - mLegacyNodeDidJoin = false; - - if ((mLegacyHandlers != NULL) && (mLegacyHandlers->mStopLegacy != NULL)) - { - mLegacyHandlers->mStopLegacy(); - } -#endif // OPENTHREAD_ENABLE_LEGACY + StopLegacy(); } } @@ -2520,9 +2504,6 @@ void NcpBase::HandleLegacyNodeDidJoin(const otExtAddress *aExtAddr) mUpdateChangedPropsTask.Post(); } -#endif // OPENTHREAD_ENABLE_LEGACY - -#if OPENTHREAD_ENABLE_LEGACY otError NcpBase::GetPropertyHandler_NEST_LEGACY_ULA_PREFIX(void) { return mEncoder.WriteData(mLegacyUlaPrefix, sizeof(mLegacyUlaPrefix)); @@ -2560,6 +2541,27 @@ otError NcpBase::GetPropertyHandler_NEST_LEGACY_LAST_NODE_JOINED(void) return mEncoder.WriteEui64(mLegacyLastJoinedNode); } + +void NcpBase::StartLegacy(void) +{ + mLegacyNodeDidJoin = false; + + if ((mLegacyHandlers != NULL) && (mLegacyHandlers->mStartLegacy != NULL)) + { + mLegacyHandlers->mStartLegacy(); + } +} + +void NcpBase::StopLegacy(void) +{ + mLegacyNodeDidJoin = false; + + if ((mLegacyHandlers != NULL) && (mLegacyHandlers->mStopLegacy != NULL)) + { + mLegacyHandlers->mStopLegacy(); + } +} + #endif // OPENTHREAD_ENABLE_LEGACY otError NcpBase::EncodeChannelMask(uint32_t aChannelMask) diff --git a/src/ncp/spinel.h b/src/ncp/spinel.h index 2a33820b5..a0b6789f5 100644 --- a/src/ncp/spinel.h +++ b/src/ncp/spinel.h @@ -216,6 +216,16 @@ typedef enum SPINEL_SCAN_STATE_DISCOVER = 3, } spinel_scan_state_t; +typedef enum +{ + SPINEL_MCU_POWER_STATE_ON = 0, + SPINEL_MCU_POWER_STATE_LOW_POWER = 1, + SPINEL_MCU_POWER_STATE_OFF = 2, +} spinel_mcu_power_state_t; + +// The `spinel_power_state_t` enumeration and `POWER_STATE` +// property are deprecated. Please use `MCU_POWER_STATE` +// instead. typedef enum { SPINEL_POWER_STATE_OFFLINE = 0, @@ -397,6 +407,7 @@ enum SPINEL_CAP_TRNG = 10, SPINEL_CAP_CMD_MULTI = 11, SPINEL_CAP_UNSOL_UPDATE_FILTER = 12, + SPINEL_CAP_MCU_POWER_STATE = 13, SPINEL_CAP_802_15_4__BEGIN = 16, SPINEL_CAP_802_15_4_2003 = (SPINEL_CAP_802_15_4__BEGIN + 0), @@ -457,12 +468,13 @@ typedef enum SPINEL_PROP_VENDOR_ID = 4, ///< [i] SPINEL_PROP_CAPS = 5, ///< capability list [A(i)] SPINEL_PROP_INTERFACE_COUNT = 6, ///< Interface count [C] - SPINEL_PROP_POWER_STATE = 7, ///< PowerState [C] + SPINEL_PROP_POWER_STATE = 7, ///< PowerState [C] (deprecated, use `MCU_POWER_STATE` instead). SPINEL_PROP_HWADDR = 8, ///< PermEUI64 [E] SPINEL_PROP_LOCK = 9, ///< PropLock [b] SPINEL_PROP_HBO_MEM_MAX = 10, ///< Max offload mem [S] SPINEL_PROP_HBO_BLOCK_MAX = 11, ///< Max offload block [S] SPINEL_PROP_HOST_POWER_STATE = 12, ///< Host MCU power state [C] + SPINEL_PROP_MCU_POWER_STATE = 13, ///< NCP's MCU power state [c] SPINEL_PROP_BASE_EXT__BEGIN = 0x1000,