Levans' workshop

Experiments and thoughts about Machine Learning, Rust, and other stuff...

Booter gentoo sur un Banana Pi-R1


Je me suis récemment acheté un Banana Pi R1. Cette petite bête, fortement inspirée du fameux Raspberry Pi a toutefois un but assez différent.

En effet, elle dispose de 5 ports Ethernet Gigabit, ainsi que d'une puce wifi. Il s'agit d'un routeur personnalisable presque complètement open-hardware. Disposant également d'un port SATA interne, où l'on peut mettre un disque 2.5", il est presque naturel d'également songer à en faire un NAS.

Toutefois, il était hors de question que j'installe sur ce joujou autre chose que Gentoo, qui a emporté mon cœur depuis plusieurs années. Mais compiler sur une petite puce ARM ? Est-ce vraiment raisonnable ? Bien sûr que non ! Qu'à celà ne tienne, la compilation croisée distribuée n'est pas de la magie noire !

Voici donc mon setup, dont je vais par la suite décrire l'installation.

Sur le Banana Pi R1:

  • Une carte micro-SD avec une image Bananian dessus. Elle m'a servi de livecd pour l'installation de Gentoo, et je la garde en fallback au cas où. La carte contient obligatoirement le bootloader (ici: U-Boot).
  • Un disque SATA 2.5" extrait d'un vieux portable agonisant, sur lequel j'ai installé gentoo.
  • Une installation de distcc sur la gentoo, pour faire de la compilation distribuée.

Sur mon ordinateur fixe:

  • Un distcc également, accompagné d'une chaine de compilation ARM fournie avec crossdev.

Durant la mise en place de tout ça, je me suis fortement inspiré d'une vieille page du gentoo wiki sur le BananaPi (qui n'est malheureusement plus à jour sur plusieurs plans).

La mise en place de distcc

Côté de mon desktop gentoo, ce fut allé assez simplement:

emerge -av crossdev distcc
crossdev -S -P -v EXTRA_ECONF="-march=armv7-a -mfpu=neon-vfpv4 -ffast-math" \
            -t armv7a-hardfloat-linux-gnueabi
distcc-config --update-masquerade-with-crossdev

Configurez distcc pour accepter les connexion depuis votre réseau local:

# Dans /etc/conf.d/distccd
DISTCC_OPTS="$DISTCC_OPTS --allow 192.168.1.0/24"

Puis enfin, démarrez distccd:

service distccd start

Bootloader et kernel

Une première note, sur des boards comme le BananaPi, deux approches sont possibles pour le kernel. Soit utiliser le kernel sunxi, qui est un vieux fork de linux (base sur la 3.4) intégrant le support de ces architectures, soit utiliser le kernel mainline (notre bon vieux gentoo-sources).

Si la deuxième option était encore impossible il y a un an, il est maintenant tout à fait possible de booter un kernel comme le 4.0.5 (actuel stable gentoo) sur une board comme la mienne, ce que j'ai donc fait.

L'image Bananian vient toutefois avec un kernel sunxi. Ce n'est pas un soucis, car étant en train de migrer vers la mainline, ils ont intégré le support des nouveaux kernels et du nouveau protocole de boot les accompagnant.

Il reste toutefois un bug dans le U-Boot fourni avec l'image bananian, mais il a été fixé. Donc après avoir flashé la carte SD, booté dessus, et configuré l'accès réseau, n'oubliez de faire un apt-get update && apt-get upgrade, ou bien votre gentoo ne pourra utiliser qu'un des deux cœurs de la board. ;)

L'installation proprement dite

Armez vous de votre gentoo handbook amd64 et globalement suivez la procédure habituelle en installant votre système sur /dev/sda à votre guise. Seules certaines parties vont changer : la configuration du kernel et l'intallation du bootloader.

J'ai également décidé de monter dans /boot la partition de la carte SD contenant les images et le bootloader (/dev/mmcblk0p1). On devra tout y faire à la main, pas de jolis scripts comme make install ou grub2-mkconfig.

L'installation de distcc

