Reprenez le contrôle ! Faites tourner Linux nativement sur vos téléphones et tablettes

Magazine
Marque
GNU/Linux Magazine
Numéro
237
Mois de parution
mai 2020
Spécialité(s)


Résumé

J’ai toujours été admiratif des personnes qui arrivent à faire tourner le noyau Linux sur de nouvelles plateformes. J’ai passé un nombre de soirées incalculables à étudier différents portages de Linux (Nintendo Switch, Nintendo 3DS, PlayStation...) et par la suite, j’ai moi-même passé beaucoup de temps à essayer de porter Linux sur tout ce qui me tombait sous la main. C’est une passion pour moi ! Et je vous propose de la partager. Dans cet article, je vais vous expliquer comment faire tourner vos distributions favorites (Debian, Arch, CentOS, Fedora, Gentoo…) nativement sur vos téléphones et tablettes Android.


Body

Quand je dis nativement, c’est vraiment nativement ! Je ne parle pas des pseudo-solutions, comme on en voir des milliers sur Internet, qui consistent en un chroot et ne donnent finalement aucune liberté. Je ne dénigre surtout pas les solutions telles que Linux Deploy ou autre, mais il faut avouer qu’il suffit d’appuyer sur un bouton pour que cela fonctionne. On reste complètement dépendant d’Android et toute la complexité nous est masquée (or justement, c’est ce qui est fun). La méthode présentée dans cet article est bien plus ardue, mais aussi bien plus gratifiante ! Nous allons enlever toutes traces d’Android, compiler nous-mêmes le noyau, créer l’initramfs, flasher la partition de boot, créer et flasher le système, configurer le réseau, voir comment démarrer tout ça et finalement, obtenir un shell sur notre appareil. C’est du Linux « pour les grands » et nous commençons immédiatement !

Vous pourrez trouver le code présenté dans cet article sur : https://github.com/arnaudmeauzoone/GLMF-linux-phone.

1. Mais pourquoi faire tourner un Linux sur mon téléphone ?

Pour toutes les raisons habituelles :

  • cela permet d’avoir un système Linux à moindre coût et en plus, nos appareils mobiles d’aujourd’hui ont suffisamment de puissance pour faire tourner un système Linux sans problèmes ;
  • on peut faire tourner des applications de poche (serveurs HTTP, serveur FTP, caméra IP, etc.) ;
  • c’est plus écologique, on recycle les anciens appareils plutôt que de les jeter ;
  • c’est énormément gratifiant d’y arriver soi-même.

Mais surtout pour moi, nous sommes à l’aube d’une seconde révolution dans le monde du libre. Tout comme vers la fin des années 90, nous avons remplacé les Windows et autres OS propriétaires par des Linux, les environnements graphiques libres sont de plus en plus adaptés aux appareils tactiles (Phosh [1] et Plasma Mobile [2]). Il est certain que l’on pourra utiliser dans quelques années un système (Linux) complètement libre sur son téléphone et sa tablette. J’étais bien trop jeune dans les années 1990/2000 pour le voir et je compte bien faire partie de ceux qui aident à porter les OS libres sur les téléphones et tablettes.

De plus, de nos jours nous avons un avantage, comparé aux générations précédentes : c’est qu’il existe plein d’appareils autour de nous sur lesquels nous pouvons nous amuser avec du Linux embarqué (téléphones, tablettes, consoles de jeu...). Et pour les personnes comme moi, un vieux téléphone signifie bien souvent un nouveau petit système sous Linux, qui en plus consomme très peu.

Bon, maintenant que nous avons vu les différentes raisons pour faire tourner un Linux nativement sur son téléphone/sa tablette (figure 1), voyons comment faire en pratique.

linux-android figure 01 0

Fig. 1 : Voici un exemple de ce qu’il est possible d’obtenir. On peut voir (de gauche à droite et de haut en bas) : Manjaro (LXDE) avec Firefox, Debian (XFC4) avec Chocolate Doom ainsi que CentOS (XFCE4) avec Dolphin. Tous ont Neofetch dans un terminal. Tous ont le tactile qui fonctionne parfaitement ainsi que le clavier et la souris (USB ou tactile). Toutes ces distributions tournent nativement (pas d’Android) et les images ont été prises en faisant une capture d’écran via import de ImageMagik. Nous arriverons à ce résultat en fin d’article.

