6.2.4 How to Pass IRPs with Valid Parameters on from a Dispatch Routine
When a Dispatch routine has checked its own I/O stack location in an input IRP and determined that any parameters are valid, it must pass the IRP on for further processing by lower-level drivers or by other device driver routines if it cannot satisfy and complete the given request in the Dispatch routine itself.
A higher-level NT driver should pass such a request on to a next-lower driver as follows:
1.Call IoGetCurrentIrpStackLocation with the input IRP, if the Dispatch routine has not already done so, to get a pointer for its own I/O stack location in the IRP.
In most circumstances, a higher-level driver’s Dispatch routine has already made this call. However, a Dispatch routine devoted exclusively to handling a particular IRP_MJ_XXX with parameters that the driver cannot validate or with no parameters might call IoGetCurrentIrpStackLocation.
2.Call IoGetNextIrpStackLocation if the driver will pass the input IRP on to the next lower-level driver. If it allocates additional IRP(s) for one or more lower drivers, the Dispatch routine makes either of the following sets of calls with each of the IRPs it allocates:
·IoGetNextIrpStackLocation to get a pointer to the next-lower driver’s I/O stack location if the driver allocated no stack location of its own in the new IRP
·IoSetNextIrpStackLocation, followed by IoGetCurrentIrpStackLocation, to get a pointer to its own stack location in a new IRP, where it can set up whatever context its IoCompletion routine needs; then, IoGetNextIrpStackLocation to get a pointer to the next-lower driver’s I/O stack location
3.Set up the next-lower driver(s)’ I/O stack location, usually by copying the contents of its own I/O stack location in the original IRP to the next-lower driver’s. However, the Dispatch routine can modify some of the parameters in the next-lower driver’s I/O stack location for certain requests.
For example, a higher-level driver might modify the parameters for a large transfer request when the underlying device has a known limit in transfer capacity, and reuse the IRP to send partial-transfer requests to the underlying device driver.
4.Call IoSetCompletionRoutine with each IRP that the Dispatch routine allocated so the driver’s IoCompletion routine releases each such IRP when lower drivers have completed it.
The Dispatch routine also can call IoSetCompletionRoutine with an input IRP in which it has set up the next-lower driver’s I/O stack location. Then, its IoCompletion routine can check on how lower drivers completed the request, reuse the IRP for partial transfers, update whatever state the driver maintains if it tracks IRPs, possibly retry a request returned with an error, and so forth.
For more information about IoCompletion routines, see Chapter 13.
5.Call IoCallDriver with each IRP to be processed by lower driver(s).
6.Return an appropriate NTSTATUS value, such as:
·Usually, STATUS_PENDING if the input IRP is an asynchronous request, such as IRP_MJ_READ or IRP_MJ_WRITE
·Frequently, the result of the call to IoCallDriver if the input IRP is a synchronous request, such as IRP_MJ_CREATE or IRP_MJ_DEVICE_CONTROL
An NT device driver passes any IRP that it cannot complete in its Dispatch routine on to other driver routines as follows:
1.Call IoMarkIrpPending with the input IRP.
2.Call IoStartPacket to pass on or queue the IRP to the driver’s StartIo routine, unless the driver manages its own internal IRP queueing. See Chapter 7 for more information about driver-managed queues.
If the driver does not have a StartIo routine but handles cancelable IRPs, it must call IoSetCancelRoutine with the entry point for a Cancel routine and the IRP before queueing the IRP for further processing. See Chapter 12 for more information about Cancel routines.
3.Return STATUS_PENDING.