次の方法で共有


要求の送信と受信

概要

パケット キューまたはデータパス キューは、NetAdapterCx で導入されたオブジェクトで、クライアント ドライバーがハードウェアの送受信キューなどのハードウェア機能をより明示的にソフトウェア ドライバーでモデル化できるようにします。 このトピックでは、NetAdapterCx で送信キューおよび受信キューを処理する方法について説明します。

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

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

パケット キューの作成

パケット キュー (送信キューまたは受信キュー) を作成する場合、クライアントは次の 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 つのパケット キューに対するポーリング操作は、他のキューとは完全に独立しています。 ポーリング モデルは、次の図に示すように、クライアント ドライバーのキュー アドバンス コールバックを呼び出すことによって実装されます。

Diagram that shows the polling flow in 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 が EvtPacketQueueSetNotificationEnabled を呼び出す際に NotificationEnabledFALSE に設定されている場合には、クライアントは、NetTxQueueNotifyMoreCompletedPacketsAvailable の呼び出しは、次に NetAdapterCx がこのコールバック関数 EvtPacketQueueSetNotificationEnabled を NotificationEnabledTRUE に設定して呼び出すまでは実行するべきではありません。

次に例を示します。

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 コールバックのポーリングを続行します。