Writing IO Completion Routines

< BACK  NEXT >
[oR]

Writing I/O Completion Routines

An I/O Completion routine is an I/O Manager callback that notifies a higher-level driver when the lower-level driver has finished work on an IRP. This section explains how to use I/O Completion routines in intermediate drivers.

Requesting an I/O Completion Callback

To regain control of an IRP after it has been processed by a lower-level driver, use IoSetCompletionRoutine (described in Table 15.1). This function puts the address of an I/O Completion routine in the IRP stack location associated with the next lower driver. When some lower-level driver calls IoCompleteRequest, the I/O Completion routine executes as the IRP bubbles its way back to the top of the driver hierarchy.

Except for the driver on the bottom, each driver in the hierarchy can attach its own I/O Completion routine to an IRP. This allows every level to receive notification when an IRP completes. The I/O Completion routines execute in driver-stacking order, from bottom to top.

Table 15.1. Function Prototype for IoSetCompletionRoutine
VOID IoSetCompletionRoutine IRQL <=DISPATCH_LEVEL
Parameter Description
IN PIRP pIrp Address of IRP the driver wants to track
IN PIO_COMPLETION_ROUTINE CompletionRoutine Routine to call when a lower driver completes the IRP
IN PVOID pContext Argument passed to I/O Completion routine
IN BOOLEAN bInvokeOnSuccess Call routine if IRP completes successfully
IN BOOLEAN bInvokeOnError Call routine if IRP completes with error
IN BOOLEAN bInvokeOnCancel Call routine if IRP is canceled
Return value - void -

The three BOOLEAN parameters passed to IoSetCompletionRoutine allow the callback to be invoked based on the disposition of the IRP by the lower level. The I/O Manager uses the IoStatus.Status field of the IRP to decide whether to invoke the I/O Completion routine.

Execution Context

By the time an I/O Completion routine is called, the I/O Manager has already popped the I/O stack pointer, so the current stack location is the one belonging to the driver. Table 15.2 lists the arguments passed to an I/O Completion routine.

The execution context of an I/O Completion routine is the same as that of the caller of IoCompleteRequest. This could be either PASSIVE_LEVEL or DISPATCH_LEVEL, depending upon whether a DPC routine is used to complete the IRP. Since the design of the lower level is not likely to be under the control of the higher level, an I/O completion routine must assume the more restrictive context of DISPATCH_LEVEL IRQL.

When an I/O Completion routine is finished, it should return one of two status codes. Returning STATUS_SUCCESS allows the IRP to continue its journey back toward the original requester. Along the way, other I/O Completion routines attached to higher-level drivers execute. This is normally the appropriate return value when the IRP originated from a higher level.

To suspend further processing of the "completed" IRP, an I/O Completion routine can return STATUS_MORE_PROCESSING_REQUIRED. This value blocks the execution of any higher-level I/O Completion routines attached to the IRP. It also prevents the original caller from receiving notification that the IRP has completed. An I/O Completion routine should return this code if it either plans to send the IRP back down to a lower-level driver (as in the case of a split transfer) or if the IRP was allocated by this driver and the I/O Completion routine is going to deallocate it.

Table 15.2. Function Prototype for an I/O Completion Routine
NTSTATUS IoCompletion IRQL ==PASSIVE_LEVEL or IRQL == DISPATCH_LEVEL
Parameter Description
IN PDEVICE_OBJECT pDevObj Device object of the just completed request
IN PIRP pIrp The IRP being completed
IN PVOID pContext Context passed from IoSetCompletionRoutine
Return value
  • STATUS_MORE_PROCESSING_REQUIRED

  • STATUS_SUCCESS

What I/O Completion Routines Do

An intermediate driver can attach an I/O Completion routine to any IRP it sends to another driver. This includes the original IRP the driver received from another caller, as well as any IRPs that the driver itself allocates. When an I/O Completion routine executes, there are three general kinds of tasks it may need to perform.

RELEASE THE ORIGINAL IRP

If the completed IRP is one that came from an outside caller, it may require some driver-specific cleanup. At the very least, the I/O Completion routine for one of these IRPs needs to do the following:

  1. Test the value of the IRP's PendingReturned flag.

  2. If this flag is TRUE, the I/O Completion routine puts the current I/O stack location into the pending state with a call to IoMarkIrpPending.

  3. Finally, it returns a value of STATUS_SUCCESS to allow completion processing to continue.

