means that some nonpaged memory must be allocated for the output data. The output data is copied back to the shared buffer and the temporary memory freed. The RunCmds routine performs this task[39].

If WdmIo has not connected to a hardware interrupt, the IOCTL_PHDICLRUN_CMDS handler in Listing 16.2 simply calls RunCmds. However, if the device extension ConnectedToInterrupt field is true, an interrupt may occur that could scupper any command processing. This problem is overcome by calling RunCmds in the context of a Critical Section. To recap, a Critical Section routine runs at Device IRQL (DIRQL) and so cannot be interrupted (by our interrupt at least).

In this case, the IOCTL_PHDIO_RUN_CMDS handler calls KeSynchronizeExecution to run the RunCmdsSynch routine as a Critical Section. RunCmdsSynch just calls RunCmds with the CanTrace parameter set to false. Both RunCmdsSynch and RunCmds return FALSE if they can not allocate enough memory for the output buffer.

Listing 16.3 RunCmdsSynch and RunCmds

BOOLEAN RunCradsSynch(IN PDEVICE_OBJECT fdo) {

 return RunCmds(fdo, false);

}

BOOLEAN RunCmds(IN PDEVICE_OBJECT fdo, IN bool CanTrace) {

 PWDMIO_DEVICE_EXTENSION dx = (PWDMIO_DEVICE_EXTENSION)fdo->DeviceExtension;

 PIRP Irp = fdo->CurrentIrp;

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

 ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;

 ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;

 PUCHAR Buffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;

 PUCHAR OutBuffer = NULL;

 if (OutputLength>0) {

  OutBuffer = (PUCHAR)ExAllocatePool(NonPagedPool.OutputLength);

  if (OutBuffer==NULL) return FALSE;

 }

 ProcessCmds(dx, Buffer, InputLength, OutBuffer, OutputLength, CanTrace);

 if (OutBuffer!=NULL) {

  RtlMoveMemory(Buffer, OutBuffer, OutputLength);

  ExFreePool(OutBuffer);

 }

 return TRUE;

}

Cancelling Queued IRPs

Chapter 14 explained when Windows tries to cancel IRPs using IRP cancel routines. In addition, Windows issues the Cleanup IRP to cancel IRPs in some circumstances. Chapter 14 also showed how the DebugPrint driver provides a cancel routine for its one queued IRP.

As WdmIo queues IRP, it must also provide cancel routines for these IRPs. In addition, it handles the Cleanup IRP correctly so that IRPs are cancelled if a user mode application closes its file handle with overlapped I/O requests pending.

Queued IRP Cancelling

When considering a strategy for cancelling IRPs, two cases must usually be considered. In the first case, the IRP is still being held in the device queue. The second case is when the IRP has been removed from the device queue and is being processed by StartIo.

The I/O Manager does not know whether an IRP is in the device queue[40]. It simply calls the cancel routine. The cancel routine must determine what to do. If the IRP pointer matches the FDO CurrentIrp field, the IRP is running in StartIo (or in the process of a transfer started by StartIo). Otherwise, the cancel routine must try to remove the IRP from the device queue.

The I/O Manager uses its Cancel spin lock to guard cancelling operations. An IRP's cancel routine is called at DISPATCH_LEVEL IRQL while holding the Cancel spin lock. The IRP CancelIrql field holds the old IRQL that should be passed to IoReleaseCancelSpinLock before the IRP is completed and the cancel routine exits.

I have already mentioned that a device queue includes a spin lock to ensure that all operations on the queue are handled safely in a multiprocessor environment. When cancel routines are involved, the Cancel spin lock must be held. This is to ensure that a Cancel routine is not called on one processor while the IRP is being dequeued on another processor.

WdmIo IRP Cancelling Strategy

The WdmIo driver cancels a queued IRP by removing it from the device queue and completing it with status STATLJS_CANCELLED.

If the IRP is being processed by StartIo, the cancel routine in effect does nothing. Before the cancel routine is called, the I/O Manager sets the IRP's Cancel flag. The code called by WdmIoStartIo checks this Cancel flag every now and then. If it is found to be set, the current operation is abandoned and the IRP is completed with status STATUS_CANCELLED.

The DDK documentation does not say that the Cancel routine has to complete the IRP. In WdmIo, if the IRP is being processed by StartIo, the Cancel routine does not cancel the IRP. The IRP is only cancelled later. This strategy seems to work. See the following section for an alternative technique.

Listing 16.4 shows the WdmIoCancelIrp routine. If the IRP to be cancelled matched the FDO CurrentIrp field, the IRP is being processed by StartIo (or its interrupt driven follow on code). In this case, all WdmIoCancelIrp does is to release the Cancel spin lock and exit.

If the IRP to be cancelled is not the current IRP, KeRemoveEntryDeviceQueue is called to try to remove the IRP from the device queue. The FDO DeviceQueue field holds the device queue list head. The IRP Tail.Overlay.DeviceQueueEntry field holds the list entry. The Cancel spin lock can now be released safely. If the IRP was removed from the queue, UnlockDevice is called and the IRP is completed with status STATUS_CANCELLED.

Listing 16.4 WdmIoCancelIrp routine

VOID WdmIoCancelIrp(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PWDMIO_DEVICE_EXTENSION dx = (PWDMIO_DEVICE_EXTENSION)fdo->DeviceExtension;

 DebugPrint('WdmIoCancelIrp: Cancelling %I', Irp);

 if (Irp==fdo->CurrentIrp) {

  DebugPrintMsg('WdmIoCancelIrp: IRP running in StartIo');

  // IRP is being processed by WdmIoStartIo.

  // Irp->Cancel flag already set.

  // WdmIoStartIo or timeout will detect Cancel flag

  // and cancel IRP in due course

  IoReleaseCancelSpinLock(Irp->CancelIrql);

 } else {

  DebugPrintMsg('WdmIoCancelIrp: IRP in StartIo queue');

  // IRP is still in StartIo device queue.

  // Just dequeue and cancel it. No need to start next IRP.

  BOOLEAN dequeued = KeRemoveEntryDeviceQueue(&fdo->DeviceQueue, &Irp->Tail.Overlay.DeviceQueueEntry);

  IoReleaseCancelSpinLock(Irp->CancelIrql);

  if (dequeued) {

   UnlockDevice(dx);

   CompleteIrp(Irp, STATUS_CANCELLED);

  }

 }

}

Cancel Checking

The code in DeviceIo.cpp makes various checks to see if the I/O Manager has requested that the IRP be cancelled.

At the end of WdmIoStartIo, as shown in Listing 16.2, the status is set to cancelled if the IRP Cancel field has been set. The code at the end of WdmIoStartIo also removes the cancel routine before completing the IRP. It acquires the I/O Manager Cancel spin lock before calling IoSetCancelRoutine with NULL for the cancel routine parameter. Remove a cancel routine before completing an IRP.

The ProcessCmds routine also checks the IRP Cancel field just before it gets the next command. If it is set, processing stops with error code PHDIO_CANCELLED in the output buffer and the IRP is cancelled.

The interrupt handling code also checks the IRP Cancel field, as shown in the next chapter.

Alternative Cancel Strategy

An alternative IRP cancelling strategy is to remove an IRP's cancel routine as soon as it starts being processed in StartIo. The advantage is that the cancel routine can complete the IRP straightaway. The downside is that no cancel routine is available while the IRP is being processed by StartIo or its follow on code.

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

0

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

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