Créer un système complet avec Buildroot

Christophe BLAESS - mars 2020

Le projet Buildroot permet de construire facilement un système embarqué et d’en ajuster finement le contenu. Pour produire des systèmes autonomes robustes, résilients et reproductibles, on préfère cette approche à l'utilisation d'une distribution pré-compilée dont le contenu est assez mal maîtrisé. Nous allons utiliser Buildroot pour construire un système personnalisé pour Raspberry Pi.

Les développeurs de Buildroot nous fournissent une nouvelle version chaque trimestre. Celle de février est une version maintenue sur le long terme pendant toute l'année, alors que celles de mai, août et novembre sont des versions intermédiaires marquant les évolutions du projet.

Cet article a été mis à jour en mars 2020 pour prendre en considération Buildroot 2020.02 (la dernière version «long term» disponible à ce jour) et testé sur Raspberry Pi 4.

Les fichiers produits au cours de cet article sont regroupés ici :

https://www.blaess.fr/christophe/buildroot-lab/pi4-config.tar.bz2

Sommaire

I - Environnement de travail

Commençons par créer une arborescence de travail qui contiendra tous nos fichiers personnalisés, les répertoires de construction, les archives des projets compilés par Buildroot, etc.

[~]$ mkdir br-lab
[~]$ cd br-lab
[br-lab]$

Au sein de cet environnement, nous allons respecter une organisation des fichiers assez répandue lorsque l'on utilise Buildroot : le répertoire des sources de Buildroot restera intact et nous pourrons faire toutes les compilations dans des répertoires build-... pour les différentes cibles. Nous verrons par la suite comment sauvegarder toutes nos configurations spécifiques dans un external tree.

Nous téléchargeons l'archive de la dernière version de Buildroot, et la décompressons :

[br-lab]$ wget http://www.buildroot.org/downloads/buildroot-2020.02.tar.bz2 
[br-lab]$ tar xf buildroot-2020.02.tar.bz2

Comme indiqué plus haut, nous allons travailler dans un répertoire de compilation indépendant des sources de Buildroot. Ainsi on peut enchaîner plusieurs builds successifs pour différentes architectures ou différents projets en conservant les sources originales intactes. Par acquit de conscience j'ai même l'habitude pour m'assurer qu'aucune modification n'interviendra dans les sources de Buildroot d'y interdire l'accès en écriture :

[br-lab]$ chmod -R -w buildroot-2020.02

C'est un peu paranoïaque, j'en conviens, mais cela me garantit qu'aucun fichier de configuration ne pourra être sauvegardé par erreur dans cette arborescence dans le cas d'une faute de frappe dans le nom d'une variable d'environnement sur une ligne de commande.

II - Utilisation d'une configuration par défaut

Nous allons demander à Buildroot de préparer une configuration par défaut pour Raspberry Pi 4. Puis nous la modifierons très légèrement dans un premier temps, et l'affinerons de manière plus importante ensuite. Voyons les configurations disponibles (longue liste !) :

[br-lab]$ cd buildroot-2020.02/ 
[buildroot-2020.02]$ ls configs/
aarch64_efi_defconfig
acmesystems_aria_g25_128mb_defconfig
acmesystems_aria_g25_256mb_defconfig
acmesystems_arietta_g25_128mb_defconfig
acmesystems_arietta_g25_256mb_defconfig
amarula_a64_relic_defconfig
amarula_vyasa_rk3288_defconfig
andes_ae3xx_defconfig
arcturus_ucls1012a_defconfig
arcturus_ucp1020_defconfig
armadeus_apf27_defconfig
armadeus_apf28_defconfig
armadeus_apf51_defconfig
arm_foundationv8_defconfig
arm_juno_defconfig
asus_tinker_rk3288_defconfig
at91sam9260eknf_defconfig
at91sam9g20dfc_defconfig
at91sam9g45m10ek_defconfig
at91sam9rlek_defconfig
at91sam9x5ek_defconfig
at91sam9x5ek_dev_defconfig
at91sam9x5ek_mmc_defconfig
at91sam9x5ek_mmc_dev_defconfig
atmel_sama5d27_som1_ek_mmc_dev_defconfig
atmel_sama5d2_xplained_mmc_defconfig
atmel_sama5d2_xplained_mmc_dev_defconfig
atmel_sama5d3xek_defconfig
atmel_sama5d3_xplained_defconfig
atmel_sama5d3_xplained_dev_defconfig
atmel_sama5d3_xplained_mmc_defconfig
atmel_sama5d3_xplained_mmc_dev_defconfig
atmel_sama5d4_xplained_defconfig
atmel_sama5d4_xplained_dev_defconfig
atmel_sama5d4_xplained_mmc_defconfig
atmel_sama5d4_xplained_mmc_dev_defconfig
bananapi_m1_defconfig
bananapi_m2_plus_defconfig
bananapi_m2_ultra_defconfig
bananapi_m64_defconfig
bananapro_defconfig
beagleboardx15_defconfig
beagleboneai_defconfig
beaglebone_defconfig
beaglebone_qt5_defconfig
beelink_gs1_defconfig
chromebook_snow_defconfig
ci20_defconfig
csky_gx6605s_defconfig
cubieboard2_defconfig
engicam_imx6qdl_icore_defconfig
engicam_imx6qdl_icore_qt5_defconfig
engicam_imx6qdl_icore_rqs_defconfig
engicam_imx6ul_geam_defconfig
engicam_imx6ul_isiot_defconfig
freescale_imx28evk_defconfig
freescale_imx6dlsabreauto_defconfig
freescale_imx6dlsabresd_defconfig
freescale_imx6qsabreauto_defconfig
freescale_imx6qsabresd_defconfig
freescale_imx6sxsabresd_defconfig
freescale_imx7dsabresd_defconfig
freescale_imx8mmevk_defconfig
freescale_imx8mqevk_defconfig
freescale_imx8qxpmek_defconfig
freescale_p1025twr_defconfig
freescale_t1040d4rdb_defconfig
freescale_t2080_qds_rdb_defconfig
friendlyarm_nanopi_a64_defconfig
friendlyarm_nanopi_neo2_defconfig
friendlyarm_nanopi_neo_plus2_defconfig
galileo_defconfig
grinn_chiliboard_defconfig
grinn_liteboard_defconfig
hifive_unleashed_defconfig
imx23evk_defconfig
imx6-sabreauto_defconfig
imx6-sabresd_defconfig
imx6-sabresd_qt5_defconfig
imx6slevk_defconfig
imx6sx-sdb_defconfig
imx6ulevk_defconfig
imx6ulpico_defconfig
imx7dpico_defconfig
imx7d-sdb_defconfig
imx8mmpico_defconfig
imx8mpico_defconfig
lafrite_defconfig
lego_ev3_defconfig
licheepi_zero_defconfig
linksprite_pcduino_defconfig
minnowboard_max_defconfig
minnowboard_max-graphical_defconfig
mx25pdk_defconfig
mx51evk_defconfig
mx53loco_defconfig
mx6cubox_defconfig
mx6sx_udoo_neo_defconfig
mx6udoo_defconfig
nanopi_m1_defconfig
nanopi_m1_plus_defconfig
nanopi_neo_defconfig
nexbox_a95x_defconfig
nitrogen6sx_defconfig
nitrogen6x_defconfig
nitrogen7_defconfig
nitrogen8m_defconfig
odroidxu4_defconfig
olimex_a10_olinuxino_lime_defconfig
olimex_a13_olinuxino_defconfig
olimex_a20_olinuxino_lime2_defconfig
olimex_a20_olinuxino_lime_defconfig
olimex_a20_olinuxino_micro_defconfig
olimex_a33_olinuxino_defconfig
olimex_a64_olinuxino_defconfig
olimex_imx233_olinuxino_defconfig
openblocks_a6_defconfig
orangepi_lite2_defconfig
orangepi_lite_defconfig
orangepi_one_defconfig
orangepi_one_plus_defconfig
orangepi_pc2_defconfig
orangepi_pc_defconfig
orangepi_pc_plus_defconfig
orangepi_plus_defconfig
orangepi_prime_defconfig
orangepi_r1_defconfig
orangepi_win_defconfig
orangepi_zero_defconfig
orangepi_zero_plus2_defconfig
pandaboard_defconfig
pc_x86_64_bios_defconfig
pc_x86_64_efi_defconfig
pine64_defconfig
pine64_sopine_defconfig
qemu_aarch64_virt_defconfig
qemu_arm_versatile_defconfig
qemu_arm_versatile_nommu_defconfig
qemu_arm_vexpress_defconfig
qemu_arm_vexpress_tz_defconfig
qemu_csky610_virt_defconfig
qemu_csky807_virt_defconfig
qemu_csky810_virt_defconfig
qemu_csky860_virt_defconfig
qemu_m68k_mcf5208_defconfig
qemu_m68k_q800_defconfig
qemu_microblazebe_mmu_defconfig
qemu_microblazeel_mmu_defconfig
qemu_mips32r2el_malta_defconfig
qemu_mips32r2_malta_defconfig
qemu_mips32r6el_malta_defconfig
qemu_mips32r6_malta_defconfig
qemu_mips64el_malta_defconfig
qemu_mips64_malta_defconfig
qemu_mips64r6el_malta_defconfig
qemu_mips64r6_malta_defconfig
qemu_nios2_10m50_defconfig
qemu_or1k_defconfig
qemu_ppc64_e5500_defconfig
qemu_ppc64le_pseries_defconfig
qemu_ppc64_pseries_defconfig
qemu_ppc_g3beige_defconfig
qemu_ppc_mac99_defconfig
qemu_ppc_mpc8544ds_defconfig
qemu_ppc_virtex_ml507_defconfig
qemu_riscv32_virt_defconfig
qemu_riscv64_virt_defconfig
qemu_sh4eb_r2d_defconfig
qemu_sh4_r2d_defconfig
qemu_sparc64_sun4u_defconfig
qemu_sparc_ss10_defconfig
qemu_x86_64_defconfig
qemu_x86_defconfig
qemu_xtensa_lx60_defconfig
qemu_xtensa_lx60_nommu_defconfig
raspberrypi0_defconfig
raspberrypi0w_defconfig
raspberrypi2_defconfig
raspberrypi3_64_defconfig
raspberrypi3_defconfig
raspberrypi3_qt5we_defconfig
raspberrypi4_64_defconfig
raspberrypi4_defconfig
raspberrypi_defconfig
riotboard_defconfig
rock64_defconfig
roseapplepi_defconfig
s6lx9_microboard_defconfig
sheevaplug_defconfig
snps_aarch64_vdk_defconfig
snps_arc700_axs101_defconfig
snps_archs38_axs103_defconfig
snps_archs38_haps_defconfig
snps_archs38_hsdk_defconfig
snps_archs38_vdk_defconfig
socrates_cyclone5_defconfig
solidrun_clearfog_defconfig
solidrun_clearfog_gt_8k_defconfig
solidrun_macchiatobin_mainline_defconfig
solidrun_macchiatobin_marvell_defconfig
stm32f429_disco_defconfig
stm32f469_disco_defconfig
stm32mp157c_dk2_defconfig
toradex_apalis_imx6_defconfig
ts4900_defconfig
ts5500_defconfig
ts7680_defconfig
wandboard_defconfig
warp7_defconfig
warpboard_defconfig
zynq_microzed_defconfig
zynqmp_zcu106_defconfig
zynq_zc706_defconfig
zynq_zed_defconfig
[buildroot-2020.02]$