2. Le plan d’action

Pour faire démarrer un Linux, c’est comme une recette de cuisine, il nous faut plusieurs ingrédients :

  • un bootloader ;
  • un noyau Linux (+ un DTB dans le cas ARM) ;
  • un initramfs ;
  • le système final (Debian, Arch, CentOS...).

Maintenant, nous devons les cuisiner.

2.1 Le bootloader

C’est le seul bout de code que nous ne réécrirons pas dans cet article. Nous allons laisser le bootloader fourni par défaut avec votre téléphone. Le système ne sera donc pas encore entièrement libre et du travail sera encore à fournir pour cela. Le bootloader (ou la chaîne de bootloaders) est le premier programme exécuté lorsque vous démarrez votre téléphone. Souvent, il effectue des tests, puis charge le noyau et l’initramfs (qui se trouvent dans la partition BOOT) et les place en mémoire. Ensuite, il passe l’exécution au noyau. Sur des cartes ARM, il est fréquent d’utiliser U-Boot, mais Android utilise par défaut un bootloader basé sur LK (Little Kernel) et bien qu’il soit possible de changer/compiler le bootloader d’un appareil, dans cet article, pour ne pas ajouter d’autres difficultés, nous allons garder le bootloader par défaut. Vous pouvez trouver plus d’informations sur LK ici [3].

2.2 Le noyau

Pour avoir le noyau, nous allons devoir le compiler. Pour cela, il va nous falloir récupérer le code source. C’est une obligation pour le constructeur de l’appareil de fournir le code source du noyau.

En effet, étant donné que le code source du noyau Linux est sous GPLv2, toute distribution du noyau compilé (ou d’un dérivé du noyau) doit s’accompagner d’une possibilité pour celui qui reçoit l’exécutable d’obtenir le code source correspondant. Le code source du noyau correspondant à votre appareil doit donc être disponible librement.

Voilà les moyens les plus communs pour le récupérer :

  • clonage d’un dépôt Git d’un noyau (constructeur, LineageOS, TWRP, etc.) ;
  • récupération d’une archive sur le site du constructeur ;
  • rappeler poliment, par mail, au constructeur qu’il doit fournir le code source.

Commencez donc par récupérer les sources du noyau. Dans mon cas pour le Samsung Galaxy S7 :

$ git clone https://github.com/LineageOS/android_kernel_samsung_universal8890.git
Clonage dans 'android_kernel_samsung_universal8890'...
remote: Enumerating objects: 3881570, done.
$ cd android_kernel_samsung_universal8890

Une fois que vous avez récupéré les sources du noyau, c’est là que les choses sérieuses commencent. Il va falloir le compiler. Ou plutôt le cross-compiler.

Cross-compiler signifie compiler pour une architecture différente de l’architecture sur laquelle vous compilez. En effet, il est très fortement probable que votre ordinateur sur lequel vous travaillez utilise un processeur Intel ou AMD, donc une architecture amd64. Or ce que vous, vous voulez faire, c’est faire tourner le noyau sur un processeur ARM (ou arm64). Ainsi, si vous compilez simplement les sources du noyau, vous obtiendrez un binaire fait pour tourner sur une architecture amd64 et pas sur une architecture ARM (ou arm64). Cross-compiler revient donc à dire « Je compile sur une architecture amd64, mais le système cible est une architecture ARM (ou arm64) ».

Pour cross-compiler, il va falloir récupérer une chaîne de compilation (ensemble d’outils permettant la génération de l’exécutable) pour ARM (ou arm64). On peut en trouver sur le site de la compagnie Bootlin (anciennement Free Electrons) qui vous propose de les télécharger pour votre architecture cible [4].

Une fois que vous aurez téléchargé la chaîne de compilation propre à votre architecture hôte et cible (parfois, il est aussi nécessaire d’utiliser une ancienne version du compilateur, par exemple GCC 4.9 plutôt que GCC 8), naviguez dans l’archive et à la racine, vous trouverez un dossier /bin. Dans ce dossier se trouvent des fichiers aarch64-buildroot-linux-musl-*, ce sont votre toolchain.

