Voilà un titre digne d'une publication pour décideur pressé, mais nous parlerons bien de technique (quoi d'autre, sinon ?). Il n'y a pas si longtemps, STMicroelectronics annonçait l'arrivée de la famille de SoC STM32MP1, combinant la puissance de cœurs ARM Cortex-A7 à l'efficacité d'un Cortex-M4, le tout réuni sur une seule puce. La commercialisation récente des « Discovery Kits » et du STM32MP157F-DK2 en particulier est l'occasion rêvée de prendre la bête en main.
Lorsqu'on entend « STM32 », on pense automatiquement « microcontrôleur » ou « MCU », mais cette famille, déjà très importante et diversifiée, s'étend depuis quelque temps déjà bien au-delà de ce que même un STM32H7 (Cortex-M7) est en mesure de fournir. Nous parlons, en effet, ici d'un SoC double-cœur Cortex-A7 bourré de fonctionnalités (HDMI, SPDIF, Ethernet Giga, LCD-TFT, MIPI DSI, GPU Vivante avec OpenGL ES, etc.) avec, en prime, un Cortex-M4 intégré. Là, on ne parle plus de MCU, mais de MPU capable de supporter un OS dit « riche » (en comparaison avec un RTOS comme FreeRTOS, Chibios ou RIOT). Pour prendre en main cette petite merveille, rien de mieux qu'un kit officiel incluant tout le nécessaire : le STM32MP157F-DK2.
Les « Discovery kits » se différencient de la gamme Nucleo en intégrant des éléments nécessaires pour évaluer la solution et faire la démonstration des fonctionnalités spécifiques à un MCU ou MPU. Celui qui nous intéresse ici, construit autour du MPU le plus « haut de gamme » de la famille STM32MP1 à ce moment, inclus :
- un STM32MP157F à 800 MHz ;
- 512 Mo de RAM DDR3L ;
- une interface gigabit Ethernet ;
- deux ports USB-C, dont un USB OTG HS (l'autre est pour l'alimentation) ;
- 4 ports USB 2 Type-A ;
- un codec audio ;
- divers LED et boutons ;
- un port HDMI ;
- un connecteur MIPI DSI ;
- un connecteur GPIO 40 broches « compatible Raspberry Pi » ;
- un connecteur « Arduino Uno V3 » ;
- un débogueur/programmeur ST-LINK/V2-1 intégré ;
- un emplacement microSD (avec une carte 16 Go) ;
- Wi-Fi 802.11b/g/n ;
- Bluetooth Low Energy 4.1 ;
- et un écran TFT 4 pouces, 480x800 MIPI, tactile capacitif.
La comparaison avec une Raspberry Pi 3 ou 4 est tentante, mais malgré la présence de connecteurs incitant à le faire, il s'agirait d'une erreur. Pour à peine plus que le prix d'une Pi 4 8 Go, vous avez là un devkit et non un SBC généraliste. Ce que j’entends par là concerne bien entendu le matériel, mais également et surtout la documentation (un vrai « Reference manual » de quelques 4000 pages), un SDK complet, plusieurs implémentations de référence, des contributions « mainline » aux différents projets concernés et, dans le cas du STM32MP157F, un vrai support Trusted Execution Environment (OP-TEE) et Trusted Firmware-A (TF-A) reposant sur TrustZone.
Remarquez que le STM32MP157F-DK2 n'est pas le seul Discovery Kit disponible pour ce SoC. Il existe également le STM32MP157D-DK1, moins coûteux, sans écran, Wi-Fi, BLE et accélération matérielle AES 128/192/256. Les deux sont en vente chez les détaillants habituels comme Mouser, Farnel, RS ou Digi-Key, mais au moment de la préparation de cet article, seul le DK2 était disponible chez Mouser et ces problèmes d'approvisionnement ne semblent malheureusement pas près d'être réglés (pas courant 2022, a priori).
1. Environnement officiel
Contrairement aux solutions économiques chinoises se résumant généralement à une simple carte et éventuellement un BSP (Board Support Package) sans aucune évolution planifiée, ou à des SBC aussi populaires que généralistes comme les Raspberry Pi, mais n'offrant que des datasheets édulcorées, la famille STM32MP15 dispose d'un écosystème complet, vraiment complet.
ST a en effet créé un environnement complet d'outils et d'éléments logiciels appelés STM32MPU Embedded Software distribution, composé d'applicatifs, d'un système de construction et d'outils de développement à destination des MPU STM32. Cet ensemble, en version v3.1.0 actuellement, comprend :
- une distribution GNU/Linux OpenSTLinux basée sur le framework de construction OpenEmbedded (LTS Dunfell) et intégrant un BSP supportant une chaîne de boot sécurisée (TF-A), un OS sécurisé OP-TEE et un kernel Linux standard en mode non sécurisé. Le tout intégrant plusieurs frameworks applicatifs modernes comme Wayland/Weston, ALSA, GC Nano ou encore Gstreamer ;
- une distribution Buildroot basée sur le BSP d'OpenSTLinux sous la forme d'une extension (BR2_EXTERNAL) pour Buildroot 2021.02, développée via un partenariat entre ST et Bootlin [1] ;
- un paquet STM32CubeMP1 fournissant le support (HAL, CMSIS, pilotes, etc.) pour le coprocesseur ARM Cortex-M4 intégré au SoC. Ainsi qu'une mise à jour de STM32CubeMX permettant à la fois la génération du device tree (Cortex-A7) et la configuration des fonctionnalités du Cortex-M4 (pas de génération de Makefile pour l'instant) ;
- STM32CubeProgrammer supportant la génération de clés cryptographiques et la signature des firmwares, et permettant de programmer le SoC et donc le Discovery Kit via USB ou UART, en plus de gérer l'accès à l'interface STLINK/V2-1 intégré au kit ;
- l'environnement de développement intégré STM32CubeIDE basé sur Eclipse (on aime ou l’on n’aime pas).
Je ne vous cache pas que cet « écosystème » utilise une terminologie un peu déroutante en mélangeant des notions qui, à mon sens, ne vont pas réellement ensemble et c'est l'une des principales difficultés que l'on rencontre dans la masse importante de documentations en ligne (wiki ST). C'est par exemple le cas pour la notion de « Software Packages » qu'on pourrait plus simplement résumer en parlant de « paquets d'éléments pour une tâche donnée » :
- le « Starter Package » est simplement un ensemble d'images binaires à enregistrer sur la microSD, mais via une connexion USB-C et STM32CubeProgrammer, pour obtenir un système utilisable. C'est tout bonnement une version installable du système de démonstration, plus récent que celui sans doute déjà présent sur la microSD au moment où vous recevez le produit ;
- le « Developer Package » est un SDK contenant les éléments nécessaires pour développer à destination d'OpenSTLinux qu'il s'agisse d'applications, de modules noyau, d'un noyau personnalisé, de modifications sur le bootloader U-Boot, etc. Voyez cela comme un simple environnement de cross-développement vous permettant de créer des binaires userspace ou de modifier des composants (kernel, device tree, U-Boot, etc.) du système. On comprend déjà ici que « Starter Package » et « Developer Package » ne se rangent pas vraiment dans la même catégorie ;
- et le « Distribution Package » qui, comme le précise le wiki ST, vous permet « de créer votre propre distribution, votre propre Starter Package et votre propre Developer Package ». Vous êtes perdu ? Moi aussi, et pour cause, le « Distribution Package » n'est rien de plus qu'une installation d'OpenEmbedded-Core avec quelques layers supplémentaires, spécifiques à la plateforme, le tout téléchargeable via repo [2].
Nous avons donc ici trois choses de nature complètement différentes, présentées comme des « packages », alors qu'il s'agit typiquement de trois cas d'usage différents : flasher la carte avec un OS binaire, disposer d'un SDK pour compléter/modifier un système existant et reconstruire tout le système avec le Build System OpenEmbedded. De plus, comme nous le verrons en pratique plus loin, deux de ces « packages » sont le résultat de l'utilisation du troisième. Il y a donc comme un mélange de choux et de carottes, il me semble...
1.1 Starter Package : juste pour commencer
Ce package, ou en d'autres termes, l'image de la distribution OpenSTLinux en version binaire, n'a pour but que de vous permettre de flasher le firmware sur la carte microSD. Mais cette opération ne se fera pas, comme on peut s'y attendre, en copiant simplement l'image sur le support, avec dd par exemple (même si cela reste parfaitement possible). La mise à jour du système se fera par une connexion USB, en DFU (le protocole standardisé Device Firmware Upgrade), via le connecteur USB-C marqué « USB » se trouvant à côté du port HDMI, après avoir passé la carte dans le bon mode à l'aide des micro-interrupteurs BOOT0 et BOOT2 sous PCB (les deux sur OFF).
Bien que DFU soit standard, nous allons utiliser l'outil ST comme recommandé dans la documentation. STM32CubeProgrammer est téléchargeable sur le site officiel [3] en version binaire pour GNU/Linux, Win32, Win64 et macOS, gratuitement, mais cela demandera un enregistrement sur le site pour obtenir le fichier Zip (ceci est également valable pour les autres éléments téléchargeables dans la suite de cet article). Dans le cas d'un environnement GNU/Linux, l'archive, à cette date, se nomme en.stm32cubeprg-lin_v2-9-0_v2.9.0.zip et contient étrangement deux exécutables (un EFL Linux et un PE32 pour Windows) ainsi qu'un répertoire jre. Il s'agit en réalité d'un installeur vous proposant de déployer l'outil, après acceptation des conditions d'utilisation, par défaut dans ~/STMicroelectronics/STM32Cube/STM32CubeProgrammer. Peu importe la destination en réalité, mais vous remarquerez rapidement un certain penchant, chez ST, pour les noms de répertoires et de fichiers à rallonge. Inutile non plus d'ajouter le répertoire d'installation au $PATH, nous pouvons parfaitement appeler l'outil avec un chemin absolu.
Une fois l'outil installé, la carte passée en mode DFU, connectée en USB-C, alimentée via l'autre connecteur USB-C (« PWR_IN ») et réinitialisée (bouton « RESET » à droite du connecteur jack audio), celle-ci se fera connaître du système comme un périphérique en mode DFU :
Pour autoriser les utilisateurs non root à accéder à ce périphérique (ainsi qu'aux autres interfaces ST), un certain nombre de règles udev sont à votre disposition dans le répertoire d'installation de STM32CubeProgrammer, sous Drivers/rules. Vous pouvez les installer en copiant les fichiers dans /etc/udev/rules.d, puis en rafraîchissant avec un udevadm control --reload-rules && udevadm trigger (en root). Dès lors, il vous devient possible d'utiliser STM32CubeProgrammer, que ce soit en version GUI ou en ligne de commandes (préférable) et donc de vérifier que la carte est bien détectée :
Un outil comme dfu-util fonctionnera également, mais n'apportera, tout naturellement, pas d'informations spécifiques sur la plateforme. Nous pouvons à présent télécharger le « Starter Package » depuis le site ST [4] sous la forme d'une archive en.FLASH-stm32mp1-openstlinux-5-10-dunfell-mp1-21-11-17_tar_v3.1.0.xz (je vous avais prévenu pour les noms à rallonge).
À ce stade, il est recommandé d'immédiatement faire preuve de rigueur dans votre future arborescence comme le précise le wiki ST [5], pour clairement distinguer les « packages ». Ceci peut paraître anodin, mais étant donné les désignations utilisées, cela prend rapidement tout son sens. Nous désarchiverons alors le fichier avec tar xfJv pour obtenir un répertoire stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17 contenant lui-même le sous-répertoire images, puis stm32mp1, et trouverons là une arborescence regroupant ce que nous cherchons.
Le support microSD est partitionné en fonction des éléments qui y sont placés (FSBL, FIP, U-Boot, rootfs, etc.) et nous avons une image par partition. Plutôt que de flasher individuellement ces images, l'arborescence met à notre disposition des tables de partitions dans flashlayout_st-image-weston, sous la forme de fichier .tsv. Il nous suffit donc d'utiliser STM32_Programmer_CLI en spécifiant le port (-c) et l'option -w suivie du TSV concerné. Pour flasher la distribution, nous nous plaçons donc dans stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17/images/stm32mp1 et utilisons :
L'opération est relativement longue, étant donné la taille du système et la latence propre à la microSD. L'écran du kit affichera la progression en même temps que votre terminal où est exécutée la commande. De plus, si vous connectez un câble micro-USB au port STLINK, vous aurez accès à une console série (115200 8N1) affichant les mêmes informations de progression.
Si l'écriture échoue avec un message du type :
Assurez-vous d'utiliser un câble USB de qualité, que la source d'alimentation est stable et capable de fournir suffisamment de courant, que l'éventuel hub USB utilisé soit fiable ou passez-vous-en. Procédez à un reset de la carte et renouvelez l'opération. Si vous utilisez une autre carte microSD que celle fournie avec le kit, vérifiez qu'il s'agit bien d'une « Class 10 » (10 Mo/s) ou « UHS Speed Class 1 » (10 Mo/s), car ceci peut également avoir une incidence selon le wiki [6] de ST (alors que la carte livrée avec le DK est une SanDisk classe C 4 Mo/sec).
Au terme de la procédure, se finissant idéalement par le message « Flashing service completed successfully », la plateforme ne redémarrera pas automatiquement. Vous pourrez alors repasser les micro-interrupteurs BOOT0 et BOOT2 sur ON puis appuyer sur « RESET ». Avant cela, si ce n'est pas déjà fait, il est recommandé de brancher un câble micro-USB sur le port STLINK et de vous connecter au port série ayant fait son apparition (/dev/ttyACM0) avec GNU Screen, Minicom, Picocom ou autre. Vous pourrez alors voir toutes les informations de démarrage, du bootloader jusqu'au shell.
Ce premier démarrage prendra un temps plus important que les suivants du fait de la vérification des systèmes de fichiers ainsi que de leur redimensionnement. Soyez patient et au bout de quelques minutes, vous devriez voir apparaître l'application de démonstration sur l'écran LCD et une invite de shell sur la console série :
Très honnêtement, je pense que STM32_Programmer_CLI est une véritable horreur en termes d'ergonomie. L'option --help a elle seule est très représentative, en affichant brutalement quelques 470 lignes d'options utilisables, tout comme le a.out traînant dans bin/, les Readme.txt de zéro octet, la présence de bibliothèques dynamique Windows (DLL) et macOS (DYLIB) dans l'arborescence d'un outil ELF GNU/Linux ou encore une FAQ qui parle d'un manuel doc/UM2336.pdf inexistant, remplacé par une Application note AN2606.pdf sur les bootmodes des MCU STM32. Je comprends parfaitement le souhait de tout centraliser au sein d'un outil unique universel, mais le principe KISS existe précisément pour éviter ce genre de conséquences. Et ST, pour l'amour du ciel, s'il vous plaît, ajoutez une page de manuel (manpage) pour cet outil.
Une solution alternative existe cependant, via le script create_sdcard_from_flashlayout.sh se trouvant dans le bien nommé sous-répertoire scripts/ d’images/stm32mp1/. Celui-ci s'utilise comme STM32_Programmer_CLI en spécifiant un fichier .tsv, mais au lieu d'accéder au matériel via USB-C, il produira une image .raw qu'on pourra transférer ensuite sur le support avec dd. Petit problème cependant, le script utilise sgdisk (outil permettant l'édition de tables de partitions GPT) qui se trouve dans /sbin, un chemin n'étant normalement pas dans le PATH des utilisateurs standards. Comme il est absolument hors de question d'exécuter, en root, un script non vérifié de plus de 880 lignes, mieux vaudra alors le modifier en remplaçant toutes occurrences de sgdisk en /sbin/sgdisk. Ceci nous permettra alors de l'utiliser en toute confiance sans sudo :
Nous obtenons un fichier .raw de quelques 1,5 Go étant l'image de la microSD, ainsi qu'un fichier du même nom, suffixé how_to_update.txt résumant la structure des partitions et les informations permettant de mettre à jour ce qu'elles contiennent.
1.2 Developer Package : un SDK
Le système binaire, autrement appelé « Starter Package », est une simple démonstration technique des fonctionnalités du MPU, du devkit et du support logiciel. Ce n'est pas réellement un environnement destiné à une utilisation courante, et c'est bien normal, la plateforme elle-même ne l'est pas. Nous n'avons pas là une tentative de création d'un ordinateur ARM desktop généraliste, mais bel et bien un système embarqué. Certes, vous pouvez si vous le souhaitez administrer le système, supprimer et ajouter des paquets (APT), personnaliser l'environnement, la configuration, les services, etc., mais ce n'est clairement pas l'objectif.
En cela, développer pour cette plateforme ne consiste donc pas à installer un environnement dédié, comme un IDE et une chaîne de compilation sur celle-ci (ça ne devrait d'ailleurs jamais être le cas), mais bel et bien de développer sur une machine performante à destination de cette cible. Généralement, ceci passe par l'installation d'une chaîne de compilation et la confection d'un environnement dédié comprenant les éléments logiciels indispensables (headers, lib, outils, debugger, etc.). Pour simplifier cela, ST met à disposition un « Developer Package » constitué d'un SDK basé sur celui du projet Yocto, un ensemble de sources des éléments constituant le système (noyau, bootloader, TF-A, OP-TEE), un paquet STM32Cube pour le Cortex-M4 intégré et un IDE spécifique, STM32CubeIDE.
Là encore, cette notion de « package » est, à mon sens, peu appropriée, puisqu'il ne s'agit pas d'un tout monolithique, mais de différents composants téléchargeables et installables individuellement, et pour certains, optionnellement. Le principal composant est bien entendu le SDK, téléchargeable via le site officiel [7] sous la forme d'une archive en.SDK-x86_64-stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17.tar_v3.1.0.xz à décompresser avec tar xfJv et fournissant un répertoire stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17 qui n'est pas le SDK, mais un installeur contenu dans un sous répertoire sdk/ sous la forme d'un imposant script shell qu'il vous suffira d’exécuter. Celui-ci vous demandera simplement de spécifier un répertoire de destination pour l'installation (et de confirmer) :
À ce stade, on peut commencer très sérieusement à se demander si ST n'a pas un problème avec les répertoires en général (et je ne plaisante qu'à moitié). Pourquoi vouloir installer le SDK par défaut dans /usr/local, ce qui nécessiterait les permissions du super-utilisateur, alors que l'environnement de développement est précisément fait pour éviter de torturer inutilement son système comme nous allons le voir plus loin ?
Quoi qu'il en soit, une fois le SDK installé dans le répertoire désigné plus judicieusement par vos soins (et, oui, on peut spécifier un chemin relatif), ces fichiers ainsi que l'archive seront inutiles et peuvent être supprimés.
Comme le précise le message en fin d'installation, le script environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi, se trouvant à la racine de l'arborescence créée, devra être « sourcé » avec « . » ou la commande source suivie de son nom. Dès lors, un certain nombre de variables d'environnement auront été définies ou complétées, parmi lesquelles PATH, CC, LD, OBJCOPY, GDB ou encore CFLAGS et LDFLAGS. Cette évaluation du script fourni est décrite dans la documentation officielle (wiki ST) comme constituant un « démarrage du SDK » (« Starting up the SDK »).
Une fois cet environnement « démarré », on pourra très simplement vérifier son bon fonctionnement en quelques lignes. Avec un peu de C :
Un Makefile basique :
Et quelques commandes :
Ceci, en supposant que vous avez : connecté la carte au réseau Ethernet, ajouté un utilisateur standard et ajouté sa clé dans le ~/.ssh/authorized_keys distant pour faciliter la copie de fichiers avec scp. Ceci est, bien entendu, un exemple simpliste n'utilisant que la libc. Pour des développements plus avancés, vous trouverez dans l'arborescence du SDK un répertoire sysroots contenant deux sous-répertoires :
- cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi/ : le sysroot ou racine système de l'environnement cible est littéralement le contenu du système de fichiers de la cible embarqué. C'est donc là que nous trouvons les bibliothèques et les headers adéquats.
- x86_64-ostl_sdk-linux/ : est, en quelque sorte, l'équivalent pour le système sur lequel nous développons, contenant ce dont nous aurons besoin en cours de développement (comme la libncursesw pour le menuconfig du noyau, le binaire cmake, ninja ou encore pkg-config).
Ces deux éléments découlent directement de l'utilisation du dernier « package » que nous allons voir dans un instant et qui n'est autre que le système de construction OpenEmbedded du projet Yocto. OpenEmbedded, accompagné des layers spécifiques au STM32MP1, permet de générer un système pour le devkit, mais également le SDK correspondant. Le Starter Package et le Developer Package que nous venons de télécharger, d’installer et de mettre en œuvre ont été produits par ce système de construction. Il y a donc une dépendance entre la configuration utilisée pour cette construction et les deux « packages » en question.
Tout l'intérêt du système, et de cette apparente complexité inutile, est précisément de permettre la construction d'un écosystème complet où, sur la base d'une distribution élaborée avec OpenEmbedded, on sera en mesure de construire non seulement un système pour la cible, mais le SDK qui lui est associé pour des développements futurs.
Nous aurions encore d'autres choses à détailler concernant le Developer Package, comme la cross-compilation et la réinstallation d'un noyau Linux ou de U-Boot, mais non seulement ceci est relativement bien détaillé sur le wiki ST [8], mais repose également sur un environnement de développement fonctionnel comme celui que nous venons de mettre en œuvre et sur des procédures qui sont en réalité décrites dans des README.HOW_TO.txt qui font partie intégrante des recettes OpenEmbedded [9] et [10].
1.3 Distribution Package : ce dont nous avons vraiment besoin
Le SDK permet de faire des choses intéressantes, qu'il s'agisse de développement original ou de modification de quelques composants choisis du système. Mais la raison d'être première d'un kit de découverte est de permettre de faire ses premiers pas dans la direction d'un firmware adapté à nos besoins, ce que la documentation ST désigne par « la création d'une distribution ». Bien entendu, la démarche logique pour aller dans ce sens consiste à tout d'abord recréer ce qui existe afin de valider la compréhension du processus.
Le « Distribution Package » est littéralement la source des deux éléments que nous venons de prendre en main. Maîtriser le système de construction, et donc le système de build OpenEmbedded du projet Yocto est ce qui nous permettra de produire un système pour le STM32MP157 (une distribution binaire), ainsi qu'un nouveau SDK lui correspondant.
Terminologie : Yocto, OpenEmbedded, Poky, etc.
Ces trois mots désignent des choses très différentes, mais cependant liées entre elles. Ils sont parfois utilisés indifféremment, tantôt pour désigner un projet, un système, une version ou un environnement de construction, mais ceci une erreur. Chacun de ces termes à une définition claire qu'il est important de connaître. Il en va de même pour d'autres désignations utilisées avec OpenEmbedded :
- Yocto est le projet responsable du développement d'OpenEmbedded et Poky. Ce n'est ni un système, ni une distribution, ni un outil, pas plus que Mozilla est le navigateur Firefox ou que Google est votre client mail. La confusion entre Yocto et OpenEmbedded provient du fait qu'OpenEmbedded existait en tant que projet avant que Yocto ne prenne forme et chapeaute l'ensemble.
- OpenEmbedded (ou « EO ») est le système de construction reposant sur des recettes, des fichiers de configuration et des outils comme bitbake pour exécuter les tâches de construction. OpenEmbedded n'est pas non plus un système ou une distribution. Vous n'exécutez pas OpenEmbedded sur la cible, pas plus que vous exécutez Yocto.
- Poky est l'implémentation de référence, ou distribution de référence, du projet Yocto. Une distribution comprend un système de construction et un ensemble de recettes, regroupées en layers, permettant de produire un système. Poky est un exemple fonctionnel et une base pour la construction d'une distribution personnalisée.
- metadata ou meta désigne n'importe quel fichier configurant le processus de construction par le jeu d'outils accompagnant OpenEmbedded. L'appellation de métadonnées découle du fait qu'il s'agit d'éléments satellites aux briques construisant réellement un système (les sources).
- Les recipes ou recettes sont des meta d'un type spécifique constituées d'instructions permettant de télécharger, de configurer et de compiler les codes sources des composants du système d'exploitation à créer.
- Les layers regroupent des metadata par lots ou ensembles, sous la forme de répertoires contenant des fichiers metadata.
- Un BSP ou Board Support Package est un type particulier de layer définissant la manière dont le système de construction doit agir pour construire un système à destination d'une carte spécifique. Un BSP est généralement créé et maintenu par une entité externe au projet Yocto, le plus souvent un fabricant de processeurs, SoC, ou devkits.
- Une distribution (ou « distro ») est un ensemble basé sur le système de construction OpenEmbedded, contenant un jeu de layers, où nous trouvons le BSP propre à la cible, les différents composants nécessaires au système et les éléments personnalisés du développeur. Il est possible, avec un même ensemble de layers, de fournir plusieurs distros, chacune d'elles en mesure de construire plusieurs images à flasher sur la cible.
Installer et utiliser OpenEmbedded pour construire un système pour la carte STM32MP157F-DK2 peut se faire de plusieurs façons. En effet, le constructeur met à disposition, sur GitHub, un ensemble de layers [11] dont un BSP, mais également la distribution OpenSTLinux que nous avons installée en début d'article. Le contenu du répertoire images/stm32mp1/ du « Starter Package » constitue, en effet, le résultat de la construction de l'image st-image-weston fournie par la distribution openstlinux-weston. Reconstruire cette image et le SDK est une excellente façon de faire connaissance avec OpenEmbedded et les layers fournis par ST.
Pour cela, deux voies sont possibles :
- suivre les indications du constructeur en utilisant les éléments mis à disposition par ST ;
- ou partir sur la base de Poky et ajouter les layers nécessaires (ST ou autres).
Nous allons explorer ces deux approches, la première dans le but de reproduire l'existant et la seconde pour arriver à quelque chose de plus standard et facilement personnalisable. Notez que nous nous permettrons quelques écarts par rapport à la documentation officielle, ne serait-ce que pour rendre l'article un peu plus lisible (oui, je parle des noms de répertoires à nouveau).
1.3.1 Approche ST
Composer sa distribution passe par l'obtention de différents layers, le plus souvent via Git et depuis plusieurs dépôts de sources différents. Pour simplifier les choses dans ce genre de situations, un outil Python est généralement utilisé : Repo [2]. Initialement développé pour les sources AOSP d'Android, Repo est désormais utilisé par bon nombre de projets et c'est également le cas ici. Votre distribution GNU/Linux dispose certainement d'une version de l'outil disponible sous forme de paquet, mais une version à jour pourra facilement être téléchargée et installée n'importe où dans le système (il n'est pas même nécessaire de l'ajouter au PATH).
Récupérer les éléments de construction d'OpenSTLinux sera donc relativement simple :
Notez que Repo utilise Git et que votre configuration courante (~/.gitconfig) sera utilisée, expliquant l'éventuelle présence de votre nom et adresse mail. Ceci fait, nous pouvons synchroniser les fichiers avec :
L'opération est relativement longue, mais vous vous retrouverez, à terme, avec un répertoire layers à l'emplacement courant, contenant :
- openembedded-core : la base ou noyau (core) d'OpenEmbedded qui est le strict minimum pour le système de construction et ne dispose de support que pour des machines émulées ;
- meta-openembedded : également appelé « meta-oe », ce layer contient des metadata supplémentaires, regroupées en layers, fournissant des paquets supplémentaires (Python, Perl, démons réseau, outils de base, etc.) ;
- meta-qt5 : regroupe tous les éléments (hôte et cible) pour supporter des applications utilisant le toolkit Qt ;
- meta-st : un ensemble de layers provenant directement de ST et incluant, entre autres, le BSP pour les différentes cartes STM32MP157 (EV et DK).
Chaque layer contient un répertoire conf lui-même contenant un fichier layer.conf décrivant sa priorité, sa compatibilité, ses dépendances, etc. On trouve également, dans chacun d'eux, un ensemble de recettes (recipes) contenant les instructions permettant de construire des paquets, de la récupération des sources à l'intégration dans le système, en passant par l'application de patchs, les configurations, la compilation, etc.
Normalement, avec OpenEmbedded, on commence par initialiser l'environnement de construction en sourçant le fichier oe-init-build-env (openembedded-core/) en lui passant en argument un répertoire de construction. Dans le cas présent, ST utilise un script « maison » permettant de définir la distribution et la machine pour configurer l'environnement. Il faut donc sourcer (avec source ou .) ce script depuis le répertoire d'où vous avez lancé repo :
Un message (interface dialog) s'affichera éventuellement, vous signalant que vous n'utilisez pas une distribution GNU/Linux officiellement supportée (pour l'heure Ubuntu 16.04, 18.04 ou 20.04) et vous pourrez simplement choisir « IGNORE WARNING » pour poursuivre. J'utilise une Debian 9.11 relativement ancienne et la procédure se déroule sans le moindre problème. Les layers ST incluent des éléments qui sont soumis à une licence spécifique, vous serez donc invité à lire les textes (EULA) et en accepter les termes. Ceci fait, vous vous retrouverez automatiquement placé dans build-openstlinuxweston-stm32mp1/ qui constitue le répertoire de construction.
Le message à l'écran précise que deux images peuvent être construites, st-image-weston qui est précisément ce qui fonctionne actuellement sur votre devkit et st-image-core, une version minimaliste beaucoup plus légère. À votre disposition également, toutes les commandes de configuration et de construction d'OpenEmbedded : bitbake, bitbake-layers, oe-pkgdata-util, devtool, etc. Ceci vient du fait que vous avez modifié votre environnement en sourçant le script et que si vous fermez votre terminal, vous devrez obligatoirement le sourcer à nouveau avant de pouvoir utiliser OpenEmbedded.
Notez que les notions de « DISTRO » et de « MACHINE » sont fondamentales avec OpenEmbedded. « DISTRO » désigne la distribution, mais dans la terminologue Yocto, il est plus exact de parler de politique de configuration, un ensemble de règles qui déterminent comment est composée la construction et le système obtenu. « MACHINE », pour sa part, fait référence aux spécifications et aux configurations provenant du BSP, fournissant une ou plusieurs cibles matérielles utilisables. La modularité fournie par les layers est ici la clé, il est parfaitement possible d'utiliser une même distribution avec plusieurs machines. Poky, par exemple, la distribution de référence pourra parfaitement être utilisée avec notre STM32MP157, une machine x86 émulée avec QEMU, ou n'importe quelle autre cible.
Pour construire une image, tout ce que vous avez à faire ici est d'utiliser la commande bitbake st-image-weston et d’attendre que le processus se termine, ce qui peut prendre un temps considérable. Pour optimiser la construction, vous pouvez éditer le fichier conf/local.conf pour ajouter quelques lignes :
Disposant d'un bi-Xeon E5520, j'ai 8 cœurs et 16 threads à ma disposition, le tout avec 24 Go de RAM. Vous devrez adapter ces valeurs à votre configuration. Même avec une configuration relativement musclée comme celle-ci, il faudra plusieurs heures pour que la construction arrive à terme. Il est donc recommandé de lancer cela en soirée et laisser la machine faire son travail pendant votre sommeil. Vous pourrez ensuite utiliser la commande bitbake -c populate_sdk st-image-weston pour produire le « Developer Package » sous la forme d'un gros script Bash (cf. plus haut).
L'ensemble des éléments téléchargés et/ou produits se trouveront dans build-openstlinuxweston-stm32mp1 et plus précisément dans le sous-répertoire tmp-glibc. OpenEmbedded travaille de façon incrémentale, ce qui signifie qu'en lançant une seconde fois bitbake st-image-weston, seul le nécessaire sera traité. Cela veut également dire qu'en cas d'erreur (comme un site temporairement indisponible), vous pourrez relancer la commande et la construction reprendra là où elle s'est arrêtée.
Les éléments qui vous intéressent se trouveront dans le sous-répertoire tmp-glibc/deploy/ du répertoire de construction. Là, vous trouverez :
- deb/ : l'ensemble des paquets construits. OpenEmbedded peut utiliser plusieurs formats de paquet (configuré dans local.conf), RPM, DEB ou IPK. Dans le cas d'OpenSTLinux, ce sera DEB ;
- images/ : vous retrouvez ici, peu ou prou, ce que vous avez téléchargé en récupérant le « Starter Package », les images des systèmes de fichiers, les manifest contenant la liste des paquets et les fichiers .tsv à utiliser avec STM32_Programmer_CLI (ou create_sdcard_from_flashlayout.sh qui se trouve également là) ;
- licenses/ : l'ensemble des fichiers de licence pour tous les paquets (MIT, GPL, BSD ou autres) ;
- sdk/ : le « Developer Package » correspondant à l'image construite ;
- sources/ : les archives sources des différents éléments accompagnant le « Developer Package » (noyau, U-Boot, TF-A, OP-TEE, etc.).
À ce stade, vous obtenez littéralement exactement la même chose que ce que met à disposition ST, à la nuance près que vous pouvez désormais personnaliser l'ensemble à souhait. Bien entendu, l'image st-image-weston est avant tout une démonstration incluant bien trop d'éléments pour servir de base pour un projet. Une alternative possible est st-image-core, plus basique, mais reposant toujours sur la distribution OpenSTLinux. Une autre approche possible, pour disposer d'une fondation plus standard, consiste à construire sa distribution sur la base de Poky.
1.3.2 Approche générique
Partir d'une distribution déjà très riche est souvent un problème puisqu'on se retrouve à « déconstruire » ce qui a été fait, tout en prenant soin de corriger les problèmes de dépendance au fur et à mesure. C'est un peu comme jouer à Jenga, on enlève des briques en tentant de faire en sorte que rien ne s'écroule. Il est bien plus simple de partir sur une base la plus minimaliste possible et d'ajouter le nécessaire petit à petit. Nous allons donc ici faire précisément cela, en partant de Poky, la distribution de référence.
À ce stade, vous avez compris qu'une distribution est avant tout une accumulation cohérente de layers, nous commençons donc par récupérer le BSP fourni par ST via GitHub :
Notez l'utilisation de git branch afin de savoir exactement sur quelle release nous travaillons, car celle-ci devra être identique pour tous les layers. Dunfell est le nom de code de la release 3.1 du projet Yocto datant de novembre 2021 (3.1.12) et étant une LTS (Long Term Support). À cette date, la dernière stable est Hardknott (3.3) et la future LTS est Kirkstone (3.5). La lecture du README.md du BSP nous indique une dépendance vers openembedded-core ou poky, mais également vers meta-python et meta-oe. Nous récupérons donc ces layers dans la foulée :
Nous disposons de tous les layers nécessaires, mais ceux-ci ne sont pas encore intégrés dans notre build. Mais avant toute chose, nous devons initialiser l'environnement, cette fois en utilisant le script officiel fourni par Poky :
Le répertoire de construction par défaut est build et nous y sommes automatiquement placés. Là, nous pouvons utiliser les commandes d'OpenEmbedded pour, tout d'abord, lister les layers intégrés, puis ajouter ceux dont nous avons besoin :
Une nouvelle occurrence de la commande bitbake-layers show-layers nous montrera que tout a été intégré correctement. Notez que l'ordre d'intégration est important en raison des dépendances et si vous tentez de commencer par meta-st-stm32mp, vous serez rappelé à l'ordre. Remarquez également la colonne priority dans la sortie. Ceci peut s'avérer excessivement important, car si plusieurs recettes identiques sont fournies par des layers, la priorité de ces derniers sera utilisée pour déterminer laquelle devra être utilisée. Ainsi, si vous souhaitez remplacer une recette d'un layer par une version personnalisée, il vous suffit de créer un nouveau layer la contenant et de lui attribuer une valeur de priorité plus importante (voir plus loin dans l'article).
Nous sommes presque prêts pour lancer la construction, mais devons tout d'abord nous pencher sur la configuration en éditant le fichier conf/local.conf. Le premier élément à ajuster est MACHINE qui est actuellement réglé sur l'une des cibles prises en charge nativement par Poky, qemux86-64. Nous pouvons consulter le contenu du répertoire conf/machine/ du BSP pour avoir cette information. Le fichier stm32mp1.conf indique clairement :
Nous ajustons donc MACHINE = “stm32mp1” dans local.conf et en profitons pour ajouter, en fin de fichier, les paramètres que nous avons utilisés précédemment :
ACCEPT_EULA_stm32mp1 est mentionné dans le README.md du BSP et nous permet de signifier l'acceptation des conditions d'utilisation de ST et de ses partenaires. D'autres éléments peuvent être ajustés dans le fichier et les commentaires intégrés détaillent clairement leur signification. DISTRO est réglé sur poky, ce qui nous convient parfaitement, mais PACKAGE_CLASSES peut être changé. Cet élément détermine le format de paquet utilisé et est, par défaut, réglé sur package_rpm. Étant utilisateur de Debian de très longue date, je préfère package_deb, mais c'est là un choix tout personnel.
Le fichier local.conf correspondant à vos attentes et préférences, vous pouvez alors passer à la construction avec :
Cette opération durera un temps conséquent, mais bien moins long que celle construisant l'image officielle. Vous pouvez directement poursuivre avec la construction du SDK via la commande bitbake -c populate_sdk core-image-minimal. On retrouve alors dans tmp/deploy/ une arborescence assez similaire à celle de l'approche précédente et on pourra flasher la carte directement avec :
Bien entendu, le système une fois démarré sur le devkit sera bien différent de celui de la distribution OpenSTLinux. Nous avons là une configuration minimale sans SSH, sans Bash, sans systemd (est-ce vraiment un mal ?), sans mDNS (avahi), sans Wayland, etc. Ce sera à vous d'ajouter des layers, ou des recettes dans un layer personnalisé, pour composer votre système en vous inspirant, éventuellement, de ceux mis à disposition par ST pour leur OpenSTLinux [12].
1.3.3 Petit supplément : ajuster selon vos besoins
Plus haut dans l'article, nous avons soulevé un petit problème concernant le script create_sdcard_from_flashlayout.sh et le fait que la commande sgdisk devrait être référencée avec son chemin complet pour une utilisation par un utilisateur standard. Nous pouvons corriger ce problème et, par la même occasion, voir comment ajouter une recette et un layer personnalisé à notre projet.
Nous nous assurons, premièrement, d'avoir initialisé l'environnement de construction avec oe-init-build-env, puis nous nous plaçons dans le répertoire racine (et non build) pour utiliser la commande :
Un sous-répertoire meta-denis est automatiquement créé et peuplé dans layers/. Nous y trouvons une licence simple de type MIT/BSD, un README, une recette exemple et un répertoire conf contenant le fichier de configuration du layer, layer.conf :
Nous pouvons ajuster ici les dépendances et le niveau de priorité. Étant donné que notre layer a pour but de remplacer l'une des recettes fournies par meta-st-stm32mp, et sdcard-raw-tools en particulier, nous pouvons rendre ce nouveau layer dépendant de stm-st-stm32mp, mais surtout augmenter sa priorité à 7, puisque le fichier meta-st-stm32mp/conf/layer.conf indique 6. Les deux lignes en question deviennent donc :
Il ne nous reste plus ensuite qu'à copier meta-st-stm32mp/recipes-devtools/sdcard-raw-tools dans notre layer. Notez que le chemin complet est important. Vous ne pouvez pas simplement copier sdcard-raw-tools, ce répertoire doit être dans recipes-devtools. Enfin, nous éditons create_sdcard_from_flashlayout.sh pour procéder à nos corrections.
Notre layer est maintenant prêt pour être intégré à notre build. Nous retournons donc dans le répertoire de construction et utilisons :
Nous pouvons vérifier la bonne prise en compte avec un bitbake-layers show-layers ou en listant le contenu de conf/bblayers.conf. Comme tout est correct, nous pouvons lancer une nouvelle construction :
Nos modifications sont minimales, et même si notre configuration a changé, OpenEmbedded a déjà fait la très grande majorité du travail. Seules 13 tâches doivent donc être exécutées pour compléter l'objectif de construction en prenant en compte les dépendances. Et effectivement, nous pouvons constater que tmp/deploy/images/stm32mp1/scripts/ contient notre version modifiée du script.
Bien entendu, notre nouveau layer peut inclure des recettes pour faire bien plus que cela. Nous pouvons y intégrer de quoi produire d'autres images, ajouter nos propres outils et applications, ou tout simplement « surcharger » la configuration pour intégrer davantage de services, d'utilitaires, de bibliothèques, etc.
2. Conclusion très temporaire
J'aime beaucoup le STM32MP157F et son Discovery kit, car il permet pleinement de faire connaissance avec un système de build massivement utilisé dans l'embarqué. Mieux encore, le STM32MP157F intègre énormément de fonctionnalités intéressantes comme le boot sécurisé ou l'utilisation de concert du Cortex-A7 et du Cortex-M4. Deux points que nous creuserons probablement dans de prochains articles.
Bien entendu, tout n'est pas tout rose et, sans vouloir me montrer trop critique, on constate une nette différence de « rigueur » ou de perfectionnisme technique entre des projets comme OpenEmbedded et, par exemple, STM32CubeProgrammer. Cela fonctionne, bien sûr, mais il est relativement difficile de ne pas y voir un aspect un peu « brut de décoffrage », en particulier en contraste avec des choses soignées comme OpenEmbedded.
Cependant, tout ceci n'enlève rien à l'aspect le plus important lorsqu'on se penche sur une telle plateforme : la documentation. Le wiki de ST, une fois les points de terminologie assimilés et la philosophie générale comprise, est une mine d'informations sans fin. Et je ne parle même pas des datasheets, les manuels de référence et autres application notes qui contiennent, par définition, l'information dont on a besoin. Nous sommes loin des documents avares et parcellaires comme ceux de Broadcom ou totalement incompréhensibles (voir inexistant en anglais) de certains fabricants chinois.
Une autre bonne raison, mais tout à fait non technique et toute personnelle, qui me fait apprécier cette plateforme (ainsi que le reste de la famille STM32) est tout simplement le fait que STMicroelectronics soit non seulement une multinationale européenne (franco-italienne avec un siège en Suisse), mais qu'elle fabrique elle-même ses puces. Il a d’ailleurs été annoncé dernièrement un objectif visant à doubler les capacités de production en Europe d'ici 2025. Il est réellement rassurant de voir qu'on peut encore, en France et en Europe, innover et produire dans le domaine des semi-conducteurs...
Références
[1] https://github.com/bootlin/buildroot-external-st
[2] https://gerrit.googlesource.com/git-repo/
[3] https://www.st.com/en/development-tools/stm32cubeprog.html
[4] https://www.st.com/en/embedded-software/stm32mp1starter.html
[5] https://wiki.st.com/stm32mpu/wiki/Example_of_directory_structure_for_Packages
[6] https://wiki.st.com/stm32mpu/wiki/STM32MP15_Discovery_kits_-_Starter_Package#Image_download
[7] https://www.st.com/en/embedded-software/stm32mp1dev.html
[8] https://wiki.st.com/stm32mpu/wiki/STM32MP1_Developer_Package
[11] https://github.com/orgs/STMicroelectronics/repositories?q=meta+stm32mp
[12] https://github.com/STMicroelectronics/meta-st-openstlinux