Condividi tramite


Invio di lavoro in modalità utente

Importante

Alcune informazioni riguardano un prodotto in versione preliminare che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Questo articolo descrive la funzionalità di invio di lavoro in modalità utente ancora in fase di sviluppo a partire da Windows 11, versione 24H2 (WDDM 3.2). L'invio di lavoro di messaggistica unificata consente alle applicazioni di inviare il lavoro alla GPU direttamente dalla modalità utente con una latenza molto bassa. L'obiettivo è migliorare le prestazioni delle applicazioni che inviano spesso carichi di lavoro di piccole dimensioni alla GPU. Inoltre, l'invio in modalità utente dovrebbe trarre vantaggio significativamente da tali applicazioni se sono in esecuzione all'interno di un contenitore o di una macchina virtuale . Questo vantaggio è dovuto al fatto che il driver in modalità utente (UMD) in esecuzione nella macchina virtuale può inviare direttamente il lavoro alla GPU senza dover inviare un messaggio all'host.

I driver IHV e l'hardware che supportano l'invio di lavoro di messaggistica unificata devono continuare a supportare contemporaneamente il modello tradizionale di invio di lavoro in modalità kernel. Questo supporto è necessario per scenari come un guest meno recente che supporta solo le code tradizionali del servizio di gestione delle chiavi in esecuzione in un host più recente.

Questo articolo non illustra l'interoperabilità dell'invio di messaggistica unificata con Flip/FlipEx. L'invio di messaggistica unificata descritto in questo articolo è limitato al rendering solo/classe di calcolo di scenari. La pipeline di presentazione continua a essere basata sull'invio in modalità kernel per il momento perché ha una dipendenza dai recinti monitorati nativi. La progettazione e l'implementazione della presentazione basata sull'invio di messaggistica unificata possono essere considerate una volta implementati completamente i recinti monitorati nativi e l'invio di messaggistica unificata solo per il calcolo/rendering. Di conseguenza, i driver devono supportare l'invio in modalità utente per ogni coda.

Campanelli

La maggior parte delle generazioni correnti o future di GPU che supportano la pianificazione hardware supportano anche il concetto di campanello GPU. Un campanello è un meccanismo per indicare a un motore GPU che il nuovo lavoro viene accodato nella coda di lavoro. I campanelli vengono in genere registrati nella BARRA PCIe (barra degli indirizzi di base) o nella memoria di sistema. Ogni GPU IHV ha una propria architettura che determina il numero di campanelli, dove si trovano nel sistema e così via. Il sistema operativo Windows usa i campanelli come parte della progettazione per implementare l'invio di lavoro di messaggistica unificata.

A livello generale sono disponibili due diversi modelli di campanelli implementati da diversi IHV e GPU:

  • Campanelli globali

    Nel modello Global Doorbells tutte le code hardware in contesti e processi condividono un singolo campanello globale. Il valore scritto nel campanello informa l'utilità di pianificazione GPU su quale particolare coda hardware e motore ha un nuovo lavoro. L'hardware GPU usa una forma di meccanismo di polling per recuperare il lavoro se più code hardware inviano attivamente lavoro e suonano lo stesso campanello globale.

  • Campanelli dedicati

    Nel modello di campanello dedicato, a ogni coda hardware viene assegnato il proprio campanello che viene generato ogni volta che c'è un nuovo lavoro da inviare alla GPU. Quando viene generato un campanello, l'utilità di pianificazione GPU conosce esattamente quale coda hardware ha inviato nuovo lavoro. Sono presenti campanelli limitati condivisi tra tutte le code hardware create nella GPU. Se il numero di code hardware create supera il numero di campanelli disponibili, il driver deve disconnettere il campanello di una coda hardware precedente o meno usata di recente e assegnarne il campanello a una coda appena creata, in modo efficace "virtualizzando" i campanelli.

Individuazione del supporto per l'invio di lavoro in modalità utente

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

