Installation de Debian sur des machines ARM

dim. 22 nov. 2020 by Marmotte

Les machines basées sur un processeur ARM sont devenues très populaires depuis une dizaine d'années. Comme pour la plupart des machines, j'installe Debian dessus, mais contrairement aux ordinateurs à base de processeur x86, il n'existe pas de procédure d'installation générique.

Dans cet article, je décris la manière dont j'installe Debian sur ces machines, en détaillant les spécificités de chaque installation.

Pourquoi pas Armbian ?

En 2011, j'ai acheté un SheevaPlug, afin de disposer d'une machine consommant très peu d'électricité sur laquelle je pourrais installer Debian, afin de gérer mon réseau (DNS, DHCP, PXE, VPN, etc.). Ce genre d'appareils n'était pas encore répandu, et il n'existait pas de projet proposant un système prêt à l'emploi pour ces machines. J'ai donc dû construire moi-même le système que j'ai installé dessus. La procédure ressemblait fortement à celle que je commençais à utiliser sur mes ordinateurs fixes et portables à base de processeur x86.

Pour les machines plus récentes, des projets comme Armbian proposent des images toutes prêtes à télécharger, et la procédure permettant de les construire soi-même. Mais ces systèmes tous faits ne me conviennent pas, puisqu'ils contiennent tout un tas de paquets installés et de configurations qui ne me servent pas, voire me dérangent. Je préfère, comme toujours, installer une Debian minimale, afin d'avoir un système correspondant exactement à mes besoins, et surtout à mes habitudes, plutôt qu'installer un système déjà construit duquel je devrais commencer par supprimer tout ce qui ne me plait pas.

J'utilise cependant le projet Armbian dans deux cas :

  • Lorsque j'achète un nouvel appareil, afin d'avoir rapidement un système fonctionnel, qui me permet de prendre connaissance des particularités et des configurations nécessaires pour que la machine fonctionne.
  • Lorsque les paquets présents dans les dépôts Debian ne permettent pas de faire fonctionner la machine. Dans ce cas, j'ajoute le dépôt Armbian sur mon système afin d'installer les paquets nécessaires, comme un noyau Linux contenant les bons modules ou patches.

Enfin, en construisant moi-même le système, je suis plus à l'aise lorsqu'il est nécessaire de le dépanner, puisque je connais le rôle de chaque élément nécessaire à son fonctionnement.

Ma procédure complète, en bref

Même s'il existe des particularités, la base de mes installations est toujours la même, que ce soit pour un ordinateur fixe, une machine ARM, un conteneur, ou toute autre machine : un système Debian minimal, créé à l'aide de debootstrap. J'ai d'ailleurs déjà écrit des articles décrivant mes installations de Debian Wheezy et Debian Jessie par cette méthode.

Afin d'éviter de répéter ces étapes à la main, j'ai maintenant écrit quelques scripts qui me permettent de générer rapidement un système Debian vierge comportant les quelques personnalisations que j'applique sur tous mes systèmes.

Particularités génériques pour les machines ARM

Installation initiale

Comme je construis les systèmes de mes machines ARM depuis mon ordinateur fixe qui n'a pas la même architecture, je dois exécuter debootstrap en deux étapes. La première, avec le paramètre --foreign, génère le rootfs initial, alors que la seconde, exécutée par dans l'architecture cible, effectue les configurations de base sur le nouveau système. Pour cela, le binaire statique qemu de l'architecture cible doit être copié dans le système en cours de création, afin de pouvoir exécuter des programmes compilés pour cette architecture.

$ sudo debootstrap --foreign --arch=${ARCH} --verbose --variant=minbase --include=bash-completion,dbus,dialog,htop,iproute2,less,libpam-systemd,locales,openssh-server,screen,sudo,systemd,systemd-sysv,tzdata,vim --exclude=nano buster ${NEW_ROOTFS} ${MIRROR}
$ sudo cp /usr/bin/qemu-${QEMU_ARCH}-static ${NEW_ROOTFS}/usr/bin/
$ sudo chroot ${NEW_ROOTFS} debootstrap/debootstrap --second-stage

J'effectue ensuite quelques configurations de base :

