Programming the Microsoft Windows Driver Model

Additional Power-Management Details

In this section, I ll describe some additional details about power management, including flags you might need to set in your device object, controlling your device s wake-up feature, arranging for power-down requests after your device has been idle for a predetermined time, and optimizing context-restore operations.

Flags to Set in AddDevice

Two flag bits in a device object see Table 8-5 control various aspects of power management. After you call IoCreateDevice in your AddDevice function, both of these bits will be set to 0, and you can set one or the other of them depending on circumstances.

Table 8-5. Power-Management Flags in DEVICE_OBJECT

Flag

Description

DO_POWER_PAGABLE

Driver s IRP_MJ_POWER dispatch routine must run at PASSIVE_LEVEL.

DO_POWER_INRUSH

Powering on this device requires a large amount of current.

Set the DO_POWER_PAGABLE flag if your dispatch function for IRP_MJ_POWER requests must run at PASSIVE_LEVEL. The flag has the name it does because, as you know, paging is allowed at PASSIVE_LEVEL only. If you leave this flag set to 0, the Power Manager is free to send you power requests at DISPATCH_LEVEL. In fact, it always will do so in the current release of Windows XP.

Set the DO_POWER_INRUSH flag if your device draws so much current when powering up that other devices should not be allowed to power up simultaneously. The problem solved by this flag is familiar to people who ve experienced multiple simultaneous spikes of electricity demand at the end of a power outage having all your appliances trying to cycle on at the same time can blow the main breaker. The Power Manager guarantees that only one inrush device at a time will be powered up. Furthermore, it sends power requests to inrush devices at DISPATCH_LEVEL, which implies that you should not also set the DO_POWER_PAGABLE flag.

The system s ACPI filter driver will set the INRUSH flag in the PDO automatically if the ASL description of the device so indicates. All that s required for the system to properly serialize inrush power is that some device object in the stack have the INRUSH flag; you won t need to set the flag in your own device object too. If the system can t automatically determine that you require inrush treatment, however, you ll need to set the flag yourself.

The settings of the PAGABLE and INRUSH flags need to be consistent in all the device objects for a particular device. If the PDO has the PAGABLE flag set, every device object should also have PAGABLE set. Otherwise, a bug check with the code DRIVER_POWER_STATE_FAILURE can occur. (It s legal for a PAGABLE device to be layered on top of a non-PAGABLE device, just not the other way around.) If a device object has the INRUSH flag set, neither it nor any lower device objects should be PAGABLE, or else an INTERNAL_POWER_ERROR bug check will occur. If you re writing a disk driver, don t forget that you may change back and forth from time to time between pageable and nonpageable status in response to device usage PnP notifications about paging files.

Device Wake-Up Features

Some devices have a hardware wake-up feature, which allows them to wake up a sleeping computer when an external event occurs. See Figure 8-15. The power switch on the current crop of PCs is such a device. So are many modems and network cards, which are able to listen for incoming calls and packets, respectively. USB devices ordinarily claim wake-up capability, and many hubs and host controllers implement the wake-up signaling needed to support that claim.

Figure 8-15. Examples of devices that wake the system.

If your device has a wake-up feature, your function driver has additional power-management responsibilities beyond the ones we ve already discussed. The additional responsibilities revolve around the IRP_MN_WAIT_WAKE flavor of IRP_MJ_POWER:

Giving the End User Control

The end user has ultimate control over whether your device s wake-up feature, if any, should actually be armed. The standard way to provide this control is to support a Windows Management Instrumentation (WMI) class named MSPower_DeviceWakeEnable. (See Chapter 10 for more information about WMI.) The Device Manager automatically generates a Power Management tab in the device properties if the driver supports either MSPower_Device Wake Enable or MSPower_DeviceEnable. See Figure 8-16.

Figure 8-16. Power Management tab in device properties.

Your driver should remember the current state of MSPower_Device Wake Enable s Enable member in both your device extension structure and in a registry key. You ll probably want to initialize your device extension variable from the registry at AddDevice time.

