windows 驱动开发-DMA技术(二)

前面描述了DMA技术中适配器相关的部分以及DMA的分类,接下来看一下系统具体在支持两种DMA时候的操作的细微差别。

此处解释一下***Scatter/Gather,***这个也翻译为散点/收集,是指指示设备能够读取或写入内存中的任何区域,而不仅仅是特定范围。在早期,DMA读写的区域必须是一个整页面,但是这种要求其实对于内核来说,不是每时每刻都可以满足的,故和MDL类似,系统也支持将不在同一个页面上N个数据块映射为一整个页面。

使用基于数据包的系统 DMA

使用基于数据包的 DMA 的从属设备的驱动程序在处理请求 DMA 传输的 IRP 时调用以下常规支持例程序列:

  • 尝试分配系统 DMA 控制器 ;
  • 当驱动程序准备好针对 DMA 对设备进行编程并需要系统 DMA 控制器调用AllocateAdapterChannel,AllocateAdapterChannel 依次调用驱动程序的 AdapterControl 例程。
  • MmGetMdlVirtualAddress 获取 MDL 中的索引,在对 MapTransfer 的初始调用中需要作为参数MapTransfer 为传输操作对系统 DMA 控制器进行编程
  • 驱动程序可能需要多次调用 MapTransfer 来传输所有请求的数据;
  • FlushAdapterBuffers ,就在每个 DMA 传输操作与从属设备之间
  • 如果驱动程序必须多次调用 MapTransfer 以传输所有请求的数据,则它必须调用 FlushAdapterBuffers 的次数与调用 MapTransfer 的次数一样多。
  • FreeAdapterChannel 只要传输了所有请求的数据,或者驱动程序因设备 I/O 错误导致 IRP 失败

IoGetDmaAdapter 返回的适配器对象指针是每个例程的必需参数,KeFlushIoBuffers 和 MmGetMdlVirtualAddress 除外,后者需要指向在 Irp->MdlAddress 传递的 MDL 的指针。

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

分配适配器通道

为了准备基于数据包的系统 DMA,驱动程序在收到IRP_MJ_READ或IRP_MJ_WRITE请求后调用 KeFlushIoBuffers 和 AllocateAdapterChannel。

在驱动程序调用这些例程之前,其 DispatchRead 或 DispatchWrite 例程 (或其他处理 DMA 传输) 的调度例程应已检查 IRP 参数的有效性。 调度例程还可能已将 IRP 排到另一个驱动程序例程进行进一步处理。

调用 AllocateAdapterChannel 的 驱动程序例程必须在 IRQL=DISPATCH_LEVEL执行。 除了指向 IoGetDmaAdapter 返回的适配器对象的指针之外,驱动程序还必须在调用 AllocateAdapterChannel 时提供以下内容:

  • 指向目标设备对象的指针
  • 其 AdapterControl 例程的入口点
  • 指向 AdapterControl 例程将使用的任何驱动程序确定的上下文信息的指针

AllocateAdapterChannel 将驱动程序的 AdapterControl 例程排队,该例程在系统 DMA 控制器分配给此驱动程序时运行,并且已为驱动程序的 DMA 操作分配了一组 映射寄存器。

输入时,AdapterControl 例程接收在对 AllocateAdapterChannel 的调用中传递的 DeviceObject 和 Context 指针,以及分配的映射寄存器 (MapRegisterBase) 句柄。

如果驱动程序具有 StartIo 例程,AdapterControl 例程还会收到指向 DeviceObject->CurrentIrp 的指针。 如果驱动程序管理自己的 IRP 队列,而不是使用StartIo 例程,则驱动程序应包含指向当前 IRP 的指针,作为它在调用 AllocateAdapterChannel 时传递的上下文的一部分。

AdapterControl 例程通常执行以下操作:

  • 保存或初始化驱动程序维护的有关 DMA 操作的任何上下文。 上下文可能包括驱动程序必须传递给 MapTransfer 和 FlushAdapterBuffers 的输入 MapRegisterBase 句柄,以及从 IRP 中的 I/O 堆栈位置请求的传输的长度。
  • 调用 MmGetMdlVirtualAddress 和 MapTransfer。
  • 设置从属设备以启动传输操作;
  • 返回值 KeepObject;

