Responding to Power State Changes

The most basic level of power management support that a driver can implement is to opt-in to receive notifications for changes to the overall system power state. These include notifications before the system is put to sleep and a notification when the system wakes from sleep. This is particularly important for the driver of a PCI device, since these notifications need to be handled to allow the PCI bus to be powered down completely during system sleep.

The I/O Kit's power model is unique in that the framework doesn't dictate the set of power states that a driver must implement; rather a driver defines its own list of power states that match the capabilities of the device that it controls. At the very least, a driver must define two states: one in which the device is off, and another in which the device is fully operational. During the off state, the device consumes no power (and so draws no power from its parent in the power plane). In this state, the device is unusable. During the on state, the device is drawing power and all of its functionality is available to the user. When the system is put to sleep, the device is put into the off state, and when the system is awake, the device is put into the on state.

In addition to the off and on states, a driver can define power states that correspond to states in which, for example, the device is still powered but at a lower power state where it is still usable but with reduced capabilities. For example, the driver for an LCD monitor could create a power state to describe a mode in which the display is still running but the backlight has been dimmed.

Each power state that a device supports is described by a structure known as IOPMPowerState, which is defined in the header file <IOKit/pwr_mgt/IOPMpowerState.h>. The IOPMPowerState structure is defined as follows:

struct IOPMPowerState
{
        unsigned long   version;
        IOPMPowerFlags  capabilityFlags;
        IOPMPowerFlags  outputPowerCharacter;
        IOPMPowerFlags  inputPowerRequirement;
        unsigned long   staticPower;
        unsigned long   unbudgetedPower;
        unsigned long   powerToAttain;
        unsigned long   timeToAttain;
        unsigned long   settleUpTime;
        unsigned long   timeToLower;
        unsigned long   settleDownTime;
        unsigned long   powerDomainBudget;
};

The fields of this structure are described as follows:

  • version: Holds the version of this IOPMPowerState structure, allowing the structure to be extended in future versions of the I/O Kit while maintaining backwards compatibility. As of Mac OS X 10.7, the structure is still at version 1; the header file provides a definition kIOPMPowerStateVersion1 that can be used.
  • capabilityFlags: A bitmask of flags that describes the capabilities of the device in this power state. Possible flags are:
    • kIOPMPowerOn indicates that the device requires power from its parent and is able to provide power to its children.
    • kIOPMDeviceUsable indicates that the device is usable in this state.
    • kIOPMLowPower indicates that the device is running at a reduced power state compared to the kIOPMPowerOn state. The device may still be usable in this state, which can be indicated by setting both the kIOPMDeviceUsable and kIOPMLowPower bits. The device may or may not be able to provide power to its children while in the low power state.
    • kIOPMPreventIdleSleep is set to disable the system from going to sleep while this power state is active. Note that the user is still able to put the system to sleep (such as by selecting “Sleep” from the Apple menu). It only stops the system from automatically sleeping after a period of inactivity.
    • kIOPMInitialDeviceState indicates that the device starts up in this state, and therefore the driver doesn't need to be sent a power request after being loaded. Note that the I/O Kit may decide to start the driver in a power state that doesn't have the kIOPMInitialDeviceState flag set, and in this case, the driver will receive a power request when it loads.
  • outputPowerCharacter: A flag that describes the power that the device is able to provide to devices that depend on it for power while in this state. This can be either kIOPMPowerOn, to indicate that the device is able to power its children, or 0, to indicate that the device cannot provide power to its children.
  • inputPowerRequirement: A flag that describes the power required by the device from its parent while in this state. This can be either kIOPMPowerOn, to indicate that the device requires its parent to provide it with power, or 0, to indicate that the device does not draw any power from its parent in this state.
  • staticPower: The average power consumption of the device while in this state (in milliwatts). Note that if this value is unknown, a driver can provide a value of 0 for this field (as is done by many of the Apple drivers in the Darwin repository).
  • unbudgetedPower: The power that this device draws from a separate power supply (and not from its parent) while in this state. This value is currently unused in Mac OS X, and so a driver can provide a value of 0 for this field.
  • powerToAttain: The power that this device requires to transition into this state from the previous lower power state. This value is currently unused in Mac OS X, and so a driver can provide a value of 0 for this field.
  • timeToAttain: The time required to transition the hardware into this state from the previous lower power state (in microseconds). If this value is unknown, a driver can provide a value of 0 for this field.
  • settleUpTime: The time required for the power to settle after entering this state from the previous lower power state (in microseconds). If this value is unknown, a driver can provide a value of 0 for this field.
  • timeToLower: The time required to transition the hardware from this state into the next lower power state (in microseconds). If this value is unknown, a driver can provide a value of 0 for this field.
  • settleDownTime: The time required for the power to settle after leaving this state and entering the next lower power state (in microseconds). If this value is unknown, a driver can provide a value of 0 for this field.
  • powerDomainBudget: The amount of power that the device is able to provide to its children while in this state. This value is currently unused in Mac OS X, and so a driver can provide a value of 0 for this field.

Each power state that a device supports is described by an IOPMPowerState structure. The device's driver creates an array of IOPMPowerState structures, each one of which corresponds to a device power state. Every driver that supports power management must contain a power state that corresponds to the off state (in which the device uses no power) and a power state that corresponds to the device's fully on state (in which the device is fully operational). The off state must be the first element in the driver's power state array, and the on state must be the final element in the driver's power state array. The driver can define as many power states as it needs to describe the distinct power states provided by its hardware device, with the only requirement being that the power state array must be sorted, starting with the off state, through the (optional) intermediate power states that require more power and are more functional, to the final state in which the device is running at its full power and is completely operational.

