Freigeben über


Arbeitsübermittlung im Benutzermodus

Von Bedeutung

Einige Informationen beziehen sich auf Vorabversionen, die vor der kommerziellen Freigabe grundlegend geändert werden können. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

In diesem Artikel wird das User-Mode-Arbeitsübermittlungsfeature beschrieben, das sich noch in der Entwicklung befindet, beginnend mit Windows 11, Version 24H2 (WDDM 3.2). Mit der UM-Arbeitsübermittlung können Anwendungen Die Arbeit direkt aus dem Benutzermodus mit sehr geringer Latenz an die GPU übermitteln. Ziel ist es, die Leistung von Anwendungen zu verbessern, die häufig kleine Workloads an die GPU übermitteln. Darüber hinaus wird davon ausgegangen, dass die Übermittlung im Benutzermodus solche Anwendungen erheblich zugute kommt, wenn sie in einem Container oder virtuellen Computer (VM) ausgeführt werden. Dieser Vorteil liegt daran, dass der im virtuellen Computer ausgeführte Benutzermodustreiber (USER-Mode Driver, UMD) direkt Arbeit an die GPU senden kann, ohne eine Nachricht an den Host senden zu müssen.

IHV-Treiber und Hardware, die die UM-Arbeitsübermittlung unterstützen, müssen weiterhin das herkömmliche Kernelmodus-Übermittlungsmodell gleichzeitig unterstützen. Diese Unterstützung ist für Szenarien wie einen älteren Gast erforderlich, der nur herkömmliche KM-Warteschlangen unterstützt, die auf einem neuesten Host ausgeführt werden.

In diesem Artikel wird die UM-Übermittlungsinteroperabilität mit Flip/FlipEx nicht behandelt. Die in diesem Artikel beschriebene UM-Übermittlung ist auf Szenarien der Render-Only-Klasse und der Berechnungsklasse beschränkt. Die Präsentationspipeline basiert weiterhin auf der Kernel-Modus-Übermittlung, da sie von nativ überwachten Zäunen abhängig ist. Der Entwurf und die Implementierung einer UM-übermittlungsbasierten Präsentation kann berücksichtigt werden, sobald native überwachte Zäune und UM-Übermittlung nur für Berechnung/Rendering vollständig implementiert sind. Daher sollten Treiber die Übermittlung des Benutzermodus pro Warteschlange unterstützen.

Türklingeln

Die meisten aktuellen oder bevorstehenden Generationen von GPUs, die die Hardwareplanung unterstützen, unterstützen auch das Konzept einer GPU-Türglocke. Eine Doorbell ist ein Mechanismus, der einer GPU-Engine signalisiert, dass neue Arbeit in die Arbeitswarteschlange eingereiht wird. Doorbells werden in der Regel im PCIe BAR (Basisadressleiste) oder im Systemspeicher registriert. Jeder GPU-IHV verfügt über eine eigene Architektur, die die Anzahl der Türglocken bestimmt, wo sie sich im System befinden usw. Das Windows-Betriebssystem verwendet Türglocken als Teil seines Designs, um die UM-Arbeitsübermittlung zu implementieren.

Auf hoher Ebene gibt es zwei verschiedene Modelle von Türglocken, die von verschiedenen IHV und GPUs implementiert werden:

  • Globale Türglocken

    Im Global Doorbells-Modell teilen alle Hardwarewarteschlangen über Kontexte und Prozesse hinweg eine einzelne globale Türglocke. Der Wert, der in die Klingel geschrieben wurde, informiert den GPU-Scheduler darüber, welche spezielle Hardwarewarteschlange und welche Engine neue Arbeit hat. Die GPU-Hardware verwendet eine Form eines Abfrageverfahrens, um Arbeit abzurufen, wenn mehrere Hardwarewarteschlangen aktiv Arbeit übermitteln und dieselbe globale Glocke läuten.

  • Spezielle Türglocken

    Im dedizierten Doorbell-Modell wird jeder Hardwarewarteschlange eine eigene Türglocke zugewiesen, die immer dann ausgelöst wird, wenn neue Arbeiten an die GPU übermittelt werden. Wenn ein Doorbell-Signal ausgelöst wird, weiß der GPU-Scheduler genau, welche Hardwarewarteschlange neue Arbeit eingereicht hat. Es gibt eine begrenzte Anzahl von Türglocken, die gemeinsam von allen hardwarebasierten Warteschlangen verwendet werden, die auf der GPU erstellt wurden. Wenn die Anzahl der erstellten Hardwarewarteschlangen die Anzahl der verfügbaren Türklingeln übersteigt, muss der Treiber die Türklingel einer älteren oder zuletzt verwendeten Hardwarewarteschlange trennen und ihre Türklingel einer neu erstellten Warteschlange zuweisen, was effektiv zu einer "Virtualisierung" der Türklingeln führt.

