Writing Driver Dispatch Routines

< BACK  NEXT >
[oR]

Depending on the complexity of the device operation and the kind of I/O request, driver Dispatch routines range from trivial to quite difficult to implement. This section explains how to code these routines.

Execution Context

All Dispatch routines share the same function signature. (A function signature includes the number and type of parameters, and its calling convention.) Table 7.2 shows the prototype for all Dispatch routines. Like the driver's initialization and unload routines, Dispatch routines run at PASSIVE_LEVEL IRQL, which means they can access paged system resources.

The I/O Manager invokes Dispatch routines in response to user-mode or kernel-mode requests. Before calling, the I/O Manager builds and fills the IRP with valid data. This includes the pointer to the user's buffer. The user buffer is validated by the I/O Manager to ensure that each page address spanned by the buffer is readable or writeable within the context of the requestor. If the request is for Buffered I/O, the I/O Manager first allocates a nonpaged pool buffer and, if a write request, copies data from the user buffer into the pool. If the request is for Direct I/O, the I/O Manager faults the entire user buffer into physical memory and locks it down.

A Dispatch routine can usually track the state of an I/O request using only the IRP. If a Dispatch routine uses any data structures outside the IRP, the driver must ensure that proper synchronization steps are taken. This would mean using a spin lock to coordinate with other driver routines running at DISPATCH_LEVEL or below IRQL, and KeSynchronizeExecution to synchronize with Interrupt Service code.

The IRP is shared data, albeit serially, with the I/O Manager. In particular, the I/O Manager uses fields of the Parameters union to complete the I/O request. For example, after a Buffered I/O request, it needs to copy data from the nonpaged pool into the user buffer. It must then deallocate the pool buffer. A field within the Parameters union points to this buffer. Therefore, changing the value of this buffer pointer would lead to disastrous results.

In general, if a Dispatch routine needs to modify an IRP field, it should make working copies on the stack or in the device extension.

Table 7.2. Function Prototype for Dispatch Routines
NTSTATUS Dispatch IRQL==PASSIVE_LEVEL
Parameter Description
IN PDEVICE_OBJECT pDevObject Pointer to target device for this request
IN PRIP pIrp Pointer to IRP describing this request
Return value STATUS_SUCCESS - request complete
STATUS_PENDING - request pending
STATUS_XXX - appropriate error code

What Dispatch Routines Do

The exact behavior of a Dispatch routine will depend on the function it supports. However, the general responsibilities of these routines include the following.

  1. Call IoGetCurrentIrpStackLocation to get a pointer to the IRP stack location belonging to this driver.

  2. Perform any additional parameter validation specific to this request and device.

  3. For an intermediate-level driver, consideration must be given to the limitations of the underlying physical device (for example, its maximum transfer size). The Dispatch routine may need to split the caller's request into multiple requests for the lower-level driver.

  4. Continue processing the IRP until the request is complete or an error condition prevents further processing.

Exiting the Dispatch Routine

When a Dispatch routine processes an IRP, there are only three possible outcomes.

  • The request's parameters do not pass the driver's validation tests and the request is rejected.

  • The request can be handled entirely within the Dispatch routine without need for any device operation. An example of such a request would be a Read of zero bytes.

  • The device must be started in order to complete the request.

Each of these possible outcomes is described in more detail in the following sections.

SIGNALING AN ERROR

If a Dispatch routine uncovers a problem with the IRP, it needs to reject the request and notify the caller. The following steps describe how to reject an IRP.

  1. Store an appropriate error code in the Status field of the IRP's IoStatus block and clear the Information field.

  2. Call IoCompleteRequest to release the IRP with no priority increment. (Priority increment is discussed in a later section.)

  3. The Dispatch routine should return the same error code placed in the Status field of the IRP.

The code fragment below shows how a Dispatch routine rejects an I/O request.

 NTSTATUS DispatchWrite( IN PDEVICE_OBJECT pDO,                         IN PIRP pIrp ) {     :     // If the request is not supported by this device     // report it and reject the request     pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;     // report that no bytes were transferred     pIrp->IoStatus.Information = 0;     // Mark the IRP as "complete", no priority increment     IoCompleteRequest( pIrp, IO_NO_INCREMENT);     return STATUS_NOT_SUPPORTED; } 

Note that after marking the IRP as complete, the I/O Manager is free to release the IRP memory storage from nonpaged pool. As such, it would be incorrect to

 return pIrp->IoStatus.Status; 

since the memory pointed to by pIrp has already been released.

COMPLETING A REQUEST

Some I/O requests can be handled without performing any actual device operations. Opening a handle to a device or returning information stored in the device object are examples of these kinds of requests. To complete such a request in the Dispatch routine, do the following:

  1. Put a successful completion code in the Status field of the IRP's IoStatus block, and set the Information field to some appropriate value.

  2. Call IoCompleteRequest to release the IRP with no priority increment.

  3. Exit the Dispatch routine with a value of STATUS_SUCCESS.

The code fragment below shows how a Dispatch routine completes a request.

 NTSTATUS DispatchClose( IN PDEVICE_OBJECT pDO,                         IN PIRP pIrp ) {     :     pIrp->IoStatus.Status = STATUS_SUCCESS;     // Indicate that zero bytes of data were transferred     pIrp->IoStatus.Information = 0;     // "Mark" the IRP as complete - no further processing     IoCompleteRequest( pIrp, IO_NO_INCREMENT );     return STATUS_SUCCESS; } 
SCHEDULING A DEVICE OPERATION

The last action a Dispatch routine might take is the most likely that it will need to interact with the actual device to fulfill the request. Examples include a data transfer, a control function, or an informational query. In this case, the Dispatch routine must queue the IRP for ultimate processing by the driver's Start I/O routine and then promptly return to the I/O Manager stating that the request is pending. To schedule (queue) a device operation, do the following:

  1. Call IoMarkIrpPending to inform the I/O Manager that the request is still in progress.

  2. Call IoStartPacket to queue the IRP for the Start I/O routine. A driver can also provide its own custom queuing mechanism in lieu of using the I/O Manager's routine.

  3. Exit the Dispatch routine with a value of STATUS_PENDING. This allows the original requestor to continue its other operations in parallel with the device's operation.

The following code fragment shows how a Dispatch routine schedules a device operation.

 NTSTATUS DispatchWrite( IN PDEVICE_OBJECT pDO,                         IN PIRP pIrp ) {     :     // Mark the IRP as "in progress"     IoMarkIrpPending( pIrp );     // Now queue (schedule) the IRP for eventual passage     // to the driver's Start I/O routine.     // Third parameter allows insertion into the queue     //     other than at the tail     // Fourth parameter allows specification of a     //    Cancel routine     IoStartPacket( pDO, pIrp, 0, NULL );     return STATUS_PENDING; } 

It is a little-known fact that the I/O Manager automatically completes any IRP that isn't marked pending as soon as the Dispatch routine returns. Unfortunately, this automatic mechanism does not call I/O Completion routines attached to the IRP by higher-level drivers. Consequently, it is important that a driver either calls IoCompleteRequest or IoMarkIrpPending to explicitly set the status of the IRP.

< 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