Net 링으로 네트워크 데이터 보내기

NetAdapterCx 클라이언트 드라이버는 프레임워크가 전송 큐 에 대한 EvtPacketQueueAdvance 콜백 함수를 호출할 때 네트워크 데이터를 보냅니다. 이 콜백 중에 클라이언트 드라이버는 큐의 조각 링에서 하드웨어로 버퍼를 게시한 다음 완료된 패킷과 조각을 OS로 다시 드레이닝합니다.

전송(Tx) 사후 및 드레이닝 작업 개요

다음 애니메이션에서는 간단한 PCI NIC(네트워크 인터페이스 카드)에 대한 클라이언트 드라이버가 전송(Tx) 큐에 대한 사후 및 드레이닝 작업을 수행하는 방법을 보여 줍니다.

PCI 네트워크 인터페이스 카드 대한 전송(Tx)에 대한 넷 링 포스트 및 드레이닝 작업을 보여 주는 애니메이션.

이 애니메이션에서는 클라이언트 드라이버가 소유한 패킷이 연한 파란색과 진한 파란색으로 강조 표시되고 클라이언트 드라이버가 소유한 조각이 노란색과 주황색으로 강조 표시됩니다. 밝은 색은 드라이버가 소유한 요소의 드레이닝 하위 섹션을 나타내고 어두운 색은 드라이버가 소유한 요소의 포스트 하위 섹션을 나타냅니다.

순서대로 데이터 보내기

다음은 간단한 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. 조각 반복기의 현재 인덱스와 일치하도록 조각 링의 다음인덱스 를 업데이트합니다. 이는 하드웨어에 게시가 완료되었음을 나타냅니다.
      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 스택은 클라이언트 드라이버의 완료를 순서대로 표시하므로 클라이언트 드라이버는 완료 콜백 루틴 중에 패킷의 완료 상태 별도로 기록해야 합니다. 이렇게 하려면 클라이언트 드라이버는 패킷의 스크래치 필드를 사용하거나 큐 컨텍스트 공간에 정보를 저장하는 것과 같은 다른 방법을 사용할 수 있습니다. 그런 다음 , EvtPacketQueueAdvance 호출에서 클라이언트 드라이버는 패킷 완료 테스트를 위해 이 정보를 확인합니다.