Kolejki wysyłania i odbierania
Przegląd
kolejki pakietówlub kolejki ścieżki danych są obiektami wprowadzonymi w NetAdapterCx, aby umożliwić sterownikom klienta modelowanie ich funkcji sprzętowych, takich jak sprzętowe kolejki nadawcze i odbiorcze, bardziej przejrzyście w sterownikach oprogramowania. W tym temacie wyjaśniono, jak pracować z kolejkami transmisji i odbierania w systemie NetAdapterCx.
Gdy sterownik klienta wywołuje NET_ADAPTER_DATAPATH_CALLBACKS_INIT, zazwyczaj z funkcji wywołania zwrotnego zdarzeń EVT_WDF_DRIVER_DEVICE_ADD, udostępnia dwa wywołania zwrotne tworzenia kolejki: EVT_NET_ADAPTER_CREATE_TXQUEUE i EVT_NET_ADAPTER_CREATE_RXQUEUE. Klient tworzy odpowiednio kolejki transmisji i odbierania w tych wywołaniach zwrotnych.
Struktura opróżnia kolejki przed przejściem do stanu niskiego poboru energii, a następnie je usuwa przed usunięciem adaptera.
Tworzenie kolejek pakietów
Podczas tworzenia kolejki pakietów, kolejki transmisji lub kolejki odbierania, klient musi podać wskaźniki do następujących trzech funkcji wywołania zwrotnego:
Ponadto klient może udostępnić te opcjonalne funkcje wywołania zwrotnego po zainicjowaniu struktury konfiguracji kolejki:
Tworzenie kolejki transmisji
NetAdapterCx wywołuje EVT_NET_ADAPTER_CREATE_TXQUEUE na samym końcu sekwencji zasilania. Podczas tego wywołania zwrotnego sterowniki klienta zwykle wykonują następujące czynności:
- Możesz opcjonalnie zarejestrować funkcje zwrotne dla uruchamiania i zatrzymywania tej kolejki.
- Wywołaj NetTxQueueInitGetQueueId, aby pobrać identyfikator kolejki nadawczej do skonfigurowania.
- Wywołaj NetTxQueueUtwórz, aby przydzielić kolejkę.
- Jeśli NetTxQueueCreate zakończy się niepowodzeniem, funkcja EvtNetAdapterCreateTxQueue wywołania zwrotnego powinna zwrócić kod błędu.
- Wykonywanie zapytań o przesunięcia rozszerzenia pakietu.
W poniższym przykładzie pokazano, jak te kroki mogą wyglądać w kodzie. Kod obsługi błędów został pominięty w tym przykładzie w celu zapewnienia przejrzystości.
NTSTATUS
EvtAdapterCreateTxQueue(
_In_ NETADAPTER Adapter,
_Inout_ NETTXQUEUE_INIT * TxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG txConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&txConfig,
EvtTxQueueAdvance,
EvtTxQueueSetNotificationEnabled,
EvtTxQueueCancel);
// Optional: register the queue's start and stop callbacks
txConfig.EvtStart = EvtTxQueueStart;
txConfig.EvtStop = EvtTxQueueStop;
// Get the queue ID
const ULONG queueId = NetTxQueueInitGetQueueId(TxQueueInit);
// Create the transmit queue
NETPACKETQUEUE txQueue;
status = NetTxQueueCreate(
TxQueueInit,
&txAttributes,
&txConfig,
&txQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_TX_QUEUE_CONTEXT queueContext = GetMyTxQueueContext(txQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->ChecksumExtension);
// Query Large Send Offload packet extension offset and store it in the context
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_LSO_NAME,
NET_PACKET_EXTENSION_LSO_VERSION_1);
NetTxQueueGetExtension(txQueue, &extension, &queueContext->LsoExtension);
return status;
}
Tworzenie kolejki odbierania
Aby utworzyć kolejkę odbierania z EVT_NET_ADAPTER_CREATE_RXQUEUE, użyj tego samego wzorca co kolejka przesyłania i wywołaj NetRxQueueCreate.
W poniższym przykładzie pokazano, jak tworzenie kolejki odbierania może wyglądać w kodzie. Kod obsługi błędów został pominięty w tym przykładzie w celu zapewnienia przejrzystości.
NTSTATUS
EvtAdapterCreateRxQueue(
_In_ NETADAPTER NetAdapter,
_Inout_ PNETRXQUEUE_INIT RxQueueInit
)
{
NTSTATUS status = STATUS_SUCCESS;
// Prepare the configuration structure
NET_PACKET_QUEUE_CONFIG rxConfig;
NET_PACKET_QUEUE_CONFIG_INIT(
&rxConfig,
EvtRxQueueAdvance,
EvtRxQueueSetNotificationEnabled,
EvtRxQueueCancel);
// Optional: register the queue's start and stop callbacks
rxConfig.EvtStart = EvtRxQueueStart;
rxConfig.EvtStop = EvtRxQueueStop;
// Get the queue ID
const ULONG queueId = NetRxQueueInitGetQueueId(RxQueueInit);
// Create the receive queue
NETPACKETQUEUE rxQueue;
status = NetRxQueueCreate(
RxQueueInit,
&rxAttributes,
&rxConfig,
&rxQueue);
// Get the queue context for storing the queue ID and packet extension offset info
PMY_RX_QUEUE_CONTEXT queueContext = GetMyRxQueueContext(rxQueue);
// Store the queue ID in the context
queueContext->QueueId = queueId;
// Query the checksum packet extension offset and store it in the context
NET_EXTENSION_QUERY extension;
NET_EXTENSION_QUERY_INIT(
&extension,
NET_PACKET_EXTENSION_CHECKSUM_NAME,
NET_PACKET_EXTENSION_CHECKSUM_VERSION_1);
NetRxQueueGetExtension(rxQueue, &extension, &queueContext->ChecksumExtension);
return status;
}
Model sondowania
Ścieżka danych NetAdapter jest modelem sondowania, a operacja sondowania w jednej kolejce pakietów jest całkowicie niezależna od innych kolejek. Model sondowania jest implementowany przez wywołanie wywołania zwrotnego z wyprzedzeniem sterownika klienta, jak pokazano na poniższej ilustracji:
Usprawnianie kolejek pakietów
Sekwencja operacji sondowania w kolejce pakietów jest następująca:
- System operacyjny dostarcza bufory sterownikowi klienta na potrzeby przesyłania lub odbierania.
- Sterownik klienta programuje pakiety do sprzętu.
- Sterownik klienta zwraca ukończone pakiety do systemu operacyjnego.
Operacje odpytywania są wykonywane w funkcji wywołania zwrotnego EvtPacketQueueAdvance. Każda kolejka pakietów w sterowniku klienta jest wspierana przez bazowe struktury danych o nazwie pierścienie sieci, które zawierają lub łączą się z faktycznymi danymi sieciowymi w pamięci systemowej. Podczas EvtPacketQueueAdvancesterowniki klienckie przeprowadzają operacje wysyłania i odbierania danych na pierścieniach sieciowych, kontrolując indeksy w pierścieniach oraz przekazując własność bufora między sprzętem a systemem operacyjnym w trakcie przesyłania lub odbierania danych.
Aby uzyskać więcej informacji na temat pierścieni sieciowych, zobacz Wprowadzenie do pierścieni sieciowych.
Aby zapoznać się z przykładem implementacji EvtPacketQueueAdvance dla kolejki transmisji, zobacz Wysyłanie danych sieciowych za pomocą pierścieni sieciowych. Aby zapoznać się z przykładem implementacji EvtPacketQueueAdvance dla kolejki odbioru, proszę zapoznać się z Odbieranie danych sieciowych za pomocą pierścieni sieciowych.
Włączanie i wyłączanie powiadomień o kolejkach pakietów
Gdy sterownik klienta odbiera nowe pakiety w pierścieniach sieci kolejki pakietów, netAdapterCx wywołuje sterownik klienta EvtPacketQueueSetNotificationEnabled funkcji wywołania zwrotnego. To wywołanie zwrotne wskazuje sterownikowi klienta, że sondowanie (z EvtPacketQueueAdvance lub EvtPacketQueueCancel) zostanie zatrzymane i nie będzie kontynuowane, dopóki sterownik klienta nie wywoła NetTxQueueNotifyMoreCompletedPacketsAvailable lub NetRxQueueNotifyMoreReceivedPacketsAvailable. Zwykle urządzenie PCI używa tego wywołania zwrotnego do włączania przerwań Tx lub Rx. Po odebraniu przerwania można ponownie wyłączyć przerwania, a sterownik klienta wywołuje NetTxQueueNotifyMoreCompletedPacketsAvailable lub NetRxQueueNotifyMoreReceivedPacketsAvailable, aby spowodować, że framework znowu zacznie sondować.
Włączanie i wyłączanie powiadomienia dla kolejki transmisji
W przypadku karty sieciowej PCI włączenie powiadomienia o kolejce transmisji zwykle oznacza włączenie przerwania sprzętowego kolejki transmisji. Gdy występuje przerwanie sprzętowe, klient wywołuje NetTxQueueNotifyMoreCompletedPacketsAvailable ze swojego DPC.
Podobnie w przypadku karty sieciowej PCI, wyłączenie powiadomienia kolejki oznacza wyłączenie przerwania skojarzonego z kolejką.
W przypadku urządzenia, które ma asynchroniczny model we/wy, klient zazwyczaj używa flagi wewnętrznej do śledzenia stanu włączonego. Po zakończeniu operacji asynchronicznej program obsługi uzupełniania sprawdza tę flagę i wywołuje NetTxQueueNotifyMoreCompletedPacketsAvailable, jeśli jest ustawiona.
Jeśli netAdapterCx wywołuje EvtPacketQueueSetNotificationEnabled z NotificationEnabled ustawioną na FALSE, klient nie może wywołać NetTxQueueNotifyMoreCompletedPacketsAvailable, dopóki netAdapterCx następne wywoła tę funkcję wywołania zwrotnego z NotificationEnabled ustawioną na true.
Na przykład:
VOID
MyEvtTxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE TxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// Optional: retrieve queue's WDF context
MY_TX_QUEUE_CONTEXT *txContext = GetTxQueueContext(TxQueue);
// If NotificationEnabled is TRUE, enable transmit queue's hardware interrupt
...
}
VOID
MyEvtTxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetTxQueueNotifyMoreCompletedPacketsAvailable(interruptContext->TxQueue);
}
Włączanie i wyłączanie powiadomień dla kolejki odbiorczej
W przypadku karty sieciowej PCI włączenie powiadomienia o kolejce odbierania wygląda bardzo podobnie do kolejki Tx. Zazwyczaj oznacza to włączenie przerwania sprzętowego kolejki odbierania. Po uruchomieniu się przerwania sprzętowego klient wywołuje NetRxQueueNotifyMoreReceivedPacketsAvailable ze swojego DPC.
Na przykład:
VOID
MyEvtRxQueueSetNotificationEnabled(
_In_ NETPACKETQUEUE RxQueue,
_In_ BOOLEAN NotificationEnabled
)
{
// optional: retrieve queue's WDF Context
MY_RX_QUEUE_CONTEXT *rxContext = GetRxQueueContext(RxQueue);
// If NotificationEnabled is TRUE, enable receive queue's hardware interrupt
...
}
VOID
MyEvtRxInterruptDpc(
_In_ WDFINTERRUPT Interrupt,
_In_ WDFOBJECT AssociatedObject
)
{
MY_INTERRUPT_CONTEXT *interruptContext = GetInterruptContext(Interrupt);
NetRxQueueNotifyMoreReceivedPacketsAvailable(interruptContext->RxQueue);
}
W przypadku urządzenia USB lub innej kolejki z mechanizmem do obsługi odbioru za pomocą oprogramowania, sterownik klienta powinien monitorować we własnym kontekście, czy powiadomienie kolejki jest włączone. Z procedury uzupełniania (wyzwalanej na przykład, gdy komunikat stanie się dostępny w czytniku ciągłym USB), wywołaj NetRxQueueNotifyMoreReceivedPacketsAvailable, jeśli powiadomienie jest włączone. W poniższym przykładzie pokazano, jak można to zrobić.
VOID
UsbEvtReaderCompletionRoutine(
_In_ WDFUSBPIPE Pipe,
_In_ WDFMEMORY Buffer,
_In_ size_t NumBytesTransferred,
_In_ WDFCONTEXT Context
)
{
UNREFERENCED_PARAMETER(Pipe);
PUSB_RCB_POOL pRcbPool = *((PUSB_RCB_POOL*) Context);
PUSB_RCB pRcb = (PUSB_RCB) WdfMemoryGetBuffer(Buffer, NULL);
pRcb->DataOffsetCurrent = 0;
pRcb->DataWdfMemory = Buffer;
pRcb->DataValidSize = NumBytesTransferred;
WdfObjectReference(pRcb->DataWdfMemory);
ExInterlockedInsertTailList(&pRcbPool->ListHead,
&pRcb->Link,
&pRcbPool->ListSpinLock);
if (InterlockedExchange(&pRcbPool->NotificationEnabled, FALSE) == TRUE)
{
NetRxQueueNotifyMoreReceivedPacketsAvailable(pRcbPool->RxQueue);
}
}
Anulowanie kolejek pakietów
Gdy system operacyjny zatrzymuje ścieżkę danych, rozpoczyna od wywołania funkcji zwrotnej EvtPacketQueueCancel klienta. To wywołanie zwrotne polega na tym, że sterowniki klienta wykonują wszelkie wymagane operacje przetwarzania, zanim platforma usunie kolejki pakietów. Anulowanie kolejki transmisji jest opcjonalne i zależy od tego, czy sprzęt obsługuje anulowanie transmisji w locie, ale anulowanie kolejki odbierania jest wymagane.
Podczas EvtPacketQueueCancelsterowniki, w razie potrzeby, zwracają pakiety do systemu operacyjnego. Aby zapoznać się z przykładami kodu anulowania kolejki nadawczej i kolejki odbiorczej, zobacz Anulowanie danych sieciowych za pomocą pierścieni sieciowych.
Po wywołaniu wywołania zwrotnego EvtPacketQueueCancel, struktura kontynuuje sondowanie wywołania zwrotnego EvtPacketQueueAdvance, aż wszystkie pakiety i bufory zostaną zwrócone do systemu operacyjnego.