使用公用缓冲区系统 DMA

使用系统 DMA 控制器的自动初始化模式的驱动程序必须为可以执行 DMA 传输的缓冲区分配内存。驱动程序调用 AllocateCommonBuffer 来获取此缓冲区,通常来自处理IRP_MN_START_DEVICE请求的 DispatchPnP 例程。 下图显示了驱动程序如何分配缓冲区并将其虚拟地址范围映射到系统物理内存。

说明驱动程序如何为系统 dma 分配通用缓冲区的示意图。

如上图所示,驱动程序执行以下步骤为系统 DMA 分配缓冲区:

  1. 驱动程序调用 AllocateCommonBuffer,将指针传递到 IoGetDmaAdapter 返回的适配器对象,以及为其缓冲区请求的长度(以字节为单位)。 若要以经济方式使用内存,缓冲区的输入 Length 值应小于或等于 PAGE_SIZE或应是PAGE_SIZE的整数倍数。

  2. 如果 AllocateCommonBuffer 返回 NULL 指针,驱动程序应释放已声明的任何系统资源,并返回STATUS_INSUFFICIENT_RESOURCES以响应 IRP_MN_START_DEVICE 请求。

    否则, AllocateCommonBuffer 在系统虚拟地址空间中分配请求的内存量,并返回指向该缓冲区的两种不同类型的指针:

    • 上图中缓冲区的 LogicalAddress (BufferLogicalAddress) ,驱动程序必须为此提供存储,但随后应忽略存储

    • 上图中缓冲区虚拟地址 (BufferVirtualAddress) ,驱动程序还必须存储该地址,以便它可以生成描述其缓冲区的 MDL 以执行 DMA 操作

    驱动程序应将这些指针存储在设备扩展或其他驱动程序分配的驻留内存中。

  3. 驱动程序调用 IoAllocateMdl 为缓冲区分配 MDL。 驱动程序传递 AllocateCommonBuffer 返回的缓冲区的 VirtualAddress 及其缓冲区的 Length 以分配 MDL。

  4. 驱动程序使用 IoAllocateMdl 返回的指针调用 MmBuildMdlForNonPagedPool,以将其驻留缓冲区的虚拟地址范围映射到系统物理内存。

分配公共缓冲区并映射其虚拟地址范围后,从属设备的驱动程序可以开始处理请求 DMA 传输的 IRP。 为此,驱动程序调用以下常规支持例程序列:

  1. 由驱动程序编写者自行决定, RtlMoveMemory 将数据从锁定的用户缓冲区复制到驱动程序分配的公共缓冲区,以便传输到设备

  2. 当驱动程序准备好针对 DMA 对设备进行编程并需要系统 DMA 控制器时,AllocateAdapterChannel

  3. MapTransfer,使用描述驱动程序分配的公共缓冲区的 MDL,为传输操作设置系统 DMA 控制器

    请注意,驱动程序仅调用 MapTransfer 一次,以将系统 DMA 控制器设置为使用其公共缓冲区。 在传输期间,驱动程序可以调用 ReadDmaCounter 来确定剩余要传输的字节数,并在必要时调用 RtlMoveMemory 以向用户缓冲区或从用户缓冲区复制更多数据。

  4. 当驱动程序完成从属设备的 DMA 传输时,FlushAdapterBuffers

  5. 只要传输了所有请求的数据,或者驱动程序必须因设备 I/O 错误而使 IRP 失败,则 FreeAdapterChannel

IoGetDmaAdapter 返回的适配器对象指针是除 RtlMoveMemory 以外的每个支持例程的必需参数。

各个驱动程序在不同点调用此支持例程序列,具体取决于实现每个驱动程序以为其设备提供服务的方式。 例如,一个驱动程序的 StartIo 例程可能会调用 AllocateAdapterChannel,另一个驱动程序可能会从从驱动程序创建的互锁队列中删除 IRP 的例程发出此调用,另一个驱动程序可能在其从属 DMA 设备指示它已准备好传输数据时发出此调用。