Entdeckung der Unterstützung für die Arbeitsübermittlung im Benutzermodus

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

Für GPU-Knoten, die das Um-Arbeitsübermittlungsfeature unterstützen, legt KMD's DxgkDdiGetNodeMetadata das Metadaten-Flag des UserModeSubmissionSupported-Knotens fest, das zu DXGK_NODEMETADATA_FLAGS hinzugefügt wird. Das Betriebssystem ermöglicht UMD dann das Erstellen von Benutzermodus-Übermittlungen HWQueues und Doorbells nur auf Knoten, für die dieses Flag festgelegt ist.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Zum Abfragen von Doorbell-spezifischen Informationen ruft das Betriebssystem die Funktion DxgkDdiQueryAdapterInfo von KMD mit dem Abfrageadapter-Infotyp DXGKQAITYPE_USERMODESUBMISSION_CAPS auf. KMD antwortet, indem sie eine DXGK_USERMODESUBMISSION_CAPS Struktur mit ihren Supportdetails für die Arbeitsübermittlung im Benutzermodus ausfüllt.

Derzeit ist die einzige erforderliche Begrenzung die Größe des Türklingel-Speichers (in Bytes). Dxgkrnl benötigt aus mehreren Gründen die Größe des Doorbell-Speichers:

  • Während der Doorbell Creation (D3DKMTCreateDoorbell) gibt Dxgkrnl eine DoorbellCpuVirtualAddress an UMD zurück. Dabei muss Dxgkrnl zunächst intern einer Dummyseite zugeordnet werden, da die Türglocke noch nicht zugewiesen und verbunden ist. Die Größe der Türglocke wird benötigt, um die Dummyseite zuzuordnen.
  • Während der Verbindung von Doorbell (D3DKMTConnectDoorbell) muss Dxgkrnl die DoorbellCpuVirtualAddress zu einer DoorbellPhysicalAddress drehen, die von KMD bereitgestellt wird. Auch hier muss Dxgkrnl die Türglockengröße kennen.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission in D3DKMTCreateHwQueue

UMD setzt das hinzugefügte UserModeSubmission-Flag in D3DDDI_CREATEHWQUEUEFLAGS, um HWQueues zu erstellen, die das Übermittlungsmodell im Benutzermodus verwenden. HWQueues, die mit diesem Flag erstellt wurden, können den regulären Kernelmodus-Übermittlungspfad nicht verwenden und müssen sich auf den Doorbell-Mechanismus für die Arbeitsübermittlung in der Warteschlange verlassen.

Arbeitsübermittlungs-APIs im Benutzermodus

Die folgenden Benutzermodus-APIs werden hinzugefügt, um die Einreichung von Aufgaben im Benutzermodus zu unterstützen.

  • D3DKMTCreateDoorbell erstellt einen Signalgeber für eine D3D-HW-Warteschlange zur Arbeitsübermittlung im Benutzermodus.

  • D3DKMTConnectDoorbell verbindet eine zuvor erstellte Doorbell mit einer D3D HWQueue zur Einreichung von Benutzerarbeiten.

  • D3DKMTDestroyDoorbell zerstört eine zuvor geschaffene Türglocke.

  • D3DKMTNotifyWorkSubmission benachrichtigt die KMD, dass neue Aufgaben an einer HWQueue eingereicht wurden. Der Zweck dieses Features ist ein Übermittlungspfad mit geringer Latenz, bei dem KMD nicht beteiligt ist oder sich der Arbeitsübermittlung nicht bewusst ist. Diese API ist in Szenarien nützlich, in denen die KMD benachrichtigt werden muss, sobald Arbeit in einer HWQueue eingereicht wird. Treiber sollten diesen Mechanismus in bestimmten und seltenen Szenarien verwenden, da es einen Roundtrip von UMD zu KMD für jede Arbeitsübermittlung beinhaltet und somit den Zweck eines Übermittlungsmodells mit geringer Latenz für den Benutzermodus besiegt.

