Power Controller DPowerController Implementation Tutorial

This topic describes how to implement the DPowerController class to provide the main power controller functionality in a base port.

DPowerController is defined as:

class DPowerController : public DBase
    {
public:
    IMPORT_C DPowerController();
    IMPORT_C void Register();
    IMPORT_C void WakeupEvent();

#ifndef __X86__
    IMPORT_C TInt RegisterResourceController(DBase* aController, TInt aClientId);
private:
    struct SResourceControllerData
        {
        DBase* iResourceController;
        TInt   iClientId;
        } iResourceControllerData;
#endif

public:
    volatile TPowerState    iTargetState;
public:
    virtual void CpuIdle() = 0;
    virtual void EnableWakeupEvents() = 0;
    virtual void DisableWakeupEvents() = 0;
    virtual void AbsoluteTimerExpired() = 0;
    virtual void PowerDown(TTimeK aWakeupTime) = 0;
    };
      

The first three functions are exported from the kernel, EKERN.EXE, while the other five pure virtual functions are implemented by your base port. You do not need to export any other functions.

Initialising the power controller

Typically, you implement your derived class in a power management kernel extension, which has the opportunity to perform initialisation before the system itself is fully initialised. This is done via the extension's DLL entry point. You use the DECLARE_STANDARD_EXTENSION() macro as a wrapper around your initialisation code. There are at least three things to do here:

For example:

DECLARE_STANDARD_EXTENSION()
    {
    TInt r=KErrNoMemory;

    // Create the power controller object, and register it.
    DPowerController* pC = new DYourPowerController;
    if !(pC)
        {
        return (r);
        }
        pC->Register();

    // Create the battery monitor.
    DBatteryMonitor* pM = new DYourBatteryMonitor;
    if !(pM)
        {
        return(r);
        }

    r = KErrNone;
    return(r);
    }

DPowerController::RegisterResourceController()

The function DPowerController::RegisterResourceController() allows the Resource Controller to register itself with the Power Controller. See registering with the resource controller in the PSL implementation document.

To support idle power management the Power Controller's PSL must override RegisterResourceController() to include the registration of a list of resources whose states are of interest to idle power management.

Idle power management

The Power Controller can assemble a list of resources whose state it is interested in from a NULL thread in an array of SIdleResourceInfo structures. Each entry in the list contains the resource ID that the power controller must enter before registering with the PRM. The resource list should be created in the kernel heap or data section.

The example below creates a buffer to capture the state of all static resources. The PRM captures the information using DPowerResourceController::RegisterResourcesForIdle().

    ...
//Allocate buffer for Idle resource management
    NKern::ThreadEnterCS();
    pBuf = HBuf::New(numResources * sizeof(SIdleResourceInfo)); 
    NKern::ThreadLeaveCS();
    if(!pBuf)
        {
           return KErrNoMemory;
        }
    SIdleResourceInfo* pI = (SIdleResourceInfo*)pBuf->Ptr();

//Update resource ID
    for(TUint c = 0; c < numResources; c++)
        {
        pI[c].iResourceId = c+1; 
        }
    r = (iResourceControllerData.iResourceController)->RegisterResourcesForIdle(iResourceControllerData.iClientId, numResources, (TPtr*)pI);
    ...

The first parameter passed to RegisterResourcesForIdle() is the client Id generated for the power controller this is the client ID passed by the PRM when calling RegisterResourceController.

Note: RegisterResourcesForIdle() is not exported and is only available for use by the Power Controller to register resources for idle power management. Note: RegisterResourcesForIdle() can only be called by the Power Controller once.

DPowerController::EnableWakeupEvents()

virtual void EnableWakeupEvents() = 0;

The Symbian Power Model defines 3 generic system-wide power states: Active, Standby and Off, as defined in the Symbian platform power model.

