Compartir a través de


Envío de tareas en modo de usuario

Importante

Parte de la información hace referencia a un producto de versión preliminar que puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

En este artículo se describe la característica de envío de tareas en modo de usuario (UM) que todavía está en desarrollo a partir de la versión 24H2 de Windows 11 (WDDM 3.2). El envío de tareas en UM permite a las aplicaciones enviar tareas a la GPU directamente desde el modo de usuario con una latencia muy baja. El objetivo es mejorar el rendimiento de las aplicaciones que envían cargas de trabajo pequeñas con frecuencia a la GPU. Además, se espera que el envío en modo de usuario beneficie significativamente a estas aplicaciones si se ejecutan dentro de un contenedor o una máquina virtual (VM). Esta ventaja se debe a que el controlador en modo de usuario (UMD) que se ejecuta en la máquina virtual puede enviar directamente la tarea a la GPU sin tener que enviar un mensaje al host.

Los controladores IHV y el hardware que admiten el envío de tareas en UM deben seguir aceptando el modelo tradicional de envío de tareas en modo kernel de forma simultánea. Esta compatibilidad es necesaria para casos como un antiguo invitado que solo admite colas de KM tradicionales que se ejecuten en un host más reciente.

En este artículo no se habla de la interoperabilidad de envío en UM con Flip/FlipEx. El envío en UM descrito en este artículo se limita a clase de escenarios de solo representación o de procesos. El procedimiento de presentación sigue basándose en el envío en modo kernel por ahora, ya que tiene una dependencia en las barreras supervisadas nativas. El diseño y la implementación de la presentación basada en envíos en UM se pueden tener en cuenta una vez que se implementan del todo las barreras supervisadas nativas y el envío en UM para procesos o solo representación. Por tanto, los controladores deben admitir el envío en modo de usuario por cola.

Timbres

La mayoría de las generaciones actuales o futuras de GPU que admiten la programación de hardware también admiten el concepto de timbre de GPU. Un timbre es un mecanismo para indicar a un motor de GPU que la nueva tarea se pone en cola en la cola de trabajo. Normalmente, los timbres se registran en la PCIe BAR (barra de direcciones base) o en la memoria del sistema. Cada IHV de la GPU tiene su propia arquitectura que determina el número de timbres, en qué lugar se encuentran en el sistema, etc. El sistema operativo Windows usa timbres como parte de su diseño para implementar el envío de tareas en UM.

En un nivel alto, hay dos modelos diferentes de timbres implementados por diferentes IHV y GPU:

  • Timbres globales

    En el modelo de timbres globales, todas las colas de hardware entre contextos y procesos comparten un solo timbre global. El valor escrito en el timbre informa al programador de GPU la cola de hardware y el motor concretos que tienen una nueva tarea. El hardware de la GPU usa una forma de mecanismo de búsqueda para capturar la tarea si hay varias colas de hardware que envían la tarea activamente y hacen sonar el mismo timbre global.

  • Timbres dedicados

    En el modelo de timbre dedicado, a cada cola de hardware se le asigna su propio timbre que suena cada vez que hay una nueva tarea que se va a enviar a la GPU. Cuando suena un timbre, el programador de GPU sabe exactamente qué cola de hardware ha enviado la nueva tarea. Hay timbres limitados que comparten todas las colas de hardware creadas en la GPU. Si el número de colas de hardware creadas supera el número de timbres disponibles, el controlador debe desconectar el timbre de la cola de hardware más antigua o menos usada recientemente y asignar el timbre a una cola recién creada, que "virtualice" de forma efectiva los timbres.

Función de detección de envío tareas en modo de usuario

DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported

En los nodos de la GPU que admiten la característica de envío de tareas en UM, el DxgkDdiGetNodeMetadata crea el indicador de metadatos del nodo UserModeSubmissionSupported que se agrega a DXGK_NODEMETADATA_FLAGS. Hecho esto, el sistema operativo permite que el UMD cree HWQueues y timbres de envío en modo de usuario solo en los nodos en los que se cree este indicador.

DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS

Para consultar información específica sobre los timbres, el sistema operativo llama a la función DxgkDdiQueryAdapterInfo del KMD con el tipo de información del adaptador de consulta DXGKQAITYPE_USERMODESUBMISSION_CAPS. El KMD responde rellenando una estructura DXGK_USERMODESUBMISSION_CAPS con los detalles de la compatibilidad con el envío de tareas en modo de usuario.

Actualmente, el único límite necesario es el tamaño de memoria del timbre (en bytes). Dxgkrnl necesita el valor de tamaño de memoria del timbre por una serie de razones:

  • Durante la creación del timbre (D3DKMTCreateDoorbell), Dxgkrnl devuelve un DoorbellCpuVirtualAddress al UMD. Antes de hacerlo, Dxgkrnl primero debe asignarse internamente a una página ficticia porque el timbre aún no está asignado y conectado. El tamaño del timbre es necesario para asignar la página ficticia.
  • Mientras se conecta el timbre (D3DKMTConnectDoorbell), Dxgkrnl debe rotar la DoorbellCpuVirtualAddress en una DoorbellPhysicalAddress facilitada por el KMD. Una vez más, Dxgkrnl debe conocer el tamaño del timbre.

D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission en D3DKMTCreateHwQueue

El UMD crea el indicador UserModeSubmission añadido a D3DDDI_CREATEHWQUEUEFLAGS para crear HWQueues que usen el modelo de envío en modo de usuario. Las HWQueues creadas con este indicador no pueden usar la ruta de envío de tareas en modo kernel normal y deben confiar en el mecanismo de timbre para el envío de tareas en la cola.

API de envío de tareas en modo de usuario

Se han añadido las siguientes API en modo de usuario para admitir el envío de tareas en modo de usuario.

  • D3DKMTCreateDoorbell crea un timbre para una D3D HWQueue para el envío de tareas en modo usuario.

  • D3DKMTConnectDoorbell conecta un timbre creado anteriormente en una D3D HWQueue para el envío de tareas en modo de usuario.

  • D3DKMTDestroyDoorbell destruye un timbre creado anteriormente.

  • D3DKMTNotifyWorkSubmission notifica al KMD que se ha enviado una nueva tarea en una HWQueue. Lo que caracteriza a esta funcionalidad es la ruta de envío de tareas de baja latencia, donde el KMD no interviene cuando se envía la tarea o no la conoce. Esta API es útil en casos en los que el KMD debe recibir una notificación cada vez que se envía la tarea en una HWQueue. Los controladores deben usar este mecanismo en situaciones específicos y poco frecuentes, ya que implica un ciclo de ida y vuelta del UMD al KMD en cada envío de tarea, lo que anula la finalidad de un modelo de envío en modo de usuario de baja latencia.