WAIT_WAKE Mechanics

As the power policy owner for your device, your driver originates a WAIT_WAKE request by calling PoRequestPowerIrp:

if (InterlockedExchange(&pdx->wwoutstanding, 1)) <skip remaining statements>pdx->wwcancelled = 0; POWER_STATE junk; junk.SystemState = pdx->devcaps.SystemWake status = PoRequestPowerIrp(pdx->Pdo, IRP_MN_WAIT_WAKE, junk, (PREQUEST_POWER_COMPLETE) WaitWakeCallback, pdx, &pdx->WaitWakeIrp);if (!NT_SUCCESS(status)) { pdx->WakeupEnabled = FALSE; pdx->wwoutstanding = 0; }

PoRequestPowerIrp creates an IRP_MJ_POWER with the minor function IRP_MN_WAIT_WAKE and sends it to the topmost driver in your own PnP stack. (I ll explain the other statements shortly.) This fact means that your own POWER dispatch function will see the IRP as it travels down the stack. You should install a completion routine and pass it further down the stack:

IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) WaitWakeCompletionRoutine, pdx, TRUE, TRUE, TRUE); PoStartNextPowerIrp(Irp); status = PoCallDriver(pdx->LowerDeviceObject, Irp); return status;

The bus driver will normally pend the IRP and return STATUS_PENDING. That status code will percolate back up through the dispatch routines of all the drivers in your stack and will eventually cause PoRequestPowerIrp to return STATUS_PENDING. Several other actions are possible, however:

Note that if the bus driver immediately causes the IRP to fail, your I/O completion routine (WaitWakeCompletionRoutine in the preceding example) will be called. Your power callback routine (WaitWakeCallback) will not.

Completing IRP_MN_WAIT_WAKE

In the normal case, when the bus driver returns STATUS_PENDING, you simply leave the IRP sitting there, waiting for one of several things to happen:

In all of these situations, the I/O Manager calls your I/O completion routine (WaitWakeCompletionRoutine) as a normal part of completing the IRP. In addition, the Power Manager calls your power callback routine (WaitWakeCallback) once the IRP is totally complete.

Your I/O Completion Routine

Cancelling an IRP_MN_WAIT_WAKE exposes the same race condition we discussed in Chapter 5 between your call to IoCancelIrp and the bus driver s call to IoCompleteRequest. To safely cancel the IRP, you can use a variation of the technique I showed you for cancelling asynchronous IRPs. This technique relies on interlocking your cancel logic with your completion routine in a special way, as shown here:

VOID CancelWaitWake(PDEVICE_EXTENSION pdx) { PIRP Irp = (PIRP) InterlockedExchangePointer( (PVOID*) &pdx->WaitWakeIrp, NULL); if (Irp) { IoCancelIrp(Irp); if (InterlockedExchange(&pdx->wwcancelled, 1)) IoCompleteRequest(Irp, IO_NO_INCREMENT); } } NTSTATUS WaitWakeCompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PDEVICE_EXTENSION pdx) { if (Irp->PendingReturned) IoMarkIrpPending(Irp); if (InterlockedExchangePointer( (PVOID*) &pdx->WaitWakeIrp, NULL)) return STATUS_SUCCESS; if (InterlockedExchange(&pdx->wwcancelled, 1)) return STATUS_SUCCESS; return STATUS_MORE_PROCESSING_REQUIRED; }

In the example I showed you in Chapter 5, we were dealing with an asynchronous IRP that we had created. We had the responsibility for calling Io FreeIrp to delete the IRP when it was finally complete. In this situation, the Power Manager has created the WAIT_WAKE IRP and will make its own call to IoFreeIrp when the IRP completes. To avoid the cancel/complete race condition, therefore, we need to delay completion until we re past the point where we might want to cancel the request.

