adv_power:Advanced Power Management APIs

This is part of an ongoing development of power management
support in zephyr. This implementation builds upon an existing
hook interface and adds more enhancements. This was tested
with reference implementations on quark_d2000 and quark_se.

Change-Id: I28092b7ec90ce1f1cc661cf99ca88708910c8eb2
Signed-off-by: Ramesh Thomas <ramesh.thomas@intel.com>
This commit is contained in:
Ramesh Thomas 2016-02-09 15:17:24 -08:00
commit 3888735e0d
5 changed files with 149 additions and 125 deletions

View file

@ -31,6 +31,7 @@ config ARM
config X86
bool "x86 architecture"
select NANOKERNEL_TICKLESS_IDLE_SUPPORTED
select ADVANCED_IDLE_SUPPORTED
endchoice

View file

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

View file

@ -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);

View file

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

View file

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