separate.)

Holding IRPs

When a device is not fully started, a full PnP implementation ought to queue I/O request IRPs.

Consider the various PnP stop messages. These occur when some new hardware has been added to the system or a new device has been plugged in. The PnP Manager may decide that it can only accommodate the new device by reassigning the resources that an existing device currently uses. It does this by issuing a Query Stop message first. If all devices in the stack agree that a stop can take place, then the PnP Manager issues a Stop Device request. If any of the drivers in the stack do not want the device stopped, the PnP Manager issues a Cancel Stop message. It then probably informs the user that there are not enough resources available currently and so a restart is necessary. When the resources have been reassigned, it sends a StartDevice message with the new resource allocations.

During this entire process, it seems reasonable that I/O requests on existing devices carry on as normal. In practice, this means not starting any new requests and holding them in a queue for processing when the device is started again. A user might therefore notice a slight pause in proceedings while resources are reassigned, but I/O requests should not fail.

The DDK documentation recommends that drivers do not start any new I/O requests after a Query Stop or Query Remove message has been received. This lets any following stop or remove request proceed quickly.

The end result is that IRPs ought to be queued when in the Stop Pending, Remove Pending, and Stopped states. In the Wdm2 driver, this means that IRPs ought to be queued when the IODisabled flag is true. However, as stated before, Wdm2 does not hold IRPs, as this is a complicated subject to be covered in Chapter 16.

As shown in Listing 9.3, Wdm2 handles Query Stop message by setting the Paused and IODisabled flags before passing the IRP down the stack. The Query Remove request does the same job. The Cancel Stop and Cancel Remove messages undo these actions by clearing Paused and IODisabled.

Open Handles

What happens if the user asks to remove the Wdm2 device while there are open handles to it?

The PnP Manager sends a Query Remove request to the driver. The simplest approach is to refuse to let a device be removed while there are any open handles. The DDK documentation says that the Query Remove must be failed if 'there are open handles that cannot be closed'.

Wdm2 uses the simple approach. It keeps a count of open handles to a device in the OpenHandleCount variable in the device extension. OpenHandleCount is initialized to zero when the device is created in Wdm2AddDevice. The InterlockedIncrement and InterlockedDecrement routines are used in the Wdm2Create and Wdm2Close dispatch routines, respectively, to maintain this count safely.

InterlockedIncrement(&dx->OpenHandleCount);

Listing 9.6 shows the complete Query Remove handler for Wdm2. If OpenHandleCount is greater than zero, PnpQueryRemoveDeviceHandler simply fails the IRP straightaway. Notice that it does not need to pass the PnP IRP down the stack as it is failing it. Instead, it just completes the IRP with the STATUS_UNSUCCESSFUL status code.

If there are no open handles, Wdm2 sets its Paused and IODisabled flags, as discussed before. However, in this case, PnpQueryRemoveDeviceHandler must pass the IRP down the stack in PnpDefaultHandler to give lower devices a chance to reject the Query Remove IRP.

Listing 9.6 Wdm2 PnpQueryRemoveDeviceHandler

