NT drivers of slave devices that use packet-based DMA call the following general sequence of support routines as they process an IRP requesting a DMA transfer:
See Chapter 16 for more information about maintaining cache coherency during DMA with KeFlushIoBuffers and IoFlushAdapterBuffers.
Note that a driver might need to call IoMapTransfer more than once to transfer all the requested data, as explained in Section 3.3.3.
If a driver must call IoMapTransfer more than once to transfer all the requested data, it must call IoFlushAdapterBuffers as many times as it calls IoMapTransfer.
The AdapterObject pointer returned by HalGetAdapter is a required parameter to each of these routines except KeFlushIoBuffers and MmGetMdlVirtualAddress, which require a pointer to the MDL at Irp->MdlAddress.
Individual NT drivers call this sequence of support routines at different points, depending on how each driver is implemented to service its device. For example, one driver’s StartIo routine might make the call to IoAllocateAdapterChannel, another driver might make this call from a routine that removes IRPs from a driver-created interlocked queue, and still another driver might make this call when its slave DMA device indicates it is ready to transfer data.
A driver calls KeFlushIoBuffers and IoAllocateAdapterChannel after its Dispatch routine for IRP_MJ_READ and/or IRP_MJ_WRITE requests, or for any other request that requires a DMA transfer, has already checked the validity of the IRP’s parameters (if necessary), possibly queued the IRP to another driver routine for further processing, and the transfer request is the current IRP requiring a device I/O operation. Figure 3.7 illustrates such a call to IoAllocateAdapterChannel.
Figure 3.7 Allocating the System DMA Controller
The driver routine that calls IoAllocateAdapterChannel must be executing at IRQL DISPATCH_LEVEL when this call occurs. For more information about the IRQLs at which NT drivers’ standard routines execute, see Chapters 5 through 15. For more information about support-routine-specific IRQL requirements, see the Kernel-Mode Driver Reference.
As Figure 3.7 shows, the driver must supply more than a pointer to the adapter object returned by HalGetAdapter when it calls IoAllocateAdapterChannel. Along with a pointer to the target device object, it must supply the entry point for its AdapterControl routine and a pointer to any driver-determined context information the AdapterControl routine will use.
IoAllocateAdapterChannel queues the driver’s AdapterControl routine, which executes when the system DMA controller is assigned to this driver and a set of map registers (described in Section 3.3.1) has been allocated for the driver’s DMA operation(s).
On entry, an AdapterControl routine is given the DeviceObject and Context pointers passed in the call to IoAllocateAdapterChannel, as well as a handle (MapRegisterBase) for the allocated map registers, as shown in Figure 3.7.
The AdapterControl routine also is given a pointer to the DeviceObject->CurrentIrp if the driver has a StartIo routine. If a driver manages its own queueing of IRPs (instead of having a StartIo routine), the driver should include a pointer to the current IRP as part of the context it passes when it calls IoAllocateAdapterChannel.
The AdapterControl routine usually does the following:
As Figure 3.7 shows, an AdapterControl routine must return a system-defined value of type IO_ALLOCATION_ACTION. For NT drivers that use system DMA, the AdapterControl routine must return the value KeepObject. This allows the driver to retain "ownership” of the system DMA controller and allocated map registers until it has transferred all the requested data.
Note that an AdapterControl routine cannot wait for the slave device to carry out the DMA operation, so an AdapterControl routine must at least do the following:
Another driver routine (probably the DpcForIsr) must call IoFlushAdapterBuffers when each DMA transfer operation is complete. This routine also must call IoMapTransfer and IoFlushAdapterBuffers again if it is necessary to set up the DMA controller more than once to satisfy the current IRP’s transfer request.
When a driver has satisfied the current IRP’s request, it must call IoFreeAdapterChannel. This support routine should be called immediately following the last call to IoFlushAdapterBuffers for the current IRP so that other drivers and this driver can use the system DMA controller to satisfy other transfer requests expeditiously.
Note that the driver of a slave device with scatter/gather capabilities should also return KeepObject from its AdapterControl routine. Such a device must be capable of waiting while the system DMA controller is reprogrammed between DMA operations when the driver must split up a given DMA request. On some Windows NT platforms, these kinds of devices can transfer at most a page of data per DMA operation because the HAL can assign only a single map register to the driver of such a device.
When IoAllocateAdapterChannel transfers control to a driver’s AdapterControl routine, the driver “owns” the system DMA controller and a set of map registers. Then, the DMA controller must be set up for a transfer operation by calling the following routines:
The return value is a required parameter (CurrentVa) to IoMapTransfer.
Figure 3.8 illustrates such a call to IoMapTransfer.
Figure 3.8 Programming the System DMA Controller
As Figure 3.8 shows, the driver supplies the following parameters to IoMapTransfer:
Otherwise, the driver should supply an updated CurrentVa value, indicating where in the buffer the next transfer operation should start. (How to calculate an updated CurrentVa is described later.)
If the driver can transfer all the requested data with a single call to IoMapTransfer and has no device-specific constraints on its DMA operations, Length can be set to the value of Length in the driver’s I/O stack location of the IRP. At most, the length in bytes can be (PAGE_SIZE * the NumberOfMapRegisters returned by HalGetAdapter). Otherwise, the driver must split up the request, as explained in Section 3.3.3 and must update the value of Length in subsequent calls to IoMapTransfer for the current IRP.
IoMapTransfer returns a logical address, which drivers that use system DMA must ignore. When IoMapTransfer returns control, the driver should set up its device for the DMA operation. When the device indicates that its current DMA operation has completed, the driver should call IoFlushAdapterBuffers, usually from the driver’s DpcForIsr routine.
Note that the DpcForIsr or another driver routine that completes a DMA operation calls IoFlushAdapterBuffers to ensure that any data cached in the system DMA controller is read into system memory or written out to the device. The same routine also must call IoMapTransfer again if it is necessary to reprogram the system DMA controller to transfer more data for the current IRP and IoFlushAdapterBuffers again following each transfer operation.
If a driver must call IoMapTransfer more than once for the current IRP, it supplies the same AdapterObject pointer, Mdl pointer, MapRegisterBase handle, and transfer direction in every call. However, the driver must update the CurrentVa and Length parameters before it makes the second and any subsequent calls to IoMapTransfer. To calculate an updated value for each of these parameters, use the following formulas:
The context information each driver maintains about its DMA transfers, such as the IrpLengthNextTransfer, IrpLengthDMAedSoFar, and MdlCurrentVa shown in Figure 3.8, depends on the driver writer.
When all the requested transfer is complete or the driver must return an error status for the IRP, the driver should call IoFreeAdapterChannel promptly to release the system DMA controller for other drivers and this driver to use.