Peripheral Driver Power Implementation Tutorial

Describes an implementation of the DPowerHandler class.

Peripheral driver power management is based on the DPowerHandler class. This is a class that defines the interface that the driver must provide to the generic kernel side power manager.

The class also provides the necessary support functions. The class is defined as:

class DPowerHandler : public DBase
    {
public:
    IMPORT_C ~DPowerHandler();
public:
    IMPORT_C DPowerHandler(const TDesC& aName);
    IMPORT_C void Add();
    IMPORT_C void Remove();
    IMPORT_C void PowerUpDone();
    IMPORT_C void PowerDownDone();
    IMPORT_C void SetCurrentConsumption(TInt aCurrent);
    IMPORT_C void DeltaCurrentConsumption(TInt aCurrent);
public: 
    virtual void PowerDown(TPowerState aTargetState) = 0;
    virtual void PowerUp() = 0;
private:
    ...
    };
      

Typically, at least one power handler object is implemented by the peripheral driver. In some cases the peripheral driver interface class may derive from DPowerHandler, in others it creates and owns an instance of a DPowerHandler derived class.

The first eight functions are exported from the kernel, EKERN.EXE, while the remaining two pure virtual functions are implemented by the peripheral driver.

Notes:

  1. Current consumption monitoring does not have a full implementation in the power manager at present. It is unclear whether this will be ever required. However, DPowerHandler does provide two functions: DPowerHandler::SetCurrentConsumption() and DPowerHandler::DeltaCurrentConsumption() that a power handler can use to track the peripheral's power consumption. Note that this is not based on actual measurements.

    SetCurrentConsumption() sets a target current consumption, usually at driver creation time.

    DeltaCurrentConsumption() changes this target current consumption value, specifying a positive or a negative value as appropriate; this might be called in response to an operation that is about to start.

    Although we have described the context in which these functions would be used, we recommend that you do not use them.

  2. Fixed media drivers do not have a power handler. This means there is currently no mechanism to power down media drivers and the associated fixed media when the system transitions to a low power state.

  3. The __NEW_POWER_HANDLERS macro is used to maintain backwards compatibility with the power model used in previous versions of Symbian platform. If this macro is not defined, it is possible for power handlers to revert back to the behavior they present in Kernel Architecture 1 (EKA1).

    If implementing an old style power handler, the following functions will have to have an implementation provided by the peripheral driver:

    virtual TInt DoPowerUp()
    virtual void DoPowerDown(TUint32 /* aPowerDownMask */)
    virtual void DoEmergencyPowerDown()
              

    If using at least an old style power handler the power manager will not complete any powering down (transition to Off or Standby). Thus it is recommended that they not be used.

DPowerHandler::PowerDown()

virtual void PowerDown(TPowerState) = 0;

When is it called?

DPowerHandler::PowerDown() is called by the Power manager during a transition to the Standby or the Off state. The TPowerState argument specifies which of the two power states is the target.

Implementation issues

  1. After receiving a request to power down, as a result of a system transition to the Standby or Off states, a peripheral driver should perform the necessary activity to power down the peripheral and ancillary hardware, unless it is required for the detection of wake-up events. This activity might include requesting the removal of the power supply and any other power resources.

  2. The power down operation can be done in the same thread in which PowerDown() runs, i.e. synchronously, or it can run in another thread, i.e. asynchronously. You would probably implement power down in another thread if the operation were potentially slow. Once the peripheral has powered down, it must inform the power manager by calling DPowerHandler::PowerDownDone(), and this function can be called from the same thread in which PowerDown() runs, or it can be called from another thread. Two points to note:

    • PowerDownDone() can be called before or after PowerDown() returns

    • PowerDownDone() cannot be called before PowerDown() has been entered.

  3. PowerDown() is only called on a transition to the Standby or the Off state. If the peripheral hardware is powered down when the peripheral driver is closed, or when the hardware resources are relinquished by the driver, then this is managed by the driver alone.

  4. There are synchronisation issues related to calls to the DPowerHandler::Add() and DPowerHandler::Remove() functions. DPowerHandler::Add() is called by the peripheral driver when the driver object is created. This registers the power handler with the power manager so that so that the driver can receive notification of power state transitions. Conversely, DPowerHandler::Remove() is called when the peripheral driver is in the process of being destroyed. This de-registers the power handler so that the driver is no longer notified of power state transitions. Calls to DPowerHandler::Add(), DPowerHandler::Remove(), DPowerHandler::PowerDown() and DPowerHandler::PowerUp() can run asynchronously in relation to one another. For example, it is entirely possible that the kernel may be asking the driver to power down while it is being created, or indeed while it is being destroyed.

    To avoid deadlock, DPowerHandler::Add(), DPowerHandler::Remove(), and the Power manager functions that call your DPowerHandler::PowerDown() and your DPowerHandler::PowerUp() functions, all acquire a lock, a DMutex. While the lock itself is internal to Symbian platform, it does impose a requirement that:

    all run in the same thread. A common implementation of DPowerHandler::PowerDown(), therefore, schedules a DFC to run on the same thread (a DFC queue) as the one that calls DPowerHandler::Add() and DPowerHandler::Remove().

DPowerHandler::PowerUp()

virtual void PowerUp() = 0;

When is it called?

DPowerHandler::PowerUp() is called by the Power manager during a transition from the Standby state back to the Active state. It is up to the peripheral driver to decide whether or not to power up the peripheral.

Implementation issues

  1. After receiving a notification to power up, as a result of a system transition from the Standby to the Active state, it is up to the peripheral driver to decide whether or not to power up the peripheral and ancillary hardware. The decision usually depends on whether or not the peripheral driver is currently in use.

  2. The power up operation can be done in the same thread in which PowerUp() runs, i.e. synchronously, or it can run in another thread, i.e. asynchronously. You would probably implement power up in another thread if the operation were potentially slow. Whether or not the peripheral driver intends to power up immediately, it must acknowledge the power up request by calling DPowerHandler::PowerUpDone(), and this function can be called from the same thread in which PowerUp() runs, or it can be called from another thread. Two points to note:

    • PowerUpDone() can be called before or after PowerUp() returns

    • PowerUpDone() cannot be called before PowerUp() has been entered.

  3. PowerUp() is only called on a transition to the Active state. If the peripheral hardware is powered up when the peripheral driver is opened, or when the hardware resources are first used by the driver, then this is managed by the driver alone.

  4. There are synchronisation issues related to calls to the DPowerHandler::Add() and DPowerHandler::Remove() functions. DPowerHandler::Add() is called by the peripheral driver when the driver object is created. This registers the power handler with the power manager so that so that the driver can receive notification of power state transitions. Conversely, DPowerHandler::Remove() is called when the peripheral driver is in the process of being destroyed. This de-registers the power handler so that the driver is no longer notified of power state transitions. Calls to DPowerHandler::Add(), DPowerHandler::Remove(), DPowerHandler::PowerDown() and DPowerHandler::PowerUp() can run asynchronoulsy in relation to one another. For example, it is entirely possible that the kernel may be asking the driver to power down while it is being created, or indeed while it is being destroyed.

    To avoid deadlock, DPowerHandler::Add(), DPowerHandler::Remove(), and the power manager functions that call your DPowerHandler::PowerDown() and your DPowerHandler::PowerUp() functions, all acquire a lock, a DMutex. While the lock itself is internal to Symbian platform, it does impose a requirement that:

    all run in the same thread. A common implementation of DPowerHandler::PowerUp(), therefore, schedules a DFC to run on the same thread (a DFC queue) as the one that calls DPowerHandler::Add() and DPowerHandler::Remove().