Soumission de travail en mode utilisateur
Important
Certaines informations portent sur la préversion du produit qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Cet article décrit la fonctionnalité de soumission de travail en mode utilisateur (UM), qui est toujours en phase de développement, à partir de Windows 11, version 24H2 (WDDM 3.2). La soumission de travail UM permet aux applications d'envoyer du travail au GPU directement à partir du mode utilisateur avec une très faible latence. L'objectif est d'améliorer les performances des applications qui envoient fréquemment de petites charges de travail au GPU. En outre, la soumission en mode utilisateur devrait apporter un avantage significatif à ces applications si elles s'exécutent à l'intérieur d'un conteneur ou d'une machine virtuelle (VM). Cet avantage est dû au fait que le pilote en mode utilisateur (UMD) exécuté dans la machine virtuelle peut envoyer directement du travail au GPU sans avoir à envoyer un message à l'hôte.
Les pilotes et le matériel des fournisseurs indépendants (IHV) prenant en charge la soumission de travail en mode utilisateur (UM) doivent simultanément continuer à prendre en charge le modèle de soumission de travail en mode noyau traditionnel. Cette prise en charge est nécessaire pour les scénarios dans lesquels un invité plus ancien prenant uniquement en charge des files d'attente traditionnelles en mode noyau s'exécute sur un hôte plus récent.
Cet article ne traite pas de l'interopérabilité entre la soumission en mode utilisateur et Flip/FlipEx. La soumission UM décrite dans cet article est limitée à la classe de scénarios rendu uniquement/calcul. Pour l'instant, le pipeline de présentation continue d'être basé sur la soumission en mode noyau, car il dépend des barrières surveillées natives. La conception et l'implémentation d'une présentation basée sur la soumission UM peuvent être prises en compte une fois que les barrières surveillées natives et la soumission UM pour le calcul/rendu uniquement seront entièrement implémentées. Par conséquent, les pilotes doivent prendre en charge la soumission en mode utilisateur pour chaque file d'attente.
Doorbells
La plupart des générations actuelles ou à venir de GPU qui prennent en charge la planification matérielle prennent également en charge le concept de doorbell de GPU. Une doorbell est un mécanisme permettant d'indiquer à un moteur GPU qu'un nouveau travail est mis en attente dans sa file de travaux en attente. Les doorbells sont généralement enregistrées dans la PCIe BAR (barre d'adresses de base) ou la mémoire système. Chaque IHV de GPU possède sa propre architecture qui détermine le nombre de doorbells, leur emplacement dans le système, etc. Le système d'exploitation Windows utilise des doorbells dans le cadre de sa conception pour implémenter la soumission de travail en mode utilisateur.
À un niveau élevé, il existe deux modèles de doorbells implémentés par différents fournisseurs indépendants et GPU :
Doorbells globales
Dans le modèle de doorbell globale, toutes les files d'attente matérielles entre les contextes et les processus partagent une seule doorbell globale. La valeur écrite dans la doorbell signale au planificateur GPU la file d'attente matérielle et le moteur qui ont reçu du travail. Le matériel GPU utilise une forme de mécanisme d'interrogation pour extraire le travail si plusieurs files d'attente matérielles envoient activement du travail et déclenchent la même doorbell globale.
Doorbells dédiées
Dans le modèle de doorbell dédiée, chaque file d'attente matérielle se voit attribuer sa propre doorbell, qui se déclenche chaque fois qu'il y a un nouveau travail à envoyer au GPU. Lorsqu'une doorbell se déclenche, le planificateur GPU sait exactement quelle file d'attente matérielle a envoyé un nouveau travail. Il existe des doorbells limitées qui sont partagées sur toutes les files d'attente matérielles créées sur le GPU. Si le nombre de files d'attente matérielles créées dépasse le nombre de doorbells disponibles, le pilote doit déconnecter la doorbell d'une file d'attente matérielle plus ancienne, ou celle qui est la moins récemment utilisée, et affecter sa doorbell à une file d'attente nouvellement créée, ce qui revient à une « virtualisation » des doorbells.
Découverte de la prise en charge de la soumission de travail en mode utilisateur
DXGK_NODEMETADATA_FLAGS::UserModeSubmissionSupported
Pour les nœuds GPU qui prennent en charge la fonctionnalité de soumission de travail UM, le DxgkDdiGetNodeMetadata du KMD définit l'indicateur de métadonnées de nœud UserModeSubmissionSupported qui est ajouté à DXGK_NODEMETADATA_FLAGS. Le système d'exploitation permet ensuite à l'UMD de créer des HWQueues à soumission en mode utilisateur et des doorbells uniquement sur les nœuds pour lesquels cet indicateur est défini.
DXGK_QUERYADAPTERINFOTYPE::DXGKQAITYPE_USERMODESUBMISSION_CAPS
Pour interroger les informations spécifiques de la doorbell, le système d'exploitation appelle la fonction DxgkDdiQueryAdapterInfo du KMD avec le type d'informations de l'adaptateur de requête DXGKQAITYPE_USERMODESUBMISSION_CAPS. Le KMD répond en remplissant une structure DXGK_USERMODESUBMISSION_CAPS avec les détails de sa prise en charge de la soumission de travail en mode utilisateur.
Actuellement, la seule limite requise est la taille de la mémoire de doorbell (en octets). Dxgkrnl a besoin de la taille de la mémoire de doorbell pour plusieurs raisons :
- Lors de la création d'une doorbell (D3DKMTCreateDoorbell), Dxgkrnl retourne une DoorbellCpuVirtualAddress à UMD. Auparavant, Dxgkrnl doit tout d'abord mapper en interne à une page factice, car la doorbell n'est encore ni attribuée ni connectée. La taille de la doorbell est nécessaire pour allouer la page factice.
- Au moment de la connexion de la doorbell (D3DKMT Connecter Doorbell), Dxgkrnl doit faire passer DoorbellCpuVirtualAddress à une DoorbellPhysicalAddress fournie par le KMD. Là encore, Dxgkrnl a besoin de connaître la taille de la doorbell.
D3DDDI_CREATEHWQUEUEFLAGS::UserModeSubmission in D3DKMTCreateHwQueue
L'UMD définit l'indicateur UserModeSubmission ajouté à D3DDDI_CREATEHWQUEUEFLAGS pour créer des HWQueues qui utilisent le modèle de soumission en mode utilisateur. Les HWQueues créées à l'aide de cet indicateur ne peuvent pas utiliser le chemin de soumission de travail en mode noyau standard et doivent s'appuyer sur le mécanisme de doorbell pour la soumission de travail dans la file d'attente.
API de soumission de travail en mode utilisateur
Les API en mode utilisateur suivantes sont ajoutées pour prendre en charge la soumission de travail en mode utilisateur.
D3DKMTCreateDoorbell crée une doorbell à l'attention d'une D3D HWQueue pour la soumission de travail en mode utilisateur.
D3DKMTConnectDoorbell connecte une doorbell préalablement créée à une D3D HWQueue pour la soumission de travail en mode utilisateur.
D3DKMTDestroyDoorbell détruit une doorbell précédemment créée.
D3DKMTNotifyWorkSubmission informe le KMD qu'un nouveau travail a été envoyé sur une HWQueue. L'intérêt de cette fonctionnalité est de fournir un chemin de soumission de travail à faible latence, où le KMD n'est pas impliqué, ni même conscient du moment où le travail est envoyé. Cette API est utile dans les scénarios où le KMD doit être averti chaque fois qu'un travail est envoyé sur une HWQueue. Les pilotes doivent utiliser ce mécanisme dans des scénarios spécifiques et peu fréquents, car il implique un aller-retour entre l'UMD et le KMD à chaque soumission de travail, ce qui va à l'encontre de l'objectif d'un modèle de soumission en mode utilisateur à faible latence.
Modèle de résidence de la mémoire de doorbell et des allocations de mémoire tampon en anneau
- L'UMD est responsable de faire en sorte que les allocations de mémoire tampon en anneau et de contrôle de mémoire tampon en anneau soient résidentes avant de créer une doorbell.
- L'UMD gère la durée de vie des allocations de mémoire tampon en anneau et de contrôle de mémoire tampon en anneau. Dxgkrnl ne détruit pas implicitement ces allocations, même si la doorbell correspondante est détruite. L'UMD est responsable de l'allocation et de la destruction de ces allocations. Toutefois, pour empêcher un programme malveillant en mode utilisateur de détruire ces allocations tant que la doorbell est active, Dxgkrnl prend une référence sur celles-ci pendant la durée de vie de la doorbell.
- Le seul scénario dans lequel Dxgkrnl détruit des allocations de mémoire tampon en anneau est lors de l'arrêt de l'appareil. Dxgkrnl détruit toutes les HWQueues, les doorbells et les allocations de mémoire tampon en anneau associées à l'appareil.
- Tant que les allocations de mémoire tampon en anneau sont actives, la CPUVA de la mémoire tampon en anneau reste valide et accessible à l'UMD, quel que soit l'état des connexions de la doorbell. Autrement dit, la résidence de la mémoire tampon en anneau n'est pas liée à la doorbell.
- Lorsque le KMD effectue le rappel DXG pour déconnecter une doorbell (ce qui signifie qu'il appelle DxgkCbDisconnectDoorbell avec l'état D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY), Dxgkrnl fait passer la CPUVA de la doorbell à une page factice. Il ne met pas à l'écart et ne démappe pas les allocations de mémoire tampon en anneau.
- En cas de scénario de perte d'appareil (TDR/GPU Stop/Page, etc.), Dxgkrnl déconnecte la doorbell et marque l'état comme D3DDDI_DOORBELL_STATUS_DISCONNECTED_ABORT. Le mode utilisateur est responsable de la destruction de la HWQueue, de la doorbell et de la mémoire tampon en anneau, ainsi que de leur recréation. Cette exigence est similaire à la façon dont d'autres ressources d'appareil sont détruites et recréées dans ce scénario.
Suspension du contexte matériel
Lorsque le système d'exploitation suspend un contexte matériel, Dxgkrnl maintient la connexion de la doorbell active et l'allocation de la mémoire tampon en anneau (file d'attente de travail) résidente. De cette façon, l'UMD peut continuer à mettre du travail dans la file d'attente du contexte ; la seule chose est que ce travail n'est pas planifié tant que le contexte est suspendu. Une fois le contexte repris et planifié, le processeur de gestion de contexte (CMP) du GPU observe le nouveau pointeur d'écriture et la soumission de travail.
Cette logique est similaire à celle de l'actuelle soumission en mode noyau, où l'UMD peut appeler D3DKMTSubmitCommand avec un contexte suspendu. Dxgkrnl met en file d'attente cette nouvelle commande dans HwQueue, mais elle ne sera planifiée qu'ultérieurement.
La séquence d'événements suivante se produit lors de la suspension et de la reprise du contexte matériel.
Suspension d'un contexte matériel :
- Dxgkrnl appelle DxgkddiSuspendContext.
- Le KMD supprime toutes les HWQueues du contexte de la liste du planificateur matériel.
- Les doorbells sont toujours connectées et les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau sont toujours résidentes. L'UMD peut écrire de nouvelles commandes dans la HWQueue de ce contexte, mais le GPU ne les traite pas, tout comme aujourd'hui avec la soumission de commandes en mode noyau pour un contexte suspendu.
- Si le KMD choisit de victimiser la doorbell d'une HWQueue suspendue, l'UMD perd sa connexion. L'UMD peut tenter de reconnecter la doorbell, et le KMD attribuera une nouvelle doorbell à cette file d'attente. L'intention est de ne pas bloquer l'UMD, et plutôt de l'autoriser à continuer à envoyer le travail que le moteur matériel peut éventuellement traiter une fois le contexte repris.
Reprise d'un contexte matériel :
- Dxgkrnl appelle DxgkddiResumeContext.
- Le KMD ajoute toutes les HWQueues du contexte à la liste du planificateur matériel.
Transitions d'état F du moteur
Dans la soumission traditionnelle de travail en mode noyau, Dxgkrnl est chargé d'envoyer de nouvelles commandes à la HWQueue et de surveiller les interruptions d'exécution à partir du KMD. Pour cette raison, Dxgkrnl a une vue d'ensemble du moment où un moteur est actif et inactif.
Dans la soumission de travail en mode utilisateur, Dxgkrnl surveille si un moteur GPU progresse à partir de la cadence de délai d'expiration TDR. Par conséquent, s'il est utile de lancer une transition vers l'état F1 plus tôt que dans le délai d'expiration TDR de deux secondes, le KMD peut demander au système d'exploitation de le faire.
Les modifications suivantes ont été apportées pour faciliter cette approche :
Le type d'interruption DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE est ajouté à DXGK_INTERRUPT_TYPE. Le KMD utilise cette interruption pour informer Dxgkrnl de transitions d'état du moteur qui nécessitent une action d'alimentation du GPU ou une reprise du délai d'expiration telles que Active -> TransitionToF1 et Active -> Hung.
La structure de données d'interruption EngineStateChange est ajoutée à DXGKARGCB_NOTIFY_INTERRUPT_DATA.
L'énumération DXGK_ENGINE_STATE est ajoutée pour représenter les transitions d'état du moteur pour EngineStateChange.
Lorsque le KMD déclenche une interruption DXGK_INTERRUPT_GPU_ENGINE_STATE_CHANGE avec EngineStateChange.NewState défini sur DXGK_ENGINE_STATE_TRANSITION_TO_F1, Dxgkrnl déconnecte toutes les doorbells des HWQueues sur ce moteur, puis lance une transition de composant d'alimentation F0 vers F1.
Lorsque l'UMD tente d'envoyer un nouveau travail au moteur GPU dans l'état F1, il doit reconnecter la doorbell, ce qui provoque à son tour l'initialisation par Dxgkrnl d'une transition vers l'état d'alimentation F0.
Transitions d'état D du moteur
Lors d'une transition d'état d'alimentation D0 à D3 sur un appareil, Dxgkrnl interrompt lA HWQueue, déconnecte la doorbell (en faisant passer la CPUVA vers une page factice) et met à jour l'état de la doorbell DoorbellStatusCpuVirtualAddress sur D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
Si l'UMD appelle D3DKMT Connecter Doorbell lorsque le GPU est sur l'état D3, il force Dxgkrnl à provoquer la sortie de veille du GPU sur D0. Dxgkrnl est également responsable de la reprise de la HWQueue et du passage de la CPUVA de la doorbell à un emplacement physique.
La séquence d'événements suivante se produit.
Une mise hors tension du GPU D0 vers D3 se produit :
- Dxgkrnl appelle DxgkddiSuspendContext pour tous les contextes matériels sur le GPU. Le KMD supprime ces contextes de la liste du planificateur matériel.
- Dxgkrnl déconnecte toutes les doorbells.
- Si nécessaire, Dxgkrnl met éventuellement à l'écart toutes les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau de la VRAM. Il le fait une fois que tous les contextes sont suspendus et supprimés de la liste du planificateur matériel afin que le matériel ne fasse référence à aucune mémoire mise à l'écart.
L'UMD écrit une nouvelle commande dans une HWQueue lorsque le GPU est à l'état D3 :
- L'UMD voit que la doorbell est déconnectée, et appelle D3DKMT Connecter Doorbell.
- Dxgkrnl lance une transition D0.
- Si elles ont été mises à l'écart, Dxgkrnl rend toutes les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau résidentes.
- Dxgkrnl appelle la fonction DxgkddiCreateDoorbell du KMD pour demander à ce dernier de créer une connexion doorbell pour cette HWQueue.
- Dxgkrnl appelle DxgkddiResumeContext pour tous les HWContexts. Le KMD ajoute les files d'attente correspondantes à la liste du planificateur HW.
DDI pour la soumission de travail en mode utilisateur
DDI implémentés par le KMD
Les DDI en mode noyau suivants sont ajoutés pour que le KMD implémente la prise en charge de la soumission de travail en mode utilisateur.
DxgkDdiCreateDoorbell. Lorsque l'UMD appelle D3DKMTCreateDoorbell en vue de créer une doorbell pour une HWQueue, Dxgkrnl effectue un appel correspondant à cette fonction afin que le KMD puisse initialiser ses structures de doorbell.
DxgkDdiConnectDoorbell. Lorsque l'UMD appelle D3DKMTConnectDoorbell, Dxgkrnl effectue un appel correspondant à cette fonction afin que le KMD puisse fournir une CPUVA mappée à l'emplacement physique de la doorbell, ainsi qu'établir les connexions requises entre l'objet HWQueue, l'objet doorbell, l'adresse physique de la doorbell, le planificateur GPU, etc.
DxgkDdiDisconnectDoorbell. Lorsque le système d'exploitation souhaite déconnecter une doorbell donnée, il appelle KMD avec ce DDI.
DxgkDdiDestroyDoorbell. Lorsque l'UMD appelle D3DKMTDestroyDoorbell, Dxgkrnl effectue un appel correspondant à cette fonction afin que le KMD puisse détruire ses structures de doorbell.
DxgkDdiNotifyWorkSubmission. Lorsque l'UMD appelle D3DKMTNotifyWorkSubmission, Dxgkrnl effectue un appel correspondant à cette fonction afin que le KMD puisse être informé des nouvelles soumissions de travail.
DDI implémenté par Dxgkrnl
Le rappel DxgkCbDisconnectDoorbell est implémenté par Dxgkrnl. Le KMD peut appeler cette fonction pour informer Dxgkrnl que le KMD doit déconnecter une doorbell donnée.
Modifications d'une barrière de progression de file d'attente matérielle
Les files d'attente matérielles exécutées dans le modèle de soumission de travail en mode utilisateur ont toujours un concept de valeur de barrière de progression monotonique croissante que l'UMD génère et écrit lorsqu'une mémoire tampon de commande est terminée. Pour que Dxgkrnl sache si une file d'attente matérielle particulière a du travail en suspens, l'UMD doit mettre à jour la valeur de barrière de progression mise en file d'attente juste avant d'ajouter une nouvelle mémoire tampon de commande à la mémoire tampon en anneau et de la rendre visible au GPU. CreateDoorbell.HwQueueProgressFenceLastQueuedValueCPUVirtualAddress est un mappage de processus de lecture/écriture en mode utilisateur de la dernière valeur mise en file d'attente.
Il est essentiel pour l'UMD de veiller à ce que la valeur mise en file d'attente soit mise à jour juste avant que la nouvelle soumission ne soit rendue visible au GPU. La séquence d'opérations recommandée comprend les étapes suivantes. Elles partent de l'hypothèse que la file d'attente matérielle est inactive et que la dernière mémoire tampon terminée a eu une valeur de barrière de progression de N.
- Générer une nouvelle valeur de barrière de progression N+1.
- Remplir la mémoire tampon de commande. La dernière instruction de la mémoire tampon de commande est l'écriture d'une valeur de barrière de progression de N+1.
- Informer le système d'exploitation de la valeur nouvellement mise en file d'attente en donnant à *(HwQueueProgressFenceLastQueuedValueCPUVirtualAddress) la valeur N+1.
- Rendre la mémoire tampon de commande visible au GPU en l'ajoutant à la mémoire tampon en anneau.
- Déclencher la doorbell.
Arrêt normal et anormal du processus
La séquence d'événements suivante a lieu pendant un arrêt normal du processus.
Pour chaque HWQueue de l'appareil/contexte :
- Dxgkrnl appelle DxgkDdiDisconnectDoorbell pour déconnecter la doorbell.
- Dxgkrnl attend que la dernière HwQueueProgressFenceLastQueuedValueCPUVirtualAddress mise en file d'attente soit terminée sur le GPU. Les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau restent résidentes.
- L'attente de Dxgkrnl est satisfaite et celui-ci peut désormais détruire les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau, ainsi que les objets doorbell et HWQueue.
La séquence d'événements suivante a lieu pendant un arrêt anormal du processus.
Dxgkrnl marque l'appareil comme étant en erreur.
Pour chaque contexte de l'appareil, Dxgkrnl appelle DxgkddiSuspendContext pour suspendre le contexte. Les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau sont toujours résidentes. Le KMD préempte le contexte et le supprime de sa liste d'exécution HW.
Pour chaque HWQueue de contexte, Dxglrnl :
a. Appelle DxgkDdiDisconnectDoorbell pour déconnecter la doorbell.
b. Détruit les allocations de mémoire tampon en anneau/contrôle de mémoire tampon en anneau, ainsi que les objets doorbell et HWQueue.
Exemples de pseudo-code
Pseudo-code de soumission de travail dans l'UMD
Le pseudo-code suivant est un exemple de base du modèle que l'UMD doit utiliser pour créer et envoyer un travail à HWQueues avec les API doorbell. Considérer que hHWqueue1
est le handle d'une HWQueue créée avec l'indicateur UserModeSubmission
à l'aide de l'API D3DKMTCreateHwQueue existante.
// 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);
Victimisation du pseudo-code de doorbell dans le KMD
L'exemple suivant illustre comment le KMD peut avoir besoin de « virtualiser » et de partager les doorbells disponibles entre les HWQueues sur les GPU qui utilisent des doorbells dédiées.
Pseudo-code de la fonction VictimizeDoorbell()
du KMD :
- Le KMD décide que la doorbell logique
hDoorbell1
connectée àPhysicalDoorbell1
a besoin d'être victimisée et déconnectée. - Le KMD appelle
DxgkCbDisconnectDoorbellCB(hDoorbell1->hHwQueue)
dans Dxgkrnl.- Dxgkrnl fait passer la CPUVA visible par l'UMD de cette doorbell vers une page factice et met à jour la valeur d'état sur D3DDDI_DOORBELL_STATUS_DISCONNECTED_RETRY.
- Le KMD reprend le contrôle et effectue la victimisation/déconnexion réelle.
- Le KMD victimise
hDoorbell1
et la déconnecte dePhysicalDoorbell1
. PhysicalDoorbell1
est disponible pour son utilisation
- Le KMD victimise
Considérons désormais le scénario suivant :
Il existe une seule doorbell physique dans la PCI BAR, avec une CPUVA en mode noyau égale à
0xfeedfeee
. Un objet doorbell créé pour une HWQueue est affecté à cette valeur de doorbell physique.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
Le système d'exploitation appelle
DxgkDdiCreateDoorbell
pour une autreHWQueue2
: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
Le système d'exploitation appelle
DxgkDdiConnectDoorbell
surhDoorbell2
:// 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 ``
Ce mécanisme n'est pas nécessaire si un GPU utilise des doorbells globales. Dans un tel cas, avec cet exemple, hDoorbell1
et hDoorbell2
se verraient toutes deux attribuer la même doorbell physique 0xfeedfeee
.