Per i nodi GPU che supportano la funzionalità di invio di lavoro di messaggistica unificata, DxgkDdiGetNodeMetadata imposta il flag di metadati del nodo UserModeSubmissionSupported aggiunto a DXGK_NODEMETADATA_FLAGS. Il sistema operativo consente quindi a UMD di creare HWQueues e campanelli di invio in modalità utente solo nei nodi per cui è impostato questo flag.

DXGK_QUERYADAPTERINFOTYPE::D XGKQAITYPE_U edizione Standard RMODESUBMISSION_C piattaforma di strumenti analitici

Per eseguire query sulle informazioni specifiche di doorbell, il sistema operativo chiama la funzione DxgkDdiQueryAdapterInfo di KMD con il tipo di informazioni dell'adattatore di query DXGKQAITYPE_U edizione Standard RMODESUBMISSION_C piattaforma di strumenti analitici. KmD risponde popolando una struttura DXGK_U edizione Standard RMODESUBMISSION_C piattaforma di strumenti analitici con i relativi dettagli di supporto per l'invio di lavoro in modalità utente.

Attualmente, l'unico limite necessario è la dimensione della memoria del campanello (in byte). Dxgkrnl ha bisogno delle dimensioni della memoria del campanello per un paio di motivi:

  • Durante la creazione del campanello (D3DKMTCreateDoorbell), Dxgkrnl restituisce un doorbellCpuVirtualAddress a UMD. Prima di farlo, Dxgkrnl deve prima eseguire il mapping interno a una pagina fittizia perché il campanello non è ancora assegnato e connesso. Le dimensioni del campanello sono necessarie per allocare la pagina fittizia.
  • Durante la connessione del campanello (D3DKMT Connessione Doorbell), Dxgkrnl deve ruotare il doorbellCpuVirtualAddress a un doorbellPhysicalAddress fornito dal KMD. Anche in questo caso, Dxgkrnl deve conoscere le dimensioni del campanello.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission in D3DKMTCreateHwQueue

UMD imposta il flag UserModeSubmission aggiunto a D3DDDI_CREATEHWQUEUEFLAGS per la creazione di oggetti HWQueue che usano il modello di invio in modalità utente. Gli HWQueue creati con questo flag non possono usare il normale percorso di invio di lavoro in modalità kernel e devono basarsi sul meccanismo del campanello per l'invio di lavoro nella coda.

API per l'invio di lavoro in modalità utente

Le API in modalità utente seguenti vengono aggiunte per supportare l'invio di lavoro in modalità utente.

  • D3DKMTCreateDoorbell crea un campanello per un D3D HWQueue per l'invio di lavoro in modalità utente.

  • D3DKMT Connessione Doorbell connette un campanello creato in precedenza a un HWQueue D3D per l'invio di lavoro in modalità utente.

  • D3DKMTDestroyDoorbell distrugge un campanello creato in precedenza.

  • D3DKMTNotifyWorkSubmission notifica al KMD che il nuovo lavoro è stato inviato in un HWQueue. Il punto di questa funzionalità è un percorso di invio di lavoro a bassa latenza, in cui kmD non è coinvolto o consapevole quando viene inviato il lavoro. Questa API è utile negli scenari in cui il kmD deve ricevere una notifica ogni volta che il lavoro viene inviato in un HWQueue. I driver devono usare questo meccanismo in scenari specifici e poco frequenti perché implica un round trip da UMD a KMD in ogni invio di lavoro, sconfiggendo così lo scopo di un modello di invio in modalità utente a bassa latenza.