Dès que vous avez accès à emerge, installez distcc, ça ne prendra pas longtemps et vous sauvera la vie pour la suite:

emerge -av distcc

Pour que la chaine de cross compilation fonctionne bien, il va falloir ruser un peu, afin que distcc envoie à notre machine de compilation le bon nom de compilateur, à savoir armv7a-hardfloat-linux-gnueabi-g++ au lieu de g++.

Pour ce faire, rendez-vous dans le dossier /usr/lib/distcc/bin/, et créez-y un petit script nommé armv7a-hardfloat-linux-gnueabi-wrapper contenant ceci:

#!/bin/bash
exec /usr/lib/distcc/bin/armv7a-hardfloat-linux-gnueabi-g${0:$[-2]} "$@"

Puis arrangez vous pour que les noms de compilateur "de base" soient des liens symboliques vers ce script (supprimez les liens déjà existants si nécessaire):

ln -s armv7a-hardfloat-linux-gnueabi-wrapper cc
ln -s armv7a-hardfloat-linux-gnueabi-wrapper c++
ln -s armv7a-hardfloat-linux-gnueabi-wrapper gcc
ln -s armv7a-hardfloat-linux-gnueabi-wrapper g++

Ajoutez votre machine de calcul comme serveur dans la liste des hosts de caluls. Notez que nous ne lançons pas distccd sur la board, l'objectif étant qu'elle en fasse le moins possible.

distcc-config --set-hosts "192.168.1.42,cpp,lzo"

Puis intégrons distcc dans notre /etc/portage/make.conf:

FEATURES="distcc distcc-pump"

Assurez vous également que votre variable CFLAGS (toujours dans le make.conf) ne contient pas -march=native, qui est incompatible avec distcc. Chez moi j'ai:

CFLAGS="-O2 -pipe -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard"
CXXFLAGS="${CFLAGS}"
CHOST="armv7a-hardfloat-linux-gnueabi"

La configuration du kernel

Voilà où les choses deviennent amusantes. Après avoir vaillament installé les sources:

emerge -av gentoo-sources

Vous pouvez vous rendre dans /usr/src/linux pour le configurer.

Si vous ne voulez pas vous amuser à tout régler à la main, vous pouvez générer un .config assez générique qui devrait vous apporter les principaux réglages de base:

make ARCH=arm sunxi_defconfig

Maintenant, il et temps de s'attaquer au menuconfig, je vais vous décrire ici les options que j'ai repéré comme importantes:

make ARCH=arm menuconfig

D'abord, une option nécessaire pour plusieurs trucs de gentoo qui n'était pas activée par défaut pour moi:

General setup --->
    [*] POSIX Message Queues

Puis, pour le support de notre SoC, vérifiez que ces options sont bien réglées:

System Type --->
    ARM system type (Allow multiple platforms to be selected)
    Multiple platform selection --->
        [*] ARMv7 based platforms (Cortex-A, PJ4, Scorpion, Krait)
    Allwinner SoCs --->
        [*] Allwinner A20 (sun7i) SoCs support

Pour supporter notre double-cœur et l'utiliser:

Kernel Features --->
    [*] Symmetric Multi-Processing

N'oubliez pas d'activer le bluetooth et le wifi pour en profiter:

[*] Networking Support --->
    [*] Bluetooth subsystem support
    [*] Wireless --->
        [*] cfg80211 - wireless configuration API
        [*] Generic IEEE 802.11 Networking Stack (mac80211)

Puis les pilotes pour notre matériel:

Device drivers --->
    [*] Network device support --->
        [*] Network core driver support
        [*]   Universal TUN/TAP device driver support
        [*] Ethernet driver support --->
            [*] STMicroelectronics devices
            [*]   STMicroelectronics 10/100/1000 Ethernet driver
            [*]     STMMAC Platform bus support
        [*] Wireless LAN --->
            [*] Realtek rtlwifi family of devices --->
                [*] Realtek RTL8192CU/RTL8188CU USB Wireless Network Adapter
    Graphics support --->
        Frame buffer Devices --->
            [*] Support for frame buffer devices
            [*] Simple framebuffer support
        Console display driver support --->
            [*] Framebuffer console support

