Enabling Power Management

Power management functions respond to system calls for turning the system off or for idling it. These system calls may be triggered by either hardware or software events, such as throwing a power switch or an idle timer count. The following table shows the functions you must implement to enable power management on your target device.

Function Description
OEMPowerOff Places the target device in a power-off state.
OEMIdle Places the target device in an idle state. This is the lowest energy usage state possible balanced with the need to return from idle quickly.

The kernel calls OEMIdle whenever there are no threads ready to run. The kernel provides an extern DWORDdwReschedTime value that indicates when the next known event is to occur. Such asynchronous events as user-generated interrupt from a keyboard or mouse can occur before then and need to be handled.

The kernel calls OEMPowerOff when the user presses the OFF button or when the system requests the device to power off. OEMPowerOff is responsible for handling the state of the board-level logic through the suspend and resume process.

Note   Individual drivers are responsible for handling the board-level logic for individual devices, such as video, audio, and USB.

You can find code samples of OEMPowerOff and OEMIdle in %_WINCEROOT%\Platform\<Platform>\Kernel\Hal.

When the kernel calls the OEMIdle function, the OEM device is requested to go into a sleep, or idle, state. This consists of saving the current state, placing the memory into a refresh state if necessary, stopping the clock, and suspending execution.

In order to conserve power while continually awakening the target device, OEMIdle should sleep for as long as possible. This is usually until the sooner of two events: dwReschedTime, or the maximum delay supported by the hardware timer.

When an interrupt occurs, scheduled or otherwise, the device ends its idle state, the previous state is restored, and the scheduler is invoked. If no new threads are ready to run, the kernel will again call OEMIdle.

When the system returns from idle, OEMIdle must update the CurMSec variable with the real number of milliseconds that have elapsed. The sample platforms also keep partial millisecond counts, dwPartialCurMSec, in case another interrupt occurs, which will cause the system to stop idling before the system timer fires.

The following code example shows how to implement OEMIdle with these variables.

static DWORD dwPartialCurMSec = 0;
void OEMIdle(DWORD dwIdleParam)
{
    DWORD dwIdleMSec;
    DWORD dwPrevMSec = *pCurMSec;

    // Use for 64-bit math.
    ULARGE_INTEGER currIdle = {
        curridlelow,
        curridlehigh
    };

    if ((int) (dwIdleMSec = dwReschedTime - dwPrevMSec) <= 0) {
        // Time to wake up.
        return;
    }

    // Idle until tick if profiling or running iltiming.
    if (bProfileTimerRunning || fIntrTime) {  // fIntrTime : Interrupt Latency timeing.

        // Idle until the end of tick.

        CPUEnterIdle(dwIdleParam);

        // Update global idle time and return.
        currIdle.QuadPart += RESCHED_PERIOD;
        curridlelow = currIdle.LowPart;
        curridlehigh = currIdle.HighPart;
        
        return;
    }

    //
    // Because OEMIdle( ) is being called in the middle of a 
    // standard reschedule period,
    // CurMSec, dwPartialCurMSec, and CurTicks need to be updated 
    // accordingly.
    // At this point, reprogram the timer because dwPartialCurMSec
    // is modified in the next function call.
    //
    CPUGetSysTimerCountElapsed(RESCHED_PERIOD, pCurMSec, &dwPartialCurMSec, pCurTicks);

    if ((int) (dwIdleMSec -= *pCurMSec - dwPrevMSec) > 0) {

        dwPrevMSec = *pCurMSec;

        //
        // The system timer may not be capable of arbitrary timeouts. Get
        // the highest possible CPU-specific timeout available.
        //
        dwIdleMSec = CPUGetSysTimerCountMax(dwIdleMSec);
    
        //
        // Set the timer to wake up much later than usual, if needed.
        //
        CPUSetSysTimerCount(dwIdleMSec);
        CPUClearSysTimerIRQ( );
        
        //
        // Enable wakeup on any interrupt, then go to sleep.
        //
        CPUEnterIdle(dwIdleParam);
        INTERRUPTS_OFF( );
        
        //
        // The wake-up interrupt service routine (ISR) (or any other ISR) 
        // has already run.
        //
        if (dwPrevMSec != *pCurMSec) {
            //
            // The full sleep period was completed.
            // Update the counters.
            //
            *pCurMSec  += (dwIdleMSec - RESCHED_PERIOD); // Subtract resched period, because ISR also incremented.
            CurTicks.QuadPart += (dwIdleMSec - RESCHED_PERIOD) * dwReschedIncrement;
            currIdle.QuadPart += dwIdleMSec;
        } else {
            //
            // Some other interrupt was run before the full idle period 
            // was complete. Determine how much time has elapsed.
            //
            currIdle.QuadPart += CPUGetSysTimerCountElapsed(dwIdleMSec, pCurMSec, &dwPartialCurMSec, pCurTicks);
        }
    }

    // Re-arm counters.
    CPUSetSysTimerCount(RESCHED_PERIOD);
    CPUClearSysTimerIRQ( );

    // Update global idle time.
    curridlelow = currIdle.LowPart;
    curridlehigh = currIdle.HighPart;

    return;
}

Use the previous code example and the system tick ISR to implement the extended idle period. The following code example is used to determine whether the kernel should schedule a thread (SYSINTR_RESCHED returned) or do nothing (SYSINTR_NOP returned).

ULONG ulRet = SYSINTR_NOP;
if ((int)(CurMSec >= dwReschedTime))
       ulRet = SYSINTR_RESCHED;

By returning SYSTINTR_NOP when appropriate, a system tick ISR can prevent the kernel from rescheduling unnecessarily and therefore save power.

For more information about the variables the OAL needs to update in order to support OEMIdle, see CPU Utilization.

See Also

How to Develop an OEM Adaptation Layer

Last updated on Wednesday, April 13, 2005

© 2005 Microsoft Corporation. All rights reserved.