Each of the system-wide low power states: Standby and Off, has a defined set of wake-up events. If a wake-up event occurs while the system is preparing to transition into one of these low power states, or when the system is in the Standby state, then this can result in the system moving back to the Active power state. Wake-up events may be different for different target power states. Wake-up events are platform-specific hardware events, and it is your base port that decides which events count as wake-up events.

When is it called?

DPowerController::EnableWakeupEvents() is called called by the power manager as a result of a user side call to Power::EnableWakeupEvents(). The class Power is the user side interface to the power manager, and is used by the user side entity that is responsible for moving the device into a low power state.

Context

When the user side entity decides to move the device to a low power state, it sends a notification to all of the user side power domains registered with it, giving their applications a chance to save their data and state. However, before doing this, it calls Power::EnableWakeupEvents(). It also calls Power::RequestWakeupEventNotification() so that it can be informed of a wake-up event. If it is notified of a wake-up event, it can halt, and reverse the user side response to the power transition.

Before calling DPowerController::EnableWakeupEvents(), the power manager sets the iTargetState member of DPowerController to the applicable TPowerState. This means that you can enable the appropriate set of wake-up events.

Once the user side transition is complete, it calls Power::PowerDown() in the user side interface to the power manager, which results in a call to DPowerController::PowerDown() in the power controller. This starts the transition of all power handlers to a low power state, a potentially long process. This means that the power controller needs the ability to detect wake-up events so that it can halt, and reverse the power transition.

Implementation issues

There are three possibilities in dealing with wake-up events:

  • if wake-up events are not handled by specific peripheral drivers, you could set up the hardware so that wake-up events are recorded in a location accessible by the software. Prior to completing the system power transition, the Power controller would check the relevant hardware to see whether a wakeup event had occurred. On detecting a wake-up event it would tell the power manager by calling DPowerController::WakeupEvent().

  • if wake-up events are not handled by specific peripheral drivers, you could arrange for wake-up events to interrupt the CPU. The code that services those interrupts would tell the power manager by calling DPowerController::WakepEvent().

  • if wakeup events are intercepted, serviced and cleared by peripheral drivers, then the following outline solution could be adopted:

    • The power controller would need to publish a list of wake-up events, preferably as a bit mask, and export an API which peripheral drivers could use to notify the power controller that a wake-up event has occurred. The following interface class could be used for this purpose:

      class TPowerController
          {
      public:
          inline static void SetPowerControllerPointer(DPowerController* apPowerController)
          { iPowerControllerPointer = apPowerController; }
          IMPORT_C static void WakeupEventOccurred(TUint aWakeupEvent);
          …        // other methods if required
      private:
          DPowerController* iPowerControllerPointer;
          };
                            

      The class would be implemented as part of the power management kernel extension and SetPowerControllerPointer() would be called when initialising the power controller, but after creation of the power controller object.

      Those peripheral drivers that intercept wake-up events would need to link to the power controller DLL (i.e. the power management kernel extension).

      On the occurrence of a wake-up event, the peripheral driver software servicing that event would notify the power controller by calling WakeupEventOccurred(), specifying the appropriate wake-up event bit(s).

      You might implement WakeupEventOccurred() as follows:

      EXPORT_C void TPowerController::WakeupEventOccurred(TUint aWakeupEvent)
          {
          if(iPowerControllerPointer->iWakeupEvents & aWakeupEvent)
              {
              iPowerControllerPointer->WakeupEvent();
              }
          }

      where iWakeupEvents is a data member defined in your DPowerController -derived class that contains the bitmask representing the wake-up events. The bitmask groups wakeup events according to the target power state.

When the peripheral driver powers down, it should leave the hardware that generates the wake-up event powered and enabled for detection. Thus, if a wake-up event of the type handled by this peripheral driver occurs, it will not be serviced in the normal way, but will be left pending until the power controller services it.