Modello di residenza delle allocazioni di memoria e buffer circolare

  • UMD è responsabile di rendere residenti le allocazioni del buffer circolare e del buffer circolare prima di creare un campanello.
  • UMD gestisce la durata delle allocazioni del buffer circolare e del controllo del buffer circolare. Dxgkrnl non distruggerà queste allocazioni in modo implicito anche se il campanello corrispondente viene distrutto. UMD è responsabile dell'allocazione e dell'eliminazione di queste allocazioni. Tuttavia, per impedire a un programma dannoso in modalità utente di distruggere queste allocazioni mentre il campanello è vivo, Dxgkrnl prende un riferimento su di loro durante la durata del campanello.
  • L'unico scenario in cui Dxgkrnl distrugge le allocazioni del buffer circolare è durante la terminazione del dispositivo. Dxgkrnl distrugge tutti gli HWQueue, i campanelli e le allocazioni del buffer circolare associati al dispositivo.
  • Finché le allocazioni del buffer circolare sono attive, la CPUVA del buffer circolare è sempre valida e disponibile per l'accesso alla messaggistica unificata, indipendentemente dallo stato delle connessioni del campanello. Ovvero, la residenza del buffer circolare non è legata al campanello.
  • Quando KMD effettua il callback DXG per disconnettere un campanello (ovvero, chiama DxgkCbDisconnectDoorbell con stato D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl ruota il campanello CPUVA su una pagina fittizia. Non rimuove o rimuove il mapping delle allocazioni del buffer circolare.
  • In caso di eventuali scenari di perdita del dispositivo (TDR/GPU Stop/Page e così via), Dxgkrnl disconnette il campanello e contrassegna lo stato come D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. La modalità utente è responsabile dell'eliminazione del controllo HWQueue, del campanello, del buffer circolare e della ricreazione. Questo requisito è simile al modo in cui altre risorse del dispositivo vengono eliminate e ricreate in questo scenario.

Sospensione del contesto hardware

Quando il sistema operativo sospende un contesto hardware, Dxgkrnl mantiene attiva la connessione porta e il buffer circolare (coda di lavoro) residente. In questo modo, UMD può continuare a eseguire l'accodamento nel contesto; questo lavoro non viene pianificato mentre il contesto è sospeso. Una volta ripreso e pianificato il contesto, il processore di gestione del contesto della GPU osserva il nuovo puntatore di scrittura e l'invio di lavoro.

Questa logica è simile alla logica di invio in modalità kernel corrente, in cui UMD può chiamare D3DKMTSubmitCommand con un contesto sospeso. Dxgkrnl accoda questo nuovo comando a HwQueue, ma non viene pianificato fino a un secondo momento.

La sequenza di eventi seguente si verifica durante la sospensione e la ripresa del contesto hardware.

  • Sospensione di un contesto hardware:

    1. Dxgkrnl chiama DxgkddiSuspendContext.
    2. KmD rimuove tutti gli HWQueue del contesto dall'elenco dell'utilità di pianificazione HW.
    3. I campanelli sono ancora connessi e le allocazioni di controllo buffer circolare/buffer circolare sono ancora residenti. La UMD può scrivere nuovi comandi nella coda HWQueue di questo contesto, ma la GPU non li elabora, che è simile all'invio di comandi in modalità kernel di oggi a un contesto sospeso.
    4. Se kmd sceglie di vittima il campanello di un HWQueue sospeso, UMD perde la connessione. UMD può tentare di riconnettere il campanello e kmD assegnerà un nuovo campanello a questa coda. L'intenzione è di non fermare il UMD, ma piuttosto di consentire di continuare a inviare il lavoro che il motore HW può eventualmente elaborare una volta ripreso il contesto.
  • Ripresa di un contesto hardware:

    1. Dxgkrnl chiama DxgkddiResumeContext.
    2. KmD aggiunge tutte le chiavi HWQueue del contesto all'elenco dell'utilità di pianificazione HW.

Transizioni di stato F motore

Nell'invio tradizionale di lavoro in modalità kernel, Dxgkrnl è responsabile dell'invio di nuovi comandi agli interrupt di completamento HWQueue e monitoraggio dal KMD. Per questo motivo, Dxgkrnl ha una visione completa di quando un motore è attivo e inattiva.

Nell'invio di lavoro in modalità utente, Dxgkrnl monitora se un motore GPU sta effettuando progressi usando la cadenza di timeout TDR, quindi se è opportuno avviare una transizione allo stato F1 prima del timeout TDR di due secondi, il KMD può richiedere il sistema operativo a tale scopo.

Per facilitare questo approccio sono state apportate le modifiche seguenti:

  • Il tipo di interrupt DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE viene aggiunto a DXGK_INTERRUPT_TYPE. KmD usa questo interrupt per notificare a Dxgkrnl transizioni di stato del motore che richiedono un'azione di alimentazione GPU o un ripristino di timeout, ad esempio Attivo -> TransitionToF1 e Attivo -> Bloccato.

  • La struttura dei dati interrupt EngineStateChange viene aggiunta a DXGKARGCB_NOTIFY_INTERRUPT_DATA.

  • L'enumerazione DXGK_ENGINE_STATE viene aggiunta per rappresentare le transizioni di stato del motore per EngineStateChange.

Quando kmd genera un interrupt DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE con EngineStateChange.NewState impostato su DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl disconnette tutti i campanelli di HWQueues su questo motore e quindi avvia una transizione da F0 a F1 del componente di alimentazione.

Quando il UMD tenta di inviare nuovo lavoro al motore GPU nello stato F1, deve riconnettere il campanello, che a sua volta fa sì che Dxgkrnl avvii una transizione allo stato di alimentazione F0.

Transizioni di stato D motore

Durante una transizione da D0 a D3 dello stato di alimentazione del dispositivo, Dxgkrnl sospende HWQueue, disconnette il campanello (ruotando la PORTA CPUVA a una pagina fittizia) e aggiorna lo stato del campanello DoorbellStatusCpuVirtualAddress a D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Se UMD chiama D3DKMT Connessione Doorbell quando la GPU è in D3, forza Dxgkrnl a riattivare la GPU in D0. Dxgkrnl è anche responsabile della ripresa della HWQueue e della rotazione della CPUVA del campanello a una posizione fisica del campanello.

Viene eseguita la sequenza di eventi seguente.

  • Si verifica un risparmio di energia da D0 a D3 GPU:

    1. Dxgkrnl chiama DxgkddiSuspendContext per tutti i contesti HW nella GPU. KmD rimuove questi contesti dall'elenco dell'utilità di pianificazione HW.
    2. Dxgkrnl disconnette tutti i campanelli.
    3. Dxgkrnl eventualmente rimuove tutte le allocazioni di buffer circolare/buffer circolare da VRAM, se necessario. In questo modo, dopo che tutti i contesti vengono sospesi e rimossi dall'elenco dell'utilità di pianificazione hardware, in modo che l'hardware non faccia riferimento ad alcuna memoria rimossa.
  • UMD scrive un nuovo comando in un oggetto HWQueue quando la GPU è in stato D3:

    1. UMD vede che il campanello è disconnesso, quindi chiama D3DKMT Connessione Doorbell.
    2. Dxgkrnl avvia una transizione D0.
    3. Dxgkrnl rende residenti tutte le allocazioni di buffer circolare/buffer circolare residenti se sono state rimosse.
    4. Dxgkrnl chiama la funzione DxgkddiCreateDoorbell del KMD per richiedere che kmd faccia una connessione porta per questo HWQueue.
    5. Dxgkrnl chiama DxgkddiResumeContext per tutti i HWContexts. KmD aggiunge le code corrispondenti all'elenco dell'utilità di pianificazione HW.

DDI per l'invio di lavoro in modalità utente

DD implementate dal servizio di gestione delle chiavi

Le DDI in modalità kernel seguenti vengono aggiunte per il KmD per implementare il supporto per l'invio di lavoro in modalità utente.

DDI implementato da Dxgkrnl

Il callback DxgkCbDisconnectDoorbell viene implementato da Dxgkrnl. KMD può chiamare questa funzione per notificare a Dxgkrnl che kmd deve disconnettere un particolare campanello.

Modifiche all'isolamento dello stato della coda HW

Le code hardware in esecuzione nel modello di invio di lavoro di messaggistica unificata hanno ancora un concetto di limite di avanzamento che aumenta in modo monotonico che UMD genera e scrive quando viene completato un buffer dei comandi. Affinché Dxgkrnl sappia se una particolare coda hardware ha un lavoro in sospeso, la UMD deve aggiornare il valore limite di stato in coda subito prima di aggiungere un nuovo buffer dei comandi al buffer circolare e renderlo visibile alla GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress è un mapping di processo in modalità utente di lettura/scrittura del valore in coda più recente.

È essenziale per la UMD assicurarsi che il valore in coda venga aggiornato subito prima che il nuovo invio venga reso visibile alla GPU. I passaggi seguenti sono la sequenza consigliata di operazioni. Presuppongono che la coda HW sia inattiva e l'ultimo buffer finito abbia un valore di limite di stato pari a N.

  • Generare un nuovo valore di recinto di stato N+1.
  • Compilare il buffer dei comandi. L'ultima istruzione del buffer dei comandi è un valore di isolamento dello stato scritto in N+1.
  • Informare il sistema operativo del valore appena accodato impostando *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) uguale a N+1.
  • Rendere visibile il buffer dei comandi alla GPU aggiungendolo al buffer circolare.
  • Suona il campanello.

