传输和接收队列
概述
数据包队列或 数据路径队列 是 NetAdapterCx 中引入的对象,使客户端驱动程序能够在软件驱动程序中更明确地为其硬件功能(如硬件传输和接收队列)建模。 本主题说明如何在 NetAdapterCx 中使用传输和接收队列。
当客户端驱动程序调用 NET_ADAPTER_DATAPATH_CALLBACKS_INIT(通常从其 EVT_WDF_DRIVER_DEVICE_ADD 事件回调函数)时,它会提供两个队列创建回调: EVT_NET_ADAPTER_CREATE_TXQUEUE 和 EVT_NET_ADAPTER_CREATE_RXQUEUE。 客户端分别在这些回调中创建传输和接收队列。
框架在转换为低功耗状态之前清空队列,并在删除适配器之前将其删除。
创建数据包队列
创建数据包队列(传输队列或接收队列)时,客户端必须提供指向以下三个回调函数的指针:
此外,客户端可以在初始化队列配置结构后提供这些可选的回调函数:
创建传输队列
NetAdapterCx 在启动序列的末尾调用EVT_NET_ADAPTER_CREATE_TXQUEUE。 在此回调期间,客户端驱动程序通常执行以下操作:
- (可选)为队列注册开始和停止回调。
- 调用 NetTxQueueInitGetQueueId 以检索要设置的传输队列的标识符。
- 调用 NetTxQueueCreate 以分配队列。
- 如果 NetTxQueueCreate 失败, EvtNetAdapterCreateTxQueue 回调函数应返回错误代码。
- 查询数据包扩展偏移量。
以下示例演示这些步骤在代码中的外观。 为清楚起见,此示例中已排除错误处理代码。
NTSTATUS
EvtAdapterCreateTxQueue(
_In_ NETADAPTER Adapter,
_Inout_ NETTXQUEUE_INIT * TxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG txConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&txConfig,
EvtTxQueueAdvance,
EvtTxQueueSetNotificationEnabled,
EvtTxQueueCancel);
// Optional: register the queue's start and stop callbacks
txConfig.EvtStart = EvtTxQueueStart;
txConfig.EvtStop = EvtTxQueueStop;
// Get the queue ID
const ULONG queueId = NetTxQueueInitGetQueueId(TxQueueInit);
// Create the transmit queue
NETPACKETQUEUE txQueue;
status = NetTxQueueCreate(
TxQueueInit,
&txAttributes,
&txConfig,
&txQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_TX_QUEUE_CONTEXT queueContext = GetMyTxQueueContext(txQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->ChecksumExtension);
// Query Large Send Offload packet extension offset and store it in the context
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_LSO_NAME,
NET_PACKET_EXTENSION_LSO_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->LsoExtension);
return status;
}
创建接收队列
若要从 EVT_NET_ADAPTER_CREATE_RXQUEUE 创建接收队列,请使用与传输队列相同的模式并调用 NetRxQueueCreate。
以下示例演示如何在代码中创建接收队列。 为清楚起见,此示例中已排除错误处理代码。
NTSTATUS
EvtAdapterCreateRxQueue(
_In_ NETADAPTER NetAdapter,
_Inout_ PNETRXQUEUE_INIT RxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG rxConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&rxConfig,
EvtRxQueueAdvance,
EvtRxQueueSetNotificationEnabled,
EvtRxQueueCancel);
// Optional: register the queue's start and stop callbacks
rxConfig.EvtStart = EvtRxQueueStart;
rxConfig.EvtStop = EvtRxQueueStop;
// Get the queue ID
const ULONG queueId = NetRxQueueInitGetQueueId(RxQueueInit);
// Create the receive queue
NETPACKETQUEUE rxQueue;
status = NetRxQueueCreate(
RxQueueInit,
&rxAttributes,
&rxConfig,
&rxQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_RX_QUEUE_CONTEXT queueContext = GetMyRxQueueContext(rxQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query the checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetRxQueueGetExtension(rxQueue, &extension, &queueContext->ChecksumExtension);
return status;
}
轮询模型
NetAdapter 数据路径是一个轮询模型,对一个数据包队列的轮询操作完全独立于其他队列。 轮询模型是通过调用客户端驱动程序的队列提前回调来实现的,如下图所示:
推进数据包队列
对数据包队列执行轮询操作的顺序如下所示:
- OS 为客户端驱动程序提供用于传输或接收的缓冲区。
- 客户端驱动程序将数据包程序化到硬件。
- 客户端驱动程序将已完成的数据包返回到 OS。
轮询操作发生在客户端驱动程序的 EvtPacketQueueAdvance 回调函数中。 客户端驱动程序中的每个数据包队列都由称为 “网络环”的基础数据结构提供支持,该结构包含或链接到系统内存中的实际网络数据缓冲区。 在 EvtPacketQueueAdvance 期间,客户端驱动程序通过控制环内的索引,在传输或接收数据时在硬件和 OS 之间传输缓冲区所有权,在网络环上执行发送和接收操作。
有关网络环的详细信息,请参阅 Net Ring 简介。
有关为传输队列实现 EvtPacketQueueAdvance 的示例,请参阅 使用网络环发送网络数据。 有关为接收队列实现 EvtPacketQueueAdvance 的示例,请参阅 使用网络通道接收网络数据。
启用和禁用数据包队列通知
当客户端驱动程序在数据包队列的网环中收到新数据包时,NetAdapterCx 会调用客户端驱动程序的 EvtPacketQueueSetNotificationEnabled 回调函数。 此回调向客户端驱动程序指示,轮询 EvtPacketQueueAdvance 或 EvtPacketQueueCancel) (将停止,并且不会继续,直到客户端驱动程序调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 或 NetRxQueueNotifyMoreReceivedPacketsAvailable。 通常,PCI 设备使用此回调来启用 Tx 或 Rx 中断。 收到中断后,可以再次禁用中断,客户端驱动程序调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 或 NetRxQueueNotifyMoreReceivedPacketsAvailable 以触发框架再次开始轮询。
为传输队列启用和禁用通知
对于 PCI NIC,启用传输队列通知通常意味着启用传输队列的硬件中断。 当硬件中断触发时,客户端从其 DPC 调用 NetTxQueueNotifyMoreCompletedPacketsAvailable 。
同样,对于 PCI NIC,禁用队列通知意味着禁用与队列关联的中断。
对于具有异步 I/O 模型的设备,客户端通常使用内部标志来跟踪已启用状态。 异步操作完成后,完成处理程序会检查此标志,并调用 NetTxQueueNotifyMoreCompletedPacketsAvailable (如果已设置)。
如果 NetAdapterCx 调用 EvtPacketQueueSetNotificationEnabled 并将 NotificationEnabled 设置为 FALSE,则客户端不得调用 NetTxQueueNotifyMoreCompletedPacketsAvailable ,直到 NetAdapterCx 接下来调用 NotificationEnabled 设置为 TRUE 的此回调函数。
例如:
VOID
MyEvtTxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE TxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// Optional: retrieve queue's WDF context
MY_TX_QUEUE_CONTEXT *txContext = GetTxQueueContext(TxQueue);
// If NotificationEnabled is TRUE, enable transmit queue's hardware interrupt
...
}
VOID
MyEvtTxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetTxQueueNotifyMoreCompletedPacketsAvailable(interruptContext->TxQueue);
}
为接收队列启用和禁用通知
对于 PCI NIC,启用接收队列通知与 Tx 队列非常相似。 这通常意味着启用接收队列的硬件中断。 当硬件中断触发时,客户端从其 DPC 调用 NetRxQueueNotifyMoreReceivedPacketsAvailable 。
例如:
VOID
MyEvtRxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE RxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// optional: retrieve queue's WDF Context
MY_RX_QUEUE_CONTEXT *rxContext = GetRxQueueContext(RxQueue);
// If NotificationEnabled is TRUE, enable receive queue's hardware interrupt
...
}
VOID
MyEvtRxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetRxQueueNotifyMoreReceivedPacketsAvailable(interruptContext->RxQueue);
}
对于 USB 设备或任何其他具有软件接收完成机制的队列,客户端驱动程序应在自己的上下文中跟踪是否启用了队列的通知。 从完成例程 (触发,例如,当某个消息在 USB 连续读取器) 中可用时,如果启用了通知,请调用 NetRxQueueNotifyMoreReceivedPacketsAvailable 。 以下示例演示如何执行此操作。
VOID
UsbEvtReaderCompletionRoutine(
_In_ WDFUSBPIPE Pipe,
_In_ WDFMEMORY Buffer,
_In_ size_t NumBytesTransferred,
_In_ WDFCONTEXT Context
)
{
UNREFERENCED_PARAMETER(Pipe);
PUSB_RCB_POOL pRcbPool = *((PUSB_RCB_POOL*) Context);
PUSB_RCB pRcb = (PUSB_RCB) WdfMemoryGetBuffer(Buffer, NULL);
pRcb->DataOffsetCurrent = 0;
pRcb->DataWdfMemory = Buffer;
pRcb->DataValidSize = NumBytesTransferred;
WdfObjectReference(pRcb->DataWdfMemory);
ExInterlockedInsertTailList(&pRcbPool->ListHead,
&pRcb->Link,
&pRcbPool->ListSpinLock);
if (InterlockedExchange(&pRcbPool->NotificationEnabled, FALSE) == TRUE)
{
NetRxQueueNotifyMoreReceivedPacketsAvailable(pRcbPool->RxQueue);
}
}
取消数据包队列
当 OS 停止数据路径时,首先调用客户端驱动程序的 EvtPacketQueueCancel 回调函数。 在此回调中,客户端驱动程序在框架删除数据包队列之前执行所需的任何处理。 取消传输队列是可选的,具体取决于硬件是否支持正在进行的传输取消,但需要取消接收队列。
在 EvtPacketQueueCancel 期间,驱动程序会根据需要将数据包返回到 OS。 有关传输队列和接收队列取消的代码示例,请参阅 使用网络环取消网络数据。
调用驱动程序的 EvtPacketQueueCancel 回调后,框架将继续轮询驱动程序的 EvtPacketQueueAdvance 回调,直到所有数据包和缓冲区都返回到 OS。