pm: pm_idle add support for SMP

Signed-off-by: buxiasen <buxiasen@xiaomi.com>
This commit is contained in:
buxiasen 2024-06-06 23:14:04 +08:00 committed by Xiang Xiao
parent 4b7c36554c
commit 43176bbade
3 changed files with 295 additions and 5 deletions

View File

@ -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"

View File

@ -26,18 +26,197 @@
#include <nuttx/power/pm.h>
#include <sched/sched.h>
/****************************************************************************
* 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

View File

@ -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 */