Compilation d'un kernel Linux pour une Raspberry Pi 3B

25 juin 2022 ― Pierre-Loup GOSSE

Résumé ― Le Raspberry Pi OS est le système d'exploitation prêt à l'emploi fourni par Raspberry Pi. Dans certains cas de figure il peut être nécessaire de remplacer l'image kernel Linux fournie par défaut. Nous allons voir dans cette article comment compiler depuis une machine hôte x86 64-bits le kernel Linux avec la compilation croisée et comment intégrer l'image et les devicetree dans Raspberry Pi OS.

Compilation du kernel Linux

La première étape consiste à compiler un kernel Linux mainline destiné à s'exécuter sur une Raspberry Pi 3B d'architecture ARM 32 bits. Il est donc nécessaire de mettre en place un compilateur croisé pour compiler notre kernel depuis une machine hôte x86_64.

Compilation croisée avec Linaro

Nous utiliserons le compilateur croisé Linaro. Vous pouvez télécharger ici le compilateur croisé x86_64 -> ARM gnueabihf basé sur GCC. Ou installer son paquet Debian via APT :

# apt install gcc-arm-linux-gnueabihf

Les termes gnueabi ou gnueabihf sont importants, le mot se décompose en :

  • gnu, signifie que la toolchain est sous licence GNU (GPL). Le kernel Linux utilisant la synthaxe assembleur de GNU (GAS), il est nécessaire de compiler le kernel avec une toolchain sous licence GPL.
  • eabi, signifie que la toolchain utilise l'Embedded ABI de ARM.
  • hf, signifie que les virgules flottantes sont gérées de façon hardware par le compilateur.

La Raspberry Pi 3B étant en armhf, il est nécessaire de prendre le compilateur gnueabihf.

Variables d'environnement

Pour utiliser votre compilateur croisé pour une compilation vous devez exporter les variables CROSS_COMPILE et ARCH dans votre terminal. Via APT, le compilateur a été installé dans /usr/bin :

$ dpkg -L gcc-arm-linux-gnueabihf | grep bin.*gcc

/usr/bin/arm-linux-gnueabihf-gcc
/usr/bin/arm-linux-gnueabihf-gcc-ar
/usr/bin/arm-linux-gnueabihf-gcc-nm
/usr/bin/arm-linux-gnueabihf-gcc-ranlib

Il faut donc exporter les valeurs suivantes :

$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ export ARCH=arm

La variable CROSS_COMPILE va permettre de préfixer les appels aux binaire GCC (gcc, gcc-ar, gcc-nm et gcc-ranlib) ainsi, il faut exporter le binaire de la toolchain sans le suffixe gcc.

Configuration du kernel

Créeons un dossier de travail et téléchargeons les sources :

$ mkdir rpi3b
$ cd rpi3b
$ git clone https://github.com/torvalds/linux.git

Nous allons nous brancher sur la dernière version stable du kernel, soit la 5.17 au moment de la rédaction :

$ git checkout v5.17

La raspberry Pi 3B utilise la Broadcom BCM2835. Cela tombe bien, la configuration arch/arm/configs/bcm2835_defconfig existe dans les sources du kernel. Il suffit de la charger dans le .config avec :

$ make bcm2835_defconfig

#
# configuration written to .config
#

Compilation de l'image

La raspberry Pi 3B fonctionne avec des zImage, et non le format bzImage. Lançons la compilation de notre image kernel (et ses modules) :

$ make -j24 zImage modules

L'image arch/arm/boot/zImage produite est bien au format ARM 32 bits :

$ file arch/arm/boot/zImage

arch/arm/boot/zImage: ARM OpenFirmware FORTH Dictionary, Text length: -509607936 bytes, Data length: -509607936 bytes, Text Relocation Table length: -369098747 bytes, Data Relocation Table length: 24061976 bytes, Entry Point: 0x00000000, BSS length: 6021760 bytes

Compilation des devicetree

Il est nécessaire que le kernel puisse connaître le matériel physique sur lequel il s'exécute, afin de charger les bons drivers et d'utiliser toutes les ressources. Cette "découverte" du matériel peut se faire de plusieurs manière : le protocol ACPI est généralement utilisé en x86, et la structure devicetree (fichier .dts), est plutôt utilisé en ARM (32 et 64 bits).

$ make -j24 dtbs

L'ensemble des devicetree ont été compilé en .dtb dans arch/arm/boot/dts/ :

$ ls arch/arm/boot/dts/*.dtb

arch/arm/boot/dts/bcm2711-rpi-400.dtb
arch/arm/boot/dts/bcm2835-rpi-a-plus.dtb
...

Image finale

Nous allons nous baser sur une image Raspberry Pi OS officielle et la modifier avec notre propre image kernel. Cette image contient déjà une image kernel Linux et le système de fichiers pour la Raspberry Pi. Créez un dossier et téléchargez l'image :

$ wget https://downloads.raspberrypi.org/raspios_full_armhf/images/raspios_full_armhf-2022-04-07/2022-04-04-raspios-bullseye-armhf-full.img.xz

Branchez la carte SD que vous voulez utiliser pour flasher l'image et trouvez le device associé avec la commande lsblk :

$ lsblk

...
mmcblk0             8:32   1  14,5G  0 disk
├─mmcblk0p1         8:33   1   256M  0 part
└─mmcblk0p2         8:34   1  14,2G  0 part
...

Dans ma situation, le device associé à ma carte SD est /dev/mmcblk0. Le device fait 14,5G et on remarque qu'il y a déjà deux partitions présentes : mmcblk0p1 et mmcblk0p2. Il s'agit du contenu actuel de la carte. Si vous avez une carte vierge, ou partitionnée différement, lsblk affichera autre chose. Nous allons directement extraire l'image .xz sur le device mmcblk0.

attention
Nous allons utiliser la commande dd pour copier bit à bit notre image sur notre carte SD. Surnommé "Disk Destroyer", cette commande est dangereuse : se tromper de destination d'écriture peut être fatale et corrompre les données. Vérifiez toujours la commande avant de la lancer !

Remplacez la destination d'écriture of=/dev/{votre_device} avec le device de votre carte SD et exécutez la commande :

# xz -dc 2022-04-04-raspios-bullseye-armhf-full.img.xz | dd of=/dev/mmcblk0 status=progress

Les anciennes données et partitions, si présentes, ont été écrasées. Affichez les partitions de l'image Raspbian flashée sur votre device avec l'outil fdisk :

$ fdisk -l /dev/mmcblk0

Disque /dev/mmcblk0: 14,5 GiB, 15552479232 octets, 30375936 secteurs
Disk model: MassStorageClass
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Type d'étiquette de disque : dos
Identifiant de disque : 0x18bb6cde

Périphérique    Amorçage  Début   Fin      Secteurs  Taille  Id  Type
/dev/mmcblk0p1            8192    532479   524288    256M    c   W95 FAT32 (LBA)
/dev/mmcblk0p2            532480  30375935 29843456  14,2G   83  Linux

Vous pouvez observer que le device de votre carte SD est divisé en 2 partitions : mmcblk0p1 et mmcblk0p2. Bien que /dev/mmcblk0 est le device de notre carte SD, les deux partitions se trouvent dans /dev et non dans /dev/mmcblk0. En effet, cela reste 3 devices différents :

  • /dev/mmcblk0 : toute l'image. Un dd nous écrase tout le contenu.
  • /dev/mmcblk0p1 : la première partition de l'image, elle sera la partition de boot et contiendra l'image kernel, les devices tree et des fichiers de configuration.
  • /dev/mmcblk0p2 : la deuxième partition de l'image, elle sera la partition du système de fichier de notre Raspberry.

Nous allons donc monter nos deux partitions boot (/dev/mmcblk0p1) et rootfs (/dev/mmcblk0p2) de notre device /dev/mmcblk0 :

$ mkdir {boot,rootfs}
# mount /dev/mmcblk0p1 boot
# mount /dev/mmcblk0p2 rootfs

Vous pouvez observez avec lsblk le point de montage de vos partitions :

$ lsblk

...
mmcblk0             8:32   1  14,5G  0 disk
├─mmcblk0p1         8:33   1   256M  0 part howto/kernel/rpi/boot
└─mmcblk0p2         8:34   1  14,2G  0 part howto/kernel/rpi/rootfs
...

Copiez dès à present l'image kernel zImage produite et les devicetree compilés dans la partition boot :

# cp linux/arch/arm/boot/zImage boot
# cp linux/arch/arm/boot/dts/*.dtb boot

Sans modification de la configuration de la raspberry, le kernel d'origine sera executé. Ajoutez les lignes suivantes dans le fichier de configuration config.txt pour surcharger le kernel et le devicetree avec ceux produit :

# echo "kernel=zImage" >> boot/config.txt
# echo "device_tree=bcm2835-rpi-b-rev2.dtb" >> boot/config.txt

Vous pouvez entreprendre des modifications dans le rootfs en modifiant le dossier rootfs. Démontez les partitions :

$ sync
# umount boot
# umount rootfs

note
Le commande sync demande au kernel de vider ses buffers et d'écrire toutes les données qui étaient en attente. La commande est bloquante et se libère une fois les écritures faites, assurrant ainsi aucune perte de donnée lors du démontage des partitions.

Vous pouvez débrancher la carte SD.

Démarrage sur la Raspberry Pi

Branchez la carte SD et démarrez votre Raspberry Pi pour tester votre kernel. Pour avoir accès à la console vous devez brancher un lien série UART entre la Raspberry Pi 3B et l'ordinateur. Des modifications devrons être nécessaire dans le fichier de configuration config.txt. Voici un article sur la marche à suivre.