$ # Définition du nom d'hôte
$ echo ${NEW_HOSTNAME} | sudo tee ${NEW_ROOTFS}/etc/hostname
$ # Écriture du fstab avec quelques options pour allonger la durée de vie des mémoires flash
$ echo /dev/mmcblk0p1 / ext4 defaults,noatime,nodiratime,commit=300,errors=remount-ro 0 0 | sudo tee ${NEW_ROOTFS}/etc/fstab
$ # Ajout du dépôt backports, souvent nécessaire pour une Debian stable
$ echo deb http://deb.debian.org/debian buster-backports main | sudo tee ${NEW_ROOTFS}/etc/apt/sources.list.d/backports.list
$ # Activation du réseau
$ cat << EOF | sudo tee ${NEW_ROOTFS}/etc/systemd/network/10-eth0.network
[Match]
Name=eth0

[Network]
DHCP=yes

[DHCP]
UseDomains=yes
EOF
$ sudo chroot ${NEW_ROOTFS} systemctl enable systemd-networkd.service systemd-resolved.service

La plupart des machines nécessitent le dépôt backports pour fonctionner. Il est donc nécessaire, après avoir ajouté ce dépôt dans les sources, d'effectuer une mise à jour du système :

$ sudo chroot ${NEW_ROOTFS} apt update
$ sudo chroot ${NEW_ROOTFS} apt --assume-yes dist-upgrade

Enfin, comme aucune connexion utilisateur n'est possible à ce moment, il faut aussi créer un utilisateur avec des droits d'administration :

$ sudo chroot ${NEW_ROOTFS} adduser --gecos "" ${ADMIN_USERNAME}
$ sudo chroot ${NEW_ROOTFS} adduser ${ADMIN_USERNAME} adm
$ sudo chroot ${NEW_ROOTFS} adduser ${ADMIN_USERNAME} sudo

Configuration du bootloader

Le bootloader u-boot est rapidement devenu un standard pour permettre de démarrer des machines ARM. Comme grub, c'est un programme très simple, qui charge les premiers composants du système en mémoire, et passe ensuite la main au système à démarrer.

Pour la plupart des machines récentes, il lit ses instructions dans un fichier /boot/boot.scr placé sur le périphérique contenant le système (carte SD, clé USB, etc.). La génération de ce fichier est une des étapes réalisées par l'utilitaire flash-kernel, dont le nom vient de la nécessité de flasher le noyau directement sur la mémoire flash de certains appareils. La suite d'instructions peut varier d'une machine à l'autre, il lui faut donc pouvoir déterminer quel modèle il utilise.

Cette information est trouvée dans le fichier virtuel /proc/device-tree/model lorsqu'il est exécuté depuis la machine en question. Lorsque cette information n'est pas disponible, ou qu'on veut la modifier, il est possible de l'écrire dans le fichier /etc/flash-kernel/machine, ce qui est nécessaire ici, puisque les commandes sont lancées dans un environnement chroot. On peut ensuite installer le noyau correspondant à l'appareil pour lequel le système est construit. Il ne faut pas non plus oublier les paquets u-boot-tools et flash-kernel, qui permettent de générer les éléments nécessaires au démarrage du système.

$ echo ${BOARD_MODEL} | sudo tee ${NEW_ROOTFS}/etc/flash-kernel/machine
$ sudo chroot ${NEW_ROOTFS} apt --assume-yes install u-boot-tools flash-kernel linux-image-armmp

Indication des particularités de la machine au système

Au début, la liste des périphériques présents dans chaque machine était écrite directement dans le noyau Linux. Cependant, avec l'explosion du nombre de machines, il a été décidé d'extraire cette liste dans un fichier séparé, permettant d'ajouter beaucoup plus facilement le support de nouvelles machines, sans nécessiter d'attendre des mises à jour du noyau. Cela se traduit par la présence d'un fichier binaire, le dtb (pour Device Tree Blob), généré à partir d'un simple fichier texte, le dts (Device Tree Source).

Les versions récentes de u-boot permettent d'indiquer le fichier dtb à charger via des instructions spécifiques. Avec des versions plus anciennes, il était nécessaire de concaténer le contenu du dtb au noyau Linux.

Activation de fonctionnalités optionnelles

Certaines machines proposent des fonctionnalités optionnelles. Par exemple, certaines broches d'un connecteur GPIO peuvent constituer un port série. Dans ce cas, l'utilisateur a alors le choix entre interroger les broches directement, ou configurer le système pour n'avoir qu'un port série standard à manipuler, ce qui nécessite de le décrire dans le fichier dtb.

Pour activer ces fonctionnalités, il est possible de remplacer le fichier dtb, mais cette solution nécessite d'en créer un par combinaison de fonctionnalités optionnelles possible, ce qui génèrerait un nombre exponentiel de dtb. Ce problème est résolu grâce à l'utilisation des fichiers dtbo (Device Tree Blob Overlay), qui sont des "extraits" de fichier dtb, chargés en plus du fichier d'origine, pour y ajouter des définitions de périphériques supplémentaires. Ils sont chargés de la même manière que le dtb, soit par des instructions spécifiques de u-boot, soit concaténés à la fin du noyau.

