12.3 Cancel Routine Functionality

The I/O Manager calls a driver-supplied Cancel routine with an input IRP to be cancelled and a DeviceObject pointer representing the target for the request.

Such an IRP might be one that the driver's DispatchReadWrite routine has queued just as the current Win32 application is being closed by the user. Such an IRP also might be one that a higher-level driver explicitly cancelled, depending on the nature of the underlying device.

When its Cancel routine is called, the input IRP might already be the CurrentIrp in the target device object or might already be in the device queue associated with the target device object if the driver has a StartIo routine. If the driver has no StartIo routine, such a request might be in a driver-managed internal queue of IRPs when its Cancel routine is called. In either case, the I/O Manager resets the Cancel entry point in the incoming IRP to NULL before it calls the Cancel routine with that IRP.

NT driver writers should consider the following implementation guidelines for Cancel routines:

·Because a Cancel routine is always called with the system cancel spin lock held, this routine must not call IoAcquireCancelSpinLock unless it calls IoReleaseCancelSpinLock first.

·A Cancel routine cannot be holding the system cancel spin lock when it returns control. That is, every Cancel routine must call IoReleaseCancelSpinLock at least once before it returns control.

·If it calls IoAcquireCancelSpinLock, a Cancel routine must make the reciprocal call to IoReleaseCancelSpinLock as quickly as possible.

·Never call IoCompleteRequest with an IRP while holding a spin lock.

Attempting to complete an IRP while holding a spin lock can cause deadlocks.

Cancel Routines in Drivers with StartIo Routines

The I/O Manager maintains the CurrentIrp field in a device object only if IRPs are queued in the associated device queue object. That is, the driver has a StartIo routine.

In such a driver, a Cancel routine usually does the following:

1.Tests whether the pointer for the input IRP matches the target device object's CurrentIrp address

If these pointers are equivalent, the Cancel routine calls ReleaseCancelSpinLock, sets the IRP's I/O status block with STATUS_CANCELLED for Status and zero for Information, calls IoStartNextPacket, calls IoCompleteRequest with the IRP, and returns control.

2.Otherwise, the Cancel routine checks whether the input IRP is in the device queue associated with the target device object by calling KeRemoveEntryDeviceQueue with the IRP's Tail.Overlay.DeviceQueueEntry pointer

·If the IRP is in the device queue, calling KeRemoveEntryDeviceQueue removes it from the queue. The Cancel routine calls IoReleaseCancelSpinLock, sets the IRP's I/O status block with STATUS_CANCELLED for Status and zero for Information, calls IoStartNextPacket, calls IoCompleteRequest with the cancelled IRP, and returns control.

·If the IRP is not in the device queue, the Cancel routine calls IoReleaseCancelSpinLock and returns control.

Such a driver's Cancel routine calls KeRemoveEntryDeviceQueue to test whether the IRP is in the device queue because this support routine either removes the given IRP from the device queue or does nothing except return FALSE, indicating that the given entry was not queued. A Cancel routine cannot assume that the input IRP is at any particular position in the device queue, so it cannot call KeRemoveDeviceQueue or KeRemoveByKeyDeviceQueue to compare the pointers to the returned IRP and input IRP.

NT drivers with Cancel routines can handle IRP_MJ_CLEANUP requests as well. For more information about DispatchCleanup routines, see Chapter 6.

Cancel Routines in Drivers without StartIo Routines

The I/O Manager maintains the CurrentIrp field in a device object only if IRPs are queued in the associated device queue object.

If an NT driver manages its own internal queues of IRPs, its Cancel routines can be called with an input IRP that is neither the CurrentIrp for the input target device object nor an IRP in the driver's internal queue. Such a driver must maintain its own state about which IRP is currently being processed and should have a Cancel routine for each of its queues. Such a driver's internal queue should be an interlocked queue because its internal queue must be protected by an executive spin lock.

When such a driver's Cancel routine is called, it usually does the following:

1.Calls IoReleaseCancelSpinLock

2.Acquires the spin lock that protects its interlocked queue and walks the queue to find an IRP with Irp->Cancel set to TRUE

·If it finds such an IRP in the interlocked queue, dequeues it thereby releasing the spin lock protecting the queue, sets the IRP's I/O status block with STATUS_CANCELLED for Status and zero for Information, starts the next queued IRP, calls IoCompleteRequest with the cancelled IRP, and returns control

·Otherwise, releases any spin locks it is holding and returns control

Such a driver usually assumes that I/O processing for the input IRP has already begun if it is not queued.

NT drivers with Cancel routines can handle IRP_MJ_CLEANUP requests as well. For more information about DispatchCleanup routines, see Chapter 6.