When the power controller’s DPowerController::PowerDown() function is called, the power controller must check the peripheral hardware directly to see whether the wakeup event has happened, or is happening right now, prior to transitioning the device to the target system-wide low power state. If the wake-up event is found to have happened then DPowerController::PowerDown() would return immediately, instead of initiating the power handlers power down of the peripheral drivers.

DPowerController::AbsoluteTimerExpired()

virtual void AbsoluteTimerExpired() = 0;

When is it called?

DPowerController::AbsoluteTimerExpired() is called by the power manager whenever an absolute timer expires. An absolute timer is one that is set to complete at a specific time, as queued by a call to RTimer::At().

Implementation issues

If absolute timer expiration is one of your wake-up events, then your implementation of DPowerController::AbsoluteTimerExpired() should call DPowerController::WakeupEvent() to notify the power manager that a wake-up event has happened.

It is recommended that you track absolute timers.

DPowerController::PowerDown()

virtual void PowerDown(TTimeK aWakeupTime) = 0;

When is it called?

DPowerController::PowerDown() is called by the power manager to move the device into one of the system-wide low power states: Standby or Off.

Typically, this is a result of the user side entity that is responsible for moving the device into a low power state calling Power::PowerDown() in the user side interface to the power manager.

If physical RAM defragmentation is implemented you may wish to include calls to TRamDefragRequest::DefragRam() within this function to enable defragmentation of RAM zones that can then be powered down.

Context

The target state is defined by the value of the iTargetState member of DPowerController, as set by the power manager.

Implementation issues

Implementation depends on the target state:

  • if the target state is Standby, as implied by a value of TPowerState::EPwStandby in DPowerController::iTargetState, then the power controller should put the hardware into a state corresponding to the Standby state.

    If at least one wake-up event has been detected and recorded since the last call to DPowerController::EnableWakeupEvents(), then PowerDown() should return immediately; otherwise, it should continue with the process of putting the device into Standby; execution of the function will halt, and can only continue, and return, when a wake-up event occurs.

  • if the target state is Off, as implied by a value of TPowerState::EPwOff in DPowerController::iTargetState, then PowerDown() must never return. Typically, the power controller turns the device off, but can choose to perform other device-specific action such as a system reboot.

The TTimeK parameter passed to the function is a system time value. If non-zero, it specifies the time when the system should wakeup. The kernel calculates it as the time that the next absolute timer expires. Typically, your implementation will use the Real Time Clock module to generate an event at the specified time, and this will cause a return to the Active state. This implies that the base port should enable Real Time Clock event detection during Standby.

The Symbian definition of the Standby state usually translates into the hardware manufacturer’s CPU “Standby” or “Sleep” modes of operation. Typically, the internal clocks associated with the core and some of the core peripherals, or their power supply, are suppressed, and their internal state is not preserved. In this case, the state of the core and core peripherals should be saved before going into Standby so that they can be restored when the system wakes-up. Note the following:

  • for the core state, save the current mode, the banked registers for each mode, and the stack pointer for both the current mode and user mode

  • for the core peripherals, save the state of the interrupt controller, the pin function controller, the bus state controller, and the clock controller

  • for the MMU state, save the control register, the translation table base address, and the domain access control, if this is supported

  • flush the data cache and drain the write buffer.

If all of this data is saved to a DRAM device, then this should be put into self refresh mode.

Peripherals modules involved in the detection of wake-up events should be left powered.

Tick timer events should be disabled, and the current count of this and any other system timers should be saved; relevant wake-up events should be left unmasked, and any others should be disabled.

DPowerController::CpuIdle()

virtual void CpuIdle()=0;

When is it called?

DPowerController::CpuIdle() is called whenever the Null thread is scheduled to run. This can happen as soon as the power model has been installed. It is the mechanism through which your base port can increase power savings when the system becomes inactive by moving the CPU or the system into a low power mode.

If physical RAM defragmentation is implemented you may wish to include calls to TRamDefragRequest::DefragRam() within this function to enable defragmentation while the device is idle.

Implementation issues

