From 43176bbade79129de88f9cab25e4ca0dad2a0e39 Mon Sep 17 00:00:00 2001 From: buxiasen Date: Thu, 6 Jun 2024 23:14:04 +0800 Subject: [PATCH] pm: pm_idle add support for SMP Signed-off-by: buxiasen --- drivers/power/pm/Kconfig | 2 + drivers/power/pm/pm_idle.c | 185 ++++++++++++++++++++++++++++++++++++- include/nuttx/power/pm.h | 113 +++++++++++++++++++++- 3 files changed, 295 insertions(+), 5 deletions(-) diff --git a/drivers/power/pm/Kconfig b/drivers/power/pm/Kconfig index de6b24976d..598230cc6c 100644 --- a/drivers/power/pm/Kconfig +++ b/drivers/power/pm/Kconfig @@ -23,6 +23,8 @@ config PM_NDOMAINS For example, you may want to separately manage the power from the Network domain, shutting down the network when it is not be used, from the UI domain, shutting down the UI when it is not in use. + For SMP case, should add CONFIG_SMP_NCPU manually, or will lead to + a static assert. config PM_PROCFS bool "PM proc fs support" diff --git a/drivers/power/pm/pm_idle.c b/drivers/power/pm/pm_idle.c index 683f6b8487..97e50eb84f 100644 --- a/drivers/power/pm/pm_idle.c +++ b/drivers/power/pm/pm_idle.c @@ -26,18 +26,197 @@ #include #include +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_SMP + +# define PM_SMP_ALL_CPUS ((1 << CONFIG_SMP_NCPUS) - 1) +# define PM_SMP_CPU_DOMAIN(cpu) (CONFIG_PM_NDOMAINS - CONFIG_SMP_NCPUS + (cpu)) + +static_assert(CONFIG_PM_NDOMAINS >= (CONFIG_SMP_NCPUS + 1), + "No enough domain for PM SMP to occupy"); + +/**************************************************************************** + * Private Type Declarations + ****************************************************************************/ + +struct pm_idle_s +{ + spinlock_t lock; + cpu_set_t running; + cpu_set_t firstdone; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct pm_idle_s g_pm_idle = +{ + SP_UNLOCKED, + PM_SMP_ALL_CPUS, + 0, +}; + /**************************************************************************** * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: pm_idle_unlock + * + * Description: + * Release SMP idle cpus lock, allow other cpu continue idle process. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void pm_idle_unlock(void) +{ + spin_unlock(&g_pm_idle.lock); +} + +/**************************************************************************** + * Name: pm_idle_lock + * + * Description: + * Claim SMP idle cpus lock, other cpu have to wait until released. + * + * Input Parameters: + * cpu - The current CPU, used to update cpu_set_t + * + * Returned Value: + * true - Current CPU is the first one woken from sleep, should handle + * system domain restore process also. + * false - Current CPU is not the first one woken from sleep, should only + * handle cpu domain restore process. + * + * Assumptions: + * Restore operation pm_changestate(, PM_RESTORE) will done inside pm_idle. + * Handler don't have to care about it. + * + ****************************************************************************/ + +bool pm_idle_lock(int cpu) +{ + bool first; + spin_lock(&g_pm_idle.lock); + first = (g_pm_idle.running == 0); + CPU_SET(cpu, &g_pm_idle.running); + return first; +} + /**************************************************************************** * Name: pm_idle * * Description: - * Standard pm idle work flow for up_idle, for not smp case. + * Standard pm idle work flow for up_idle. + * pm_idle_handler_t will be different prototype when SMP. * * Input Parameters: - * handler - The execution after PM_IDLE_DOMAIN state changed. + * handler - The execution after cpu and system domain state changed. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void pm_idle(pm_idle_handler_t handler) +{ + enum pm_state_e systemstate; + enum pm_state_e oldstate; + enum pm_state_e newstate; + irqstate_t flags; + int domain; + bool first; + bool last; + int cpu; + int ret; + + systemstate = PM_RESTORE; + cpu = this_cpu(); + domain = PM_SMP_CPU_DOMAIN(cpu); + + /* If sched lock before irq save, and irq handler do post, scheduler will + * be delayed after WFI until next sched unlock. which is not acceptable. + */ + + flags = up_irq_save(); + sched_lock(); + + oldstate = pm_querystate(domain); + newstate = pm_checkstate(domain); + + ret = pm_changestate(domain, newstate); + if (ret < 0) + { + newstate = PM_NORMAL; + } + + if (oldstate != newstate) + { + pm_stay(PM_IDLE_DOMAIN, newstate); + if (CPU_ISSET(cpu, &g_pm_idle.firstdone)) + { + pm_relax(PM_IDLE_DOMAIN, oldstate); + } + else + { + spin_lock(&g_pm_idle.lock); + CPU_SET(cpu, &g_pm_idle.firstdone); + spin_unlock(&g_pm_idle.lock); + } + } + + spin_lock(&g_pm_idle.lock); + CPU_CLR(cpu, &g_pm_idle.running); + last = (g_pm_idle.running == 0); + if (last) + { + systemstate = pm_checkstate(PM_IDLE_DOMAIN); + ret = pm_changestate(PM_IDLE_DOMAIN, systemstate); + if (ret < 0) + { + systemstate = PM_NORMAL; + } + } + + first = handler(cpu, newstate, systemstate); + + if (first) + { + pm_changestate(PM_IDLE_DOMAIN, PM_RESTORE); + } + + spin_unlock(&g_pm_idle.lock); + pm_changestate(domain, PM_RESTORE); + + /* If there is pending irq, enable irq make handlers finish all + * execution will be better decrease scheduler context switch times. + */ + + up_irq_restore(flags); + sched_unlock(); +} + +#else + +/**************************************************************************** + * Name: pm_idle + * + * Description: + * Standard pm idle work flow for up_idle. + * pm_idle_handler_t will be different prototype when SMP. + * + * Input Parameters: + * handler - The execution after cpu and system domain state changed. * * Returned Value: * None. @@ -75,3 +254,5 @@ void pm_idle(pm_idle_handler_t handler) up_irq_restore(flags); sched_unlock(); } + +#endif diff --git a/include/nuttx/power/pm.h b/include/nuttx/power/pm.h index cb6aa2ae7b..3c434484f2 100644 --- a/include/nuttx/power/pm.h +++ b/include/nuttx/power/pm.h @@ -145,7 +145,63 @@ enum pm_state_e PM_COUNT, }; -typedef void (*pm_idle_handler_t)(enum pm_state_e); +#ifdef CONFIG_SMP + +/**************************************************************************** + * Name: pm_idle_handler_t + * + * Description: + * Handle the pm low power operations and lock actions. + * As there is WFI inside handler, must manually call + * pm_idle_unlock before go into WFI. + * Also must call pm_idle_lock when woken from WFI at once. + * + * Input Parameters: + * cpu - The current working cpu. + * cpustate - The current cpu power state. + * systemstate - The current system power state. If not the lastcore + * enter idle, systemstate always PM_RESTORE. If not + * PM_RESTORE, handler should cover system pm operations. + * + * Returned Value: + * Should pass the parameter get from pm_idle_lock. + * true - Is the first core already wake up from WFI. + * false - Not the first core who woken up from WFI. + * + * Assumptions: + * The action between pm_idle_unlock and pm_idle_lock must be + * no cross cpu and no system pm operation related. + * Always call handler with locked, should do this action chain inside + * handle. enter_ops->unlock->wfi->lock->leave_ops->return. + * Wait-like kernel API not allowed here. + * + ****************************************************************************/ + +typedef bool (*pm_idle_handler_t)(int cpu, + enum pm_state_e cpustate, + enum pm_state_e systemstate); +#else + +/**************************************************************************** + * Name: pm_idle_handler_t + * + * Description: + * Handle the pm low power action and execution for not SMP case. + * Possible execution for long time because of WFI inside. + * + * Input Parameters: + * systemstate - The new system power state. + * + * Returned Value: + * None + * + * Assumptions: + * Wait-like kernel API not allowed here. + * + ****************************************************************************/ + +typedef void (*pm_idle_handler_t)(enum pm_state_e systemstate); +#endif #ifdef CONFIG_PM_PROCFS struct pm_preparefail_s @@ -823,10 +879,11 @@ void pm_auto_updatestate(int domain); * Name: pm_idle * * Description: - * Standard pm idle work flow for up_idle, for not smp case. + * Standard pm idle work flow for up_idle. + * pm_idle_handler_t will be different prototype when SMP. * * Input Parameters: - * handler - The execution after PM_IDLE_DOMAIN state changed. + * handler - The execution after cpu and system domain state changed. * * Returned Value: * None. @@ -835,6 +892,53 @@ void pm_auto_updatestate(int domain); void pm_idle(pm_idle_handler_t handler); +/**************************************************************************** + * Name: pm_idle_unlock + * + * Description: + * Release SMP idle cpus lock, allow other cpu continue idle process. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +#if CONFIG_SMP +void pm_idle_unlock(void); +#else +# define pm_idle_unlock() +#endif + +/**************************************************************************** + * Name: pm_idle_lock + * + * Description: + * Claim SMP idle cpus lock, other cpu have to wait until released. + * + * Input Parameters: + * cpu - The current CPU, used to update cpu_set_t + * + * Returned Value: + * true - Current CPU is the first one woken from sleep, should handle + * system domain restore process also. + * false - Current CPU is not the first one woken from sleep, should only + * handle cpu domain restore process. + * + * Assumptions: + * Restore operation pm_changestate(, PM_RESTORE) will done inside pm_idle. + * Handler don't have to care about it. + * + ****************************************************************************/ + +#if CONFIG_SMP +bool pm_idle_lock(int cpu); +#else +# define pm_idle_lock(cpu) (0) +#endif + #undef EXTERN #ifdef __cplusplus } @@ -874,6 +978,9 @@ void pm_idle(pm_idle_handler_t handler); # define pm_changestate(domain,state) (0) # define pm_querystate(domain) (0) # define pm_auto_updatestate(domain) +# define pm_idle(handler) +# define pm_idle_unlock() +# define pm_idle_lock(cpu) (0) #endif /* CONFIG_PM */ #endif /* __INCLUDE_NUTTX_POWER_PM_H */