Nous voyons que Buildroot intègre un nombre assez conséquent de configurations toutes prêtes pour des plateformes variées. Même si vous ne trouvez pas celle qui correspond exactement à votre cible, il est souvent possible de choisir une configuration pour un system-on-chip proche et de l'ajuster assez facilement.

Nous avons prévu de construire un système pour Raspberry Pi 4, aussi je demande à Buildroot de me préparer une configuration pour cette cible :

[buildroot-2020.02]$ make O=../build-pi4 raspberrypi4_defconfig 
  [...]
#
# configuration written to /home/cpb/br-lab/build-pi4/.config
#

Analysons le contenu de la ligne saisie :

Maintenant que la configuration a été préparée dans notre répertoire de compilation, nous pouvons nous y rendre pour continuer le travail, nous n'aurons plus besoin de revenir dans les sources de Buildroot.

[buildroot-2020.02]$ cd ../build-pi4/
[build-pi4]$ ls
build  Makefile
[build-pi4]$ ls -a
.                       .br2-external.in.openssl     build
..                      .br2-external.in.paths       .config
.br2-external.in.jpeg   .br2-external.in.toolchains  Makefile 
.br2-external.in.menus  .br2-external.mk
[build-pi4]$ 

Pour information, la configuration ainsi préparée est enregistrée dans le fichier «.config» (dont le nom commence par un point ce qui le rend invisible à la commande «ls» sauf si elle est suivie de l'option «-a»).

Il s'agit d'un fichier de définitions de constantes pour «make» :

[build-pi4]$ cat .config
#
# Automatically generated file; DO NOT EDIT.
# Buildroot 2020.02 Configuration
#
BR2_HAVE_DOT_CONFIG=y
BR2_HOST_GCC_AT_LEAST_4_9=y
BR2_HOST_GCC_AT_LEAST_5=y
BR2_HOST_GCC_AT_LEAST_6=y
BR2_HOST_GCC_AT_LEAST_7=y
 [...]
# BR2_ELF2FLT is not set
# BR2_VFP_FLOAT is not set
# BR2_PACKAGE_GCC_TARGET is not set
# BR2_HAVE_DEVFILES is not set
[build-pi4]$

III - Modification de la configuration

Dans un premier temps nous allons faire quelques personnalisations rapides, nous y reviendrons plus en détail ultérieurement.

Pour cela nous demandons le menu de configuration de Buildroot. Comme nous l'avons indiqué, on fait appel à «make» pour interagir avec Buildroot :

[build-pi4]$ make menuconfig
  [...]

Un menu de configuration semblable à celui de la figure 1 s'ouvre.

Menu général de configuration
Figure 1 - Menu général de configuration.

Le menu est affiché en utilisant la bibliothèque Ncurses. Il existe des commandes équivalentes pour afficher le menu avec les bibliothèques Qt («make xconfig») ou Gtk («make gconfig»).

III.1 - Menu «Target options»

Le premier sous-menu «Target options» contient essentiellement des paramètres correspondant à certaines options du compilateur. Nous voyons sur la figure 2 la configuration pour un Raspberry Pi 4.

Sous-menu «Target options»
Figure 2 - Sous-menu «Target options».

Dans notre cas, il n'y a pas de raison de modifier le contenu de ce sous-menu mais on peut en profiter pour jeter un œil à la variété des processeurs supportés directement par Buildroot.

III.2 - Menu «Build options»

Le sous-menu «Build options» contient de nombreux paramètres permettant de configurer la manière de compiler le système. Une modification que je conseille : celle de l'option «Download dir». Il s'agit de l'emplacement où les packages téléchargés sont stockés avant d'être extraits et compilés. Par défaut il s'agit du sous-répertoire «dl» dans le répertoire de Buildroot. Mais nous avons supprimé les droits d'écriture sur ce dernier. Il faut donc extraire le répertoire «dl» en le remontant d'un cran. Ceci permet en outre de mutualiser les téléchargements entre les différents builds que l'on est amené à réaliser.

Sur la figure 3, on voit que j'ai édité le champ «Download dir» pour insérer un «../» avant «dl».

Sous-menu «Build options»
Figure 3 - Sous-menu «Build options».

III.3 - Menu «Toolchain»

Le sous-menu «Toolchain» permet de configurer des paramètres de la chaîne de cross-compilation. Tout d'abord on peut choisir d'utiliser une chaîne de compilation produite par Buildroot (le cas par défaut) ou une toolchain externe, précompilée et téléchargée depuis certains sites de références (Linaro par exemple). Bien que cette dernière solution soit plus rapide, je la déconseille pour des raisons de pérennité et de reproductibilité du système de compilation.

Sous-menu «Toolchain» (1)
Figure 4 - Sous-menu «Toolchain» (1).

Un second choix important concerne la bibliothèque C à employer pendant la production du compilateur et à installer sur la cible. C’est une décision qui dépend beaucoup du code métier. La bibliothèque C est un point-clé du système ; c’est elle qui permet d’entrer dans le noyau pour bénéficier de ses services (les appels-système). Le choix par défaut est celui de la «uclibc-ng», une bibliothèque spécialement dédiée aux systèmes embarqués restreints. C'est la solution que je vais conserver ici, mais les alternatives «glibc» et «musl» sont tout aussi acceptables.

Je vais modifier deux options que l'on voit sur la figure 4 :

Sous-menu «Toolchain» (2)
Figure 5 - Sous-menu «Toolchain» (2).

Enfin, j'active l'option «Build cross gdb for the host» comme on le voit sur la figure 6, pour produire tout de suite le débogueur qui fonctionnera sur la machine hote (le PC).

Sous-menu «Toolchain» (3)
Figure 6 - Sous-menu «Toolchain» (3).

Pour le moment, nous nous limitons à ces modifications, nous examinerons le contenu des autres menus ultérieurement. Nous pouvons quitter en sauvegardant la configuration comme sur la figure 7.

Sauvegarde de la configuration
Figure 7 - Sauvegarde de la configuration.

IV - Compilation et test

IV.1 - Compilation de l'image standard

Pour lancer la production de cette image, rien de plus simple :

[build-pi4]$ make
 [...]

La première compilation est plus longue que les suivantes, du fait de la génération de la toolchain et de la compilation du noyau. La durée exacte dépend beaucoup de la machine hôte et de la connexion Internet, mais on peut considérer environ une heure sur une machine d'entrée de gamme. La compilation se termine ainsi :

INFO: vfat(boot.vfat): cmd: "MTOOLS_SKIP_CHECK=1 mcopy -bsp -i '/home/cpb/br-lab/build-pi4/images/boot.vfat' '/home/cpb/br-lab/build-pi4/images/rpi-firmware/overlays' '::'" (stderr):
INFO: vfat(boot.vfat): adding file 'zImage' as 'zImage' ...
INFO: vfat(boot.vfat): cmd: "MTOOLS_SKIP_CHECK=1 mcopy -bsp -i '/home/cpb/br-lab/build-pi4/images/boot.vfat' '/home/cpb/br-lab/build-pi4/images/zImage' '::'" (stderr):
INFO: hdimage(sdcard.img): adding partition 'boot' (in MBR) from 'boot.vfat' ...
INFO: hdimage(sdcard.img): adding partition 'rootfs' (in MBR) from 'rootfs.ext4' ...
INFO: hdimage(sdcard.img): writing MBR
[build-pi4]$

De nouveaux sous-répertoires sont apparus dans notre répertoire de travail.

[build-pi4]$ ls
build  host  images  Makefile  staging  target
[build-pi4]$ ls target/
bin  lib      media  proc  sbin                              tmp
dev  lib32    mnt    root  sys                               usr
etc  linuxrc  opt    run   THIS_IS_NOT_YOUR_ROOT_FILESYSTEM  var
[build-pi4]$ 

Pour le moment nous nous intéressons au répertoire «images» 

[build-pi4]$ ls images/
bcm2711-rpi-4-b.dtb  rootfs.ext2  rpi-firmware  zImage
boot.vfat            rootfs.ext4  sdcard.img
[build-pi4]$

Les fichiers qu'il contient sont les suivants :

IV.2 - Préparation de la carte micro-SD

J'exécute la commande suivante sur mon PC :

[build-pi4]$ lsblk
NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda              8:0    0 465,8G  0 disk  
├─sda1           8:1    0   400G  0 part  /home/testing
└─sda2           8:2    0     8G  0 part  [SWAP]
sdb              8:16   0 931,5G  0 disk  
└─sdb1           8:17   0   512G  0 part  /media/cpb/USB-EXT
nvme0n1        259:0    0 232,9G  0 disk  
├─nvme0n1p1    259:1    0   512M  0 part  /boot/efi
├─nvme0n1p2    259:2    0 224,6G  0 part  /
└─nvme0n1p3    259:3    0   7,8G  0 part  
  └─cryptswap1 253:0    0   7,8G  0 crypt 
[build-pi4]$ 

On voit tous les périphériques «blocks» présents : «nvme0n1» le SSD principal du système, «sda» un disque dur SATA interne de travail, «sdb» un disque externe USB. Je connecte ensuite une carte micro-SD insérée dans un adpateur USB, puis je relance la même commande après quelques secondes d'attente pour que l'auto-monteur ait terminé son travail :

[build-pi4]$ lsblk
NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda              8:0    0 465,8G  0 disk  
├─sda1           8:1    0   400G  0 part  /home/testing
└─sda2           8:2    0     8G  0 part  [SWAP]
sdb              8:16   0 931,5G  0 disk  
└─sdb1           8:17   0   512G  0 part  /media/cpb/USB-EXT
sdc              8:32   1   3,8G  0 disk  
├─sdc1           8:33   1    32M  0 part  /media/cpb/0B09-D1D7
└─sdc2           8:34   1   256M  0 part  /media/cpb/0af348dc-34fe-4482-b341-a87
nvme0n1        259:0    0 232,9G  0 disk  
├─nvme0n1p1    259:1    0   512M  0 part  /boot/efi
├─nvme0n1p2    259:2    0 224,6G  0 part  /
└─nvme0n1p3    259:3    0   7,8G  0 part  
  └─cryptswap1 253:0    0   7,8G  0 crypt

Je vois que le périphérique apparu est «/dev/sdc/». Je démonte les partitions montées automatiquement et je copie l'image produite par Buildroot sur ce périphérique. Attention, cette étape est potentiellement dangereuse (comme tout ce qu'on préfixe par «sudo»), soyez attentif pour ne pas écraser votre disque système par mégarde...

