Partager via


Hypercall Interface

L’hyperviseur fournit un mécanisme d’appel pour les invités. Ces appels sont appelés hypercalls. Chaque hypercall définit un ensemble de paramètres d’entrée et/ou de sortie. Ces paramètres sont spécifiés en termes de structure de données basée sur la mémoire. Tous les éléments des structures de données d’entrée et de sortie sont rembourrés aux limites naturelles jusqu’à 8 octets (autrement dit, les éléments à deux octets doivent se trouver sur des limites à deux octets, etc.).

Une deuxième convention d’appel hypercall peut éventuellement être utilisée pour un sous-ensemble d’hypercalls, notamment ceux qui ont deux paramètres d’entrée ou moins et aucun paramètre de sortie. Lors de l’utilisation de cette convention d’appel, les paramètres d’entrée sont transmis dans des registres à usage général.

Une troisième convention d’appel hypercall peut éventuellement être utilisée pour un sous-ensemble d’hypercalls où le bloc de paramètres d’entrée est jusqu’à 112 octets. Lors de l’utilisation de cette convention d’appel, les paramètres d’entrée sont transmis dans des registres, y compris les registres XMM volatiles.

Les structures de données d’entrée et de sortie doivent toutes deux être placées en mémoire sur une limite de 8 octets et complétées à un multiple de 8 octets de taille. Les valeurs dans les régions de remplissage sont ignorées par l’hyperviseur.

Pour la sortie, l’hyperviseur est autorisé à remplacer (mais pas garanti) les régions de remplissage. S’il remplace les régions de remplissage, il écrit des zéros.

Hypercall Classes

Il existe deux classes d’hypercalls : simple et rep (court pour « répéter »). Un hypercall simple effectue une opération unique et a un ensemble de paramètres d’entrée et de sortie de taille fixe. Un hypercall de rep agit comme une série d’hypercalls simples. En plus d’un ensemble de paramètres d’entrée et de sortie de taille fixe, les hypercalls de représentation impliquent une liste d’éléments d’entrée et/ou de sortie de taille fixe.

Lorsqu’un appelant appelle initialement un hypercall de rep, il spécifie un nombre de rep qui indique le nombre d’éléments dans la liste des paramètres d’entrée et/ou de sortie. Les appelants spécifient également un index de démarrage rep qui indique l’élément d’entrée et/ou de sortie suivant qui doit être consommé. L’hyperviseur traite les paramètres de rep dans l’ordre de liste, c’est-à-dire en augmentant l’index d’élément.

Pour les appels suivants de l’hypercall de rep, l’index de début du rep indique le nombre d’éléments qui ont été terminés et, conjointement avec la valeur de nombre de rep, combien d’éléments sont laissés. Par exemple, si un appelant spécifie un nombre de rep de 25 et que seules 20 itérations sont effectuées dans les contraintes de temps, l’hypercall retourne le contrôle au processeur virtuel appelant après avoir mis à jour l’index de démarrage du rep à 20. Lorsque l’hypercall est réexécuté, l’hyperviseur reprend à l’élément 20 et termine les 5 éléments restants.

Si une erreur se produit lors du traitement d’un élément, un code d’état approprié est fourni avec un nombre de reps terminé, indiquant le nombre d’éléments qui ont été correctement traités avant la rencontre de l’erreur. En supposant que le mot de contrôle hypercall spécifié est valide (voir ci-dessous) et que les listes de paramètres d’entrée/sortie sont accessibles, l’hyperviseur est garanti pour tenter au moins un représentant, mais il n’est pas nécessaire de traiter l’intégralité de la liste avant de retourner le contrôle à l’appelant.

Hypercall Continuation

Un hypercall peut être considéré comme une instruction complexe qui prend de nombreux cycles. L’hyperviseur tente de limiter l’exécution d’hypercall à 50μs ou moins avant de retourner le contrôle au processeur virtuel qui a appelé l’hypercall. Certaines opérations hypercall sont suffisamment complexes qu’une garantie de 50μs est difficile à faire. L’hyperviseur s’appuie donc sur un mécanisme de continuation hypercall pour certains hypercalls, y compris toutes les formes d’hypercall de rep.

Le mécanisme de continuation hypercall est principalement transparent pour l’appelant. Si un hypercall n’est pas en mesure de se terminer dans la limite de temps prescrite, le contrôle est retourné à l’appelant, mais le pointeur d’instruction n’est pas avancé après l’instruction qui a appelé l’hypercall. Cela permet de gérer les interruptions en attente et d’autres processeurs virtuels. Lorsque le thread appelant d’origine reprend l’exécution, il réexécute l’instruction hypercall et avance la progression vers la fin de l’opération.

