Points to Consider When Canceling IRPs
This section discusses guidelines for implementing a Cancel routine and handling cancelable IRPs. For more information about handling cancelable IRPs, see the Flow of Control for Cancel-Safe IRP Queuing white paper on the Microsoft Windows Hardware Developer Central (WHDC) website.
The I/O manager holds the cancel spin lock any time it calls a driver's Cancel routine. Consequently, every Cancel routine must:
Call IoReleaseCancelSpinLock before it returns control.
Not call IoAcquireCancelSpinLock unless it calls IoReleaseCancelSpinLock first.
Make a reciprocal call to IoReleaseCancelSpinLock for each call it makes to IoAcquireCancelSpinLock.
Each time the Cancel routine calls IoReleaseCancelSpinLock, it must pass the IRQL returned by the most recent call to IoAcquireCancelSpinLock. When releasing the spin lock acquired by the I/O manager (and held when the Cancel routine was called), the Cancel routine must pass Irp->CancelIrql.
A driver must not call outside routines (such as IoCompleteRequest) while holding a spin lock because a deadlock can result.
Unless a driver manages its own internal queues of IRPs, its Cancel routine is called with an incoming IRP that could be either of the following:
The CurrentIrp in the input target device object
An entry in the device queue associated with the target device object
Unless a driver manages its own internal queues of IRPs, its Cancel routine should call KeRemoveEntryDeviceQueue with the input IRP to test whether it is an entry in the device queue associated with the target device object. The driver's Cancel routine cannot call KeRemoveDeviceQueue or KeRemoveByKeyDeviceQueue because it cannot assume that the given IRP is at any particular position in the device queue.
If a Cancel routine is called with an IRP for which the driver has already started I/O processing and the request will be completed soon, the Cancel routine should release the system cancel spin lock and return control.
If the current state of the input IRP is Pending, a Cancel routine must do the following:
Set the input IRP's I/O status block with STATUS_CANCELLED for Status and zero for Information.
Release any spin locks it is holding, including the system cancel spin lock.
Call IoCompleteRequest with the given IRP.
Any driver routine that holds an IRP in a cancelable state must call IoMarkIrpPending and must call IoSetCancelRoutine to set its entry point for the Cancel routine in the IRP. Only then can that driver routine call additional support routines such as IoStartPacket, IoAllocateController, or an ExInterlockedInsert..List routine.
Any driver routine that subsequently processes cancelable IRPs must check whether an IRP has already been canceled before it begins operations to satisfy the request. The routine must call IoSetCancelRoutine to reset its entry point for the Cancel routine to NULL in the IRP. Only then can that routine begin its I/O processing for the input IRP.
A routine might have to reset the entry point for a Cancel routine in an IRP if it, too, passes IRPs on for further processing by other driver routines and those IRPs might be held in a cancelable state.
Any higher-level driver that holds an IRP in a cancelable state must reset its Cancel entry point to NULL before it passes the IRP on to the next-lower driver with IoCallDriver.
Any higher-level driver can call IoCancelIrp with an IRP that it has allocated and passed on for further processing by lower-level drivers. However, such a driver cannot assume that the given IRP will be completed with STATUS_CANCELLED by lower drivers.
A driver can (or must, depending on its design) maintain additional state information in its device extension to track the cancelable status of IRPs. If this state is shared by driver routines running at IRQL <= DISPATCH_LEVEL, the shared data should be protected with a driver-allocated and initialized spin lock.
The driver should manage its acquisitions and releases of the system cancel spin lock and its own spin locks carefully. It should hold the system cancel spin lock for the shortest possible intervals. Before accessing a cancelable IRP, such a driver should always check the return value of IoSetCancelRoutine to determine whether the Cancel routine is already running (or is about to run); if so, it should let the Cancel routine complete the IRP.
If a device driver maintains state information about cancelable IRPs that various driver routines share with its ISR, these other routines must synchronize access to the shared state with the ISR. Only a driver-supplied SynchCritSection routine can access state information that is shared with the ISR in a multiprocessor-safe way.
For more information, see Synchronization Techniques.