«« sommaire   

IV.3 – Personnaliser le support du matériel

Christophe BLAESS - janvier 2020

La démarche pour la création d'un système embarqué est généralement la suivante :

L'utilisation de Yocto, telle que nous l'avons vue jusqu'ici, permettra de mener à bien la seconde phase du projet, c'est-à-dire l'implémentation sur le kit de développement du module SOM. En effet la plupart des fabricants de modules proposent un layer pour Yocto regroupant les recettes nécessaires au fonctionnement sur leur kit de développement.

Nous allons nous intéresser dans cette séquence à l'étape ultérieure : le support de périphériques non intégrés dans le kit de développement (par exemple des convertisseurs analogiques-numériques, des capteurs de température, de lumière, de présence…).

Pour supporter un périphérique non présent dans la configuration du kit de développement du SOM, deux étapes sont nécessaires :

Configuration du noyau

Le noyau (kernel) Linux assure la mise à disposition des ressources matérielles pour les applications. C'est lui qui contient les pilotes (drivers) de périphériques qui communiquent avec les équipements autour du processeur. Mais il contient également beaucoup d'autres éléments : systèmes de fichiers, protocoles réseau, gestion de la mémoire, mécanisme d'ordonnancement…

Le noyau comporte des milliers d'options de configuration, que nous pouvons ajuster par l'intermédiaire d'un menu. Les options que l'on modifie le plus souvent en préparant un système embarqué concernent des drivers qu'il faut intégrer dans le noyau. Malheureusement pour notre expérimentation, l'essentiel des drivers susceptibles de nous intéresser sont déjà inclus dans la configuration du noyau fournie par le layer pour Raspberry Pi.

Nous allons donc modifier des options qui ne sont pas directement intéressantes pour nous, mais qui ont des effets visibles depuis la ligne de commande :

Nous allons tout d'abord vérifier que ces options sont bien absentes d'un noyau compilé avec les options par défaut :

Poky (Yocto Project Reference Distro) 3.0.1 mybox ttyS0

mybox login: root
Password: (linux)
root@mybox:~# uname  -a
Linux mybox 4.19.75 #1 SMP Thu Dec 26 11:13:52 UTC 2019 armv7l armv7l armv7l GNU/Linux 

Pas de mot-clé «PREEMPT» dans le résultat de uname. Vérifions les triggers disponibles pour la led :

root@mybox:~# cat  /sys/class/leds/led0/trigger
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrl 
lock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlig
ht gpio cpu cpu0 cpu1 cpu2 cpu3 default-on input panic mmc1 [mmc0] rfkill-any rfkill-none rfkill0 
root@mybox:~# 

Le trigger sélectionné est encadré par des crochets ; c'est «mmc0». Dans la liste des triggers disponibles, aucune trace de «activity».

La configuration du kernel ressemble un peu à celle de Busybox que nous avons vue dans la séquence précédente (plus exactement Busybox utilise le mécanisme de configuration mis au point pour le noyau Linux). Nous appelons bitbake avec la même option «-c menuconfig» que précédemment. Toutefois le nom de la recette est particulier : comme nous ne connaissons pas le nom exact de la recette gérant le kernel, et que cela peut varier en fonction de la machine cible, de la version de Yocto, etc. on utilise un nom générique «virtual/kernel» qui sera traduit par bitbake :

[build-rpi]$ bitbake  -c menuconfig  virtual/kernel

Une nouvelle fenêtre (ou un nouvel onglet de terminal) s'ouvre. Je vous encourage à parcourir quelques menus pour vous familiariser avec la configuration du kernel Linux, notamment le menu «Device Drivers». Pour avoir un peu d'explication sur une option, on peut appuyer sur «?».

Deux options ont retenu notre attention.

La première est dans le menu «General Setup» (figure IV.4-1)…

… sous-menu «Preemption Model» (figure IV.4-2)…

… sélectionner l'option «Preemptible Kernel (Low-Latency Desktop)» (figure IV.4-3).

La seconde option se trouve en ouvrant le menu «Device Drivers» (figure IV.4-4)…

… en descendant jusqu'au sous-menu «LED Support» (figure IV.4-5)…

… puis le sous-menu «LED Trigger Support» (figure IV.4-6)…

… il faut activer (en pressant «Y») l'option «LED activity Trigger» (figure IV.4-7).

Comme lors de la configuration de Busybox, nous demandons à bitbake de nous préparer un fragment de configuration regroupant les options qui diffèrent des valeurs par défaut :

