Although the specific details vary with the nature of the device and the architecture of the driver, DMA drivers generally have to perform several kinds of operations on Adapter objects.
The following sections discuss these topics in general terms. Later sections of this chapter add more detail. Finding the Right Adapter ObjectAll DMA drivers need to locate an Adapter object before they can perform any I/O operations. To find the right one, a driver's initialization code needs to call the IoGetDmaAdapter function described in Table 12.2. Given a description of some DMA hardware, IoGetDmaAdapter returns a pointer to a structure of function pointers that manipulate the corresponding Adapter object. It also reports a count of the maximum number of mapping registers available for a single transfer. The driver needs to save both these items in nonpaged storage (usually the Device or Controller Extension) for later use.
By returning a structure of function pointers, the Adapter object is truly an encapsulated object it can only be manipulated through its interface. The main input to IoGetDmaAdapter is the DEVICE_DESCRIPTION block listed in Table 12.3. Unused entries of this input structure must be zero. Some fields of this structure deserve comment. ScatterGather. For bus master devices, this field signifies that the hardware supports transfer of data to and from noncontiguous ranges of physical memory. For slave devices, this field indicates that the device can be paused between page transfers, allowing the I/O Manager to repoint the DMA channel's address register to a new page of physical memory. DemandMode. DMA demand mode is a transfer protocol that allows a device to hold off the (slave) DMA controller. In normal mode (DemandMode==FALSE), the DMA controller does not allow the device to delay its request to transfer another block of data. Autoinitialization. DMA autoinitialization mode allows system DMA channels to restart themselves after a completed transfer. Specified address and count values are automatically reset into the DMA hardware and another operation is "good to go." IgnoreCount. Some DMA hardware maintains an improper count of bytes transferred. This can occur because the hardware counts words instead of bytes. If this field is set to TRUE, the HAL manually maintains the transfer count on behalf of the device.
Acquiring and Releasing the Adapter ObjectThere is no guarantee that the DMA resources needed for a device transfer will be free when a driver's Start I/O routine runs. For example, a slave device DMA channel may already be in use by another device, or there may not be enough mapping registers to handle the request. Consequently, all packet-based DMA drivers and drivers for common buffer slave devices have to request ownership of the Adapter object before starting a data transfer. Since the Start I/O routine runs at DISPATCH_LEVEL IRQL, there is no way it can stop and wait for the Adapter object. Instead, it calls the Allocate-AdapterChannel method of the Adapter object (see Table 12.4) and then returns control to the I/O Manager. When the requested DMA resources become available, the I/O Manager notifies the driver by calling its Adapter Control routine. It's important to keep in mind that this is an asynchronous callback. It may happen as soon as Start I/O calls AllocateAdapterChannel, or it may not occur until some other driver releases the Adapter resources. Notice that the caller of AllocateAdapterChannel must be at DISPATCH_LEVEL IRQL. Since the function is normally called from the Start I/O routine, this poses no problem. However, if it is called from another driver routine from PASSIVE_LEVEL, make sure to use KeRaiseIrql and KeLower-Irql before and after the call to AllocateAdapterChannel.
The Adapter Control routine in a DMA driver is responsible for calling MapTransfer to set up the DMA hardware and starting the actual device operation. Table 12.5 contains a prototype of the Adapter Control callback.
The MapRegisterBase argument is an opaque value that identifies the mapping registers assigned to the I/O request. It is really a kind of handle to a specific group of registers. This handle is used to set up the DMA hardware for the transfer. Normally, this handle value is saved in the Device or Controller extension because it is needed in later parts of the DMA operation. The pIrp argument passed to the Adapter Control callback is valid only when AllocateAdapterChannel is called from the Start I/O routine. If it is called from some other context, the pIrp pointer will be NULL. In such a case, another mechanism must be used to pass the IRP (and its associated MDL address) to the Adapter Control routine. The context pointer argument can possibly be used for this purpose. After it programs the DMA controller and starts the data transfer, the Adapter Control routine gives control back to the I/O Manager. Drivers of slave devices should return a value of KeepObject from this function so that the Adapter object remains the exclusive property of this request. Bus master drivers return DeallocateObjectKeepRegisters. When the DpcForIsr routine in a DMA driver completes an I/O request, it needs to release any Adapter resources it owns. Drivers of DMA devices do this by calling FreeAdapterChannel. Setting Up the DMA HardwareAll packet-based drivers, as well as common buffer drivers for slave devices, have to program the DMA hardware at the beginning of each data transfer. Using the abstract DMA model of Windows 2000, this means loading the Adapter object's mapping registers with physical page addresses taken from the MDL. This setup work is done by the MapTransfer method of the Adapter object, described in Table 12.6. MapTransfer uses the CurrentVa and Length arguments to figure out what physical page addresses to put into the mapping registers. These values must fall somewhere within the range of addresses described by the MDL. Keep in mind that MapTransfer may actually move the contents of the DMA output buffer from one place to another in memory. For example, on an ISA machine, if the pages in the MDL are outside the 16-megabyte DMA limit, calling this function results in data being copied to a buffer in low physical memory. Similarly, if the DMA input buffer is out of range, MapTransfer allocates a buffer in low memory for the transfer. On buses that support 32-bit DMA addresses, no copying or duplicate buffers are required. Drivers of bus master devices also need to call MapTransfer. In this case, however, the function behaves differently, since it doesn't know how to program the bus master's control registers. Instead, MapTransfer simply returns address and length values that the driver again loads into the device's registers. For bus masters with built-in scatter/gather support, this same mechanism allows the driver to create a scatter/gather list for the device. Later sections of this chapter explain how this works. Flushing the Adapter Object CacheAt the end of a data transfer, all packet-based DMA drivers and drivers for common buffer slave devices have to call FlushAdapterBuffers, a method of the Adapter object (see Table 12.7). For devices using the system DMA controller, this function flushes any hardware caches associated with the Adapter object.
In the case of ISA devices doing packet-based DMA, this call releases any low memory used for auxiliary buffers. For input operations, it also copies data back to the physical pages of the caller's input buffer. Refer back to the section on cache coherency for a discussion of this process.
|