Terminazione normale e anomala del processo

La sequenza di eventi seguente viene eseguita durante la terminazione normale del processo.

Per ogni HWQueue del dispositivo/contesto:

  1. Dxgkrnl chiama DxgkDdiDisconnectDoorbell per disconnettere il campanello.
  2. Dxgkrnl attende il completamento dell'ultimo HwQueueProgressFenceLastQueuedValueCPUVirtualAddress in coda sulla GPU. Le allocazioni di buffer circolare/buffer circolare rimangono residenti.
  3. L'attesa di Dxgkrnl è soddisfatta e ora può distruggere le allocazioni del buffer circolare o del buffer circolare, e il campanello e gli oggetti HWQueue.

La sequenza di eventi seguente viene eseguita durante la terminazione del processo anomala.

  1. Dxgkrnl contrassegna il dispositivo in errore.

  2. Per ogni contesto di dispositivo, Dxgkrnl chiama DxgkddiSuspendContext per sospendere il contesto. Le allocazioni di buffer circolare/buffer circolare sono ancora residenti. KmD annulla il contesto e lo rimuove dall'elenco di esecuzioni HW.

  3. Per ogni HWQueue di contesto, Dxglrnl:

    a. Chiama DxgkDdiDisconnectDoorbell per disconnettere il campanello.

    b. Elimina definitivamente le allocazioni di buffer circolare/buffer circolare e il campanello e gli oggetti HWQueue.

