3.3.5.1 Packet-Based Busmaster DMA

To use packet-based DMA, NT drivers of busmaster DMA devices call the following general sequence of support routines as they process an IRP requesting a DMA transfer:

  1. KeFlushIoBuffers just before attempting to allocate map registers for a transfer request

    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 the busmaster adapter for DMA

  3. MmGetMdlVirtualAddress to get an index into the MDL, required as an initial parameter to IoMapTransfer, and IoMapTransfer to make the system physical memory that backs the IRP’s buffer device-accessible

    Note that any driver might need to carry out more than one transfer operation in order to satisfy the current IRP, as explained in Section 3.3.3. Drivers of devices that do not have scatter/gather capabilities can call IoMapTransfer once per transfer operation. Drivers of devices that have scatter/gather capabilities can call IoMapTransfer more than once to set up each transfer operation.

  4. IoFlushAdapterBuffers at the end of each DMA transfer operation to/from the target device, in order to determine whether all the requested data has been completely transferred

  5. IoFreeMapRegisters as soon as all DMA operations for the current IRP are done, either all the requested data has been completely transferred or the driver must fail the IRP because of a device or bus I/O error

The AdapterObject pointer returned by HalGetAdapter is a required parameter to IoAllocateAdapterChannel and IoFreeMapRegisters. However, drivers of busmaster devices pass a NULL AdapterObject pointer to IoMapTransfer and IoFlushAdapterBuffers. KeFlushIoBuffers and MmGetMdlVirtualAddress 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, while another driver might make this call from a routine that removes IRPs from a driver-created interlocked queue or device queue.

3.3.5.1.1 Allocating the Busmaster Adapter Object

A driver calls KeFlushIoBuffers and IoAllocateAdapterChannel after its Dispatch entry point 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.10 illustrates such a call to IoAllocateAdapterChannel.

Figure 3.10 Allocating an Adapter Object for Busmaster DMA

The driver routine that calls IoAllocateAdapterChannel must be executing at IRQL DISPATCH_LEVEL when the 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.10 shows, the driver must supply more than the AdapterObject pointer returned by HalGetAdapter when it calls IoAllocateAdapterChannel. Along with a pointer to the target device object for the current IRP, it must supply the entry point for its AdapterControl routine and a pointer to any driver-determined context data the AdapterControl routine will use.

IoAllocateAdapterChannel queues the driver’s AdapterControl routine, which executes when the adapter object is free and a set of map registers (described in Section 3.3.1) has been allocated for the driver’s DMA operation(s) to or from the target device.

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.10. 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 can include a pointer to the current IRP as part of the context it passes when it calls IoAllocateAdapterChannel.

For the driver of a busmaster DMA device without scatter/gather capabilities, the AdapterControl routine usually does the following:

  1. Saves or initializes whatever context the driver maintains about DMA operations, such as saving the MapRegisterBase handle the driver must pass to IoMapTransfer and IoFlushAdapterBuffers, the Length in bytes of the requested transfer from its I/O stack location in the IRP, and so forth

  2. Calls MmGetMdlVirtualAddress followed by IoMapTransfer (described in the next subsection) to get the logical address its device can use to start the transfer operation

  3. Sets up the busmaster adapter to start the transfer operation

  4. Returns the value DeallocateObjectKeepRegisters

For the driver of a busmaster device with scatter/gather capabilities, the AdapterControl routine usually does the following:

  1. Saves or initializes whatever state the driver maintains about DMA operations, such as saving the MapRegisterBase handle the driver must pass to IoMapTransfer and IoFlushAdapterBuffers, the Length in bytes of the requested transfer from its I/O stack location in the IRP, and so forth

  2. Calls MmGetMdlVirtualAddress followed by IoMapTransfer (described in the next subsection) to get the logical address its device can use to start the transfer operation

    The AdapterControl routine calls IoMapTransfer repeatedly until it has used all the available map registers to build a scatter/gather list for the busmaster adapter.

  3. Sets up the busmaster adapter to start the transfer operation

  4. Returns the value DeallocateObjectKeepRegisters

