ネット リングを使用したネットワーク データの送信

NetAdapterCx クライアント ドライバーは、フレームワークが送信キューの EvtPacketQueueAdvance コールバック関数を呼び出すときにネットワーク データを送信します。 このコールバック中に、クライアント ドライバーはキューのフラグメント リングからハードウェアにバッファーをポストし、完了したパケットとフラグメントを OS にドレインします。

送信 (Tx) ポストおよびドレイン動作の概要

次のアニメーションは、単純な PCI ネットワーク インターフェイス カード (NIC) のクライアント ドライバーが、送信 (Tx) キューのポスト操作とドレイン操作を実行する方法を示しています。

Animation showing net ring post and drain operations for transmit (Tx) for a PCI network interface card.

このアニメーションでは、クライアント ドライバーが所有するパケットが水色と濃い青色で強調表示され、クライアント ドライバーが所有するフラグメントが黄色とオレンジ色で強調表示されます。 明るい色は、ドライバーが所有する要素の drain サブセクションを表し、暗い色は、ドライバーが所有する要素の post サブセクションを表します。

データを順番に送信する

単純な PCI NIC など、デバイスがデータを順番に送信するドライバーの一般的なポスト アンド ドレイン シーケンスを次に示します。

  1. NetTxQueueGetRingCollection を呼び出して、送信キューのリング コレクション構造を取得します。 これをキューのコンテキスト空間に格納して、ドライバーからの呼び出しを減らすことができます。 リング コレクションを使用して、送信キューのパケット リングを取得します。
  2. ハードウェアにデータをポストする:
    1. パケット インデックスに UINT32 変数を割り当て、パケット リングの NextIndex (リングのポスト サブセクションの開始) に設定します。
    2. ループで次の操作を行います。
      1. パケット インデックスを指定して NetRingGetPacketAtIndex を呼び出して、パケットを取得します。
      2. このパケットを無視するかどうかを確認します。 無視する必要がある場合は、このループの手順 6 に進んでください。 そうでない場合は、続行します。
      3. このパケットのフラグメントを取得します。 リング コレクションから送信キューのフラグメント リングを取得し、パケットの FragmentIndex メンバーからパケットのフラグメントの先頭を取得し、パケットの FragmentCount を指定して NetRingIncrementIndex を呼び出してパケットのフラグメントの末尾を取得します。
      4. 次の操作をループで行います。
        1. フラグメントを取得するには、NetRingGetFragmentAtIndex を呼び出します。
        2. NET_FRAGMENT 記述子を、関連付けられたハードウェア フラグメント記述子に変換します。
        3. NetRingIncrementIndex を呼び出して、フラグメント インデックスを進めます。
      5. フラグメント リングの Next インデックスを更新して、ハードウェアへの投稿が完了したことを示すフラグメント反復子の現在の Index と一致します。
      6. NetRingIncrementIndex を呼び出して、パケット インデックスを進めます。
    3. パケット リングの NextIndex をパケット インデックスに更新して、ハードウェアへのパケットの送信を完了します。
  3. 完了した送信パケットを OS にドレインします。
    1. パケット インデックスをパケット リングの BeginIndex (リングのドレイン サブセクションの開始) に設定します。
    2. ループで次の操作を行います。
      1. パケット インデックスを指定して NetRingGetPacketAtIndex を呼び出して、パケットを取得します。
      2. パケットの送信が完了したかどうかを確認します。 そうでない場合は、ループから抜け出します。
      3. NetRingIncrementIndex を呼び出して、パケット インデックスを進めます。
    3. パケット リングの BeginIndex をパケット インデックスに更新して、ハードウェアへのパケットの送信を完了します。

これらの手順は、コードでは次のようになります。 記述子をハードウェアにポストする方法や、成功したポスト トランザクションをフラッシュする方法など、ハードウェア固有の詳細は、わかりやすくするために省略されていることに注意してください。