The cancel logic depends partly on an undocumented but essential side effect of how PoRequestPowerIrp works internally. Recall that the last argument to PoRequestPowerIrp is the address of a PIRP variable that will receive the address of the IRP that gets created. PoRequestPowerIrp sets that variable before it sends the IRP to the topmost driver. In the example, I used pdx->WaitWakeIrp for that parameter. This is the same data member used in the I/O completion routine and in the CancelWaitWake routine. I m deliberately relying on the fact that PoRequestPowerIrp will set this member before sending the IRP rather than after so that my completion and cancel logic will work correctly, even if invoked before the topmost dispatch routine returns to PoRequestPowerIrp. Note that it would be a mistake to write your code this way:

PIRP foo; status = PoRequestPowerIrp(pdx->Pdo, IRP_MN_WAIT_WAKE, junk, (PREQUEST_POWER_COMPLETE) WaitWakeCallback, pdx, &foo); pdx->WaitWakeIrp = foo; // <== don't do this

This sequence risks setting WaitWakeIrp to the address of a completed IRP. Havoc would occur were you to later try to cancel that IRP.

Your Callback Routine

Your power callback routine for WAIT_WAKE will look something like this one:

VOID WaitWakeCallback(PDEVICE_OBJECT junk, UCHAR MinorFunction, POWER_STATE state, PDEVICE_EXTENSION pdx, PIO_STATUS_BLOCK pstatus) { InterlockedExchange(&pdx->wwoutstanding, 0); if (!NT_SUCCESS(pstatus->Status)) return; else { SendDeviceSetPower(pdx, PowerDeviceD0, FALSE); } }

Here I imagine that you ve written (or copied!) a subroutine named SendDeviceSetPower that will originate a device SET_POWER request. I don t want to show you that function here because it needs to dovetail with your other power logic. There s a routine by that name in GENERIC.SYS or in any driver you build using WDMWIZ, so you can check out my implementation of that function. You call SendDeviceSetPower to put your device into the D0 state to deal with the possibility that the system is already in the working state when your wake-up signal occurs. As I discussed earlier, you need to bring your own device out of its sleep state because you won t get a system SET_POWER in the near future.

A Modest Proposal

System wake-up appears to have been afflicted with very many bugs across the different releases of WDM operating system platforms, different buses, and different chip sets. Here is some anecdotal evidence that wake-up is pretty well broken on platforms earlier than Windows XP:

Faced with an apparently insoluble puzzle, I have formulated the following bit of advice: Never arm your device s wake-up feature without providing a user interface element (such as the Device Manager s Power Management tab) that gives the user control over whether you re actually going to use the feature. Set the default for this feature to off (that is, no wake-up) in all platforms prior to Windows XP. In Windows 98/Me, you ll need to provide your own user interface for controlling your wake-up feature since the Device Manager doesn t generate the property page even if you support the WMI controls. At the very least, provide for a registry setting that your tech support people know about.

There! I feel much better having gotten that off my chest!

Powering Off When Idle

As a general matter, the end user would prefer that your device not draw any power if it isn t being used. Your driver might use two schemes (at least) to implement such a policy. You can register with the Power Manager to be sent a low-power device IRP when your device remains idle for a specified period. Alternatively, you can decide to keep your device in a low power state if no handles happen to be open.

Powering Off After an Idle Period

The mechanics of the time-based idle detection scheme involve two service functions: PoRegisterDeviceForIdleDetection and PoSetDeviceBusy.

To register for idle detection, make this service function call:

pdx->idlecount = PoRegisterDeviceForIdleDetection(pdx->Pdo, ulConservationTimeout, ulPerformanceTimeout, PowerDeviceD3);

The first argument to PoRegisterDeviceForIdleDetection is the address of the PDO for your device. The second and third arguments specify timeout periods measured in seconds. The conservation period will apply when the system is trying to conserve power, such as when running on battery power. The performance period will apply when the system is trying to maximize performance, such as when running on AC power. The fourth argument specifies the device power state into which you want your device to be forced if it s idle for longer than whichever of the timeout periods applies.

Indicating That You re Not Idle