As Figure 3.10 shows, an AdapterControl routine must return a system-defined value of type IO_ALLOCATION_ACTION. For NT drivers that use busmaster DMA, the AdapterControl routine must return the value DeallocateObjectKeepRegisters, which allows the driver to retain the allocated map registers for the target device object until it has transferred all the requested data for the current IRP.

Note that an AdapterControl routine cannot wait for the busmaster adapter to complete a DMA operation. Whether the busmaster adapter supports scatter/gather or not, the AdapterControl routine must at least do the following:

  1. Save necessary 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 DeallocateObjectKeepRegisters.

Another driver routine (probably the DpcForIsr) must call IoFlushAdapterBuffers when each DMA transfer operation is done. This routine also must set up any additional DMA operations necessary to satisfy the current IRP.

When the driver has satisfied the current IRP’s transfer request or must fail the IRP due to a device or bus I/O error, it must call IoFreeMapRegisters. This call should occur immediately following the last call to IoFlushAdapterBuffers for the current IRP, so that the driver can service other DMA requests, possibly for other devices on the bus.

3.3.5.1.2 Setting Up a Transfer Operation

When IoAllocateAdapterChannel transfers control to a driver’s AdapterControl routine, it has allocated a set of map registers. However, system physical memory for the current IRP’s transfer request must be mapped to the busmaster adapter’s logical address range 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 map the system physical address ranges for the IRP’s buffer to the busmaster adapter’s logical address range before the driver sets up the adapter for the transfer operation

Figure 3.11 illustrates such a call to IoMapTransfer.

Figure 3.11 Setting Up a Logical Range for DMA

As Figure 3.11 shows, the driver supplies the following parameters to IoMapTransfer:

IoMapTransfer returns a logical address at which the driver can program the busmaster adapter to begin the transfer operation.

If the driver must call IoMapTransfer more than once to satisfy the current IRP, it supplies the same NULL AdapterObject pointer, Mdl pointer, MapRegisterBase handle, and transfer direction in every call to IoMapTransfer. However, the driver must supply updated CurrentVa and Length values in its second and subsequent calls to IoMapTransfer. Use the following formulas to calculate these values:

The context information each driver maintains about its DMA transfers, such as the IrpLengthNextTransfer, IrpLengthDMAedSoFar, and MdlCurrentVa shown in Figure 3.11, depends on the driver writer.

For drivers of devices with scatter/gather capabilities, the Length parameter to IoMapTransfer is both an input and output parameter. On return from IoMapTransfer, it indicates how many bytes of data the system has mapped. That is, the return value of Length, in combination with the returned logical address, indicates the range of logical addresses the busmaster adapter can use for this piece of the transfer in this DMA operation.

    Since Length is overwritten by IoMapTransfer, follow this implementation guideline:

Never pass a pointer to the Length in the driver’s I/O stack location of an IRP as the Length parameter to IoMapTransfer if your device supports scatter/gather.

Doing this could destroy the value in the current IRP, making it impossible to determine whether the driver has transferred all the requested data.

At the end of each DMA operation, the driver must call IoFlushAdapterBuffers with a NULL AdapterObject pointer and the MapRegisterBase handle to be sure that all the data has been transferred (see Chapter 16), and to release the physical-to-logical mapping(s) for the current DMA operation. If the driver must set up additional DMA operations to satisfy the current IRP, it must call IoFlushAdapterBuffers after each transfer operation is complete.

When all the requested transfer is complete or the driver must return an error status for the IRP, the driver should call IoFreeMapRegisters immediately after its last call to IoFlushAdapterBuffers in order to get the best possible throughput for the busmaster adapter. The driver must pass the AdapterObject pointer that it passed in the preceding call to IoAllocateAdapterChannel to IoFreeMapRegisters.