3.3.4.1 Packet-Based System DMA
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:
1.KeFlushIoBuffers just before attempting to allocate the system DMA controller
See Chapter 16 for more information about maintaining cache coherency during DMA with KeFlushIoBuffers and IoFlushAdapterBuffers.
2.IoAllocateAdapterChannel when the driver is ready to program its device for DMA and needs the system DMA controller
3.MmGetMdlVirtualAddress to get an index into the MDL, required as an parameter in the initial call to IoMapTransfer, and IoMapTransfer to program the system DMA controller for the transfer operation
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.
4.IoFlushAdapterBuffers just after each DMA transfer operation to/from the slave device
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.
5.IoFreeAdapterChannel as soon as all the requested data has been transferred or the driver fails the IRP because of a device I/O error
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.
3.3.4.1.1 Allocating an Adapter Channel for Packet-Based DMA
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:
1.Saves or initializes whatever context the driver maintains about DMA operations, such as saving the input MapRegisterBase handle the driver must pass to IoMapTransfer and IoFlushAdapterBuffers and, possibly, the Length of the requested transfer from its I/O stack location in the IRP
2.Calls MmGetMdlVirtualAddress followed by IoMapTransfer (described in the next section)
3.Sets up the slave device to start the transfer operation
4.Returns the value KeepObject
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:
1.Save context information, particularly the MapRegisterBase handle, in the driver’s device extension, controller extension, or other driver-accessible resident storage area (nonpaged pool allocated by the driver).
2.Return KeepObject.
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.
3.3.4.1.2 Setting Up the System DMA Controller for Packet-Based DMA
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:
1.MmGetMdlVirtualAddress with the MDL at Irp->MdlAddress to get an index for the system physical address where the transfer should start
The return value is a required parameter (CurrentVa) to IoMapTransfer.
2.IoMapTransfer to set up the system DMA controller before the driver sets up its device for the transfer operation
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:
·The AdapterObject pointer returned by HalGetAdapter (see Section 3.3.2)
·A pointer (Mdl) to the MDL at Irp->MdlAddress for the current IRP
·The MapRegisterBase handle passed to the driver’s AdapterControl routine by IoAllocateAdapterChannel
·The value (CurrentVa) returned by MmGetMdlVirtualAddress if this is the first call to IoMapTransfer for the IRP
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.)
·A pointer to a variable (Length, shown in Figure 3.8 as IrpLengthNextTransfer)
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.
·A Boolean value (WriteToDevice), indicating the direction of the transfer operation (TRUE for a requested transfer from system memory to the device)
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:
·CurrentVa = CurrentVa + (Length requested in the preceding call to IoMapTransfer)
·Length = Minimum (remaining Length to be transferred, (PAGE_SIZE * NumberOfMapRegisters returned by HalGetAdapter))
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.