diff --git a/Documentation/components/drivers/special/power/pm/index.rst b/Documentation/components/drivers/special/power/pm/index.rst index 72b3fdc24cf6c..28bd3e9ee1266 100644 --- a/Documentation/components/drivers/special/power/pm/index.rst +++ b/Documentation/components/drivers/special/power/pm/index.rst @@ -229,24 +229,61 @@ All PM interfaces are declared in the file ``include/nuttx/power/pm.h``. :param handler: The execution after PM_IDLE_DOMAIN state changed +.. c:function:: void pm_idle_unlock(void) + + This function provide assist of smp pm idle work progress, for pm sequence + other cores will not release before the core hold cpus lock. + Call this function to release SMP idle cpus lock. + +.. c:function:: bool pm_idle_lock(int cpu) + + This function provide assist of smp pm idle work progress, for pm sequence + other cores will not release before the core hold cpus lock. + Call this function to ensure other core will not run until released. + + :param cpu: The current CPU, used to update cpu_set_t + :return: - None + 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. Callbacks ========= -.. c:typedef::pm_idle_handler_t +.. c:type:: pm_idle_handler_t This type declare is provide for pm_idle interface. - Handle the pm low power action and execution for not SMP case. + Handle the pm low power action and execution. Possible execution for long time because of WFI inside. + - for not SMP case. + .. code-block:: c - typedef void (*pm_idle_handler_t)(enum pm_state_e); + typedef void (*pm_idle_handler_t)(enum pm_state_e systemstate); + + :param systemstate: + Indicate the new system power state. + + - for SMP case. + + .. code-block:: c - :param pm_state_e: - Indicate the new system power state. + typedef bool (*pm_idle_handler_t)(int cpu, + enum pm_state_e cpustate, + enum pm_state_e systemstate); + + :param cpu: + Indicate the current working cpu. + :param cpustate: + Indicate the current cpu power state. + :param systemstate: + Indicate the new system power state. If not the lastcore enter idle, + systemstate always PM_RESTORE. If not PM_RESTORE, handler should + cover system pm operations. .. c:struct:: pm_callback_s diff --git a/arch/arm64/src/qemu/CMakeLists.txt b/arch/arm64/src/qemu/CMakeLists.txt index eda0e13e2ef36..7c83bec2d2a74 100644 --- a/arch/arm64/src/qemu/CMakeLists.txt +++ b/arch/arm64/src/qemu/CMakeLists.txt @@ -25,4 +25,7 @@ endif() if(CONFIG_ARCH_USE_TEXT_HEAP) list(APPEND SRCS qemu_textheap.c) endif() +if(CONFIG_PM) + list(APPEND SRCS qemu_initialize.c) +endif() target_sources(arch PRIVATE ${SRCS}) diff --git a/arch/arm64/src/qemu/Make.defs b/arch/arm64/src/qemu/Make.defs index 037ce804280fd..0816edca2d3a2 100644 --- a/arch/arm64/src/qemu/Make.defs +++ b/arch/arm64/src/qemu/Make.defs @@ -30,3 +30,7 @@ endif ifeq ($(CONFIG_ARCH_USE_TEXT_HEAP),y) CHIP_CSRCS += qemu_textheap.c endif + +ifeq ($(CONFIG_PM), y) +CHIP_CSRCS += qemu_initialize.c +endif diff --git a/arch/arm64/src/qemu/qemu_initialize.c b/arch/arm64/src/qemu/qemu_initialize.c new file mode 100644 index 0000000000000..45d32f2c5e09d --- /dev/null +++ b/arch/arm64/src/qemu/qemu_initialize.c @@ -0,0 +1,116 @@ +/**************************************************************************** + * arch/arm64/src/qemu/qemu_initialize.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void arm64_pminitialize(void) +{ + pm_initialize(); +} + +#ifdef CONFIG_SMP +static bool pm_idle_handler(int cpu, + enum pm_state_e cpu_state, + enum pm_state_e system_state) +{ + bool first = false; + switch (cpu_state) + { + case PM_NORMAL: + case PM_IDLE: + case PM_STANDBY: + case PM_SLEEP: + + /* do cpu domain pm enter operations */ + + asm("NOP"); + + if (system_state >= PM_NORMAL) + { + switch (system_state) + { + case PM_NORMAL: + case PM_IDLE: + case PM_STANDBY: + case PM_SLEEP: + + /* do system domain pm enter operations */ + + asm("NOP"); + + break; + default: + break; + } + } + + pm_idle_unlock(); + + /* do no cross-core relative operations */ + + asm("WFI"); + + first = pm_idle_lock(cpu); + if (first) + { + /* do system domain pm leave operations */ + + asm("NOP"); + } + + /* do cpu domain pm leave operations */ + + asm("NOP"); + + break; + default: + break; + } + + return first; +} +#else + +static void pm_idle_handler(enum pm_state_e state) +{ + switch (state) + { + default: + asm("WFI"); + break; + } +} + +#endif + +void up_idle(void) +{ + pm_idle(pm_idle_handler); +} diff --git a/drivers/power/pm/Kconfig b/drivers/power/pm/Kconfig index de6b24976d7c5..598230cc6c5d9 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 683f6b8487829..80d65a4ca853e 100644 --- a/drivers/power/pm/pm_idle.c +++ b/drivers/power/pm/pm_idle.c @@ -26,18 +26,199 @@ #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. + * per cpu sched_lock/unlock is not available here, all post/wait api will + * not allow in pm_callbacks. + */ + + flags = up_irq_save(); + + oldstate = pm_querystate(domain); + newstate = pm_checkstate(domain); + + ret = pm_changestate(domain, newstate); + if (ret < 0) + { + newstate = oldstate; + } + + 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. + * per cpu sched_lock/unlock is not available here, all post/wait api will + * not allow in pm_callbacks. + */ + + up_irq_restore(flags); +} + +#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 +256,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 cb6aa2ae7b5d6..3c434484f24be 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 */