The return value from PoRegisterDeviceForIdleDetection is the address of a long integer that the system uses as a counter. Every second, the Power Manager increments that integer. If it reaches the appropriate timeout value, the Power Manager sends you a device set-power IRP indicating the power state you registered. At various places in your driver, you ll reset this counter to 0 to restart the idle detection period:

if (pdx->idlecount) PoSetDeviceBusy(pdx->idlecount);

PoSetDeviceBusy is a macro in the WDM.H header file that uncritically dereferences its pointer argument to store a 0. It turns out that PoRegisterDeviceForIdleDetection can return a NULL pointer, so you should check for NULL before calling PoSetDeviceBusy.

Now that I ve described what PoSetDeviceBusy does, you can see that its name is slightly misleading. It doesn t tell the Power Manager that your device is busy, in which case you d expect to have to make another call later to indicate that your device is no longer busy. Rather, it indicates that, at the particular instant you use the macro, your device isn t idle. I m not making this point as a mere semantic quibble. If your device is busy with some sort of active request, you ll want to have logic that forestalls idle detection. So you might want to call PoSetDeviceBusy from many places in your driver: from various dispatch routines, from your StartIo routine, and so on. Basically, you want to make sure that the detection period is longer than the longest time that can elapse between the calls to PoSetDeviceBusy that you make during the normal processing of a request.

NOTE

PoRegisterSystemState allows you to prevent the Power Manager from changing the system power state, but you can t use it to forestall idle timeouts. Besides, it isn t implemented in Windows 98/Me, so calling it is contraindicated for drivers that need to be portable between Windows XP and Windows 98/Me.

Choosing Idle Timeouts

Picking the idle timeout values isn t necessarily simple. Certain kinds of devices can specify -1 to indicate the standard power policy time out for their class of device. At the time of this writing, only FILE_DEVICE_DISK and FILE_DEVICE_MASS_STORAGE devices are in this category. While you ll probably want to have default values for the timeout constants, their values should ultimately be under end user control. Underlying the method by which a user gives you these values is a tale of considerable complexity.

Unless your device is one for which the system designers planned a generic idle detection scheme, you ll need to provide a user-mode component that allows the end user to specify timeout values. To fit in best with the rest of the operating system, that piece should be a property page extension of the Power control panel applet. That is, you should provide a user-mode DLL that implements the IShellPropSheetExt and IShellExtInit COM interfaces. This DLL will fit the general description of a shell extension DLL, which is the topic you ll research if you want to learn all the ins and outs of writing this particular piece of user interface software.

NOTE

Learning about COM in general and shell extension DLLs in particular seems to me like a case of the tail wagging the dog insofar as driver programming goes. You can download a free Visual Studio application wizard from my Web site (http://www.oneysoft.com) and use it to construct a property-page extension DLL for the Power applet in the Control Panel. You could define a private IOCTL interface between your DLL and your driver for specifying idle timeout constants and other policy values. Alternatively, you could define a custom WMI schema that includes idle timeout functionality. As you ll see in Chapter 10, it s exceptionally easy to use WMI from a scripting language.

Restoring Power

If you implement idle detection, you ll also have to provide a way to restore power to your device at some later time for example, when you next receive an IRP that requires power. You ll need some relatively complex coding to make this feature work:

I think the best way to handle all of these complications is to always route IRPs that require power through a DEVQUEUE. A skeletal dispatch routine might look like this one:

NTSTATUS DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; if (pdx->powerstate > PowerDeviceD0) SendDeviceSetPower(pdx, PowerDeviceD0, FALSE); IoMarkIrpPending(Irp); StartPacket(&pdx->dqReadWrite, fdo, Irp, OnCancel); return STATUS_PENDING; }

The idea is to unconditionally queue the IRP after starting a power operation that will finish asynchronously. The power-management code elsewhere in your driver will unstall the queue when power finally comes back, and that will release the IRP.

Powering Off When Handles Close