Because the I/O Kit provides support for power management within the IOService class from which every driver is ultimately derived, all drivers have the ability to take part in the power management of the system. To demonstrate this, we will add power management notifications to the simple IOKitTest sample that was developed in Chapter 4 (see Listing 4-2 and Listing 4-3).

To begin with, we need to define the power states that the device supports. This is typically done with a global array of IOPMPowerState structures that is defined at the top of the driver's implementation file. The example in Listing 10-1 shows a very basic set of power states that provide an off state and an on state.

Listing 10-1. Defining a Set of Power States for a Driver

enum {
     kOffPowerState,
     kOnPowerState,
     //
     kNumPowerStates
};

static IOPMPowerState gPowerStates[kNumPowerStates] = {
     // kOffPowerState
     {kIOPMPowerStateVersion1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
     // kOnPowerState
     {kIOPMPowerStateVersion1, (kIOPMPowerOn | kIOPMDeviceUsable),
                                kIOPMPowerOn, kIOPMPowerOn, 0, 0, 0, 0, 0, 0, 0, 0}
};

When the I/O Kit makes a request to change to a new power state, the requested state will be identified by the index of that state in the gPowerStates array. Rather than referring to a power state by its index, we define an enumeration that allows us to give each power state a symbolic constant, which makes the driver code easier to read and maintain.

Having defined a set of power states, the IOService class, which provides the power management API, needs to be informed that our driver wishes to receive notifications when the system's power state changes. This is done in the driver's start() method, as shown in Listing 10-2.

Listing 10-2. Registering a Driver for Power Management Support

bool com_osxkernel_driver_IOKitTest::start (IOService *provider)
{
        if (super::start(provider) == false)
                return false;
        
        // Register driver for power management
        PMinit();
        provider->joinPMtree(this);
        registerPowerDriver(this, gPowerStates, kNumPowerStates);

        
        return true;
}

The call to the PMinit() method initializes instance variables in the IOService superclass that are needed only for a driver that implements power management. To receive power management notifications, the driver needs to be part of the power plane. This is done by the joinPMtree() method, which is called on the driver object from which our device obtains its power (in this case, our provider class) and takes as its argument the child driver (our instance). Finally, we call the registerPowerDriver() method to provide the I/O Kit with the array of power states that our driver supports.

A driver that registers for power management must make sure that it removes itself from the power plane before it is unloaded. If this is not done, the I/O Kit will attempt to send power notifications to your driver, even though it is no longer active, which could potentially result in a kernel panic. A driver removes itself from the power plane by calling PMStop(), which is shown in Listing 10-3.

Listing 10-3. Removing a Driver from the Power Management System

void com_osxkernel_driver_IOKitTest::stop (IOService *provider)
{
        PMstop();
        super::stop(provider);
}

Having inserted the driver into the power plane and registered it for power management events, the driver will receive power requests from the I/O Kit in response to changes in the system. When the system's power state changes, for example when the computer is put into a sleep state or wakes from sleep, the I/O Kit will choose one of the power states that the driver registered and request that the driver transition into that new state. These power requests are made to the driver through its setPowerState() method, which is a virtual method defined in the IOService base class. To receive these requests, a driver simply needs to provide its own implementation of setPowerState() in which to handle the change. A sample implementation is shown in Listing 10-4.

Listing 10-4. Responding to a Request to Change the Device's Power State

IOReturn com_osxkernel_driver_IOKitTest::setPowerState (unsigned long powerStateOrdinal, IOService* device)
{
        switch (powerStateOrdinal)
        {
                case kOffPowerState:
                      // Save device configuration (if necessary) and prepare our hardware for
                      // sleep
                      // ...
                      break;
                case kOnPowerState:
                      // Bring our hardware out of sleep and initialize it with the saved
                      // configuration
                      // ...
                      break;
        }

        
        return kIOPMAckImplied;
}

The parameter named powerStateOrdinal describes the power state that the driver should place its device into. The parameter is expressed as an index into the array of power states that the driver passed to the registerPowerDriver() method during its initialization. In the case of our sample driver, we registered two power states that we handle in a switch statement.

The I/O Kit serializes calls to setPowerState(), so a driver can be sure that it will not receive a request to change power states while it is in the middle of handling an earlier power state transition. However, this does not guarantee that the driver won't receive a request to change power states while it is handling other operations that do not relate to power management. For example, a driver may be performing an asynchronous read operation when a power request is made to transition a driver to the sleep state. In this case, the driver must wait until the read operation has completed before powering down the hardware. This can be achieved by using the standard synchronization primitives that are provided by the I/O Kit. Most I/O Kit drivers will use the combination of a command gate and a work loop to provide synchronization, and so a driver could obtain the command gate in its setPowerState() method to ensure that power events are synchronized with the rest of the driver's code.

As of Mac OS X 10.5, the setPowerState() method is called on its own thread, and so a driver is able to perform actions in its implementation that may block or may otherwise take some time to complete. When the driver has successfully placed the hardware into the new power state, it should return with the result code kIOPMAckImplied.

If your driver will support versions of Mac OS X prior to Mac OS X 10.5, you should not perform blocking operations inside the setPowerState() method, but rather your driver should perform the tasks necessary to place the hardware into the new power state on a background thread. Instead of returning kIOPMAckImplied from the setPowerState() method, your driver should return a non-zero value that indicates the maximum time that your driver requires to place the hardware into the new power state (measured in microseconds). When your background thread has completed switching the hardware to the new device, it signals completion by calling acknowledgeSetPowerState(). Thankfully, none of this code is necessary if you are targeting Mac OS X 10.5 and later.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.219.80