The implementation can call the Variant or ASSP implementation of Asic::Idle().

The idle state is usually implemented via a Wait-For-Interrupt type instruction that will usually suspend execution of the CPU, possibly triggering other ASIC power saving actions, and will resume execution when any unmasked interrupt occurs.

Suppressing the system tick interrupt

To further increase power savings during CPU Idle, a base port can choose to suppress the system tick interrupt until the next nanokernel Timer (as implemented by NTimer) is due to expire. Nanokernel timers are the basic timing service and all Symbian platform tick-based timers and time-of-day functions are derived from nanokernel timers.

In EKA2, timing services rely on a hardware timer, which is programmed by the base port Variant code, to generate the system tick. We refer to this as the system timer.

Typically, the platform-specific ASSP or Variant object has a pointer to the nanokernel timer queue, an NTimerQ object. The number of ticks before the next NTimer is due to expire can be found by calling NTimerQ::IdleTime().

Before going into Idle mode, CpuIdle() disables the hardware timer used to generate the system tick (i.e. the system timer) for the number of ticks to be suppressed; i.e. the system timer is reset to go off and generate an interrupt only when the NTimer expires. Note that the clock supply to the hardware timer must be left enabled.

On returning from Idle mode, the software must examine the system timer and decide whether the NTimer expiration was responsible for waking up the processor, or whether it was due to some other interrupt.

If waking up was due to the NTimer, the system timer must be reset to generate the system tick at the frequency desired, and the tick count, NTimerQ::iMsCount, must be adjusted with the NTimer expiration time. As the expiration time is always an integer multiple of the number of ticks, then the NTimerQ::Advance() function can be used for this purpose.

If waking up was due to another interrupt, then the software must read the system timer and calculate the time elapsed since going into Idle mode, and adjust the system tick count with the elapsed time (which could be a fractional number of ticks) and reprogram the system timer to continue generating the tick after the correct interval, as above.

If the hardware timer that is used to generate the system ticks does not use a Compare-Match function, then some care has to be taken not to introduce skewing when manipulating the timer value directly. If the timer needs to be reloaded with the new value to give the next tick, then the software usually “spins”, waiting for the hardware timer to change, and then reloads it. This way the timer will always be reloaded on an edge of its internal clock.

Waking up from “Sleep” modes with long wakeup latencies

Often, to further enhance power savings, the CPU and some peripherals are moved into a hardware “sleep” mode when CpuIdle() is called. This could be of long latency, and waking up could take longer than the system Tick period. There are two situations:

  • if the wakeup time can be determined precisely, then CpuIdle() programs the system timer to bring the CPU back from the “Sleep“ mode at a time corresponding to the next NTimer expiration (as a number of Ticks to suppress) minus the number of ticks it takes to wake up from that mode. On waking up on the timer, the System tick count should be adjusted with the total number of ticks suppressed, i.e. the count corresponding to the time of the NTimer expiration.

  • If the wakeup time cannot be known deterministically, then the above scheme must be combined with another system to allow adjusting the system timer from the hardware Real Time Clock (RTC).

    Typically, the hardware RTC is clocked with a 1Hz clock, and can be programmed to interrupt the CPU on multiples of one second intervals. The clock supply to the RTC must be left enabled on going into "sleep" mode.

To guarantee that timed events occur when they are due, the CPU should only be allowed to go to into the long latency hardware “sleep” mode if the RTC can be guaranteed to complete a second tick before the CPU is due to wakeup.

Note that if waking up from hardware “Sleep” mode takes a non-negligible amount of time, extreme care should be taken when deciding to move the platform into that mode. For example, if a receive request is pending on a data input device and the CPU reaches the Idle mode and the platform is transitioned into a “sleep” state and data arrives while it is still in that state, then there is a possibility that the system will not wake up on time to service the incoming data.

Validation

The e32test programs t_power, and t_timer will put the system into standby and resume off a timer.

Related concepts
Power Management