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 中的 SGDMA 支持在某些方面受到限制,尤其是在多包发送方案中效果不佳。 NDIS 6.0 SGDMA 支持克服了这些限制,同时为微型端口驱动程序提供简单的接口。

NDIS SGDMA 的历史

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

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

这种 SGDMA 支持方法具有以下限制:

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

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

  • NDIS 限制为一次发送一个数据包,以避免由于资源问题而导致的死锁。 这不如发送多个数据包那么高效。

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

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

NDIS SGDMA 支持的优势

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

此方法具有以下优势:

  • 由于 NDIS 提供用于映射网络数据的 HAL 接口,NDIS 使微型端口驱动程序免受映射过程的复杂性和细节的防护。

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

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

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

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

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

注册和取消注册 DMA 通道

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

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

微型端口驱动程序还会传递 NdisMRegisterScatterGatherDma 为处理散点/收集列表而调用的 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 以刷新内存缓存。