[build-pi4]$ umount  /dev/sdc?
[build-pi4]$ sudo  cp  images/sdcard.img  /dev/sdc

Une fois la copie terminée, insérons la carte SD dans le Raspberry Pi 4 et démarrons-le.

IV.3 - Test de l'image standard

J'ai connecté un écran sur l'une des sorties mini-HDMI. Nous voyons sur la figure 8 les traces de boot avec les fameuses framboises emblèmatiques du Raspberry Pi.

Boot du Raspberry Pi 4.
Figure 8 - Boot du Raspberry Pi 4.

Nous pouvons nous connecter avec l'identité «root» (pas de mot de passe pour le moment), et passer quelques commandes comme sur la figure 9. Une remarque : le clavier est configuré en «Qwerty» ce qui nécessite une petite gymnastique si on ne dispose que d'un clavier «Azerty».

Connexion sur le Raspberry Pi 4.
Figure 9 - Connexion sur le Raspberry Pi 4.

Les photos d'écran étant difficiles à lire, je vais désormais afficher plutôt des captures prises sur un terminal («minicom») connecté sur le port série du Raspberry Pi avec un câble USB-Série de ce genre (ne pas connecter le fil rouge).

Connecteur série.
Figure 10 - Connecteur série.
Welcome to Buildroot
buildroot login: root
# uname -a
Linux buildroot 4.19.97-v7l #1 SMP Sun Mar 8 23:43:48 CET 2020 armv7l GNU/Linux 
# cat /proc/device-tree/model
Raspberry Pi 4 Model B Rev 1.1# 
# free
              total        used        free      shared  buff/cache   available
Mem:        1962164       20664     1938580          48        2920     1929232
Swap:             0           0           0
# 

Nous voyons ici Buildroot fonctionner sur un Raspberry Pi 4 avec 2 Gb de RAM. La commande «ps» nous affiche la liste des processus présents :

# ps
PID   USER     COMMAND
    1 root     init
    2 root     [kthreadd]
    3 root     [rcu_gp]
    4 root     [rcu_par_gp]
    6 root     [kworker/0:0H-kb]
    7 root     [kworker/u8:0-ev]
    8 root     [mm_percpu_wq]
    9 root     [ksoftirqd/0]
   10 root     [rcu_sched]
   11 root     [rcu_bh]
   12 root     [migration/0]
   13 root     [cpuhp/0]
   14 root     [cpuhp/1]
   15 root     [migration/1]
   16 root     [ksoftirqd/1]
   17 root     [kworker/1:0-eve]
   18 root     [kworker/1:0H-kb]
   19 root     [cpuhp/2]
   20 root     [migration/2]
   21 root     [ksoftirqd/2]
   22 root     [kworker/2:0-eve]
   24 root     [cpuhp/3]
   25 root     [migration/3]
   26 root     [ksoftirqd/3]
   29 root     [kdevtmpfs]
   30 root     [netns]
   32 root     [khungtaskd]
   33 root     [oom_reaper]
   34 root     [writeback]
   35 root     [kcompactd0]
   36 root     [crypto]
   37 root     [kblockd]
   38 root     [watchdogd]
   39 root     [kworker/2:1-ipv]
   40 root     [rpciod]
   41 root     [kworker/u9:0]
   42 root     [xprtiod]
   43 root     [kswapd0]
   44 root     [nfsiod]
   55 root     [kthrotld]
   56 root     [iscsi_eh]
   57 root     [kworker/u8:1]
   59 root     [kworker/1:1-mm_]
   60 root     [DWC Notificatio]
   61 root     [vchiq-slot/0]
   62 root     [vchiq-recy/0]
   63 root     [vchiq-sync/0]
   64 root     [vchiq-keep/0]
   65 root     [SMIO]
   66 root     [kworker/0:2-eve]
   67 root     [irq/37-brcmstb_]
   68 root     [irq/38-mmc1]
   69 root     [irq/38-mmc0]
   70 root     [kworker/0:3-eve]
   72 root     [mmc_complete]
   73 root     [kworker/0:1H-mm]
   74 root     [kworker/3:1H-kb]
   75 root     [kworker/3:2H]
   76 root     [jbd2/mmcblk0p2-]
   77 root     [ext4-rsv-conver]
   79 root     [kworker/2:1H-kb]
   80 root     [kworker/1:1H-kb]
   94 root     /sbin/syslogd -n
   98 root     /sbin/klogd -n
  123 root     [kworker/2:2H]
  124 root     [ipv6_addrconf]
  145 root     -sh
  164 root     /sbin/getty -L tty1 0 vt100
  169 root     [kworker/3:0-eve]
  170 root     [kworker/3:1-mm_]
  171 root     [kworker/3:2]
  172 root     ps
