Udostępnij przez


Przesyłanie pracy w trybie użytkownika

Ważne

Niektóre informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany przed jego wydaniem komercyjnym. Firma Microsoft nie udziela żadnych gwarancji, wyraźnych ani domniemanych, w odniesieniu do podanych tutaj informacji.

W tym artykule opisano funkcję przesyłania pracy w trybie użytkownika (UM), która jest nadal opracowywana od wersji Windows 11, 24H2 (WDDM 3.2). Przesyłanie pracy UM umożliwia aplikacjom przesyłanie pracy do procesora GPU bezpośrednio z trybu użytkownika z bardzo małym opóźnieniem. Celem jest zwiększenie wydajności aplikacji, które często przesyłają małe obciążenia do procesora GPU. Ponadto przesyłanie w trybie użytkownika ma znacznie przynieść korzyści takim aplikacjom, jeśli działają wewnątrz kontenera lub maszyny wirtualnej. Ta korzyść wynika z faktu, że sterownik trybu użytkownika (UMD) uruchomiony na maszynie wirtualnej może bezpośrednio przesyłać pracę do procesora GPU bez konieczności wysyłania komunikatu do hosta.

Sterowniki i sprzęt IHV, które obsługują przesyłanie pracy w trybie użytkownika (UM), muszą jednocześnie nadal wspierać tradycyjny model przesyłania pracy w trybie jądra. Ta obsługa jest niezbędna w scenariuszach, takich jak starszy gość, który obsługuje tylko tradycyjne kolejki km uruchomione na najnowszym hoście.

W tym artykule nie omówiono interoperacyjności przesyłania UM z funkcją Flip/FlipEx. Opisane w tym artykule przesyłanie UM jest ograniczone do scenariuszy z kategorii tylko renderowania lub obliczeń. Potok prezentacji nadal opiera się na przesyłaniu w trybie jądra, ponieważ wymaga natywnych monitorowanych barier synchronizacyjnych. Projekt i implementacja prezentacji opartej na przesyłaniu UM można rozważać, gdy natywne monitorowane bariery i przesyłanie UM wyłącznie do obliczeń/renderowania zostaną w pełni zaimplementowane. W związku z tym sterowniki powinny obsługiwać przesyłanie w trybie użytkownika oddzielnie dla każdej kolejki.

Dzwonki drzwiowe

Większość obecnych lub nadchodzących generacji procesorów GPU, które obsługują planowanie sprzętu, obsługują również koncepcję dzwonka do drzwi procesora GPU. Dzwonek to mechanizm sygnalizujący silnikowi GPU, że nowe zadanie jest umieszczone w jego kolejce. Dzwonki są zwykle rejestrowane w PCIe BAR (Base Address Register) lub pamięci systemowej. Każdy procesor GPU IHV ma własną architekturę, która określa liczbę dzwonków, gdzie znajdują się w systemie i tak dalej. System operacyjny Windows używa dzwonków do drzwi w ramach projektu w celu zaimplementowania przesyłania pracy UM.

Na wysokim poziomie istnieją dwa różne modele dzwonków drzwi wdrożonych przez różnych producentów sprzętu IHV i jednostki GPU:

  • Globalne dzwonki do drzwi

    W modelu Global Doorbells wszystkie kolejki sprzętowe w kontekstach i procesach współdzielą jeden globalny dzwonek. Wartość zapisana w dzwonce do drzwi informuje harmonogram procesora GPU o tym, która konkretna kolejka sprzętowa i silnik ma nową pracę. Sprzęt GPU używa formy mechanizmu pollingu do przydzielania zadań, jeśli wiele kolejek sprzętowych aktywnie przesyła pracę i aktywuje ten sam globalny mechanizm sygnalizacyjny.

  • Dedykowane dzwonki do drzwi

    W dedykowanym modelu dzwonka do drzwi każda kolejka sprzętowa ma przypisany własny dzwonek, który jest uruchamiany za każdym razem, gdy do procesora GPU ma zostać przesłana nowa praca. Gdy dzwonek drzwi jest uruchamiany, harmonogram procesora GPU wie dokładnie, która kolejka sprzętowa przesłała nową pracę. Istnieją ograniczone dzwonki drzwi, które są współużytkowane we wszystkich kolejkach sprzętowych utworzonych na procesorze GPU. Jeśli liczba utworzonych kolejek sprzętowych przekracza liczbę dostępnych dzwonków, sterownik musi odłączyć dzwonek starszej lub najrzadziej używanej kolejki sprzętowej i przypisać go do nowo utworzonej kolejki, skutecznie wirtualizując dzwonki.

