Trasmettere e ricevere code

Panoramica

Le code di pacchetti o le code dei percorsi dati sono oggetti introdotti in NetAdapterCx per consentire ai driver client di modellare le funzionalità hardware, ad esempio la trasmissione hardware e la ricezione delle code, in modo più esplicito nei driver software. Questo argomento illustra come usare le code di trasmissione e ricezione in NetAdapterCx.

Quando il driver client chiama NET_ADAPTER_DATAPATH_CALLBACKS_INIT, in genere dalla relativa funzione di callback degli eventi di EVT_WDF_DRIVER_DEVICE_ADD , fornisce due callback di creazione della coda: EVT_NET_ADAPTER_CREATE_TXQUEUE e EVT_NET_ADAPTER_CREATE_RXQUEUE. Il client crea rispettivamente code di trasmissione e ricezione in questi callback.

Il framework svuota le code prima di passare a uno stato a basso consumo e le elimina prima di eliminare l'adattatore.

Creazione di code di pacchetti

Quando si crea una coda di pacchetti, una coda di trasmissione o una coda di ricezione, il client deve fornire puntatori alle tre funzioni di callback seguenti:

Inoltre, il client può fornire queste funzioni di callback facoltative dopo l'inizializzazione della struttura di configurazione della coda:

Creazione di una coda di trasmissione

NetAdapterCx chiama EVT_NET_ADAPTER_CREATE_TXQUEUE alla fine della sequenza di alimentazione. Durante questo callback, i driver client eseguono in genere le operazioni seguenti:

  • Facoltativamente, registrare i callback di avvio e arresto per la coda.
  • Chiamare NetTxQueueInitGetQueueId per recuperare l'identificatore della coda di trasmissione da configurare.
  • Chiamare NetTxQueueCreate per allocare una coda.
    • Se NetTxQueueCreate ha esito negativo, la funzione di callback EvtNetAdapterCreateTxQueue deve restituire un codice di errore.
  • Query per gli offset dell'estensione del pacchetto.

Nell'esempio seguente viene illustrato l'aspetto di questi passaggi nel codice. Il codice di gestione degli errori è stato lasciato fuori da questo esempio per maggiore chiarezza.

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

Creazione di una coda di ricezione

Per creare una coda di ricezione da EVT_NET_ADAPTER_CREATE_RXQUEUE, usare lo stesso modello di una coda di trasmissione e chiamare NetRxQueueCreate.

Nell'esempio seguente viene illustrato come la creazione di una coda di ricezione potrebbe avere un aspetto nel codice. Il codice di gestione degli errori è stato lasciato fuori da questo esempio per maggiore chiarezza.

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

Modello di polling

Il percorso dei dati NetAdapter è un modello di polling e l'operazione di polling in una coda di pacchetti è completamente indipendente da altre code. Il modello di polling viene implementato chiamando i callback di avanzamento della coda del driver client, come illustrato nella figura seguente:

Diagramma che mostra il flusso di polling in NetAdapterCx.

Avanzamento delle code di pacchetti

La sequenza di un'operazione di polling in una coda di pacchetti è la seguente:

  1. Il sistema operativo fornisce buffer al driver client per la trasmissione o la ricezione.
  2. Il driver client programma i pacchetti all'hardware.
  3. Il driver client restituisce i pacchetti completati al sistema operativo.

Le operazioni di polling si verificano all'interno della funzione di callback EvtPacketQueueAdvance del driver client. Ogni coda di pacchetti in un driver client è supportata da strutture di dati sottostanti denominate anelli di rete, che contengono o collegano ai buffer di dati di rete effettivi nella memoria di sistema. Durante EvtPacketQueueAdvance, i driver client eseguono operazioni di invio e ricezione sugli anelli di rete controllando gli indici all'interno degli anelli, trasferendo la proprietà del buffer tra l'hardware e il sistema operativo man mano che i dati vengono trasmessi o ricevuti.

Per altre informazioni sugli anelli di rete, vedere Introduzione agli anelli di rete.

Per un esempio di implementazione di EvtPacketQueueAdvance per una coda di trasmissione, vedere Invio di dati di rete con anelli di rete. Per un esempio di implementazione di EvtPacketQueueAdvance per una coda di ricezione, vedere Ricezione di dati di rete con anelli di rete.

