diff --git a/include/zephyr/pm/pm.h b/include/zephyr/pm/pm.h index 443f5e37d8bd..096ca6942e6f 100644 --- a/include/zephyr/pm/pm.h +++ b/include/zephyr/pm/pm.h @@ -55,16 +55,33 @@ extern "C" { */ struct pm_notifier { sys_snode_t _node; - /** - * Application defined function for doing any target specific operations - * for power state entry. - */ - void (*state_entry)(enum pm_state state); - /** - * Application defined function for doing any target specific operations - * for power state exit. - */ - void (*state_exit)(enum pm_state state); + union { + struct { + /** + * Application defined function for doing any target specific operations + * for power state entry. + */ + void (*state_entry)(enum pm_state state); + /** + * Application defined function for doing any target specific operations + * for power state exit. + */ + void (*state_exit)(enum pm_state state); + }; + struct { + /** + * Application defined function for doing any target specific operations + * for power state entry. Reports the substate id additionally. + */ + void (*substate_entry)(enum pm_state state, uint8_t substate_id); + /** + * Application defined function for doing any target specific operations + * for power state exit. Reports the substate id additionally. + */ + void (*substate_exit)(enum pm_state state, uint8_t substate_id); + }; + }; + bool report_substate; /* 0 is for backwards compatibility that didn't report substates */ }; #if defined(CONFIG_PM) || defined(__DOXYGEN__) diff --git a/include/zephyr/pm/policy.h b/include/zephyr/pm/policy.h index eab33582b920..8175214e8774 100644 --- a/include/zephyr/pm/policy.h +++ b/include/zephyr/pm/policy.h @@ -125,6 +125,48 @@ void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id); */ void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id); +/** + * @brief Request to lock all power states. + * + * Requests use a reference counter. + */ +void pm_policy_state_all_lock_get(void); + +/** + * @brief Release locking of all power states. + */ +void pm_policy_state_all_lock_put(void); + +/** + * @brief Apply power state constraints by locking the specified states. + * + * This function locks all power states specified in the union of all constraints + * in the provided constraint list. Each constraint in the set contributes to + * determining which power states should be locked (not allowed), by increasing + * a reference count just as if pm_policy_state_lock_get was called on each constraints' + * states individually. + * + * @param constraints Pointer to the power state constraints set to apply. + * + * @see pm_policy_state_constraints_put() + */ +void pm_policy_state_constraints_get(struct pm_state_constraints *constraints); + +/** + * @brief Remove power state constraints by unlocking the specified states. + * + * This function unlocks all power states that were previously locked by a + * corresponding call to pm_policy_state_constraints_get() with the same + * constraint set. The function decreases the lock counter for each affected + * power state specified in the union of all constraints in the list, just as + * if pm_policy_state_put was called on all the constraints' states individually. + * + * @param constraints Pointer to the power state constraints set to remove. + * + * @see pm_policy_state_constraints_get() + */ +void pm_policy_state_constraints_put(struct pm_state_constraints *constraints); + /** * @brief Check if a power state lock is active (not allowed). * @@ -244,6 +286,14 @@ static inline void pm_policy_state_lock_put(enum pm_state state, uint8_t substat ARG_UNUSED(substate_id); } +static inline void pm_policy_state_all_lock_get(void) +{ +} + +static inline void pm_policy_state_all_lock_put(void) +{ +} + static inline bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id) { ARG_UNUSED(state); diff --git a/include/zephyr/pm/state.h b/include/zephyr/pm/state.h index ef191d87c143..69d0cae8863a 100644 --- a/include/zephyr/pm/state.h +++ b/include/zephyr/pm/state.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -167,7 +168,8 @@ struct pm_state_info { }; /** - * Power state information needed to lock a power state. + * Information needed to be able to reference a power state as a constraint + * on some device or system functions. */ struct pm_state_constraint { /** @@ -184,6 +186,16 @@ struct pm_state_constraint { uint8_t substate_id; }; +/** + * Collection of multiple power state constraints. + */ +struct pm_state_constraints { + /** Array of power state constraints */ + struct pm_state_constraint *list; + /** Number of constraints in the list */ + size_t count; +}; + /** @cond INTERNAL_HIDDEN */ /** @@ -358,6 +370,54 @@ struct pm_state_constraint { Z_PM_STATE_FROM_DT_CPU, (), node_id) \ } +/** + * @brief initialize a device pm constraint with information from devicetree. + * + * @param node_id Node identifier. + */ +#define PM_STATE_CONSTRAINT_INIT(node_id) \ + { \ + .state = PM_STATE_DT_INIT(node_id), \ + .substate_id = DT_PROP_OR(node_id, substate_id, 0), \ + } + +#define Z_PM_STATE_CONSTRAINT_REF(node_id, phandle, idx) \ + PM_STATE_CONSTRAINT_INIT(DT_PHANDLE_BY_IDX(node_id, phandle, idx)) + +#define Z_PM_STATE_CONSTRAINTS_LIST_NAME(node_id, phandles) \ + _CONCAT_4(node_id, _, phandles, _constraints) + +/** + * @brief Define a list of power state constraints from devicetree. + * + * This macro creates an array of pm_state_constraint structures initialized + * with power state information from the specified devicetree property. + * + * @param node_id Devicetree node identifier. + * @param prop Property name containing the list of power state phandles. + */ +#define PM_STATE_CONSTRAINTS_LIST_DEFINE(node_id, prop) \ + struct pm_state_constraint Z_PM_STATE_CONSTRAINTS_LIST_NAME(node_id, prop)[] = \ + { \ + DT_FOREACH_PROP_ELEM_SEP(node_id, prop, Z_PM_STATE_CONSTRAINT_REF, (,)) \ + } + +/** + * @brief Get power state constraints structure from devicetree. + * + * This macro creates a structure containing a pointer to the constraints list + * and the count of constraints, suitable for initializing a pm_state_constraints + * structure. Must be used after the PM_STATE_CONSTRAINTS_LIST_DEFINE call for the same + * @param prop to refer to the array of constraints. + * + * @param node_id Devicetree node identifier. + * @param prop Property name containing the list of power state phandles. + */ +#define PM_STATE_CONSTRAINTS_GET(node_id, prop) \ + { \ + .list = Z_PM_STATE_CONSTRAINTS_LIST_NAME(node_id, prop), \ + .count = DT_PROP_LEN(node_id, prop), \ + } #if defined(CONFIG_PM) || defined(__DOXYGEN__) /** @@ -382,6 +442,38 @@ uint8_t pm_state_cpu_get_all(uint8_t cpu, const struct pm_state_info **states); * @return Pointer to the power state structure or NULL if state is not found. */ const struct pm_state_info *pm_state_get(uint8_t cpu, enum pm_state state, uint8_t substate_id); + +/** + * @brief Convert a pm_state enum value to its string representation. + * + * @param state Power state. + * + * @return A constant string representing the state. + */ +const char *pm_state_to_str(enum pm_state state); + + +/** + * @brief Parse a string and convert it to a pm_state enum value. + * + * @param name Input string (e.g., "suspend-to-ram"). + * @param out Pointer to store the parsed pm_state value. + * + * @return 0 on success, -EINVAL if the string is invalid or NULL. + */ +int pm_state_from_str(const char *name, enum pm_state *out); + +/** + * @brief Check if a power management constraint matches any in a set of constraints. + * + * @param constraints Pointer to the power state constraints structure. + * @param match The constraint to match against. + * + * @return true if the constraint matches, false otherwise. + */ +bool pm_state_in_constraints(const struct pm_state_constraints *constraints, + const struct pm_state_constraint match); + /** * @} */ @@ -407,6 +499,14 @@ static inline const struct pm_state_info *pm_state_get(uint8_t cpu, return NULL; } +static inline bool pm_state_in_constraints(struct pm_state_constraints *constraints, + struct pm_state_constraint match) +{ + ARG_UNUSED(constraints); + ARG_UNUSED(match); + + return false; +} #endif /* CONFIG_PM */ #ifdef __cplusplus diff --git a/subsys/pm/pm.c b/subsys/pm/pm.c index 58448e1eba01..062c7bd623a0 100644 --- a/subsys/pm/pm.c +++ b/subsys/pm/pm.c @@ -47,19 +47,29 @@ static inline void pm_state_notify(bool entering_state) { struct pm_notifier *notifier; k_spinlock_key_t pm_notifier_key; - void (*callback)(enum pm_state state); + union { + void (*without_substate)(enum pm_state state); + void (*with_substate)(enum pm_state state, uint8_t substate_id); + } callback; pm_notifier_key = k_spin_lock(&pm_notifier_lock); SYS_SLIST_FOR_EACH_CONTAINER(&pm_notifiers, notifier, _node) { if (entering_state) { - callback = notifier->state_entry; + /* should be equivalent to also setting the "with substate" */ + callback.without_substate = notifier->state_entry; } else { - callback = notifier->state_exit; + /* should be equivalent to also setting the "with substate" */ + callback.without_substate = notifier->state_exit; } - if (callback) { - callback(z_cpus_pm_state[CPU_ID]->state); - } + if (callback.with_substate && notifier->report_substate) { + callback.with_substate(z_cpus_pm_state[CPU_ID]->state, + z_cpus_pm_state[CPU_ID]->substate_id); + } else if (callback.without_substate) { + callback.without_substate(z_cpus_pm_state[CPU_ID]->state); + } else { + /* intentionally empty */ + }; } k_spin_unlock(&pm_notifier_lock, pm_notifier_key); } diff --git a/subsys/pm/policy/policy_device_lock.c b/subsys/pm/policy/policy_device_lock.c index 290a1ff51597..0c70dd17a189 100644 --- a/subsys/pm/policy/policy_device_lock.c +++ b/subsys/pm/policy/policy_device_lock.c @@ -24,16 +24,6 @@ struct pm_state_device_constraint { */ #define PM_CONSTRAINTS_NAME(node_id) _CONCAT(__devicepmconstraints_, node_id) -/** - * @brief initialize a device pm constraint with information from devicetree. - * - * @param node_id Node identifier. - */ -#define PM_STATE_CONSTRAINT_INIT(node_id) \ - { \ - .state = PM_STATE_DT_INIT(node_id), \ - .substate_id = DT_PROP_OR(node_id, substate_id, 0), \ - } /** * @brief Helper macro to define a device pm constraints. diff --git a/subsys/pm/policy/policy_state_lock.c b/subsys/pm/policy/policy_state_lock.c index f69133dfe893..f8f5a7c41de6 100644 --- a/subsys/pm/policy/policy_state_lock.c +++ b/subsys/pm/policy/policy_state_lock.c @@ -43,10 +43,27 @@ static const struct { static atomic_t lock_cnt[ARRAY_SIZE(substates)]; static atomic_t latency_mask = BIT_MASK(ARRAY_SIZE(substates)); static atomic_t unlock_mask = BIT_MASK(ARRAY_SIZE(substates)); +static atomic_t global_lock_cnt; static struct k_spinlock lock; #endif +void pm_policy_state_all_lock_get(void) +{ +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) + (void)atomic_inc(&global_lock_cnt); +#endif +} + +void pm_policy_state_all_lock_put(void) +{ +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) + __ASSERT(global_lock_cnt > 0, "Unbalanced state lock get/put"); + (void)atomic_dec(&global_lock_cnt); +#endif +} + + void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) @@ -65,6 +82,14 @@ void pm_policy_state_lock_get(enum pm_state state, uint8_t substate_id) #endif } +void pm_policy_state_constraints_get(struct pm_state_constraints *constraints) +{ + for (int i = 0; i < constraints->count; i++) { + pm_policy_state_lock_get(constraints->list[i].state, + constraints->list[i].substate_id); + } +} + void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) @@ -84,13 +109,22 @@ void pm_policy_state_lock_put(enum pm_state state, uint8_t substate_id) #endif } +void pm_policy_state_constraints_put(struct pm_state_constraints *constraints) +{ + for (int i = 0; i < constraints->count; i++) { + pm_policy_state_lock_put(constraints->list[i].state, + constraints->list[i].substate_id); + } +} + bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) for (size_t i = 0; i < ARRAY_SIZE(substates); i++) { if (substates[i].state == state && (substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) { - return atomic_get(&lock_cnt[i]) != 0; + return (atomic_get(&global_lock_cnt) != 0) || + (atomic_get(&lock_cnt[i]) != 0); } } #endif @@ -101,6 +135,10 @@ bool pm_policy_state_lock_is_active(enum pm_state state, uint8_t substate_id) bool pm_policy_state_is_available(enum pm_state state, uint8_t substate_id) { #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_power_state) + if (atomic_get(&global_lock_cnt) != 0) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(substates); i++) { if (substates[i].state == state && (substates[i].substate_id == substate_id || substate_id == PM_ALL_SUBSTATES)) { @@ -119,7 +157,8 @@ bool pm_policy_state_any_active(void) /* Check if there is any power state that is not locked and not disabled due * to latency requirements. */ - return atomic_get(&unlock_mask) & atomic_get(&latency_mask); + return (atomic_get(&global_lock_cnt) == 0) && + (atomic_get(&unlock_mask) & atomic_get(&latency_mask)); #endif return true; } diff --git a/subsys/pm/state.c b/subsys/pm/state.c index 477c462408d3..3427d6684288 100644 --- a/subsys/pm/state.c +++ b/subsys/pm/state.c @@ -107,3 +107,65 @@ const struct pm_state_info *pm_state_get(uint8_t cpu, enum pm_state state, uint8 return NULL; } + +const char *pm_state_to_str(enum pm_state state) +{ + switch (state) { + case PM_STATE_ACTIVE: + return "active"; + case PM_STATE_RUNTIME_IDLE: + return "runtime-idle"; + case PM_STATE_SUSPEND_TO_IDLE: + return "suspend-to-idle"; + case PM_STATE_STANDBY: + return "standby"; + case PM_STATE_SUSPEND_TO_RAM: + return "suspend-to-ram"; + case PM_STATE_SUSPEND_TO_DISK: + return "suspend-to-disk"; + case PM_STATE_SOFT_OFF: + return "soft-off"; + default: + return "UNKNOWN"; + } +} + +int pm_state_from_str(const char *name, enum pm_state *out) +{ + if (strcmp(name, "active") == 0) { + *out = PM_STATE_ACTIVE; + } else if (strcmp(name, "runtime-idle") == 0) { + *out = PM_STATE_RUNTIME_IDLE; + } else if (strcmp(name, "suspend-to-idle") == 0) { + *out = PM_STATE_SUSPEND_TO_IDLE; + } else if (strcmp(name, "standby") == 0) { + *out = PM_STATE_STANDBY; + } else if (strcmp(name, "suspend-to-ram") == 0) { + *out = PM_STATE_SUSPEND_TO_RAM; + } else if (strcmp(name, "suspend-to-disk") == 0) { + *out = PM_STATE_SUSPEND_TO_DISK; + } else if (strcmp(name, "soft-off") == 0) { + *out = PM_STATE_SOFT_OFF; + } else { + return -EINVAL; + } + + return 0; +} + +bool pm_state_in_constraints(const struct pm_state_constraints *constraints, + const struct pm_state_constraint match) +{ + struct pm_state_constraint *constraints_list = constraints->list; + size_t num_constraints = constraints->count; + bool match_found = false; + + for (int i = 0; i < num_constraints; i++) { + enum pm_state state = constraints_list[i].state; + uint8_t substate = constraints_list[i].substate_id; + + match_found |= ((state == match.state) && (substate == match.substate_id)); + } + + return match_found; +} diff --git a/tests/subsys/pm/policy_api/src/main.c b/tests/subsys/pm/policy_api/src/main.c index e336b756294d..9bd2e9fccc85 100644 --- a/tests/subsys/pm/policy_api/src/main.c +++ b/tests/subsys/pm/policy_api/src/main.c @@ -71,6 +71,37 @@ ZTEST(policy_api, test_pm_policy_next_state_default) zassert_equal(next->state, PM_STATE_SUSPEND_TO_RAM); } +ZTEST(policy_api, test_pm_policy_state_all_lock) +{ + /* initial state: PM_STATE_RUNTIME_IDLE allowed */ + zassert_false(pm_policy_state_lock_is_active(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_true(pm_policy_state_is_available(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_true(pm_policy_state_any_active()); + + /* Locking all states. */ + pm_policy_state_all_lock_get(); + pm_policy_state_all_lock_get(); + + /* States are locked. */ + zassert_true(pm_policy_state_lock_is_active(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_false(pm_policy_state_is_available(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_false(pm_policy_state_any_active()); + + pm_policy_state_all_lock_put(); + + /* States are still locked due to reference counter. */ + zassert_true(pm_policy_state_lock_is_active(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_false(pm_policy_state_is_available(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_false(pm_policy_state_any_active()); + + pm_policy_state_all_lock_put(); + + /* States are available again. */ + zassert_false(pm_policy_state_lock_is_active(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_true(pm_policy_state_is_available(PM_STATE_RUNTIME_IDLE, PM_ALL_SUBSTATES)); + zassert_true(pm_policy_state_any_active()); +} + /** * @brief Test the behavior of pm_policy_next_state() when * states are allowed/disallowed and CONFIG_PM_POLICY_DEFAULT=y.