Freigeben über


Übertragen und Empfangen von Warteschlangen in NetAdapterCx-Treibern

Überblick

Übermittlungs- und Empfangswarteschlangen in NetAdapterCx-Treibern ermöglichen Clienttreibern das Modellieren von Hardwarefeatures, z. B. Hardwareübertragungs- und Empfangswarteschlangen, in Software. In diesem Artikel wird erläutert, wie Sie diese Warteschlangen implementieren und verwalten, um die Leistung von Datenpfaden in NetAdapterCx zu optimieren.

Wenn Ihr Clienttreiber NET_ADAPTER_DATAPATH_CALLBACKS_INIT aufruft, in der Regel über die EVT_WDF_DRIVER_DEVICE_ADD Ereignisrückruffunktion, stellt er zwei Rückrufe zum Erstellen von Warteschlangen bereit: EVT_NET_ADAPTER_CREATE_TXQUEUE und EVT_NET_ADAPTER_CREATE_RXQUEUE. Der Client erstellt Übertragungs- und Empfangswarteschlangen in diesen Callbacks.

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 Übertragungswarteschlange oder einer Empfangswarteschlange, muss der Client Zeiger auf die folgenden drei Rückruffunktionen bereitstellen:

Darüber hinaus kann der Client diese optionalen Rückruffunktionen nach der Initialisierung der Warteschlangenkonfigurationsstruktur bereitstellen:

Erstellen einer Übertragungswarteschlange

NetAdapterCx ruft EVT_NET_ADAPTER_CREATE_TXQUEUE am Ende der Power-Up-Sequenz auf. Während dieses Rückrufs führen Clienttreiber in der Regel die folgenden Aktionen aus:

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

Das folgende Beispiel zeigt, wie diese Schritte im Code aussehen können. Aus Gründen der Übersichtlichkeit wird der Fehlerbehandlungscode aus dem Beispiel weggelassen.

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 von EVT_NET_ADAPTER_CREATE_RXQUEUE zu erstellen, verwenden Sie dasselbe Muster wie eine Übertragungswarteschleife und rufen NetRxQueueCreate an.

Das folgende Beispiel zeigt, wie das Erstellen einer Empfangswarteschlange im Code aussehen kann. Aus Gründen der Übersichtlichkeit wird der Fehlerbehandlungscode aus dem Beispiel weggelassen.

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 Pollingmodell, und der Pollingvorgang in einer Paketwarteschlange ist unabhängig von anderen Warteschlangen. Das Polling-Modell wird implementiert, indem die Warteschlangen-Fortschrittsrückrufe des Clienttreibers aufgerufen werden, wie in der folgenden Abbildung dargestellt:

Diagramm, das den Polling-Ablauf in NetAdapterCx zeigt.

Erweiterte Paketwarteschlangen

Der Ablauf eines Abrufprozesses in einer Paketwarteschlange ist wie folgt:

  1. Das Betriebssystem gibt dem Clienttreiber Puffer für die Übertragung oder den Empfang.
  2. Der Clienttreiber programmiert die Pakete auf Hardware.
  3. Der Clienttreiber gibt die abgeschlossenen Pakete an das Betriebssystem zurück.

Abfragevorgänge treten in der EvtPacketQueueAdvance-Rückruffunktion des Clienttreibers auf. Jede Paketwarteschlange in einem Clienttreiber wird durch zugrunde liegende Datenstrukturen unterstützt, die als Netringe bezeichnet werden, die die tatsächlichen Netzwerkdatenpuffer im Systemspeicher enthalten oder verknüpfen. Während evtPacketQueueAdvance führen Clienttreiber Sende- und Empfangsvorgänge auf den Netringen durch Steuern von Indizes innerhalb der Ringe durch, übertragen Pufferbesitz zwischen Hardware und Betriebssystem, wenn Daten übertragen oder empfangen werden.

