Freigeben über


Übertragungs- und Empfangswarteschlangen

Übersicht

Paketwarteschlangen oder Datenpfadwarteschlangen sind Objekte, die in NetAdapterCx eingeführt wurden, um Clienttreibern das Modellieren ihrer Hardwarefeatures, z. B. Hardwareübertragungs- und Empfangswarteschlangen, expliziter in Softwaretreibern zu ermöglichen. In diesem Thema wird erläutert, wie Sie mit Sende- und Empfangswarteschlangen in NetAdapterCx arbeiten.

Wenn Ihr Clienttreiber NET_ADAPTER_DATAPATH_CALLBACKS_INIT aufruft, in der Regel über seine EVT_WDF_DRIVER_DEVICE_ADD-Ereignisrückruffunktion, stellt er zwei Rückrufe für die Warteschlangenerstellung bereit: EVT_NET_ADAPTER_CREATE_TXQUEUE und EVT_NET_ADAPTER_CREATE_RXQUEUE. Der Client erstellt in diesen Rückrufen jeweils Sende- und Empfangswarteschlangen.

Das Framework leert Warteschlangen vor dem Übergang zu einem Energiesparzustand und löscht sie vor dem Löschen des Adapters.

Erstellen von Paketwarteschlangen

Beim Erstellen einer Paketwarteschlange, entweder einer Übertragungs- oder einer Empfangswarteschlange, muss der Client Zeiger auf die folgenden drei Rückruffunktionen bereitstellen:

Darüber hinaus kann der Client diese optionalen Rückruffunktionen bereitstellen, nachdem die Warteschlangenkonfigurationsstruktur initialisiert wurde:

Erstellen einer Übertragungswarteschlange

NetAdapterCx ruft EVT_NET_ADAPTER_CREATE_TXQUEUE ganz am Ende der Einschaltsequenz auf. Während dieses Rückrufs führen Clienttreiber in der Regel folgendes aus:

  • Registrieren Sie optional Start- und Stopprückrufe für die Warteschlange.
  • Rufen Sie NetTxQueueInitGetQueueId auf, um den Bezeichner der einzurichtenden Übertragungswarteschlange abzurufen.
  • Rufen Sie NetTxQueueCreate auf , um eine Warteschlange zuzuweisen.
    • Wenn NetTxQueueCreate fehlschlägt, sollte die Rückruffunktion EvtNetAdapterCreateTxQueue einen Fehlercode zurückgeben.
  • Abfrage nach Paketerweiterungsoffsets.

Das folgende Beispiel zeigt, wie diese Schritte im Code aussehen können. Fehlerbehandlungscode wurde in diesem Beispiel aus Gründen der Übersichtlichkeit nicht verwendet.

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;
}

Erstellen einer Empfangswarteschlange

Um eine Empfangswarteschlange aus EVT_NET_ADAPTER_CREATE_RXQUEUE zu erstellen, verwenden Sie das gleiche Muster wie eine Übertragungswarteschlange, und rufen Sie NetRxQueueCreate auf.

Das folgende Beispiel zeigt, wie das Erstellen einer Empfangswarteschlange im Code aussehen kann. Fehlerbehandlungscode wurde in diesem Beispiel aus Gründen der Übersichtlichkeit nicht verwendet.

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;
}

Abfragemodell

Der NetAdapter-Datenpfad ist ein Abfragemodell, und der Abrufvorgang für eine Paketwarteschlange ist völlig unabhängig von anderen Warteschlangen. Das Abfragemodell wird implementiert, indem die Warteschlangenrückrufe des Clienttreibers aufgerufen werden, wie in der folgenden Abbildung gezeigt:

Diagramm, das den Abfragefluss in NetAdapterCx zeigt.

Voranschreiten von Paketwarteschlangen

Die Reihenfolge eines Abrufvorgangs in einer Paketwarteschlange sieht wie folgt aus:

  1. Das Betriebssystem stellt dem Clienttreiber Puffer zum Senden oder Empfangen bereit.
  2. Der Clienttreiber programmiert die Pakete auf Hardware.
  3. Der Clienttreiber gibt die abgeschlossenen Pakete an das Betriebssystem zurück.

Abfragevorgänge erfolgen innerhalb der Rückruffunktion EvtPacketQueueAdvance des Clienttreibers. Jede Paketwarteschlange in einem Clienttreiber wird durch zugrunde liegende Datenstrukturen unterstützt, die als Netzringe bezeichnet werden, die die tatsächlichen Netzwerkdatenpuffer im Systemspeicher enthalten oder mit ihnen verknüpfen. Während EvtPacketQueueAdvance führen Clienttreiber Sende- und Empfangsvorgänge auf den Netzringen durch, indem sie Indizes innerhalb der Ringe steuern und den Pufferbesitz zwischen Hardware und Betriebssystem übertragen, während Daten übertragen oder empfangen werden.

Weitere Informationen zu Netzringen finden Sie unter Einführung in Netzringe.

Ein Beispiel für die Implementierung von EvtPacketQueueAdvance für eine Übertragungswarteschlange finden Sie unter Senden von Netzwerkdaten mit Netzringen. Ein Beispiel für die Implementierung von EvtPacketQueueAdvance für eine Empfangswarteschlange finden Sie unter Empfangen von Netzwerkdaten mit Netzringen.

Aktivieren und Deaktivieren der Paketwarteschlangenbenachrichtigung

Wenn ein Clienttreiber neue Pakete in den Netzringen einer Paketwarteschlange empfängt, ruft NetAdapterCx die Rückruffunktion EvtPacketQueueSetNotificationEnabled des Clienttreibers auf. Dieser Rückruf gibt einem Clienttreiber an, dass die Abfrage (von EvtPacketQueueAdvance oder EvtPacketQueueCancel) beendet wird und erst fortgesetzt wird, wenn der Clienttreiber NetTxQueueNotifyMoreCompletedPacketsAvailable oder NetRxQueueNotifyMoreReceivedPacketsAvailable aufruft. In der Regel verwendet ein PCI-Gerät diesen Rückruf, um Tx- oder Rx-Interrupts zu aktivieren. Sobald ein Interrupt empfangen wurde, können Interrupts wieder deaktiviert werden, und der Clienttreiber ruft NetTxQueueNotifyMoreCompletedPacketsAvailable oder NetRxQueueNotifyMoreReceivedPacketsAvailable auf, um das Framework zu veranlassen, die Abfrage erneut zu beginnen.

Aktivieren und Deaktivieren der Benachrichtigung für eine Übertragungswarteschlange

Für eine PCI-NIC bedeutet das Aktivieren der Übertragungswarteschlangenbenachrichtigung in der Regel, dass die Hardwareunterbrechung der Übertragungswarteschlange aktiviert wird. Wenn die Hardwareunterbrechung ausgelöst wird, ruft der Client NetTxQueueNotifyMoreCompletedPacketsAvailable über seinen DPC auf.

Für eine PCI-NIC bedeutet das Deaktivieren von Warteschlangenbenachrichtigungen, dass der Interrupt deaktiviert wird, der der Warteschlange zugeordnet ist.

Für ein Gerät mit einem asynchronen E/A-Modell verwendet der Client in der Regel ein internes Flag, um den aktivierten Zustand nachzuverfolgen. Wenn ein asynchroner Vorgang abgeschlossen ist, überprüft der Vervollständigungshandler dieses Flag und ruft NetTxQueueNotifyMoreCompletedPacketsAvailable auf, wenn er festgelegt ist.

Wenn NetAdapterCx EvtPacketQueueSetNotificationEnabled aufruft und NotificationEnabled auf FALSE festgelegt ist, darf der Client NetTxQueueNotifyMoreCompletedPacketsAvailable erst aufrufen, wenn NetAdapterCx als Nächstes diese Rückruffunktion aufruft, wobei NotificationEnabled auf TRUE festgelegt ist.

Beispiel:

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);
}

Aktivieren und Deaktivieren von Benachrichtigungen für eine Empfangswarteschlange

Bei einer PCI-NIC ähnelt das Aktivieren der Empfangswarteschlangenbenachrichtigung sehr einer Tx-Warteschlange. Dies bedeutet in der Regel, den Hardware-Interrupt der Empfangswarteschlange zu aktivieren. Wenn die Hardwareunterbrechung ausgelöst wird, ruft der Client NetRxQueueNotifyMoreReceivedPacketsAvailable über seinen DPC auf.

Beispiel:

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);
}

Bei einem USB-Gerät oder einer anderen Warteschlange mit einem Software-Empfangsmechanismus sollte der Clienttreiber in seinem eigenen Kontext nachverfolgen, ob die Benachrichtigung der Warteschlange aktiviert ist. Rufen Sie in der Vervollständigungsroutine (z. B. ausgelöst, wenn eine Nachricht im kontinuierlichen USB-Reader verfügbar wird) NetRxQueueNotifyMoreReceivedPacketsAvailable auf, wenn die Benachrichtigung aktiviert ist. Das folgende Beispiel zeigt, wie Sie dies tun können.

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);
    }
}

Abbrechen von Paketwarteschlangen

Wenn das Betriebssystem den Datenpfad beendet, wird die Rückruffunktion EvtPacketQueueCancel des Clienttreibers aufgerufen. In diesem Rückruf führen Clienttreiber alle erforderlichen Verarbeitungen aus, bevor das Framework die Paketwarteschlangen löscht. Das Abbrechen für eine Übertragungswarteschlange ist optional und hängt davon ab, ob die Hardware den Abbruch der Übertragung während des Flugs unterstützt, aber der Abbruch für eine Empfangswarteschlange ist erforderlich.

Während EvtPacketQueueCancel geben Treiber Pakete nach Bedarf an das Betriebssystem zurück. Codebeispiele für den Abbruch von Übertragungs- und Empfangswarteschlangen finden Sie unter Abbrechen von Netzwerkdaten mit Netzringen.

Nach dem Aufrufen des EvtPacketQueueCancel-Rückrufs des Treibers fragt das Framework weiterhin den EvtPacketQueueAdvance-Rückruf des Treibers ab, bis alle Pakete und Puffer an das Betriebssystem zurückgegeben wurden.