Êíèãà: Writing Windows WDM Device Drivers

Handling Set Power IRPs

Handling Set Power IRPs

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);
}

Setting System Power States

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);
}

Setting Device Power States

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;
}

SetPowerState

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);
}

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.232. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 1
ïîäåëèòüñÿ
Ââåðõ Âíèç