Odnajdywanie obsługi przesyłania pracy w trybie użytkownika

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

W przypadku węzłów GPU, które obsługują funkcję UM przesyłania pracy, usługa KMD DxgkDdiGetNodeMetadata ustawia flagę metadanych węzła UserModeSubmissionSupported, dodawaną do DXGK_NODEMETADATA_FLAGS. System operacyjny umożliwia następnie usłudze UMD tworzenie trybu przesyłania HWQueues i dzwonków do drzwi tylko w węzłach, dla których ustawiono tę flagę.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Aby wysyłać zapytania dotyczące informacji specyficznych dla dzwonka, system operacyjny wywołuje funkcję DxgkDdiQueryAdapterInfo z typem informacji o adapterze zapytań DXGKQAITYPE_USERMODESUBMISSION_CAPS. Usługa KMD reaguje, wypełniając strukturę DXGK_USERMODESUBMISSION_CAPS ze szczegółami pomocy technicznej dotyczącymi przesyłania pracy w trybie użytkownika.

Obecnie jedynym wymaganym limitem jest rozmiar pamięci dzwonka drzwiowego (w bajtach). Dxgkrnl potrzebuje rozmiaru pamięci typu doorbell z kilku powodów:

  • Podczas tworzenia doorbell (D3DKMTCreateDoorbell), Dxgkrnl zwraca DoorbellCpuVirtualAddress do UMD. Przed wykonaniem tej czynności Dxgkrnl najpierw musi wewnętrznie mapować na fikcyjną stronę, ponieważ dzwonek do drzwi nie jest jeszcze przypisany i połączony. Rozmiar dzwonka jest potrzebny do przydzielenia strony testowej.
  • Podczas łączenia dzwonka (D3DKMTConnectDoorbell), Dxgkrnl musi przekształcić DoorbellCpuVirtualAddress na DoorbellPhysicalAddress dostarczony przez KMD. Ponownie , Dxgkrnl musi znać rozmiar dzwonka do drzwi.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission w D3DKMTCreateHwQueue

Usługa UMD ustawia dodaną flagę UserModeSubmission dodaną do D3DDDI_CREATEHWQUEUEFLAGS do tworzenia kolejek HWQueues korzystających z modelu przesyłania w trybie użytkownika. Kolejki HWQueues utworzone przy użyciu tej flagi nie mogą korzystać ze standardowej ścieżki przesyłania pracy w trybie jądra i muszą polegać na mechanizmie dzwonka do zgłaszania pracy w kolejce.

Interfejsy API zlecania pracy w trybie użytkownika

Następujące interfejsy API trybu użytkownika są dodawane do obsługi przesyłania pracy w trybie użytkownika.

  • D3DKMTCreateDoorbell tworzy dzwonek do drzwi dla D3D HWQueue na potrzeby przesyłania pracy w trybie użytkownika.

  • D3DKMTConnectDoorbell łączy wcześniej utworzony dzwonek drzwiowy z D3D HWQueue na potrzeby przesyłania pracy w trybie użytkownika.

  • D3DKMTDestroyDoorbell niszczy wcześniej utworzony dzwonek drzwiowy.

  • D3DKMTNotifyWorkSubmission powiadamia KMD, że nowe zadanie zostało przesłane w kolejce HWQueue. Punktem tej funkcji jest ścieżka przesyłania pracy o niskim opóźnieniu, w której KMD nie jest zaangażowane ani świadome, kiedy praca jest przesyłana. Ten interfejs API jest przydatny w scenariuszach, w których usługa KMD musi być powiadamiana za każdym razem, gdy praca jest przesyłana do kolejki HWQueue. Sterowniki powinny używać tego mechanizmu w określonych i rzadkich scenariuszach, ponieważ obejmuje on pełny cykl z UMD do KMD przy każdym przesłaniu pracy, co zaprzecza celowi modelu z niskimi opóźnieniami w trybie użytkownika.