[build-rpi]$ bitbake  -c diffconfig  virtual/kernel
Config fragment has been dumped into:
 /home/cpb/Yocto-lab/build-rpi/tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/1_4.19.75+gitAUTOINC+642e12d892-r0/fragment.cfg 
NOTE: Tasks Summary: Attempted 235 tasks of which 234 didn't need to be rerun and all succeeded.

Et nous appelons recipetool pour qu'il crée l'extension de recette nécessaire en intégrant le fragment dont nous lui fournissons le chemin.

[build-rpi]$ recipetool  appendsrcfile  -w  ../meta-my-layer/  virtual/kernel  tmp/work/raspberrypi4-poky-linux-gnueabi/linux-raspberrypi/1_4.19.75+gitAUTOINC+642e12d892-r0/fragment.cfg 

À l'issue de cette opération, nous voyons qu'un nouveau sous-répertoire «recipes-kernel» est apparu dans notre layer et qu'il contient une extension de recette pour le noyau :

[build-rpi]$ ls  ../meta-my-layer/
conf  COPYING.MIT  README  recipes-core  recipes-custom  recipes-example  recipes-kernel  recipes-support 
[build-rpi]$ ls  ../meta-my-layer/recipes-kernel/
linux
[build-rpi]$ ls  ../meta-my-layer/recipes-kernel/linux/
linux-raspberrypi  linux-raspberrypi_%.bbappend
[build-rpi]$ ls  ../meta-my-layer/recipes-kernel/linux/linux-raspberrypi
fragment.cfg
[build-rpi]$ 

Avant de relancer un build de l'image nous forçons l'effacement de la recette «virtual/kernel» pour éviter les warnings de bitbake :

[build-rpi]$ bitbake  -c  clean  virtual/kernel 
 [...]
[build-rpi]$ bitbake  my-image
 [...]

Une fois la compilation terminée, et l'image installée sur le Raspberry Pi, nous allons vérifier le résultat de nos modifications :

Poky (Yocto Project Reference Distro) 3.0.1 mybox ttyS0

mybox login: root
Password: (linux)
root@mybox:~# uname  -a
Linux mybox 4.19.75 #1 SMP PREEMPT Sun Jan 5 09:17:11 UTC 2020 armv7l armv7l armv7l GNU/Linux 

Notre noyau est bien marqué comme préemptible. Voyons la configuration de la led verte, et les triggers disponibles :

root@mybox:~# cat /sys/class/leds/led0/trigger 
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrl
lock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlig
ht gpio cpu cpu0 cpu1 cpu2 cpu3 activity default-on input panic mmc1 [mmc0] rfkill-any rfkill-none rfkill0

Le trigger «activity» est apparu. C'est toujours «mmc0» qui est sélectionné par défaut, mais nous pouvons en changer :

root@mybox:~# echo  activity  >  /sys/class/leds/led0/trigger
root@mybox:~# cat  /sys/class/leds/led0/trigger
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrl
lock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlig
ht gpio cpu cpu0 cpu1 cpu2 cpu3 [activity] default-on input panic mmc1 mmc0 rfkill-any rfkill-none rfkill0

La led verte clignote avec un bref flash toutes les secondes, le processeur est très faiblement chargé. Lançons une petite boucle active :

root@mybox:~# while  true  ;  do  :  ;  done

Instantanément, la led clignote plus intensément. Elle revient à son motif initial dès que l'on presse Contrôle-C. Si on lance plusieurs instances de cette boucle en parallèle (en faisant suivre le «done» d'un «&»), la led est quasiment allumée en permanence.

Nous voyons que les modifications de configuration que nous avons apportées au noyau Linux sont bien visibles sur notre cible. Dans un projet réel, il s'agit le plus souvent d'activer des drivers, des protocoles réseau, etc. qui ne le sont pas dans la configuration par défaut.

Modification du Device Tree

Lorsque le noyau démarre, il n'a que très peu d'informations sur le système sur lequel il s'exécute. Il doit déterminer de nombreux paramètres concernant le processeur (nombre de cœurs et fréquence, type et adresse du contrôleur d'interruption, nombre et adresses des timers, etc.), la mémoire (quantité disponible, adresses, etc.), les bus de communication avec les périphériques, les périphériques effectivement présents…

Sur un PC par exemple, l'ensemble de ces informations est maintenu par le BIOS que l'on peut partiellement configurer avec le menu Setup à la mise sous tension.