Sachez qu’il est aussi possible de compiler soi-même le cross compilateur via des outils tels que Buildroot ou Crosstool-NG. Mais cela sort du cadre de cet article.

# wget https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--musl--stable-2018.11-1.tar.bz2
# tar -xvf aarch64--musl--stable-2018.11-1.tar.bz2 -C /opt/
# ls /opt/aarch64—musl--stable-2018.11-1/bin/
aarch64-buildroot-linux-musl-addr2line          aarch64-linux-elfedit
aarch64-buildroot-linux-musl-ar                 aarch64-linux-g++
aarch64-buildroot-linux-musl-as                 aarch64-linux-g++.br_real
[...]

Maintenant que nous avons la toolchain, nous allons configurer le noyau. Tout d’abord nous allons mettre la configuration par défaut de votre constructeur. Elle doit normalement se trouver dans le répertoire arch/arm*/configs/.

$ export ARCH=arm64
$ export CROSS_COMPILE=/opt/aarch64--musl--stable-2018.11-1/bin/aarch64-buildroot-linux-musl-
$ ls arch/arm64/configs/
exynos8890-hero2lte_defconfig exynos8890-herolte_defconfig samsung_herolte_defconfig
 
$ make samsung_erolte_defconfig

Après ça, vous obtenez un fichier .config contenant la configuration par défaut du noyau proposée par votre constructeur. Mais nous allons tout de même la modifier.

Il suffit de taper (pensez à installer libncurses-dev) :

$ make menuconfig

Vous êtes maintenant sur l’interface de configuration du noyau (figure 2). C’est ici que nous allons pouvoir ajouter ou enlever des options au noyau.

linux-android figure 02

Fig. 2 : Interface de configuration du noyau (make menuconfig).

Voilà la liste des options dont je vous recommande l’activation :

CONFIG_VT
 
Location:
    -> Device Drivers
        -> Character devices
            -> Enable TTY (TTY [=y])
Prompt: Virtual terminal

Ceci est nécessaire si vous souhaitez afficher quelque chose sur l’écran de votre appareil. D’après la documentation du noyau Linux :

« You need at least one virtual terminal device in order to make use of your keyboard and monitor. Therefore, only people configuring an embedded system would want to say N here in order to save some memory; the only way to log into such a system is then via a serial or network connection. »

CONFIG_VT_CONSOLE
 
Location :
    -> Device Drivers
        -> Character devices
            -> Enable TTY (TTY [=y])
                -> Virtual terminal (VT [=y])
Prompt: Support for console on virtual terminal

Ceci permet d’afficher (par défaut) les messages provenant du noyau (donc de boot) sur /dev/tty0. Le tty est paramétrable via l’option console=ttyN dans la ligne de commande du noyau.

CONFIG_FB
 
Location :
    -> Device Drivers
        -> Graphics support
            -> Frame buffer Devices
Prompt: Support for frame buffer devices

C’est ce module qui va vous permettre d’avoir le pseudo-périphérique /dev/fb0 (gestion pixel par pixel de l’écran)

CONFIG_FRAMEBUFFER_CONSOLE
 
Location:
    -> Device Drivers
        -> Graphics support
            -> Console display driver support
Prompt: Framebuffer Console support

Si le premier module nous permet de gérer l’écran pixel par pixel, celui-ci va nous permettre d’afficher du texte sur l’écran de notre appareil. Et donc les messages de boot du noyau et de systemd (ou autre), et ainsi de pouvoir « déboguer » beaucoup plus facilement (figure 3).

linux-android figure 03

Fig. 3 : Configuration minimale permettant d'avoir les messages de boot à l'écran.
CONFIG_EXT4_FS
 
Location:
    -> File systems
Prompt: The Extended 4 (ext4) filesystem

Permet d’avoir le support du système de fichier ext4.

CONFIG_USB_G_ANDROID
 
Location:
    -> Device Drivers
        -> USB support (USB_SUPPORT [=y])
            -> USB Gadget Support (USB_GADGET [=y])
                -> USB Gadget Drivers (<choice> [=y])
Prompt: Android Composite Gadget

Permet d’avoir le support de l’USB gadget et donc nous sera utile par la suite pour nous créer une connexion Ethernet-USB.