Abilitazione e disabilitazione della notifica della coda di pacchetti

Quando un driver client riceve nuovi pacchetti negli anelli di rete di una coda di pacchetti, NetAdapterCx richiama la funzione di callback EvtPacketQueueSetNotificationEnabled del driver client. Questo callback indica a un driver client che il polling (di EvtPacketQueueAdvance o EvtPacketQueueCancel) verrà arrestato e non continuerà finché il driver client non chiama NetTxQueueNotifyMoreCompletedPacketsAvailable o NetRxQueueNotifyMoreReceivedPacketsAvailable. In genere, un dispositivo PCI usa questo callback per abilitare gli interrupt Tx o Rx. Dopo la ricezione di un interrupt, è possibile disabilitare di nuovo gli interrupt e il driver client chiama NetTxQueueNotifyMoreCompletedPacketsAvailable o NetRxQueueNotifyMoreReceivedPacketsAvailable per attivare nuovamente il polling del framework.

Abilitazione e disabilitazione della notifica per una coda di trasmissione

Per una scheda di interfaccia di rete PCI, l'abilitazione della notifica della coda di trasmissione significa in genere abilitare l'interrupt hardware della coda di trasmissione. Quando l'interruzione dell'hardware viene attivata, il client chiama NetTxQueueNotifyMoreCompletedPacketsAvailable dal proprio DPC.

Analogamente, per una scheda di interfaccia di rete PCI, la disabilitazione della notifica della coda significa disabilitare l'interrupt associato alla coda.

Per un dispositivo con un modello di I/O asincrono, il client usa in genere un flag interno per tenere traccia dello stato abilitato. Al termine di un'operazione asincrona, il gestore di completamento controlla questo flag e chiama NetTxQueueNotifyMoreCompletedPacketsAvailable , se impostato.

Se NetAdapterCx chiama EvtPacketQueueSetNotificationEnabled con NotificationEnabled impostato su FALSE, il client non deve chiamare NetTxQueueNotifyMoreCompletedPacketsAvailable finché NetAdapterCx chiama la funzione di callback con NotificationEnabled impostato su TRUE.

Ad esempio:

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

Abilitazione e disabilitazione della notifica per una coda di ricezione

Per una scheda di interfaccia di rete PCI, l'abilitazione della notifica della coda di ricezione è molto simile a una coda Tx. Ciò significa in genere abilitare l'interrupt hardware della coda di ricezione. Quando l'interruzione dell'hardware viene attivata, il client chiama NetRxQueueNotifyMoreReceivedPacketsAvailable dal proprio DPC.

Ad esempio:

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

Per un dispositivo USB o qualsiasi altra coda con un meccanismo di completamento di ricezione software, il driver client deve tenere traccia nel proprio contesto se la notifica della coda è abilitata. Dalla routine di completamento (attivata ad esempio quando un messaggio diventa disponibile nel lettore continuo USB), chiama NetRxQueueNotifyMoreReceivedPacketsAvailable se la notifica è abilitata. Nell'esempio seguente viene illustrato come eseguire questa operazione.

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

Annullamento delle code di pacchetti

Quando il sistema operativo arresta il percorso dati, inizia richiamando la funzione di callback EvtPacketQueueCancel del driver client. Questo callback è la posizione in cui i driver client eseguono qualsiasi elaborazione necessaria prima che il framework elimini le code di pacchetti. L'annullamento per una coda di trasmissione è facoltativo e dipende dal fatto che l'hardware supporti l'annullamento della trasmissione in anteprima, ma è necessario annullare la coda di ricezione.

Durante EvtPacketQueueCancel, i driver restituiscono pacchetti al sistema operativo in base alle esigenze. Per esempi di codice di trasmissione coda e annullamento della coda di ricezione, vedere Annullamento dei dati di rete con anelli di rete.

Dopo aver chiamato il callback EvtPacketQueueCancel del driver, il framework continua a eseguire il polling del callback evtPacketQueueAdvance del driver fino a quando non vengono restituiti tutti i pacchetti e i buffer al sistema operativo.