Sur l'architecture ARM — largement majoritaire pour les systèmes embarqués — il n'y a pas de BIOS et les paramétrage doit être explicitement fourni au noyau. C'est le rôle du Device Tree<.

Le Device Tree pour un système donné est renseigné manuellement dans un fichier avec l'extension «.dts» (Device Tree Source). Celui-ci peut hériter de paramètres génériques (concernant par exemple la famille de System On Chip) se trouvant dans des fichiers d'extention «.dtsi» (Device Tree Source Include).

Les fichiers «.dts» et «.dtsi» sont livrés avec le noyau Linux pour un grand nombre de plateformes (dont les Raspberry Pi).

On utilise ensuite un outil nommé «dtc» (Device Tree Compiler) pour obtenir une représentation binaire du Device Tree prête à être interprétée par le noyau. Cette image est inscrite dans un fichier d'extension «.dtb» (Device Tree Blob) qui sera chargé en mémoire par le bootloader et transmis ainsi au kernel.

Pour nous donner un but réaliste, nous allons ajouter un petit capteur de température qui communique avec le processeur par l'intermédiaire du bus i2c. J'ai choisi un capteur MCP9808 monté sur un module simple à connecter comme on le voit sur la figure IV.3-8.

Ce petit module peut facilement être installé sur un plaquette d'essai. J'ai utilisé une plaquette que nous employons en session de formation (le «RPI 5» sur la la figure IV.3-9 est le numéro de la platine d'essai, pas celui d'un Raspberry Pi du futur !)

Sur certains bus (PCI, USB…) il est possible d'énumérer et d'identifier les périphériques présents. Sur d'autres bus (SPI, i2c…) ce n'est pas possible et il faut indiquer au noyau quels périphériques sont connectés, ainsi que leurs adresses de communication. On réalise traditionnellement cela dans le Device Tree.

Pendant la phase de prototypage avec un montage volant de ce type, on peut généralement se dispenser de modifier le Device Tree et agir sur les entrées bind dans l'arborescence /sys/. Nous allons imaginer que nous sommes à l'étape de validation d'une carte porteuse et que l'on souhaite ajuster précisément le Device Tree du module SOM.

Les connexions sont réalisées comme suit :

Les points importants pour le support dans le noyau sont :

Patch sur les sources du noyau

Nous allons faire un patch sur le noyau Linux en modifiant l'extrait de Device Tree concerné. Pour cela nous devons vérifier quelle version du noyau est compilée par Poky. La commande «uname -a» nous a déjà indiqué 4.19.75.

En cherchant dans le layer «meta-raspberrypi», nous trouvons une recette meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi_4.19.bb contenant :

LINUX_VERSION ?= "4.19.75"
LINUX_RPI_BRANCH ?= "rpi-4.19.y"

SRCREV = "642e12d892e694214e387208ebd9feb4a654d287"

require linux-raspberrypi_4.19.inc

Cette recette inclut le fichier meta-raspberrypi/recipes-kernel/linux/linux-raspberrypi_4.19.inc dont voici le contenu :

FILESEXTRAPATHS_prepend := "${THISDIR}/linux-raspberrypi:"

SRC_URI = " \
    git://github.com/raspberrypi/linux.git;branch=${LINUX_RPI_BRANCH} \
    "
SRC_URI_append_raspberrypi4-64 = " file://rpi4-64-kernel-misc.cfg"

require linux-raspberrypi.inc

LIC_FILES_CHKSUM = "file://COPYING;md5=bbea815ee2795b2f4230826c0c6b8814"

KERNEL_EXTRA_ARGS_append_rpi = " DTC_FLAGS='-@ -H epapr'"

Voici les éléments qui nous intéressent :

Nous pouvons donc, dans un répertoire de travail, télécharger ces (volumineuses) sources et extraire le numéro de commit qui nous intéresse :

[build-rpi]$ git  clone  git://github.com/raspberrypi/linux.git  -b  rpi-4.19.y 
Clonage dans 'linux'...
remote: Enumerating objects: 7604610, done.
remote: Total 7604610 (delta 0), reused 0 (delta 0), pack-reused 7604610
Réception d'objets: 100% (7604610/7604610), 2.22 GiB | 23.70 MiB/s, fait.
Résolution des deltas: 100% (6352750/6352750), fait.
Extraction des fichiers: 100% (62358/62358), fait.
[build-rpi]$ cd  linux/
[linux]$ git  checkout  642e12d8
Extraction des fichiers: 100% (2567/2567), fait.
Note : extraction de '642e12d8'.
  [...]