CONFIG_DEVTMPFS
 
Location :
    -> Device Drivers
        -> Generic Driver Options
Prompt: Maintain a devtmpfs filesystem to mount at /dev

Permet d’avoir le support du devtmpfs (peuplement automatique de /dev).

Je vous conseille aussi de NE PAS activer :

CONFIG_ANDROID_PARANOID_NETWORK
 
Location:
    -> Networking support (NET [=y])
        -> Networking options
Prompt: Only allow certain groups to create sockets

Par défaut, le noyau Android (Linux modifié) utilise l’option paranoid network, ce qui restreint les options de connexion des process (en fonction de leurs groupes). Cette option doit être désactivée si l’on souhaite pouvoir se connecter à Internet sans trop de paramétrages.

Maintenant que vous avez fini la configuration (exit et oui à la question), il est enfin possible de compiler le noyau avec la commande :

$ make -jN

-j pour augmenter le nombre de threads et N doit être remplacé par le nombre de cœurs de votre processeur.

Si la compilation s’est bien déroulée, vous devriez retrouver le noyau dans arch/arm*/boot/. Il s’agit des fichiers Image, zImage ou zImage-dtb. En fonction de votre appareil, Image (noyau non compressé), zImage (noyau compressé) et zImage-dtb (noyau compressé avec DTB à la suite).

Mainlining

Dans cet article, nous utilisons encore les sources du noyau fourni par le constructeur. C’est déjà très bien, mais avec le noyau Linux mainline, ce serait encore mieux ! Sachez qu’il existe actuellement de gros efforts de mainlining (voir postmarketOS) et qu’en plus, le noyau Android se rapproche de plus en plus du noyau Linux mainline (projet Treble). Il sera donc bientôt bien plus simple de faire tourner le noyau mainline sur nos machines.

2.3 Le DTB

Le DTB ou device tree blob est un fichier contenant les informations nécessaires au noyau pour connaître les périphériques non découvrables. C’est une notion un peu particulière à saisir, car il n’y a pas de device tree sur nos architectures X86-64 et donc, nous y sommes peu habitués.

La naissance du device tree provient du fait qu’il existe des périphériques dits « non découvrables ». C’est-à-dire que le noyau n’a aucun moyen de savoir s’ils sont là, et comment communiquer avec. Ainsi dans le cas de ces périphériques et avant la mise en place du device tree, on était obligé de coder en dur ces informations dans le noyau et du code de description était donc mélangé avec du code fonctionnel. Pour l’ensemble des plateformes existantes, cela devenait ingérable, on a donc préféré mettre en place un fichier de description pour justement dire au noyau comment communiquer avec ces périphériques. Vous trouverez plus d’informations sur le device tree ici [5].

Normalement, vous pouvez le récupérer après la compilation du noyau, il doit être dans arch/arm*/boot/dts/ :

$ ls arch/arm64/boot/dts/
[...]
exynos8890-herolte_eur_open_04.dtb
exynos8890-herolte_eur_open_08.dtb
exynos8890-herolte_eur_open_09.dtb
 
$ dtbTool-exynos --pagesize 2048 --platform 0x50a6 --subtype 0x217584da -o "arch/arm64/boot/dt.img" "arch/arm64/boot/dts/exynos8890-herolte_eur_open_04.dtb" "arch/arm64/boot/dts/exynos8890-herolte_eur_open_08.dtb" "arch/arm64/boot/dts/exynos8890-herolte_eur_open_09.dtb"

Si vous en avez plusieurs, vous pouvez créer une image dt.img avec dtbtool via cette procédure [6].

2.4 L’initramfs

L’initramfs est une archive CPIO d’un système de fichiers (un petit système Linux) contenant tout le nécessaire pour monter le système final. Nous allons donc en avoir besoin pour démarrer une distribution (Debian, Manjaro, CentOS...). Nous allons voir comment en créer un simplement, cependant si vous souhaitez approfondir le rôle de l’initramfs, je vous recommande l’article d'Étienne Dublé dans le numéro 206 de ce magazine [7].

