當架構叫用其 EvtPacketQueueAdvance 傳輸佇列的回呼函式時,NetAdapterCx 用戶端驅動程式會傳送網路數據。 在此回呼期間,用戶端驅動程式會將緩衝區從佇列的片段環傳送到硬體,然後回收已完成的封包和片段回到作業系統。
傳輸(Tx)後序和排放作業概觀
下列動畫說明簡單 PCI 網路適配器 (NIC) 的用戶端驅動程式如何執行傳輸 (Tx) 佇列的張貼和清空作業。
在此動畫中,用戶端驅動程式所擁有的封包會以淺藍色和深藍色反白顯示,而用戶端驅動程式所擁有的碎片會以黃色和橙色顯示。 較淺的色彩代表驅動程式擁有之元素的 清空 子區段,而較深的色彩則代表驅動程式擁有之元素的 后 子區段。
依序傳送數據
以下是一個驅動程式的典型發送和排出序列,其裝置會依序傳輸資料,例如簡單的 PCI 網路介面卡。
- 呼叫 NetTxQueueGetRingCollection 以擷取傳輸佇列的環集合結構。 您可以將此內容儲存在佇列的內容空間中,以減少驅動程式的呼叫。 使用環集合來擷取傳輸佇列的封包環。
- 將資料張貼至硬體:
- 為封包索引配置 UINT32 變數,並將其設定為封包通道的 NextIndex,這是通道后小節的開頭。
- 在循環中執行下列動作:
- 使用封包索引呼叫 NetRingGetPacketAtIndex 來取得封包。
- 檢查是否應該忽略此封包。 如果應該忽略它,請跳至這個迴圈的步驟 6。 如果沒有,請繼續。
- 取得這個封包的片段。 從環集合中擷取傳輸佇列的片段環,從封包的 FragmentIndex 成員擷取封包片段的開頭,然後呼叫 NetRingIncrementIndex,以封包的 FragmentCount來擷取封包片段的結尾。
- 循環進行下列操作:
- 呼叫 NetRingGetFragmentAtIndex 以取得片段。
- 將 NET_FRAGMENT 描述元轉譯為相關聯的硬體片段描述元。
- 呼叫 NetRingIncrementIndex 來推進片段索引。
- 更新片段環的 Next 索引,以符合片段迭代器的當前 Index,這表明發送至硬體的操作已完成。
- 呼叫 NetRingIncrementIndex來推進封包索引。
- 將封包通道的 NextIndex 更新為封包索引,以完成將封包張貼至硬體。
- 清空已完成的傳輸封包至 OS:
- 將封包索引設定為封包環的 BeginIndex,這是封包環排放子區段的開頭。
- 在循環中執行下列動作:
- 使用封包索引呼叫 NetRingGetPacketAtIndex 來取得封包。
- 檢查封包是否已完成傳輸。 如果沒有,請中斷迴圈。
- 呼叫 NetRingIncrementIndex來推進封包索引。
- 將封包通道的 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 網路介面卡的有序傳輸,作業系統通常會配置、附加,並最終擁有片段緩衝區。 然後,客戶端驅動程式可以依次在 EvtPacketQueueAdvance期間測試每個片段的相應硬體擁有權旗標。
與此模型相反,請考慮一般的USB型NIC。 在此情況下,USB 堆疊會擁有用於傳輸的記憶體緩衝區,而這些緩衝區可能位於系統記憶體中的其他地方。 USB 堆疊會無序地向客戶端驅動程式指出完成狀態,因此客戶端驅動程式必須在其完成回呼例程中個別記錄封包的完成狀態。 若要這樣做,客戶端驅動程式可以使用封包的 Scratch 欄位,也可以使用其他方法,例如將資訊儲存在其佇列內容空間中。 然後,在呼叫 EvtPacketQueueAdvance中,客戶端驅動程式會檢查此資訊以進行封包完成測試。