Nous avons les sources du noyau. Les fichiers sources de Device Tree se trouvent dans le sous-répertoire arch/arm/boot/dts/. Celui du Raspberry Pi 4 est nommé bcm2711-rpi-4-b.dtsbcm2711» faisant référence au System On Chip Broadcom 2711 utilisé dans cette version du Raspberry Pi).

En examinant le contenu du fichier, on trouve la section

  [...]
&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <100000>;
}
  [...]

J'édite alors le fichier pour ajouter les lignes suivantes :

[linux]$ nano  arch/arm/boot/dts/bcm2711-rpi-4-b.dts
  [...]
&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_pins>;
        clock-frequency = <100000>;

        status = "okay";
        temp@18 {
                compatible = "jedec,jc-42.4-temp";
                reg = <0x18>;
                #address-cells = <0x1>;
                #size-cells = <0x0>;
        };
};

Notre propos n'est pas ici de détailler le contenu du Device Tree mais de voir comment intégrer nos modifications au build de Yocto. Pour produire un patch, le plus simple est de demander à git de s'en charger :

[linux]$ git  diff  >  ../001-add-i2c-device-in-dts.patch 
[linux]$ cd  ..

Je peux alors l'outil recipetool comme nous l'avions fait dans la séquence II.3 :

[build-rpi]$ recipetool  appendsrcfile  ../meta-my-layer/  virtual/kernel  001-add-i2c-device-in-dts.patch 

Il me reste à regénérer mon image (après avoir effacé le noyau précédemment compilé pour éviter les warnings) et à la charger sur le Raspberry Pi :

[build-rpi]$ bitbake  -c  clean  virtual/kernel 
[build-rpi]$ bitbake  my-image

Si tout se passe bien, je dois avoir dès le démarrage une nouvelle entrée dans le sous-répertoire «hwmon/» de /sys/class :

Poky (Yocto Project Reference Distro) 3.0.1 mybox ttyS0

mybox login: root
Password: (linux)
root@mybox:~# ls  /sys/class/hwmon/
hwmon0  hwmon1

En effet, normalement le Raspberry Pi 4 n'a qu'une entrée hwmon0, dont le contenu est le suivant :

root@mybox:~# ls  /sys/class/hwmon/hwmon0/
device  in0_lcrit_alarm  name  power  subsystem  uevent

Voyons le contenu de la seconde entrée :

root@mybox:~# ls  /sys/class/hwmon/hwmon1/
device  of_node  subsystem   temp1_crit_alarm  temp1_input  temp1_max_alarm  temp1_min        uevent 
name    power    temp1_crit  temp1_crit_hyst   temp1_max    temp1_max_hyst   temp1_min_alarm

Ceci est typique des capteurs de températures servant à surveiller des seuils. Voyons la température actuelle (en millidegrés Celsius) :

root@mybox:~# cat  /sys/class/hwmon/hwmon1/temp1_input 
24750

24,75 °C, il fait vraiment trop chaud dans mon bureau ! Je pose le doigt quelques secondes sur le boîtier du capteur :

root@mybox:~# cat  /sys/class/hwmon/hwmon1/temp1_input 
29062

Je retire mon doigt et la température descend doucement :

root@mybox:~# cat  /sys/class/hwmon/hwmon1/temp1_input 
26250
root@mybox:~# cat  /sys/class/hwmon/hwmon1/temp1_input
25687
root@mybox:~# cat  /sys/class/hwmon/hwmon1/temp1_input
25000
root@mybox:~# 

Conclusion

Cette séquence nous aura permis d'intervenir dans deux points critiques de l'image Yocto : le noyau Linux et le Device Tree. Aujourd'hui c'est une part importante du travail nécessaire pour supporter une nouvelle carte, celle qui concerne la communication avec le hardware.

Nous avons vu au long de ce cours en ligne de nombreux aspects de l'utilisation de Yocto pour produire une image au contenu parfaitement maitrisé.

J'espère que ces documents vous seront utiles. Les lecteurs souhaitant bénéficier d'un support technique pour leur projet, d'une formation spécifique (dans leurs locaux, adaptée à leur matériel personnel, etc.) ou qui préfèrent au contraire suivre un cours classique en format inter-entreprise pourront me retrouver chez Logilin.

Ce document est placé sous licence Creative Common CC-by-nc. Vous pouvez copier son contenu et le réemployer à votre gré pour une utilisation non-commerciale. Vous devez en outre mentionner sa provenance.

«« sommaire