Model rezydencji alokacji pamięci i buforu dzwonka drzwiowego

  • UMD odpowiada za zapewnienie rezydentności buforu pierścienia i alokacji kontroli buforu pierścienia przed utworzeniem dzwonka.
  • UMD zarządza okresem istnienia bufora pierścieniowego i alokacjami kontrolnymi bufora pierścieniowego. Dxgkrnl nie zniszczy tych alokacji niejawnie, nawet jeśli odpowiedni dzwonek drzwi zostanie zniszczony. Usługa UMD jest odpowiedzialna za przydzielanie i niszczenie tych alokacji. Aby zapobiec niszczeniu tych alokacji przez złośliwy program w trybie użytkownika, gdy dzwonek jest aktywny, Dxgkrnl utrzymuje referencje do nich przez okres istnienia dzwonka.
  • Jedynym scenariuszem, w którym Dxgkrnl niszczy alokacje buforu pierścieniowego, jest zakończenie działania urządzenia. Dxgkrnl niszczy wszystkie alokacje HWQueues, dzwonki drzwi i bufor pierścieniowy skojarzone z urządzeniem.
  • Tak długo, jak alokacje buforu pierścienia są aktywne, bufor pierścieniowy CPUVA jest zawsze prawidłowy i dostępny dla UMD dostępu, niezależnie od stanu połączeń dzwonka drzwi. Oznacza to, że rezydencja buforu pierścieniowego nie jest związana z dzwonkiem do drzwi.
  • Gdy usługa KMD wykonuje wywołanie zwrotne DXG w celu odłączenia dzwonka drzwiowego (czyli wywołuje DxgkCbDisconnectDoorbell ze stanem D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl obraca dzwonek do drzwi CPUVA do fikcyjnej strony. Nie wyklucza ani nie demapuje przydziałów bufora pierścieniowego.
  • W przypadku jakichkolwiek scenariuszy utraty urządzenia (TDR/GPU Stop/Page itp.), Dxgkrnl rozłącza dzwonek i oznacza stan jako D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. Tryb użytkownika jest odpowiedzialny za zniszczenie kolejki HWQueue, dzwonka drzwi, bufor pierścienia i ponowne ich utworzenie. To wymaganie jest podobne do sposobu niszczenia i ponownego tworzenia innych zasobów urządzeń w tym scenariuszu.

Zawieszenie kontekstu sprzętowego

Gdy system operacyjny zawiesza kontekst sprzętu, Dxgkrnl utrzymuje aktywne połączenie dzwonka drzwi i bufor pierścieniowy (kolejka robocza) alokacji rezydenta. W ten sposób UMD może kontynuować kolejkowanie zadań w kontekście; te zadania po prostu nie są planowane, gdy kontekst jest zawieszony. Po wznowieniu i zaplanowaniu kontekstu, procesor zarządzania kontekstem GPU (CMP) obserwuje nowy wskaźnik zapisu oraz przesyłanie pracy.

Ta logika jest podobna do obecnej logiki przesyłania w trybie jądra, gdzie UMD może wywołać D3DKMTSubmitCommand z zawieszonym kontekstem. Dxgkrnl umieszcza to nowe polecenie w HwQueue, ale nie zostanie ono zaplanowane do wykonania aż do późniejszego czasu.

Podczas wstrzymywania i wznawiania kontekstu sprzętowego występuje następująca sekwencja zdarzeń.

  • Wstrzymywanie kontekstu sprzętowego:

    1. Dxgkrnl wywołuje DxgkddiSuspendContext.
    2. KMD usuwa wszystkie kolejki HW kontekstu z listy harmonogramu HW.
    3. Dzwonki drzwi są nadal połączone, a kontrola alokacji bufora pierścieniowego jest nadal w pamięci. Usługa UMD może zapisywać nowe polecenia w kolejce HWQueue tego kontekstu, ale GPU ich nie przetworzy, co jest podobne do aktualnego przesyłania poleceń w trybie jądra do zawieszonego kontekstu.
    4. Jeśli KMD zdecyduje się atakować system dzwonka zawieszonej kolejki sprzętowej, wtedy UMD traci połączenie. Usługa UMD może podjąć próbę ponownego połączenia dzwonka drzwiowego, a usługa KMD przypisze nowy dzwonek do tej kolejki. Zamiarem nie jest zatrzymanie UMD, ale raczej umożliwienie mu kontynuowania przesyłania pracy, którą aparat HW może ostatecznie przetworzyć po wznowieniu kontekstu.
  • Wznawianie kontekstu sprzętu:

    1. Dxgkrnl wywołuje DxgkddiResumeContext.
    2. Usługa KMD dodaje wszystkie kolejki HWQueues kontekstu do listy harmonogramu HW.

Przejścia stanów F silnika

W tradycyjnym przekazywaniu pracy w trybie jądra Dxgkrnl jest odpowiedzialny za przesyłanie nowych poleceń do kolejki HWQueue i monitorowanie przerwań ukończenia z KMD. Z tego powodu Dxgkrnl ma pełny widok, gdy silnik jest aktywny i bezczynny.

W przypadku przesyłania pracy w trybie użytkownika Dxgkrnl monitoruje, czy silnik GPU wykonuje postępy przy użyciu rytmu limitu czasu TDR. Dlatego też, jeśli zasadnym jest zainicjowanie przejścia do stanu F1 wcześniej niż w dwóch sekundach limitu czasu TDR, sterownik KMD może poprosić system operacyjny o to.

Wprowadzono następujące zmiany w celu ułatwienia tego podejścia:

  • Typ przerwania DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE jest dodawany do DXGK_INTERRUPT_TYPE. Usługa KMD używa tego przerwania w celu powiadamiania Dxgkrnl o przejściach stanu silnika, które wymagają akcji zasilania GPU lub odzyskiwania po przekroczeniu limitu czasu, takich jak Aktywne —> TransitionToF1 i Aktywne —> Zawieszenie.

  • Struktura danych przerwania engineStateChange jest dodawana do DXGKARGCB_NOTIFY_INTERRUPT_DATA.

  • Dodano wyliczenie DXGK_ENGINE_STATE reprezentujące przejścia stanu silnika dla EngineStateChange.

Gdy usługa KMD zgłasza przerwanie DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE z parametrem EngineStateChange.NewState ustawionym na DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl rozłącza wszystkie sygnały HWQueues na tym silniku, a następnie inicjuje przejście składnika zasilania z F0 do F1.

Gdy UMD próbuje przesłać nową pracę do silnika GPU w stanie F1, musi ponownie połączyć sygnał, co z kolei powoduje, że Dxgkrnl inicjuje przejście do stanu zasilania F0.

Przejścia stanu D silnika

Podczas przejścia stanu zasilania urządzenia D0 do D3 Dxgkrnl zawiesza HWQueue, rozłącza dzwonek drzwi (obracając dzwonek CPUVA na fikcyjną stronę) i aktualizuje stan doorbellStatusCpuVirtualAddress do D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Jeśli UMD wywołuje D3DKMTConnectDoorbell, gdy procesor GPU jest w D3, wymusza to, że Dxgkrnl przywraca układ GPU do stanu D0. Dxgkrnl jest również odpowiedzialny za wznowienie HWQueue i przekierowanie CPUVA do fizycznej lokalizacji dzwonka.

Odbywa się następująca sekwencja zdarzeń.

  • Występuje wyłączenie zasilania procesora GPU D0 do D3:

    1. Dxgkrnl wywołuje DxgkddiSuspendContext dla wszystkich kontekstów sprzętowych na GPU. KMD usuwa te konteksty z listy harmonogramu HW.
    2. Dxgkrnl rozłącza wszystkie dzwonki.
    3. Dxgkrnl prawdopodobnie eksmituje wszystkie alokacje pierścieniowego buforu i kontroli buforu pierścieniowego z VRAM w razie potrzeby. Robi to po zawieszeniu i usunięciu wszystkich kontekstów z listy harmonogramu zadań sprzętu, tak aby sprzęt nie odwoływał się do usuniętej pamięci.
  • UMD zapisuje nowe polecenie w kolejce HWQueue, gdy GPU jest w stanie D3.

    1. UMD widzi, że dzwonek do drzwi jest odłączony, więc wywołuje D3DKMTConnectDoorbell.
    2. Dxgkrnl inicjuje przejście D0.
    3. Dxgkrnl zapewnia, że wszystkie alokacje bufora pierścieniowego/kontroli bufora pierścieniowego stają się rezydentne, jeśli zostały usunięte z pamięci.
    4. Dxgkrnl wywołuje funkcję DxgkddiCreateDoorbell usługi KMD, aby zażądać połączenia dzwonka do drzwi dla tego HWQueue.
    5. Dxgkrnl wywołuje element DxgkdiResumeContext dla wszystkich elementów HWContext. KMD dodaje odpowiednie kolejki do listy harmonogramu sprzętowego.

DDI na potrzeby przekazywania zadań w trybie użytkownika

Identyfikatory DDIs implementowane przez KMD

Następujące DDI w trybie jądra są dodawane do KMD w celu implementacji obsługi przesyłania zadań w trybie użytkownika.

  • DxgkDdiCreateDoorbell. Gdy UMD wywołuje D3DKMTCreateDoorbell, aby utworzyć dzwonek dla HWQueue, Dxgkrnl wykonuje odpowiednie wywołanie tej funkcji, aby KMD mógł zainicjować jego struktury dzwonka.

  • DxgkDdiConnectDoorbell. Gdy UMD wywołuje D3DKMTConnectDoorbell, Dxgkrnl wykonuje odpowiednie wywołanie tej funkcji, aby KMD mógł zapewnić procesor CPUVA mapowany na fizyczną lokalizację dzwonka drzwi, a także wykonać wymagane połączenia między obiektem HWQueue, obiektem dzwonka drzwi, adresem fizycznym dzwonka drzwi, harmonogramem procesora GPU itd.

  • DxgkDdiDisconnectDoorbell. Gdy system operacyjny chce odłączyć określony dzwonek do drzwi, wywołuje usługę KMD z tym identyfikatorem DDI.

  • DxgkDdiDestroyDoorbell. Gdy UMD wywołuje D3DKMTDestroyDoorbell, Dxgkrnl wykonuje odpowiednie wywołanie tej funkcji, aby KMD mógł zniszczyć odpowiednie struktury dzwonka.

  • DxgkDdiNotifyWorkSubmission. Gdy UMD wywołuje D3DKMTNotifyWorkSubmission, Dxgkrnl wykonuje odpowiednie wywołanie tej funkcji, aby KMD zostało powiadomione o nowych zleceniach pracy.

DDI zaimplementowane przez Dxgkrnl

Wywołanie zwrotne DxgkCbDisconnectDoorbell jest implementowane przez Dxgkrnl. Usługa KMD może wywołać tę funkcję, aby powiadomić Dxgkrnl , że usługa KMD musi odłączyć określony dzwonek.

Zmiany ogrodzenia postępu kolejki HW

Kolejki sprzętowe działające w modelu przesyłania zadań UM nadal mają koncepcję monotonicznie rosnącej wartości blokady postępu, którą generuje i zapisuje UMD po zakończeniu bufora poleceń. Aby Dxgkrnl wiedział, czy określona kolejka sprzętowa ma oczekującą pracę, UMD musi zaktualizować wartość bariery postępu przed dołączeniem nowego buforu poleceń do buforu pierścienia i uczynieniem go widocznym dla GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress jest mapowaniem użytkownika w trybie odczytu/zapisu procesu dla najnowszej wartości w kolejce.

Ważne jest, aby UMD upewniło się, że wartość w kolejce jest aktualizowana bezpośrednio przed przekazaniem nowego zgłoszenia procesorowi GPU. Poniższe kroki to zalecana sekwencja operacji. Zakładają, że kolejka HW jest bezczynna, a ostatni gotowy bufor miał wartość ogrodzenia postępu N.

  • Wygeneruj nową wartość progu postępu N+1.
  • Uzupełnij bufor poleceń. Ostatnia instrukcja buforu poleceń to zapis wartości bariery postępu do N+1.
  • Poinformuj system operacyjny o nowo w kolejce wartości, ustawiając wartość *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) równą N+1.
  • Uczyń bufor poleceń widocznym dla GPU, dodając go do bufora pierścieniowego.
  • Naciśnij dzwonek do drzwi.