Pour générer l’initramfs, nous allons recréer une arborescence de fichiers Linux classique. Dans ce sous-système, il va nous falloir des programmes qui vont être utilisés pour monter le système. Alors là, on peut les installer à la main, mais le mieux est d’utiliser BusyBox. BusyBox est un programme qui regroupe l’ensemble des utilitaires de base d’un système Linux.

Pour installer BusyBox, vous pouvez soit le télécharger, soit le compiler vous-même. Si vous le compilez vous-même, pensez bien à le compiler en static (dans un premier temps) et pour une architecture ARM ou arm64. Sinon, le plus simple est de télécharger le binaire (en version static) à l’adresse suivante [8] et pour la bonne architecture.

Cela peut être fait avec les commandes suivantes :

$ mkdir initramfs
$ cd initramfs
$ mkdir usr bin sbin opt etc root
$ wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv8l
$ cp ./busybox-armv8l ./bin
$ chmod 755 ./bin/busybox-armv8l
$ qemu-arm-static ./bin/busybox-armv8l --install -s ./bin
$ ls -la ./bin/
lrwxrwxrwx 1 user user      39 déc. 30 16:18 '[' -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 '[[' -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 acpid -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 addgroup -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 add-shell -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 adduser -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 adjtimex -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 arch -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 arp -> /home/user/initramfs/bin/busybox
lrwxrwxrwx 1 user user      39 déc. 30 16:18 arping -> /home/user/initramfs/bin/busybox
[...]

Nous avons maintenant BusyBox d’installé. Bien que ce soit un initramfs, nous allons (par la suite) nous y connecter. Il est donc intéressant d’installer bash (ou zsh) pour avoir un shell plus moderne. Comme j’aime bien aussi utiliser Neofetch (pour être sûr que le système est vivant), nous allons l’installer. Je précise que cette étape est complètement optionnelle et si votre souci est la légèreté ou la rapidité, vous ne devez pas ajouter ces packages dans l’initramfs.

Nous allons installer bash en static. Nous pouvons le récupérer dans les dépôts de Debian [9]. Nous allons récupérer le package correspondant à l’architecture cible et extraire bash-static (surtout, n’installez pas le package sur votre système, ce n’est pas le but !) :

# wget http://ftp.de.debian.org/debian/pool/main/b/bash/bash-static_5.0-4_arm64.deb
# dpkg -c bash-static_5.0-4_arm64.deb
drwxr-xr-x root/root         0 2019-04-18 06:12 ./
drwxr-xr-x root/root         0 2019-04-18 06:12 ./bin/
-rwxr-xr-x root/root   1870232 2019-04-18 06:12 ./bin/bash-static
drwxr-xr-x root/root         0 2019-04-18 06:12 ./usr/
[...]
-rw-r--r-- root/root     90010 2019-04-18 06:12 ./usr/share/man/man1/bash-static.1.gz
 
# ar x bash-static_5.0-4_arm64.deb
# ls
bash-static_5.0-4_arm64.deb control.tar.xz data.tar.xz debian-binary
# tar -xvf data.tar.xz
./
./bin/
./bin/bash-static
./usr/
[...]
./usr/share/man/man1/bash-static.1.gz
# cp ./bin/bash-static /home/user/initramfs/bin/bash

De même avec Neofetch [9] :

# wget https://codeload.github.com/dylanaraps/neofetch/tar.gz/6.1.0
# tar -xvf neofetch-6.1.0.tar.gz -C $HOME/initramfs/opt/
neofetch-6.1.0/
neofetch-6.1.0/.github/
neofetch-6.1.0/.github/ISSUE_TEMPLATE.md
neofetch-6.1.0/.github/PULL_REQUEST_TEMPLATE.md
neofetch-6.1.0/.travis.yml
neofetch-6.1.0/CHANGELOG.md
neofetch-6.1.0/CONTRIBUTING.md
neofetch-6.1.0/LICENSE.md
neofetch-6.1.0/Makefile
neofetch-6.1.0/README.md
neofetch-6.1.0/neofetch
neofetch-6.1.0/neofetch.1

Maintenant que nous avons les utilitaires de base, nous allons pouvoir écrire notre programme d’init (un script shell dans notre cas).

Voici le contenu du fichier init. Les commentaires expliquent son fonctionnement. Le code est disponible sur le GitHub de GNU/Linux Magazine :

