使用网环发送网络数据

当框架为传输队列调用其 EvtPacketQueueAdvance 回调函数时,NetAdapterCx 客户端驱动程序会发送网络数据。 在此回调期间,客户端驱动程序将缓冲区从队列的片段环发布到硬件,然后将已完成的数据包和片段排出回 OS。

传输 (Tx) 后和排出操作概述

以下动画演示了简单 PCI 网络接口的客户端驱动程序卡 (NIC) 如何对传输 (Tx) 队列执行后和排出操作。

显示 PCI 网络接口卡传输 (Tx) 的网环后和排出操作的动画。

在此动画中,客户端驱动程序拥有的数据包以浅蓝色和深蓝色突出显示,客户端驱动程序拥有的片段以黄色和橙色突出显示。 较浅的颜色表示驱动程序拥有的元素的 排出 子部分,而较深的颜色表示驱动程序拥有的元素 的后 子部分。

按顺序发送数据

下面是其设备按顺序传输数据的驱动程序的典型 post 和 drain 序列,例如简单的 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 索引以匹配片段迭代器的当前 索引,这表示发布到硬件已完成。
      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 时,客户端驱动程序会检查此信息,以便进行数据包完成测试。