Modelo de residencia de asignaciones de memoria de timbre y búfer en anillo

  • El UMD es responsable de hacer que el búfer en anillo y las asignaciones de control del búfer en anillo residan antes de crear un timbre.
  • El UMD gestiona la duración del búfer en anillo y las asignaciones de control del búfer en anillo. Dxgkrnl no destruirá estas asignaciones implícitamente aunque se destruya el timbre correspondiente. El UMD es responsable de asignar y destruir estas asignaciones. Sin embargo, para evitar que un programa malintencionado en modo de usuario destruya estas asignaciones mientras el timbre está activo, Dxgkrnl toma una referencia de ellos mientras esté activo el timbre.
  • El único escenario en el que Dxgkrnl destruye las asignaciones de búfer en anillo es durante la terminación del dispositivo. Dxgkrnl destruye todas las asignaciones de HWQueues, timbres y búferes en anillo asociados al dispositivo.
  • Siempre que las asignaciones de búfer en anillo estén activas, la VA de CPU de búfer en anillo siempre es válida y estará disponible para que el UMD acceda a ella, independientemente del estado de las conexiones del timbre. Es decir, la residencia del búfer en anillo no está vinculada al timbre.
  • Cuando el KMD realiza una devolución de llamada DXG para desconectar un timbre (es decir, llama a DxgkCbDisconnectDoorbell con el estado D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl rota la VA de CPU del timbre en una página ficticia. No desaloja ni desasigna las asignaciones de búfer en anillo.
  • En los casos en que se pierda el dispositivo (TDR/GPU Stop/Page, etc.), Dxgkrnl desconecta el timbre y marca el estado como D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. El modo de usuario es responsable de destruir la HWQueue, el timbre y el búfer en anillo y volver a crearlos. Este requisito es similar a la forma en que se destruyen y vuelven a crear otros recursos de dispositivo en esta situación.

Suspensión de contexto de hardware

Cuando el sistema operativo suspende un contexto de hardware, Dxgkrnl deja activa la conexión del timbre y mantiene la residencia del búfer en anillo (cola de trabajo). De esta manera, el UMD puede seguir trabajando en cola en el contexto; esta tarea no se programa mientras se suspende el contexto. Una vez reanudado y programado el contexto, el procesador de administración de contextos (CMP) de la GPU observa el nuevo puntero de escritura y el envío de tareas.

Esta lógica se parece a la lógica de envío en modo kernel actual, donde el UMD puede llamar a D3DKMTSubmitCommand con un contexto suspendido. Dxgkrnl pone en cola este nuevo comando en la HwQueue, pero no se programa hasta después.

La siguiente secuencia de eventos se produce durante la suspensión y reanudación del contexto de hardware.

  • Cómo se suspende un contexto de hardware:

    1. Dxgkrnl llama a DxgkddiSuspendContext.
    2. El KMD quita todas las HWQueues del contexto de la lista del programador de HW.
    3. Los timbres siguen conectados y las asignaciones de control de búfer en anillo/anillo siguen siendo residentes. El UMD puede escribir nuevos comandos en la HWQueue de este contexto, pero la GPU no las procesará, lo que es similar al envío de comandos en modo kernel actual en un contexto suspendido.
    4. Si el KMD decide victimizar el timbre de una HWQueue suspendida, el UMD perderá la conexión. El UMD puede intentar volver a conectar el timbre y el KMD asignará un nuevo timbre a esta cola. Lo que se pretende no es detener el UMD, sino permitir que siga enviando tareas que el motor de HW pueda procesar después, una vez que se reanude el contexto.
  • Cómo se reanuda un contexto de hardware:

    1. Dxgkrnl llama a DxgkddiResumeContext.
    2. El KMD agrega todas las HWQueues del contexto a la lista del programador de HW.

Transiciones de estado F del motor

En el envío de tareas en modo kernel tradicional, Dxgkrnl se encarga de enviar nuevos comandos a la HWQueue y supervisar las interrupciones de finalización del KMD. Por este motivo, Dxgkrnl tiene una visión total de cuándo un motor está activo e inactivo.

En el envío de tareas en modo de usuario, Dxgkrnl supervisa si el motor de GPU está progresando con la cadencia de tiempo de espera de TDR, por lo que si vale la pena iniciar una transición al estado F1 antes de que se agote el tiempo de espera de TDR de dos segundos, el KMD podrá solicitar al sistema operativo que lo haga.

Se han realizado los siguientes cambios para facilitar esta aplicación de uso:

  • El tipo de interrupción DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE se agrega a DXGK_INTERRUPT_TYPE. El KMD usa esta interrupción para informar a Dxgkrnl de las transiciones de estado del motor que necesitan una operación de consumo de energía de la GPU o un tiempo de recuperación de espera, como Active -> TransitionToF1 y Active -> Hung.

  • La estructura de datos de la interrupción EngineStateChange se agrega a DXGKARGCB_NOTIFY_INTERRUPT_DATA.

  • La enumeración DXGK_ENGINE_STATE se agrega para representar las transiciones de estado del motor en EngineStateChange.

Cuando el KMD genera una interrupción DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE con el valor EngineStateChange.NewState creado en DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl desconecta todos los timbres de HWQueues en este motor e inicia una transición de componente de uso de energía de F0 a F1.

Cuando el UMD intenta enviar una nueva tarea al motor de GPU en estado F1, debe volver a conectar el timbre, lo que a su vez hace que Dxgkrnl inicie una transición al estado de uso de energía F0.

Transiciones de estado D del motor

Durante una transición de estado de uso de energía del dispositivo de D0 a D3, Dxgkrnl suspende la HWQueue, desconecta el timbre (rotando la VA de CPU del timbre en una página ficticia) y cambia el estado de timbre DoorbellStatusCpuVirtualAddress a D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.

Si el UMD llama a D3DKMTConnectDoorbell cuando la GPU está en D3, se fuerza a que Dxgkrnl active el GPU y pase a D0. Dxgkrnl también es responsable de reanudar la HWQueue y rotar la VA de CPU del timbre en una ubicación física del timbre.

Se produce la siguiente secuencia de eventos.

  • Se produce un apagado de la GPU de D0 a D3:

    1. Dxgkrnl llama a DxgkddiSuspendContext en todos los contextos de HW en la GPU. El KMD quita estos contextos de la lista del programador de HW.
    2. Dxgkrnl desconecta todos los timbres.
    3. Dxgkrnl puede llegar a desalojar todas las asignaciones de búfer en anillo y de control de búfer en anillo a través de la VRAM si es necesario. Lo hace una vez que todos los contextos se suspenden y se quitan de la lista del programador de hardware para que el hardware no enlace con ninguna memoria desalojada.
  • El UMD escribe un nuevo comando en una HWQueue cuando la GPU está en estado D3:

    1. El UMD detecta que el timbre está desconectado, por lo que llama a D3DKMTConnectDoorbell.
    2. Dxgkrnl inicia una transición de D0.
    3. Dxgkrnl hace residir a todas las asignaciones de búfer en anillo o de control de búfer en anillo si se han desalojado.
    4. Dxgkrnl llama a la función DxgkddiCreateDoorbell del KMD para solicitar que este realice una conexión de timbre en esta HWQueue.
    5. Dxgkrnl llama a DxgkddiResumeContext en todos los contextos de hardware (HWContexts). El KMD agrega las colas correspondientes a la lista del programador de HW.

DDI para el envío de tareas en modo de usuario

DDI implementadas por KMD

Se han agregado las siguientes DDI en modo kernel para que el KMD implemente la compatibilidad con el envío de tareas en modo de usuario.

  • DxgkDdiCreateDoorbell. Cuando el UMD llama a D3DKMTCreateDoorbell para crear un timbre en una HWQueue, Dxgkrnl realiza una llamada correspondiente a esta función para que el KMD pueda inicializar sus estructuras de timbre.

  • DxgkDdiConnectDoorbell. Cuando el UMD llama a D3DKMTConnectDoorbell, Dxgkrnl realiza una llamada correspondiente a esta función para que el KMD pueda facilitar una VA de CPU asignada a la ubicación física del timbre y, además, realizar las conexiones necesarias entre el objeto de HWQueue, el objeto de timbre, la dirección física del timbre, el programador de GPU, etc.

  • DxgkDdiDisconnectDoorbell. Cuando el sistema operativo quiere desconectar un timbre en concreto, llama al KMD con esta DDI.

  • DxgkDdiDestroyDoorbell. Cuando el UMD llama a D3DKMTDestroyDoorbell, Dxgkrnl realiza una llamada correspondiente a esta función para que el KMD pueda destruir las estructuras de timbre.

  • DxgkDdiNotifyWorkSubmission. Cuando el UMD llama a D3DKMTNotifyWorkSubmission, Dxgkrnl realiza una llamada correspondiente a esta función para que el KMD pueda recibir notificaciones de nuevos envíos de tareas.

DDI implementada por Dxgkrnl

La devolución de llamada DxgkCbDisconnectDoorbell la implementa Dxgkrnl. El KMD puede llamar a esta función para informar a Dxgkrnl de que el KMD necesita desconectar un timbre en concreto.

Cambios en la barrera de progreso de la cola de HW

Las colas de hardware que se ejecutan en el modelo de envío de tareas en UM siguen teniendo un concepto de valor de barrera de progreso que aumenta de forma uniforme que el UMD genera y escribe cuando se completa un búfer de comandos. Para que Dxgkrnl sepa si una cola de hardware determinada tiene una tarea pendiente, el UMD debe actualizar el valor de barrera de progreso en cola justo antes de anexar un nuevo búfer de comandos al búfer en anillo y hacer que sea visible para la GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress es una asignación de procesos de modo de usuario de lectura y escritura del último valor en cola.

Es esencial que el UMD se asegure de que el valor en cola se actualiza justo antes de que el nuevo envío se haga visible para la GPU. Los siguientes pasos son la secuencia recomendada de operaciones. Asumen que la cola de HW está inactiva y el último búfer terminado tenía un valor de barrera de progreso de N.

  • Genere un nuevo valor de barrera de progreso N+1.
  • Rellene el búfer de comandos. La última instrucción del búfer de comandos es una operación de escritura de valor de barrera de progreso en N+1.
  • Informe al sistema operativo del valor recién puesto en cola poniendo *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) igual a N+1.
  • Haga que el búfer de comandos sea visible para la GPU agregándolo al búfer en anillo.
  • Haga sonar el timbre.