Esempi di pseudocodice

Pseudocodice invio di lavoro in UMD

Lo pseudocodice seguente è un esempio di base del modello che UMD deve usare per creare e inviare lavoro a HWQueues usando le API doorbell. Si consideri hHWqueue1 l'handle di un oggetto HWQueue creato con il UserModeSubmission flag usando l'API D3DKMTCreateHwQueue esistente.

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

Pseudocodice porta vittima in KMD

L'esempio seguente illustra in che modo il KMD potrebbe dover "virtualizzare" e condividere i campanelli disponibili tra le gpu HWQueue che usano campanelli dedicati.

Pseudocodice della funzione kmD VictimizeDoorbell() :

  • KMD decide che il campanello hDoorbell1 logico connesso a PhysicalDoorbell1 deve essere vittima e disconnesso.
  • KMD chiama dxgkrnl's DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue).
    • Dxgkrnl ruota la CPUVA visibile UMD di questo campanello a una pagina fittizia e aggiorna il valore di stato a D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • KmD recupera il controllo e esegue la vittima/disconnessione effettiva.
    • KmD vittima hDoorbell1 e lo disconnette da PhysicalDoorbell1.
    • PhysicalDoorbell1 è disponibile per l'uso

Si consideri ora lo scenario seguente:

  1. C'è un singolo campanello fisico in PCI BAR con una CPUVA in modalità kernel uguale a 0xfeedfeee. A un oggetto doorbell creato per un HWQueue viene assegnato questo valore di campanello fisico.

    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. Il sistema operativo chiama DxgkDdiCreateDoorbell un diverso 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. Il sistema operativo chiama DxgkDdiConnectDoorbell su 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
    ``
    
    

Questo meccanismo non è necessario se una GPU usa i campanelli globali. In questo esempio, invece, verrebbe hDoorbell1hDoorbell2 assegnato lo stesso 0xfeedfeee campanello fisico.