I. Introduction▲
Dans le domaine de l'embarqué, nous nous retrouvons souvent en situation où nous devons reconstruire un système complet à partir des sources, pour une architecture cible souvent différente de notre architecture hôte. Que l'on soit débutant ou développeur confirmé, la (cross-)compilation et l'organisation d'un système embarqué sont des étapes longues et fastidieuses, surtout lorsque les éléments du système à compiler nécessitent des adaptations. Il existe heureusement des outils libres qui simplifient et accélèrent cette tâche, en proposant généralement des fonctionnalités complémentaires intéressantes.
Cet article est consacré à l'un de ces outils libres pour systèmes Linux embarqués : Buildroot.
Nous avions déjà croisé Buildroot dans un article de Tristan Lelong concernant l'ajout de paquet à Buildroot. Cette fois-ci, nous proposons une approche didactique du logiciel avec un cas d'utilisation simple : nous détaillons en effet les étapes permettant de construire son premier système Linux embarqué grâce à Buildroot. Pour cela, notre cible sera la carte FriendlyARM mini2440 déjà utilisée pour le concours Linuxembedded 2011 et 2012.
Buildroot est un logiciel libre, sous licence GPL, composé d'un ensemble de Makefile et de patchs. Cette conception fait de Buildroot un outil abordable, et il est relativement simple de participer et de suivre son développement en s'inscrivant sur la mailing-list par exemple. On peut obtenir de l'aide également sur IRC (Freenode, #buildroot).
II. Choix de la version▲
Pour ce tutoriel, privilégions une version stable et figée… En effet, le développement de Buildroot est actif et une nouvelle version est mise en ligne tous les trois mois, avec son lot d'évolutions.
À la date d'écriture de ces lignes, nous prendrons la version 2012.11.1 qui, comme son nom l'indique, est sortie en novembre 2012 (suivie d'un bugfix en janvier 2013). La prochaine version sera donc la 2013.02.
L'archive est disponible ici.
III. Documentation▲
Après avoir extrait l'archive, la première chose à conseiller est de consulter la documentation. Une version en texte est disponible dans « docs/manual/ ». Vous pouvez la compiler dans un autre format (HTML, SPLIT-HTML, PDF, TXT et ePub) pour plus de lisibilité.
Pour du HTML par exemple :
$
make manual-html
La documentation générée se trouve dans « output/docs/manual/manual.html ». Celle-ci explique la structure et les fonctionnements principaux de Buildroot. Elle est pour l'instant disponible en anglais uniquement. Je ne peux que vous conseiller de vous orienter vers la documentation générée qui est à jour, et répond à bon nombre de questions (notamment concernant le chapitre des licences).
IV. Principe de fonctionnement▲
Buildroot est techniquement un ensemble de Makefiles définissant, en fonction des options paramétrées par l'utilisateur, la manière de compiler chaque paquet sélectionné avec des options particulières. Il construit finalement une distribution complète et cohérente dont chaque composant a été compilé.
Buildroot possède un outil confortable de configuration, basé et très similaire à celui du noyau Linux : Kconfig, que l'on retrouve également avec Busybox, uClibc et qui peut être utilisé dans tout projet, comme présenté par Vincent Bassi dans un article précédent.
La configuration est donc accessible avec :
make menuconfig
Le gestionnaire de configuration est basé sur ncurses. Il existe d'autres versions du gestionnaire utilisant des bibliothèques de rendu différentes :
make xconfig (
qt)
make gconfig (
gtk)
À la fermeture du menuconfig, la configuration est sauvegardée dans un fichier (.config à la racine de Buildroot).
Buildroot intègre un système de « templates » prédéfinis de configuration, ce qui permet de configurer des options par défaut pour une cible particulière. Bonne nouvelle : le support de la mini2440 est disponible !
Ces templates sont sauvegardés dans Buildroot dans le dossier configs/*_defconfig et sont listés par :
$
make help
[&
#8230;]
->
mini2440_defconfig - Build for
mini2440
[&
#8230;]
Nous allons donc configurer Buildroot pour utiliser ce template de base :
$
make mini2440_defconfig
Nous pouvons maintenant refaire un make menuconfig pour retrouver les options configurées par défaut.
V. Compilation et configuration des « briques » principales▲
Buildroot va nous permettre de compiler toutes les briques de base nécessaires pour utiliser notre carte. Cela concerne quatre composants principaux :
- La toolchain ;
- Le bootloader ;
- Le noyau Linux ;
- Le rootfs.
Petit conseil : avant de poursuivre, nous allons créer un dossier friendly-arm/ dans board/ puis un dossier mini2440. C'est une bonne habitude à prendre pour clarifier la configuration d'une cible avec Buildroot.
mkdir -p board/friendly-arm/mini2440/
À l'avenir, ce dossier va permettre de centraliser des configurations, des patchs ou même la structure du système de fichier final pour notre cible.
V-A. La toolchain▲
La toolchain désigne l'ensemble des outils à compiler qui nous permettra ensuite d'avoir un environnement capable de cross-compiler depuis notre architecture host (dans notre cas de l'x86_64) vers l'architecture cible (ARM).
La toolchain regroupe un certain nombre de composants obligatoires comme :
- un compilateur ;
- un linker ;
- un assembleur.
Buildroot propose plusieurs mécanismes pour utiliser une toolchain. Le plus direct est d'utiliser la toolchain interne, et dans ce cas c'est Buildroot qui gérera la création et l'utilisation de la toolchain.
Il est possible aussi d'utiliser un backend vers crosstool-ng qui est un outil spécifique pour faire des toolchains, disposant en particulier d'un nombre d'options de configuration plus important. En contrepartie certaines options ne sont pas directement prises en compte par Buildroot, et il peut être nécessaire d'accéder à la configuration de crosstool-ng via la commande :
make ctng-menuconfig
Notons qu'il est aussi possible d'utiliser une toolchain externe et donc déjà compilée, mais il n'y a du coup pas de possibilité pour Buildroot de vérifier que les options de compilation sont cohérentes avec les composants qui seront ensuite sélectionnés.
Nous choisissons ici le backend crosstool-ng par défaut, avec la libc uClibc et en ajoutant le support des WCHAR (utile pour la suite).
Toolchain ->
Toolchain Type (
Crosstool-ng)
Toolchain ->
Croostool-ng C library (
uClibc)
Toolchain ->
[*] Enable WCHAR support
J'ai rencontré un problème de compilation de crosstool-ng concernant les « Companion libraries ». Pour résoudre le problème, il faut faire un tour dans la configuration de ct-ng (make ctng-menuconfig) et mettre en version supérieure les « Companion libraries ».
V-B. Le bootloader▲
Pour le bootloader, nous choisirons u-boot (qui supporte la mini2440). Il faut noter que Buildroot propose le support d'autres bootloader tels que Barebox, syslinux, ou grub (sur architecture x86 uniquement).
Les options par défaut du template sont correctes pour u-boot. Le dépôt récupéré n'est pas le dépôt officiel, mais celui d'un fork réalisé par buserror qui a repris un fork de la communauté openmoko (modèle de Processeurs très proche : s3c2410 et s3c2440). (Accès gitweb)
Une chose à retenir concernant l'utilisation de Buildroot est qu'il faut toujours être attentif à l'ajout de composants sur notre cible. En effet, Buildroot n'est pas un outil de vérification de compatibilité entre un composant et une plateforme hardware, c'est au développeur de s'assurer du bon fonctionnement d'un composant sur sa carte. Dans le cas présent, si on choisit u-boot comme bootloader et que le support n'est pas prévu pour la mini2440, celui-ci ne fonctionnera pas.
V-C. Le noyau▲
Comme évoqué précédemment, l'interface de configuration de Buildroot est basée sur celle du noyau. L'idéal pour le configurer serait d'accéder à son propre menu de configuration avec l'environnement de notre carte (ARCH=arm). Buildroot permet cela, via la règle :
$
make linux-menuconfig
Il faut savoir que cette opération télécharge, extrait (dans le répertoire output/build/linux-<version>), et préconfigure le kernel avec les options paramétrées dans Buildroot pour la configuration du noyau. La règle make ci-dessus rentre dans le répertoire et lance un « make menuconfig » avec les options d'environnement pour votre cible.
Nous allons ajouter une option indispensable dans le noyau pour la suite de notre article : le support de devtmpfs (c'est le noyau qui va se charger de créer les nœuds dans /dev) :
$
make linux-menuconfig
Device Drivers ->
Generic Driver Options ->
[*] Maintain a devtmpfs filesystem to mount at /dev
Une fois le noyau configuré, nous pouvons sauvegarder la configuration. Le mécanisme des « defconfig » du noyau est aussi supporté :
make linux-savedefconfig
La configuration est sauvegardée dans le dossier output/build/linux-<version>/defconfig. Nous allons le récupérer et le copier dans notre dossier de configuration :
cp output/build/linux-<
version>
/defconfig board/friendly-arm/mini2440/linux.defconfig
Il faut maintenant indiquer à Buildroot le fichier de configuration qui sera utilisé à la compilation du Kernel.
Kernel ->
Kernel Configuration (
Select : Using a custom config file)
configuration File Path ->
« board/friendly-arm/mini2440/linux.defconfig »
V-D. Le rootfs▲
Ce composant constitue le système complet installé sur la carte qui sera utilisé après le démarrage du bootloader puis de Linux. Sa configuration s'effectue principalement dans le choix des paquets à installer sur la cible, dans le menu :
Package Selection for
the target
Certaines configurations de Buildroot affectent aussi le rootfs, comme les options que l'on retrouve dans le menu :
System Configuration
Ce menu nous permet de configurer par exemple l'invite de login qui sera envoyée pour ajouter la console série (ce qui est fait par le template de la mini2440 de Buildroot). Il dispose aussi d'une option très importante, celle de la gestion des nœuds de périphériques dans /dev (Menu « /dev/ management »). Plusieurs options sont disponibles :
- « static using device table » : dans ce cas, un fichier spécifie chaque nœud à créer ;
- devtmpfs only : laisse le kernel créer les nœuds avec devtmpfs ;
- dynamic using mdev : utilise mdev de busybox pour gérer les nœuds ;
- dynamic using udev : utilise udev.
Nous choisirons dans notre cas « devtmpfs only ».
Nous pouvons choisir quelques paquets de tests à installer sur notre cible pour la suite de cet article :
- Busybox (sélectionné par défaut) ;
- Enlightenment Foundation Libraries (menu « Graphic libraries and applications »).
VI. Coffee time▲
Maintenant tout est prêt pour lancer la compilation du système ! Pour cela rien de plus simple, c'est la règle exécutée par défaut du Makefile de Buildroot.
$
make
Vous avez le temps de prendre un café.
Si tout se passe sans erreur (et que vous n'avez pas renversé votre café…), les images finales sont dans le dossier output/images/ :
- uboot.bin : l'image d'uboot ;
- uImage : l'image du noyau Linux ;
- rootfs.tar : la tarball du système de fichier complet.
VII. Installation sur la friendly-arm▲
Nous allons utiliser notre carte avec le rootfs en NFS et le noyau à récupérer via tftp sur notre machine de développement.
À partir de ce point, nous considérons que vous avez déjà un serveur TFTP fonctionnel et un serveur NFS avec les bonnes options (ne pas oublier le « no_root_squash » !). De même, il est nécessaire que votre mini2440 possède encore en NOR l'utilitaire supervivi.
VII-A. Installation et configuration de uboot▲
Nous allons commencer par installer l'image u-boot.bin sur la mémoire Nand de la mini2440 à l'aide de l'utilitaire usb-push de friendlyarm disponible ici.
(Attention : vous avez besoin de la « libusb-dev » pour compiler usb-push).
Connectez le port RS232 de la carte sur votre ordinateur, ainsi que l'usb hôte. Ouvrez un minicom (ou autre) sur la liaison série en 115200 bps 8N1.
La carte peut maintenant être démarrée avec le switch sur la position NOR. Sur votre console de port série, l'utilitaire supervivi se lance et affiche un menu. Nous allons choisir le menu « Download vivi » qui signifie en fait de copier l'image que nous allons envoyer par usb vers la première partition de la Nand.
sudo ./usbpush <
/buildroot/path/>
output/images/u-boot.bin
Une fois la copie effectuée, éteindre la carte, changer le switch en position NAND, puis rebooter. Si tout se passe bien, u-boot doit se lancer et nous allons maintenant pouvoir le configurer.
Cette commande permet d'afficher le partitionnement de la mémoire NAND :
# mdtparts
Le résultat doit ressembler à celui-ci :
device nand0 <
mini2440-nand>
, # parts = 4
#: name size offset mask_flags
0
: u-boot 0
&
#215;00040000 0×00000000 0
1
: env 0
&
#215;00020000 0×00040000 0
2
: kernel 0
&
#215;00500000 0×00060000 0
3
: root 0x07aa0000 0
&
#215;00560000 0
active partition: nand0,0
- (
u-boot) 0
&
#215;00040000 @ 0×00000000
Avant de continuer, nous allons préparer la partition NAND de la mini2440. Pour cela, depuis la console de la liaison série, effectuons la commande suivante :
MINI2440 # nand createbbt
Create BBT and erase everything ? <
y/N>
y
Évidemment, vous pouvez prévoir le problème que l'on aura au prochain démarrage de la carte : nous avons copié uboot sur la 1re partition NAND et nous avons tout écrasé pour créer la BBT. Il faut donc refaire l'étape précédente de l'usbpush (sans oublier de remettre le switch sur la position NOR).
Vous avez maintenant une NAND fonctionnelle et nous pouvons reprendre la configuration de uboot.
La commande suivante nous permet de spécifier à uboot que son espace de sauvegarde de configuration se trouve sur la nand (le « env » est la deuxième partition listée par mtdparts) :
# dynenv set env
Spécifier à u-boot l'adresse ip de notre carte :
# setenv ipaddr <ip_carte>
Spécifier l'adresse ip du serveur :
# setenv serverip <ip_de_hôte>
Spécifier le répertoire du root nfs. C'est dans ce répertoire (de l'hôte) que nous allons extraire la tarball du rootfs et celui-ci sera partagé en NFS.
# setenv root_nfs <path/nfs>
Spécifier le nom de l'image noyau à récupérer.
# setenv bootfile uImage
Spécifier l'adresse en ram de stockage du noyau.
# setenv fileaddr 32000000
# setenv loadaddr 0×32000000
Le système de variable de u-boot est minimaliste, mais nous permet de définir des alias pour former des commandes complexes à partir des variables définies ci-dessus. Nous allons configurer la ligne de boot pour utiliser le boot par NFS. Pour cela, la commande `set_bootarg_nfs` est déjà prête :
# run set_bootargs_nfs
# printenv bootargs
# saveenv
Cette dernière commande va sauvegarder l'environnement uboot actuel dans la partition Nand que nous avons préalablement précisée (« env »). Ainsi, au redémarrage de la carte, « printenv » devrait retourner l'environnement que l'on vient de sauvegarder.
VII-B. Noyau et rootfs▲
Nous allons récupérer le noyau (uImage dans le répertoire output/ de buildroot) via le protocole TFTP. Une commande Uboot va automatiquement récupérer les informations que nous lui avons passées précédemment (serverip, bootfile et loadaddr) :
# tftp
Pour le rootfs, il suffit de récupérer la tarball (rootfs.tar) générée par Buildroot, puis de l'extraire dans le répertoire de votre choix que vous avez partagé en NFS.
Note : n'oubliez pas d'extraire la tarball en root, car il est nécessaire de garder les bonnes permissions ainsi que les nœuds dans /dev
sudo tar xvf rootfs.tar -C <
ROOT_NFS>
VIII. Enjoy▲
Vous n'avez plus qu'à lancer la dernière commande depuis la console série, qui va exécuter le kernel que uboot viens de copier en RAM avec la commande tftp.
# bootm
Si tout s'est déroulé correctement, vous avez maintenant une carte qui démarre avec un noyau récupéré par tftp et un rootfs sur NFS.
En plus de cela, vous avez également la satisfaction d'avoir construit (à l'aide de votre système hôte) un système complet sans utiliser d'outil précompilé !