Finalización normal y anómala del proceso

La siguiente secuencia de eventos tiene lugar durante la finalización normal de un proceso.

En cada HWQueue del dispositivo o contexto:

  1. Dxgkrnl llama a DxgkDdiDisconnectDoorbell para desconectar el timbre.
  2. Dxgkrnl espera a que se completen la última HwQueueProgressFenceLastQueuedValueCPUVirtualAddress puesta en cola en la GPU. Las asignaciones de búfer en anillo o de control de búfer en permanecen siendo residentes.
  3. Se completa la espera de Dxgkrnl’ y puede destruir las asignaciones de búfer en anillo o de control de búfer en anillo, así como los objetos de timbre y de HWQueue.

La siguiente secuencia de eventos tiene lugar durante la finalización anómala de un proceso.

  1. Dxgkrnl marca el dispositivo como error.

  2. En cada contexto de dispositivo, Dxgkrnl llama a DxgkddiSuspendContext para suspender el contexto. Las asignaciones de búfer en anillo o de control de búfer en anillo siguen siendo residentes. El KMD reemplaza el contexto y lo quita de la lista de ejecución de HW.

  3. En cada HWQueue de contexto, Dxglrnl:

    a. Llama a DxgkDdiDisconnectDoorbell para desconectar el timbre.

    b. Destruye las asignaciones de búfer en anillo o de control de búfer en anillo, así como los objetos de timbre y HWQueue.