Les hypercalls les plus simples sont assurés de se terminer dans le délai imparti. Toutefois, un petit nombre d’hypercalls simples peut nécessiter plus de temps. Ces hypercalls utilisent la continuation hypercall de manière similaire aux hypercalls. Dans ce cas, l’opération implique deux états internes ou plus. Le premier appel place l’objet (par exemple, la partition ou le processeur virtuel) dans un état, et après des appels répétés, l’état passe enfin à un état terminal. Pour chaque hypercall qui suit ce modèle, les effets secondaires visibles des états internes intermédiaires sont décrits.

Hypercall Atomicity and Ordering

Sauf indication contraire, l’action effectuée par un hypercall est atomique à la fois par rapport à toutes les autres opérations invitées (par exemple, les instructions exécutées dans un invité) et tous les autres hypercalls exécutés sur le système. Un hypercall simple effectue une action atomique unique ; un hypercall rep effectue plusieurs actions atomiques indépendantes.

Les hypercalls simples qui utilisent la continuation hypercall peuvent impliquer plusieurs états internes visibles en externe. Ces appels comprennent plusieurs opérations atomiques.

Chaque action hypercall peut lire les paramètres d’entrée et/ou écrire des résultats. Les entrées de chaque action peuvent être lues à n’importe quelle granularité et à tout moment après l’exécution de l’hypercall et avant l’exécution de l’action. Les résultats (autrement dit, les paramètres de sortie) associés à chaque action peuvent être écrits à n’importe quelle granularité et à tout moment après l’exécution de l’action et avant le retour de l’hypercall.

L’invité doit éviter l’examen et/ou la manipulation de tous les paramètres d’entrée ou de sortie liés à un hypercall en cours d’exécution. Bien qu’un processeur virtuel exécutant un hypercall ne soit pas en mesure de le faire (car son exécution invité est suspendue jusqu’à ce que l’hypercall retourne), il n’y a rien à empêcher les autres processeurs virtuels de le faire. Les invités qui se comportent de cette façon peuvent se bloquer ou provoquer une altération dans leur partition.

Les hypercalls peuvent être appelés uniquement à partir du mode processeur invité le plus privilégié. Sur les plateformes x64, cela signifie le mode protégé avec un niveau de privilège actuel (CPL) de zéro. Bien que le code en mode réel s’exécute avec une CPL efficace de zéro, les hypercalls ne sont pas autorisés en mode réel. Une tentative d’appel d’un hypercall dans un mode processeur illégal génère une exception #UD (opération non définie) sur x64 et une exception d’instruction non définie sur ARM64.

