12.4 Points to Consider In Handling Cancelable IRPs
Keep the following points in mind when implementing a Cancel routine and
handling cancelable IRPs:
-
A Cancel routine is called with the cancel spin lock already held, so it must
call IoReleaseCancelSpinLock before it returns control. It must not
call IoAcquireCancelSpinLock unless it calls IoReleaseCancelSpinLock
first, and it must make a reciprocal call to IoReleaseCancelSpinLock
for each call it makes to IoAcquireCancelSpinLock.
-
Unless a driver manages its own internal queue(s) 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.
Such a 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 driver does manage its own internal queue of IRPs and has a Cancel
routine, the queue must be protected by an executive spin lock. Such a queue
should be an interlocked queue so the driver can use the ExInterlocked..List
routines.
-
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.
-
Provided that 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 passes IRPs on for further processing by other driver
routines when an IRP might be held in a cancelable state must call IoSetCancelRoutine
to set its entry point for the Cancel routine in the IRP. Only then can that
driver routine call any support routine that causes the IRP to be held in a
cancelable state, 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 cancelled before it begins operations to
satisfy the request. Such a routine must call IoSetCancelRoutine to
reset its entry point for the Cancel routine to NULL in the . Only then can
that routine begin its I/O processing for the input IRP.
Note that such 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 IRPs in a cancelable state must reset its
Cancel entry point to NULL before it passes an 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 driver(s).
However, such a driver cannot assume that the given IRP will be completed with
STATUS_CANCELLED by lower driver(s).
-
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 can be protected with a driver-allocated and initialized
executive spin lock.
Such a driver should manage its acquisitions and releases of the system cancel
spin lock and its own executive spin lock(s) carefully, and it should be
designed to hold the system cancel spin lock for the shortest possible
interval(s).
-
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 such shared-with-the-ISR state in a
multiprocessor-safe way.
For more information about using spin locks, see Chapter 16. See Chapters 8
and 10, respectively, for more information about ISRs and SynchCritSection
routines.