void
MyEvtTxQueueAdvance(
    NETPACKETQUEUE TxQueue
)
{
    //
    // Retrieve the transmit queue's ring collection and packet ring. 
    // This example stores the Tx queue's ring collection in its queue context space.
    //
    PMY_TX_QUEUE_CONTEXT txQueueContext = MyGetTxQueueContext(TxQueue);
    NET_RING_COLLECTION const * ringCollection = txQueueContext->RingCollection;
    NET_RING * packetRing = ringCollection->Rings[NET_RING_TYPE_PACKET];
    UINT32 currentPacketIndex = 0;

    //
    // Post data to hardware
    //      
    currentPacketIndex = packetRing->NextIndex;
    while(currentPacketIndex != packetRing->EndIndex)
    {
        NET_PACKET * packet = NetRingGetPacketAtIndex(packetRing, currentPacketIndex);        
        if(!packet->Ignore)
        {
            NET_RING * fragmentRing = ringCollection->Rings[NET_RING_TYPE_FRAGMENT];
            UINT32 currentFragmentIndex = packet->FragmentIndex;
            UINT32 fragmentEndIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex + packet->FragmentCount - 1);
            
            for(txQueueContext->PacketTransmitControlBlocks[packetIndex]->numTxDescriptors = 0; 
                currentFragmentIndex != fragmentEndIndex; 
                txQueueContext->PacketTransmitControlBlocks[packetIndex]->numTxDescriptors++)
            {
                NET_FRAGMENT * fragment = NetRingGetFragmentAtIndex(fragmentRing, currentFragmentIndex);

                // Post fragment descriptor to hardware
                ...
                //

                currentFragmentIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex);
            }

            //
            // Update the fragment ring's Next index to indicate that posting is complete and prepare for draining
            //
            fragmentRing->NextIndex = currentFragmentIndex;
        }
        currentPacketIndex = NetRingIncrementIndex(packetRing, currentPacketIndex);
    }
    packetRing->NextIndex = currentPacketIndex;

    //
    // Drain packets if completed
    //
    currentPacketIndex = packetRing->BeginIndex;
    while(currentPacketIndex != packetRing->NextIndex)
    {        
        NET_PACKET * packet = NetRingGetPacketAtIndex(packetRing, currentPacketIndex); 
        
        // Test packet for transmit completion by checking hardware ownership flags in the packet's last fragment
        // Break if transmit is not complete
        ...
        //
        
        currentPacketIndex = NetRingIncrementIndex(packetRing, currentPacketIndex);
    }
    packetRing->BeginIndex = currentPacketIndex;
}

順不同でデータを送信する

デバイスが順不同で送信を完了する可能性があるドライバーの場合、順序どおりのデバイスとの主な違いは、送信バッファーを割り当てるユーザーと、ドライバーが送信完了のテストを処理する方法にあります。 DMA 対応の PCI NIC を使用した順序どおりの転送の場合、通常、OS はフラグメント バッファーを割り当て、アタッチし、最終的に所有します。 次に、順番に、クライアント ドライバーは、EvtPacketQueueAdvance 中に、各フラグメントの対応するハードウェア所有権フラグをテストできます。

このモデルとは対照的に、一般的な USB ベースの NIC について考えてみましょう。 この状況では、USB スタックが送信用のメモリ バッファーを所有し、それらのバッファーはシステム メモリ内の別の場所に配置されている可能性があります。 USB スタックは、クライアント ドライバーに順不同で完了を示すため、クライアント ドライバーは、完了コールバック ルーチン中にパケットの完了状態を個別に記録する必要があります。 これを行うには、クライアント ドライバーは、パケットの Scratch フィールドを使用するか、キュー コンテキスト空間に情報を格納するなどの他の方法を使用できます。 次に、EvtPacketQueueAdvance の呼び出しで、クライアント ドライバーはパケット完了テストのためにこの情報を確認します。