NTSTATUS PnpQueryRemoveDeviceHandler(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PWDM2_DEVICE_EXTENSION dx=(PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension;

 DebugPrintMsg('PnpQueryRemoveDoviceHandler');

 if( dx->OpenHandleCount>0) {

  DebugPrint('PnpQueryRemoveDeviceHandler: %d handles still open', dx->OpenHandleCount);

  return CompleteIrp(Irp, STATUS_UNSUCCESSFUL, 0);

 }

 dx->Paused = true;

 dx->IODisabled = true;

 return PnpDefaultHandler(fdo,Irp);

}

When to Process PnP IRPs

You must be careful to process PnP IRPs at the right time. When a Start Device message is received for a USB device, for example, the USB drivers must enable the device at the bus level first. It is only then that the function drivers above can access the device. In fact, when processing the Start Device message, the drivers must process the message in order, going up the device stack.

Similar considerations apply when processing Stop, Remove, and Surprise Removal messages. In these cases, all the drivers in the stack must process the IRP first, in order going down the stack. Each driver must do whatever it needs to do to stop its device before the lower drivers pull the rug out from under its feet.

Handle the Cancel Remove and Cancel Stop requests on the way up the stack so that all the lower devices have restarted. However, the Wdm2 driver enables requests straightaway and then passes the IRP down the stack for processing.

The other PnP messages are usually processed by drivers on the way down the device stack. However, in a few circumstances, you may wish to see what results the lower drivers have produced.

WDM drivers can process IRPs in both these orders. So far, I have only shown how to process IRPs in order going down the stack. I shall now look at how to process IRPs in the other order.

IRP Completion Routines

As mentioned before, the IoCallDriver routine is used to call another driver. It is important to realize that IoCallDriver may return before the IRP has been completely processed. In this case, IoCallDriver returns STATUS_PENDING.

If a driver wants to process an IRP when all lower drivers have completed processing it, the driver must set a completion routine for the IRP. The completion routine is called when all the lower drivers have finished processing the IRP. The completion routine is called in an arbitrary context. I now show how a completion routine signals that it has been run using a kernel event.

Listing 9.7 shows how the ForwardIrpAndWait routine forwards an IRP to the lower drivers and waits for its completion. As ForwardIrpAndWait waits for the completion routine event to become signalled, it must run at PASSIVE_LEVEL IRQL. Waiting for dispatcher objects, such as events, is covered in full in Chapter 14. PnP IRPs are always called at PASSIVE_LEVEL, so it is safe to call ForwardIrpAndWait.

To set a completion routine, the next IRP stack location must be set up correctly. As described previously, IoCopyCurrentIrpStackLocationToNext copies all the current stack location parameters. Having done this, IoSetCompletionRoutine is used to set the completion routine to the ForwardedIrpCompletionRoutine function. ForwardIrpAndWait is then ready to call the next driver using IoCallDriver.

The last three BOOLEAN parameters of IoSetCompletionRoutine specify the circumstances in which you want the completion routine called. If the first BOOLEAN, InvokeOnSuccess, is TRUE, the completion routine is called if the IRP completes successfully. The other two BOOLEAN parameters, InvokeOnError and InvokeOnCancel, state whether the completion routine should be called if an error is returned or the IRP is cancelled. In ForwardIrpAndWait, I want the completion routine called in all circumstances, so all these parameters are set to TRUE.

ForwardIrpAndWait now has two tasks to perform. The completion routine has to signal when it has run, and the main code must wait for this signal. The signalling mechanism is a kernel event, which is basically the same as its Win32 equivalent. Chapter 14 discusses kernel events in full. The event is initialized to the nonsignalled state using KeInitializeEvent. When the completion routine runs, it simply calls KeSetEvent to set the event into the signalled state. ForwardIrpAndWait uses KeWaitForSingleObject to wait for the event to become signalled.

A completion routine has a standard prototype, passing the device object, the IRP, and a context pointer. In this case, ForwardIrpAndWait uses a pointer to the event as the context pointer. The context pointer is set in the IoSetCompletionRoutine call. When the IRP has been processed by all the lower drivers, ForwardedIrpCompietionRoutine is called. It simply sets the event.

ForwardedIrpCompletionRoutine returns a status of STATUS_MORE_PROCESSING_REQUIRED. This means that the IRP has not been completed by this driver and some other part of the driver will complete it. The only other alternative is to return STATUS_SUCCESS, in which case the IRP continues its journey back up the device stack.

IoCallDriver returns STATUS_PENDING if the IRP has not completed its processing by the lower drivers. If this value is returned, ForwardIrpAndWait must wait for the completion routine to run. Once the event has been set, the call to KeWaitForSingleObject returns. ForwardIrpAndWait retrieves the status returned by the lower drivers from the IRP's IoStatus.Status field.

If IoCallDriver returns any value apart from STATUS_PENDING, this means that the IRP has been processed by all the lower drivers. The completion routine has been run and the event set. However, ForwardIrpAndWait does not need to wait for the event, as it already knows that the IRP has been processed in full.

There is no way to know in advance if IoCallDriver will return a pending status. Therefore, if you want to use an IRP after calling IoCallDriver you must set a completion routine.

Completion routines may run at DISPATCH_LEVEL IRQL or lower in an arbitrary thread context. To be safe, you should assume that it is running at DISPATCH_LEVEL IRQL. This means that the completion routine itself and the context pointer must not be in paged memory. The ForwardIrpAndWait event variable is in the kernel stack. This is normally in non-paged memory. Apparently, the kernel stack can be pageable if a user mode wait is issued. However, as a kernel mode wait is used here, the event memory should be safely nonpaged.

Listing 9.7 Wdm2 Forwarding IRPs and waiting for completion

NTSTATUS Forward IrpAndWait(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 DebugPrintMsg('ForwardIrpAndWait');

 PWDM2_DEVICE_EXTENSION dx=(PWDM2_DEVICE_EXTENSION)fdo->DeviceExtension;

 KEVENT event;

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×