拆分 DMA 传输请求
任何驱动程序都可能需要拆分传输请求并执行多个 DMA 传输操作来满足给定的 IRP,具体取决于以下内容:
IoGetDmaAdapter 返回的映射寄存器数
要传输的数据字节,包含在 IRP 的驱动程序 I/O 堆栈位置的 Length 成员中
在系统物理内存中,驱动程序要从中传输数据的缓冲区的页边界数
驱动程序的 DMA 操作上特定于设备的约束。 例如,由于磁盘控制器的限制,系统“AT”磁盘驱动程序必须拆分超过 256 个扇区的传输请求。
驱动程序可以确定传输 IRP 指定的所有数据所需的映射寄存器数,如下所示:
调用 MmGetMdlVirtualAddress,将指针传递到 Irp-MdlAddress> 处的 MDL,以获取缓冲区的起始虚拟地址。 请注意,驱动程序不得尝试使用此虚拟地址访问内存。 MmGetMdlVirtualAddress 返回的值是 MDL 中的索引,不一定是有效的地址。
将 IRP 的驱动程序 I/O 堆栈位置中返回的索引和 Length 值传递给 ADDRESS_AND_SIZE_TO_SPAN_PAGES 宏。
如果 ADDRESS_AND_SIZE_TO_SPAN_PAGES 返回的值大于 IoGetDmaAdapter 返回的 NumberOfMapRegisters 值,则驱动程序无法在单个 DMA 操作中传输此 IRP 的所有请求数据。 相反,它必须执行以下操作:
将缓冲区拆分为大小以适应可用映射寄存器数 (和任何特定于设备的 DMA 约束) 的部分。
执行尽可能多的 DMA 操作以满足传输请求。
例如,假设 ADDRESS_AND_SIZE_TO_SPAN_PAGES 指示需要 12 个映射寄存器来满足传输请求,但 IoGetDmaAdapter 返回的 NumberOfMapRegisters 值仅为 5。 (假设没有特定于设备的 DMA 约束。) 在这种情况下,驱动程序必须执行三个 DMA 传输操作,调用 MapTransfer 三次以传输 IRP 请求的所有数据。
当映射寄存器不足以通过单个 I/O 操作满足 IRP 时,系统的 DMA 设备驱动程序使用各种技术拆分 DMA 传输。 一种要使用的技术如下:
调用 IoAllocateMdl 以分配描述用户缓冲区部分的 MDL。
调用 MmProbeAndLockPages 以锁定用户缓冲区的这一部分。
传输缓冲区的该部分的数据。
调用 MmUnlockPages 并执行以下任一操作:
- 如果驱动程序在步骤 1 中分配的 MDL 足够大,用于下一个传输部分,请调用 MmPrepareMdlForReuse 并重复步骤 2 到 4。
- 否则,请调用 IoFreeMdl 并重复步骤 1 到 4。
传输所有数据后,调用 MmUnlockPages 和 IoFreeMdl 。
如果最高级别的驱动程序无法在内存受限的计算机中使用 MmProbeAndLockPage 锁定整个用户缓冲区,它可以执行以下操作:
调用 IoBuildSynchronousFsdRequest 以分配部分传输 IRP 并锁定用户缓冲区的一部分。 锁定区域通常是 PAGE_SIZE 的倍数,或者根据基础设备的传输容量调整大小。
为部分传输 IRP 调用 IoCallDriver ,并调用 KeWaitForSingleObject 以等待驱动程序设置为与其部分传输 IRP 关联的事件对象,如果较低的驱动程序返回STATUS_PENDING。
当它重新获得控制权时,重复步骤 1 和 2,直到传输所有数据,然后完成原始 IRP。
当存储类驱动程序拆分基础 SCSI 端口/微型端口驱动程序的大型传输请求时,它会为传输请求的每个部分分配额外的 IRP。 它为每个驱动程序分配的 IRP 注册 IoCompletion 例程,以跟踪完整传输请求的状态并释放驱动程序分配的 IRP。 然后使用 IoCallDriver 将这些 IRP 发送到端口驱动程序。
其他类/端口驱动程序仅当类驱动程序可以确定有多少个映射寄存器可用于端口驱动程序时,才能使用此方法。 端口驱动程序必须将此配置信息存储在配对类驱动程序的注册表中,或者配对的驱动程序必须使用内部设备 I/O 控制请求定义专用接口,以便将有关可用映射寄存器数量的配置信息从端口驱动程序传递到类驱动程序。
整体驱动程序 (也就是说,不属于 DMA 设备的类/端口对) 的驱动程序必须自行拆分大型传输请求。 此类驱动程序通常会将大型请求拆分为多个部分,并执行一系列 DMA 操作以满足 IRP。
如果传输请求太大,基础设备驱动程序无法处理,则更高级别的驱动程序可以调用 MmGetMdlVirtualAddress 和 IoBuildPartialMdl,然后为基础设备驱动程序设置一系列部分传输 IRP。