#!/bin/sh
 
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
echo $PATH
 
# On monte une arborescence Linux minimale (proc, sys et dev)
# pour que le système fonctionne
mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs none /dev
 
# /dev/pts est important pour que Telnet fonctionne
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
 
# Évite que les messages du noyau ne
# "polluent" l'écran
echo 0 > /proc/sys/kernel/printk
 
# On configure le framebuffer
_mode="$(cat /sys/class/graphics/fb0/modes)"
echo "Configuration du framebuffer mode: $_mode"
echo "$_mode" > /sys/class/graphics/fb0/mode
 
# Obligatoire pour rafraîchir le framebuffer
# voir https://github.com/AsteroidOS/msm-fb-refresher
/usr/sbin/msm-fb-refresher --loop &
 
# mode 16 bits par pixel et on s’assure que la
# taille est la bonne 1440x2560 pour un Samsung Galaxy S7
echo 16 > /sys/class/graphics/fb0/bits_per_pixel
echo 1440,2560 > /sys/class/graphics/fb0/virtual_size
 
# Réinitialisation du framebuffer
echo 1 > /sys/class/graphics/fb0/blank
echo 0 > /sys/class/graphics/fb0/blank
 
# Réinitialisation de la console pour passer sur le framebuffer
# à partir de là, vous devriez voir les lignes de boot sur votre écran
echo 0 > /sys/class/vtconsole/vtcon1/bind
echo 1 > /sys/class/vtconsole/vtcon1/bind
 
echo "Initialisation de la console"
 
# Mise en place de la fonction d'un lien
# Ethernet virtuel via android_usb
SYS=/sys/class/android_usb/android0
printf "%s" "0" >"$SYS/enable"
printf "%s" "18D1" >"$SYS/idVendor"
printf "%s" "D001" >"$SYS/idProduct"
printf "%s" "rndis" >"$SYS/functions"
printf "%s" "1" >"$SYS/enable"
 
echo "USB ethernet via android_usb"
 
# Les paramètres réseau
TELNET_PORT=23
IP=172.16.42.1
 
# On configure l'interface réseau.
# Ici rndis0, car créé via android_usb
ifconfig rndis0 "$IP" 2>/dev/null
 
# On démarre le daemond Telnet
# Permet d'avoir un shell sur le périphérique
# via l'ordinateur
busybox telnetd -b "${IP}:${TELNET_PORT}" -l /bin/bash
 
# On lance un interpréteur de commandes
/bin/bash
 
# Normalement on ne doit pas arriver
# ici. Cela permet d'éviter que le
# système ne reboote infiniment
while true; do
    sleep 1
done

On n'oublie pas les droits :

$ chmod +x /home/user/initramfs/init

2.5 La création du boot.img

Super, nous avons maintenant tout le nécessaire dans l’initramfs pour pouvoir démarrer notre Linux sur le téléphone. Nous allons maintenant devoir créer une image bootable. Pour cela, j’utilise souvent mkbootimg (la dernière version que vous pourrez trouver ici [10]).

Et voici un script permettant de générer une image boot.img :

#!/bin/bash
 
# Les variables de boot.img
var_flash_offset_base="0x10000000"
var_flash_offset_kernel="0x00008000"
var_flash_offset_ramdisk="0x01000000"
var_flash_offset_second="0x00f00000"
var_flash_offset_tags="0x00000100"
var_flash_pagesize="2048"
 
# On crée l'initramfs
cd initramfs/
find . | cpio --create --format='newc' | gzip > ../myinitramfs.img
cd ..
 
# Création du fichier boot.img
$HOME/mkbootimg/mkbootimg \
--kernel "$HOME/kernel/arch/arm64/boot/Image" \
--ramdisk "myinitramfs.img" \
--base "${var_flash_offset_base}" \
--second_offset "${var_flash_offset_second}" \
--cmdline "${var_kernel_cmdline}" \
--kernel_offset "${var_flash_offset_kernel}" \
--ramdisk_offset "${var_flash_offset_ramdisk}" \
--tags_offset "${var_flash_offset_tags}" \
--pagesize "${var_flash_pagesize}" \
--dt "dtb.img" \
-o "boot.img"