Ejemplos de pseudocódigo

Pseudocódigo de envío de tareas en UMD

El pseudocódigo siguiente es un ejemplo básico del modelo que debe usar el UMD para crear y enviar tareas a HWQueues mediante las API de timbre. Tenga en cuenta que hHWqueue1 es el identificador en una HWQueue creado con el indicador UserModeSubmission mediante la API de D3DKMTCreateHwQueue existente.

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

Victimización de pseudocódigos de timbre en el KMD

En el ejemplo siguiente se muestra cómo el KMD podría necesitar "virtualizar" y compartir los timbres disponibles entre las HWQueues en las GPU que usan timbres dedicados.

Pseudocódigo de la función VictimizeDoorbell() del KMD:

  • El KMD decide que el timbre lógico hDoorbell1 conectado a PhysicalDoorbell1 se debe victimizar y desconectar.
  • El KMD llama a DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue) de Dxgkrnl.
    • Dxgkrnl rota la VA de CPU visible para el UMD de este timbre en una página ficticia y cambia el valor de estado a D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
  • El KMD vuelve a controlar y realiza la victimización o desconexión efectiva.
    • El KMD victimiza hDoorbell1 y lo desconecta de PhysicalDoorbell1.
    • PhysicalDoorbell1 ya se puede usar

Ahora consulte el caso siguiente:

  1. Hay un solo timbre físico en la PCI BAR con una VA de CPU en modo kernel igual a 0xfeedfeee. A un objeto de timbre creado en una HWQueue se le asigna este valor de timbre físico.

    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. El sistema operativo llama a DxgkDdiCreateDoorbell en una HWQueue2 diferente:

    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. El sistema operativo llama a DxgkDdiConnectDoorbell en 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
    ``
    
    

Este mecanismo no es necesario si una GPU usa timbres globales. En su lugar, en este ejemplo, tanto hDoorbell1 como hDoorbell2 se asignarían al mismo timbre físico 0xfeedfeee.