# 

Hormis les threads internes du noyau (toutes les tâches avec des noms entre crochets), nous observons la présence de seulement six processus :

Voilà un système dont le contenu est bien sous contrôle !

Cette première image est très toutefois limitée, elle ne comprend guère que Busybox, et le compte «root» est même accessible sans mot de passe. Nous allons améliorer sa configuration.

V - Affinement de la configuration

Nous pouvons faire une toute une série d’améliorations, afin d’obtenir un système un peu plus convivial, accueillant un autre utilisateur que «root» par exemple ou renforçant la partition principale contre les risques de coupures d’alimentation intempestives.

V.1 - Menu «System Configuration»

Nous relançons :

[build-pi4]$ make  menuconfig

Et nous nous intéressons au menu «System configuration» comme nous le voyons sur la figure 11.

Sous-menu 'System configuration'.
Figure 11 - Sous-menu «System configuration»

Intervenons sur quelques options de ce menu :

Nous allons devoir fournir un fichier pour les utilisateurs supplémentaires (autres que «root»). Ce sera notre premier fichier de configuration personnalisé mais nous en ajouterons d'autres par la suite. La méthode la plus adéquate à mon sens consiste à ajouter une arborescence externe (external tree) regroupant tous nos fichiers personnels.

Quittons donc quelques instants le menu de configuration (en sauvegardant les modifications) pour créer le fichier des utilisateurs dans un external tree.

V.2 - External Tree

Une arborescence externe est un répertoire placé en-dehors des sources de Buildroot et en-dehors du répertoire de compilation «build-pi4» dans lequel nous pourrons ajouter des recettes supplémentaires (pour intégrer des packages absents de ceux connus par Buildroot, ou pour intégrer du code métier applicatif), des paramètres de configuration pour Busybox ou pour le noyau, ou encore des fichiers personnalisés à ajouter dans l'arborescence de la cible. Nous allons commencer par un external tree très simple.

Remontons d'un cran dans notre système de fichiers, puis créons un répertoire de notre choix. Par exemple «pi4-config».

[build-pi4]$ cd  ..
[br-lab]$ mkdir  pi4-config
[br-lab]$ ls
build-pi4          buildroot-2020.02.tar.bz2  pi4-config 
buildroot-2020.02  dl
[br-lab]$ cd  pi4-config/
[pi4-config]$ 

Pour que cette arborescence soit valide pour Buildroot, nous devons y créer trois fichiers :

[pi4-config]$ echo  "name: PI4_CONFIG"  >  external.desc
[pi4-config]$ echo  "desc: Custom configuration for Pi 4"  >>  external.desc 
[pi4-config]$ cat external.desc
name: PI4_CONFIG
desc: Custom configuration for Pi 4
[pi4-config]$
[pi4-config]$ touch  external.mk
[pi4-config]$ touch  Config.in

Ajoutons un répertoire dans lequel nous stockerons nos éléments de configuration, à commencer par la table des utilisateurs.

[pi4-config]$ mkdir configs
[pi4-config]$

V.3 - Table des utilisateurs

Nous allons remplir «Path to the users tables» avec le nom d’un fichier qui contient la liste des utilisateurs. Il doit y avoir un compte par ligne. Les champs, séparés par une espace, sont les suivants :

Je crée donc un fichier «configs/users.tbl» :

[pi4-config]$ echo  "rpi 1000 rpi 1000 =rpi /home/rpi /bin/sh - Raspberry Pi User"  >  configs/users.tbl 
[pi4-config]$

Comme on le voit, j'ai ajouté un utilisateur «rpi» appartenant à son propre groupe, et dont le mot de passe est... «rpi» !

Le stockage du mot de passe en clair dans ce fichier de configuration est acceptable pour une expérimentation comme celle-ci mais doit être proscrit en environnement de production. En effet si la sécurité de la machine utilisée pour la compilation est compromise, c'est l'ensemble des systèmes déployés qui sont vulnérables

V.4 - Intégration de l'arborescence externe et test

Pour que l'arborescence externe soit prise en considération, il va nous falloir l'indiquer une fois sur la ligne de commande de «make menuconfig» puis elle sera mémorisée automatiquement.

[pi4-config]$ cd ../build-pi4/
[build-pi4]$ export BR2_EXTERNAL=../pi4-config/
[build-pi4]$ make menuconfig

Une nouvelle ligne est apparue au bas du menu de configuration :

Apparition du sous-menu «External options»
Figure 12 - Apparition du sous-menu «External options»

Comme nous n'avons pas indiqué de menu ou de packages supplémentaires, le sous-menu concerné indique seulement la description de cette option.

Sous-menu «External options»
Figure 13 - Sous-menu «External options»

Ce qui nous intéresse pour le moment, c'est de renseigner la table des utilisateurs. Nous pouvons donc donner le chemin dans le menu «System configuration». J'utilise une variable qui va être automatiquement créée par Buildroot : «BR2_EXTERNAL_PI4_CONFIG_PATH». Elle représente la racine de notre arborescence externe. Le nom de la variable a été composé à partir du champ «name» que nous avons inscrit dans le fichier «external.desc». Le nom de l'external tree était «PI4_CONFIG», celui de la variable est donc «BR2_EXTERNAL_PI4_CONFIG_PATH».

Cette approche nous donne de la souplesse, il sera possible d'utiliser le même external tree dans d'autres builds. Nous verrons même comment extraire la configuration de Buildroot que nous réalisons pour la réutiliser avec une version ultérieure par exemple.

J'ai donc modifié l'option «Path to the users tables» du menu «System configuration» pour y inscrire la chaîne : «$(BR2_EXTERNAL_PI4_CONFIG_PATH)/configs/users.tbl».

Après sauvegarde, on relance la compilation du système :

[build-pi4]$ make

La durée est très courte, car il n'y a que quelques fichiers de paramétrage système à modifier.

Après réécriture de la carte micro-SD comme nous l'avons fait plus haut, nous pouvons démarrer le Raspberry Pi :

Welcome on board!
R-Pi login:

Nous pouvons déjà remarquer la bonne prise en compte du message de bienvenue et du nom d'hôte («R-Pi» au lieu de «Buildroot» précédemment). Connectons-nous sous la nouvelle identité ajoutée :

R-Pi login: rpi 
Password: (rpi)
$ pwd
/home/rpi
$ exit

La connexion sur le nouveau compte a fonctionné, vérifions si «root» est bien muni d'un mot de passe cette fois :

Welcome on board!
R-Pi login: root
Password: (root)
# pwd
/root
#

Nous avons placé le système de fichiers en lecture seulement, vérifions cela :

# echo  HELLO  >  file.txt
-sh: can't create file.txt: Read-only file system
#

L'écriture est bien interdite. Nous pouvons toutefois remonter le système de fichiers en lecture-écriture :

# mount  /  -o  remount,rw
[   73.376221] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null) 
# echo  HELLO  >  file.txt
# ls
file.txt
#

Le message du kernel qui apparaît sur cette console après la commande «mount» est normal, il nous annonce que la partition a été remontée.

Nous pouvons aussi remettre le système de fichiers en lecture seule :

# mount  /  -o  remount,ro
[   89.070566] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null) 
# rm  -f  file.txt 
rm: can't remove 'file.txt': Read-only file system
# 

Le système est bien en lecture seule à nouveau. Notre système n'est plus vulnérable aux coupures d'alimentation électrique.

VI - Ajout de contenu

VI.1 - Ajout de packages supportés par Buildroot

L'ajout de logiciels proposés par Buildroot est très simple, il suffit de se rendre dans le menu «Target packages» visible sur la figure 14, d'explorer ses sous-menus et de piocher dans les applications proposées pour composer le contenu de notre système.

Sous-menu «Target packages»
Figure 14 - Sous-menu «Target packages»

Par exemple, dans le menu «Networking applications» j'ajoute le serveur SSH Dropbear :

Ajout de Dropbear
Figure 15 - Ajout de Dropbear

On peut remarquer sur la figure 15, que j'ai également coché l'option «disable reverse DNS lookups» pour accélérer les connexions.

J'ai choisi également d'ajouter l'éditeur Gnu Nano se trouvant dans le sous-menu «Text editors and viewers» comme on le voit sur la figure 16.

Ajout de Gnu Nano
Figure 16 - Ajout de Gnu Nano