Weitere Informationen zu Netzringen finden Sie in der 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 Netringen einer Paketwarteschlange empfängt, ruft NetAdapterCx die EvtPacketQueueSetNotificationEnabled-Rückruffunktion des Clienttreibers auf. Dieser Rückruf zeigt einem Clienttreiber an, dass das Abfragen (von EvtPacketQueueAdvance oder EvtPacketQueueCancel) gestoppt wird und erst dann fortgesetzt wird, wenn der Clienttreiber NetTxQueueNotifyMoreCompletedPacketsAvailable oder NetRxQueueNotifyMoreReceivedPacketsAvailable aufruft. In der Regel verwendet ein PCI-Gerät diesen Rückruf, um Tx- oder Rx-Unterbrechungen zu aktivieren. Sobald eine Unterbrechung empfangen wurde, können Interrupts wieder deaktiviert werden, und der Clienttreiber ruft NetTxQueueNotifyMoreCompletedPacketsAvailable oder NetRxQueueNotifyMoreReceivedPacketsAvailable auf, um das Framework auszulösen, um die Abfrage erneut zu starten.

Aktivieren und Deaktivieren der Benachrichtigung für eine Übertragungswarteschlange

Bei einer PCI-NIC bedeutet die Aktivierung der Benachrichtigung über die Übertragungswarteschlange in der Regel, dass die Hardwareunterbrechung der Übertragungswarteschlange aktiviert wird. Wenn ein Hardware-Interrupt ausgelöst wird, ruft der Client aus seinem DPC NetTxQueueNotifyMoreCompletedPacketsAvailable auf.

Ebenso bedeutet die Deaktivierung der Warteschlangenbenachrichtigung für eine PCI-NIC das Deaktivieren des mit der Warteschlange verbundenen Interrupts.

Für ein Gerät, das über ein asynchrones E/A-Modell verfügt, verwendet der Client in der Regel ein internes Flag, um den aktivierten Zustand nachzuverfolgen. Wenn ein asynchroner Vorgang abgeschlossen ist, überprüft der Abschlusshandler dieses Flag und ruft NetTxQueueNotifyMoreCompletedPacketsAvailable auf, wenn es festgelegt ist.

Wenn NetAdapterCx EvtPacketQueueSetNotificationEnabled mit NotificationEnabled auf FALSE aufruft, darf der Client NetTxQueueNotifyMoreCompletedPacketsAvailable erst aufrufen, wenn NetAdapterCx diese Rückruffunktion mit NotificationEnabled auf TRUE aufruft.

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 der Benachrichtigung für eine Empfangswarteschlange

Bei einer PCI-NIC sieht die Aktivierung einer Empfangswarteschlangenbenachrichtigung einer Tx-Warteschlange sehr ähnlich aus. Dies bedeutet in der Regel, dass der Hardware-Interrupt der Empfangsqueue aktiviert wird. Wenn der Hardware-Interrupt ausgelöst wird, ruft der Client NetRxQueueNotifyMoreReceivedPacketsAvailable von seinem 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);
}

Für ein USB-Gerät oder eine andere Warteschlange mit einem Software-Empfangs-Abschlussmechanismus sollte der Clienttreiber in seinem eigenen Kontext nachverfolgen, ob die Benachrichtigung der Warteschlange aktiviert ist. Rufen Sie aus der Abschlussroutine (z. B. ausgelöst, wenn eine Nachricht im USB-Endlosleser verfügbar ist) 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);
    }
}

Paketwarteschlangen abbrechen

Wenn das Betriebssystem den Datenpfad beendet, beginnt er mit dem Aufrufen der EvtPacketQueueCancel-Rückruffunktion des Clienttreibers. Bei diesem Rückruf führen die Clienttreiber alle erforderlichen Verarbeitungen durch, bevor das Framework die Paketwarteschlangen löscht. Der Abbruch für eine Übertragungswarteschlange ist optional und hängt davon ab, ob die Hardware den Abbruch von In-Flight-Übertragungen unterstützt, aber das Abbrechen einer Empfangswarteschlange ist erforderlich.

Während EvtPacketQueueCancel geben Treiber nach Bedarf Pakete an das Betriebssystem zurück. Codebeispiele für die Abbruch der Sende-Warteschlange und Empfangs-Warteschlange finden Sie unter Netzwerkdaten mit Net Rings abbrechen.

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