The other basic strategy for idle power management involves keeping your device in a low power state except while an application has a handle open. Your driver should honor the MSPower_DeviceEnable WMI control to decide whether to implement this strategy and should maintain a persistent registry entry that records the end user s most recent specification of the value of this control. Let s suppose you define a member of your device extension structure to record the value of MSPower_DeviceEnable and another to record the number of open handles:

typedef struct _DEVICE_EXTENSION { LONG handles; BOOLEAN autopower; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

Your IRP_MJ_CREATE and IRP_MJ_CLOSE dispatch functions will do something like this:

NTSTATUS DispatchCreate(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DevicExtension; if (InterlockedIncrement(&pdx->handles) == 1) SendDeviceSetPower(fdo, PowerDeviceD0, TRUE); } NTSTATUS DispatchClose(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DevicExtension; if (InterlockedDecrement(&pdx->handles) == 0 && pdx->autopower) SendDeviceSetPower(fdo, PowerDeviceD3, TRUE); }

These are synchronous calls to SendDeviceSetPower, so we don t have to worry about race conditions between the power-up operation initiated by DispatchCreate and the power-down operation initiated by the matching call to DispatchClose.

When you employ this strategy, you rely on the DEVQUEUE or an equivalent package to stall delivery of substantive IRPs to a StartIo routine while power is off.

NOTE

A USB driver running in Windows XP or later should use the Selective Suspend protocol instead of directly sending a device IRP to idle the device. Refer to Chapter 12 for details about Selective Suspend. The only change to the sample code shown above is that you don t just call SendDeviceSetPower. Instead, you send a special idle registration IRP down the PnP stack with a pointer to a callback routine. The parent driver calls you back when it s time for you to actually put your device into a low power state, whereupon you make the call to SendDeviceSetPower. The WAKEUP sample driver illustrates the necessary mechanics.

Using Sequence Numbers to Optimize State Changes

You might want to use an optimization technique in connection with removing and restoring power to your device. Two background facts will help you make sense of the optimization technique. First, the bus driver doesn t always power down a device, even when it receives a device set-power IRP. This particular bit of intransigence arises because of the way computers are wired together. There might be one or more power channels, and any random collection of devices might be wired to any given channel. These devices are said to share a power relation. A particular device can t be powered down unless all the other devices on the same power channel are powered down as well. So to use the macabre example that I sometimes give my seminar students, suppose the modem you want to power down happens to share a power channel with your computer s heart-lung machine the system can t power down your modem until the bypass operation is over.

The second background fact is that some devices require a great deal of time to change power. To return to the preceding example, suppose your modem were such a device. At some point, you received and passed along a device set-power request to put your modem to sleep. Unbeknownst to you, however, the bus driver didn t actually power down the modem. When the time came to restore power, you could have saved some time if you had known that your modem hadn t lost power. That s where this particular optimization comes into play.

At the time you remove power, you can create and send a power request with the minor function code IRP_MN_POWER_SEQUENCE to the drivers underneath yours. Even though this IRP is technically an IRP_MJ_POWER, you use IoBuildAsynchronousFsdRequest instead of PoRequestPowerIrp to create it. You still use PoStartNext PowerIrp and PoCallDriver when you handle it, though. The request completes after the bus driver stores three sequence numbers in an array you provide. The sequence numbers indicate how many times your device has been put into the D1, D2, and D3 states. When you re later called upon to restore power, you create and send another IRP_MN_POWER_SEQUENCE request to obtain a new set of sequence numbers. If the new set is the same as the set you captured at power-down time, you know that no state change has occurred and that you can bypass whatever expensive process would be required to restore power.

Since IRP_MN_POWER_SEQUENCE simply optimizes a process that will work without the optimization, you needn t use it. Furthermore, the bus driver needn t support it, and you shouldn t treat failure of a power-sequence request as indicative of any sort of error. The GENERIC sample in the companion content actually includes code to use the optimization, but I didn t want to further complicate the textual discussion of the state machine by showing it here.

Категории