Очереди передачи и получения

Общие сведения

Очереди пакетов или очереди пути к данным — это объекты, представленные в NetAdapterCx, которые позволяют клиентским драйверам моделировать свои аппаратные функции, такие как очереди передачи и получения оборудования, более явно в программных драйверах. В этом разделе объясняется, как работать с очередями передачи и получения в NetAdapterCx.

Когда драйвер клиента вызывает NET_ADAPTER_DATAPATH_CALLBACKS_INIT, как правило, из функции обратного вызова события EVT_WDF_DRIVER_DEVICE_ADD , он предоставляет два обратных вызова для создания очереди: EVT_NET_ADAPTER_CREATE_TXQUEUE и EVT_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. ОС предоставляет драйверу клиента буферы для передачи или получения.
  2. Драйвер клиента программирует пакеты на оборудование.
  3. Драйвер клиента возвращает завершенные пакеты в ОС.

Операции опроса выполняются в функции обратного вызова EvtPacketQueueAdvance драйвера клиента. Каждая очередь пакетов в драйвере клиента поддерживается базовыми структурами данных, называемыми сетевыми кольцами, которые содержат или связываются с фактическими сетевыми буферами данных в системной памяти. Во время EvtPacketQueueAdvance клиентские драйверы выполняют операции отправки и получения на сетевых кольцах, контролируя индексы в кольцах, передавая права владения буфером между оборудованием и ОС по мере передачи или получения данных.

Дополнительные сведения о сетевых кольцах см. в статье Введение в круги сетки.

Пример реализации EvtPacketQueueAdvance для очереди передачи см. в разделе Отправка сетевых данных с помощью сетевых кругов. Пример реализации EvtPacketQueueAdvance для очереди получения см. в разделе Получение сетевых данных с помощью сетевых кругов.

Включение и отключение уведомления очереди пакетов

Когда драйвер клиента получает новые пакеты в сетевых кругах очереди пакетов, NetAdapterCx вызывает функцию обратного вызова EvtPacketQueueSetNotificationEnabled драйвера клиента. Этот обратный вызов указывает драйверу клиента, что опрос ( evtPacketQueueAdvance или EvtPacketQueueCancel) будет остановлен и не будет продолжаться до тех пор, пока драйвер клиента не вызовет NetTxQueueNotifyMoreCompletedPacketsAvailable или NetRxQueueNotifyMoreReceivedPacketsAvailable. Как правило, устройство PCI использует этот обратный вызов для включения прерываний Tx или Rx. После получения прерывания можно снова отключить прерывания, и драйвер клиента вызывает NetTxQueueNotifyMoreCompletedPacketsAvailable или NetRxQueueNotifyMoreReceivedPacketsAvailable , чтобы активировать платформу для повторного начала опроса.

Включение и отключение уведомлений для очереди передачи

Для сетевого адаптера PCI включение уведомления о очереди передачи обычно означает включение аппаратного прерывания очереди передачи. При срабатывании аппаратного прерывания клиент вызывает NetTxQueueNotifyMoreCompletedPacketsAvailable из своего DPC.

Аналогично, для сетевого адаптера PCI отключение уведомления о очереди означает отключение прерывания, связанного с очередью.

Для устройства с асинхронной моделью ввода-вывода клиент обычно использует внутренний флаг для отслеживания включенного состояния. После завершения асинхронной операции обработчик завершения проверяет этот флаг и вызывает NetTxQueueNotifyMoreCompletedPacketsAvailable , если он задан.

Если NetAdapterCx вызывает EvtPacketQueueSetNotificationEnabled с параметром NotificationEnabled, для параметра 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 включение уведомлений очереди получения очень похоже на очередь Tx. Обычно это означает включение аппаратного прерывания очереди получения. При срабатывании аппаратного прерывания клиент вызывает NetRxQueueNotifyMoreReceivedPacketsAvailable из своего DPC.

Например:

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

Отмена очередей пакетов

Когда ОС останавливает путь к данным, он начинается с вызова функции обратного вызова EvtPacketQueueCancel драйвера клиента. Этот обратный вызов позволяет клиентским драйверам выполнять любую обработку, необходимую до того, как платформа удалит очереди пакетов. Отмена для очереди передачи необязательна и зависит от того, поддерживает ли оборудование отмену передачи в полете, но отмена для очереди получения является обязательной.

Во время EvtPacketQueueCancel драйверы возвращают пакеты в ОС по мере необходимости. Примеры кода для отмены очереди передачи и получения см. в разделе Отмена сетевых данных с помощью сетевых кругов.

После вызова обратного вызова EvtPacketQueueCancel драйвера платформа продолжает опрашивать обратный вызов EvtPacketQueueAdvance драйвера, пока все пакеты и буферы не будут возвращены в ОС.