pm: Fix possible race condition in multicore

There is a race condition in a multicore system that happens when the
idle thread in a CPU checks if the state was forced, if not it will
call the policy manager. If a secondary core forces a state after that
this point the value returned by the policy will be rewritten.

Another case is, if a state is forced while a CPU is sleeping,
when this CPU resumes, the forced bit is cleared and the forced state
is never be used.

Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
This commit is contained in:
Flavio Ceolin 2022-05-05 20:58:43 -07:00 committed by Marti Bolivar
commit bbb8c91afa

View file

@ -35,15 +35,18 @@ static struct pm_state_info z_cpus_pm_state[] = {
LISTIFY(CONFIG_MP_NUM_CPUS, CPU_PM_STATE_INIT, (,)) LISTIFY(CONFIG_MP_NUM_CPUS, CPU_PM_STATE_INIT, (,))
}; };
/* bitmask to check if a power state was forced. */ static struct pm_state_info z_cpus_pm_forced_state[] = {
static ATOMIC_DEFINE(z_cpus_pm_state_forced, CONFIG_MP_NUM_CPUS); LISTIFY(CONFIG_MP_NUM_CPUS, CPU_PM_STATE_INIT, (,))
};
static struct k_spinlock pm_forced_state_lock;
#ifdef CONFIG_PM_DEVICE #ifdef CONFIG_PM_DEVICE
static atomic_t z_cpus_active = ATOMIC_INIT(CONFIG_MP_NUM_CPUS); static atomic_t z_cpus_active = ATOMIC_INIT(CONFIG_MP_NUM_CPUS);
#endif #endif
static struct k_spinlock pm_notifier_lock; static struct k_spinlock pm_notifier_lock;
#ifdef CONFIG_PM_DEVICE #ifdef CONFIG_PM_DEVICE
extern const struct device *__pm_device_slots_start[]; extern const struct device *__pm_device_slots_start[];
@ -182,27 +185,30 @@ void pm_system_resume(void)
bool pm_state_force(uint8_t cpu, const struct pm_state_info *info) bool pm_state_force(uint8_t cpu, const struct pm_state_info *info)
{ {
bool ret = false; k_spinlock_key_t key;
__ASSERT(info->state < PM_STATE_COUNT, __ASSERT(info->state < PM_STATE_COUNT,
"Invalid power state %d!", info->state); "Invalid power state %d!", info->state);
key = k_spin_lock(&pm_forced_state_lock);
z_cpus_pm_forced_state[cpu] = *info;
k_spin_unlock(&pm_forced_state_lock, key);
if (!atomic_test_and_set_bit(z_cpus_pm_state_forced, cpu)) { return true;
z_cpus_pm_state[cpu] = *info;
ret = true;
}
return ret;
} }
bool pm_system_suspend(int32_t ticks) bool pm_system_suspend(int32_t ticks)
{ {
uint8_t id = _current_cpu->id; uint8_t id = _current_cpu->id;
k_spinlock_key_t key;
SYS_PORT_TRACING_FUNC_ENTER(pm, system_suspend, ticks); SYS_PORT_TRACING_FUNC_ENTER(pm, system_suspend, ticks);
if (!atomic_test_bit(z_cpus_pm_state_forced, id)) { key = k_spin_lock(&pm_forced_state_lock);
if (z_cpus_pm_forced_state[id].state != PM_STATE_ACTIVE) {
z_cpus_pm_state[id] = z_cpus_pm_forced_state[id];
z_cpus_pm_forced_state[id].state = PM_STATE_ACTIVE;
} else {
const struct pm_state_info *info; const struct pm_state_info *info;
info = pm_policy_next_state(id, ticks); info = pm_policy_next_state(id, ticks);
@ -210,12 +216,12 @@ bool pm_system_suspend(int32_t ticks)
z_cpus_pm_state[id] = *info; z_cpus_pm_state[id] = *info;
} }
} }
k_spin_unlock(&pm_forced_state_lock, key);
if (z_cpus_pm_state[id].state == PM_STATE_ACTIVE) { if (z_cpus_pm_state[id].state == PM_STATE_ACTIVE) {
LOG_DBG("No PM operations done."); LOG_DBG("No PM operations done.");
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks, SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
z_cpus_pm_state[id].state); z_cpus_pm_state[id].state);
atomic_clear_bit(z_cpus_pm_state_forced, id);
return false; return false;
} }
@ -239,7 +245,6 @@ bool pm_system_suspend(int32_t ticks)
(void)atomic_add(&z_cpus_active, 1); (void)atomic_add(&z_cpus_active, 1);
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks, SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
z_cpus_pm_state[id].state); z_cpus_pm_state[id].state);
atomic_clear_bit(z_cpus_pm_state_forced, id);
return false; return false;
} }
} }
@ -269,7 +274,6 @@ bool pm_system_suspend(int32_t ticks)
#endif #endif
pm_stats_update(z_cpus_pm_state[id].state); pm_stats_update(z_cpus_pm_state[id].state);
pm_system_resume(); pm_system_resume();
atomic_clear_bit(z_cpus_pm_state_forced, id);
k_sched_unlock(); k_sched_unlock();
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks, SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
z_cpus_pm_state[id].state); z_cpus_pm_state[id].state);