每个 AdapterControl 例程都必须返回 IO_ALLOCATION_ACTION类型的系统定义值。 对于使用系统 DMA 的驱动程序, AdapterControl 例程必须返回值 KeepObject。 这允许驱动程序保留系统 DMA 控制器和分配的映射寄存器的所有权,直到传输了所有请求的数据。

由于 AdapterControl 例程无法等待从属设备执行 DMA 操作,因此每个 AdapterControl 例程至少必须执行以下操作:

  • 将上下文信息(尤其是 MapRegisterBase 句柄)保存在驱动程序的设备扩展、控制器扩展或其他驱动程序可访问的驻留存储区域中 (驱动程序 分配的非分页池中);
  • 返回 KeepObject;

另一个驱动程序例程 (可能是 DpcForIsr 例程) 必须在每个 DMA 传输操作完成时调用 FlushAdapterBuffers 。 如果需要多次设置 DMA 控制器以满足当前 IRP 的传输请求,此例程还必须再次调用 MapTransfer 和 FlushAdapterBuffers 。

当驱动程序满足当前 IRP 的请求时,它必须调用 FreeAdapterChannel。 此支持例程应在上次调用当前 IRP 的 FlushAdapterBuffers 之后立即调用,以便系统 DMA 控制器可供任何驱动程序 迅速满足其他传输请求。

具有Scatter/Gather功能的从属设备的驱动程序还应从其 AdapterControl 例程返回 KeepObject。 当驱动程序必须拆分给定 DMA 请求时,当系统 DMA 控制器在 DMA 操作之间重新编程时,设备必须能够等待。 在某些 Windows 平台上,此类设备每次 DMA 操作最多可以传输一页数据,因为 HAL 只能将单个映射寄存器分配给该设备的驱动程序。

设置系统DMA

当 AllocateAdapterChannel 将控制权转移到驱动程序的 AdapterControl 例程时,驱动程序将"拥有"系统 DMA 控制器和一组映射寄存器。 然后,驱动程序必须为传输操作设置 DMA 控制器,如下图所示:

如果驱动程序具有 StartIo 例程,则 AllocateAdapterChannel 会将 PIrp 参数中指向 DeviceObject-CurrentIrp> 的指针传递到 AdapterControl 例程。 但是,如果驱动程序管理自己的 IRP 队列,则驱动程序应包含指向当前 IRP 的指针,作为它传递给 AdapterControl 的上下文的一部分。

如上图所示,驱动程序的 AdapterControl 例程设置 DMA 传输,如下所示:

1. AdapterControl 例程获取开始传输的地址。 对于满足 IRP 所需的初始传输, AdapterControl 例程调用 MmGetMdlVirtualAddress,将指针传递到 Irp->MdlAddress 处的 MDL,该指针描述此 DMA 传输的缓冲区;MmGetMdlVirtualAddress 返回一个虚拟地址,驱动程序可以使用该地址作为应开始传输的系统物理地址的索引;如果 IRP 需要多个传输操作,驱动程序将计算更新的起始地址,如后面所述。

2. AdapterControl 例程保存 MmGetMdlVirtualAddress 返回或在步骤 1 中计算的地址。 此地址是 MapTransfer (CurrentVa) 的必需参数。

3. AdapterControl 例程调用 MapTransfer 来设置系统 DMA 控制器,并提供以下参数:

  • IoGetDmaAdapter 返回的适配器对象指针;
  • 指向当前 IRP 的 Irp-> MdlAddress处的 MDL (Mdl 的指针),通过 AllocateAdapterChannel 传递给驱动程序的 AdapterControl 例程的 MapRegisterBase 句柄;
  • 如果这是第一次调用 IRP 的 MapTransfer,则 mmGetMdlVirtualAddress 返回的 Current) Va 值 ;否则,驱动程序会提供更新的 CurrentVa 值,指示下一个传输操作应在缓冲区中的哪个位置启动;
  • 指向变量的指针 (Length) 指示此传输的字节数;
  • 如果驱动程序可以通过一次调用 MapTransfer 来传输请求的所有数据,并且对其 DMA 操作没有特定于设备的约束,则可以在驱动程序的 I/O 堆栈位置的 IRP 中将 Length 设置为 Length 值。 最多可以 (PAGE_SIZE * IoGetDmaAdapter) 返回的 NumberOfMapRegisters,以字节为单位。 否则,驱动程序必须拆分请求(如拆分传输请求中所述),并且必须在对当前 IRP 的 MapTransfer 的后续调用中更新 Length 值;
  • 一个布尔值 (WriteToDevice) ,指示传输操作的方向, TRUE 将数据从系统内存传输到设备;反之亦然;