Normalne i nietypowe kończenie procesu

Następująca sekwencja zdarzeń odbywa się podczas normalnego zakończenia procesu.

Dla każdej kolejki sprzętowej urządzenia w kontekście:

  1. Dxgkrnl wywołuje DxgkDdiDisconnectDoorbell, aby odłączyć dzwonek.
  2. Dxgkrnl czeka na zakończenie ostatniej kolejki HwQueueProgressFenceLastQueuedValueCPUVirtualAddress na procesorze GPU. Alokacje kontroli buforu cyklicznego oraz samego buforu pozostają w pamięci.
  3. Oczekiwanie Dxgkrnl zostało spełnione i może teraz zniszczyć alokacje bufora pierścieniowego/kontroli bufora pierścieniowego oraz mechanizm synchronizacji 'doorbell' i obiekty HWQueue.

Następująca sekwencja zdarzeń odbywa się podczas nietypowego zakończenia procesu.

  1. Dxgkrnl oznacza urządzenie z błędem.

  2. Dla każdego kontekstu urządzenia Dxgkrnl wywołuje element DxgkddiSuspendContext , aby zawiesić kontekst. Alokacje kontroli buforu pierścieniowego są nadal obecne. KMD przejmuje kontekst i usuwa go z listy uruchomień HW.

  3. Dla każdego HWQueue kontekstu : Dxglrnl

    a. Wywołuje DxgkDdiDisconnectDoorbell , aby odłączyć dzwonek do drzwi.

    b. Niszczy alokacje buforu pierścieniowego i mechanizmu dzwonka oraz obiekty HWQueue.