Tous les hypercalls doivent être appelés via l’interface hypercall définie par l’architecture (voir ci-dessous). Une tentative d’appel d’un hypercall par tout autre moyen (par exemple, la copie du code de la page de codes hypercall vers un autre emplacement et son exécution à partir de là) peut entraîner une exception d’opération non définie (#UD). L’hyperviseur n’est pas garanti pour fournir cette exception.

Configuration requise pour l’alignement

Les appelants doivent spécifier l’adresse physique invité 64 bits (GPA) des paramètres d’entrée et/ou de sortie. Les pointeurs GPA doivent être alignés sur 8 octets. Si l’hypercall n’implique aucun paramètre d’entrée ou de sortie, l’hyperviseur ignore le pointeur GPA correspondant.

Les listes de paramètres d’entrée et de sortie ne peuvent pas chevaucher ou traverser les limites de page. Les pages d’entrée et de sortie Hypercall sont censées être des pages GPA et non des pages « superposition ». Si le processeur virtuel écrit les paramètres d’entrée dans une page de superposition et spécifie un GPA dans cette page, l’accès hyperviseur à la liste des paramètres d’entrée n’est pas défini.

L’hyperviseur vérifie que la partition appelante peut lire à partir de la page d’entrée avant d’exécuter l’hypercall demandé. Cette validation se compose de deux vérifications : le GPA spécifié est mappé et le GPA est marqué comme lisible. Si l’un de ces tests échoue, l’hyperviseur génère un message d’interception de mémoire. Pour les hypercalls qui ont des paramètres de sortie, l’hyperviseur valide que la partition peut écrire dans la page de sortie. Cette validation se compose de deux vérifications : le GPA spécifié est mappé et le GPA est marqué en écriture.

Entrées Hypercall

Les appelants spécifient un hypercall par une valeur 64 bits appelée valeur d’entrée hypercall. Il est mis en forme comme suit :

Terrain Bits Informations fournies
Code d’appel 15-0 Spécifie l’hypercall demandé
Rapide 16 Spécifie si l’hypercall utilise la convention d’appel basée sur le registre : 0 = mémoire, 1 = register-based
Taille de l’en-tête variable 26-17 Taille d’un en-tête de variable, dans QWORDS.
RsvdZ 30-27 Doit être égal à zéro
Est imbriqué 31 Spécifie que l’hypercall doit être géré par l’hyperviseur L0 dans un environnement imbriqué.
Nombre de rep 43-32 Nombre total de reps (pour l’appel de rep, doit être égal à zéro sinon)
RsvdZ 47-44 Doit être égal à zéro
Index de début rep 59-48 Index de départ (pour l’appel de rep, doit être égal à zéro sinon)
RsvdZ 63-60 Doit être égal à zéro

Pour les hypercalls de rep, le champ nombre de rep indique le nombre total de reps. L’index de début du rep indique la répétition particulière par rapport au début de la liste (zéro indique que le premier élément de la liste doit être traité). Par conséquent, la valeur du nombre de rep doit toujours être supérieure à l’index de début du rep.

Conventions d’inscription Hypercall (x86/x64)

Sur x86/x64, inscrivez le mappage pour les entrées hypercall lorsque l’indicateur Rapide est égal à zéro :

x64 x86 Informations fournies
RCX EDX :EAX Valeur d’entrée Hypercall
RDX EBX :ECX GPA des paramètres d’entrée
R8 EDI :ESI GPA des paramètres de sortie

La valeur d’entrée hypercall est transmise dans les registres ainsi qu’un GPA qui pointe vers les paramètres d’entrée et de sortie.

Les mappages d’inscription dépendent du mode 32 bits (x86) ou 64 bits (x64). L’hyperviseur détermine le mode de l’appelant en fonction de la valeur d’EFER. LMA et CS.L. Si ces deux indicateurs sont définis, l’appelant est supposé être un appelant 64 bits.

Inscrivez le mappage pour les entrées hypercall lorsque l’indicateur Rapide est un :

x64 x86 Informations fournies
RCX EDX :EAX Valeur d’entrée Hypercall
RDX EBX :ECX Paramètre d’entrée
R8 EDI :ESI Paramètre de sortie

La valeur d’entrée hypercall est passée dans les registres ainsi que les paramètres d’entrée.

Conventions d’inscription Hypercall (ARM64 SMCCC)

Sur ARM64, les hypercalls sont exécutés à l’aide de l’instruction « HVC #0 ». Les appels adhèrent à la SMCCC ARM64 (Convention d’appel SMC).

Le mappage d’inscription pour les entrées hypercall est le suivant :

Register Informations fournies
X0 Identificateur de fonction SMCCC
X1 Valeur d’entrée Hypercall
X2 GPA des paramètres d’entrée
X3 GPA des paramètres de sortie

L’identificateur de fonction SMCCC dans X0 suit ce format :

Bits Terrain Valeur Descriptif
31 Appel de rendement 0 Toujours 0
30 Convention d’appel 1 1 pour les conventions d’appel HVC64
29:24 Type d’appel de service 6 6 pour les appels de service Hyperviseur spécifiques au fournisseur
23:16 Réservé 0 Réservé, doit être égal à zéro (Res0)
15:0 Numéro de fonction 1 1 indique que le code d’appel HV est défini dans X1

Format complet de l’identificateur de fonction : 0x46000001

Conventions d’inscription Hypercall (ARM64 HVC #1)

Pour des raisons historiques, l’interface hyperviseur ARM64 prend également en charge une convention d’appel différente. Les hypercalls sont exécutés avec l’instruction « HVC #1 ». Nous vous recommandons d’utiliser la convention d’appel SMCCC pour le nouveau code.

Le mappage d’inscription pour les entrées hypercall est le suivant :

Register Informations fournies
X0 Valeur d’entrée Hypercall
X1 GPA des paramètres d’entrée
X2 GPA des paramètres de sortie

En-têtes d’entrée Hypercall de taille variable

La plupart des en-têtes d’entrée hypercall ont une taille fixe. La quantité de données d’en-tête passées de l’invité à l’hyperviseur est donc implicitement spécifiée par le code d’hypercall et ne doit pas être spécifiée séparément. Toutefois, certains hypercalls nécessitent une quantité variable de données d’en-tête. Ces hypercalls ont généralement un en-tête d’entrée de taille fixe et une entrée d’en-tête supplémentaire de taille variable.

Un en-tête de taille variable est similaire à une entrée hypercall fixe (alignée sur 8 octets et dimensionnée à un multiple de 8 octets). L’appelant doit spécifier la quantité de données qu’il fournit en tant qu’en-têtes d’entrée. Cette taille est fournie dans le cadre de la valeur d’entrée hypercall (voir « Taille de l’en-tête variable » dans le tableau ci-dessus).

Étant donné que la taille d’en-tête fixe est implicite, au lieu de fournir la taille totale de l’en-tête, seule la partie variable est fournie dans les contrôles d’entrée :

Variable Header Bytes = {Total Header Bytes - sizeof(Fixed Header)} rounded up to nearest multiple of 8

Variable Header Size = Variable Header Bytes / 8

Il est illégal de spécifier une taille d’en-tête variable non nulle pour un hypercall qui n’est pas explicitement documenté comme acceptant des en-têtes d’entrée de taille variable. Dans ce cas, l’hypercall entraîne un code de retour de HV_STATUS_INVALID_HYPERCALL_INPUT.

Il est possible que pour un appel donné d’un hypercall qui accepte les en-têtes d’entrée de taille variable que toutes les entrées d’en-tête d’en-tête correspondent entièrement à l’en-tête de taille fixe. Dans ce cas, l’en-tête d’entrée de taille variable est de taille zéro et les bits correspondants dans l’entrée hypercall doivent être définis sur zéro.

À tous les autres égards, les hypercalls acceptant des en-têtes d’entrée de taille variable sont sinon similaires aux hypercalls d’en-tête d’entrée de taille fixe en ce qui concerne les conventions d’appel. Il est également possible pour un hypercall d’en-tête de taille variable de prendre en charge la sémantique rep. Dans ce cas, les éléments de rep se trouvent après l’en-tête de la manière habituelle, sauf que la taille totale de l’en-tête inclut à la fois les parties fixes et variables. Toutes les autres règles restent identiques, par exemple, le premier élément rep doit être aligné sur 8 octets.

Entrée Hypercall rapide XMM (x86/x64)

Sur les plateformes x86/x64, l’hyperviseur prend en charge l’utilisation des hypercalls rapides XMM, ce qui permet à certains hypercalls de tirer parti des performances améliorées de l’interface hypercall rapide, même s’ils nécessitent plus de deux paramètres d’entrée. L’interface hypercall rapide XMM utilise six registres XMM pour permettre à l’appelant de passer un bloc de paramètres d’entrée jusqu’à 112 octets de taille.

La disponibilité de l’interface hypercall rapide XMM est indiquée par le biais de la feuille UCID ucID (0x40000003) « Hypervisor Feature Identification » (0x40000003) :

  • Bit 4 : la prise en charge de la transmission d’une entrée hypercall via des registres XMM est disponible.

Notez qu’il existe un indicateur distinct pour indiquer la prise en charge de la sortie rapide XMM. Toute tentative d’utilisation de cette interface lorsque l’hyperviseur n’indique pas que la disponibilité entraîne une erreur #UD.

Inscrire le mappage (entrée uniquement)

x64 x86 Informations fournies
RCX EDX :EAX Valeur d’entrée Hypercall
RDX EBX :ECX Bloc de paramètres d’entrée
R8 EDI :ESI Bloc de paramètres d’entrée
XMM0 XMM0 Bloc de paramètres d’entrée
XMM1 XMM1 Bloc de paramètres d’entrée
XMM2 XMM2 Bloc de paramètres d’entrée
XMM3 XMM3 Bloc de paramètres d’entrée
XMM4 XMM4 Bloc de paramètres d’entrée
XMM5 XMM5 Bloc de paramètres d’entrée

La valeur d’entrée hypercall est passée dans les registres ainsi que les paramètres d’entrée. Les mappages d’inscription dépendent du mode 32 bits (x86) ou 64 bits (x64). L’hyperviseur détermine le mode de l’appelant en fonction de la valeur d’EFER. LMA et CS.L. Si ces deux indicateurs sont définis, l’appelant est supposé être un appelant 64 bits. Si le bloc de paramètres d’entrée est inférieur à 112 octets, tous les octets supplémentaires dans les registres sont ignorés.

Inscrire une entrée d’appel rapide (ARM64 SMCCC)

Sur les plateformes ARM64, l’hyperviseur prend en charge l’utilisation d’hypercalls rapides d’inscription, ce qui permet à certains hypercalls de tirer parti des performances améliorées de l’interface hypercall rapide, même s’ils nécessitent plus de deux paramètres d’entrée. L’interface hypercall rapide d’inscription utilise 16 registres à usage général pour permettre à l’appelant de passer un bloc de paramètres d’entrée jusqu’à 128 octets de taille.

Inscrire le mappage (entrée uniquement)

Register Informations fournies
X0 Identificateur de fonction SMCCC
X1 Valeur d’entrée Hypercall
X2 - X17 Bloc de paramètres d’entrée

Si le bloc de paramètres d’entrée est inférieur à 128 octets, tous les octets supplémentaires dans les registres sont ignorés.

Inscrire une entrée d’appel rapide (ARM64 HVC #1)

L’interface hypercall rapide d’inscription utilise seize registres à usage général pour permettre à l’appelant de passer un bloc de paramètres d’entrée jusqu’à 128 octets de taille.

Inscrire le mappage (entrée uniquement)

Register Informations fournies
X0 Valeur d’entrée Hypercall
X1 - X17 Bloc de paramètres d’entrée

Si le bloc de paramètres d’entrée est inférieur à 128 octets, tous les octets supplémentaires dans les registres sont ignorés.

Sorties Hypercall

Tous les hypercalls retournent une valeur 64 bits appelée valeur de résultat hypercall. Il est mis en forme comme suit :

Terrain Bits Commentaire
Résultat 15-0 HV_STATUS code indiquant la réussite ou l’échec
Rsvd 31-16 Les appelants doivent ignorer la valeur dans ces bits
Reps terminé 43-32 Nombre de représentants correctement terminés
RsvdZ 63-40 Les appelants doivent ignorer la valeur dans ces bits

Pour les hypercalls de rep, le champ reps complete est le nombre total de reps terminé et non par rapport à l’index de début du rep. Par exemple, si l’appelant a spécifié un index de début de rep de 5 et un nombre de rep de 10, le champ complet des reps indique 10 une fois l’achèvement réussi.

La valeur du résultat hypercall est renvoyée dans les registres.

Sur x64, le mappage d’inscription dépend de l’exécution de l’appelant en mode 32 bits (x86) ou 64 bits (x64) (voir ci-dessus). Le mappage d’inscription pour les sorties hypercall est le suivant :

x64 x86 Informations fournies
RAX EDX :EAX Valeur de résultat Hypercall

Sur ARM64, le mappage d’inscription pour les sorties hypercall est le suivant :

Register Informations fournies
X0 Valeur de résultat Hypercall

Sortie Hypercall rapide XMM (x86/x64)

Comme la façon dont l’hyperviseur prend en charge les entrées d’hypercall rapide XMM, les mêmes registres peuvent être partagés pour retourner la sortie. Cela n’est pris en charge que sur les plateformes x64.

La possibilité de retourner la sortie via des registres XMM est indiquée via la feuille UCID de l’UCID (0x40000003) « Hypervisor Feature Identification » (hyperviseur Feature Identification) :

  • Bit 15 : la prise en charge du retour de la sortie hypercall via des registres XMM est disponible.

Notez qu’il existe un indicateur distinct pour indiquer la prise en charge de l’entrée rapide XMM. Toute tentative d’utilisation de cette interface lorsque l’hyperviseur n’indique pas que la disponibilité entraîne une erreur #UD.

Inscrire le mappage (entrée et sortie)

Les registres qui ne sont pas utilisés pour passer des paramètres d’entrée peuvent être utilisés pour retourner la sortie. En d’autres termes, si le bloc de paramètres d’entrée est inférieur à 112 octets (arrondi au bloc aligné sur 16 octets le plus proche), les registres restants retournent la sortie hypercall.

x64 Informations fournies
RDX Bloc d’entrée ou de sortie
R8 Bloc d’entrée ou de sortie
XMM0 Bloc d’entrée ou de sortie
XMM1 Bloc d’entrée ou de sortie
XMM2 Bloc d’entrée ou de sortie
XMM3 Bloc d’entrée ou de sortie
XMM4 Bloc d’entrée ou de sortie
XMM5 Bloc d’entrée ou de sortie

Par exemple, si le bloc de paramètres d’entrée est de 20 octets de taille, l’hyperviseur ignore les 12 octets suivants. Les 80 octets restants contiennent une sortie hypercall (le cas échéant).

Inscrire la sortie d’appel rapide (ARM64 SMCCC)

Sur les plateformes ARM64, comme l’hyperviseur prend en charge l’inscription d’entrées hypercall rapides, les mêmes registres peuvent être partagés pour retourner la sortie.

Inscrire le mappage (entrée et sortie)

Les registres qui ne sont pas utilisés pour passer des paramètres d’entrée peuvent être utilisés pour retourner la sortie. En d’autres termes, si le bloc de paramètres d’entrée est inférieur à 128 octets (arrondi au bloc aligné sur 8 octets le plus proche), les registres restants retournent la sortie hypercall.

Register Informations fournies
X2 - X17 Bloc d’entrée ou de sortie

Par exemple, si le bloc de paramètres d’entrée est de 20 octets de taille, l’hyperviseur ignore les 4 octets suivants. Les 104 octets restants contiennent une sortie hypercall (le cas échéant).

Inscrire la sortie d’appel rapide (ARM64 HVC #1)

Comme pour les versions SMCCC, l’interface HVC #1 utilise les mêmes registres pour retourner la sortie.

Inscrire le mappage (entrée et sortie)

Les registres qui ne sont pas utilisés pour passer des paramètres d’entrée peuvent être utilisés pour retourner la sortie. En d’autres termes, si le bloc de paramètres d’entrée est inférieur à 128 octets (arrondi au bloc aligné sur 8 octets le plus proche), les registres restants retournent la sortie hypercall.

Register Informations fournies
X1 - X17 Bloc d’entrée ou de sortie

Par exemple, si le bloc de paramètres d’entrée est de 20 octets de taille, l’hyperviseur ignore les 4 octets suivants. Les 104 octets restants contiennent une sortie hypercall (le cas échéant).

Registres volatiles (x86/x64)

Hypercalls modifie uniquement les valeurs de registre spécifiées dans les conditions suivantes :

  1. RAX (x64) et EDX :EAX (x86) sont toujours remplacés par la valeur de résultat hypercall et les paramètres de sortie, le cas échéant.
  2. Les hypercalls de rep modifient RCX (x64) et EDX :EAX (x86) avec le nouvel index de début de rep.
  3. HvCallSetVpRegisters peut modifier tous les registres pris en charge avec cet hypercall.
  4. RDX, R8 et XMM0 à XMM5, lorsqu’ils sont utilisés pour l’entrée hypercall rapide, restent inchangés. Toutefois, les registres utilisés pour la sortie hypercall rapide peuvent être modifiés, notamment RDX, R8 et XMM0 à XMM5. Hyper-V modifie uniquement ces registres pour une sortie hypercall rapide, limitée à x64.

Registres volatiles (ARM64 SMCCC)

Hypercalls modifie uniquement les valeurs de registre spécifiées dans les conditions suivantes :

  1. X0 est toujours remplacé par la valeur de résultat hypercall et les paramètres de sortie, le cas échéant.
  2. Les hypercalls de rep modifient X1 avec le nouvel index de démarrage de rep.
  3. HvCallSetVpRegisters peut modifier tous les registres pris en charge avec cet hypercall.
  4. X2 - X17, lorsqu’il est utilisé pour l’entrée hypercall rapide, restent inchangés. Toutefois, les registres utilisés pour la sortie hypercall rapide peuvent être modifiés, notamment X2 - X17. Hyper-V modifie uniquement ces registres pour une sortie hypercall rapide.

Registres volatiles (ARM64 HVC #1)

Hypercalls modifie uniquement les valeurs de registre spécifiées dans les conditions suivantes :

  1. X0 est toujours remplacé par la valeur de résultat hypercall et les paramètres de sortie, le cas échéant.
  2. Les hypercalls de rep modifient X0 avec le nouvel index de démarrage de rep.
  3. HvCallSetVpRegisters peut modifier tous les registres pris en charge avec cet hypercall.
  4. X1 - X17, lorsqu’il est utilisé pour l’entrée hypercall rapide, reste inchangé. Toutefois, les registres utilisés pour la sortie hypercall rapide peuvent être modifiés, y compris X1 - X17. Hyper-V modifie uniquement ces registres pour une sortie hypercall rapide.

Hypercall Restrictions

Les hypercalls peuvent avoir des restrictions associées qui doivent être satisfaites pour qu’elles exécutent leur fonction prévue. Si toutes les restrictions ne sont pas remplies, l’hypercall se termine par une erreur appropriée. Les restrictions suivantes sont répertoriées, le cas échéant :

  • La partition appelante doit posséder un privilège particulier
  • La partition en cours d’exécution doit être dans un état particulier (par exemple, « Actif »)

Codes d’état Hypercall

Chaque hypercall est documenté comme retournant une valeur de sortie qui contient plusieurs champs. Un champ valeur d’état (de type HV_STATUS) est utilisé pour indiquer si l’appel a réussi ou échoué.

Validité des paramètres de sortie sur les hypercalls ayant échoué

Sauf indication explicite, lorsqu’un hypercall échoue (autrement dit, le champ de résultat de la valeur de résultat hypercall contient une valeur autre que HV_STATUS_SUCCESS), le contenu de tous les paramètres de sortie est indéterminé et ne doit pas être examiné par l’appelant. Uniquement lorsque l’hypercall réussit, tous les paramètres de sortie appropriés contiennent des résultats valides et attendus.

Classement des conditions d’erreur

L’ordre dans lequel les conditions d’erreur sont détectées et signalées par l’hyperviseur n’est pas définie. En d’autres termes, si plusieurs erreurs existent, l’hyperviseur doit choisir la condition d’erreur à signaler. La priorité doit être donnée à ces codes d’erreur offrant une plus grande sécurité, l’intention étant d’empêcher l’hyperviseur de révéler des informations aux appelants qui n’ont pas suffisamment de privilège. Par exemple, le code d’état est le code HV_STATUS_ACCESS_DENIED d’état préféré par rapport à un autre qui révélerait des informations de contexte ou d’état purement basées sur le privilège.

Codes d’état Hypercall courants

Plusieurs codes de résultat sont communs à tous les hypercalls et ne sont donc pas documentés pour chaque hypercall individuellement. Ces options en question sont les suivantes :

Code de statut Condition d’erreur
HV_STATUS_SUCCESS L’appel a réussi.
HV_STATUS_INVALID_HYPERCALL_CODE Le code hypercall n’est pas reconnu.
HV_STATUS_INVALID_HYPERCALL_INPUT Le nombre de reps est incorrect (par exemple, un nombre de rep non nul est passé à un appel non-rep ou un nombre de rep zéro est passé à un appel de rep).
L’index de début du rep n’est pas inférieur au nombre de rep.
Un bit réservé dans la valeur d’entrée hypercall spécifiée n’est pas égal à zéro.
HV_STATUS_INVALID_ALIGNMENT Le pointeur GPA d’entrée ou de sortie spécifié n’est pas aligné sur 8 octets.
Les listes de paramètres d’entrée ou de sortie spécifiées s’étendent sur les pages.
Le pointeur GPA d’entrée ou de sortie n’est pas dans les limites de l’espace GPA.

Le code HV_STATUS_SUCCESS de retour indique qu’aucune condition d’erreur n’a été détectée.

Création de rapports sur l’identité du système d’exploitation invité

Le système d’exploitation invité exécuté dans la partition doit s’identifier à l’hyperviseur en écrivant sa signature et sa version dans un MSR (HV_X64_MSR_GUEST_OS_ID/HvRegisterGuestOsId) avant de pouvoir appeler des hypercalls. Ce MSR est à l’échelle de la partition et est partagé entre tous les processeurs virtuels.

La valeur de ce registre est initialement zéro.

Sur x86/x64, une valeur différente de zéro doit être écrite dans le MSR ID de système d’exploitation invité avant que la page de codes hypercall puisse être activée (voir Établissement de l’interface Hypercall (x86/x64)). Si ce registre est ensuite supprimé, la page de codes hypercall est désactivée.

Sur ARM64, une valeur non nulle doit être écrite dans l’ID de système d’exploitation invité MSR avant que les codes hypercall puissent être appelés. L’exception est l’hypercallSetVpRegisters/HvCallGetVpRegisters . Pour plus d’informations, consultez leur documentation respective.

#define HV_X64_MSR_GUEST_OS_ID 0x40000000
#define HvRegisterGuestOsId 0x00090002

Sur ARM64, seul HvRegisterGuestOsId est pris en charge, qui doit être écrit à l’aide de l’hypercall HvCallSetVpRegisters sur le processeur de démarrage.

Identité du système d’exploitation invité pour les systèmes d’exploitation propriétaires

Voici l’encodage recommandé pour ce MSR. Certains champs peuvent ne pas s’appliquer à certains systèmes d’exploitation invités.

Bits Terrain Descriptif
15:0 Numéro de build Indique le numéro de build du système d’exploitation
23:16 Service Version Indique la version du service (par exemple, le numéro « Service Pack »)
31:24 Version mineure Indique la version mineure du système d’exploitation
39:32 Version majeure Indique la version principale du système d’exploitation
47:40 ID du système d’exploitation Indique la variante du système d’exploitation. L’encodage est unique pour le fournisseur. Les systèmes d’exploitation Microsoft sont encodés comme suit : 0=Undefined, 1=MS-DOS®, 2=Windows® 3.x, 3=Windows® 9x, 4=Windows® NT (et dérivés), 5=Windows® CE
62:48 ID du fournisseur Indique le fournisseur du système d’exploitation invité. La valeur 0 est réservée. Consultez la liste des fournisseurs ci-dessous.
63 Type de système d’exploitation Indique le type de système d’exploitation. La valeur 0 représente un système d’exploitation propriétaire (source fermée). La valeur 1 représente un système d’exploitation open source.

Les valeurs du fournisseur sont allouées par Microsoft. Pour demander un nouveau fournisseur, envoyez un problème dans le référentiel de documentation de virtualisation GitHub (https://aka.ms/VirtualizationDocumentationIssuesTLFS).

Fournisseur Valeur
Microsoft 0x0001
HPE 0x0002
BlackBerry 0x0003
LANCOM 0x0200

MSR d’identité de système d’exploitation invité pour les systèmes d’exploitation open source

L’encodage suivant est proposé comme conseils pour les fournisseurs de systèmes d’exploitation open source qui ont l’intention de se conformer à cette spécification. Il est suggéré que les systèmes d’exploitation open source adoptent la convention suivante.

Bits Terrain Descriptif
15:0 Numéro de build Informations spécifiques à la distribution (par exemple, numéro de build).
47:16 Version Informations sur la version du noyau en amont.
55:48 ID du système d’exploitation Informations supplémentaires sur le fournisseur
62:56 Type de système d’exploitation Type de système d’exploitation (par exemple, Linux, FreeBSD, etc.). Voir la liste des types de système d’exploitation connus ci-dessous
63 Open source La valeur 1 indique un système d’exploitation open source.

Les valeurs de type de système d’exploitation sont allouées par Microsoft. Pour demander un nouveau type de système d’exploitation, envoyez un problème dans le référentiel de documentation de virtualisation GitHub (https://aka.ms/VirtualizationDocumentationIssuesTLFS).

Type de système d’exploitation Valeur
Linux 0x1
FreeBSD 0x2
Xen 0x3
Illumos 0x4

Établissement de l’interface Hypercall (x86/x64)

Sur x86/x64, les hypercalls sont appelés à l’aide d’un opcode spécial. Étant donné que cet opcode diffère entre les implémentations de virtualisation, il est nécessaire que l’hyperviseur extrait cette différence. Cela s’effectue via une page hypercall spéciale. Cette page est fournie par l’hyperviseur et apparaît dans l’espace GPA de l’invité. L’invité doit spécifier l’emplacement de la page en programmant le MSR Hypercall invité.

#define HV_X64_MSR_HYPERCALL 0x40000001
Bits Descriptif Attributes
63:12 Hypercall GPFN : indique le numéro de page physique invité de la page hypercall Lecture/écriture
11:2 RsvdP. Les bits doivent être ignorés sur les lectures et conservés sur les écritures. Réservé
1 Verrouillé. Indique si le MSR est immuable. Si elle est définie, ce MSR est verrouillé, ce qui empêche le déplacement de la page hypercall. Une fois défini, seule une réinitialisation système peut effacer le bit. Lecture/écriture
0 Activer la page Hypercall Lecture/écriture

La page hypercall peut être placée n’importe où dans l’espace GPA de l’invité, mais doit être alignée sur les pages. Si l’invité tente de déplacer la page hypercall au-delà des limites de l’espace GPA, une erreur #GP se produit lorsque le MSR est écrit.

Ce MSR est un MSR à l’échelle de la partition. En d’autres termes, il est partagé par tous les processeurs virtuels dans la partition. Si un processeur virtuel écrit correctement dans le MSR, un autre processeur virtuel lit la même valeur.

Avant l’activation de la page hypercall, le système d’exploitation invité doit signaler son identité en écrivant sa signature de version dans un MSR distinct (HV_X64_MSR_GUEST_OS_ID). Si aucune identité de système d’exploitation invité n’a été spécifiée, les tentatives d’activation de l’hypercall échouent. Le bit d’activation reste égal à zéro lors de l’écriture d’un bit. En outre, si l’identité du système d’exploitation invité est effacée à zéro une fois la page hypercall activée, elle devient désactivée.

La page hypercall s’affiche sous la forme d’une « superposition » dans l’espace GPA ; autrement dit, elle couvre tout autre élément mappé à la plage GPA. Son contenu est lisible et exécutable par l’invité. Les tentatives d’écriture dans la page hypercall entraînent une exception de protection (#GP). Une fois la page hypercall activée, l’appel d’un hypercall implique simplement un appel au début de la page.

Voici une liste détaillée des étapes impliquées dans l’établissement de la page hypercall :

  1. L’invité lit cpuID feuille 1 et détermine si un hyperviseur est présent en vérifiant le bit 31 du registre ECX.
  2. L’invité lit la feuille UCID 0x40000000 pour déterminer la feuille UCID maximale de l’hyperviseur (retournée dans le registre EAX) et la feuille UCID 0x40000001 pour déterminer la signature d’interface (retournée dans le registre EAX). Il vérifie que la valeur feuille maximale est au moins 0x40000005 et que la signature de l’interface est égale à « Hv#1 ». Cette signature implique que HV_X64_MSR_GUEST_OS_ID, HV_X64_MSR_HYPERCALL et HV_X64_MSR_VP_INDEX sont implémentés.
  3. L’invité écrit son identité de système d’exploitation dans le MSR HV_X64_MSR_GUEST_OS_ID si ce registre est égal à zéro.
  4. L’invité lit le MSR Hypercall (HV_X64_MSR_HYPERCALL).
  5. L’invité vérifie le bit Activer la page Hypercall. Si elle est définie, l’interface est déjà active et les étapes 6 et 7 doivent être omises.
  6. L’invité trouve une page dans son espace GPA, de préférence une page qui n’est pas occupée par la RAM, MMIO, et ainsi de suite. Si la page est occupée, l’invité doit éviter d’utiliser la page sous-jacente à d’autres fins.
  7. L’invité écrit une nouvelle valeur dans le MSR Hypercall (HV_X64_MSR_HYPERCALL) qui inclut l’application GPA de l’étape 6 et définit le bit Activer la page Hypercall pour activer l’interface.
  8. L’invité crée un mappage VA exécutable sur l’objet GPA de la page hypercall.
  9. L’invité consulte la feuille UCID 0x40000003 pour déterminer quelles installations d’hyperviseur sont disponibles. Une fois l’interface établie, l’invité peut lancer un hypercall. Pour ce faire, il remplit les registres par le protocole hypercall et émet un APPEL au début de la page hypercall. L’invité doit supposer que la page hypercall effectue l’équivalent d’un retour proche (0xC3) pour revenir à l’appelant. Par conséquent, l’hypercall doit être appelé avec une pile valide.

Établissement de l’interface Hypercall (ARM64)

Étant donné que ARM64 prend en charge en mode natif l’instruction HVC, l’hyperviseur n’a pas besoin d’une configuration supplémentaire pour activer les hypercalls.

Interface Hypercall étendue

Les hypercalls avec des codes d’appel ci-dessus 0x8000 sont appelés hypercalls étendus. Les hypercalls étendus utilisent la même convention d’appel que les hypercalls normaux et apparaissent identiques du point de vue d’une machine virtuelle invitée. Les hypercalls étendus sont gérés en interne différemment dans l’hyperviseur Hyper-V.

Les fonctionnalités hypercall étendues peuvent être interrogées avec HvExtCallQueryCapabilities.