次の方法で共有


NetAdapterCx ドライバーでのキューの送受信

概要

NetAdapterCx ドライバーの送受信キューを使用すると、クライアント ドライバーは、ハードウェアの送受信キューなどのハードウェア機能をソフトウェアでモデル化できます。 この記事では、NetAdapterCx でデータ パスのパフォーマンスを最適化するために、これらのキューを実装および管理する方法について説明します。

クライアント ドライバー は、NET_ADAPTER_DATAPATH_CALLBACKS_INITを呼び出すとき (通常は EVT_WDF_DRIVER_DEVICE_ADD イベント コールバック関数から)、 EVT_NET_ADAPTER_CREATE_TXQUEUEEVT_NET_ADAPTER_CREATE_RXQUEUEの 2 つのキュー作成コールバックを提供します。 クライアントは、これらのコールバックでそれぞれ送信キューと受信キューを作成します。

フレームワークは、低電力状態に移行する前にキューを空にし、アダプターを削除する前にキューを削除します。

パケット キューを作成する

パケット キュー (送信キューまたは受信キュー) を作成する場合、クライアントは次の 3 つのコールバック関数へのポインターを提供する必要があります。

さらに、クライアントは、キュー構成構造を初期化した後に、次の省略可能なコールバック関数を提供できます。

送信キューを作成する

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 データ パスはポーリング モデルであり、1 つのパケット キューに対するポーリング操作は他のキューから独立しています。 ポーリング モデルは、次の図に示すように、クライアント ドライバーのキューの事前コールバックを呼び出すことによって実装されます。

NetAdapterCx のポーリング フローを示す図。

パケット キューを進める

パケット キューでのポーリング操作のシーケンスは次のとおりです。

  1. OS は、送受信用のバッファーをクライアント ドライバーに提供します。
  2. クライアント ドライバーは、ハードウェアにパケットをプログラムします。
  3. クライアント ドライバーは、完了したパケットを OS に返します。

ポーリング操作は、クライアント ドライバーの EvtPacketQueueAdvance コールバック関数内で発生します。 クライアント ドライバーの各パケット キューは、システム メモリ内の実際のネットワーク データ バッファーを格納またはリンクする 、ネット リングと呼ばれる基になるデータ構造によってサポートされます。 EvtPacketQueueAdvance 中に、クライアント ドライバーは、リング内のインデックスを制御し、データの送受信時にハードウェアと OS の間でバッファーの所有権を転送することで、ネット リングに対して送受信操作を実行します。

ネット リングの詳細については、「ネット リング の概要」を参照してください。

送信キューに EvtPacketQueueAdvance を実装する例については、「 ネット リングを使用したネットワーク データの送信」を参照してください。 受信キューに EvtPacketQueueAdvance を実装する例については、「 ネット リングを使用したネットワーク データの受信」を参照してください。

パケット キュー通知を有効または無効にする

クライアント ドライバーがパケット キューのネット リングで新しいパケットを受信すると、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 にパケットを返します。 送信キューと受信キューの取り消しのコード例については、「 ネットワーク データをネット リングでキャンセルする」を参照してください。

ドライバーの EvtPacketQueueCancel コールバックを呼び出した後、フレームワークは、すべてのパケットとバッファーが OS に返されるまで、ドライバーの EvtPacketQueueAdvance コールバックをポーリングし続けます。