Introduction
Nous avons découvert l'hyperviseur Xvisor dans un article précédent sur le blog : Xvisor - première mise en oeuvre. Nous y avons vu comment mettre en œuvre Xvisor sur une Raspberry Pi, et dans une machine virtuelle.
Pour comprendre cet article, le lecteur doit avoir déjà manipulé Xvisor ou au moins l'article précédent mentionné ci-dessus.
Xvisor est disponible sur de nombreuses plates-formes (Raspberry pi, i.MX, …). Cependant, il ne dispose pas de tous les drivers pour ces cibles. Or il est plus agréable de tester nos manipulations concrètement en jouant avec des entrées / sorties par exemple.
Nous proposons donc d'étudier le fonctionnement des drivers / émulateurs sous Xvisor puis de développer un driver GPIO pour Xvisor sur une plateforme Raspberry Pi 3.
Cet article est organisé de la façon suivante :
- Architecture de Xvisor: Nous rappelons l'architecture de Xvisor; les modules, le device tree, les drivers;
- Environnement de développement: Nous posons un environnement de développement; une Raspberry Pi 3 avec un serveur NFS;
- Nous continuons avec la conception du driver GPIO pour la Raspberry Pi 3 sous Xvisor;
- Nous terminons avec des essais.
Architecture de Xvisor
Nous rappelons qu'un hyperviseur émule une architecture matérielle. Ainsi il est possible de faire tourner Linux (qui est alors un invité) sur une carte Saberlite émulée par Xvisor (qui est alors l'hôte) et qui lui-même tourne sur une Raspberry Pi 3. C'est ce que permettent les couples driver / émulateur au sein de Xvisor. Les drivers offrent une interface d'accès au matériel physique pour Xvisor, et les émulateurs (au sein de Xvisor) émulent le comportement d'un périphérique matériel.
Architecture générale
Xvisor émule des périphériques physiques, qui peuvent être différents des périphériques présents sur la carte. Pour cela Xvisor implémente des couples de drivers / émulateurs (comme décrit précédemment, le driver est l'interface vers le matériel physique et l'émulateur est une interface vers un matériel émulé).
Un driver peut servir plusieurs émulateurs, ainsi Xvisor offre un moyen de distribuer les drivers sur les différents émulateurs. Ce point sera décrit dans le prochain article.
Le flot de contrôle descendant vers le matériel est le suivant :
- L'invité effectue une demande (lecture ou écriture) vers une interface physique;
- Cette demande est captée par un émulateur;
- L'émulateur traite la demande et la transforme en une requête vers le driver approprié si besoin;
- Le driver traite la demande
Le flot de contrôle ascendant est symétrique, le mécanisme de notification du driver vers l'émulateur est différent, dans notre cas, le driver émet une interruption vers l'émulateur.
Notons qu'un émulateur peut très bien émuler le fonctionnement complet d'un périphérique sans avoir besoin de driver ou, au contraire, solliciter plusieurs drivers. L'objectif est tout de même de construire des émulateurs simples pour maximiser l'efficacité du système complet.
Les drivers et émulateurs de Xvisor sont implémentés comme des modules.
Les modules
Un module définit un ensemble de fonctionnalités. Les modules de Xvisor sont fortement inspirés des modules de Linux. Ils sont chargés au démarrage de Xvisor et peuvent être déchargés durant l'exécution. L'activation ou la désactivation du module se fait lors de la configuration / compilation de Xvisor. Actuellement, tous les modules sont chargés statiquement au démarrage. Il est possible de les charger dynamiquement au moyen de la commande "module load".
Un module est constitué des éléments suivants :
- Une structure de données
vmm_module
. Cette structure est compilée dans la section.modtbl
; - Une fonction d'initialisation / chargement;
- Une fonction de déchargement;
La structure vmm_module
contient des informations sur le module ainsi qu'un pointeur de fonction permettant d'initialiser le module et un pointeur de fonction permettant de décharger le module.
La variable de type struct vmm_module
est définie grâce à la macro VMM_DECLARE_MODULE
.
VMM_DECLARE_MODULE(MODULE_DESC, MODULE_AUTHOR, MODULE_LICENSE, MODULE_IPRIORITY, MODULE_INIT, MODULE_EXIT);
Les paramètres de VMM_DECLARE_MODULE
sont également des macros :
MODULE_DESC
: la description du moduleMODULE_AUTHOR
: l'auteur du moduleMODULE_LICENSE
: la licence du moduleMODULE_IPRIORITY
: la priorité du moduleMODULE_INIT
: un pointeur de fonction vers la fonction d'initialisation du moduleMODULE_EXIT
: un pointeur de fonction vers la fonction de déchargement du module
Voici un exemple simple de création de module.
Choisissons de créer un module module_test qui affiche "Hello World"
lors de l'initialisation de Xvisor. Nous le positionnons dans :
drivers/gpio/module_test.c
Le contenu du fichier module_test.c est :
#include <vmm_stdio.h> #include <vmm_modules.h> #include <vmm_devemu.h> #define MODULE_DESC "Test module" #define MODULE_AUTHOR "Je" #define MODULE_LICENSE "GPL" #define MODULE_IPRIORITY 0 #define MODULE_INIT module_test_init #define MODULE_EXIT module_test_exit static int __init module_test_init(void) { vmm_printf("%s: Hello World\n", __func__); return 0; } static void __exit module_test_exit(void) { vmm_printf("%s: Bye World\n", __func__); } VMM_DECLARE_MODULE(MODULE_DESC, MODULE_AUTHOR, MODULE_LICENSE, MODULE_IPRIORITY, MODULE_INIT, MODULE_EXIT);
Il nous faut ensuite déclarer le module dans son environnement.
Le répertoire drivers/gpio
contient les modules et la configuration :
. ├── gpio-mmio.c ├── gpio-mxc.c ├── gpiolib-legacy.c ├── gpiolib-of.c ├── gpiolib.c ├── gpiolib.h ├── module-test.c ├── objects.mk └── openconf.cfg
Nous nous intéressons à notre fichier module_test.c
mais aussi aux fichiers objects.mk
et openconf.cfg
.
Nous choisissons de créer un module statique (built-in), il sera donc chargé au démarrage de Xvisor sans possibilité de le décharger.
Il est possible créer des modules dynamiques, pour cela il faut ajouter le support des modules dynamiques dans la configuration de Xvisor. Il sera alors possible de charger le module avec la commande module load
. Notons que nous n'avons pas encore testé cette fonctionnalité.
Ajoutons le module à objects.mk
:
drivers-objs-$(CONFIG_MODULE_TEST)+= gpio/module-test.o
Ajoutons nos règles à openconf.cfg
:
config CONFIG_MODULE_TEST def_bool y default y
Nous pouvons dès lors tester le module en générant Xvisor pour le modèle de ARMv8 par exemple. Vous trouverez le détail dans l'article précédent.
Lors de l'initialisation de Xvisor, et donc de l'initialisation du module, le message module_test_init: Hello World
est affiché dans la console.
On peut consulter la liste des modules disponibles en utilisant la commande module sous l'invite de commande Xvisor.
Sous la console Xvisor :
XVisor# module help Usage: module help module list module info module unload XVisor# module list -------------------------------------------------------------------------------- Num Name Author License Type -------------------------------------------------------------------------------- ... 15 sp804 Anup Patel GPL built-in 16 sp805 Anup Patel GPL built-in 17 module_test Je GPL built-in 18 bcm2835_mailbox Anup Patel GPL built-in 19 mailbox Anup Patel GPL built-in ...
Cette commande permet, entre autre, de lister les modules (module list
) et de vérifier qu'un module est bien chargé.
Xvisor utilise actuellement deux types de modules : les drivers et les émulateurs. Ces deux types de modules déclarent une structure similaire à vmm_module
et des fonctions pour enregistrer et supprimer ces structures. Le système de chargement de modules ne peut différencier les différents types de modules. Seul l'implémentation du module le permet. En effet, les émulateurs et les drivers sont tous les deux implémentés sous forme de module générique.
Lorsque Xvisor charge un module, il va lire la section .modtbl
du fichier objet. Il va pouvoir ainsi charger la structure présentée dans cette section, puis appeler la fonction d'initialisation pointée par MODULE_INIT
. Cette dernière sert à préparer le module pour qu'il soit prêt à être utilisé.
L'intégration d'un module dans Xvisor est présenté plus loin dans l'article.
L'objet d'un driver est d'offrir une interface vers le périphérique afférent. Ainsi, le driver doit accéder aux ressources physiques du périphérique; configurer le contenu de ses registres, contrôler son comportement. Étant donné la multiplicité des périphériques et leurs multiples configurations, la communauté Linux a développé depuis quelques années un concept appelé Device Tree.
Le Device Tree
Le Device Tree est la configuration du logiciel vers les ressources matérielles de la cible (registres des périphériques, mémoire, fréquences, interruptions, exceptions, etc.), il est chargé par le noyau au démarrage.
Prenons le cas de notre driver GPIO. Celui-ci va interagir avec des ressources de la cible matérielle. Les caractéristiques de la cible sont décrites dans un Device Tree, et le module implémentant le driver viendra piocher les ressources dont il a besoin dans ce Device Tree, telles que les adresses des registres, la constitution des registres bit par bit, la configuration des périphériques (vitesses d'horloge, forme des signaux, codage, etc.). Par ailleurs les émulateurs sont accompagnés d'un Device Tree car les drivers des invités doivent également avoir le moyen de connaître les caractéristiques de la cible émulée. Il y a donc deux niveaux de Device Tree, l'un entre la cible matérielle et Xvisor, l'autre entre Xvisor et les invités.
Le Device Tree est une structure de données arborescente sous forme de nœuds et propriétés. Une propriété est un couple clé-valeur. Un nœud peut contenir des propriétés, d'autres nœuds ou les deux.
Par exemple (copié de cette page) :
/dts-v1/; / { node1 { a-string-property = "A string"; a-string-list-property = "first string", "second string"; // L'écriture hexadécimale est implicite pour les tableaux de bytes. // Il n'est pas nécessaire d'utiliser le préfixe 0x a-byte-data-property = [01 23 34 56]; child-node1 { first-child-property; second-child-property = <1>; a-string-property = "Hello, world"; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; /* Chaque nombre est un uint32 */ child-node1 { }; }; };
Les drivers et émulateurs
Les drivers et émulateurs sont des modules, par exemple la fonction d’initialisation du driver GPIO est la suivante :
static int __init bcm2835_pinctrl_init(void) { return vmm_devdrv_register_driver(&bcm2835_pinctrl_driver); }
vmm_devdrv_register_driver
est une fonction définie par Xvisor qui permet d’enregistrer le driver grâce à une structure de donnée similaire à celle définissant un module.
static struct platform_driver bcm2835_pinctrl_driver = { .probe = bcm2835_pinctrl_probe, .remove = bcm2835_pinctrl_remove, .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, .of_match_table = bcm2835_pinctrl_match, }, };
Le membre probe de la structure est un pointeur de fonction vers la fonction de sondage du périphérique. Cette fonction initialise le driver et l’installe si il peut être installé. Un driver ne peut être installé que si la propriété compatible du device tree (figure 7) est incluse dans of_match_table et que le driver n’est pas déjà présent.
Les émulateurs font appel à une fonction d’enregistrement similaire qui enregistre une structure de donnée définie pour les émulateurs.
Par exemple, la fonction d’initialisation de l’émulateur gpio_forward est la suivante :
static int __init gpio_forward_emulator_init(void) { return vmm_devemu_register_emulator(&gpio_forward_emulator); }
gpio_forward_emulator :
static struct vmm_emulator gpio_forward_emulator = { .name = "gpio-forward", .match_table = gpio_forward_emuid_table, .endian = VMM_DEVEMU_NATIVE_ENDIAN, .probe = gpio_forward_emulator_probe, .reset = gpio_forward_emulator_reset, .sync = gpio_forward_emulator_sync, .remove = gpio_forward_emulator_remove, }
Le membre match_table
sert au même but que of_match_table
pour la structure platform_driver
.
Le driver GPIO
Sous Xvisor, les drivers et émulateurs sont des modules, le lien entre ces modules est concrétisé dans le Device Tree.
La fonction d’initialisation du driver GPIO est la suivante :
static int __init bcm2835_pinctrl_init(void) { return vmm_devdrv_register_driver(&bcm2835_pinctrl_driver); }
vmm_devdrv_register_driver
est une fonction définie par Xvisor qui permet d’enregistrer le driver grâce à une structure de donnée similaire à celle définissant un module :
static struct platform_driver bcm2835_pinctrl_driver = { .probe = bcm2835_pinctrl_probe, .remove = bcm2835_pinctrl_remove, .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, .of_match_table = bcm2835_pinctrl_match, }, };
Le membre probe de la structure est un pointeur de fonction vers la fonction de sondage du périphérique. Cette fonction initialise le driver et l’installe si il peut être installé. Un driver ne peut être installé que si la propriété compatible du Device Tree (figure 7) est incluse dans of_match_table et que le driver n’est pas déjà présent.
Les émulateurs font appel à une fonction d’enregistrement similaire qui enregistre une structure de donnée définie pour les émulateurs.
Par exemple, la fonction d’initialisation de l’émulateur gpio_forward est la suivante :
static int __init gpio_forward_emulator_init(void) { return vmm_devemu_register_emulator(&gpio_forward_emulator); }
gpio_forward_emulator :
static struct vmm_emulator gpio_forward_emulator = { .name = "gpio-forward", .match_table = gpio_forward_emuid_table, .endian = VMM_DEVEMU_NATIVE_ENDIAN, .probe = gpio_forward_emulator_probe, .reset = gpio_forward_emulator_reset, .sync = gpio_forward_emulator_sync, .remove = gpio_forward_emulator_remove, }
Le membre match_table
sert au même but que of_match_table
pour la structure platform_driver
.
Enfin, une partie du Device Tree liant les deux modules est :
gpio0 { manifest_type = "virtual"; guest_physical_addr = <0x10013000>; gpio-slave { device_type = "gpio-slave"; out_gpio = <1 2 3>; }; };
Les drivers et les émulateurs sont chargés s’il y a un nœud correspondant au périphérique (physique ou simulé) dans le Device Tree de la carte ou de l’invité. La correspondance entre le driver (ou l’émulateur) et le nœud du Device Tree est fait grâce à la propriété compatible.
Les drivers et les émulateurs ont, dans les structures de données les représentants, un attribut match_table ou of_match_table représentant la table de correspondance. Cet attribut sert à être comparé à la propriété compatible du nœud du Device Tree. Le driver ou l’émulateur n’est chargé que si la propriété est présente dans la table de correspondance. Nous précisons l'utilisation de ces attributs plus loin dans l'article.
Conception du driver GPIO
Nous avons choisi de partir d'un driver existant sous Linux et fonctionnant déjà sur Raspberry PI 3.
Préparation de Xvisor
Pour pouvoir manipuler le driver, il faut tout d’abord l’intégrer à la chaîne de compilation de Xvisor.
Dans le dossier drivers/gpio de Xvisor, il y a deux fichiers : object.mk et openconf.cfg.
openconf.cfg définit les variables de compilation. Ces variables servent à déterminer les fichiers à compiler ou sont utilisés par le pré-processeur C pour activer ou désactiver des parties de code.
object.mk déclare les variables requises pour ajouter un fichier au code à compiler.
Pour que le driver soit compilé dans Xvisor, il faut activer les variables requises pour sa compilation. Il faut ajouter ces variables dans le fichier defconfig (dans arch/<architecture>/config) utilisé (par exemple bcm2835_v8_defconfig pour la Raspberry Pi 3).
Le fichier openconf.cfg pour le driver GPIO pour la Raspberry Pi 3 est le suivant :
config CONFIG_PINCTRL_BCM2835 bool "BCM2835 pinctrl driver" depends on CONFIG_PINCTRL && CONFIG_GPIOLIB select CONFIG_OF_GPIO select CONFIG_GPIO_GENERIC select CONFIG_GPIOLIB_IRQCHIP select CONFIG_PINMUX select CONFIG_PINCONF help Say Y here to enable the BCM2835 pinctrl driver
Ce fichier définit une variable CONFIG_PINCTRL_BCM2835
qui requiert, pour être activée, d’avoir activé CONFIG_PINCTRL
et CONFIG_GPIOLIB
. De plus, lorsque cette variable est activée, les variables CONFIG_OF_GPIO
, CONFIG_GPIO_GENERIC
, CONFIG_GPIOLIB_IRQCHIP
, CONFIG_PINMUX
et CONFIG_PINCONF
sont activées.
Le fichier object.mk, pour le même driver est le suivant:
drivers-objs-\$(CONFIG_PINCTRL_BCM2835)+= pinctrl/bcm/pinctrl-bcm2835.o
Ce fichier indique à la chaîne de compilation que pour ajouter le fichier objet du driver, il faut que la variable CONFIG_PINCTRL_BCM2835
soit activée.
Pour compiler le driver GPIO pour Raspberry Pi 3 il faut donc rajouter les variables CONFIG_PINCTRL_BCM2835, CONFIG_PINCTRL et CONFIG_GPIOLIB dans bcm2835_v8_defconfig. On peut aussi
utiliser make menuconfig qui fournit une interface graphique pour la configuration de la compilation.
Extrait de bcm2835_v8_defconfig:
... CONFIG_GPIOLIB=y CONFIG_PINCTRL=y CONFIG_PINCTRL_BCM2835=y ...
Environnement de développement
La Raspberry Pi 3
Préparation de la carte SD
Nous avons déjà vu, dans l'article précédent, comment préparer une carte SD en vue d'accueillir les images générées lors de la construction de Xvisor. La démarche adoptée dans cet article est plus souple, mais nécessite un peu de configuration au préalable. Elle consistera à démarrer la Raspberry Pi 3 avec une version de U-Boot pouvant démarrer sur un réseau avec NFS. Nous rappelons ce que doit contenir une carte SD pour lancer Xvisor sur une Raspberry Pi (1,2,3) :
-
- L'image de U-Boot : u-boot.bin sur la partition boot;
-
- L'image de Xvisor : uvmm.bin sur la partition data;
-
- le fichier DTB qui décrit les paramètres bas-niveau des invités, par exemple : one_guest_virt-v8.dtb (un invité sur une architecture ARMv8) sur la partition data;;
-
- L'image de Xvisor contenant les invités : udisk.img sur la partition data;.
Nous commençons par préparer une carte SD prête à recevoir l'image Xvisor. Si vous avez déjà installé une Raspbian ou toute autre distribution permettant de démarrer votre Rapsberry Pi 3, alors ce qui suit n'est pas utile. Dans l'autre cas voici une démarche simple. On récupère une image de Raspbian, puis on la transfère en mode bloc sur la carte SD. Tout est clairement expliqué sur le site de Raspberry: https://www.raspberrypi.org/documentation/installation/installing-images/linux.md La carte est constituée de 2 partitions, une partition de boot au format FAT, et une partition data au format ext4. Ces deux partitions sont référencées de la façon suivante :
-
- boot : /media/<user>/boot
-
- data : /media/<user>/data
La carte est prête. Notre objectif est d'éviter de copier systématiquement les images sur la partition data de la carte SD, nous allons donc partager notre environnement PC au travers d'une liaison réseau avec la Raspberry Pi. Nous configurons tout d'abord U-Boot.
Création de u-boot.bin
Nous téléchargeons U-Boot puis le compilons:
$ cd $HOME/workspace/xvisor-tree/u-boot $ wget ftp://ftp.denx.de/pub/u-boot/u-boot-2016.09-rc1.tar.bz2 $ tar xvf u-boot-2016.09-rc1.tar.bz2
$ cd $HOME/workspace/xvisor-tree/u-boot/u-boot-2016.09-rc1 $ make rpi_3_defconfig $ make all
Nous obtenons un fichier u-boot.bin qu'il faut copier sur la partition boot de la carte sd.
Insérer cette dernière, puis :
$ cp u-boot.bin /media/<user>/boot
Éditer le fichier config.txt se trouvant sur la partition boot de la carte sd et y ajouter les lignes suivantes :
enable_uart=1 arm_control=0x200 kernel=u-boot.bin
Ces lignes permettent, respectivement, de :
- activer la console série, option "enable_uart";
- modifier le mode de démarrage de la carte sur l'architecture aarch64 (mode 64bits), option "arm_control";
- indiquer le nom de l'exécutable à lancer pour le boot de la carte, ici il s'agit de u-boot.bin, option "kernel".
Il faut démonter le périphérique:
$ umount /dev/<votre media>
Nous avons également construit dans le répertoire tools l'utilitaire mkimage qui nous servira par la suite.
A ce point, nous pouvons d'ores et déjà tester si la carte démarre correctement. Nous utilisons un adaptateur USB-TTL, par exemple https://www.kubii.fr/composants-raspberry-pi/1761-cable-usb-vers-ttl-4-pin-3272496006263.html.
La connexion est simple :
- pin2 : alimentation câble rouge;
- pin6 : masse, câble noir;
- pin8 : TXD, câble blanc;
- pin 10 : RXD, câble vert.
Pour un exemple, on peut consulter https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/overview.
On connecte le câble USB, puis on démarre minicom :
$ minicom -b 115200 -D /dev/ttyUSB0
Dès la mise sous tension de la carte, u-boot attend quelques secondes puis démarre son autoboot. Pour éviter ceci, il suffit d'appuyer sur une touche avant la fin du compte à rebours. Débrancher et reconnecter le câble usb pour redémarrer la carte.
Si vous voyez l'invite de commande U-Boot, alors la carte est fonctionnelle.
Mise en place du réseau NFS
Nous avons une carte SD, avec U-Boot, prête à démarrer. Nous voulons que U-Boot démarre en téléchargeant les images de Xvisor puis les lance à partir d'un serveur (notre machine de développement).
Nous configurons l'environnement NFS en deux étapes; la machine de développement, puis U-Boot.
Configuration du serveur NFS
Notre machine de développement sera le serveur auprès duquel U-Boot viendra télécharger les images pour démarrer Xvisor.
Nous donnons un exemple dans un environnement Linux Ubuntu.
Nous choisissons un répertoire NFS, /srv/nfs
, sur notre machine. Cette dernière possède l'adresse IP 10.5.3.199
(utiliser ip a
ou ifconfig
pour la récupérer).
Nous choisissons l'adresse IP 10.5.3.200
pour la Raspberry Pi 3.
Pour créer un serveur NFS, il faut installer le paquet nfs-kernel-server
(le paquet peut avoir un autre nom sur une autre distribution). Ensuite, on configure le serveur NFS dans le fichier /etc/exports
:
<chemin du répertoire racine NFS> *(rw,no_root_squash,no_all_squash,sync)
Dans notre cas, on crée le fichier /etc/exports
et on y écrit la ligne suivante :
/srv/nfs *(rw,no_root_squash,no_all_squash,sync)
Ensuite il faut lancer ou relancer le serveur. C'est fait en (re)démarrant le daemon :
$ sudo systemctl enable nfs # Relance le daemon au démarrage
$ sudo systemctl start nfs # Lance le daemon
ou, au cas où le daemon est déjà lancé:
$ sudo systemctl enable nfs # Relance le daemon au démarrage
$ sudo systemctl restart nfs # Relance le daemon
Le serveur est prêt, passons à la configuration côté client.
Configuration de U-Boot
Nous créons un fichier boot.cmd
dans lequel on écrit :
setenv ipaddr 10.5.3.200 setenv serverip 10.5.3.199 setenv rootdir /srv/nfs nfs 200000 $rootdir/uvmm.bin # 20 000 nfs 2000000 $rootdir/udisk.img # 2 000 000 nfs 800000 $rootdir/one_guest_virt-v8.dtb # 80 000 bootm 20000 2000000 800000 setenv bootcmd "nfs 200000 $rootdir/uvmm.bin;\ nfs 2000000 $rootdir/udisk.img;\ nfs 800000 $rootdir/one_guest_virt-v8.dtb;\ bootm 20000 2000000 800000"
La commande nfs permet de charger en mémoire un fichier présent dans le répertoire NFS. La syntaxe est nfs <adresse mémoire> <chemin du fichier>
.
La commande bootm sert à indiquer où sont positionnés, dans la mémoire, les fichiers nécessaire au démarrage.
La variable prédéfinie bootcmd définie la ou les commandes à exécuter lors du démarrage.
Ensuite il faut compiler le script boot.cmd
:
mkimage -C none -A arm64 -T script -d "${dir}/boot.cmd" "${dir}/boot.scr"
avec ${dir} l'emplacement du script.
(avec ${dir}
le chemin où est enregistré boot.cmd
).
Pour que u-boot puisse utiliser ce script, il faut le placer dans la partition boot de la carte SD.
Une fois ces deux étapes finies, il suffit de copier les fichiers de Xvisor dans le répertoire NFS et de redémarrer la Raspberry PI 3 pour démarrer sur Xvisor, sans avoir à enlever la carte SD.
Conception du driver GPIO
Architectur
L’architecture des GPIO de Xvisor peut être divisée en trois parties () :
- l’émulateur
- la commande Xvisor; cette commande est un shell à disposition. Ce shell nous permet d'interagir directement avec le driver.
- le driver
[caption id="attachment_4598" align="aligncenter" width="610"] Architecture GPIO[/caption]
La commande gpio de Xvisor communique avec le driver en faisant des appels à gpiolib.
Le driver GPIO est composé de deux parties. gpiolib définit les fonctions de manipulation des GPIO. Ces fonctions sont communes à tous les drivers. Elles font appel aux fonctions définies dans le driver GPIO. Le driver GPIO implémente les fonctions de manipulation pour le composant, par exemple les GPIO de la Raspberry Pi 3.
L’émulateur est composé de l’émulateur GPIO et du framework d’émulation de composants. Le framework d’émulation de composants fourni une interface entre l’émulateur et la gestion des invités. L’émulateur de GPIO communique avec le driver par l’intermédiaire d’interruptions.
Choix du driver de départ
Pour porter un driver il faut tout d’abord choisir sur quel driver s’appuyer. Une solution est d’utiliser le driver Linux pour le composant sur cette plateforme. En effet, Xvisor est fortement inspiré de Linux. Les modifications à apporter au driver seront réduites.
Nous remplaçons le driver "driver/pinctrl/bcm/pinctrl-bcm2835.c" par celui de Raspbian disponible à https://github. om/raspberrypi/linux/blob/rpi-4.9.y/drivers/pin trl/b m/pin trl-b m2835.c
Modification du driver
La première étape du portage d’un driver est l’identification des fonctions et structures de données à modifier.
La mise au point de ces fonctions peut être facilitée en utilisant la macro BUG
Xvisor. Xvisor terminera l'exécution de l'invité sur la fonction attachée à la macro.
On peut utiliser vmm_printf
pour afficher des informations à cet instant.
Par exemple:
#define NOT_IMPLEMENTED vmm_printf("%s not implemented\n", __func__); \ BUG()
Le code applicatif suivant:
void foo() { NOT_IMPLEMENTED; }
affichera :
foo not implemented Bug is in foo() at foo.c:200 Please reset the system
si foo()
est implémenté à la ligne 200 de foo.c
Le travail consiste alors à l'adapter à notre besoin, pour cela je vous invite à analyser les commits disponibles sur https://github.com/avpatel/xvisor-next. Ces derniers sont assez explicites, le gros du travail étant de comprendre l'architecture de Xvisor, savoir quel driver remplacer et de quel driver partir.
Les commits intéressants sont les suivants :
- 4d5633f5a1b848c1f6324fea7343f4086cbef334 : configuration et driver
- 57db8e2bc8a34ba5b49d09775d69d7f7872dfadd : configuration
Pour cela l'outil graphique gitk est plutôt pratique, sinon on utilise le traditionnel git show.
Device Tree
Pour charger le module dans le noyau, il faut le configurer dans le device tree en y ajoutant le nouveau noeud GPIO.
La configuration d'un device tree est assez complexe.
Dans notre cas, nous avons un noeud gpio0 définissant les lignes de gpio en entrée (gpio_in_irq
) et en sortie (gpio_out_irq
).
gpio0 { manifest_type = "virtual"; address_type = "memory"; guest_physical_addr = <0x10013000>; physical_size = <0x1000>; device_type = "gpio"; compatible = "primecell,pl061"; gpio_in_invert = <0 0 0 0 0 0 0 0>; gpio_in_irq = <1400 1401 1402 1403 1404 1405 1406 1407>; gpio_out_irq = <1408 1409 1410 1411 1412 1413 1414 1415>; interrupts = <38>; };
Le commit correspondant au driver GPIO est le suivant :
- 74db889124183876ef0af2a6927e272b68da2c2f : s'intéresser au fichier /arch/arm/board/generic/dts/bcm2837/bcm2837.dtsi
On remarquera, dans ce device tree, l'existance du noeud SERIAL0: uart0. Ce noeud implémente un émulateur de base permettant de tester le driver GPIO, il permet d'utiliser la commande gpio de Xvisor, commande permettant de manipulier le GPIO dans le shell.
Nous n'entrons pas plus dans les détails de la configuration du device tree.
Test du driver
La commande gpio de Xvisor nous sert à faire notre test.
Xvisor # gpio help Usage : gpio help - Displays the help gpio list - Displays the GPIOs gpio set ID {in , out } [1/0] - Set direction and value
Par exemple :
Xvisor # gpio set 1 out 0
Xvisor # gpio set 1 in
Il reste à réaliser un petit montage avec une breadboard, quelques leds et résistances pour visualiser le résultat, et ne pas se tromper de GPIO.
On trouvera plétore de montages sur le net, par exemple https://thepihut.com/blogs/raspberry-pi-tutorials/27968772-turning-on-an-led-with-your-raspberry-pis-gpio-pins
Un montage type est :
Écriture de l'émulateur
Nous avons un driver GPIO disponible qui, pour l'instant, n'est pas utilisable d'un système invité. L'émulateur permet de router et multiplexer les GPIO entre le driver bas-niveau de Xvisor / RPI3 et les drivers des invités.
En réalité, un émulateur est constitué de :
- un émulateur qui simule le périphérique,
- un émulateur-esclave qui sert d'intermédiaire entre le driver et l'émulateur.
En ce qui concerne les GPIO, nous avons déjà 2 émulateurs disponibles, mais incomplets:
- l'émulateur : pl061,
- l'émulateur-esclave : gpio-forward, de type gpio-slave dans le device tree.
L'émulateur pl061 définit des gpiochip. Un gpiochip contient deux lignes de gpio:
- une ligne de GPIO en entrée,
- une ligne de GPIO en sortie.
Dans notre exemple, le noeud de GPIO virtuelle du Device Tree (gpio0) définit une gpiochip possédant les caractéristiques suivantes:
- Des GPIO en entrée; [1400, 1407],
- Des GPIO en sortie; [1408, 1415].
On s'intéresse aux commits suivants :
- cc54ee806a01a4bd665094362f82b772f92f5137 et 4a73bd36b15ac06296203fdc4ed287a35b04d3a8 : gpio-forward; utilisation des descripteurs gpio au lieu des numéros,
- a4804774b6c45b33c30ec0bb5ef20bab04379d37 : gestion des directions in / out, interruption virtuelle,
- 155ce095536c33f874a9944c083bf570938ee853 : ajout d'une API pour communiquer entre VCPU normaux (espace utilisateur invité) et VCPU orphelins (espace noyau Xvisor).
Tester le driver dans un invité
Pour tester le driver complet, il suffira de reprendre la procédure décrite dans le premier article et de le tester sur la Raspberry Pi 3. Attention à bien utiliser la dernière version de Xvisor.
Voici quelques exemples de manipulation à réaliser dans notre invité.
[guest0/uart0] /sys/class/gpio $ echo 506 > export
[guest0/uart0] /sys/class/gpio $ echo out > gpio506/direction
[guest0/uart0] /sys/class/gpio $ echo 0 > gpio506/value
On peut visualiser l'état des GPIO sous Xvisor avec gpio list
, par exemple :
Xvisor# gpio list GPIOs 0-53, platform/gpio@3f200000, pinctrl-bcm2835: gpio-0 (guest0 ) out low irq_no=1024, flags=0x3 gpio-1 (guest0 ) out low irq_no=1025, flags=0x3 gpio-2 (guest0 ) in high irq_no=1026, flags=0x1 gpio-3 (guest0 ) out low irq_no=1027, flags=0x3 gpio-4 (guest0 ) in high irq_no=1028, flags=0x1
On remarquera l'utilisation de la GPIO 506 dans le test ci-dessus. Linux gère les GPIO par paquets, avec les contrôleurs gpiochip.
Voir pour cela le document suivant https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/gpio/sysfs.txt
GPIO controllers have paths like /sys/class/gpio/gpiochip42/ (for the
controller implementing GPIOs starting at #42) and have the following read-only attributes:
/sys/class/gpio/gpiochipN/
"base" ... same as N, the first GPIO managed by this chip
"label" ... provided for diagnostics (not always unique)
"ngpio" ... how many GPIOs this manges (N to N + ngpio - 1)
On retiendra que la gpio numéro 2 du contrôleur gpiochip504 est identifiée par le numéro 506, et elle correspondra, dans notre exmple aux gpio 1402 en entrée et 1410 en sortie.
Conclusion
Cet article nous a permis d'entrer dans le coeur de Xvisor et proposer une démarche pour ajouter un driver (toujours avec l'émulateur qui va avec). Nous pourrions aller beaucoup plus loin en testant le driver GPIO distribué avec plusieurs invités en parallèle.
Dans ces cas, comment gérer les interactions possibles entre invités, comment s'assurer de ne pas allouer le même port à plusieurs invités ?
Si vous voulez vous lancer dans l'écriture d'un driver pour Xvisor et en particulier sur RPI3, vous serez très bien accueillis par l'initiateur de projet. Le projet reste vivace, avec l'initiative toute récente d'un meta Yocto pour Xvisor.
Liens
Branche de développement : https://github.com/avpatel/xvisor-next