VI.2 - Ajout de scripts

Nous avons précédemment vu comment remonter le système de fichiers, initialement en lecture seule, pour y accéder en lecture-écriture puis le replacer en lecture seule à nouveau. Ces commandes ne sont pas compliquées, mais durant la phase de mise au point on peut être amené à les taper plusieurs dizaines de fois par jour. J'ai donc l'habitude de créer deux petits scripts, que j'appelle simplement «rw» et «ro», qui sont installés sur la cible et permettent très rapidement de changer le mode d'accès à l'arborescence.

Nous allons placer nos deux scripts dans une arborescence spécifique au sein de l'external tree créé plus haut. Ensuite nous indiquerons à Buildroot de venir ajouter le contenu de cette arborescence dans le système de fichiers de la cible.

Je crée un premier répertoire dans l'external tree, correspondant à la racine de mon arborescence :

[build-pi4]$ cd  ../pi4-config/
[pi4-config]$ mkdir  custom-rootfs 

Je souhaite placer mes scripts dans le répertoire «/usr/bin» de ma cible. Je crée donc ce repertoire dans mon arborescence :

[pi4-config]$ mkdir  -p  custom-rootfs/usr/bin/ 

Puis j'y crée le script »rw» 

[pi4-config]$ nano  custom-rootfs/usr/bin/rw 

Avec le contenu suivant :

#!/bin/sh

mount / -o remount,rw 

Et le script «ro» :

[pi4-config]$ nano  custom-rootfs/usr/bin/ro 

contenant :

#!/bin/sh

mount / -o remount,ro 

N'oublions pas de rendre les scripts exécutables :

