Transmitir e receber filas

Visão geral

Filas de pacotes ou filas de caminho de dados são objetos introduzidos no NetAdapterCx para permitir que os drivers cliente modelem seus recursos de hardware, como filas de transmissão e recebimento de hardware, mais explicitamente em drivers de software. Este tópico explica como trabalhar com filas de transmissão e recebimento no NetAdapterCx.

Quando o driver cliente chama NET_ADAPTER_DATAPATH_CALLBACKS_INIT, normalmente de sua função de retorno de chamada de evento EVT_WDF_DRIVER_DEVICE_ADD , ele fornece dois retornos de chamada de criação de fila: EVT_NET_ADAPTER_CREATE_TXQUEUE e EVT_NET_ADAPTER_CREATE_RXQUEUE. O cliente cria filas de transmissão e recebimento nesses retornos de chamada, respectivamente.

A estrutura esvazia as filas antes de fazer a transição para um estado de baixa energia e as exclui antes de excluir o adaptador.

Criando filas de pacotes

Ao criar uma fila de pacotes, uma fila de transmissão ou uma fila de recebimento, o cliente deve fornecer ponteiros para as três funções de retorno de chamada a seguir:

Além disso, o cliente pode fornecer essas funções opcionais de retorno de chamada depois de inicializar a estrutura de configuração da fila:

Criando uma fila de transmissão

O NetAdapterCx chama EVT_NET_ADAPTER_CREATE_TXQUEUE no final da sequência de energia. Durante esse retorno de chamada, os drivers de cliente normalmente fazem o seguinte:

  • Opcionalmente, registre iniciar e parar retornos de chamada para a fila.
  • Chame NetTxQueueInitGetQueueId para recuperar o identificador da fila de transmissão a ser configurada.
  • Chame NetTxQueueCreate para alocar uma fila.
    • Se NetTxQueueCreate falhar, a função de retorno de chamada EvtNetAdapterCreateTxQueue deverá retornar um código de erro.
  • Consultar deslocamentos de extensão de pacote.

O exemplo a seguir mostra como essas etapas podem ser exibidas no código. O código de tratamento de erros foi deixado de fora deste exemplo para maior clareza.

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

Criando uma fila de recebimento

Para criar uma fila de recebimento de EVT_NET_ADAPTER_CREATE_RXQUEUE, use o mesmo padrão que uma fila de transmissão e chame NetRxQueueCreate.

O exemplo a seguir mostra como a criação de uma fila de recebimento pode parecer no código. O código de tratamento de erros foi deixado de fora deste exemplo para maior clareza.

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

Modelo de sondagem

O caminho de dados do NetAdapter é um modelo de sondagem e a operação de sondagem em uma fila de pacotes é completamente independente de outras filas. O modelo de sondagem é implementado chamando os retornos de chamada antecipados da fila do driver cliente, conforme mostrado na figura a seguir:

Diagrama que mostra o fluxo de sondagem no NetAdapterCx.

Avançar filas de pacotes

A sequência de uma operação de sondagem em uma fila de pacotes é a seguinte:

  1. O sistema operacional fornece buffers para o driver cliente para transmissão ou recebimento.
  2. O driver cliente programa os pacotes para hardware.
  3. O driver cliente retorna os pacotes concluídos para o sistema operacional.

As operações de sondagem ocorrem dentro da função de retorno de chamada EvtPacketQueueAdvance do driver cliente. Cada fila de pacotes em um driver de cliente é apoiada por estruturas de dados subjacentes chamadas anéis de rede, que contêm ou vinculam aos buffers de dados de rede reais na memória do sistema. Durante evtPacketQueueAdvance, os drivers cliente executam operações de envio e recebimento nos anéis de rede controlando índices dentro dos anéis, transferindo a propriedade do buffer entre o hardware e o sistema operacional à medida que os dados são transmitidos ou recebidos.

Para obter mais informações sobre anéis de rede, consulte Introdução aos anéis de rede.

Para obter um exemplo de implementação de EvtPacketQueueAdvance para uma fila de transmissão, consulte Enviando dados de rede com anéis de rede. Para obter um exemplo de implementação de EvtPacketQueueAdvance para uma fila de recebimento, consulte Recebendo dados de rede com anéis de rede.