Residenzmodell von Doorbell-Memory und Ringpufferzuweisungen

  • UMD ist dafür verantwortlich, die Ringpuffer- und Ringpuffersteuerungszuordnungen in den Speicher zu laden, bevor eine Türglocke erstellt wird.
  • UMD verwaltet die Lebensdauer des Ringpuffers und der Ringpuffersteuerungszuordnungen. Dxgkrnl zerstört diese Zuordnungen nicht implizit, auch wenn die entsprechende Türglocke zerstört wird. UMD ist für die Zuordnung und Zerstörung dieser Zuordnungen verantwortlich. Um jedoch zu verhindern, dass ein böswilliges Benutzermodus-Programm diese Zuordnungen zerstört, solange die Türklingel aktiv ist, nimmt Dxgkrnl während der Lebensdauer der Türklingel eine Referenz darauf.
  • Das einzige Szenario, in dem Dxgkrnl ringpufferzuordnungen zerstört, ist während der Geräteendung. Dxgkrnl zerstört alle mit dem Gerät verbundenen HWQueues, Türglocken und Ringpufferzuweisungen.
  • Solange die Ringpufferzuweisungen aktiv sind, ist der Ringpuffer-CPUVA immer gültig und steht UMD unabhängig vom Verbindungsstatus der Türglocken zur Verfügung. Das heißt, die Verweildauer im Ringpuffer ist nicht an die Klingel gebunden.
  • Wenn KMD den DXG-Rückruf durchführt, um eine Türklingel zu trennen (d. h. dxgkCbDisconnectDoorbell mit dem Status D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), verschiebt Dxgkrnl die Türklingel-CPUVA auf eine Dummy-Seite. Die Zuordnungen im Ringpuffer werden weder entfernt noch ungemappt.
  • Bei Verlustszenarien von Geräten (TDR/GPU Stop/Page usw.) unterbricht Dxgkrnl die Verbindung zur Türklingel und kennzeichnet den Status als D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. Der Benutzermodus ist dafür verantwortlich, die HWQueue, die Türklingel und den Ringpuffer zu zerstören und sie neu zu erstellen. Diese Anforderung ähnelt der Zerstörung und Neuerstellung anderer Geräteressourcen in diesem Szenario.

Aussetzung des Hardwarekontexts

Wenn das Betriebssystem einen Hardwarekontext anhält, hält Dxgkrnl die Verbindung zur Türglocke aktiv und hält die Zuweisung des Ringpuffers (Arbeitswarteschlange) im Speicher. Auf diese Weise kann UMD weiterhin Arbeit zum Kontext zur Warteschlange hinzufügen; diese Arbeit wird nur nicht eingeplant, während der Kontext angehalten wird. Sobald der Kontext fortgesetzt und angesetzt wurde, überwacht der Kontextverwaltungsprozessor (CMP) der GPU den neuen Schreibzeiger und die Aufgabeinreichung.

Diese Logik ähnelt der aktuellen Kernelmodus-Übermittlungslogik, bei der UMD D3DKMTSubmitCommand mit einem angehaltenen Kontext aufrufen kann. Dxgkrnl reiht diesen neuen Befehl in die HwQueue ein, aber er wird erst später ausgeführt.

Die folgende Abfolge von Ereignissen tritt während des Anhaltens und Fortsetzens des Hardwarekontexts auf.

  • Anhalten eines Hardwarekontexts:

    1. Dxgkrnl ruft DxgkddiSuspendContext auf.
    2. KMD entfernt alle HWQueues des Kontexts aus der Liste des HW-Schedulers.
    3. Doorbells sind noch angeschlossen, und Ringpuffer-/Ringpuffersteuerungszuordnungen sind noch vorhanden. Die UMD kann neue Befehle in die HWQueue dieses Kontexts schreiben, die GPU verarbeitet sie jedoch nicht, was der heutigen Kernelmodusbefehlsübermittlung in einen angehaltenen Kontext ähnelt.
    4. Wenn KMD sich entscheidet, die Türglocke eines ausgesetzten HWQueue zu beeinträchtigen, verliert UMD dann seine Verbindung. UMD kann versuchen, die Türklingel wieder zu verbinden, und KMD wird dieser Warteschlange eine neue Türklingel zuweisen. Die Absicht besteht darin, die UMD nicht zu blockieren, sondern die Übermittlung von Arbeiten fortzusetzen, die die HW-Engine schlussendlich verarbeiten kann, sobald der Kontext wieder aufgenommen wird.
  • Fortsetzen eines Hardwarekontexts:

    1. Dxgkrnl ruft DxgkddiResumeContext auf.
    2. KMD fügt alle HWQueues des Kontexts zur Liste des HW-Schedulers hinzu.

