52964.fb2
This chapter investigates how to support Power Management in WDM device drivers. NT style drivers in Windows 2000 and Windows 98 can also support Power Management. A device can change its power usage in response to system power state changes, and can reduce its own power usage when it has been idle for a while. A sleeping device can wake the system up, such as when a modem receives an incoming call.
The Power Management aspects of the example Wdm2 driver are explained. The Win32 application Wdm2Power can be used to test Power Management facilities.
Device drivers play a crucial role in helping Windows make the most of the available power resources. This means reducing power consumption in low power situations, such as when running on batteries. Drivers that support power management can also help to minimize startup and shutdown times. Finally, turning off some devices reduces system noise and turning off the display can save a monitor screen.
Most desk-based computers only run on AC power. However, they may have one or more Uninterruptible Power Supplies (UPS) with batteries to provide power in case of emergencies when main power is lost. Most portable computers are able to run off both battery (DC) and AC power.
The system should detect when the computer is running from battery power and reduce power consumption wherever possible. If not in use, it should suspend itself into a standby sleep mode. When running from batteries, the system should shut itself down in an orderly fashion when the battery condition becomes critical. When AC power is available, batteries should be recharged, if possible. When devices are idle for a certain amount of time, they should turn themselves off to reduce noise and power consumption, even when running from AC power.
OnNow is a term for a PC that appears to be off but is always on, so it can respond immediately to user or other requests. When a computer is sleeping like this, it must shut down all inessential devices. Make sure that your drivers cooperate in this task to save energy. An alternative for energy-conscious speed freaks in Windows 2000 is to make their computers hibernate, a state in which a memory image is stored on disk to make startup quicker.
Obviously, some of these desired features are implemented in hardware, such as smooth switching between AC and DC power. However, Windows' Power Manager can take part in the decision making process if the hardware meets the Advanced Configuration and Power Interface (ACPI) specification, found at www.teleport.com/~acpi[25].
Most drivers do not need to interact with ACPI. Instead, the Power Manager and Plug and Play Manager use the ACPI facilities to manage system power and enumerate hardware devices. Non-ACPI systems use a BIOS enumerator to find all PnP devices.
For devices described by an ACPI BIOS, the Power Manager inserts an ACPI filter between the bus driver and higher-level drivers. This ACPI filter driver powers devices on and off according to its power policy. The ACPI filter is transparent to other drivers and is not present on non-ACPI machines. In addition, an ACPI bus driver implements the Plug and Play root device for the whole computer, as described in Chapter 8.
The Control Panel Power Management applet is where users change their power options. The settings here determine the time delay before Windows goes into standby, turns off hard disks, etc. In W2000, you can enable its Hibernate option.
User mode applications might be interested in receiving power events. If an application does background processing, it should stop this work if the system goes to sleep. Alternatively, it can insist that the system does not sleep.
The Wdm2Power application in the Wdm2\Power directory of the book software shows how to handle the following aspects of Power Management in Win32.
• Capturing WM_POWERBROADCAST messages
• Attempting to suspend or hibernate the system with SetSystemPowerState.
• Getting the system power status using GetSystemPowerStatus.
• Getting the hard disk status using GetDevicePowerState.
You may find Wdm2Power useful when testing the Power Management capabilities of your driver. Figure 10.1 shows how Wdm2Power looks in Windows 98 on a desktop system.
I found that it was necessary to alter some VC++ 5 settings to be able to compile Wdm2Power. I needed to use the Platform SDK standard include files and libraries. This meant altering the Directories in the Tools+Options menu. I had to put the Platform SDK include directory at the head of the "Include files" directories listing (i.e., D:\MSSDK\INCLUDE on my PC). Similarly, I had to put its library directory at the head of the "Library files" directories listing (e.g., D:\MSSDK\LIB). This may effect your other projects, so change these settings back afterwards.
Figure 10.1 Example Wdm2Power output
The system broadcasts WM_POWERBROADCAST messages to indicate various power management events or requests. In the Wdm2Power MFC dialog box application these are handled in the OnPowerBroadcast method in Wdm2PowerDlg.cpp. It simply adds the event information to the window list box.
Both the Suspend and Hibernate options eventually call the SetSystemPowerState function in the SuspendOrHibernate dialog method. In W2000 you have to enable the shutdown privilege for this process. The Hibernate option is only available in Windows 2000.
Wdm2Poiver sets a timer that calls its GetPowerStatus method every second. This calls the GetSystemPowerStatus function to get the system power state and GetDevicePowerState to determine if Wdm2Power's disk has been spun down. On my system, GetDevicePowerState always returned FALSE, which I presume means that the file system does not handle the IOCTL command IOCTL_GET_DEVICE_POWER_STATUS.
Win32 applications can also ask that the system not be put to sleep using the SetThreadExecutionState function. Alternatively, they can request that the lowest latency sleep be used using RegisterWakeupLatency. Finally, they can request that the system wake up at a certain time or in response to a device event, such as a modem ring.
The Power Manager uses battery miniclass drivers to provide an interface to batteries. These miniclass drivers must fit in with the specification given in the DDK documentation. For example, it must register its various callback entry points using BatteryClassInitializeDevice.
Battery miniclass drivers must include routines to support PnP and to support battery management and monitoring.
The rest of this chapter looks at how device drivers handle Power Management. Some classes of device ought to have certain Power Management characteristics. These details are found in the Device Class Power Management specification.
System power states indicate the overall energy usage of a whole system, while a device power state says how much energy an individual device is using. Even though the system may be fully powered up, a device can power itself down. For example, if a battery-operated computer is fully on but the hard disk is not being used, the disk driver may reasonably decide to power the disk down to save energy. Conversely, a sleeping computer may keep its modem powered up if it is waiting for incoming faxes.
Windows defines six system power states and four device power states. Please be very clear about whether you are referring to system or device power states.
A POWER_STATE variable represents either a system or device power state. The POWER_STATE typedef is a union of the two enum typedefs, SYSTEM_POWER_STATE and DEVICE_POWER_STATE.
typedef union _POWER_STATE {
SYSTEM_POWER_STATE SystemState;
DEVICE_POWER_STATE DeviceState;
} POWER_STATE, *PPOWER__STATE;
System Power States
Table 10.1 shows the system power states along with the SYSTEM_POWER_STATE enum names and values. Sleeping state S1 has the lowest latency so the system can return to the fully on state in the quickest time possible. States S2 and S3 gradually increase power up latency and decrease power consumption.
Table 10.1 System power states
ACPI State | Description | Enum name |
---|---|---|
S0 | Working/Fully on | PowerSystemWorking(1) |
S1 | Sleeping | PowerSystemSleeping1(2) |
S2 | Sleeping | PowerSystemSleeping2(3) |
S3 | Sleeping | PowerSystemSleeping3(4) |
S4 | Hibernating: Off, except for trickle current to the power button and similar devices. | PowerSystemHibernate(5) |
S5 | Shutdown/Off | PowerSystemShutdown(6) |
Windows moves to and from only S0. There are never changes between the states S1 to S5 (e.g., from S4 to S2). Windows always assumes that it can power up to S0, so it does not have to ask drivers for permission (using the IRP_MN_QUERY_POWER request).
The IRP stack Parameters.Power.ShutdownType value gives extra information about system state changes. It is particularly useful for transitions to S5. PowerActionShutdownReset indicates a reboot while PowerActionShutdownOff means the computer is being switched off.
Device Power States
Table 10.2 shows the available device power states along with the DEVICE_POWER_STATE enum names and values. Again, state D1 has lower latency than D2.
Table 10.2 Device power states
State | Description | Enum name |
---|---|---|
D0 | Fully working | PowerDeviceD0(1) |
D1 | Sleeping | PowerDeviceD1(2) |
D2 | Sleeping | PowerDeviceD2(3) |
D3 | Off | PowerDeviceD3(4) |
Both the state values increase from 1 the sleepier they are.
A device need not support all the device states. DO for on and D3 for off are a basic minimum. A device is not limited in its state transitions, so a change from D1 to D3 is possible.
Later, I describe how each device (or its bus driver) provides a table for device states for each system state.
A driver's Power Management routines revolve around the Power IRP, IRP_MJ_POWER, by both handling it and generating it when necessary. There are four minor function code variants of the Power IRP shown in Table 10.3.
Table 10.3 IRP_MN_POWER minor function codes
Minor function code | Description |
---|---|
IRP_MN_SET_POWER | Set system or device power state |
IRP_MN_QUERY_POWER | Ask if a system or device state change is OK |
IRP_MN_WAIT_WAKE | Wake computer in response to an external event |
IRP_MN_POWER_SEQUENCE | Send this IRP to determine whether your device actuallyentered a specific power state |
The Power Manager maintains a separate internal queue of Power IRPs. This ensures that there is only one Set System Power IRP being processed in the system. It also ensures that there is only one Set Device Power IRP running for each device. These rules ensure that power transitions are handled smoothly. As each device is powered up, it may require an inrush of energy. Handling Power IRPs one at a time ensures that the peak power required is kept as low as possible.
As the Power Manager has its own queue of IRPs, do not use the standard I/O Manager routines when handling IRPs. Use PoCallDriver instead of IoCallDriver to call the next driver down the stack.
You must tell the Power Manager when you have finished processing a power IRP so that it can start the next. If you are simply passing an IRP down the stack (with no completion routine) then you should call the PoStartNextPowerIrp function before you skip or copy the current IRP stack location. If you use a completion routine, it must also usually call PoStartNextPowerIrp. Full examples of these calls are given later.
A Query Power IRP request is sent to see if a state change is acceptable to all the drivers in the device stack. A Set Power IRP is then issued to actually make the change.
You can send yourself a Set Device Power State IRP at any time. However, only the Power Manager can send Set System Power State IRPs. To process a Set System Power State IRP, you must send yourself a Set Device Power State IRP. That's right — you must ask for a Power IRP to be sent to the top of your device stack.
Handling Device Power IRPs
Handling a Set Device Power State is relatively straightforward. You must power down your device before all the lower drivers. Conversely, power your device up after all the lower drivers have powered up. This means setting a completion routine and doing the power up there.
Handling System Power IRPs
Handling a Set System Power State is a different kettle of fish. If you receive a Set System Power IRP, you must first determine the equivalent device power state. The Wdm2 driver is put in the fully on device state D0 for the fully on system state S0. For all other system states, the Wdm2 device state is fully off, D3.
If the current device state is not the same as the required device state, you must act to change it. As before, powering down requires that you change the device state before you pass the Set System Power State down to the lower drivers. Power up after all the lower drivers have had their say.
You must change device power state by issuing a Set Device Power State IRP to yourself. You must then wait for this IRP to complete. You can then continue processing the Set system state IRP.
Same System Power
Suppose you receive a Set System Power State IRP. You must work out the corresponding device power state. If your device is already at this power level, then all you need to do is pass the IRP to the lower drivers.
System Powering Down
If you decide that your device needs to power down, then you must send yourself a Set Device Power IRP before you pass down the system IRP. Figure 10.2 illustrates this scenario. It assumes that the first Query Set System Power State IRP has completed OK, at (1).
In the figure, the Set System State IRP handler decides that it must power its device down at (2). It sends a Set Device State IRP to itself, and sets a completion routine so that it knows when this second IRP has been completed.
In due course, your driver will receive the Set Device State IRP (that you sent yourself) at (3). Your driver will decide again that it must power down. It does this at (4) and then sends the IRP down to the lower drivers at (5).
When the Set Device State IRP has been processed by all the lower drivers, the completion routine is called at (6). This signals to the original IRP handler that it can continue, at © again. All this has to do is pass the IRP down to the lower drivers at (7).
Figure 10.2 Power down system processing
System Powering Up
In contrast, Figure 10.3 shows what might happen when your device has to power up to get to the required new system power state. First, a Query Power IRP is sent at (1) asking if a new system state is OK. Then a Set Power IRP is issued for the system power state. The driver determines that it must power up its device. It sets a completion routine and passes the IRP to the lower drivers at (2).
The completion routine for the Set System State sends itself a Set Device State Power IRP at (3), setting a completion routine. When this IRP gets to your driver, it again works out that it must power up. It passes the IRP to the lower drivers at (4) and sets another completion routine. When this completion routine runs at (5), the driver can finally power its device up in whatever way is appropriate at (6).
The completion routine at (7) runs. The original Set System State Power IRPcompletion routine at (3) eventually continues its run. It completes the first IRP.
This example appears complicated, as there are three different IRP completion routines involved. However, the full example given later should make the process clear. The original Set System State IRP needs a completion routine at (3) so that the driver knows when the IRP has been processed by the rest of the stack. This completion routine must send a Set Device State IRP to itself. It needs a completion routine at (7) so that it knows when this IRP has completed its rites of passage. Finally, when the driver processes the Set Device State IRP, it needs a completion routine at (5) so that it knows when the lower drivers have finished processing this IRP.
Figure 10.3 Power up system processing
Some drivers like Wdm1 (and in fact, Wdm2) can happily ignore all Power IRPs. All they have to do is pass all Power IRPs down the stack. Wdm1's Wdm1 Power dispatch routine shown in Listing 10.1 does just this. Note the use of PoCallDriver rather than IoCallDriver. If you do not process Power IRPs, please include a default power handler like Wdm1 Power so that these IRPs reach lower drivers.
Drivers for removable devices should check to see if its media is present; if not, it should call PoStartNextPowerIrp and complete the Power IRP straightaway with a status of STATUS_DELETE_PENDING.
Listing 10.1 Passing on all Power IRPs
NTSTATUS Wdm1Power(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
DebugPrint("Power %I", Irp);
PWDM1_DEVICE_EXTENSION dx = (PWDM1_DEVICE_EXTENSION)fdo->DeviceExtension;
// Just pass to lower driver
PoStartNextPowerIrp(Irp);
IoSkipCurrentIrpStackLocation(Irp);
return PoCallDriver(dx->NextStackDevice, Irp);
}
Noting Device Power State Changes
Suppose a function driver does not handle Power IRPs, but its bus driver does. In this case, the function driver might want to know when its device is powered down and take some appropriate action. Unfortunately, there is no simple call to discover the current device power state. Instead, it must remember the last device power state from a Set Power IRP.
The Power IRP handler can easily be enhanced to note any changes of power state if the minor function code is IRP_MN_SET_POWER. The IRP stack Parameters.Power.Type field is either SystemPowerState or DevicePowerState. The Parameters.Power.State field gives the system or device power state.
The following code snippet shows how to check for a Set Device Power IRP and store the new device power state in a DEVICE_POWER_STATE PowerState field in the device extension.
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
POWER_STATE_TYPE PowerType = IrpStack->Parameters.Power.Type;
POWER_STATE PowerState = IrpStack->Parameters.Power.State;
if (IrpStack->MinorFunction==IRP_MN_SET_POWER && PowerType==DevicePowerState) dx->PowerState = PowerState.DeviceState;
In most device stacks, there is one driver that knows if the device is being used and its power state. This driver is deemed the device power policy owner. There is nothing to stop more than one driver trying to set power policy. In most cases, the first function driver for a device is the power policy owner. However, the bus driver may well play an important part in power management, or even be the power policy owner.
The device power policy owner should call PoSetPowerState to set the current device power state. This device power state is stored for a device by the Power Manager. However, there is no call to retrieve the current device power state, so you must keep a copy of this value.
The Wdm2 driver defines the following two extra fields in its device extension to support Power Management.
DEVICE_POWER_STATE PowerState; // Our device power state
PULONG PowerIdleCounter; // Device idle counter
The AddDevice routine initializes these variables as follows. PoSetPowerState can only be used by drivers to set the device power state. Do not try to set the system power state.
dx->PowerState = PowerDeviceD3;
dx->PowerIdleCounter = NULL;
POWER_STATE NewState;
NewState.DeviceState = dx->PowerState;
PoSetPowerState(fdo, DevicePowerState, NewState);
An AddDevice routine can also set some extra bits in the FDO Flags. If DO_POWER_INRUSH is set, this device needs an inrush of power when it is powered up. Devices with this bit set are powered up one at a time. If the DO_POWER_PAGABLE flag bit is set, the Power handling routines are called at PASSIVE_LEVEL IRQL. Otherwise, they are called at DISPATCH_LEVEL IRQL. Inrush Power IRPs are sent at DISPATCH_LEVEL, so the power routines for these devices must not be pageable.
The device power policy owner must process Query and Set Power IRPs using the technique described previously. The following Wdm2 example shows how to do this in detail.
It is recommended that a device is fully powered up by the time the Plug and Play StartDevice IRP has completed. As you will get only the full list of resources with this Start Device IRP, you will have to power up the device once the resources have been found and allocated. There might well be no need to power down a device during resource reassignment.
You may decide that the device can be powered off when it has been idle for a while. The easiest way to do this is to call the PoRegisterDeviceForIdleDetection function. After a time-out, the Power Manager sends a Set device power IRP to power down the device. Alternatively, you can generate a similar IRP yourself whenever you deem it appropriate. To avoid an idle time-out, you must keep resetting an idle counter using PoSetDeviceBusy every time an I/O operation takes place.
The following line at the end of AddDevice shows how to register for idle notification. The two ULONG parameters are the time-outs in seconds. The first applies when the system is trying to conserve power. The second applies when best performance is the goal. The final parameter to PoRegisterDeviceForIdleDetection is the device state that must be set when a device powers down. Wdm2 powers down to device state D3. Call PoRegisterDeviceForIdleDetection with zeroes for the time-outs to disable idle notification.
dx->PowerIdleCounter = PoRegisterDeviceForIdleDetection(pdo, 30, 60, PowerDeviceD3);
The following code snippet shows how PoSetDeviceBusy is used to reset the idle counter at the beginning of each I/O operation.
if (dx->PowerIdleCounter) PoSetDeviceBusy(dx->PowerIdleCounter);
At the beginning of every I/O operation, you must check to see if the device is powered up. If it is not, most drivers will simply power up the device before starting the request. An alternative strategy is to queue the IRP until the device powers up at some other time.
Make sure that you do not accidentally power down a device during a lengthy I/O operation.
The parameters on the stack of a Set Power IRP indicate which system or device power state is being set.
If Parameters.Power.Type is SystemPowerState, Parameters.Power.State.SystemState is the desired system state. If Parameters.Power.Type is DevicePowerState, Parameters.Power.State.DeviceState is the desired device state. The Parameters.Power.ShutdownType field gives more information about system Shutdown messages. A final parameter, Parameters.Power.SystemContext, is not currently used.
Listing 10.2 shows the main Wdm2 handler for all Power IRPs, Wdm2Power. If this is a Set Power IRP, PowerSetPower is called. Other Power IRPs are passed to DefaultPowerHandler, which simply hands them down to the lower drivers in the same way as Wdm1. All Power IRPs are completed with an error status if the device is not in the started PnP state.
Listing 10.2 Basic Wdm2 power handling
NTSTATUS Wdm2Power(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
PWDM2_DEVICE_EXTENSION dx = (PWDM2_DEVICE_EXTENSI0N)fdo->DeviceExtension;
if (dx->IODisabled) return CompleteIrp(Irp, STATUS_DEVICE_NOT_CONNECTED, 0);
if (!LockDevice(dx)) return CompleteIrp(Irp, STATUS_DELETE_PENDING, 0);
NTSTATUS status = STATUS_SUCCESS;
DebugPrint("Power %I",Irp);
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG MinorFunction = IrpStack->MinorFunction;
if (MinorFunction==IRP_MN_SET_POWER) status = PowerSetPower(dx,Irp);
else status = DefaultPowerHandler(dx,Irp);
UnlockDevice(dx);
return status;
}
NTSTATUS DefaultPowerHandler(IN PWDM2_DEVICE_EXTENSION dx, IN PIRP Irp) {
DebugPrintMsg("DefaultPowerHandler");
// Just pass to lower driver
PoStartNextPowerIrp(Irp);
IoSkipCurrentlrpStackLocation(Irp);
return PoCallDriver(dx->NextStackDevice, Irp);
}
Listing 10.3 shows the PowerSetPower routine. As can be seen, there are two main sections to this code. The first handles setting system power states and the second sets a device power state.
Listing 10.3 PowerSetPower routine
NTSTATUS PowerSetPower(IN PWDM2_DEVICE_EXTENSION dx, IN PIRP Irp) {
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_IOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
POWER_STATE_TYPE PowerType = IrpStack->Parameters.Power.Type;
POWER_STATE PowerState = IrpStack->Parameters.Power.State;
////////////////////////////////////////////////////////////////////
// Set System Power
if (PowerType==SystemPowerState) {
DEVICE_POWER_STATE DesiredDevicePowerState = (PowerState.SystemState<=PowerSystemWorking ? PowerDeviceD0 : PowerDeviceD3);
if (DesiredDevicePowerState<dx->PowerState) {
// This system state means we have to increase device power
DebugPrint("System state %d. Increase device power to %d", PowerState.SystemState, DesiredDevicePowerState);
// Process on way up stack…
PoStartNextPowerIrp(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, OnCompleteIncreaseSystemPower, NULL, TRUE, TRUE, TRUE);
return PoCallDriver(dx->NextStackDevice, Irp);
} else if(DesiredDevicePowerState>dx->PowerState) {
// This system state means we have to decrease device power
DebugPrint("System state %d. Decrease device power to %d",
PowerState.SystemState, DesiredDevicePowerState);
// Send power down request to device
status = SendDeviceSetPower(dx, DesiredDevicePowerState);
if (!NT_SUCCESS(status)) {
PoStartNextPowerIrp(Irp);
return CompleteIrp(Irp, status, 0);
}
}
}
////////////////////////////////////////////////////////////////////
// Set Device Power
else if (PowerType==DevicePowerState) {
DEVICE_POWER_STATE DesiredDevicePowerState = PowerState.DeviceState;
if (DesiredDevicePowerState<dx->PowerState) {
// Increase device power state
DebugPrint("Increase device power to %d", DesiredDevicePowerState);
// Process on way up stack…
PoStartNextPowerIrp(Irp);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp, OnCompleteIncreaseDevicePower, NULL,TRUE, TRUE, TRUE);
return PoCallDriver(dx->NextStackDevice, Irp);
} else if (DesiredDevicePowerState>dx->PowerState) {
// Decrease device power state
DebugPrint("Decrease device power to %d", DesiredDevicePowerState);
// Set power state
SetPowerState(dx,PawerState.DeviceState);
}
}
////////////////////////////////////////////////////////////////////
// Unrecognised Set Power
#if DBG
else DebugPrint("Power: unrecognised power type %d",PowerType);
#endif
// Finally pass to lower drivers return DefaultPowerHandler(dx, Irp);
}
When a new system state is being set, the first task is determining to which device power state this corresponds. As mentioned previously, in Wdm2 the fully on D0 device state is only used for the fully on SO system power state. For all other system power states, the fully off D3 device state is used.
PowerSetPower works out the corresponding device power state. If this matches the current device power state, saved in the device extension, no more need be done, apart from falling through to call DefaultPowerHandler.
If the device needs to be powered up, the situation depicted in Figure 10.3 applies. The Set System Power IRP handler can start the device only after the lower drivers have processed the request. PowerSetPower installs OnCompleteIncreaseSystemPower as the completion routine.
Listing 10.4 shows how OnCompleteIncreaseSystemPower eventually calls SendDeviceSetPower to send a Set Device Power IRP to itself. OnCompleteIncreaseSystemPower works out the desired device power state again. It is possible that a Set Device Power IRP has arrived in the meantime to power up the device.
If the device needs to be powered down, as per Figure 10.2, PowerSetPower calls SendDeviceSetPower to send a Set device Power request first. It can then simply let DefaultPowerHandler pass the IRP down to the lower drivers.
Listing 10.4 OnCompleteIncreaseSystemPower routine
NTSTATUS OnCompleteIncreaseSystemPower(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PVOID context) {
PWDM2_DEVICE_EXTENSI0N dx = (PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension;
if (Irp->PendingReturned) IoMarkIrpPending(Irp);
NTSTATUS status = Irp->IoStatus.Status;
DebugPrint("OnCompleteIncreaseSystemPower %x", status);
if(!NT_SUCCESS(status)) return status;
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
POWER_STATE PowerState = IrpStack->Parameters.Power.State;
DEVICE_POWER_STATE DesiredDevicePowerState = (PowerState.SystemState<=PowerSystemWorking ? PowerDeviceD0 : PowerDeviceD3);
if (DesiredDevicePowerState<dx->PowerState) status = SendDeviceSetPower(dx, DesiredDevicePowerState);
PoStartNextPowerIrp(Irp);
return status;
}
Sending a Set Device Power IRP
The Set System Power IRP has to send a Set Device Power IRP to itself to change its own device power state.
SendDeviceSetPower in Listing 10.5 does this job. The PoRequestPowerIrp call does most of the useful work. It allocates the required type of Power IRP and initializes the IRP stack parameters. You must pass the name of a completion routine and context pointer. Finally, you can receive a pointer to the allocated IRP using the last parameter.
The completion routine should not free the IRP memory. The Power Manager frees the IRP after the completion routine exits. You cannot use the allocated IRP pointer to inspect the results of the IRP.
The completion routine OnCompleteDeviceSetPower has a different prototype to normal but does the same job. SendDeviceSetPower uses an SDSP structure on the kernel stack as the context pointer for the completion routine. This contains an initialized event that is set into the signalled state by the completion routine and a field to return the IRP completion status.
When you send a Power IRP using PoRequestPowerIrp you must not start the next Power IRP using PoStartNextPowerIrp in the completion routine[26].
As usual, SendDeviceSetPower waits for the completion event to become signalled using KeWaitForSingleObject. It retrieves the Set device Power completion status from the SDSP structure.
I found a twist in this tale when running this routine in Windows 98. On my computer, the Set device Power state was sent and seemed to complete successfully. However, it was not actually received by the Wdm2 driver. The last section of code calls SetPowerState if the device power state is not what it should be. SetPowerState actually changes the device's power state. This looks like a bug in Windows 98.
Finally, on this topic, the DDK documentation says that you cannot wait using events for your own Power IRP to complete. SendDeviceSetPower makes a new IRP and so it is all right for it to wait using an event. However, when PowerSetPower sets OnCompleteIncreaseSystemPower as the completion routine of the IRP it cannot wait using an event. Luckily "forward and wait" processing is not needed in this case.
Listing 10.5 SendDeviceSetPower routine
typedef struct _SDSP {
KEVENT event;
NTSTATUS Status;
} SDSP, *PSDSP;
NTSTATUS SendDeviceSetPower(IN PWDM2_DEVICE_EXTENSION dx, IN DEVICE_POWER_STATE NewDevicePowerState) {
DebugPrint("SendDeviceSetPower to %d", NewDevicePowerState);
POWER_STATE NewState;
NewState.DeviceState = NewDevicePowerState;
SDSP sdsp;
KeInitializeEvent(&sdsp.event, NotificationEvent, FALSE);
sdsp.Status = STATUS_SUCCESS;
NTSTATUS status = PoRequestPowerIrp(dx->pdo, IRP_MN_SET_POWER, NewState, OnCompleteDeviceSetPower, &sdsp, NULL);
if (status==STATUS_PENDING) {
KeWaitForSingleObject(&sdsp.event, Executive, KernelMode, FALSE, NULL);
status = sdsp.Status;
}
// Cope with W98 not passing power irp to us
if (NT_SUCCESS(status) && dx->PowerState!=NewDevicePowerState) {
DebugPrintMsg("SendDeviceSetPower: Device state not set properly by us. Setting again");
SetPowerState(dx,NewDevicePowerState);
}
return status;
}
VOID OnCompleteDeviceSetPower(IN PDEVICE_OBJECT fdo, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus) {
DebugPrintMsg("OnCompleteDeviceSetPower");
PSDSP psdsp = (PSDSP)Context;
psdsp->Status = IoStatus->Status;
KeSetEvent(&psdsp->event, 0, FALSE);
}
PowerSetPower in Listing 10.3 handles Set device Power IRPs.
If the device must be powered up, the power state must be changed after all the lower drivers have processed the Set device Power IRP. As before, a completion routine, OnCompleteIncreaseDevicePower, is set and the lower drivers are called. OnCompleteIncreaseDevicePower shown in Listing 10.6 eventually calls SetPowerState to change the device power state.
If the device must be powered down, PowerSetPower can simply call SetPowerState and pass the IRP to the lower drivers.
Listing 10.6 OnCompleteIncreaseDevicePower routine
NTSTATUS OnCompleteIncreaseDevicePower(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PVOID context) {
PWDM2_DEVICE_EXTENSION dx = (PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension;
if (Irp->PendingReturned) IoMarkIrpPending(Irp);
NTSTATUS status = Irp->IoStatus.Status;
DebugPrint("OnCompleteIncreaseDevicePower %x",status);
if( !NT_SUCCESS(status)) return status;
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
POWER_STATE PowerState = IrpStack->Parameters.Power.State; SetPowerState(dx.PowerState.DeviceState);
PoStartNextPowerIrp(Irp);
return status;
}
Listing 10.7 shows the SetPowerState routine in DeviceIo.cpp that actually changes the device power state. SetPowerState simply stores the new device state in the Wdm2 device extension and then calls PoSetPowerState to inform the Power Manager.
A SetPowerState routine for a real device will need to interact with the device to change the power state. This will need to synchronize itself with any other hardware activities. A Critical Section routine called via KeSynchronizeExecution might be used for this purpose.
If powering a device down, you may need to store some context information that the device normally holds. For example, you could store "the current volume settings" for a set of speakers. When power is restored, pass the stored volume settings to the device.
As stated before, you may want to queue up I/O request IRPs while a device is powered down.
Listing 10.7 SetPowerState routine
void SetPowerState(IN PWDM2_DEVICE_EXTENSION dx, IN DEVICE_POWER_STATE NewDevicePowerState) {
DebugPrint("SetPowerState %d", NewDevicePowerState);
// Use KeSynchronizeExecution if necessary
// to actually change power in device
// Remember new state
dx->PowerState = NewDevicePowerState;
POWER_STATE NewState;
NewState.DeviceState = NewDevicePowerState;
PoSetPowerState(dx->fdo, DevicePowerState, NewState);
}
Each Wdm2 I/O request dispatch routine must check that the Wdm2 device is powered up. Listing 10.8 shows how Wdm2Write calls PowerUpDevice to power the device up if necessary by calling SendDeviceSetPower. Finally, PowerUpDevice resets the idle counter using PoSetDeviceBusy.
Listing 10.8 Dispatch routine power handling
NTSTATUS Wdm2Write(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
// …
NTSTATUS status = PowerUpDevice(fdo);
if (!NT_SUCCESS(status)) return CompleteIrp(Irp, status, 0);
// …
}
NTSTATUS PowerUpDevice(IN PDEVICE_OBJECT fdo) {
PWDM2_DEVICE_EXTENSION dx = (PWDH2_DEVICE_EXTENSION)fdo->DeviceExtension;
// If need be, increase power
if (dx->PowerState>PowerDeviceD0) {
NTSTATUS status = SendDeviceSetPower(dx, PowerDeviceD0);
if (!NT_SUCCESS(status)) return status;
}
// Zero our idle counter
if (dx->PowerIdleCounter) PoSetDeviceBusy(dx->PowerIdleCounter);
return STATUS_SUCCESS;
}
You can investigate some of Wdm2's power capabilities by inspecting the checked build DebugPrint output using the DebugPrint Monitor. Make sure that you have installed the DebugPrint driver and the checked build of the Wdm2 driver.
Under W2000, the idle detection process can be seen working. After 60 seconds on my AC powered desktop system, the Power Manager sends a Set device Power IRP to power down the device to D3. If you then run the Wdm2Test Win32 application, the first read or write powers the Wdm2 device back up to D0.
On the same computer running Windows 98, no idle Set device Power IRPs were seen. This must be another manifestation of the bug described earlier. I changed the code so that Wdm2 started in a powered off state. When Wdm2Test was run, the Set device Power IRP (to power the device up) completed successfully, but the driver was not sent the Power IRP. This is why SendDeviceSetPower shown in Listing 10.5 checks that the device has really changed power state, and forces the change if necessary.
I cannot explain why Set Power IRPs are not received by the Wdm2 driver in Windows 98. Perhaps the idle detection process is working, but the Set device IRP is simply not seen by the driver.
If your system can sleep or hibernate then use the Wdm2Power application to test these features.
Another piece of the Power Management puzzle is the IRP_MN_QUERY_CAPABILITIES Plug and Play IRP minor function code. The bus driver usually fills in a DEVICE_CAPABILITIES structure. Several of these fields relate to Power Management. Function or filter drivers can inspect or modify the values set by the bus driver.
Listing 10.9 shows how PnpQueryCapabilitiesHandler handles the Query Capabilities PnP IRP for Wdm2. It first forwards the IRP down the stack and waits for it to complete. It then alters the DEVICE_CAPABILITIES structure, if necessary.
The DeviceState field is an array that indicates the corresponding device power state for each system power state. Or more precisely, it specifies the most powered state that a device can be in at a system power level. For example, when the system is fully on in S0, the device can be fully on in D0 or it may have idled into D3.
PnpQueryCapabilitiesHandler ensures that the correct "most powered device state" is specified by the bus driver. The SetMostPoweredState macro checks to see if a device state has been set and ups the DeviceState entry if appropriate.
Listing 10.9 PnpQueryCapabilitiesHandler routine
#define SetMostPoweredState(SystemState, OurDeviceState) \
dps = deviceCapabilities->DeviceState[SystemState]; \
if (dps==PowerDeviceUnspecified || dps>OurDeviceState) \
deviceCapabilities->DeviceState[SystemState] = OurDeviceState
NTSTATUS PnpQueryCapabilitiesHandler(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
NTSTATUS status = ForwardIrpAndWait(fdo, Irp);
if (NT_SUCCESS(status)) {
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_CAPABILITIES deviceCapabilities;
deviceCapabilities = IrpStack-Parameters->DeviceCapabilities.Capabilities;
#if DBG
for (int ds=PowerSystemWorking; ds<PowerSystemMaximum; ds++)
DebugPrint("Capabilities from bus: DeviceState[%d]=%d",
ds, deviceCapabilities->DeviceState[ds]);
#endif
DEVICE_POWER_STATE dps;
SetMostPoweredState(PowerSystemWorking, PowerDeviceD0); // S0
SetMostPoweredState(PowerSystemSleeping1, PowerDeviceD3); // S1
SetMostPoweredState(PowerSystemSleeping2, PowerDeviceD3); // S2
SetMostPoweredState(PowerSystemSleeping3, PowerDeviceD3); // S3
SetMostPoweredState(PowerSystemHibernate, PowerDeviceD3); // S4
SetMostPoweredState(PowerSystemShutdown, PowerDeviceD3); // S5
}
return CompleteIrp(Irp, status, Irp->IoStatus.Information);
}
The bus driver for the Wdm2 device sets different values in DeviceState in Windows 98 and Windows 2000 Beta 2, as shown in Table 10.4. Windows 98 sets the most powered device state for S0–S4 to D0 and for S5 to D3. The most powered values for Wdm2 do not alter these values. In contrast, Windows 2000 Beta 2 sets the most powered device state for S1-S3 to D3 but does not set values for the other system states. PnpQueryCapabilitiesHandler will, therefore, modify DeviceState tor S0, S4, and S5.
Table 10.4 DeviceState values
System state | S0 | S1 | S2 | S3 | S4 | S5 |
---|---|---|---|---|---|---|
W98 | D0 | D0 | D0 | D0 | D0 | D3 |
W2000 | ? | D3 | D3 | D3 | ? | ? |
Wdm2 | D0 | D3 | D3 | D3 | D3 | D3 |
The SystemWake and DeviceWake fields in the DEVICE_CAPABILITIES structure indicate the lowest powered state from which a device can wake the system. These fields are set to PowerSystemUndefined (0) if the device cannot wake the system. If necessary, alter the values that the bus driver has set.
The D1Latency, D2Latency, and D3Latency fields indicate the approximate time in 100-microsecond units that the device takes to return to the fully on DO state for each sleeping state. Increase the values set by the bus driver, if necessary.
Finally, note that another PnP IRP, IRP_MN_QUERY_RELATIONS, has a PowerRelations subtype that asks for the PDOs of the devices that are related to the current device. These other devices are powered down when this device powers down.
The basic idea of IRP_MN_WAIT_WAKE Power IRP is quite simple. It lets devices wake the system up from a sleeping state. A classic example is letting an incoming call from a modem wake up the system. Alternatively, a soft power on/off switch could power the system up when it is pressed.
Send an IRP_MN_WAIT_WAKE IRP to yourself using PoRequestPowerIrp before you power down. The bus driver marks the IRP as pending. When the wake event occurs, the bus driver completes the IRP and wakes the system up or powers the device up.
The situation becomes complicated when your bus driver cannot wake up the system. The bus driver FDO must generate another IRP_MN_WAIT_WAKE IRP and send it to its PDO. This continues until the root ACPI driver is reached, which can wake the system up. Each IRP_MN_ WAIT_WAKE IRP is completed in turn by the bus drivers when a wake event is detected.
You must be prepared to cancel an IRP_MN_WAIT_WAKE IRP when a device is removed.
When a device is stopped, the IRP_MN_WAIT_WAKE IRP must be cancelled and sent again when the resources have been reassigned.
A driver can send an IRP_MN_POWER_SEQUENCE IRP to its bus driver (device power policy manager) to see how many times the device has entered the device states D1, D2, and D3. This information is used to see if a device did power down and so needs to be powered up.
A bus driver zeroes these counts when the device is created. It then increments the relevant count every time it moves into that device state. It never zeroes the counts again.
The PoRegisterSystemState kernel call provides a function similar to the SetThreadExecutionState Win32 call. It lets a driver tell the Power Manager that the system is busy, that the display is required, or that a user is present. If successful, PoRegisterSystemState returns a non-NULL pointer. When you are finished, call PoUnregisterSystemState to cancel your busy notification.
The similar function, PoSetSystemState just resets the Power Manager idle counters for these same attributes. Calling PoSetSystemState does not make the settings persist.
These functions are only available in W2000.
A driver can register to receive power notification events on a device tree using the PoRegisterDeviceNotify function. A notification handle is returned that must be passed to PoCancelDeviceNotify to stop receiving power notification events. These functions are only available in W2000.
In Windows 2000, a driver can use a kernel callback object to detect when the system power policy changes or when a sleep or shutdown is imminent. Register a callback routine to receive notification of \Callback\PowerState events.
First, initialise an attribute block using InitializeObjectAttributes. Then obtain a callback object pointer using ExCreateCallback. Finally, register your callback routine using ExRegisterCallback. The arguments passed to your callback routine indicate what power event has happened — see the DDK for details. Call ExUnregisterCallback when you do not wish to receive any more notifications.
You can also register to receive \Callback\SetSystemTime notifications when the system time is changed. Finally, you can create your own callback object; calling ExNotifyCallback runs the callback routines in all the other drivers that have registered for your callback.
Chapter 12 shows that Windows Management Instrumentation (WMI) is a facility that lets users interact with drivers. In fact, it will be just system operators, developers, or network professionals who wish to use WMI to see what is happening in a system, possibly to diagnose a fault condition.
A driver supports WMI by handling the IRP_MJ_SYSTEM_CONTROL IRP. WMI lets drivers make information available to users or receive data from them. The data objects are in structures identified by GUIDs. There are various standard WMI data blocks, but drivers can define their own data blocks, if need be.
The Power Management portion of a driver can use two standard WMI data blocks to let a user enable or disable two features. The MSPower_DeviceEnable data block, identified by GUID_POWER_DEVICE_ENABLE, has an Enable Boolean that a driver should use to determine if it should dynamically power itself down when it is idle. The MSPower_DeviceWakeEnable data block, identified by GUID_POWER_DEVICE_WAKE_ENABLE, has a similar Enable Boolean whose value should be used to enable its Wake feature.
A driver that supports these WMI features may also let their settings be changed using IOCTLs.
GUID_POWER_DEVICE_TIMEOUT is also defined to let the user change the idle timeouts. This feature is not working in W2000 Beta 3.
If you implement WMI for the MSPower_DeviceEnable or MSPower_DeviceWakeEnable data blocks, the driver properties box gains an extra Power Management tab. Figure 10.4 shows how this looks for a Wdm3 device. The Wdm3 driver supports the MSPower_DeviceEnable data block, but not MSPower_DeviceWakeEnable, so only the power down option is available in the Power Management tab.
Figure 10.4 Wdm3 Power Management device properties
This chapter has shown how to handle the various aspects of WDM Power Management. The Wdm2 driver provides a practical illustration of these techniques. The Wdm2Power user mode application can be used to suspend or hibernate a computer.
The next chapter shows how INF files are used to install WDM device drivers.
Note that there are various optional elements to the ACPI, but the PC99 specification makes several of these mandatory.
I think that this is because you are already processing a Power IRP and only one such IRP can be active at a time.