diff --git a/arch/Kconfig b/arch/Kconfig index b8f32309191..833823abe11 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -31,6 +31,7 @@ config ARM config X86 bool "x86 architecture" select NANOKERNEL_TICKLESS_IDLE_SUPPORTED + select ADVANCED_IDLE_SUPPORTED endchoice diff --git a/arch/x86/core/crt0.S b/arch/x86/core/crt0.S index 3f8a0f26890..523b5cdd903 100644 --- a/arch/x86/core/crt0.S +++ b/arch/x86/core/crt0.S @@ -49,7 +49,6 @@ #endif #ifdef CONFIG_ADVANCED_IDLE - GDATA(_AdvIdleCheckSleep) GDATA(_sys_soc_resume) #endif /* CONFIG_ADVANCED_IDLE */ @@ -126,34 +125,6 @@ __csSet: mov %edi, __start_tsc+4 /* high value */ #endif -#ifdef CONFIG_ADVANCED_IDLE - - /* - * Set up the temporary stack to call the _AdvIdleCheckSleep routine - * We use the separate stack here in order to avoid the memory - * corruption if the system recovers from deep sleep - */ - movl $_soc_resume_stack, %esp - addl $CONFIG_ADV_IDLE_STACK_SIZE, %esp - - /* align to stack boundary: ROUND_DOWN (%esp, 4) */ - - andl $0xfffffffc, %esp - - /* - * Invoke _AdvIdleCheckSleep() routine that checks if we are restoring - * from deep sleep or not. The routine returns non-zero if the kernel - * is recovering from deep sleep and to 0 if a cold boot is needed. The - * kernel can skip floating point initialization, BSS initialization, - * and data initialization if recovering from deep sleep. - */ - - call _AdvIdleCheckSleep - cmpl $0, %eax - jne memInitDone - -#endif /* CONFIG_ADVANCED_IDLE */ - #if !defined(CONFIG_FLOAT) /* * Force an #NM exception for floating point instructions @@ -197,6 +168,47 @@ __csSet: #endif /* !CONFIG_FLOAT */ + /* + * Set the stack pointer to the area used for the interrupt stack. + * Note this stack is used during the execution of __start() and + * _Cstart() until the multi-tasking kernel is initialized. The + * dual-purposing of this area of memory is safe since + * interrupts are disabled until the first context switch. + * + * This is also used to call the _sys_soc_resume() routine + * to avoid memory corruption if the system is resuming from + * deep sleep. It is important that _sys_soc_resume() restores + * the stack pointer to what it was at deep sleep before + * enabling interrupts. This is necessary to avoid + * interfering with interrupt handler use of this stack. + * If it is a cold boot then _sys_soc_resume() should not do + * anything and must return immediately. + */ + + movl $_interrupt_stack, %esp + addl $CONFIG_ISR_STACK_SIZE, %esp + + /* align to stack boundary: ROUND_DOWN (%esp, 4) */ + + andl $0xfffffffc, %esp + +#ifdef CONFIG_ADVANCED_IDLE + /* + * Invoke _sys_soc_resume() hook to handle resume from deep sleep. + * It should first check whether system is recovering from + * deep sleep state. If it is, then this function should restore + * states and resume at the point system went to deep sleep. + * In this case this function will never return. + * + * If system is not recovering from deep sleep then it is a + * cold boot. In this case, this function would immediately + * return and execution falls through to cold boot path. + */ + + call _sys_soc_resume + +#endif /* CONFIG_ADVANCED_IDLE */ + #ifdef CONFIG_XIP /* * copy DATA section from ROM to RAM region @@ -283,54 +295,15 @@ bssWords: #endif /* CONFIG_SSE */ -memInitDone: - - /* - * Set the stack pointer to the area used for the interrupt stack. - * Note this stack is only used during the execution of __start() and - * _Cstart(), i.e. only until the multi-tasking kernel is - * initialized. The dual-purposing of this area of memory is safe since - * interrupts are disabled until the first context switch. - */ - - movl $_interrupt_stack, %esp - addl $CONFIG_ISR_STACK_SIZE, %esp - - /* align to stack boundary: ROUND_DOWN (%esp, 4) */ - - andl $0xfffffffc, %esp - #ifdef CONFIG_GDT_DYNAMIC /* activate RAM-based Global Descriptor Table (GDT) */ lgdt %ds:_gdt #endif - -#if defined (CONFIG_ADVANCED_IDLE) - /* - * Invoke _sys_soc_resume(_Cstart, _gdt) by jumping to it. - * If it's a cold boot, this routine jumps to _Cstart and the normal - * kernel boot sequence continues; otherwise, it uses the TSS info - * saved in the GDT to resumes kernel processing at the point it was - * when the system went into deep sleep; that is, _sys_soc_suspend() - * completes and returns a non-zero value. - */ - -#if CONFIG_X86_IAMCU - movl $_Cstart, %eax - movl $_gdt, %edx -#else - pushl $_gdt - pushl $_Cstart -#endif - call _sys_soc_resume -#else /* Jump to C portion of kernel initialization and never return */ jmp _Cstart -#endif /* CONFIG_ADVANCED_IDLE */ - #if defined(CONFIG_SSE) /* SSE control & status register initial value */ @@ -441,10 +414,3 @@ _gdt_rom_entries: .byte 0x00 /* base : 00xxxxxx */ _gdt_rom_end: - -#ifdef CONFIG_ADVANCED_IDLE - .section .NOINIT - .balign 4,0x90 -_soc_resume_stack: - .fill CONFIG_ADV_IDLE_STACK_SIZE -#endif diff --git a/arch/x86/include/advidle.h b/arch/x86/include/advidle.h index ea67b36a4ae..29da058acc5 100644 --- a/arch/x86/include/advidle.h +++ b/arch/x86/include/advidle.h @@ -17,12 +17,12 @@ */ /** - * @brief Custom advanced idle manager + * @brief Power management hooks * - * This header file specifies the custom advanced idle management interface. - * All of the APIs declared here must be supplied by the custom advanced idle - * management system, namely the _AdvIdleCheckSleep(), _sys_soc_suspend() - * and _sys_soc_resume() functions. + * This header file specifies the Power Management hook interface. + * All of the APIs declared here must be supplied by the Power Manager + * application, namely the _sys_soc_suspend() and _sys_soc_resume() + * functions. */ #ifndef __INCadvidle @@ -35,43 +35,93 @@ extern "C" { #ifdef CONFIG_ADVANCED_IDLE /** - * @brief Determine if advanced sleep has occurred + * @brief Exit deep sleep, low power or tickless idle states * - * This routine checks if the system is recovering from advanced - * sleep or cold booting. + * The main purpose of this routine is to notify exit from + * deep sleep, low power or tickless idle. States altered + * at _sys_soc_suspend() should be restored in this function. + * This can be called under following conditions each of which + * require different handling. * - * @return 0 if the system is cold booting on a non-zero - * value if the system is recovering from advanced sleep. + * Deep sleep recovery: + * App should save information in SoC at _sys_soc_suspend() that + * will persist across deep sleep. This function should check + * that information to identify deep sleep recovery. In this case + * this function will restore states and resume execution at the + * point were system entered deep sleep. In this mode, this + * function is called with the interrupt stack. It is important + * that this function, before interrupts are enabled, restores + * the stack that was in use when system went to deep sleep. This + * is to avoid interfering interrupt handlers use of this stack. + * + * Cold boot: + * Cold boot and deep sleep recovery happen at the same location. + * The function identifies it is a cold boot if it does not find + * state information indicating deep sleep, low power state or + * tickless idle. In this case the function returns immediately. + * + * Low power recovery: + * Low power is entered by turning off peripherals, gating clocks + * and entering a low power CPU state like C2. This state is exited by + * an interrupt. In this case this function would be called from + * the interrupt's context. Any peripherals turned off at + * suspend should be turned back on in this function. + * + * Tickless idle exit: + * This function will also be called at exit of kernel's tickless + * idle. Restore any states altered in _sys_soc_suspend(). + * + * @return will not return to caller if deep sleep recovery */ -extern int _AdvIdleCheckSleep(void); +extern void _sys_soc_resume(void); /** - * @brief Continue kernel start-up or awaken kernel from sleep + * @brief Enter deep sleep, low power or tickless idle states * - * This routine checks if the system is recovering from advanced sleep and - * either continues the kernel's cold boot sequence at _Cstart or resumes - * kernel operation at the point it went to sleep; in the latter case, control - * passes to the _sys_soc_suspend() that put the system to sleep, which then - * finishes executing. + * This routine is called by the kernel when it is about to idle. + * This routine is passed the number of clock ticks that the kernel + * calculated as available time to idle. This function should compare + * this time with the wake latencies of the various power saving schemes + * and use the best one that fits. The power saving schemes use the + * following modes. * - * @param _Cstart the address of the _Cstart function - * @param _gdt the address of the global descriptor table in RAM + * Deep Sleep: + * This turns off the core voltage rail and core clock. This would save + * most power but would also have a high wake latency. CPU loses state + * so this function should save CPU states and the location in this + * function where system should resume execution at resume. Function + * should re-enable interrupts and return a non-zero value. * - * @return does not return to caller - */ -extern void _sys_soc_resume(void (*_Cstart)(void), void *_gdt); - -/** - * @brief Perform advanced sleep + * Low Power: + * Peripherals can be turned off and clocks can be gated depending on + * time available. Then swithes to CPU low power state. In this state + * the CPU is still active but in a low power state and does not lose + * any state. This state is exited by an interrupt from where the + * _sys_soc_resume() will be called. To allow the interrupt, + * this function should ensure that interrupts are atomically + * enabled before going to the low power CPU state. This function + * should return a non-zero value to indicate it was handled and kernel + * should not do its own CPU idle. Interrupts should be enabled on exit. * - * This routine checks if the upcoming kernel idle interval is sufficient to - * justify entering advanced sleep mode. If it is, the routine puts the system - * to sleep and then later allows it to resume processing; if not, the routine - * returns immediately without sleeping. + * Tickless Idle: + * This routine can take advantage of the kernel's tickless idle logic + * by turning off peripherals and clocks depending on available time. + * It can return zero to indicate the kernel should do its own CPU idle. + * After the tickless idle wait is completed or if any interrupt occurs, + * the _sys_soc_resume() function will be called to allow restoring + * altered states. Function should return zero. Interrupts should not + * be turned on. + * + * If this function decides to not do any operation then it should + * return zero to let kernel do its idle wait. + * + * This function is entered with interrupts disabled. It should + * re-enable interrupts if it returns non-zero value i.e. if it + * does its own CPU low power wait or deep sleep. * * @param ticks the upcoming kernel idle time * - * @return non-zero if advanced sleep occurred; otherwise zero + * @return non-zero value if deep sleep or CPU low power entered */ extern int _sys_soc_suspend(int32_t ticks); diff --git a/kernel/Kconfig b/kernel/Kconfig index 91a54cd63bc..a100b35ea46 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -207,18 +207,6 @@ config ADVANCED_IDLE and then to restore the system to its previous state (rather than booting up from scratch) when the kernel is re-activated. -config ADV_IDLE_STACK_SIZE - int - prompt "Advanced idle state stack size" - default 16 - depends on ADVANCED_IDLE - help - This option defines the size of the separate stack used during the - system state check while the booting up. A separate stack is used - to avoid memory corruption on the system re-activation from power - down mode. The stack size must be large enough to hold the return - address (4 bytes) and the _AdvIdleCheckSleep() stack frame. - config TICKLESS_IDLE bool prompt "Tickless idle" diff --git a/kernel/microkernel/k_idle.c b/kernel/microkernel/k_idle.c index 428f61605af..408f7cb5e27 100644 --- a/kernel/microkernel/k_idle.c +++ b/kernel/microkernel/k_idle.c @@ -279,21 +279,28 @@ void _sys_power_save_idle(int32_t ticks) } #endif /* CONFIG_TICKLESS_IDLE */ + nano_cpu_set_idle(ticks); #ifdef CONFIG_ADVANCED_IDLE /* - * Call the advanced sleep function, which checks if the system should - * enter a deep sleep state. If so, the function will return a non-zero - * value when the system resumes here after the deep sleep ends. - * If the time to sleep is too short to go to advanced sleep mode, the - * function returns zero immediately and we do normal idle processing. + * Call the suspend hook function, which checks if the system should + * enter deep sleep or low power state. The function will return a + * non-zero value if system was put in deep sleep or low power state. + * If the time available is too short to go to deep sleep or + * low power state, then the function returns zero immediately + * and we do normal idle processing. + * + * This function can turn off devices without entering deep sleep + * or cpu low power state. In this case it should return zero to + * let kernel enter its own tickless idle wait. + * + * This function is entered with interrupts disabled. If the function + * returns a non-zero value then it should re-enable interrupts before + * returning. */ - if (_sys_soc_suspend(ticks) == 0) { - nano_cpu_set_idle(ticks); nano_cpu_idle(); } #else - nano_cpu_set_idle(ticks); nano_cpu_idle(); #endif /* CONFIG_ADVANCED_IDLE */ } @@ -310,6 +317,18 @@ void _sys_power_save_idle(int32_t ticks) */ void _sys_power_save_idle_exit(int32_t ticks) { +#ifdef CONFIG_ADVANCED_IDLE + /* Any idle wait based on CPU low power state will be exited by + * interrupt. This function is called within that interrupt's + * context. _sys_soc_resume() needs to be called here mainly + * to handle exit from CPU low power states. This gives an + * oppurtunity for device states altered in _sys_soc_suspend() + * to be restored before the kernel schedules another thread. + * _sys_soc_resume() is not called from here for deep sleep + * exit. Deep sleep recovery happens at cold boot path. + */ + _sys_soc_resume(); +#endif #ifdef CONFIG_TICKLESS_IDLE if ((ticks == TICKS_UNLIMITED) || ticks >= _sys_idle_threshold_ticks) { /* Resume normal periodic system timer interrupts */