Voilà pour les truc propres à notre matériel, je vous laisse décider de la suite de votre configuration.

Compilation du kernel

Pour la compilation, nous allons compiler une uImage, pour aller avec U-Boot. Il vous faudra donc installer u-boot-tools, qui est utilisé par l'architecture de build du kernel (la version de U-Boot fournie avec bananian est la 2015.4):

emerge -av =u-boot-tools-2015.4

En plus de cette image, il faudra compiler les Device Tree Blobs. Qui sont des fichiers binaires que le bootloader devra transmettre au kernel, décrivant l'architecture hardware.

Néanmoins, le mode pump de distcc ne convient pas au kernel, et fait échouer la compilation. Nous devrons donc nous contenter du mode normal, impliquant que le préprocessing se fera sur le bananapi.

On doit donc overrider la variable DISTCC_HOSTS de distcc, pour qu'il ne râle quand à la présence de ,lzo,cpp sans l'activation du mode pump:

DISTCC_HOSTS="192.168.1.42/4" PATH=/usr/lib/distcc/bin/:$PATH \
make -j4 -l2 ARCH=arm uImage LOADADDR=0x46000000 dtbs

(l'attribut LOADADDR est nécessaire pour U-Boot, j'ai réutilisé celui de la configuration de bananian).

Installation du kernel et configuration du bootloader

Pour rappel, j'ai monté la première partition de la carte SD sur /boot dans mon chroot

mount /dev/mmcblk0p1 /boot

Dedans se trouvent normalement au début les fichiers suivants:

boot.cmd  boot.scr  fex  script.bin  uImage

L'objectif est de garder l'image debian pour faire un fallback, en cas de besoin. Nous n'allons donc pas l'écraser avec notre image gentoo, mais plutôt la backuper:

mv /boot/uImage /boot/uImage-bananian

Puis, nous allons copier notre image et son dtb:

mkdir /boot/dtb
cp /usr/src/linux/arch/arm/boot/uImage /boot/uImage-gentoo
cp /usr/src/linux/arch/arm/boot/dts/sun7i-a20-bananapi.dtb /boot/dtb/

Et éditer le fichier /boot/boot.cmd pour l'adapter à notre nouveau setup. Le script est en deux parties, sous forme d'un if/else. Il vérifie d'abord l'existence d'une image sous un certain nom, nous allons y mettre notre image gentoo:

if load mmc 0:1 0x00000000 uImage-gentoo
then

Puis, dans la branche then, les instructions pour booter notre kernel gentoo:

setenv bootargs console=ttyS0,115200 root=/dev/sda1 rootfstype=ext4 rootwait
load mmc 0:1 0x49000000 dtb/sun7i-a20-bananapi.dtb
load mmc 0:1 0x46000000 uImage-gentoo
bootm 0x46000000 - 0x49000000

Enfin, nous allons modifier la 2e partie, qui boote l'image bananian vieux format, pour l'adapter au nouveau nom de fichier:

...
load mmc 0:1 0x48000000 uImage-bananian
...

(Pour les flemmards mon fichier entier est disponible ici.)

Ensuite, il ne faut pas oublier de recompiler le script de boot:

mkimage -C none -A arm -T script -d /boot/boot.cmd /boot/boot.scr

Et voilà, vous pouvez reprendre le handbook et rebooter !

La question du switch

Le BananaPi-R1 contient un switch ethernet BCM53125 pour gérer ses 5 ports ethernet. Le driver de ce switch n'est pas dans le kernel linux. Il est donc inutilisable en l'état. Vous pouvez seulement utiliser le port WAN comme une prise ethernet classique.

Il existe de vieux patches du kernel pour ajouter le support du switch (par exemple ici), mais je n'ai pas encore réussi à les faire marcher. Je m'y attaque néanmoins dès maintenant, et écrirais un prochain billet une fois la chose réussite (ou lamentablement échouée).