4. MapTransfer 返回逻辑地址,使用系统 DMA 的驱动程序必须忽略此值;

5. AdapterControl 例程为 DMA 操作设置设备;

6. AdapterControl 例程返回 KeepObject;

当设备指示其当前 DMA 操作已完成时,驱动程序应调用 FlushAdapterBuffers,通常是从驱动程序的 DpcForIsr 例程调用。

完成 DMA 操作的 DpcForIsr 例程或其他驱动程序例程调用 FlushAdapterBuffers,以确保将系统 DMA 控制器中缓存的任何数据读入系统内存或写出到设备。 如果需要重新编程系统 DMA 控制器以便为当前 IRP 传输更多数据,则同一例程还必须再次调用 MapTransfer ;同样它必须在每次传输操作后再次调用 FlushAdapterBuffers 。

如果驱动程序必须多次为当前 IRP 调用 MapTransfer ,它将在每次调用中提供相同的适配器对象指针、 Mdl 指针、 MapRegisterBase 句柄和传输方向。 但是,驱动程序必须先更新 CurrentVa 和 Length 参数,然后才能对 MapTransfer 进行第二次和任何后续调用。 若要计算其中每个参数的更新值,请使用以下公式:

  • CurrentVa = 在对 MapTransfer的前面调用中请求的 CurrentVa + Length;
  • Length = 最小剩余要传输的长度, (PAGE_SIZE * IoGetDmaAdapter) ) 返回的 NumberOfMapRegisters;

每个驱动程序应维护的有关其 DMA 传输的上下文信息取决于其特定设备的需求。 典型上下文可能包括 MDL (CurrentVa) 中的当前虚拟地址、到目前为止传输的字节数、要传输的剩余字节数、可能指向当前 IRP 的指针,以及驱动程序编写者认为有用的任何其他信息。

当请求的传输完成时,或者如果驱动程序必须返回 IRP 的错误状态,则驱动程序应立即调用 FreeAdapterChannel ,以释放系统 DMA 控制器以供其他驱动程序和此驱动程序使用。

相关推荐
呉師傅10 小时前
得力P2500、M2500系列打印机硒鼓加粉及清零方法【纯享版】
运维·网络·windows·电脑
ZhangJun9511 小时前
从零写一个类鲁大师的硬件检测小工具,软件附上,可直接使用
windows·测试工具·电脑·笔记本电脑·硬件架构
imperialeast13 小时前
创建WAV文件 韩文、德文、俄文等unicode音乐标签
windows·小程序
易点云徐源13 小时前
社保费管理客户端打开后进度条卡20%,等待1分钟提示获取接收配置失败
windows
I'mChloe14 小时前
Windows部署BiliNote:Docker安装、AI视频转写、Markdown笔记与cpolar远程访问
人工智能·windows·docker
荣合技术服务15 小时前
EMQX mqtt在 Windows 系统上的安装教程
windows·mqtt·emqx
学逆向的15 小时前
虚拟内存与物理内存
windows·网络安全·api·win32
小小小小钰儿16 小时前
2.1-Windows本地部署大模型
人工智能·windows·计算机·网络安全·操作系统·编程
ayaya_mana16 小时前
MCTier 组网联机工具:安装、配置与私有化部署
网络·windows·内网穿透·联机
ysu_031418 小时前
Docker-Desktop-WSL2实战指南-Windows从安装配置到项目容器化
windows·python·docker·容器·composer·dockerfile·wsl2