Przykłady pseudokodów

Pseudokod przesyłania pracy w usłudze UMD

Poniższy pseudokod jest podstawowym przykładem modelu, który UMD powinien używać do tworzenia i przesyłania pracy do kolejek HWQueues przy użyciu interfejsów API typu doorbell. Zastanów się, że hHWqueue1 jest uchwytem do kolejki HWQueue utworzonej za pomocą flagi UserModeSubmission używając istniejącego interfejsu API D3DKMTCreateHwQueue.

// Create a doorbell for the HWQueue
D3DKMT_CREATE_DOORBELL CreateDoorbell = {};
CreateDoorbell.hHwQueue = hHwQueue1;
CreateDoorbell.hRingBuffer = hRingBufferAlloc;
CreateDoorbell.hRingBufferControl = hRingBufferControlAlloc;
CreateDoorbell.Flags.Value = 0;

NTSTATUS ApiStatus =  D3DKMTCreateDoorbell(&CreateDoorbell);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

assert(CreateDoorbell.DoorbellCPUVirtualAddress!=NULL && 
      CreateDoorbell.DoorbellStatusCPUVirtualAddress!=NULL);

// Get a CPUVA of Ring buffer control alloc to obtain write pointer.
// Assume the write pointer is at offset 0 in this alloc
D3DKMT_LOCK2 Lock = {};
Lock.hAllocation = hRingBufferControlAlloc;
ApiStatus = D3DKMTLock2(&Lock);
if(!NT_SUCCESS(ApiStatus))
  goto cleanup;