[pi4-config]$ chmod  +x  custom-rootfs/usr/bin/* 

Il faut indiquer à Buildroot la présence de cette nouvelle arborescence à venir ajouter, superposer à celle d'origine. Il s'agit de l'entrée «Root filesystem overlay directories» du menu «System configuration». Comme précédemment, on fait référence au répertoire de l'external tree.

[pi4-config]$ cd  ../build-pi4/ 
[build-pi4]$ make  menuconfig

Je remplis donc l'entrée «Root filesystem overlay directories» ainsi :

$(BR2_EXTERNAL_PI4_CONFIG_PATH)/custom-rootfs

Lançons la nouvelle production d’image (qui ne dure que quelques dizaines de secondes, le temps de compiler Dropbear, Nano et de reconstruire l'image).

Welcome on board!
R-Pi login: root
Password: (root) 

Vérifions si le serveur «dropbear» est bien démarré et si la commande «nano» est présente :

# ps |  grep  drop
  144 root     /usr/sbin/dropbear -R
  156 root     grep drop
# nano  --version
 GNU nano, version 4.7
 (C) 1999-2011, 2013-2019 Free Software Foundation, Inc.
 (C) 2014-2019 the contributors to nano
 Email: nano@nano-editor.org    Web: https://nano-editor.org/
 Compiled options: --enable-tiny --disable-nls --disable-utf8 

Les packages que nous avons demandés sont bien présents. Le système de fichiers doit toujours être en lecture seulement, vérifions si notre script de passage en lecture-écriture fonctionne :

# touch  my-file
touch: my-file: Read-only file system
# rw
[  244.951926] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null) 
# touch  my-file
# ls
my-file
# 

Parfait, revenons en lecture seule :

# ro
[  266.057973] EXT4-fs (mmcblk0p2): re-mounted. Opts: (null) 
# rm  -f  my-file
rm: can't remove 'my-file': Read-only file system
#

Notre système est bien sécurisé contre les coupures d'alimentation et il nous offre suffisament de souplesse pour intervenir dans le système de fichiers si besoin.

VI.3 - Surcharge de fichiers de configuration

Nous n'avons pas encore utilisé le réseau. Si nous connectons un câble Ethernet avant de démarrer le Raspberry Pi, celui-ci peut obtenir directement une adresse IP si un serveur DHCP est présent sur le réseau local :

Welcome on board!
R-Pi login: root
Password: (root)
# ip  addr
1: lo: >LOOPBACK,UP,LOWER_UP< mtu 65536 qdisc noqueue qlen 1000 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: >BROADCAST,MULTICAST,UP,LOWER_UP< mtu 1500 qdisc mq qlen 1000 
    link/ether dc:a6:32:02:57:3d brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.11/24 brd 192.168.3.255 scope global eth0 
       valid_lft forever preferred_lft forever
    inet6 2a01:e0a:22:ea90:dea6:32ff:fe02:573d/64 scope global dynamic 
       valid_lft 86366sec preferred_lft 86366sec
    inet6 fe80::dea6:32ff:fe02:573d/64 scope link 
       valid_lft forever preferred_lft forever
#

Il est toutefois des situations où l'on préfère utiliser une adresse IP fixe. Pour cela, il faut modifier le fichier «/etc/network/interfaces». De même, le serveur DHCP fournit ici l'adresse du serveur DNS local. Nous pouvons en fixer un statiquement dans «/etc/resolv.conf».

Le plus simple consiste à surcharger ces deux fichiers dans l'arborescence external tree :

[build-pi4]$ cd  ../pi4-config/
[pi4-config]$ mkdir  -p  custom-rootfs/etc/network/
[pi4-config]$ nano  custom-rootfs/etc/network/interfaces 

Contenu du fichier «interfaces» (bien entendu il faut l'ajuster selon la situation) :

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
  address 192.168.3.200
  netmask 255.255.255.0
  gateway 192.168.3.254 
[pi4-config]$ nano  custom-rootfs/etc/resolv.conf 

Contenu du fichier «resolv.conf» :

nameserver 1.1.1.1 

Après avoir regénéré l'image, l'avoir copiée sur carte micro-SD et démarré le Raspberry Pi, je me connecte en SSH depuis mon PC :

[build-pi4]$ ssh  root@192.168.3.200
The authenticity of host '192.168.3.200 (192.168.3.200)' can't be established. 
ECDSA key fingerprint is SHA256:sUFGwSyZCG/gWkEO4r9OmGgMFnZgs1LuT9fQgESIdTs.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.3.200' (ECDSA) to the list of known hosts.
root@192.168.3.200's password: (root)
# uname  -a
Linux R-Pi 4.19.97-v7l #1 SMP Sun Mar 8 23:43:48 CET 2020 armv7l GNU/Linux
# 

Je peux vérifier que le Raspberry Pi accède également à Internet :

# ping  www.kernel.org
PING www.kernel.org (136.144.49.103): 56 data bytes
64 bytes from 136.144.49.103: seq=0 ttl=53 time=17.657 ms
64 bytes from 136.144.49.103: seq=1 ttl=53 time=17.413 ms
64 bytes from 136.144.49.103: seq=2 ttl=53 time=17.844 ms 
^C
--- www.kernel.org ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 17.413/17.638/17.844 ms
# wget  https://www.kernel.org
wget: not an http or ftp url: https://www.kernel.org
# exit
Connection to 192.168.3.200 closed.
[build-pi4]$

Notre cible accède bien à Internet, mais nous ne pouvons pas télécharger une page en utilisant le protocole HTTPS, la commande «wget» n'acceptant que HTTP ou FTP.

VI.4 - Utilitaires implémentés par Busybox

La commande «wget» est implémentée en utilisant l'utilitaire Busybox. Nous pouvons modifier la configuration de ce dernier avec une commande particulière de Buildroot :

[build-pi4]$ make  busybox-menuconfig 

Un écran de configuration ressemblant à celui de Buildroot apparaît, visible sur la figure 17 :

Configuration de Busybox
Figure 17 - Configuration de Busybox

Je me rends dans le menu «Networking Utilities» que l'on voit sur la figure 18, et je valide l'option «Support HTTPS using internal TLS code» pour l'application «wget».

Option HTTPS de «wget»
Figure 18 - Option HTTPS de «wget».

Bien entendu, on en profitera pour regarder les commandes implémentées dans Busybox, et en ajouter — ou en retirer — quelques unes suivant la destination du système embarqué.

Après compilation, installation et reboot du Raspberry Pi, nous observons :

# wget  https://www.kernel.org
Connecting to www.kernel.org (136.144.49.103:443)
wget: note: TLS certificate validation not implemented 
wget: can't open 'index.html': Read-only file system
#

Et oui, «wget» ne peux pas écrire dans le répertoire courant puisque le système est en lecture seulement. Je me déplace alors dans «/tmp» sur lequel est monté un «tmpfs» (une sorte de ramdisk) utilisable en écriture :

# cd  /tmp
# wget  https://www.kernel.org
Connecting to www.kernel.org (136.144.49.103:443)
wget: note: TLS certificate validation not implemented
saving to 'index.html'
index.html           100% |********************************| 16749  0:00:00 ETA 
'index.html' saved
#

Tout s'est bien passé, «wget» a pu télécharger une page en utilisant le protocole HTTPS.

VII - Ajout de code métier

Il est rare qu'un système embarqué soit composé uniquement d'applicatifs standards et de fichiers de configuration. Cela m'est arrivé lorsqu'il s'agissait de faire de petits serveurs pour des tâches simples (référence NTP, serveur DHCP, DNS local, etc.) mais ce n'est pas le cas le plus courant d'utilisation de Buildroot.

Si nous devons ajouter du code métier, il y a plusieurs approches possibles. Tout d'abord, pendant une phase de développement, nous allons devoir coder et tester notre projet indépendamment du BSP. Pour cela nous devons disposer de la toolchain produite par Buildroot.

VII.1 - Extraction de la toolchain

Il est possible d'appeler directement le cross-compiler fourni par Buildroot en appelant les exécutables «arm-linux-gcc» ou «arm-linux-g++» qui se trouvent dans le sous-répertoire «host/bin/» de notre répertoire de travail.

[build-pi4]$ host/bin/arm-linux-gcc  --version
arm-linux-gcc.br_real (Buildroot 2020.02) 8.3.0
Copyright © 2018 Free Software Foundation, Inc.
Ce logiciel est un logiciel libre; voir les sources pour les conditions de copie.  Il n'y a
AUCUNE GARANTIE, pas même pour la COMMERCIALISATION ni L'ADÉQUATION À UNE TÂCHE PARTICULIÈRE. 
[build-pi4]$ 

Toutefois ceci peut s'avérer gênant si le développeur applicatif est dans une équipe différente de celle du concepteur du BSP. Pour cela, Buildroot nous permet d'extraire la toolchain et de l'installer sur une autre machine.

On appelle :

[build-pi4]$ make  sdk

qui nous fournit une archive :

[build-pi4]$ ls  images/
arm-buildroot-linux-uclibcgnueabihf_sdk-buildroot.tar.gz  rootfs.ext4 
bcm2711-rpi-4-b.dtb                                       rpi-firmware 
boot.vfat                                                 sdcard.img
rootfs.ext2                                               zImage

Nous pouvons copier cette archive sur une autre machine ou à un endroit plus approprié de la même machine, et l'extraire. Par exemple je vais la placer dans «/opt/» sur mon poste.

[build-pi4]$ sudo  tar  xf  images/arm-buildroot-linux-uclibcgnueabihf_sdk-buildroot.tar.gz  -C  /opt/ 
[build-pi4]$ ls  /opt/
arm-buildroot-linux-uclibcgnueabihf_sdk-buildroot
picomono
picoscope
poky
sublime_text
[build-pi4]$

On peut raccourcir le nom du répertoire :

[build-pi4]$ sudo  mv  /opt/arm-buildroot-linux-uclibcgnueabihf_sdk-buildroot/  /opt/cross-br-2020.02 
[build-pi4]$ ls  /opt/cross-br-2020.02/
arm-buildroot-linux-uclibcgnueabihf  include  libexec          share
bin                                  lib      relocate-sdk.sh  usr
etc                                  lib64    sbin

Puis il faut appeler le script «relocate-sdk» se trouvant dans ce répertoire pour qu'il enregistre le nouvel emplacement. Il faut l'invoquer avec les droits suffisants pour pouvoir écrire dans les fichiers concernés.

[build-pi4]$ sudo  /opt/cross-br-2020.02/relocate-sdk.sh 
Relocating the buildroot SDK from /home/cpb/br-lab/build-pi4/host to /opt/cross-br-2020.02 ... 
[build-pi4]$

VII.2 - Compilation par appel direct du cross-compiler

N'importe quel utilisateur peut alors appeler le cross-compiler.

[build-pi4]$ /opt/cross-br-2020.02/bin/arm-linux-g++  --version
arm-linux-g++.br_real (Buildroot 2020.02) 8.3.0
Copyright © 2018 Free Software Foundation, Inc.
Ce logiciel est un logiciel libre; voir les sources pour les conditions de copie.  Il n'y a
AUCUNE GARANTIE, pas même pour la COMMERCIALISATION ni L'ADÉQUATION À UNE TÂCHE PARTICULIÈRE. 
[build-pi4]$ 

Supposons par exemple que nous souhaitions compiler un exécutable comme celui-ci :

// hello-direct.cpp

#include <iostream>

int main()
{
        std::cout << "Hello World! (direct call to the cross-compiler)"; 
        std::cout << std::endl;

        return 0;
}

Nous pouvons appeler le cross-compiler «arm-linux-g++» directement en ajoutant son emplacement dans la variable «PATH» de notre session shell.

[build-pi4]$ PATH=$PATH:/opt/cross-br-2020.02/bin/
[build-pi4]$ arm-linux-g++  hello-direct.cpp  -o  hello-direct  -Wall 

Ce fichier peut être transféré avec «scp» sur notre cible ou placé dans l'arborescence de notre external tree. Je choisis cette seconde solution. Elle nécessite bien sûr un rebuild de l'image.

[build-pi4]$ cp  hello-direct  ../pi4-config/custom-rootfs/usr/bin/ 
[build-pi4]$ make
Welcome on board!
R-Pi login: root
Password: (root)
# hello-direct
Hello World! (direct call to the cross-compiler) 

Naturellement, il est possible d'invoquer le compilateur au travers d'un «Makefile» en remplissant les variables d'environnement adéquante (souvent «CROSS_COMPILE»).

VII.3 - Écriture de recettes pour des projets métiers

La meilleure manière d'intégrer un projet disposant d'une structure de compilation comme «automake», «Cmake» ou même un simple «Makefile» est d'écrire une recette que nous ajouterons dans notre external tree et que Buildroot se chargera de produire avec le reste de l'image.

Il y a de très nombreuses recettes fournies avec Buildroot dans le répertoire «package/» de ses sources, elles peuvent servir d'exemples pour compiler des applications supplémentaires.

Prenons un premier exemple, nous souhaitons installer un package qui se compile avec les Autools.

Nous créons dans notre external tree un répertoire qui accueillera les différentes recettes que nous souhaitons ajouter. Pour être cohérent avec l'organisation de Buildroot, je le nomme «package».

[build-pi4]$ cd  ../pi4-config/ 
[pi4-config]$ mkdir  package
[pi4-config]$

Dans ce répertoire nous créons un sous-répertoire dédié au projet concerné. Je vais prendre le package «hello-autotools» que j'avais écrit en exemple de mon cours en ligne sur Yocto Project.

[pi4-config]$ mkdir  package/hello-autotools 

Dans ce répertoire nous devons créer trois fichiers :

Voici le contenu du premier fichier, «package/hello-autotools/Config.in» :

config BR2_PACKAGE_HELLO_AUTOTOOLS
    bool "hello-tools"
    help
        This is an example of a package built with the autotools. 

A présent, voici la recette de compilation «package/hello-autotools/hello-autotools.mk». Du fait que nous employons le mécanisme des autotools elle est très simple et se borne à indiquer la provenance du package.

################################################################################ 
#
# hello-autotools
#
################################################################################

HELLO_AUTOTOOLS_VERSION = 1.0
HELLO_AUTOTOOLS_SOURCE = hello-autotools-$(HELLO_AUTOTOOLS_VERSION).tar.bz2
HELLO_AUTOTOOLS_SITE = https://www.blaess.fr/christophe/yocto-lab/files
HELLO_AUTOTOOLS_AUTORECONF = YES
HELLO_AUTOTOOLS_LICENSE = GPL-2.0

$(eval $(autotools-package))

Dès qu'il y a un téléchargement de package, il est conseillé de vérifier une somme de contrôle. Pour cela, on emploie l'utilitaire «sha256sum» et l'on inscrit la checksum dans le fichier «package/hello-autotools/hello-autotools.hash».

sha256  ad06e44345fc85e061932da4bdda964c65f1dc56fbc07ea03ea8b93c68065cfe  hello-autotools-1.0.tar.bz2 

Notre recette de compilation pour le package est prête. Mais elle ne sera intégrée par Buildroot que si nous lui indiquons où elle se trouve. Pour cela nous éditons le fichier «external.mk» à la racine de notre external tree (fichier précédemment vide) et ajoutons la ligne suivante :

include $(sort $(wildcard $(BR2_EXTERNAL_PI4_CONFIG_PATH)/package/*/*.mk)) 

Cette ligne demande à Buildroot d'explorer toutes les recettes se trouvant dans le sous-répertoire «package» de notre external tree.

Enfin nous ajoutons dans le fichier «Config.in» de notre external tree (précédemment vide également) les lignes suivantes qui y définissent un menu incluant le «Config.in» de notre package.

menu "Custom packages"

source "$BR2_EXTERNAL_PI4_CONFIG_PATH/package/hello-autotools/Config.in" 