Habilitar e desabilitar a notificação da fila de pacotes

Quando um driver de cliente recebe novos pacotes nos anéis de rede de uma fila de pacotes, o NetAdapterCx invoca a função de retorno de chamada EvtPacketQueueSetNotificationEnabled do driver cliente. Esse retorno de chamada indica a um driver de cliente que a sondagem (de EvtPacketQueueAdvance ou EvtPacketQueueCancel) será interrompida e não continuará até que o driver cliente chame NetTxQueueNotifyMoreCompletedPacketsAvailable ou NetRxQueueNotifyMoreReceivedPacketsAvailable. Normalmente, um dispositivo PCI usa esse retorno de chamada para habilitar interrupções Tx ou Rx. Depois que uma interrupção é recebida, as interrupções podem ser desabilitadas novamente e o driver cliente chama NetTxQueueNotifyMoreCompletedPacketsAvailable ou NetRxQueueNotifyMoreReceivedPacketsAvailable para disparar a estrutura para iniciar a sondagem novamente.

Habilitar e desabilitar a notificação para uma fila de transmissão

Para uma NIC PCI, habilitar a notificação de fila de transmissão normalmente significa habilitar a interrupção de hardware da fila de transmissão. Quando a interrupção de hardware é disparada, o cliente chama NetTxQueueNotifyMoreCompletedPacketsAvailable de seu DPC.

Da mesma forma, para uma NIC PCI, desabilitar a notificação de fila significa desabilitar a interrupção associada à fila.

Para um dispositivo que tem um modelo de E/S assíncrono, o cliente normalmente usa um sinalizador interno para acompanhar o estado habilitado. Quando uma operação assíncrona é concluída, o manipulador de conclusão verifica esse sinalizador e chama NetTxQueueNotifyMoreCompletedPacketsAvailable se ele estiver definido.

Se NetAdapterCx chamar EvtPacketQueueSetNotificationEnabled com NotificationEnabled definido como FALSE, o cliente não deverá chamar NetTxQueueNotifyMoreCompletedPacketsAvailable até que NetAdapterCx chame essa função de retorno de chamada com NotificationEnabled definido como TRUE.

Por exemplo:

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

Habilitar e desabilitar a notificação para uma fila de recebimento

Para uma NIC PCI, habilitar a notificação de fila de recebimento é muito semelhante a uma fila Tx. Isso normalmente significa habilitar a interrupção de hardware da fila de recebimento. Quando a interrupção de hardware é disparada, o cliente chama NetRxQueueNotifyMoreReceivedPacketsAvailable de seu DPC.

Por exemplo:

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

Para um dispositivo USB ou qualquer outra fila com um mecanismo de conclusão de recebimento de software, o driver cliente deve acompanhar em seu próprio Contexto se a notificação da fila está habilitada. Na rotina de conclusão (disparada, por exemplo, quando uma mensagem fica disponível no leitor contínuo USB), chame NetRxQueueNotifyMoreReceivedPacketsAvailable se a notificação estiver habilitada. O exemplo a seguir mostra como você pode fazer isso.

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

Cancelando filas de pacotes

Quando o sistema operacional interrompe o caminho de dados, ele começa invocando a função de retorno de chamada EvtPacketQueueCancel do driver cliente. Esse retorno de chamada é onde os drivers de cliente executam qualquer processamento necessário antes que a estrutura exclua as filas de pacotes. O cancelamento de uma fila de transmissão é opcional e depende se o hardware dá suporte ao cancelamento de transmissão em voo, mas o cancelamento de uma fila de recebimento é necessário.

Durante EvtPacketQueueCancel, os drivers retornam pacotes para o sistema operacional conforme necessário. Para obter exemplos de código de transmissão de fila e cancelamento de fila de recebimento, consulte Cancelando dados de rede com anéis de rede.

Depois de chamar o retorno de chamada EvtPacketQueueCancel do driver, a estrutura continua sondando o retorno de chamada EvtPacketQueueAdvance do driver até que todos os pacotes e buffers sejam retornados ao sistema operacional.