UINT64* WritePointerCPUVirtualAddress = (UINT64*)Lock.pData;

// Doorbell created successfully. Submit command to this HWQueue

UINT64 DoorbellStatus = 0;
do
{
  // first connect the doorbell and read status
  ApiStatus = D3DKMTConnectDoorbell(hHwQueue1);
  D3DDDI_DOORBELL_STATUS DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));

  if(!NT_SUCCESS(ApiStatus) ||  DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT)
  {
    // fatal error in connecting doorbell, destroy this HWQueue and re-create using traditional kernel mode submission.
    goto cleanup_fallback;
  }

  // update the last queue progress fence value
  *(CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) = new_command_buffer_progress_fence_value;

  // write command to ring buffer of this HWQueue
  *(WritePointerCPUVirtualAddress) = address_location_of_command_buffer;

  // Ring doorbell by writing the write pointer value into doorbell address. 
  *(CreateDoorbell.DoorbellCPUVirtualAddress) = *WritePointerCPUVirtualAddress;

  // Check if submission succeeded by reading doorbell status
  DoorbellStatus = *(UINT64*(CreateDoorbell.DoorbellStatusCPUVirtualAddress));
  if(DoorbellStatus == D3DDDI_DOORBELL_STATUS_CONNECTED_NOTIFY)
  {
      D3DKMTNotifyWorkSubmission(CreateDoorbell.hDoorbell);
  }

} while (DoorbellStatus == D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY);