Engine-F-Zustandsübergänge

In der herkömmlichen Kernelmodus-Arbeitsübermittlung ist Dxgkrnl für die Übermittlung neuer Befehle an die HWQueue und die Überwachung von KMD stammenden Abschlussunterbrechungen verantwortlich. Aus diesem Grund verfügt Dxgkrnl über eine vollständige Ansicht, wann eine Engine aktiv ist und wann sie im Leerlauf ist.

Bei der Übermittlung im Benutzermodus überwacht Dxgkrnl, ob eine GPU-Engine Fortschritte mittels TDR-Timeout-Kadenz macht. Wenn es sich also lohnt, einen Übergang in den F1-Zustand früher als im zwei Sekunden dauernden TDR-Timeout zu initiieren, kann die KMD das Betriebssystem dazu auffordern.

Die folgenden Änderungen wurden vorgenommen, um diesen Ansatz zu erleichtern:

  • Der DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE Interrupttyp wird DXGK_INTERRUPT_TYPE hinzugefügt. KMD verwendet diesen Interrupt, um Dxgkrnl über Modulzustandsübergänge zu benachrichtigen, die eine GPU-Energieaktion oder Timeoutwiederherstellung erfordern, z. B. Active -> TransitionToF1 und Active –> Hung.

  • Die EngineStateChange-Unterbrechungsdatenstruktur wird DXGKARGCB_NOTIFY_INTERRUPT_DATA hinzugefügt.

  • Die DXGK_ENGINE_STATE Enumeration wird hinzugefügt, um die Modulzustandsübergänge für EngineStateChange darzustellen.

Wenn KMD einen DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE Interrupt auslöst, bei dem EngineStateChange.NewState auf DXGK_ENGINE_STATE_TRANSITION_TO_F1 festgelegt ist, trennt Dxgkrnl alle Türglocken von HWQueues auf diesem Motor und initiiert dann einen F0-zu F1-Energiekomponentenübergang.

Wenn die UMD versucht, neue Arbeit an das GPU-Modul im F1-Zustand zu übermitteln, muss sie die Türglocke erneut verbinden, was wiederum dazu führt, dass Dxgkrnl einen Übergang zurück zum F0-Energiezustand initiiert.

Motor-D-Zustandsübergänge

Während eines D0-zu-D3-Energiezustandsübergangs hält Dxgkrnl die HWQueue an, trennt den Doorbell (verschieben des Doorbell CPUVA auf eine Dummyseite), und außerdem wird der DoorbellStatusCpuVirtualAddress doorbell status auf D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY aktualisiert.

Wenn UMD D3DKMTConnectDoorbell aufruft, wenn sich die GPU in D3 befindet, erzwingt Dxgkrnl , die GPU auf D0 aufzuwachen. Dxgkrnl ist auch für die Wiederaufnahme der HWQueue und die Zuweisung der Doorbell-CPUVA an einen physischen Doorbell-Standort verantwortlich.

Die folgende Abfolge von Ereignissen findet statt.

  • Ein D0-zu-D3-GPU-Power-Down wird durchgeführt:

    1. Dxgkrnl ruft DxgkddiSuspendContext für alle HW-Kontexte auf der GPU auf. KMD entfernt diese Kontexte aus der Hardware-Planerliste.
    2. Dxgkrnl trennt alle Türglocken.
    3. Dxgkrnl kann bei Bedarf alle Zuordnungen von Ringpuffer und Ringpuffersteuerung aus dem VRAM entfernen. Dies geschieht, sobald alle Kontexte angehalten und aus der Liste des Hardware-Planers entfernt wurden, damit die Hardware keinen Bezug auf entfernten Speicher nimmt.
  • UMD schreibt einen neuen Befehl in eine HWQueue, wenn sich die GPU im D3-Zustand befindet:

    1. UMD sieht, dass die Türklingel getrennt ist, also ruft D3DKMTConnectDoorbell auf.
    2. Dxgkrnl initiiert einen D0-Übergang.
    3. Dxgkrnl macht alle Ringpuffer-/Ringpuffersteuerungszuordnungen resident, wenn sie entfernt wurden.
    4. Dxgkrnl ruft die DxgkddiCreateDoorbell-Funktion von KMD auf, um anzufordern, dass KMD eine Türglockenverbindung für diese HWQueue macht.
    5. Dxgkrnl ruft DxgkddiResumeContext für alle HWContexts auf. KMD fügt die entsprechenden Warteschlangen zur Liste des Hardware-Schedulers hinzu.

DDIs für die Übermittlung im Benutzermodus

KMD-implementierte DDIs

Die folgenden Kernelmodus DDIs werden für KMD hinzugefügt, um die Unterstützung für die Übermittlung von Aufgaben im Benutzermodus zu implementieren.

  • DxgkDdiCreateDoorbell. Wenn UMD D3DKMTCreateDoorbell aufruft, um eine Doorbell für eine HWQueue zu erstellen, führt Dxgkrnl einen entsprechenden Aufruf dieser Funktion durch, damit KMD seine Doorbell-Strukturen initialisieren kann.

  • DxgkDdiConnectDoorbell. Wenn UMD D3DKMTConnectDoorbell aufruft, führt Dxgkrnl einen entsprechenden Aufruf dieser Funktion durch, sodass KMD eine CPUVA bereitstellen kann, die der physischen Türbellposition zugeordnet ist, und auch die erforderlichen Verbindungen zwischen dem HWQueue-Objekt, dem Doorbell-Objekt, der physischen Adresse der Türglocke, dem GPU-Zeitplan usw. vornehmen kann.

  • DxgkDdiDisconnectDoorbell. Wenn das Betriebssystem eine bestimmte Türglocke trennen möchte, ruft es mit diesem DDI bei KMD an.

  • DxgkDdiDestroyDoorbell. Wenn UMD D3DKMTDestroyDoorbell aufruft, führt Dxgkrnl einen entsprechenden Aufruf dieser Funktion durch, damit KMD seine Klingelstrukturen zerstören kann.

  • DxgkDdiNotifyWorkSubmission. Wenn UMD D3DKMTNotifyWorkSubmission aufruft, führt Dxgkrnl einen entsprechenden Aufruf dieser Funktion durch, damit KMD über neue Arbeitsübermittlungen benachrichtigt werden kann.

Dxgkrnl-implementierte DDI

Der DxgkCbDisconnectDoorbell-Rückruf wird von Dxgkrnl implementiert. KMD kann diese Funktion aufrufen, um Dxgkrnl zu benachrichtigen, dass KMD eine bestimmte Türklingel deaktivieren muss.

Änderungen an den Fortschrittsbarrieren der Hardware-Warteschlange

Hardwarewarteschlangen, die im UM-Arbeitsübermittlungsmodell laufen, behalten weiterhin das Konzept eines monoton steigenden Fortschrittswerts bei, den UMD generiert und schreibt, sobald ein Befehlsbuffer abgeschlossen ist. Damit Dxgkrnl weiß, ob eine bestimmte Hardwarewarteschlange aussteht, muss die UMD den Statuszaunwert in der Warteschlange vor dem Anfügen eines neuen Befehlspuffers an den Ringpuffer aktualisieren und für die GPU sichtbar machen. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress ist eine Benutzerprozess-Mapping mit Lese-/Schreibzugriff des zuletzt eingereihten Wertes.

Es ist wichtig, dass die UMD sicherstellen muss, dass der in die Warteschlange eingereihte Wert direkt aktualisiert wird, bevor die neue Übermittlung für die GPU sichtbar wird. Die folgenden Schritte sind die empfohlene Abfolge von Vorgängen. Sie gehen davon aus, dass die HW-Warteschlange im Leerlauf ist und der letzte fertige Puffer einen Progress-Fence-Wert von N hatte.

  • Generieren Sie einen neuen Fortschrittsbarrierewert N+1.
  • Füllen Sie den Befehlspuffer aus. Die letzte Anweisung des Befehlspuffers ist das Schreiben eines Fortschrittsgrenzenwerts an N+1.
  • Informieren Sie das Betriebssystem über den neu in die Warteschlange gestellten Wert, indem Sie *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) auf N+1 festlegen.
  • Machen Sie den Befehlspuffer für die GPU sichtbar, indem Sie ihn dem Ringpuffer hinzufügen.
  • Klingeln Sie an der Tür.

Normale und abnorme Beendigung des Prozesses

Die folgende Abfolge von Ereignissen findet während der normalen Beendigung des Prozesses statt.

Für jede HWQueue des Geräts/Kontexts:

  1. Dxgkrnl ruft DxgkDdiDisconnectDoorbell auf, um die Türglocke zu trennen.
  2. Dxgkrnl wartet darauf, dass das letzte in die Warteschlange eingereihte HwQueueProgressFenceLastQueuedValueCPUVirtualAddress auf der GPU abgeschlossen wird. Zuweisungen für Ringpuffer und Ringpuffersteuerung bleiben im Speicher.
  3. Das Warten von Dxgkrnl ist beendet, und es kann jetzt die Ring-Buffer/Ring-Buffer-Control-Zuordnungen sowie die Doorbell- und HWQueue-Objekte zerstören.

Die folgende Abfolge von Ereignissen findet während einer abnormen Beendigung des Prozesses statt.

  1. Dxgkrnl kennzeichnet das Gerät bei einem Fehler.

  2. Für jeden Gerätekontext ruft DxgkrnlDxgkddiSuspendContext auf, um den Kontext auszusetzen. Die Zuordnungen des Ringpuffer-/Ringpuffersteuerelements sind weiterhin vorhanden. KMD greift dem Kontext vor und entfernt ihn aus der HW-Ausführungsliste.

  3. Für jede HWQueue des Kontexts, Dxglrnl:

    a) Ruft DxgkDdiDisconnectDoorbell auf, um die Doorbell zu trennen.

    b. Zerstört die Ringpuffer-/Ringpuffersteuerungszuordnungen sowie die Doorbell- und HWQueue-Objekte.

Pseudocodebeispiele

Pseudocode zur Einreichung von Arbeiten in UMD

Der folgende Pseudocode ist ein einfaches Beispiel für das Modell, das UMD zum Erstellen und Übermitteln von Arbeiten an HWQueues mithilfe der Doorbell-APIs verwenden soll. Betrachten Sie hHWqueue1 als das Handle zu einem HWQueue, das mit dem UserModeSubmission-Flag unter Verwendung der vorhandenen D3DKMTCreateHwQueue-API erstellt wurde.

// 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);

Ausschaltung von Doorbell-Pseudocode in KMD

Das folgende Beispiel veranschaulicht, wie KMD möglicherweise "virtualisieren" und die verfügbaren Türglocken zwischen den HWQueues auf GPUs teilen muss, die dedizierte Türglocken verwenden.

Pseudocode der VictimizeDoorbell() KMD-Funktion:

  • KMD entscheidet, dass die logische Türglocke hDoorbell1, die mit PhysicalDoorbell1 verbunden ist, entfernt und getrennt werden muss.
  • KMD ruft Dxgkrnl's DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue) auf.
    • Dxgkrnl verschiebt die UMD-sichtbare CPUVA dieses Doorbells auf eine Dummy-Seite und aktualisiert den Statuswert auf D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • KMD erhält die Kontrolle zurück und führt die tatsächliche Schädigung/Trennung durch.
    • KMD verletzt hDoorbell1 und trennt es von PhysicalDoorbell1.
    • PhysicalDoorbell1 steht zur Verwendung zur Verfügung.

Betrachten Sie nun das folgende Szenario:

  1. Es gibt eine einzelne physische Türglocke in der PCI-BAR mit einem Kernelmodus CPUVA gleich 0xfeedfeee. Diesem physischen Türklingelwert wird ein für eine HWQueue erstelltes Türklingel-Objekt zugewiesen.

    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. Das Betriebssystem ruft DxgkDdiCreateDoorbell nach einer anderen 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. Das Betriebssystem ruft DxgkDdiConnectDoorbell auf 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
    ``
    
    

Dieser Mechanismus ist nicht erforderlich, wenn eine GPU globale Türglocken verwendet. Stattdessen würden in diesem Beispiel sowohl hDoorbell1, als auch hDoorbell2 dieselbe 0xfeedfeee physische Türglocke zugewiesen.