Globalscale SheevaPlug

C'est la plus vieille machine ARM que je possède. Il a malheureusement cessé de fonctionner cet été, suite à une extinction pourtant tout à fait normale. L'alimentation avait visiblement bien vécu, et elle n'a jamais voulu redémarrer.

Cette machine est dotée de 512 Mo de mémoire flash, contenant au moins u-boot Il est possible d'installer le système sur cette mémoire flash, ou sur un autre périphérique de stockage comme une carte SD ou une clé USB.

Il peut faire tourner un système Debian en architecture armel (qemu-arm-static) avec le noyau linux-image-kirkwood des dépôts Debian, au moins depuis Squeeze.

Configuration de u-boot

u-boot est installé sur la mémoire interne, mais ne contient pas de configuration particulière par défaut. Il est nécessaire de se connecter en console série (mini-USB) pour le configurer.

$ sudo screen /dev/ttyUSB0 115200

Voici pour information la configuration qui était présente sur mon SheevaPlug : uboot-env.

Préparation de la carte SD

Le contenu de la carte SD n'a rien de particulier, puisque u-boot vient directement lire les fichiers dessus pour charger le système. Il suffit d'y créer une partition et d'y copier les fichiers du système Debian généré.

SolidRun Cubox Pro

La seconde machine ARM que j'ai achetée est un Cubox Pro de SolidRun. Cet achat est une de mes rares déceptions, la machine n'ayant eu que très peu de support. Moins d'un an après sa sortie, une nouvelle série ayant une architecture matérielle totalement différente est sortie sous le nom Cubox-i, et le constructeur a totalement abandonné le vieux Cubox, supprimant même toutes les informations à son propos sur son site internet et son wiki. Huit ans après, il est toujours compliqué d'obtenir un affichage fluide en 1080p avec une distribution comme GeeXboX (dédiée à Kodi), et même impossible d'obtenir un affichage graphique tout court sous Debian. Il fonctionne heureusement très bien en tant que serveur headless, mais avec un port HDMI/CEC, une sortie S/PDIF et un capteur infra-rouge, ce n'est pas du tout l'usage que je lui destinais.

Tout comme pour le SheevaPlug, u-boot est installé sur une mémoire flash interne. Cependant, il est configuré par défaut pour charger les instructions de démarrage depuis un fichier /boot/boot.scr placé à la racine de la carte microSD. Il n'est donc pas nécessaire de modifier son paramétrage, qui est de toutes façons partiellement verrouillé, de manière à empêcher la modification des instructions de démarrage.

Il peut faire tourner un système Debian en architecture armhf (qemu-arm-static) avec le noyau linux-image-armmp des dépôts Debian depuis Buster. Le contenu du fichier /etc/flash-kernel/machine doit être SolidRun CuBox.

Préparation de la carte microSD

Tout comme pour le SheevaPlug, le contenu de la carte SD n'a rien de particulier, puisque u-boot vient directement lire les fichiers dessus pour charger le système. Il suffit là encore d'y créer une partition et d'y copier les fichiers du système Debian généré.

Xunlong OrangePi Zero

Plus récemment, j'ai acheté deux OrangePi Zero. Le premier est utilisé comme serveur Minitel, alors que le second est connecté sur mon compteur d'électricité.

Il peut faire tourner un système Debian en architecture armhf (qemu-arm-static) avec le noyau linux-image-current-sunxi des dépôts Armbian. Le contenu du fichier /etc/flash-kernel/machine doit être Xunlong Orange Pi Zero.

Dépôt supplémentaire nécessaire

Étant bien plus récent que les deux précédents, il n'est pas encore géré nativement par le noyau Linux disponible dans les dépôts Debian. J'utilise donc le noyau mis à disposition par le projet Armbian, ce qui nécessite d'ajouter le dépôt armbian :

$ curl -L http://apt.armbian.com/armbian.key | sudo chroot ${NEW_ROOTFS} apt-key add -
$ echo deb http://apt.armbian.com buster main | sudo tee ${NEW_ROOTFS}/etc/apt/sources.list.d/armbian.list
$ sudo chroot ${NEW_ROOTFS} apt update

Configuration de la connexion WiFi

L'utilisation de l'interface WiFi nécessite d'installer le paquet armbian-firmware depuis le dépôt Armbian.

Il faut ensuite installer le paquet wpasupplicant et le configurer en lui fournissant le SSID du réseau WiFi et la passphrase associée :

$ cat << EOF | sudo tee ${NEW_ROOTFS}/etc/systemd/network/10-wlan0.network
[Match]
Name=wlan0

[Network]
DHCP=yes

[DHCP]
UseDomains=yes
EOF

$ wpa_passphrase ${WIFI_SSID} ${WIFI_PASSPHRASE} | sudo tee ${NEW_ROOTFS}/etc/wpa_supplicant/wpa_supplicant-wlan0.conf > /dev/null
$ sudo chmod 0440 ${NEW_ROOTFS}/etc/wpa_supplicant/wpa_supplicant-wlan0.conf
$ sudo chroot ${NEW_ROOTFS} systemctl enable wpa_supplicant@wlan0.service

Configuration de la console série

Une console série est disponible sur le port microUSB d'alimentation, en utilisant le pilote gadget USB du noyau Linux. Elle n'est donc accessible qu'à partir du moment où le système commence à démarrer, et ne permet pas de déboguer les problèmes de démarrage.

$ echo g_serial | sudo tee ${NEW_ROOTFS}/etc/modules-load.d/serial.conf
$ sudo chroot ${NEW_ROOTFS} systemctl enable serial-getty@ttyGS0.service

Une fois le système lancé et l'OrangePi Zero connecté en USB sur une autre machine, on pourra s'y connecter en utilisant la commande suivante :

$ sudo screen /dev/ttyUSB0 115200

Configuration de u-boot

Malgré que Debian ne fournisse pas de noyau permettant de démarrer un OrangePi Zero, la configuration de flash-kernel fournie est correcte. Cependant, elle ne permet pas de détecter le noyau installé depuis les dépôts Armbian, je dois donc la modifier pour ajouter la référence de ce noyau. De plus, comme j'ajoute un dtbo pour pouvoir utiliser un port série du connecteur GPIO, j'ai ajouté les instructions permettant de le charger dans le script de démarrage, il faut donc aussi référencer ce script modifié.

Pour cela, il suffit de créer un fichier /usr/share/flash-kernel/db/00-orangepi-zero.db contenant ces lignes :

Machine: Xunlong Orange Pi Zero
Kernel-Flavors: armmp armmp-lpae sunxi
U-Boot-Script-Name: bootscr.sunxi-overlays

Le script de démarrage modifié doit être placé dans le répertoire /etc/flash-kernel/bootscript/ et j'ai choisi de placer les dtbo dans le répertoire /boot/dtbs/overlays/.

Préparation de la carte microSD

Cette machine ne dispose d'aucune mémoire flash. u-boot doit donc être placé sur la carte microSD, à un emplacement bien précis indiqué sur la documentation officielle :

$ sudo dd if=/dev/zero of=${SDCARD_DEVICE} bs=1M count=4
$ sudo dd if=u-boot-sunxi-with-spl.bin of=${SDCARD_DEVICE} bs=1k seek=8

On peut ensuite créer une partition sur la carte microSD et y copier simplement le contenu du système Debian généré.

Corriger la lecture de la température pour la version LTS

L'un des deux exemplaires que je possède est le modèle LTS. C'est une nouvelle version matérielle ayant subi quelques modifications mineures afin d'améliorer le signal WiFi ainsi que la consommation électrique et la température.

Cependant, son capteur de température souffre d'un problème qui n'existait pas dans la version précédente et indique une valeur erronée. Selon les informations trouvées sur Internet, il semblerait que ce soit un simple décalage d'environ 27°C. J'ai donc appliqué une configuration corrective afin que le programme sensors modifie les valeurs retournées, dans le fichier /etc/sensors.d/orangepi-zero-lts :

chip "cpu_thermal-virtual-0"
  compute temp1 @+27,@-27

FriendlyElec NanoPi R2S

La dernière machine ARM que je possède à ce jour est un NanoPi R2S, destiné à prendre la place du SheevaPlug pour gérer le réseau, en ajoutant un rôle de pare-feu/NAT entre la Livebox et le reste du LAN.

Il peut faire tourner un système Debian en architecture arm64 (qemu-aarch64-static) avec le noyau linux-image-current-rockchip64 des dépôts Armbian. Le contenu du fichier /etc/flash-kernel/machine doit être FriendlyElec NanoPi R2S.

Dépôt supplémentaire nécessaire

Tout comme l'OrangePi Zero, il nécessite des paquets du dépôt Armbian. J'ajoute donc ici aussi le dépôt armbian :

$ curl -L http://apt.armbian.com/armbian.key | sudo chroot ${NEW_ROOTFS} apt-key add -
$ echo deb http://apt.armbian.com buster main | sudo tee ${NEW_ROOTFS}/etc/apt/sources.list.d/armbian.list
$ sudo chroot ${NEW_ROOTFS} apt update

Configuration de la console série

Toujours comme l'OrangePi Zero, sa console série est disponible sur le port microUSB d'alimentation, en utilisant le pilote gadget USB du noyau Linux. La configuration est donc encore une fois la même :

$ echo g_serial | sudo tee ${NEW_ROOTFS}/etc/modules-load.d/serial.conf
$ sudo chroot ${NEW_ROOTFS} systemctl enable serial-getty@ttyGS0.service

Une fois le système lancé et le NanoPi R2S connecté en USB sur une autre machine, on pourra s'y connecter en utilisant la commande suivante :

$ sudo screen /dev/ttyUSB0 115200

Configuration de u-boot

Debian ne fournit aucune configuration pour le NanoPi R2S dans le paquet flash-kernel, il est donc nécessaire de la définir totalement. Heureusement, le format est très simple, et j'ai donc simplement écrit ces lignes dans le fichier /usr/share/flash-kernel/db/nanopi-r2s.db :

Machine: FriendlyElec NanoPi R2S
Kernel-Flavors: arm64 rockchip64
DTB-Id: rk3328-nanopi-r2-rev00.dtb
Boot-Script-Path: /boot/boot.scr
U-Boot-Script-Name: bootscr.nanopi-r2s
Required-Packages: u-boot-tools

Deux éléments cités dans cette configuration n'existent pas dans les paquets Debian : le dtb et le script de démarrage. Pour le dtb, j'ai simplement pris celui fourni par Armbian, il doit être placé dans le répertoire /etc/flash-kernel/dtbs/.

Le script de démarrage, placé dans le répertoire /etc/flash-kernel/bootscript/, est très proche du script bootscr.uboot-generic fourni par Debian, mais pour une raison que ne ne comprends pas, le chargement du dtb ne fonctionne pas avec celui d'origine. J'ai donc appliqué une modification mineure dedans pour corriger ce point :

--- bootscr.uboot-generic       2019-05-25 03:36:25.000000000 +0200
+++ bootscr.nanopi-r2s  2020-11-17 20:41:31.967175267 +0100
@@ -44,7 +44,7 @@
 @@UBOOT_PREBOOT_EXTRA@@

 load ${devtype} ${devnum}:${partition} ${kernel_addr_r} ${prefix}vmlinuz-${fk_kvers} \
-&& load ${devtype} ${devnum}:${partition} ${fdt_addr_r} ${prefix}${fdtpath} \
+&& load ${devtype} ${devnum}:${partition} ${fdt_addr_r} ${prefix}dtb-${fk_kvers} \
 && load ${devtype} ${devnum}:${partition} ${ramdisk_addr_r} ${prefix}initrd.img-${fk_kvers} \
 && echo "Booting Debian ${fk_kvers} from ${devtype} ${devnum}:${partition}..." \
 && booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r}

Préparation de la carte microSD

Le NanoPi R2S ne dispose pas non plus de mémoire flash. Comme pour l'OrangePi Zero, u-boot doit donc être aussi placé sur la carte microSD, à un emplacement bien précis indiqué sur la documentation officielle. Cependant, la disposition est différente, et les éléments à placer occupent 16 Mo au total (preloader, puis bootloader et enfin ATF).

$ sudo dd if=/dev/zero of=${TARGET_DEVICE} bs=1M count=16
$ sudo dd if=${BOARD_CONFIGURATION}/idbloader.bin of=${TARGET_DEVICE} seek=64
$ sudo dd if=${BOARD_CONFIGURATION}/uboot.img of=${TARGET_DEVICE} seek=16384
$ sudo dd if=${BOARD_CONFIGURATION}/trust.bin of=${TARGET_DEVICE} seek=24576

Puisque les données copiées occupent 16 Mo, il est nécessaire de décaler la partition, puisque l'emplacement par défaut de la première partition créée sur un disque est le plus souvent à 1Mo du début du disque.

$ echo 32768, | sudo sfdisk ${TARGET_DEVICE}

On peut enfin formater cette partition partition sur la carte microSD et y copier simplement le contenu du système Debian généré.