Alors là, la première chose qui saute aux yeux est que l’on doit fournir plusieurs paramètres à mkbootimg pour pouvoir générer l’image. Pas de panique, tous ces paramètres peuvent être trouvés en inspectant une image boot.img fonctionnelle (par exemple, l’image officielle de votre constructeur) avec l’outil abootimg [11] :

# abootimg -x $HOME/boot.img
# cat bootimg.cfg
bootsize = 0x2b88810
pagesize = 0x800
kerneladdr = 0x10008000
ramdiskaddr = 0x11000000
secondaddr = 0x10f00000
tagsaddr = 0x10000100
cmdline = buildvariant=eng

On retrouve bien les valeurs.

On remarque dans le script précédant une option --cmdline : elle est bien sûr là pour passer les paramètres de boot au noyau, mais souvent, elle n’est pas prise en compte. Il faudra alors paramétrer les options de boot du noyau en dur lors de la compilation (CONFIG_CMDLINE).

On voit aussi une deuxième option --dt : elle permet de passer un fichier dt.img à abootimg pour l’inclure à la fin du fichier boot.img. Normalement, ce n’est pas la peine si vous utilisez déjà un fichier zImage-dtb, mais ce n’est pas le cas pour mon Samsung Galaxy S7 (et je suis obligé de le passer en paramètre de la sorte).

2.6 Flasher le téléphone

Vous avez donc maintenant un fichier boot.img. Nous allons maintenant essayer de faire démarrer le téléphone ou la tablette dessus. Pour cela, nous allons devoir mettre notre téléphone en mode téléchargement (fastboot sur la majeure partie des téléphones et tablettes, et Odin/Heimdall sur les appareils Samsung). En général, la procédure est d’appuyer sur la touche home, volume bas et power en même temps pour arriver dans ce mode. Sinon, tapez le nom de votre appareil suivi de « Download Mode » sur votre moteur de recherche préféré pour avoir la procédure.

Et une fois votre téléphone dans ce mode, tapez :

# fastboot devices

Si vous voyez quelque chose apparaître, c’est que votre téléphone en mode fastboot a été reconnu.

Pour flasher la partition BOOT de votre appareil, tapez (attention, cela effacera la partition BOOT d’Android et rendra donc votre téléphone incapable de démarrer Android) :

# fastboot flash boot mon-image.img

Ou alors si vous souhaitez juste démarrer sur la nouvelle image sans effacer l’ancienne (une sorte de live cd) :

# fastboot boot mon-image.img

Et si vous avez un appareil Samsung avec Odin (supprime aussi la partition BOOT d’Android) :

# heimdall flash –BOOT boot.img

Si tout s’est bien passé, vous devriez avoir votre Linux qui a démarré et voir les messages de boot du noyau. Ensuite, vous devriez avoir un prompt sur votre téléphone. Vous pouvez y brancher un clavier USB et taper vos commandes favorites :) !

Splash screen

Lors de la phase de production, il est souvent intéressant de pouvoir afficher un splash screen (figure 4). Cela permet d’afficher par exemple le logo de votre organisation ou un message expliquant le chargement du système. L’expérience utilisateur est ainsi nettement améliorée.

Il est possible d’afficher une image ou une animation et aussi une vidéo immédiatement après avoir initialisé le framebuffer. Pour cela, passez comme paramètre console=ram au noyau pour ne pas afficher les messages de boot à l’écran ; et dans votre script d’init, utilisez fbsplash de BusyBox :

gzip -c -d logo.ppm.gz >/tmp/logo.ppm # une image de la taille de votre écran
fbsplash -s /tmp/logo.ppm # On affiche l’image sur /dev/fb0 par défaut

linux-android figure 04

Fig. 4 : Voici un exemple de splash screen. Normalement, vous mettez ici le logo de votre entreprise ou un écran expliquant le chargement.

Cependant, ce n’est pas forcement le plus pratique sur un petit écran et le mieux reste de pouvoir ouvrir un shell distant. Et justement, nous avons configuré le fichier d’init pour cela (via android_usb).

On va d’abord vérifier que l’interface réseau est bien up :

# ip a s
[...]
4: enp0s20f0u4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff

On peut voir qu’une interface réseau est détectée, mais nous n’avons pas encore d’adresse MAC et d’adresse IP. Il faut donc les paramétrer et ensuite, on devrait pouvoir se connecter en Telnet sur le téléphone.

# ip link set dev enp0s20f0u4 address 12:12:12:12:12:12
# ip addr add 172.16.42.2/24 dev enp0s20f0u4
# telnet 172.16.42.1
Trying 172.16.42.1...
Connected to 172.16.42.1.
Escape character is '^]'.
 
bash-5.0# ls
bin boot dev etc opt proc root run sbin sys usr

Et si vous obtenez un shell, c’est gagné !! Vous avez maintenant un Linux minimaliste qui tourne sur votre appareil !!! Et en plus, vous pouvez vous y connecter en Telnet (figure 5).

linux-android figure 05

Fig. 5 : Neofetch dans les deux cas. La photo de gauche représente ce que l’on obtient. Pour le moment, ce n’est pas très visible sur la photo (nous verrons comment augmenter la taille de font par la suite), mais il y a un prompt (bash) et nous pouvons taper des commandes en branchant un clavier USB. La photo de droite représente la même chose, mais dans une session Telnet depuis mon ordinateur de travail. Il est normal que Neofetch nous ressorte peu d’informations, car nous sommes dans l’initramfs et le système est minimaliste.

Voilà, on arrive au bout de la première partie et l’on peut maintenant se demander comment faire pour avoir un système complet ? Arch, Debian, CentOS et tous les autres ? Et comment allumer l’écran, le Wi-Fi, le tactile, le son et tout le reste.

Pas de panique, toutes ces questions vont avoir leurs réponses dans le prochain article, où nous allons enfin arriver au système final.

Dans cet article, nous voyons le minimum requis pour faire tourner un Linux en natif sur notre appareil. Si vous souhaitez voir un exemple en live, j’ai mis en ligne une vidéo : https://youtu.be/wjAWMKi_NAE. Il se peut cependant que la procédure diffère légèrement (ou beaucoup) pour vous. Chaque appareil a ses spécificités, mais avec un peu de recherche sur Internet et l’aide de cet article, vous devriez pouvoir y arriver. Faites bien attention à la version de votre appareil (certains modèles sont déclinés en plusieurs versions) ainsi qu’a son architecture. Dans tous les cas, si vous aussi, vous faites tourner des Linux sur des appareils ou cartes (ARM ou autres), je serai ravi d’échanger avec vous sur vos difficultés et vos succès (on en a tous :)) ; et aussi vous aider, si je le peux. N’hésitez donc pas à me contacter (ameauzoone@squad.fr) si vous souhaitez partager avec moi sur le sujet.

Conclusion

Dans cette première partie, nous avons vu le minimum requis pour faire tourner un Linux en natif sur notre appareil. Bien sûr, le système est pour le moment minimaliste, mais les fondations sont là ! On pourrait presque dire que le plus dur est fait. Dans le second article, nous allons voir comment faire pour avoir un système complet. Nous aurons ainsi un véritable petit Linux de poche :) !

Références

[1] Phosh : https://developer.puri.sm/Librem5/Software_Reference/Environments/Phosh.html

[2] Capture d’écran Plasma Mobile : https://www.plasma-mobile.org/screenshots/

[3] Wiki Little Kernel : https://github.com/littlekernel/lk

[4] Bootlin toolchains download : https://toolchains.bootlin.com/

[5] Device Tree for Dummies! - Thomas Petazzoni, Free Electrons : https://www.youtube.com/watch?v=m_NyYEBxfn8

[6] [Exynos] [DTBH] Compiling a dtb.img with Linux : https://forum.xda-developers.com/android/development/exynos-compiling-dtb-img-linux-t3700690

[7] É. DUBLÉ, « Le test de Peter », GNU/Linux Magazine n°206, juillet 2017 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-206/Le-test-de-Peter

[8] Téléchargement BusyBox static : https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/

[9] Paquet bash-static (5.0-4 ) : https://packages.debian.org/fr/buster/bash-static

[10] mkbootimg : https://github.com/osm0sis/mkbootimg

[11] abootimg : https://github.com/ggrandou/abootimg

 



Article rédigé par

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous