Freigeben über


Arbeitsübermittlung im Benutzermodus

Wichtig

Einige Informationen beziehen sich auf Vorabversionen, die vor der kommerziellen Freigabe grundlegend geändert werden können. Microsoft übernimmt hinsichtlich der hier bereitgestellten Informationen keine Gewährleistungen, seien sie ausdrücklich oder konkludent.

In diesem Artikel wird das User-Mode-(UM-)Arbeitsübermittlungsfeature beschrieben, das noch ab Windows 11, Version 24H2 (WDDM 3.2) entwickelt wird. Mit der UM-Arbeitsübermittlung können Anwendungen die Arbeit direkt aus dem Benutzermodus mit einer 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 Übertragung im Benutzermodus für solche Anwendungen, die in einem Container oder einer virtuellen Maschine (VM) ausgeführt werden, von erheblichem Vorteil ist. 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 gleichzeitig das herkömmliche Kernelmodus-Übermittlungsmodell unterstützen. Diese Unterstützung ist für Szenarien wie ein älteres Gastbetriebssystem erforderlich, das 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 das Nur Rendern/Computeklasse von Szenarien beschränkt. Die Präsentationspipeline basiert weiterhin auf der Kernel-Modus-Übermittlung, da sie von nativen überwachten Zäunen abhängt. Der Entwurf und die Implementierung einer auf UM-Übermittlung basierten Präsentation kann berücksichtigt werden, sobald systemeigene überwachte Zäune und UM-Übermittlung für Compute/Nur rendern vollständig implementiert sind. Daher sollten Treiber die Benutzermodus-Übermittlung 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ürklingel. Eine Türklingel ist ein Mechanismus, der einer GPU-Engine anzeigt, dass sich neue Arbeit in ihrer Warteschlange befindet. Türklingeln 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, ihren Standort im System usw. bestimmt. Das Windows-Betriebssystem verwendet Türglocken als Teil seines Designs, um die UM-Arbeitsübermittlung zu implementieren.

Auf einer hohen Ebene gibt es zwei verschiedene Modelle von Türklingeln, 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 Türklingel geschrieben wird, informiert den GPU-Scheduler darüber, welche Hardwarewarteschlange und welche Engine neue Arbeit hat. Die GPU-Hardware verwendet eine Art Abrufmechanismus, um Arbeit abzurufen, wenn mehrere Hardwarewarteschlangen aktiv Arbeit übermitteln und die gleiche globale Türglocke betätigen.

  • Dedizierte 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 eine Türglocke ausgelöst wird, weiß der GPU-Scheduler genau, welche Hardwarewarteschlange neue Arbeit übermittelt hat. Es gibt eine begrenzte Anzahl von Türklingeln, die von allen auf dem Grafikprozessor erstellten Hardwarewarteschlangen gemeinsam genutzt werden. Wenn die Anzahl der erstellten Hardwarewarteschlangen die Anzahl der verfügbaren Türklingeln übersteigt, muss der Treiber die Türklingel einer älteren oder am wenigsten genutzten Hardwarewarteschlange abkoppeln und ihre Türklingel einer neu erstellten Warteschlange zuweisen.

Ermitteln der Unterstützung für die Benutzermodus-Übermittlung

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

Für GPU-Knoten, die das UM-Arbeitsübermittlungsfeature unterstützen, legt DxgkDdiGetNodeMetadata von KMD das UserModeSubmissionSupported-Knotenmetadaten-Flag fest, das dem DXGK_NODEMETADATA_FLAGS hinzugefügt wird. Das Betriebssystem ermöglicht UMD dann das Erstellen von Benutzermodus-Übermittlungs-HWQueues und Türklingen nur auf den Knoten, für die dieses Flag festgelegt ist.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Zum Abfragen von Türklingel-spezifischen Informationen ruft das Betriebssystem die DxgkDdiQueryAdapterInfo-Funktion von KMD mit dem DXGKQAITYPE_USERMODESUBMISSION_CAPS-Abfragenadapterinfo-Typ ab. KMD antwortet, indem eine DXGK_USERMODESUBMISSION_CAPS-Struktur mit den Supportdetails für die Übermittlung im Benutzermodus aufgefüllt wird.