endmenu

Lorsque nous relançons la commande «make menuconfig» de Buildroot, le menu «External options» s'est rempli, comme nous le voyons sur la figure 19.

[pi4-config]$ cd  ../build-pi4/ 
[build-pi4]$ make  menuconfig 
Menu «External Options»
Figure 19 - Menu «External Options».

Dans ce nouveau menu, nous voyons notre option «hello-tools» comme on le voit sur la figure 20. Bien sûr, j'active cette option avant de relancer le build.

Option «hello-autotools»
Figure 20 - Option «hello-autotools».

Pendant la construction du système, on voit passer brièvement les messages indiquant le téléchargement, l'extraction et la compilation de notre package.

Vérifions sa présence sur la cible :

R-Pi login: root
Password: (root)
# hello-autotools 
Hello from R-Pi (built with autotools) 
# 

De la même façon, j'ai ajouté des recettes pour compiler un package utilisant CMake ou avec un simple «Makefile» classique. Je ne les détaille pas ici, ils se trouvent dans l'archive que l'on trouvera plus loin.

VII.4 - Lancement automatique au démarrage

Buildroot utilise de préférence un mécanisme de lancement des services au démarrage basé sur le processus «init» à la mode System V fourni par «Busybox».

Il est toujours possible, dans le sous-menu «Init system» du menu «System configuration», de choisir un démarrage avec «systemd» mais on l'évite généralement car la maîtrise de la séquence d'initialisation des services est plus simple avec les scripts System V.

Si on veut lancer une application au démarrage, il nous suffit d'ajouter un script dans le répertoire «etc/init.d» de la cible dont le nom commence par un «S» suivi d'un numéro d'ordre de démarrage. Ce script sera appelé au boot du système avec l'argument «start» et à l'arrêt du système avec l'argument «stop».

Voici ce que contient le répertoire «etc/init.d» initial :

# ls  /etc/init.d/
S01syslogd   S02sysctl    S40network   rcK
S02klogd     S20urandom   S50dropbear  rcS
# 

J'ajoute un script de lacement dans l'arborescence de l'external tree :

[build-pi4]$ mkdir  ../pi4-config/custom-rootfs/etc/init.d 
[build-pi4]$ nano  ../pi4-config/custom-rootfs/etc/init.d/S60hello 

Le contenu de ce fichier est le suivant :

#!/bin/sh

if [ "$1" = "start" ]
then
        /usr/bin/hello-autotools &
fi

Ce script est très simple car il ne fait que lancer une commande. Il y a des situations où il est plus complexe car il faut arrêter le service lors du shutdown du système. J'ai placé un «&» à la fin de la commande, cela n'a aucune utilité ici puisqu'elle se termine immédiatement, mais pourrait servir dans le cas d'une application qui reste en fonction de manière prolongée car le script doit rendre la main une fois le programme démarré.

Il ne faut pas oublier de le rendre exécutable :

[build-pi4]$ chmod  +x  ../pi4-config/custom-rootfs/etc/init.d/S60hello 

Une fois le système recompilé et installé, on vérifie les traces de boot du Raspberry Pi :

random: dropbear: uninitialized urandom read (32 bytes read)
OK
Hello from R-Pi (built with autotools)
Welcome on board!
R-Pi login:

Notre processus a donc bien été lancé automatiquement au boot.

VIII - Ajout d'une partition utilisateur

Notre système fonctionne de manière plutôt satisfaisante :

Welcome on board!
R-Pi login: rpi
Password: (rpi)
$ hello-autotools
Hello from R-Pi (built with autotools)
$ hello-cmake
Hello from R-Pi (built with CMake)
$ hello-makefile
Hello from R-Pi (build with standard Makefile) 

Un petit problème survient si on souhaite écrire dans un fichier :

$ hello-makefile > my-file.txt
-sh: can't create my-file.txt: Read-only file system 

C'est normal, la partition système est en lecture seulement, nous avons écrit un script pour y remédier :

$ rw
mount: you must be root 
$

