Книга: Writing Windows WDM Device Drivers

Cleanup IRP Handling

Cleanup IRP Handling

The Cleanup IRP is issued to cancel any IRPs that are outstanding when a file handle is closed. Even if the IRPs have cancel routines, they are not called.

To handle it correctly, the driver ought to cancel only IRPs that belong to the correct file handle. Only queued IRPs whose FileObject field matches the Cleanup IRP's stack FileObject field should be cancelled.

The WdmIo Cleanup IRP handler, WdmIoDispatchCleanup, shown in Listing 16.7, does not perform this FileObject check. This makes the code simpler.

WdmIoDispatchCleanup must hold the Cancel spin lock wherever it accesses the device queue. However, you must not be holding the Cancel spin lock when an IRP is completed.

The code keeps extracting IRPs using KeRemoveDeviceQueue until the device queue is empty. A pointer to the IRP is found using the usual CONTAINING_RECORD machinations. The IRP is marked for cancelling and its cancel routine is removed. The Cancel spin lock is released before UnlockDevice is called and the IRP is completed with status STATUS_CANCELLED.

The Cleanup IRP handler then goes on to try to cancel the IRP currently being processed by WdmIoStartIo or its follow-on code. CancelCurrentIrpSynch is called as a Critical Section routine. This returns TRUE if a transfer is in progress (i.e., if the device extension Timeout field is greater than or equal to zero). If a transfer is in progress, WdmIoDpcForIsr is called to complete the IRP with status STATUS_CANCELLED. The Timeout field and WdmIoDpcForIsr are described in the next chapter.

To handle the FileObject check correctly, you must still remove each IRP from the queue in turn. If the IRP's FileObject does not match the Cleanup FileObject, the IRP must be put in a temporary holding queue. At the end, all these IRPs must be reinserted in the main device queue.

Listing 16.7 WdmIo Cleanup IRP handling

NTSTATUS WdmIoDispatchCleanup(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
 PWDMIO_DEVICE_EXTENSION dx = (PWDMIO_DEVICE_EXTENSION)fdo->DeviceExtension;
 DebugPrintMsg("WdmIoDispatchCleanup");
 KIRQL OldIrql;
 IoAcquireCancelSpinLock(&OldIrql);
 // Cancel all IRPs in the I/O Manager maintained queue in device object
 PKDEVICE_QUEUE_ENTRY QueueEntry;
 while((QueueEntry=KeRemoveDeviceQueue(&fdo->DeviceQueue)) != NULL) {
  PIRP CancelIrp = CONTAINING_RECORD(QueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);
  CancelIrp->Cancel = TRUE;
  CancelIrp->CancelIrql = OldIrql;
  CancelIrp->CancelRoutine = NULL;
  IoReleaseCancelSpinLock(OldIrql);
  DebugPrint("WdmIoDispatchCleanup: Cancelling %I", CancelIrp);
  UnlockDevice(dx);
  CompleteIrp(CancelIrp, STATUS_CANCELLED);
  IoAcquireCancelSpinLock(&OldIrql);
 }
 IoReleaseCancelSpinLock(OldIrql);
 // Forceably cancel any in-progress IRP
 if (dx->Timeout!=-1) {
  if (KeSynchronizeExecution(dx->InterruptObject, (PKSYNCHRONIZE_ROUTINE)CancelCurrentIrpSynch, dx)) {
   if (fdo->CurrentIrp!=NULL) {
    DebugPrint("WdmIoDispatchCleanup: Cancelled in-progress IRP %I", fdo->CurrentIrp);
    WdmIoDpcForIsr(NULL, fdo, fdo->CurrentIrp, dx);
   }
  }
 }
 return CompleteIrp(Irp, STATUS_SUCCESS);
}
static BOOLEAN CancelCurrentIrpSynch(IN PWDMIO_DEVICE_EXTENSION dx) {
 if (dx->Timeout==-l) return FALSE;
 dx->Timeout = –1;
 dx->TxStatus = STATUS_CANCELLED;
 return TRUE;
}

Testing, Cancelling, and Cleanup

I amended WdmIoTest so that I could test the WdmIo driver's IRP cancelling and Cleanup IRP handling.

The WdmIoCancel application is substantially the same as that of WdmIoTest and is contained in the book software WdmIoCancel directory. You will have to uncomment some of the code and recompile to undertake some of the tests. The PHDIoCancel application does similar tests for the PHDIo driver.

The tests are designed to be run with the printer switched off, so that Write IRPs do not complete straightaway. WdmIoCancel opens the WdmIo device for overlapped I/O, using the GetDeviceViaInterface01 function. It then issues two overlapped Write requests and does not wait for either of them to complete. It then exits straightaway. This behavior is designed to test the IRP cancelling.

Figure 16.2 shows the DebugPrint output that demonstrates that IRP cancelling works correctly. At points (1) in the diagram, one of the IOCTL IRPs has been issued. They are passed to WdmIoStartIo, which processes them immediately. At point (2), the first Write IRP is issued. At point (3), WdmIoStartIo starts processing this first Write. At point (4), the second Write IRP is issued. As the first Write is still in progress (the printer being off), this second Write IRP is queued.

Point (5) is where it gets interesting. WdmIoCancel has just exited. The I/O Manager tries to cancel all pending IRPs. It calls the cancel routine of the second Write. WdmIoCancelIrp finds that it is not the current IRP. It therefore removes it from the StartIo queue and completes the IRP with a cancelled status.

The I/O Manager then calls the cancel routine of the first Write IRP at point (6). WdmIoCancelIrp finds that this IRP is the current IRP. In this design, WdmIoCancelIrp simply exits. In due course, the Timeout1s time-out routine runs. Timeout1s finds that the IRP Cancel flag is set. The WdmIoDpcForIsr routine is called to complete the IRP with a cancelled status.

Finally, the Cleanup IRP is issued at point (7). However, this finds that it has no work to do as the device queue is empty and there is no interrupt driven I/O in progress.

If you look carefully at the DebugPrint output, you will notice that the IRP pointer for the first four IRPs issued is the same, 0xC8548180. The I/O Manager is obviously reusing the IRP structures from its pool of available IRPs. The second write needs another IRP structure, which is at 0xC17C3E00.

Figure 16.2 Cleanup handling DebugPrint output


If you remove the comments around the CloseHandle call at the end of WdmIoCancel, you will be able to test the Cleanup IRP handling correctly. In this case, the IRP cancel routines will not be called. Instead, the Cleanup IRP handler removes the second Write IRP from the device queue. It then goes on to cancel the current IRP (i.e., the first Write).

Finally, the WdmIoCancel code contains a commented out CancelIO call after the second Write is issued. Uncomment this and recompile to check that this cancels the Write IRPs correctly.

Оглавление книги


Генерация: 1.103. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз