Système embarqué Linux avec Buildroot, Qemu et Raspberry Pi 3

Publié le 2021-03-07

Un autre article sur l'utilisation de Buildroot avec un Raspberry Pi, quelque peu similaire au précédent à ceci près qu'il comporte davantage de détails. Il présente notamment l'intégration d'éléments externes à Buildroot (comme une application) et la prise en charge du réseau sous Qemu. Nous utilisons ici Buildroot 2021.02-rc3 pour obtenir une image 64 bits compatible avec le Raspberry Pi 3.

Paramétrage de l'image, du noyau et Busybox

Commençons par télécharger Buildroot depuit son dépôt Github

git clone https://github.com/buildroot/buildroot
cd buildroot

Paramétrage de l'image

Charger la configuration par défaut pour Raspberry Pi 3 qui nous servira de base pour être ajustée selon nos besoins

make raspberrypi3_64_defconfig

Paramétrage de l'image buildroot

make menuconfig

Quelques modifications depuis la configuration de base raspberrypi3_64_defconfig:

Option Emplacement Symbole Kconfig
Enable root login with password System configuration BR2_TARGET_ENABLE_ROOT_LOGIN
Root password System configuration BR2_TARGET_GENERIC_ROOT_PASSWD
System hostname System configuration BR2_TARGET_GENERIC_HOSTNAME
System banner System configuration BR2_TARGET_GENERIC_ISSUE
remount root filesystem read-write during boot System configuration BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW
exact size * Filesystem images BR2_TARGET_ROOTFS_EXT2_SIZE

* Attention à ne pas mettre une valeur trop petite car Buildroot sera incapable de faire tenir le système sur une image sous-dimensonnée.

La configuration Buildroot peut être sauvegardée à un endroit autre que celui par défaut (buildroot/.config). Cela va nous permettre plus tard d'exporter la configuration dans un arbre externe.

make savedefconfig BR2_DEFCONFIG=./buildroot_config

Pour la charger dans Buildroot:

make defconfig BR2_DEFCONFIG=./buildroot_config

Paramétrage du noyau Linux

Paramétrer le noyau:

make linux-menuconfig

Une option importante à définir est la liste des paramètres passés au noyau:

Boot options
└── Default kernel command string (CMDLINE)

console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait

Pour fournir un accès réseau à la machine virtuelle Qemu, il faut le module cdc_ether pour utiliser l'ethernet sur USB car le Raspberry ne possède pas de contrôleur permettant d'utiliser VirtIO:

Device Drivers
└── Network device support (NETDEVICES)
    └── USB Network Adapters (USB_NET_DRIVERS)
        └── Multi-purpose USB Networking Framework (USB_USBNET)
            └── CDC Ethernet support (smart devices such as cable modems) (USB_NET_CDCETHER)

Paramétrage de BusyBox

Paramétrer BusyBox permet de sélectionner les binaires à intégrer comme ls, find, xargs, etc. Il ne peuvent pas tous être exclus car certains sont utilisés dans des scripts systèmes (par exemple xargs et touch).

make busybox-menuconfig

Création d'un arbre externe contenant une application

Arborescence

Nous allons créer un arbre externe qui va nous permettre de stocker toutes les particularités pour notre image (applications, overlays, patches pour le noyau, configurations).

La structure du dossier br-external (arbre externe), dans la racine Buildroot:

br-external/
├── config                      Stocke nos configurations Buildroot et Linux
├── Config.in                   Regroupe dynamiquement tous les descriptifs Kconfig des packages
├── external.desc               Nom de l'arbre, conditionne la variable $BR2_EXTERNAL_***_PATH
├── external.mk                 Regroupe dynamiquement les Makefiles des packages
├── linux-patches               Pour les patchs du noyau (non utilisé ici)
├── package                     Les packages (applications)
│   └── hello                   Notre application de test
│       ├── Config.in           Descriptif Kconfig
│       ├── S99hello            Script de démarrage init.d
│       ├── external.mk         Script Buildroot pour la compilation/installation
│       └── src             
│           ├── hello.c         Fichier source
│           └── Makefile        Makefile
└── rootfs_overlay              Fichiers à ajouter dans l'image (non utilisé ici)

Fichier br-external/external.desc

name: MONDEPOT
desc: Exemple arbre externe

Fichier br-external/external.mk

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

Intégration d'une application (package)

L'intégration d'une application hello dans Buildroot consiste à créer un package qui contient le code source et les directives (Makefile) pour la compilation et l'installation.

Fichier br-external/Config.in

source "$BR2_EXTERNAL_MONDEPOT_PATH/package/hello/Config.in"

Fichier br-external/package/hello/Config.in

config BR2_PACKAGE_HELLO
    bool "hello"
    help
      Application externe de test hello.

      https://cboyer.github.io/

Fichier br-external/package/hello/external.mk

################################################################################
#
# hello
#
################################################################################

HELLO_VERSION = 1.0
HELLO_SITE = $(BR2_EXTERNAL_MONDEPOT_PATH)/package/hello/src
HELLO_SITE_METHOD = local

define HELLO_BUILD_CMDS
    $(MAKE) CC="$(TARGET_CC)" LD="$(TARGET_LD)" -C $(@D)
endef

define HELLO_INSTALL_TARGET_CMDS
    $(INSTALL) -D -m 0755 $(@D)/hello $(TARGET_DIR)/usr/bin
endef

define HELLO_INSTALL_INIT_SYSV
    $(INSTALL) -D -m 0755 $(HELLO_PKGDIR)/S99hello \
    $(TARGET_DIR)/etc/init.d/S99hello
endef

$(eval $(generic-package))

Fichier br-external/package/hello/src/hello.c

#include <stdio.h>

int main(void) {
    puts("hello");
    return 0;
}

Fichier br-external/package/hello/src/Makefile

CC = gcc

.PHONY: clean

hello: hello.c
    $(CC) -o '$@' '$<'

clean:
    rm hello

Fichier br-external/package/hello/S99hello

#!/bin/sh

case "$1" in
    start)
        printf "Starting hello: "
        /usr/bin/hello --start
        [ $? = 0 ] && echo "OK" || echo "FAIL"
        ;;
    stop)
        printf "Stopping hello: "
        /usr/bin/hello --stop
        [ $? = 0 ] && echo "OK" || echo "FAIL"
        ;;
    restart|reload)
        "$0" stop
        "$0" start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?

Les nouveaux packages de l'arbre externe (hello) sont accessibles depuis les paramètres Buildroot dans un nouveau menu External options qui est disponible avec:

make BR2_EXTERNAL=./br-external menuconfig

Intégration des configurations Buildroot et du noyau Linux

Nous pouvons copier notre fichier de configuration Buildroot et celui du noyau dans notre arbre externe. Ceci permet de regrouper tous les composants spécifiques à la création de l'image en les séparant des fichiers Buildroot d'origine.

Copier la configuration du noyau

cp output/build/linux-custom/.config br-external/config/linux.config

Pour charger automatiquement le fichier de configuration noyau par Buildroot, il faut activer l'utilisation d'une configuration spécifique puis saisir le chemin vers le fichier en question: $(BR2_EXTERNAL_MONDEPOT_PATH)/config/linux.config. L'opération est identique pour les patches, overlays et scripts en ajustant le dossier cible (non utilisés ici).

make BR2_EXTERNAL=./br-external menuconfig
Option Emplacement Symbole Kconfig
Using a custom (def)config file Kernel > Linux Kernel > Kernel configuration BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG
Configuration file path Kernel > Linux Kernel BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE
global patch directories Build options BR2_GLOBAL_PATCH_DIR
Root filesystem overlay directories System configuration BR2_ROOTFS_OVERLAY
Custom scripts to run after creating filesystem images System configuration BR2_ROOTFS_POST_IMAGE_SCRIPT

Copie du fichier de configuration Buildroot

make savedefconfig BR2_DEFCONFIG=./br-external/config/raspberrypi3_64_custom_defconfig

Pour recharger le fichier de configuration Buildroot depuis l'arbre externe

make defconfig BR2_DEFCONFIG=./br-external/config/raspberrypi3_64_custom_defconfig

Compilation et création de l'image

Pour compiler/recompiler séparément un élément (noyau, Busybox ou un package)

make linux-build
make busybox-build
make hello-build

#Nettoyage et recompilation
make linux-dirclean
make linux-rebuild

Pour générer l'image buildroot (comprend la compilation de tous les éléments qui compose l'image)

make

Pour retirer le package hello sans tout recompiler:

#Désélectionner le package hello dans External options
make BR2_EXTERNAL=./br-external menuconfig

#Retrait du binaire de l'image
rm output/target/usr/bin/hello 

#Supprime le dossier de compilation, équivaut à rm -rf output/build/hello-1.0/
make hello-dirclean
make

Exécuter l'image avec Qemu

Il s'agit de charger l'image avec Qemu en spécifiant les paramètres comme les fichiers (noyau, image et device tree). Ici le port 80 de la machine virtuelle Qemu est accessible via le port 5555 sur localhost. La vm possède également accès au réseau externe et à internet. Cette configuration peut également être modifiée pour permettre l'accès via SSH en utilisant le port 22 au lieu de 80.

qemu-system-aarch64 -M raspi3 -m 1024 \
    -kernel output/images/Image \
    -dtb output/images/bcm2710-rpi-3-b.dtb \
    -drive if=sd,driver=raw,file=output/images/sdcard.img \
    -append "console=ttyAMA0 root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4" \
    -device usb-net,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:80 \
    -serial stdio

Si Qemu retourne l'erreur suivante, il suffit d'utiliser qemu-img

qemu-system-aarch64: Invalid SD card size: 57 MiB

SD card size has to be a power of 2, e.g. 64 MiB.
You can resize disk images with 'qemu-img resize <imagefile> <new-size>'
(note that this will lose data if you make the image smaller than it currently is).

Pour vérifier les dimensions de l'image

qemu-img info output/images/sdcard.img

Redimmensionner l'image

qemu-img resize -f raw output/images/sdcard.img 64M 

Au boot de la vm on peut voir la configuration réseau (ici cdc_ether est directement inclus dans le noyau)

[    2.604357] usb 1-1.1: new full-speed USB device number 3 using dwc_otg
[    2.704194] usb 1-1.1: New USB device found, idVendor=0525, idProduct=a4a2, bcdDevice= 0.00
[    2.704879] usb 1-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=10
[    2.705936] usb 1-1.1: Product: RNDIS/QEMU USB Network Device
[    2.706784] usb 1-1.1: Manufacturer: QEMU
[    2.707319] usb 1-1.1: SerialNumber: 1-1.1
[    2.731964] cdc_ether 1-1.1:1.0 eth0: register 'cdc_ether' at usb-3f980000.usb-1.1, CDC Ethernet Device, 40:54:00:12:34:57
(...)
Starting network: [    3.859276] random: crng init done
udhcpc: started, v1.33.0
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
deleting routers
adding dns 10.0.2.3
OK

Pour effectuer un test réseau nous pouvons utiliser nc comme serveur HTTP improvisé sur la vm Qemu:

while true ; do  echo -e "HTTP/1.1 200 OK\n\n Bonjour réseau!" | nc -l -p 80  ; done

La page peut être consulté depuis le host avec un navigateur (ou curl/wget) via l'adresse http://localhost:5555.

Notre application est compilée et présente dans l'image: /usr/bin/hello, celle-ci est également exécutée au démarrage (en dernier) grâce au script init.d /etc/init.d/S99hello.

Pour copier l'image sur une carte SD à destination d'un Raspberry Pi 3:

dd bs=4M if=output/images/sdcard.img of=/dev/mmcblk0 status=progress
sync

Liens complémentaires