Ofiara pseudokodu dzwonka drzwiowego w KMD

W poniższym przykładzie pokazano, jak usługa KMD może wymagać "wirtualizacji" i udostępnić dostępne dzwonki do drzwi między kolejkami HWQueues na procesorach GPU, które używają dedykowanych dzwonków drzwiowych.

Pseudokod funkcji KMD VictimizeDoorbell() :

  • KMD decyduje, że logiczny dzwonek hDoorbell1 połączony z PhysicalDoorbell1 musi zostać wyłączony i odłączony.
  • Usługa KMD wywołuje Dxgkrnl'sDxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue) .
    • Dxgkrnl obraca CPUVA widoczny dla UMD tego dzwonka do strony zastępczej i aktualizuje wartość stanu na D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • KMD odzyskuje kontrolę i dokonuje faktycznego prześladowania/rozłączenia.
    • KMD wykorzystuje hDoorbell1 i odłącza go od PhysicalDoorbell1.
    • PhysicalDoorbell1 jest dostępny do użycia

Teraz rozważmy następujący scenariusz:

  1. W interfejsie PCI BAR znajduje się jeden fizyczny dzwonek drzwiowy z procesorem CPUVA w trybie jądra równym 0xfeedfeee. Obiekt dzwonka utworzony dla kolejki HWQueue ma przypisaną wartość fizyczną tego dzwonka.

    HWQueue KMD Handle: hHwQueue1
    Doorbell KMD Handle: hDoorbell1
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell1 =>  0xfeedfeee // hDoorbell1 is mapped to 0xfeedfeee
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell1 => D3DDDI_DOORBELL_STATUS_CONNECTED
    
  2. System operacyjny wywołuje DxgkDdiCreateDoorbell dla innego HWQueue2:

    HWQueue KMD Handle: hHwQueue2
    Doorbell KMD Handle: hDoorbell2
    Doorbell CPU Virtual Address: CpuVirtualAddressDoorbell2 => 0 // this doorbell object isn't yet assigned to a physical doorbell  
    Doorbell Status CPU Virtual Address: StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY
    
    // In the create doorbell DDI, KMD doesn't need to assign a physical doorbell yet, 
    // so the 0xfeedfeee doorbell is still connected to hDoorbell1
    
  3. System operacyjny wywołuje DxgkDdiConnectDoorbell na hDoorbell2.

    // KMD needs to victimize hDoorbell1 and assign 0xfeedfeee to hDoorbell2. 
    VictimizeDoorbell(hDoorbell1);
    
    // Physical doorbell 0xfeedfeee is now free and can be used vfor hDoorbell2.
    // KMD makes required connections for hDoorbell2 with HW
    ConnectPhysicalDoorbell(hDoorbell2, 0xfeedfeee)
    
    return 0xfeedfeee
    
    // On return from this DDI, *Dxgkrnl* maps 0xfeedfeee to process address space CPUVA i.e:
    // CpuVirtualAddressDoorbell2 => 0xfeedfeee
    
    // *Dxgkrnl* updates hDoorbell2 status to connected i.e:
    // StatusCpuVirtualAddressDoorbell2 => D3DDDI_DOORBELL_STATUS_CONNECTED
    ``
    
    

Ten mechanizm nie jest wymagany, jeśli procesor GPU używa globalnych dzwonków do drzwi. Zamiast tego w tym przykładzie zarówno hDoorbell1 i hDoorbell2 zostaną przypisane temu samemu 0xfeedfeee fizycznemu dzwonkowi.