큐 전송 및 받기

개요

패킷 큐 또는 데이터 경로 큐 는 클라이언트 드라이버가 소프트웨어 드라이버에서 더 명시적으로 하드웨어 전송 및 수신 큐와 같은 하드웨어 기능을 모델링할 수 있도록 NetAdapterCx에 도입된 개체입니다. 이 항목에서는 NetAdapterCx에서 전송 및 수신 큐를 사용하는 방법을 설명합니다.

클라이언트 드라이버는 일반적으로 EVT_WDF_DRIVER_DEVICE_ADD 이벤트 콜백 함수에서 NET_ADAPTER_DATAPATH_CALLBACKS_INIT 호출할 때 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. OS는 전송 또는 수신을 위해 클라이언트 드라이버에 버퍼를 제공합니다.
  2. 클라이언트 드라이버는 하드웨어에 패킷을 프로그래밍합니다.
  3. 클라이언트 드라이버는 완료된 패킷을 OS에 반환합니다.

폴링 작업은 클라이언트 드라이버의 EvtPacketQueueAdvance 콜백 함수 내에서 발생합니다. 클라이언트 드라이버의 각 패킷 큐는 시스템 메모리의 실제 네트워크 데이터 버퍼를 포함하거나 연결하는 net 링이라는 기본 데이터 구조에 의해 뒷받침됩니다. EvtPacketQueueAdvance 중에 클라이언트 드라이버는 링 내의 인덱스를 제어하고, 데이터가 전송되거나 수신될 때 하드웨어와 OS 간에 버퍼 소유권을 전송하여 net 링에서 송신 및 수신 작업을 수행합니다.

net 링에 대한 자세한 내용은 Net 링 소개를 참조하세요.

전송 큐에 대해 EvtPacketQueueAdvance 를 구현하는 예제는 net 링으로 네트워크 데이터 보내기를 참조하세요. 수신 큐에 대해 EvtPacketQueueAdvance 를 구현하는 예제는 net 링으로 네트워크 데이터 수신을 참조하세요.

패킷 큐 알림 사용 및 사용 안 함

클라이언트 드라이버가 패킷 큐의 net 링에서 새 패킷을 받으면 NetAdapterCx는 클라이언트 드라이버의 EvtPacketQueueSetNotificationEnabled 콜백 함수를 호출합니다. 이 콜백은 클라이언트 드라이버에 폴링( EvtPacketQueueAdvance 또는 EvtPacketQueueCancel)이 중지되고 클라이언트 드라이버가 NetTxQueueNotifyMoreCompletedPacketsAvailable 또는 NetRxQueueNotifyMoreReceivedPacketsAvailable을 호출할 때까지 계속되지 않음을 나타냅니다. 일반적으로 PCI 디바이스는 이 콜백을 사용하여 Tx 또는 Rx 인터럽트 사용을 설정합니다. 인터럽트가 수신되면 인터럽트를 다시 사용하지 않도록 설정할 수 있으며 클라이언트 드라이버는 NetTxQueueNotifyMoreCompletedPacketsAvailable 또는 NetRxQueueNotifyMoreReceivedPacketsAvailable 을 호출하여 프레임워크를 트리거하여 다시 폴링을 시작합니다.

전송 큐에 대한 알림 사용 및 사용 안 함

PCI NIC의 경우 전송 큐 알림을 사용하도록 설정하는 것은 일반적으로 전송 큐의 하드웨어 인터럽트 활성화를 의미합니다. 하드웨어 인터럽트가 발생하면 클라이언트는 해당 DPC에서 NetTxQueueNotifyMoreCompletedPacketsAvailable 을 호출합니다.

마찬가지로 PCI NIC의 경우 큐 알림을 사용하지 않도록 설정하면 큐와 연결된 인터럽트는 비활성화됩니다.

비동기 I/O 모델이 있는 디바이스의 경우 클라이언트는 일반적으로 내부 플래그를 사용하여 사용 상태를 추적합니다. 비동기 작업이 완료되면 완료 처리기는 이 플래그를 확인하고 설정된 경우 NetTxQueueNotifyMoreCompletedPacketsAvailable 을 호출합니다.

NetAdapterCx가 NotificationEnabledFALSE로 설정된 EvtPacketQueueSetNotificationEnabled를 호출하는 경우 NetAdapterCx가 NotificationEnabledTRUE로 설정된 상태에서 이 콜백 함수를 호출할 때까지 클라이언트는 NetTxQueueNotifyMoreCompletedPacketsAvailable을 호출하지 않아야 합니다.

예를 들면 다음과 같습니다.

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 NIC의 경우 수신 큐 알림을 사용하도록 설정하는 것은 Tx 큐와 매우 유사합니다. 이는 일반적으로 수신 큐의 하드웨어 인터럽트 활성화를 의미합니다. 하드웨어 인터럽트 발생 시 클라이언트는 해당 DPC에서 NetRxQueueNotifyMoreReceivedPacketsAvailable 을 호출합니다.

예를 들면 다음과 같습니다.

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

패킷 큐 취소

OS가 데이터 경로를 중지하면 클라이언트 드라이버의 EvtPacketQueueCancel 콜백 함수를 호출하여 시작합니다. 이 콜백은 프레임워크가 패킷 큐를 삭제하기 전에 클라이언트 드라이버가 필요한 모든 처리를 수행하는 위치입니다. 전송 큐에 대한 취소는 선택 사항이며 하드웨어가 진행 중인 전송 취소를 지원하는지 여부에 따라 달라지지만 수신 큐에 대한 취소가 필요합니다.

EvtPacketQueueCancel 중에 드라이버는 필요에 따라 OS로 패킷을 반환합니다. 큐 전송 및 수신 큐 취소의 코드 예제는 net 링으로 네트워크 데이터 취소를 참조하세요.

드라이버의 EvtPacketQueueCancel 콜백을 호출한 후 프레임워크는 모든 패킷 및 버퍼가 OS로 반환될 때까지 드라이버의 EvtPacketQueueAdvance 콜백을 계속 폴링합니다.