13.1.2 Calling IoSetCompletionRoutine
The most common uses for higher-level drivers’ IoCompletion routines are the following:
·To dispose of IRPs that a higher-level driver has allocated with IoAllocateIrp or IoBuildAsynchronousFsdRequest to send requests to lower drivers
Any higher-level driver that allocates an IRP with either of these support routines must set its IoCompletion routine in such an IRP. The IoCompletion routine must call IoFreeIrp to dispose of these driver-allocated IRPs.
·To reuse an incoming IRP to request that lower drivers complete some number of operations, such as partial transfers, until the original request can be satisfied and completed by the IoCompletion routine
·To retry a request that a lower driver completed with an error
Highest-level NT drivers, such as file systems, are more likely to have IoCompletion routines that attempt to retry requests for which lower-level drivers have returned an error status than are intermediate drivers, except possibly class drivers layered above a closely coupled port driver. Nevertheless, any NT intermediate driver can retry requests from its IoCompletion routine(s) at the discretion of the driver designer.
In other words, an NT highest-level or intermediate driver’s DispatchReadWrite routine usually determines whether a given IRP requires the driver to set up an IoCompletion routine. At the discretion of the driver designer, such a driver’s DispatchDeviceControl or other Dispatch routine(s) also can set up an IoCompletion routine for any given IRP that is passed on to lower drivers.
For driver-allocated and reused IRPs, the Dispatch routine must call IoSetCompletionRoutine with the following Boolean parameters:
·InvokeOnSuccess set to TRUE
·InvokeOnError set to TRUE
·InvokeOnCancel set to TRUE if any lower driver in the chain might handle cancelable IRPs
Usually, InvokeOnCancel is set to TRUE, whether an IRP might be returned with STATUS_CANCELLED or not, to ensure that the IoCompletion routine frees each driver-allocated IRP or checks the completion status of each reuse of an IRP.
Consider the following implementation guidelines for calling IoSetCompletionRoutine in higher-level drivers’ Dispatch routines
·Any Dispatch routine that allocates IRPs for lower drivers with IoAllocateIrp or IoBuildAsynchronousFsdRequest must set an IoCompletion routine in each driver-allocated IRP.
·The Dispatch routine must set up state about the original IRP and the IRP(s) it allocated for the IoCompletion routine to use. At a minimum, the IoCompletion routine needs access to the original IRP and a count of how many additional IRPs were allocated.
·The Dispatch routine should call IoSetCompletion Routine with all InvokeOnXxx parameters set to TRUE for the IRP(s) it allocates.
·If it allocates an I/O stack location of its own in an IRP, the Dispatch routine must call IoSetNextIrpStackLocation before it calls IoGetCurrentIrpStackLocation to set up context in its own I/O stack location for the IoCompletion routine, as mentioned in Chapter 6.
·The Dispatch routine must call IoMarkIrpPending with the original IRP, but not with any driver-allocated IRPs because the IoCompletion routine will free them.
·If the Dispatch routine is allocating IRPs for partial transfers and the underlying device driver might control a removable-media device, the Dispatch routine must set up the thread context in its newly allocated IRPs from the value at Tail.Overlay.Thread in the original IRP.
Such an underlying removable-media device driver might call IoSetHardErrorOrVerifyDevice, which references the pointer at Irp->Tail.Overlay.Thread, with a driver-allocated IRP. If the underlying device driver calls this support routine, the file system driver can send a popup to the appropriate user thread that prompts the user to cancel, retry, or fail the operation that the device driver could not satisfy.
For more information about handling removable media, see Chapter 16.
·The Dispatch routine must return STATUS_PENDING after it has sent all driver-allocated IRPs on to lower drivers.
The driver’s IoCompletion routine should free all driver-allocated IRPs with IoFreeIrp before it calls IoCompleteRequest with the original IRP. When it completes the original IRP, the IoCompletion routine must free all driver-allocated IRPs before it returns control.
·Any Dispatch routine must call IoSetCompletionRoutine with an IRP to be reused for a sequence of operations or if the IoCompletion routine can retry requests.
·The Dispatch routine must set up state about the original IRP for the IoCompletion routine to use.
For example, a DispatchReadWrite routine must save the relevant transfer parameters of an input IRP for the IoCompletion routine before the DispatchReadWrite routine sets up a partial tranfer for the next-lower driver in that IRP, particularly if the DispatchReadWrite routine modifies any parameters that the IoCompletion routine needs to determine when the original request has been satisfied.
If the IoCompletion routine can retry the request, the Dispatch routine must set up a driver-determined upper limit on how many retries its IoCompletion routine should attempt before it completes the original IRP with an error.
·For a to-be-reused IRP, the Dispatch routine should call IoSetCompletion Routine with all InvokeOnXxx parameters set to TRUE.
·For an asynchronous request, the Dispatch routine of any intermediate driver must call IoMarkIrpPending with the original IRP.
·For an asynchronous request, the Dispatch routine of any intermediate driver must return STATUS_PENDING after it has sent the IRP on to lower drivers.
The driver’s IoCompletion routine must maintain state about each reuse of an IRP until the original request is satisfied (or failed) and then call IoCompleteRequest with the IRP. The IoCompletion routine also might be responsible for reinitializing the common state area, or notifying another driver routine to dequeue the next IRP to be processed.
An IoCompletion routine that retries IRPs must maintain the retry count to it can determine when to fail an IRP if the requested operation cannot be completed successfully.
·Any Dispatch routine that allocates per-IRP resources for a type of request that it passes on to lower drivers must call IoSetCompletionRoutine with any IRP of that type.
For example, if the Dispatch routine allocates an MDL with IoAllocateMdl and calls IoBuildPartialMdl for a partial-transfer IRP it allocates, the IoCompletion routine must release the MDL with IoFreeMdl. If it allocates resources to maintain state about the original IRP, it must free those resources, preferably before it calls IoCompleteRequest with the original IRP and definitely before it returns control.
In general, an IoCompletion routine should free any per-IRP resources such a Dispatch routine allocated before the corresponding IRP is freed or completed. Otherwise, the driver must maintain state about the resources to be freed before its IoCompletion routine returns control from completing the original request.
·At the discretion of the driver writer, a Dispatch routine can call IoSetCompletionRoutine with any other IRP that it passes on to lower drivers.