Putting It All Together

In this section, we combine what we have covered in this chapter into a single sample that demonstrates one way of structuring a driver that not only responds to power state changes from the system, but also lowers its own power state when the device has been idle for 5 minutes.

For demonstration purposes, the sample driver defines four power states consisting of the mandatory off and on states, as well as two lower power modes. The off and on state will be set by the power management system when the computer is put to sleep and woken from sleep. The two intermediate states are reached when the device has been left idle for a period; the driver sets up an idle timer in its start() method to lower the device's power state after a period of inactivity.

This sample also demonstrates one approach to synchronizing changes in the device's power state against hardware accesses that the driver makes while performing a requested operation. The driver includes a sample operation called myReadDataFromDevice(), which calls activityTickle() to ensure that the hardware is in a usable power state before attempting to perform the operation. However, since power state changes are asynchronous, the driver needs to wait until the device has fully transitioned to the new power state. The sample driver does this by sleeping on a condition variable that is signaled from the powerChangeDone() method.

Another synchronization problem that this driver needs to handle is that the device cannot be placed into a sleep state if the driver is handling outstanding operations. The sample driver uses an instance variable named m_outstandingIO to keep count of the number of outstanding operations that the driver is processing. If a request is made to lower the power state of the device, the setPowerState() method will wait until all outstanding operations have been completed before it removes the power to the hardware. While waiting for operations to complete, the driver needs to make sure that no further operations are started; this is done by setting the instance variable m_devicePowerState to a lower power state at the start of the setPowerState() method. This means that the m_devicePowerState instance variable will be in the reduced power state while we are waiting for operations to complete but, more importantly, it also means that a new operation, such as myReadDataFromDevice(), will sleep and wait for the device's power to transition to the on state.

The implementation of this sample driver is given in Listing 10-5 and Listing 10-6.

Listing 10-5. A Driver That Can Both Respond to Power State Changes and Can Control its Own Power State (header file)

#include <IOKit/IOService.h>

class com_osxkernel_driver_IOKitTest : public IOService
{
       OSDeclareDefaultStructors(com_osxkernel_driver_IOKitTest)

private:
       IOLock*          m_lock;
       unsigned long    m_devicePowerState;
       SInt32           m_outstandingIO;

protected:
       virtual void             powerChangeDone (unsigned long stateNumber);

public:
       virtual void             free (void);
       virtual bool             start (IOService* provider);
       virtual void             stop (IOService* provider);

       virtual IOReturn setPowerState (unsigned long powerStateOrdinal, IOService* device);

       IOReturn          myReadDataFromDevice ();
};

Listing 10-6. A Driver That Can Both Respond to Power State Changes and Can Control its Own Power State (implementation file)

#include "IOKitTest.h"
#include <IOKit/IOLib.h>

// Define the superclass
#define super IOService

OSDefineMetaClassAndStructors(com_osxkernel_driver_IOKitTest, IOService)

// Define our power states
enum {
     kOffPowerState,
     kStandbyPowerState,
     kIdlePowerState,
     kOnPowerState,
     //
     kNumPowerStates
};

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


bool com_osxkernel_driver_IOKitTest::start (IOService *provider)
{
     if (super::start(provider) == false)
        return false;
     
     // Create a lock for driver/power management synchronization
     m_lock = IOLockAlloc();
     if (m_lock == NULL)
        return false;
     
     // Register driver for power management
     PMinit();
     provider->joinPMtree(this);
     makeUsable();                      // Set the private power state to the highest level
     changePowerStateTo(kOffPowerState);// Set the public power state to the lowest level
     registerPowerDriver(this, gPowerStates, kNumPowerStates);
     
     // Lower the device power level after 5 minutes of activity (expressed in seconds)
     setIdleTimerPeriod(5*60);
     
     return true;
}

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

void com_osxkernel_driver_IOKitTest::free (void)
{
     if (m_lock)
        IOLockFree(m_lock);
     super::free();
}


IOReturn com_osxkernel_driver_IOKitTest::setPowerState (unsigned long powerStateOrdinal,
                                                        IOService* device)
{
     // If lowering the power state, update the saved power state before powering down the
     // hardware
     if (powerStateOrdinal < m_devicePowerState)
        m_devicePowerState = powerStateOrdinal;
     
     switch (powerStateOrdinal)
     {
        case kOffPowerState:
        case kStandbyPowerState:
        case kIdlePowerState:
                // Wait for outstanding IO to complete before putting device into a lower
                // power state
                IOLockLock(m_lock);
                        while (m_outstandingIO != 0)
                        {
                                IOLockSleep(m_lock, &m_outstandingIO, THREAD_UNINT);
                         }
                IOLockUnlock(m_lock);
                
                // Prepare our hardware for sleep
                // ...
                break;
     }
     
     // If raising the power state, update the saved power state after reinitializing the
     // hardware
     if (powerStateOrdinal > m_devicePowerState)
        m_devicePowerState = powerStateOrdinal;
     
     return kIOPMAckImplied;
}

void com_osxkernel_driver_IOKitTest::powerChangeDone (unsigned long stateNumber)
{
     // Wake any threads that are waiting for a power state change
     IOLockWakeup(m_lock, &m_devicePowerState, false);
}


// *** Sample Device Operation *** //
IOReturn com_osxkernel_driver_IOKitTest::myReadDataFromDevice ()
{
     // Ensure the device is in the on power state
     IOLockLock(m_lock);
        if (activityTickle(kIOPMSuperclassPolicy1, kOnPowerState) == false)
        {
                // Wait until the device transitions to the on state
                while (m_devicePowerState != kOnPowerState)
                {
                        IOLockSleep(m_lock, &m_devicePowerState, THREAD_UNINT);
                }
         }
        
        // Increment the number of outstanding operations
        m_outstandingIO += 1;
     IOLockUnlock(m_lock);
     
     // Perform device read ...
     
     // When the operation is complete, decrement the number of outstanding operations
     IOLockLock(m_lock);
        m_outstandingIO -= 1;
        // Wake any threads that are waiting for a change in the number of outstanding
        // operations
        IOLockWakeup(m_lock, &m_outstandingIO, false);
     IOLockUnlock(m_lock);
     
     return kIOReturnSuccess;
}

In an actual driver, the method named myReadDataFromDevice() would be called in response to an action taken by the user that requires the hardware device to be accessed. As such, the method can be called irregularly and may be called at any time. Although we don't call the method myReadDataFromDevice() in the example in Listing 10-6, the code could be extended to add a user client to the driver, allowing a user space process to call the method myReadDataFromDevice().

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

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