Voilà un véritable problème. Nous avons protégé notre partition système en la plaçant en lecture seule. Pour pouvoir y faire quand même des modifications, nous avons prévu des scripts «rw» et «ro» (en étant conscient que pendant la période où le système est en lecture-écriture, une coupure d'alimentation peut lui ètre fatale). Mais ces scripts ne peuvent être appelés que par «root», pas par un utilisateur (ou une application métier) s'exécutant sous une identité quelconque.

D'autre part, on sent bien que le fait de mettre temporairement la partition système en lecture-écriture est un risque. Si une coupure d'alimentation survient juste à ce moment le système de fichiers peut être corrompu. Et la probabilité que l'utilisateur débranche et rebranche le système juste après avoir fait une modification de configuration pour voir si elle a été prise en compte est assez élevée.

Si nous souhaitons pouvoir sauvegarder des données d'application ou des éléments de paramétrage, il va falloir envisager l’ajout d’une partition supplémentaire montée en lecture-écriture à côté de la partition système.

Cette partition peut être formatée en «vfat», système de fichiers simple et robuste qui résiste plutôt bien à des coupures d’alimentations pendant une écriture.

Il faut indiquer la présence de cette partition dans le fichier «/etc/fstab». Nous allons récupérer le fichier original produit par Buildroot (qui se trouve dans le sous-répertoire «target/etc/» de notre dossier de compilation), le copier dans notre external tree et lui ajouter une ligne pour cette nouvelle partition.

[build-pi4]$ cp  target/etc/fstab  ../pi4-config/custom-rootfs/etc/ 
[build-pi4]$ nano  ../pi4-config/custom-rootfs/etc/fstab

Le fichier est modifié ainsi (dernière ligne ajoutée) :

# <file system> <mount pt>      <type>  <options>       <dump>  <pass>
/dev/root       /               ext2    ro,noauto       0       1
proc            /proc           proc    defaults        0       0
devpts          /dev/pts        devpts  defaults,gid=5,mode=620,ptmxmode=0666   0       0
tmpfs           /dev/shm        tmpfs   mode=0777       0       0
tmpfs           /tmp            tmpfs   mode=1777       0       0
tmpfs           /run            tmpfs   mode=0755,nosuid,nodev  0       0
sysfs           /sys            sysfs   defaults        0       0
/dev/mmcblk0p3  /home/rpi       vfat    defaults,uid=1000,gid=1000   0   0

Cette nouvelle partition sera montée directement sur «/home/rpi», elle appartiendra toute entière à l'utilisateur «rpi» créé précédemment, et sera accessible en lecture et écriture.

Pour créer les partitions et l’image «sdcard.img» finale, Buildroot appelle le script «board/raspberrypi4/post-image.sh» qui est indiqué dans l’option «Custom scripts to run after creating filesystem images» du menu «System configuration». Ce script fait appel à un utilitaire nommé «genimage» en lui passant le fichier de configuration «board/raspberrypi4/genimage-raspberrypi4.cfg».

Pour personnaliser cette étape, je vais copier ces deux fichiers dans l'external path en respectant la même structure de sous-dossiers, et les personnaliser ensuite.

[build-pi4]$ cd  ../pi4-config/
[pi4-config]$ mkdir  -p  board/raspberrypi4/
[pi4-config]$ cp  ../buildroot-2020.02/board/raspberrypi4/post-image.sh  board/raspberrypi4/
[pi4-config]$ cp  ../buildroot-2020.02/board/raspberrypi4/genimage-raspberrypi4.cfg  board/raspberrypi4/ 

Le script «post-image.sh» fait référence à son propre emplacement dans l'arborescence pour chercher le fichier de description des partitions. Il n'est donc pas nécessaire de le modifier, juste de le placer dans l'external tree.

J'édite le fichier de configuration «genimage-raspberrypi4.cfg» et j'ajoute les lignes en surbrillance :

[pi4-config]$ nano  board/raspberrypi4/genimage-raspberrypi4.cfg 

image boot.vfat {
  vfat {
    files = {
      "bcm2711-rpi-4-b.dtb",
      "rpi-firmware/cmdline.txt",
      "rpi-firmware/config.txt",
      "rpi-firmware/fixup4.dat",
      "rpi-firmware/start4.elf",
      "rpi-firmware/overlays",
      "zImage"
    }
  }
  size = 32M
}

image pi-home.vfat {
  name = "pi-home.vfat"
  vfat {
    files = { }
  }
  empty = true
  size = 128M
}

image sdcard.img {

  hdimage {
  }

  partition boot {
    partition-type = 0xC
    bootable = "true"
    image = "boot.vfat"
  }

  partition rootfs {
    partition-type = 0x83
    image = "rootfs.ext4"
  }

  partition pi-home.vfat {
    partition-type = 0xC
    image  = "pi-home.vfat"
  }
}

Avant de relancer la génération d'une image, il faut indiquer à Buildroot de venir chercher le script dans notre external tree plutôt que dans le répertoire initial. Ceci se configure dans l'option «Custom scripts to run after creating filesystem image» (avant-dernière ligne) du menu «System configuration» comme on le voit sur la figure 21.

Configuration de Busybox dans notre <em>external tree</em>
Figure 21 - Configuration de Busybox dans notre external tree.
[pi4-config]$ cd  ../build-pi4/ 
[build-pi4]$ make menuconfig 

Après installation, nous vérifions si l'utilisateur «rpi» a bien accès à son répertoire personnel en lecture et écriture :

Welcome on board!
R-Pi login: rpi
Password: (rpi)
$ pwd
/home/rpi
$ echo  hello  >  my-file 
$ cat  my-file
hello
$exit

Vérifions que la partition système est seulement accessible en lecture, même pour l'utilisateur «root» :

Welcome on board!
R-Pi login: root
Password: (root)
# pwd
/root
# echo  hello  >  my-file 
-sh: can't create my-file: Read-only file system
# 

Notre image est ainsi robuste et insensible aux coupures d'alimentation en ce qui concerne son arborescence système. La partition utilisateur permet d'enregistrer des données, et en cas d'arrêt brutal seules les données en cours d'écriture seront perdues, le système «vfat» étant assez résilient.

IX - Sauvegarde des paramétrages réalisées

IX.1 - Sauvegarde de la configuration de Busybox

Afin de pouvoir aisément réutiliser notre configuration dans un build pour une autre cible par exemple — ou pour la prochaine version de Buildroot — nous allons sauvegarder cette configuration dans notre external tree.

La configuration de Busybox est sauvegardée dans un fichier «.config» ressemblant à celui de Buildroot ou du kernel. Ce fichier se trouve dans le sous-répertoire dans lequel Buildroot a fait la compilation de Busybox :

[build-pi4]$ ls  -a  build/busybox-1.31.1/
.                        editors                 networking
..                       examples                NOFORK_NOEXEC.lst
applets                  .files-list.txt         NOFORK_NOEXEC.sh
applets_sh               findutils               printutils
.applied_patches_list    include                 procps
arch                     .indent.pro             qemu_multiarch_testing
archival                 init                    README
AUTHORS                  INSTALL                 runit
busybox                  .kconfig.d              scripts
busybox.links            .kernelrelease          selinux
busybox_unstripped       klibc-utils             shell
.busybox_unstripped.cmd  libbb                   size_single_applets.sh
busybox_unstripped.map   libpwdgrp               .stamp_dotconfig
busybox_unstripped.out   LICENSE                 .stamp_downloaded
.config                  loginutils              .stamp_extracted
Config.in                mailutils               .stamp_kconfig_fixup_done 
.config.old              Makefile                .stamp_patched
configs                  Makefile.custom         sysklogd
console-tools            Makefile.flags          testsuite
coreutils                Makefile.help           .tmp_versions
debianutils              make_single_applets.sh  TODO
docs                     miscutils               TODO_unicode
e2fsprogs                modutils                util-linux
[build-pi4]$

Nous copions ce fichier dans notre external tree en lui donnant un nom un peu plus explicite :

[build-pi4]$ cp  build/busybox-1.31.1/.config  ../pi4-config/configs/busybox.config 
[build-pi4]$ 

Et nous indiquons à Buildroot où trouver ce fichier de configuration. Pour cela, nous nous rendons en haut de son menu «Target packages» et remplissons l'option «Busybox configuration file to use?» (la deuxième ligne) avec la chaîne «$(BR2_EXTERNAL_PI4_CONFIG_PATH)/configs/busybox.config» comme on le voit sur la figure 22.

Configuration de Busybox dans notre <em>external tree</em>
Figure 22 - Configuration de Busybox dans notre external tree.

La même méthode pourrait s'appliquer à la configuration du kernel, à laquelle on peut accéder avec la commande «make linux-menuconfig» et dont la configuration est enregistrée dans le fichier «build/linux-custom/.config».

IX.2 - Sauvegarde de la configuration de Buildroot

Il est possible de sauver la configuration de Buildroot lui-même dans notre external tree afin de pouvoir la réutiliser ultérieurement sous forme de defconfig et regénérer le système sur une autre machine par exemple.

Tout d'abord nous allons indiquer à Buildroot l'emplacement de la sauvegarde en utilisant l'option «Location to save buildroot config» du menu «Build options» (deuxième ligne). Nous donnons le chemin vers le répertoire «configs» de notre external tree. Le nom du fichier se termine par «_defconfig» pour pouvoir l'utiliser comme configuration par défaut. Nous apercevons le chemin et le nom sur la figure 23, je l'ai appelé «custom_pi4_defconfig :

Sauvegarde de la configuration de Buildroot
Figure 23 - Sauvegarde de la configuration de Buildroot.

Une fois cette opération réalisée, il nous suffit de demander à Buildroot de sauver les élements essentiels de sa configuration ainsi :

[build-pi4]$ make  savedefconfig
  GEN     /home/cpb/br-lab/build-pi4/Makefile
[build-pi4]$ ls  ../pi4-config/configs/
busybox.config  custom_pi4_defconfig  users.tbl
[build-pi4]$

IX.3 - Restauration d'une configuration de Buildroot

Maintenant que notre configuration par défaut est enregistrée dans notre external tree, celui-ci contient :

Il s'agit uniquement de données textuelles (configuration, recettes, etc) que l'on peut versionner avec Git par exemple.

On peut surtout transmettre cet external tree à un autre développeur — c'est ce que je fais pour mes clients — pour qu'il puisse regénérer le système à l'identique. Il lui faudra juste lancer une ligne un petit peu complexe détaillé plus bas.

Je vais tester que tout se passe bien. Commençons par effacer toute la compilation faite sur cette machine et télécharger notre external tree que l'on peut trouver ici :

https://www.blaess.fr/christophe/buildroot-lab/pi4-config.tar.bz2

br-lab]$ ls
build-pi4  buildroot-2020.02  buildroot-2020.02.tar.bz2  dl  pi4-config 
[br-lab]$ chmod  -R  +w  buildroot-2020.02
[br-lab]$ rm  -rf  build-pi4/ pi4-config/ buildroot-2020.02/ 
[br-lab]$ ls
buildroot-2020.02.tar.bz2  dl

Puis je recommence à zéro le build en utilisant notre configuration par défaut personnalisée :

[br-lab]$ tar  xf  buildroot-2020.02.tar.bz2
[br-lab]$ chmod  -R  -w  buildroot-2020.02 
[br-lab]$ ls
buildroot-2020.02  buildroot-2020.02.tar.bz2  dl
[br-lab]$ wget  https://www.blaess.fr/christophe/buildroot-lab/pi4-config.tar.bz2 
  [...]
[br-lab]$ tar  xf  pi4-config.tar.bz2 
[br-lab]$ ls
buildroot-2020.02  buildroot-2020.02.tar.bz2  dl  pi4-config  pi4-config.tar.bz2
[br-lab]$ cd  buildroot-2020.02/

Voici la commande un peu complexe, qui joue trois rôles : demander à Buildroot d'utiliser un external tree, de compiler le code dans un répertoire de travail indépendant de ses sources, et d'utiliser la configuration que nous avons sauvegardée précédemment :

[buildroot-2020.02]$ make  O=../build-pi4  BR2_EXTERNAL=../pi4-config/  custom_pi4_defconfig 
  [...]
#
# configuration written to /home/cpb/br-lab/build-pi4/.config
#
[buildroot-2020.02]$

Il ne nous reste plus qu'à relancer la compilation, et attendre quelques dizaines de minutes :

 cd  ../build-pi4/
[buildroot-2020.02]$ cd  ../build-pi4/
[build-pi4]$ make

X - Conclusion

Nous avons ainsi réussi à obtenir un système robuste, dont le contenu est parfaitement maîtrisé dans lequel nous pouvons ajouter à notre gré des applicatifs standards (il y a des centaines de packages disponibles directement avec Buildroot) et notre code métier.

L'utilisation d'un external path est un peu complexe à prendre en main mais permet de garantir la réutilisabilité de la configuration pour une autre plateforme cible, pour un autre poste de build ou pour une version ultérieure de Buildroot.

Il y a de nombreux points que nous n'avons pas abordés, comme la mise à jour du système, l'utilisation d'un bootloader (U-Boot, Barebox...), la modification du device tree, l'utilisation d'un noyau temps réel, la création de patches pour des applications ou pour le kernel, etc. Ils feront peut-être l'objet d'articles ultérieurs...

Si vous préférez être conseillés pour la conception de votre système embarqué avec Buildroot, je propose un accompagnement technique et des sessions de formation consacrées à ce sujet 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.