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.
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 !
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.
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 :
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.
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/.
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) :
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.
Voilà la liste des options dont je vous recommande l’activation :
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. »
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.
C’est ce module qui va vous permettre d’avoir le pseudo-périphérique /dev/fb0 (gestion pixel par pixel de l’écran)
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).
Permet d’avoir le support du système de fichier ext4.
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.
Permet d’avoir le support du devtmpfs (peuplement automatique de /dev).
Je vous conseille aussi de NE PAS activer :
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 :
-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/ :
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 :
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 !) :
De même avec Neofetch [9] :
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 :
On n'oublie pas les droits :
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 :
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] :
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 :
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) :
Ou alors si vous souhaitez juste démarrer sur la nouvelle image sans effacer l’ancienne (une sorte de live cd) :
Et si vous avez un appareil Samsung avec Odin (supprime aussi la partition BOOT d’Android) :
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 :
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 :
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.
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).
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