13.1.3 Allocating IRPs for Lower Drivers
An NT highest-level or intermediate driver's DispatchRead and/or DispatchWrite routine is most likely to set an IoCompletion routine in an IRP because transfer requests must be handled asynchronously by lower-level drivers, as discussed in Chapter 6. To allocate an IRP for an asynchronous request, which will be processed in an arbitrary thread context by lower drivers, such a DispatchReadWrite routine can call one of the following support routines:
·IoAllocateIrp, which allows the caller to allocate an I/O stack location for itself in the IRP and initializes all I/O stack locations in the IRP with zeros
The Dispatch routine must set up the next-lower driver's I/O stack location in the newly allocated IRP, usually by copying (possibly modified) information from its own stack location in the original IRP. If a higher-level driver allocates an I/O stack location of its own in such an IRP, the Dispatch routine can set up per-request context information there for the IoCompletion routine to use.
·IoBuildAsynchronousFsdRequest, which sets up the next-lower driver's I/O stack location for the caller, according to caller-specified parameters
Higher-level drivers can call this routine to allocate IRPs for IRP_MJ_READ, IRP_MJ_WRITE, IRP_MJ_FLUSH_BUFFERS, and IRP_MJ_SHUTDOWN requests.
When an IoCompletion routine is called with such an IRP, it can check the I/O status block, and if necessary (or possible) set up the next-lower driver's I/O stack location in the IRP again and retry the request or reuse it. However, the IoCompletion routine has no local context storage for itself in such an IRP, so the driver must maintain context about the original request elsewhere in resident memory.
·IoMakeAssociatedIrp, which allows a highest-level driver to allocate an I/O stack location for itself in the IRP, but callers very seldom set an IoCompletion routine in such an IRP
NT intermediate drivers cannot call IoMakeAssociatedIrp to create IRPs for lower drivers. Any highest-level driver that calls IoMakeAssociatedIrp to create IRP(s) for lower drivers can return control to the NT I/O Manager after sending its associated IRP(s) on and calling IoMarkIrpPending with the master (original) IRP. Such a driver can rely on the I/O Manager to complete the master IRP when all associated IRPs have been completed by lower drivers.
If such a highest-level driver calls IoSetCompletionRoutine for an associated IRPs it creates, the I/O Manager does not complete the master IRP if the driver returns STATUS_MORE_PROCESSING_REQUIRED from its IoCompletion routine. In these circumstances, the driver's IoCompletion routine must explicitly complete the master IRP with IoCompleteRequest.
A higher-level driver's DispatchDeviceControl routine can allocate an IRP with IoBuildDeviceIoControlRequest. It can set up an IoCompletion routine for such an IRP but is unlikely to do this. If the driver allocates resident storage for and initializes an event object, its DispatchDeviceControl routine can wait on an event when it sends on driver-allocated IRPs for inherently synchronous device control requests, as mentioned in Chapter 6.
Usually, a higher-level driver would not set its IoCompletion routine in an IRP allocated with IoBuildSynchronousFsdRequest for the same reason. For more information about restrictions on calls to this support routine from Dispatch routines, see also Chapter 6.
Each higher-level NT driver sets up any driver-allocated (and reused) IRPs for lower drivers in such a way that it is immaterial to the underlying device driver whether a given request comes from an NT intermediate driver or originates from any other source, such as an NT file system or user-mode application.