NetAdapterCx 클라이언트 드라이버는 프레임워크가 전송 큐에 대한 EvtPacketQueueAdvance 콜백 함수를 호출할 때 네트워크 데이터를 보냅니다. 이 콜백 함수 중에 클라이언트 드라이버는 큐의 조각 링에서 하드웨어로 버퍼를 전송한 다음, 완료된 패킷 및 조각을 OS로 다시 전송합니다.
전송(Tx) 후처리 및 배수 작업 개요
다음 애니메이션은 간단한 PCI NIC(네트워크 인터페이스 카드)의 클라이언트 드라이버가 Tx(전송) 큐에 대한 사후 및 드레이닝 작업을 수행하는 방법을 보여 줍니다.
이 애니메이션에서 클라이언트 드라이버가 소유한 패킷은 연한 파란색과 진한 파란색으로 강조 표시되고 클라이언트 드라이버가 소유한 조각은 노란색과 주황색으로 강조 표시됩니다. 밝은 색은 드라이버가 소유한 요소의 드레이닝 하위 섹션을 나타내고 어두운 색은 드라이버가 소유한 요소의 포스트 하위 섹션을 나타냅니다.
순서대로 데이터 보내기
다음은 간단한 PCI NIC와 같이 디바이스가 데이터를 순서대로 전송하는 드라이버에 대한 일반적인 포스트 및 드레이닝 시퀀스입니다.
- NetTxQueueGetRingCollection 호출하여 전송 큐의 링 컬렉션 구조를 검색합니다. 이를 큐의 컨텍스트 공간에 저장하여 드라이버의 호출을 줄일 수 있습니다. 링 컬렉션을 사용하여 전송 큐의 패킷 링을 검색합니다.
- 하드웨어에 데이터 게시:
- 패킷 인덱스를 위한 UINT32 변수를 할당하고 이를 패킷 링의 NextIndex로 설정합니다. 이는 링의 포스트 하위 섹션의 시작 부분입니다.
- 루프에서 다음을 수행합니다.
- 패킷 인덱스를 사용하여 NetRingGetPacketAtIndex 호출하여 패킷을 가져옵니다.
- 이 패킷을 무시해야 하는지 확인합니다. 무시해야 하는 경우 이 루프의 6단계로 건너뜁니다. 그렇지 않은 경우 계속합니다.
- 이 패킷의 조각을 가져옵니다. 링 컬렉션에서 전송 큐의 조각 링을 검색하고, 패킷의 FragmentIndex 멤버에서 패킷 조각의 시작을 검색한 후, NetRingIncrementIndex를 패킷의 FragmentCount와 함께 호출하여 패킷 조각의 끝을 검색합니다.
- 루프 내에서 다음을 반복적으로 수행합니다.
- NetRingGetFragmentAtIndex 호출하여 조각을 가져옵니다.
- NET_FRAGMENT 설명자를 연결된 하드웨어 조각 설명자로 변환합니다.
- NetRingIncrementIndex를 호출하여 조각 인덱스를 증가시킵니다.
- 조각 링의 Next 인덱스가 조각 반복기의 현재 인덱스일치하도록 업데이트합니다. 이는 하드웨어에 게시가 완료되었음을 나타냅니다.
- 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;
}
순서 없이 데이터 보내기
순서가 뒤바뀔 수 있는 디바이스의 경우와 순서대로 전송을 완료하는 디바이스의 주요 차이점은 전송 버퍼를 할당하는 주체와 드라이버가 전송 완료 테스트를 처리하는 방식에 있습니다. OS는 DMA를 지원하는 PCI NIC를 사용하는 순차 전송의 경우 일반적으로 조각 버퍼를 할당하고 연결하며 최종적으로 소유합니다. 그런 다음, 클라이언트 드라이버는 EvtPacketQueueAdvance동안 각 조각의 해당 하드웨어 소유권 플래그를 테스트할 수 있습니다.
이 모델과 달리 일반적인 USB 기반 NIC를 고려합니다. 이 경우 USB 스택은 전송을 위한 메모리 버퍼를 소유하며 이러한 버퍼는 시스템 메모리의 다른 위치에 있을 수 있습니다. USB 스택은 클라이언트 드라이버에 대한 완료를 순서대로 표시하므로 클라이언트 드라이버는 완료 콜백 루틴 중에 패킷의 완료 상태를 별도로 기록해야 합니다. 이렇게 하려면 클라이언트 드라이버는 패킷의 스크래치 필드를 사용하거나 큐 컨텍스트 공간에 정보를 저장하는 것과 같은 다른 방법을 사용할 수 있습니다. 그런 다음 EvtPacketQueueAdvance호출에서 클라이언트 드라이버는 패킷 완료 테스트를 위해 이 정보를 확인합니다.