DEALLOCATE THE IRP

If the IRP was allocated by the driver, the I/O Completion routine may be responsible for releasing it. Based on how the IRP was allocated, an appropriate deallocation technique must be employed. The next section explains the entire process.

RECYCLE THE IRP

Some intermediate drivers have to split a transfer into smaller pieces before sending it to a lower-level driver. Normally, the most efficient way to do this is to send each partial transfer to the lower driver by reusing the same IRP. To recycle an IRP, the I/O Completion routine does the following:

  1. It checks the context information stored with the IRP to see if this was the last partial transfer. If the whole transfer is finished and the IRP came from an outside caller, the driver performs any necessary cleanup and returns STATUS_SUCCESS to allow further completion processing.

  2. If the entire transfer is finished and the IRP is driver-allocated, the I/O Completion routine performs any necessary cleanup, frees the IRP, and returns STATUS_MORE_PROCESSING_REQUIRED to prevent any further completion processing.

  3. If there is more work to be done, the I/O Completion routine calls IoGetNextIrpStackLocation and sets up the I/O stack slot for the next lower driver.

  4. It uses IoSetCompletionRoutine to attach the address of this I/O Completion routine to the IRP.

  5. It passes the IRP to the target Device object using IoCallDriver.

  6. Finally, it returns STATUS_MORE_PROCESSING_REQUIRED to prevent any further completion processing of this IRP.

During each partial transfer, an intermediate driver must maintain the current transfer count. One clever way to maintain this context information is to store it in unused fields of the intermediate driver's I/O stack location. For example, the Parameters.ByteOffset and Parameters.Key fields of the IRP stack area are often unused fields for a higher-level driver. Three DWORDs of context data can be maintained within these fields.

Otherwise, the straightforward technique of allocating a private block from pooled memory that is passed as a context argument to the I/O Completion routine can always be used.

Code Fragment: An I/O Completion Routine

Listed below is a fragment of an I/O Completion routine. It complements the DispatchRead function presented in the last section. If the request completes normally, the IRP is sent back to the original caller. If some- thing fails at a lower level, it retries the operation a fixed number of times.

 NTSTATUS ReadCompletion(               IN PDEVICE_OBJECT pDevObj,               IN PIRP pIrp,               IN PVOID pContext ) {   PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)          pDevObj->DeviceExtension;             PIO_STACK_LOCATION pThisIrpStack =          IoGetCurrentIrpStackLocation( pIrp );             PIO_STACK_LOCATION pNextIrpStack =          IoGetNextIrpStackLocation( pIrp );             // Set up a reference variable for the retryCount.   // The variable uses the Parameters.Read.Key field.   DWORD &retryCount =      pThisIrpStack->Parameters.Read.Key;         // If the lower level finshed successfully,   //     or if we have exceeded our retry count,   //     return normally.   if ((NT_SUCCESS( pIrp->IoStatus.Status )) ||      ( retryCount == 0 )) {          // If the lower level requested that the          //  IRP be marked "pending", make it so.          if ( pIrp->bPendingReturned )               IoMarkIrpPending( pIrp );          return STATUS_SUCCESS;   }      // The lower level reported a failure, but we still   //     have the patience to try again (for a while)   retryCount--;   // patience counter      // Copy down the stack (again)   *pNextIrpStack = *pThisIrpStack;   // Don't confuse lower level with our retry count   *pNextIrpStack->Parameters.Read.Key = 0;      // The I/O Completion routine must be reset each   //     time the IRP is recyled.   IoSetCompletionRoutine(               pIrp,               ReadCompletion,               NULL,               TRUE, TRUE, TRUE );                  // Send the IRP back to the lower level   IoCallDriver( pDevExt->LowerDevice, pIrp );      // Indicate to the I/O Manager that we're still   //     working on the request.   return STATUS_MORE_PROCESSING_REQUIRED; } 
< BACK  NEXT >


The Windows 2000 Device Driver Book(c) A Guide for Programmers
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
ISBN: 0130204315
EAN: 2147483647
Year: 2000
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net