3.2.4.2 Using Direct I/O

Figure 3.4 illustrates how the I/O Manager sets up an IRP, requesting a transfer operation, for drivers that OR their device object(s)’ Flags with DO_DIRECT_IO.

Figure 3.4 Direct I/O on User Buffers

As Figure 3.4 shows, some range of user-space virtual addresses represents the current thread’s buffer, and that buffer’s contents might actually be stored on some number of physically discontiguous pages. An MDL is created to describe this buffer. As already mentioned in Section 3.1, an MDL is an opaque Memory-Manager-defined data structure that maps a particular virtual address range to one or more paged-based physical address ranges.

Figure 3.4 also shows an overview of how drivers can use the IRP’s MdlAddress to transfer data for a read request, when a driver has ORed the device object’s Flags with DO_DIRECT_IO:

  1. The I/O Manager services the current thread’s read request, for which the thread passes a range of user-space virtual addresses representing a buffer.

  2. The I/O Manager or FSD checks the user-supplied buffer for accessibility and calls MmProbeAndLockPages with an MDL, which specifies the range of virtual addresses for the user buffer. MmProbeAndLockPages also fills in the corresponding physical address range(s) in the MDL. As Figure 3.4 shows, an MDL for a virtual range can have several corresponding page-based physical address entries, and the virtual range for a buffer might begin and end at some byte offset from the start of the first and last pages described by an MDL. Note that an MDL is opaque to drivers.

    As Figure 3.4 also shows, the I/O Manager provides a pointer to this MDL (MdlAddress) in an IRP that requests a transfer operation. Until the I/O Manager or file system calls MmUnlockPages after the driver completes the IRP, the physical pages described in the MDL remain locked down and assigned to the buffer. However, the virtual addresses in such an MDL can become invisible (and invalid), even before the IRP is sent to the device driver or to any intermediate driver that might be layered above the device driver.

  3. If the device driver uses system or packet-based busmaster DMA, it calls MmGetMdlVirtualAddress with the IRP’s MdlAddress pointer to get an index for the MDL’s page-based entries. If the device uses PIO and the driver requires system (virtual) addresses, the driver calls MmGetSystemAddressForMdl with the IRP’s MdlAddress pointer to doubly map the user-space virtual addresses in the MDL to a system-space address range (AliasBuff in Figure 3.4).

  4. If the device driver uses system or packet-based busmaster DMA, it calls IoMapTransfer with the index returned by MmGetMdlVirtualAddress in order to read data from the device directly into physical memory when the driver’s AdapterControl routine has access to a DMA channel and/or map registers. If the device uses PIO, the driver uses the doubly mapped MDL’s system-space virtual address range to read data into memory.

When the driver completes the IRP by calling IoCompleteRequest, the I/O Manager or file system releases the MDL’s doubly mapped system-space range if the driver called MmGetSystemAddressForMdl. The I/O manager or file system unlocks the pages described in the MDL, and disposes of the MDL and IRP on the driver’s behalf. For better performance, NT drivers should avoid doubly mapping MDL physical addresses to system space, as described in Step 3, unless they must use virtual addresses. Releasing a doubly mapped system-space address range causes every processor in the machine to have its data cache flushed.

The current user thread’s buffer(s) and the thread itself are guaranteed to be resident in physical memory only while that thread is current. For the thread shown in Figure 3.4, its user buffer’s contents could be paged out to secondary storage while another process’s threads are run. When another process’s thread is run, the system physical memory for the requesting thread’s buffer can be overwritten unless the NT Memory Manager has locked down and preserved the corresponding physical pages that contain the original thread’s buffer.

However, the original thread’s virtual addresses for its buffer do not remain visible while another thread is current, even if the Memory Manager does preserve the buffer’s physical pages. Consequently, NT drivers cannot use a virtual address returned by MmGetMdlVirtualAddress to access memory. Callers of this routine must pass its results to IoMapTransfer (along with the IRP’s MdlAddress pointer) in order to transfer data using system or packet-based DMA.

NT drivers that use DMA also must use an adapter object, described later in Section 3.3. For more information on maintaining cache coherency during DMA and PIO transfers, see also Chapter 16.