Derzeit ist nur die Größe des Türklingelspeichers (in Byte) erforderlich. Dxgkrnl benötigt die Größe der Türglockenspeicher unter anderem aus den folgenden Gründen:

  • Während der Türklingelerstellung (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.
  • Beim Herstellen der Verbindung mit der Türklingel (D3DKMTConnectDoorbell), muss Dxgkrnl die DoorbellCpuVirtualAddress zu einer DoorbellPhysicalAddress rotieren, die von KMD bereitgestellt wird. Auch hier muss Dxgkrnl die Türglockengröße kennen.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission in D3DKMTCreateHwQueue

UMD legt das Flag UserModeSubmission fest, das dem D3DDDI_CREATEHWQUEUEFLAGS zum Erstellen von HWQueues hinzugefügt wurde, die das Übermittlungsmodell für den Benutzermodus verwenden. Die mit diesem Flag erstellten HWQueues können den regulären Kernelmodus-Übermittlungspfad nicht verwenden und müssen sich auf den Türklingelmechanismus für die Arbeitsübermittlung in der Warteschlange verlassen.

Arbeitsübermittlungs-APIs im Benutzermodus

Die folgenden Benutzermodus-APIs werden hinzugefügt, um die Übermittlung im Benutzermodus zu unterstützen.

  • D3DKMTCreateDoorbell erstellt eine Türklingel für eine D3D HWQueue für die Übermittlung im Benutzermodus.

  • DKMTConnectDoorbell verbindet eine zuvor erstellte Türklingel mit einer D3D HWQueue für die Arbeitsübermittlung im Benutzermodus.

  • D3DKMTDestroyDoorbell zerstört eine zuvor erstellte Türklingel.

  • D3DKMTNotifyWorkSubmission benachrichtigt die KMD, dass neue Arbeiten an eine HWQueue übermittelt wurden. Der Sinn dieser Funktion ist eine niedrige Latenzzeit bei der Arbeitsübermittlung, wobei KMD nicht involviert ist und nicht weiß, wann die Arbeit eingereicht wird. Diese API ist in Szenarien hilfreich, in denen die KMD benachrichtigt werden muss, wenn Arbeit an eine HWQueue übermittelt wird. Treiber sollten diesen Mechanismus nur in bestimmten und seltenen Szenarien verwenden, da er bei jeder Übermittlung von Arbeit einen Roundtrip von UMD zu KMD erfordert und damit den Zweck eines Übermittlungsmodells mit geringer Latenzzeit im Benutzermodus zunichte macht.

Residenzmodell von Türklingelspeicher- und Ringpufferzuweisungen

  • UMD ist dafür verantwortlich, dass die Zuweisungen für den Ringpuffer und die Ringpuffersteuerung resident werden, bevor eine Türklingel erstellt wird.
  • UMD verwaltet die Lebensdauer des Ringpuffers und der Ringpuffersteuerungszuordnungen. Dxgkrnl zerstört diese Zuordnungen nicht implizit, auch wenn die entsprechende Türklingel zerstört wird. UMD ist für die Zuordnung und Zerstörung dieser Zuordnungen verantwortlich. Um jedoch zu verhindern, dass ein böswilliges Programm im Benutzermodus diese Zuweisungen zerstört, während die Türklingel noch aktiv ist, nimmt nimmt Dxgkrnl während der Lebensdauer der Türklingel eine Referenz auf diese Zuweisungen auf.
  • Das einzige Szenario, in dem Dxgkrnl Eingpufferzuordnungen zerstört, ist während der Gerätebeendigung. 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 bedeutet, dass die Ringpufferresidenz nicht an die Türglocke gebunden ist.
  • Wenn KMD den DXG-Rückruf vorgibt, um eine Türglocke zu trennen (d. h. DxgkCbDisconnectDoorbell mit Status D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), rotiert Dxgkrnl den Türglocken-CPUVA auf eine Dummy-Seite. Die Ringpufferzuweisungen werden nicht entfernt oder aufgehoben.
  • Im Falle von Geräteverlustszenarien (TDR/GPU Stop/Page usw.) trennt Dxgkrnl die Türglocke und kennzeichnet den Status als D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. Der Benutzermodus ist dafür verantwortlich, die HWQueue-, Türbell-, Ringpuffer zu zerstören und sie neu zu erstellen. Diese Anforderung ähnelt der Zerstörung und Neuerstellung anderer Geräteressourcen in diesem Szenario.

Aussetzen des Hardwarekontexts

Wenn das Betriebssystem einen Hardwarekontext aussetzt, hält Dxgkrnl die Türklingelverbindung aktiv und die Zuweisung des Ringpuffers (Arbeitswarteschlange) resident. Auf diese Weise kann UMD weiterhin Arbeit für den Kontext in die Warteschlange stellen; diese Arbeit wird nur nicht eingeplant, während der Kontext ausgesetzt ist. Sobald der Kontext fortgesetzt und geplant wurde, beobachtet der Kontextverwaltungsprozessor (CMP) der GPU den neuen Schreibzeiger und die Arbeitsübermittlung.

Diese Logik ähnelt der aktuellen Kernelmodus-Übermittlungslogik, bei der UMD D3DKMTSubmitCommand mit einem ausgesetzten Kontext aufrufen kann. Dxgkrnl stellt diesen neuen Befehl in die HwQueue, aber er wird erst später geplant.

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

  • Aussetzen eines Hardwarekontexts:

    1. Dxgkrnl ruft DxgkddiSuspendContext auf.
    2. KMD entfernt alle HWQueues des Kontexts aus der Liste des HW-Schedulers.
    3. Türglocken sind noch verbunden, und Ringpuffer-/Ringpuffersteuerungszuweisungen sind noch vorhanden. UMD kann neue Befehle in die HWQueue dieses Kontexts schreiben, die GPU verarbeitet sie jedoch nicht, was der heutigen Kernelmodusbefehlsübermittlung in einen ausgesetzten Kontext ähnelt.
    4. Wenn KMD die Türglocke einer ausgesetzten HWQueue opfert, verliert UMD ihre Verbindung. UMD kann versuchen, die Türglocke wieder herzustellen, und KMD wird dieser Warteschlange eine neue Türglocke zuweisen. Die Absicht ist, die UMD nicht abzuwürgen, sondern ihr die Möglichkeit zu geben, die Arbeit fortzusetzen, die das HW-Modul schließlich verarbeiten kann, sobald der Kontext fortgesetzt wird.
  • Fortsetzen eines Hardwarekontexts:

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

Modul-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-Unterbrechungen verantwortlich. Aus diesem Grund verfügt Dxgkrnl über eine vollständige Ansicht, wann ein Modul aktiv und im Leerlauf ist.

Bei der Übermittlung im Benutzermodus überwacht Dxgkrnl anhand der TDR-Zeitüberschreitungskadenz, ob ein GPU-Modul Fortschritte macht. Wenn es sich also lohnt, einen Übergang in den F1-Zustand früher als in der TDR-Zeitüberschreitungszeit von zwei Sekunden einzuleiten, kann die KMD das Betriebssystem dazu auffordern.

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

  • Der DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE-Interrupt-Typ wird dem DXGK_INTERRUPT_TYPE hinzugefügt. KMD verwendet diesen Interrupt, um Dxgkrnl über Modulzustandsübergänge zu benachrichtigen, die eine GPU-Stromversorgungsaktion oder Timeout-Wiederherstellung erfordern, wie Active -> TransitionToF1 und Active -> Hung.

  • Die EngineStateChange-Interrupt-Datenstruktur wird DXGKARGCB_NOTIFY_INTERRUPT_DATA hinzugefügt.

  • Die DXGK_ENGINE_STATEEnumeration wird hinzugefügt, um die Modulstatusübergänge für EngineStateChange darzustellen.

Wenn KMD einen DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE-Interrupt auslöst, wobei EngineStateChange.NewState auf DXGK_ENGINE_STATE_TRANSITION_TO_F1 festgelegt ist, trennt Dxgkrnl alle Türglocken von HWQueues auf diesem Modul 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.

Modul-D-Zustandsübergänge

Während eines Übergangs des D0-zu-D3-Gerätezustands setzt Dxgkrnl die HWQueue aus, trennt die Türglocke (rotiert den Türglocken-CPUVA auf eine Dummyseite), und aktualisiert den Türglockenstatus DoorbellStatusCpuVirtualAddress auf D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Wenn die UMD D3DKMTConnectDoorbell aufruft, wenn sich die GPU in D3 befindet, erzwingt sie, dass Dxgkrnl die GPU auf D0 zu wecken. Dxgkrnl ist auch für die Fortsetzung der HWQueue und das Rotieren des Türglocken-CPUVA an einen physischen Türglockenstandort verantwortlich.

Die folgende Abfolge von Ereignissen findet statt.

  • Die GPU wird von D0 bis D3 heruntergefahren:

    1. Dxgkrnl ruft DxgkddiSuspendContext für alle HW-Kontexte auf der GPU auf. KMD entfernt diese Kontexte aus der HW-Schedulerliste.
    2. Dxgkrnl trennt alle Türglocken.
    3. Dxgkrnl entfernt ggf. alle Ringpuffer-/Ringpuffersteuerungszuweisungen von VRAM. Dies geschieht, sobald alle Kontexte ausgesetzt und aus der Liste des Hardwareschedulers entfernt werden, sodass die Hardware keinem entfernten Speicher referenziert.
  • UMD schreibt einen neuen Befehl in eine HWQueue, wenn sich die GPU im D3-Zustand befindet:

    1. UMD stellt fest, dass die Türglocke getrennt, also ruft sie DKMTConnectDoorbell auf.
    2. Dxgkrnl initiiert einen D0-Übergang.
    3. Dxgkrnl macht alle Ringpuffer-/Ringpuffersteuerungszuweisungen resident, falls sie entfernt wurden.
    4. Dxgkrnl ruft die DxgkddiCreateDoorbell-Funktion von KMD auf, um anzufordern, dass KMD eine Türglockenverbindung für diese HWQueue herstellt.
    5. Dxgkrnl ruft DxgkddiResumeContext für alle HWContexts auf. KMD fügt die entsprechenden Warteschlangen der Liste des HW-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 im Benutzermodus zu implementieren.

  • DxgkDdiCreateDoorbell. Wenn die UMD D3DKMTCreateDoorbell aufruft, um eine Türglocke für eine HWQueue zu erstellen, macht Dxgkrnl einen entsprechenden Aufruf an diese Funktion, sodass KMD seine Türglockenstrukturen initialisieren kann.

  • DxgkDdiConnectDoorbell. Wenn die UMD D3DKMTConnectDoorbell aufruft, macht Dxgkrnl einen entsprechenden Aufruf an diese Funktion, sodass KMD einen CPUVA bereitstellen kann, die dem physischen Standort der Türklingel zugeordnet ist, und auch die erforderlichen Verbindungen zwischen dem HWQueue-Objekt, dem Türklingel-Objekt, der physischen Adresse der Türklingel, dem GPU-Scheduler usw. herstellen kann.

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

  • DxgkDdiDestroyDoorbell. Wenn die UMD D3DKMTDestroyDoorbell aufruft, macht Dxgkrnl einen entsprechenden Aufruf an diese Funktion, sodass die KMD ihre Türklingelstrukturen zerstören kann.

  • DxgkDdiNotifyWorkSubmission. Wenn die UMD D3DKMTNotifyWorkSubmission aufruft, macht Dxgkrnl einen entsprechenden Aufruf an diese Funktion, damit KMD über neue Arbeitsübermittlungen informiert werden kann.

Dxgkrnl-implementierte DDI

Der DxgkCbDisconnectDoorbellRückruf wird von Dxgkrnl implementiert. KMD kann diese Funktion aufrufen, um Dxgkrnl darüber zu benachrichtigen, dass KMD eine bestimmte Türglocke trennen muss.

Änderungen am HW-Warteschlangenstatus-Zaun

Hardwarewarteschlangen, die im UM-Arbeitsübermittlungsmodell ausgeführt werden, haben weiterhin ein Konzept eines monoton zunehmenden Statuszaunwerts, den die UMD generiert und schreibt, wenn ein Befehlspuffer abgeschlossen ist. Damit Dxgkrnl weiß, ob eine bestimmte Hardwarewarteschlange über ausstehende Arbeit verfügt, 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 Prozesszuordnung mit Lese-/Schreibzugriff auf den Benutzermodus des aktuellen Warteschlangenwerts.

Es ist wichtig, dass die UMD sicherstellt, dass der Wert in der Warteschlange aktualisiert wird, unmittelbar bevor die neue Übermittlung für den Grafikprozessor sichtbar wird. Die folgenden Schritte sind die empfohlene Reihenfolge der Arbeitsschritte. Es wird davon ausgegangen, dass die HW-Warteschlange im Leerlauf ist und der letzte fertige Puffer einen Statuszaunwert von N hatte.

  • Generieren Sie einen neuen Statuszaunwert N+1.
  • Füllen Sie den Befehlspuffer aus. Die letzte Anweisung des Befehlspuffers ist ein Statuszaunwert, der in N+1 geschrieben wird.
  • 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.
  • Betätigen Sie die Türklingel.

Normale und nicht normale 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, um die Türglocke zu trennen.
  2. Dxgkrnl wartet auf die letzte in die Warteschlange gestellte HwQueueProgressFenceLastQueuedValueCPUVirtualAddress für die GPU. Ringpuffer-/Ringpuffersteuerungszuweisungen bleiben resident.
  3. Die Wartezeit von Dxgkrnl ist zufriedenstellend, und die Ringpuffer-/Ringpuffersteuerungszuweisungen sowie die Türklingel und die HWQueue-Objekte können zerstört werden.

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

  1. Dxgkrnl kennzeichnet das Gerät im Fehler.

  2. Für jeden Gerätekontext ruft DxgkrnlDxgkddiSuspendContext auf, um den Kontext auszusetzen. Die Ringpuffer-/Ringpuffersteuerungszuweisungen sind weiterhin resident. KMD entfernt den Kontext vorzeitig und entfernt ihn aus seiner HW-Ausführungsliste.

  3. Für jede HWQueue des Kontexts führt Dxglrnl Folgendes aus:

    a. Ruft DxgkDdiDisconnectDoorbell auf, um die Türglocke zu trennen.

    b. Zerstört die Ringpuffer-/Ringpuffersteuerungszuweisungen sowie die Türglocke und HWQueue-Objekte.

Pseudocodebeispiele

Pseudocode für die Arbeitsübermittlung 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 Türglocken-APIs verwenden soll. Betrachten Sie hHWqueue1 als das Handle zu einer mit dem UserModeSubmission Flag erstellten HWQueue mit der vorhandenen D3DKMTCreateHwQueue-API.

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

Opfern von Türglocken-Pseudocode in KMD

Das folgende Beispiel zeigt, wie KMD die verfügbaren Türglocken „virtualisieren“ und zwischen den HWQueues auf GPUs, die dedizierte Türglocken verwenden, aufteilen kann.

Pseudocode der VictimizeDoorbell() KMD-Funktion:

  • KMD entscheidet, dass die logische Türglocke hDoorbell1, die mit PhysicalDoorbell1 verbunden ist, geopfert und getrennt werden muss.
  • KMD ruft Dxgkrnl's DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue) auf.
    • Dxgkrnl rotiert die UMD-sichtbare CPUVA dieser Türglocke auf eine Dummyseite und aktualisiert den Statuswert auf D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • KMD erhält die Kontrolle zurück und führt die tatsächliche Opferung/Trennung durch.
    • KMD opfert hDoorbell1 und trennt sie von PhysicalDoorbell1.
    • PhysicalDoorbell1 ist für die Verwendung verfügbar.

Sehen Sie sich nun das folgende Szenario an:

  1. Es gibt eine einzelne physische Türglocke in der PCI-BAR mit einer Kernelmodus-CPUVA gleich 0xfeedfeee. Diesem physischen Türglockenwert wird ein für eine HWQueue erstelltes Türglockenobjekt 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 für eine andere HWQueue2 auf:

    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 für hDoorbell2 auf:

    // 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ürde in diesem Beispiel sowohl hDoorbell1 als auch hDoorbell2 dieselbe physische Türglocke 0xfeedfeee zugewiesen.