NDIS 分散/聚合 DMA

注意

对于 Arm 和 Arm64 处理器,我们强烈建议 NDIS 驱动程序编写器使用 WDF DMA 或 WDM DMA,而不是 NDIS 散点/收集 DMA。

有关 WDF DMA 的详细信息,请参阅 处理 KMDF 驱动程序中的 DMA 操作

有关 WDM DMA 的详细信息,请参阅 管理驱动程序输入/输出的 DMA 相关子主题。

NDIS 微型端口驱动程序可以使用散点/收集 DMA (SGDMA) 方法在 NIC 和系统内存之间传输数据。 成功的 DMA 传输要求数据的物理地址位于 NIC 支持的地址范围内。 HAL 为驱动程序提供了一种机制,用于获取 MDL 链的物理地址列表,如有必要,会将数据双重缓冲到物理地址范围。

在 NDIS 6.0 之前的 NDIS 版本中,微型端口驱动程序和 NDIS 中的 SDKMA 支持在某些方面受到限制,特别是在多包发送方案中效果不佳。 NDIS 6.0 SGDMA 支持克服了这些限制,同时为微型端口驱动程序提供简单的界面。

NDIS SGDMA 的历史

在 NDIS 6.0 之前的 NDIS 版本中,NDIS 获取每个数据包的散点收集 (SG) 列表,然后再将数据包发送到微型端口驱动程序。 NDIS 还处理原始尝试获取 SG 列表因碎片过多而失败的情况。 在这种情况下,NDIS 会将数据包双缓冲到连续缓冲区,然后重试。 如果 NIC 支持的物理地址(例如,数据的物理地址高于 32 位最大值),NIC 不支持 64 位 DMA,则 HAL 还可以对数据进行双重缓冲。

为了避免死锁情况,NDIS 会获取数据包的 SG 列表,并一次发送一个数据包。 如果在将数据包发送到微型端口驱动程序之前 NDIS 尝试映射所有数据包,系统可能会耗尽资源。 在这种情况下,NDIS 将等待地图寄存器变得可用,而某些映射寄存器被锁定为尚未发送的数据包。 无法重复使用锁定的数据包。

对 SGDMA 支持的此方法具有以下限制:

  • 由于数据包在到达微型端口驱动程序之前映射,因此驱动程序无法针对过于碎片的小数据包或数据包进行优化。 微型端口驱动程序无法将数据包双重缓冲到已知的物理地址。

  • 不能保证传递给微型端口驱动程序的 NDIS 的物理地址数组映射到原始数据的虚拟地址。 因此,如果在发送 MDL 链之前驱动程序更改了虚拟地址中的数据,则对数据的修改不会反映在物理地址中的数据中。 在这种情况下,NIC 发送未修改的数据。

  • NDIS 一次只能发送一个数据包,以避免由于资源问题而导致死锁。 这与发送多个数据包不一样高效。

  • 由于 NDIS 无法确定微型端口驱动程序的传输功能,因此无法预分配 SG 列表缓冲区的存储。 因此,NDIS 必须在运行时分配必要的存储。 这与预分配存储不一样高效。

  • 应在 IRQL = DISPATCH_LEVEL调用分配 SG 列表的 HAL 函数。 NDIS 没有当前的 IRQL 信息,因此它必须将 IRQL 设置为DISPATCH_LEVEL,即使它已在DISPATCH_LEVEL。 如果 IRQL 已在DISPATCH_LEVEL,则此方法效率不高。

NDIS SGDMA 支持的优势

在 NDIS 6.0 及更高版本中,NDIS 不会在将数据缓冲区发送到微型端口驱动程序之前映射数据缓冲区。 相反,NDIS 为驱动程序提供用于映射网络数据的接口。

此方法具有以下优势:

  • 由于 NDIS 提供到 HAL 的接口来映射网络数据,NDIS 会保护微型端口驱动程序免受映射过程的复杂性和详细信息的防护。

  • 微型端口驱动程序在映射数据之前有权访问数据。 因此,对原始数据所做的任何更改都反映在 SG 列表表示的数据中,即使 NDIS 或 HAL 对数据进行双缓冲也是如此。

  • 微型端口驱动程序可以通过将它们复制到具有已知物理地址的预分配缓冲区来优化小型或高度碎片数据包的传输。 此方法可避免不需要映射,因此可提高系统性能。

  • NDIS 可以安全地将多个缓冲区发送到微型端口驱动程序。 这会导致对微型端口驱动程序的调用减少,从而提高了系统性能。

  • 微型端口驱动程序可以将 SG 列表的内存预分配为传输描述符块的一部分。 因此,运行时不需要 NDIS 或微型端口驱动程序为 SG 列表分配内存。

  • 由于微型端口驱动程序可以在 IRQL = DISPATCH_LEVEL上运行,因此微型端口驱动程序可以避免不必要的调用来引发 IRQL DISPATCH_LEVEL。 例如,由于完成发送发生在中断 DPC 的上下文中,微型端口驱动程序可以在不引发 IRQL 的情况下释放 SG 列表。

注册和取消注册 DMA 通道

NDIS 微型端口驱动程序从其 MiniportInitializeEx 函数调用 NdisMRegisterScatterGatherDma 函数,以便向 NDIS 注册 DMA 通道。

微型端口驱动程序在 DmaDescription 参数中将 DMA 说明传递给 NdisMRegisterScatterGatherDmaNdisMRegisterScatterGatherDma 返回缓冲区的大小,该缓冲区的大小应足以容纳散点/收集列表。 微型端口驱动程序应使用此大小来预分配散点/收集列表的存储。

微型端口驱动程序还传递 NdisMRegisterScatterGatherDma 作为 NDIS 调用以处理散点/收集列表 的 MiniportXxx 函数的入口点。 NDIS 在 HAL 为缓冲区生成散点/收集列表后,调用微型端口驱动程序的 MiniportProcessSGList 函数。 NdisMRegisterScatterGatherDmapNdisMiniportDmaHandle 参数中提供句柄,微型端口驱动程序必须在后续调用 NDIS 散点/收集 DMA 函数时使用。

NDIS 微型端口驱动程序从其 MiniportHaltEx 函数调用 NdisMDeregisterScatterGatherDma 函数,以释放散点/收集 DMA 资源。

分配和释放散点/收集列表

NDIS 微型端口驱动程序在其 MiniportSendNetBufferLists 函数中调用 NdisMAllocateNetBufferSGList 函数。 微型端口驱动程序对必须映射的每个NET_BUFFER结构调用 NdisMAllocateNetBufferSGList 一次。 资源可用且 HAL 已准备好 SG 列表后,NDIS 将调用驱动程序的 MiniportProcessSGList 函数。 NDIS 可以在微型端口驱动程序调用 NdisMAllocateNetBufferSGList 之前或之后调用 MiniportProcessSGList

为了提高系统性能,从在关联NET_BUFFER_DATA结构的 CurrentMdl 成员指定的 MDL 开头的网络数据生成散点/收集列表。 SG 列表中的网络数据的开始与 SG 列表的开头偏移,该值由关联NET_BUFFER_DATA结构的 CurrentMdlOffset 成员中指定的值偏移。

处理 DPC 进行发送完成中断时,微型端口驱动程序不再需要 SG 列表后,微型端口驱动程序应调用 NdisMFreeNetBufferSGList 函数来释放 SG 列表。

注意驱动程序或硬件仍在访问与散点/收集列表关联的NET_BUFFER结构描述的内存时,请勿调用 NdisMFreeNetBufferSGList。 

访问收到的数据之前,微型端口驱动程序必须调用 NdisMFreeNetBufferSGList 来刷新内存缓存。