Share via


傳輸和接收佇列

概觀

封包佇列資料路徑佇列 是在 NetAdapterCx 中引進的物件,可讓用戶端驅動程式建立其硬體功能的模型,例如硬體傳輸和接收佇列,更明確地在軟體驅動程式中。 本主題說明如何在 NetAdapterCx 中使用傳輸和接收佇列。

當您的用戶端驅動程式呼叫 NET_ADAPTER_DATAPATH_CALLBACKS_INIT時,通常是從其 EVT_WDF_DRIVER_DEVICE_ADD 事件回呼函式,它會提供兩個佇列建立回呼: EVT_NET_ADAPTER_CREATE_TXQUEUEEVT_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 資料路徑是輪詢模型,而一個封包佇列上的輪詢作業完全與其他佇列無關。 輪詢模型是藉由呼叫用戶端驅動程式的佇列進階回呼來實作,如下圖所示:

顯示 NetAdapterCx 中輪詢流程的圖表。

推進封包佇列

封包佇列上的輪詢作業順序如下:

  1. OS 會將緩衝區提供給用戶端驅動程式,以便傳輸或接收。
  2. 用戶端驅動程式會將封包程式設定為硬體。
  3. 用戶端驅動程式會將已完成的封包傳回至 OS。

輪詢作業發生在用戶端驅動程式的 EvtPacketQueueAdvance 回 呼函式內。 用戶端驅動程式中的每個封包佇列都受到稱為 net 通道的基礎資料結構支援,其中包含或連結至系統記憶體中實際網路資料緩衝區。 在 EvtPacketQueueAdvance期間,用戶端驅動程式會藉由控制通道內的索引,在硬體與 OS 之間傳輸緩衝區擁有權,以傳輸或接收資料的方式,在 net 通道上執行傳送和接收作業。

如需 net 通道的詳細資訊,請參閱 net 通道簡介

如需實作傳輸佇列 EvtPacketQueueAdvance 的範例,請參閱 使用網路通道傳送網路資料。 如需實作接收佇列 EvtPacketQueueAdvance 的範例,請參閱 使用 net 通道接收網路資料

啟用和停用封包佇列通知

當用戶端驅動程式在封包佇列的 net 通道中收到新的封包時,NetAdapterCx 會叫用用戶端驅動程式的 EvtPacketQueueSetNotificationEnabled 回呼函式。 此回呼會向用戶端驅動程式指出輪詢 EvtPacketQueueAdvanceEvtPacketQueueCancel) 的輪詢 (將會停止,而且在用戶端驅動程式呼叫 NetTxQueueNotifyMoreCompletedPacketsAvailableNetRxQueueNotifyMoreReceivedPacketsAvailable之前不會繼續。 一般而言,PCI 裝置會使用此回呼來啟用 Tx 或 Rx 中斷。 收到中斷之後,可以再次停用中斷,而且用戶端驅動程式會呼叫 NetTxQueueNotifyMoreCompletedPacketsAvailableNetRxQueueNotifyMoreReceivedPacketsAvailable 來觸發架構再次輪詢。

啟用和停用傳輸佇列的通知

針對 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。 如需傳輸佇列和接收佇列取消的程式碼範例,請參閱 使用 net 通道取消網路資料

呼叫驅動程式的 EvtPacketQueueCancel 回呼之後,架構會繼續輪詢驅動程式的 EvtPacketQueueAdvance 回呼,直到所有封包和緩衝區都傳回 OS 為止。