Installer Debian Squeeze sur la mémoire flash interne (NAND) d'un SheevaPlug🔗

Posted by Médéric Ribreux 🗓 In blog/ Debian/

#debian

Introduction

Ça y est, Debian Squeeze est la nouvelle distribution stable de Debian. Cela signifie qu'elle sera juste corrigée pour gérer les problèmes de sécurité. On peut donc penser que les nouveaux paquets ne seront pas courants et qu'il n'y aura pas besoin, sauf urgence, de mettre à jour toutes les semaines. Ainsi, avec un système à peu près stabilisé, on peut sérieusement envisager de mettre une partie de l'OS sur la mémoire flash interne du SheevaPlug (mémoire NAND).

Pourquoi vouloir utiliser la NAND ? Simplement pour dire d'utiliser ce truc: après tout, c'est livré en interne. En sortie d'usine, un système Ubuntu est installé dans cette mémoire. Pourquoi ne pas installer Squeeze à la place ? Un deuxième intérêt est de disposer d'un système de secours: on utilise un périphérique externe (USB/SD) et quand ce dernier se casse ou qu'on veut faire une opération dessus (backup massif, debugging), on peut temporairement utiliser le système embarqué dans la NAND. Il est même envisageable d'avoir du multiboot.

Toutefois, la NAND ne supporte qu'un nombre limité de cycles d'écriture. Il faut donc veiller à ne pas avoir de fichiers qui sont modifiés toutes les 0,3 µs au risque d'user rapidement la NAND…

Dans ce document, nous verrons comment installer un système Debian basé sur Squeeze dans la NAND d'un SheevaPlug en tenant compte de ces limites techniques.

Un peu de théorie !

Avant de commencer, il faut bien voir ce que nous voulons faire. Ensuite, il faudra voir comment nous allons nous débrouiller pour parvenir à nos fins grâce aux outils dont nous disposons.

100% Debian

Bénéficiant d'un système Debian Squeeze fonctionnel sur SheevaPlug, le mieux serait de n'utiliser que les outils livrés par la distribution, sans devoir récupérer des fichiers d'ailleurs comme des noyaux compilés par d'autres distributions.

De nombreux articles existent sur Internet sur ce sujet (mettre un OS sur une mémoire Flash). Certains traitent spécifiquement du SheevaPlug. C'est le cas de cette partie du Wiki du site PlugComputer (ou encore cette ancienne documentation). Il existe également quelques articles qui indiquent comment passer d'un contenu de mémoire Flash en JFFS2 vers un contenu basé sur UBIFS: http://nitrogen.posterous.com/converting-a-sheevaplug-from-jffs2-to-ubiubif

Tous ont un point commun: ils utilisent un noyau compilé par Sheeva-with-Linux et pas le noyau de Debian Squeeze. Dans mon article, j'essayerais au maximum d'utiliser des éléments du projet Debian, y compris le noyau.

Petit focus sur la gestion de la mémoire Flash sous GNU/Linux

Il existe de nombreuses sources de documentation pour se renseigner sur ce sujet. Toutefois, j'ai pu remarquer que si cela fait longtemps que la mémoire Flash est gérée par le noyau Linux, c'est une technologie qui n'est pas encore si démocratique que cela, ce qui implique que l'information n'est pas aussi facilement disponible sur Internet. Voici une petite synthèse qui m'a permis de mieux comprendre le fonctionnement de ce nouveau périphérique de masse.

La première chose à retenir c'est que la mémoire Flash ne se comporte pas du tout comme les disques durs. En effet, d'un côté, on a respectivement de la mémoire un peu particulière alors qu'en face, on a un disque en rotation avec des pièces mécaniques qui s'agitent autour. De fait, les techniques d'accès à la donnée sur ces périphériques diffèrent sensiblement.

Dans les mémoires Flash, il faut mettre de côté les mémoires gérées en hard par un FTL (Flash Translation Layer). Ce sont les clefs USB, les cartes CF/SD/MMC. Ces périphériques contiennent, en plus d'une puce Flash, un composant qui gère les cycles d'écritures et le wear leveling et qui émule le comportement d'un disque dur. Ainsi, pour le Noyau, un tel périphérique n'est ni plus ni moins qu'un disque dur comme un autre.

Première couche: MTD

La difficulté est la gestion des mémoires Flash en direct, sans FTL. Pour celles-ci, c'est bien le noyau qui doit gérer. Pour cela, il utilise les MTD pour Memory Technology Device. Il s'agit d'une couche dans le noyau, chargée de gérer la mémoire Flash. On peut ainsi accéder à des périphériques "spéciaux" visibles comme /dev/mtd0 ou /dev/mtd1. Cette couche est la première sur laquelle on peut ensuite mettre un système de fichiers adapté. En plus du noyau, il existe des outils en espace utilisateur: les mtd-utils (qui contiennent également les couches suivantes).

Restons sur les MTD… Une mémoire Flash peut se voir compartimentée en plusieurs "blocs", l'équivalent des partitions. Après quelques recherches, il s'avère qu'il n'y a pas de table de partition et qu'il faut indiquer au noyau comment est découpée la Flash. Si je prends l'exemple du SheevaPlug, on constate que la NAND fait 512Mo. Mais ce gros bloc est découpé en 3 parties: la première qui fait 1Mo qui contient le binaire de U-Boot. La seconde qui est nommée uImage et qui contient un noyau Linux (celui de la distribution livrée en sortie d'usine). Enfin, sur la dernière partie, on trouve (rootfs), on trouve les fichiers du système d'exploitation sur un volume en JFFS2.

Pour vous en rendre compte, vous devez simplement lancer la commande mtdinfo ou un cat /proc/mtd.

Deuxième et troisième couches: UBI et UBIFS

Une fois qu'on dispose d'une "partition" de Flash, on peut mettre un système de fichiers dessus. Bien entendu, les systèmes de fichiers classiques pour disques durs ne sont clairement pas adaptés: exit ext2/3/4 ou XFS ou ReiserFS. Il faut employer quelque-chose qui gère le wear leveling et les particularités des mémoires Flash. Il existe pas mal de systèmes de fichiers gérés: JFFS2, LogFS, UBI/UBIFS. Celui qui a la cote en ce moment est le couple UBI-UBIFS pour tout un tas de bonnes raisons. Pour un comparatif, vous pouvez consulter l'excellent article de patrick_g sur LinuxFR.

En ce qui concerne UBI, sachez que c'est un équivalent de LVM: il gère des volumes sur des MTD. Il ne contient que des images mais ne gère pas de fichiers. Ensuite, on peut y mettre un système de fichiers comme UBIFS qui est spécialement conçu pour ça. Si vous voulez plus d'informations techniques sur UBI et UBIFS, vous pouvez consulter la documentation de référence (qui est assez réduite):

UBIFS et délocalisations

Un des moyens de régler le problème des cycles d'écriture limités sur la NAND est d'utiliser un système de fichier adapté. Comme nous venons de le voir, le couple UBI-UBIFS remplit très bien ce rôle.

Toutefois, on peut également remarquer que certains répertoires concentrent 80 à 99% des opérations d'écriture: il s'agit de /tmp et de /var et éventuellement de /opt. Il faut donc délocaliser ces répertoires sur des périphériques qui craignent moins que la NAND. Une carte SD, même si elle coûte un peu d'€, se remplace facilement par une neuve. En revanche, une fois que la NAND est morte, c'est foutu !

Il est clair que vu la taille réduite de notre espace disque sur la NAND, on peut imaginer que /tmp soit en RAM: tout ce qui est dans /tmp peut être supprimé à chaque reboot. Il en est de même pour d'autres répertoires (plus d'explications sur le Wiki Debian#Extendingflashmemorylife)). L'option noatime. est également à considérer.

Pour aller plus loin, il est également possible de mettre la racine du système en mode lecture seule de manière à éviter toute écriture intempestive. Le Wiki Debian documente cette technique.

Ainsi, pour être complet, nous allons utiliser un périphérique extérieur à la NAND pour stocker tout ce qui est non figé sur le système. Cette partie contiendra des éléments du système (/var notamment) ainsi que des données qui sont susceptibles d'être modifiée (des images, des fichiers, etc.).

U-Boot ne fait pas le café

Armé de toutes ces précisions techniques, il reste un élément clef à gérer: U-Boot, le bootloader du SheevaPlug.

Pour faire simple, il faut retenir que ce dernier n'a rien pour gérer les volumes UBI. Il faut donc charger le noyau ailleurs que dans une partition UBI. On peut utiliser un périphérique externe (c'est déjà le cas si vous utilisez un système complet sur carte SD ou clef USB). Mais le plus appréciable est de mettre cette partie important du système également sur la Flash, de manière à être plus autonome.

Ainsi, nous allons faire en sorte de mettre le noyau Debian ainsi qu'une image Initrd sur la Flash et faire en sorte que U-Boot puisse y accéder.

Procédure

Voici un résumé de ce que nous allons réaliser dans les opérations à venir.

Installation de Debian sur une carte SD/Clef USB

Pour ça, lisez mon article.

Revoir le partitionnement de la NAND

Par défaut, la NAND est architecturée comme suit:

# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00100000 00020000 "u-boot"
mtd1: 00400000 00020000 "uImage"
mtd2: 1fb00000 00020000 "root"

La première partie (mtd0) contient U-Boot, le bootloader du SheevaPlug. On ne va donc surtout pas y toucher: nous avons besoin de ce programme pour lancer la machine. Vient ensuite mtd1 qui contient une image de noyau Linux spécialement formatée pour le Sheevaplug avec l'outil mkimage. Enfin, on trouve une partie nommée root dans mtd2 qui contient le système d'exploitation complet (Ubuntu en sortie d'usine).

Dans notre cas, nous voulons revoir ce partitionnement en gardant mtd0 mais en ajoutant une partition de 10Mo qui contiendra l'Initrd Debian.

Mais avant tout, quelques informations sur le partitionnement d'un MTD…

Sur un disque dur, il existe une table des partitions: c'est un espace situé dans les premiers secteurs du disque qui indique comment sont organisées les partitions (secteurs de début, de fin, système d'exploitation, nom, type, etc.). Dans une NAND, il n'existe pas une telle information. La table des partitions est indiquée au moment du boot au noyau par le bootloader. Dans notre cas, c'est U-Boot qui indique au noyau Linux la "table de partition" de la NAND.

Nous devons connaître deux éléments:

Sous U-Boot, il faut d'abord définir la variable mtdids qui permet de faire le lien entre le nom du périphérique NAND dans U-Boot (nand0) avec le nom pour le Kernel (orion\_nand dans notre cas). Ensuite, il faut définir les partitions. Cette définition est arbitraire: vous pouvez mettre n'importe quoi, même si ça ne représente pas la réalité de ce qui est sur la NAND.

Remplir la variable mtdids:

setenv mtdids orion_nand

Indiquer la table des partitions à U-Boot:

setenv mtdparts mtdparts=orion_nand:1m(uboot),3m(uImage),10m(uInitrd),498m(rootubi)

Indiquer la table de partitions au Noyau (en une seule ligne):

setenv bootcmd 'setenv bootargs $(bootargs_console) $(mtdparts); run bootcmd_mmc; 
bootm 0x400000 0x0800000'

Dans cette commande, on se contente d'ajouter le paramétrage de mtdparts pour indiquer au noyau Linux le nouveau partitionnement. Vous pouvez constater qu'à aucun moment, je ne me suis occupé de partitionner et de formater avec un outil dédié ! Tout est déclaratif même si je pense qu'il doit existe un moyen de figer ça quelquepart.

Ainsi, lorsque vous allez booter sur votre système actuel, la commande cat /proc/mtd devrait vous retourner les informations suivantes:

dev:    size   erasesize  name
mtd0: 00100000 00020000 "uboot"
mtd1: 00300000 00020000 "uImage"
mtd2: 00a00000 00020000 "uInitrd"
mtd3: 1f200000 00020000 "rootubi"

Si ça fonctionne, vous pouvez enregistrer la configuration dans U-Boot grâce à la commande saveenv.

Installation du noyau et de l'image initrd sur la Flash

Les opérations sont assez simples maintenant que nous disposons des partitions. Vous aurez besoin du paquet mtd-utils pour continuer. La suite des opérations se fait sous le compte root, celui qui dispose des pouvoirs adéquats ! Attention, à partir de ce stade, vous allez réellement écrire sur la NAND: il ne sera plus possible de revenir en arrière !!!

Mettre le noyau Debian sur la bonne partition:

# flash_eraseall -j /dev/mtd1
# nandwrite -pm /dev/mtd1 /boot/uImage

Mettre l'image Initrd sur la bonne partition:

# flash_eraseall -j /dev/mtd2
# nandwrite -pm /dev/mtd2 /boot/uInitrd

A ce stade, vous voudrez sans doute tester si vous pouvez booter en utilisant la NAND. Sachant que l'image Initrd que vous avez employée contient l'emplacement de la racine du système de fichier en dur et que c'est celle que vous utilisiez pour booter sur votre Clef USB/Carte SD, si tout s'est bien passé, vous devriez vous retrouver avec un système complètement fonctionnel !

Redémarrez votre système et lancez les commandes suivantes dans U-Boot (à titre de test: on ne va rien garder en configuration par défaut):

nand read 0x00800000 uImage
nand read 0x01100000 uInitrd
bootm 0x00800000 0x01100000

Si vous vous retrouvez avec un système fonctionnel, c'est que c'est bon. Sinon, il va falloir hacker pour trouver d'où vient l'erreur…

Création d'une image UBIFS

Avant de mettre une partie du système d'exploitation sur la NAND, il faut commencer par faire une image ubifs qui va bien. La méthode la plus rapide que j'ai trouvée exploite une clef USB: c'est plus efficace que de mettre un montage NFS. La commande mkfs.ubifs va nous permettre de réaliser une image de notre système live. Toutefois, comme nous voulons exporter des répertoires sur un volume externe (clef USB ou carte SD), il faut prendre uniquement quelques morceaux de notre système d'exploitation que nous mettrons sur la NAND.

Avant de se lancer dans la fabrication de l'image UBIFS, il faut copier uniquement les parties qui nous intéressent dans un autre répertoire: mkfs.ubifs ne peut pas créer une image qui se trouve dans l'arborescence en cours. Nous allons donc utiliser notre clef usb montée dans /media/usb. Les répertoires à mettre dans la NAND seront copiés dans /media/usb/root.

cp -aur /bin /boot /etc /lib /sbin /usr /media/usr/root

Maintenant, il reste à modifier le fichier /media/usr/root/etc/fstab pour indiquer les bons points de montage. Voici celui que j'ai retenu:

# Racine de l'arborescence, notre système ubifs
/dev/root       /               ubifs   defaults,noatime        0       0

# Systèmes déportés:
/dev/mmcblk0p3  /var            ext2    defaults,noatime        0       2

# Systèmes de fichier spéciaux:
proc            /proc           proc    defaults                0       0
tmpfs           /tmp            tmpfs   defaults                0       0

Ensuite, je vous recommande de mettre à Yes les options RAMRUN et RAMLOCK du fichier /etc/default/rcS. Cela permet de placer automatiquement les répertoires /var/run et /var/lock en RAM (tmpfs).

Dans un premier temps, j'avais pensé utiliser l'option bind de mount pour placer les répertoires opt home et root mais ça pose des problèmes (\#338801, \#359717 et \#481546) il faut donc se contenter des faire des liens symboliques vers les bons emplacements:

# cd /media/usb/root && ln -s /var/local/root root && ln -s /var/local/home home 
# ln -s /var/local/opt opt

Ensuite, il faut créer les points de montage pour les systèmes de fichier à monter:

# cd /media/usb/root && mkdir dev media proc sys selinux var

Enfin, nous pouvons nous lancer dans la construction de notre image UBIFS nommé ubifs.img:

# mkfs.ubifs -v -r /media/usb/root -m 2048 -e 129024 -c 4096 -o /media/usb/ubifs.img

Préparation du système de fichiers modifiable

Nous avons vu que le système de fichiers sera en lecture seule et que nous allons dédier /var aux écritures. /var sera une partition sur notre carte SD (ou un autre média). Dans mon cas, je dispose d'une carte SD de 4Go dont 2 composent le root de ma distribution actuelle, entièrement contenue sur la carte SD et respectivement, le swap. Il me reste donc 2Go que je peux utiliser comme /var. C'est ce que nous allons créer et aménager pour coller à notre fichier fstab précédent. En utilisant des liens symboliques, on peut "déporter" un répertoire dans un autre emplacement de l'arborescence. Ainsi, les répertoires /home /opt et /root seront déportés de cette manière.

Il reste toutefois des éléments à régler avant de pouvoir mettre notre système en lecture seule. Pour cela, lisez le Wiki Debian

Voilà, nous disposons d'un /var adapté à notre besoin. Il ne reste plus qu'à essayer de booter en utilisant notre système Flash.

Mettre l'image UBIFS sur la NAND

Avant de placer une image UBIFS sur la NAND, il faut injecter cette image dans un volume UBI. Ensuite, on flashera ce volume sur la NAND.

Pour commencer, on édite le fichier /media/usb/ubi.cfg:

[rootfs-volume]
mode=ubi
image=/media/usb/ubifs.img
vol_id=0
vol_size=490MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize

Ensuite, on fabrique l'image UBI par la commande suivante:

# ubinize -v -o /media/usb/ubi.img -m 2048 -p 128KiB -s 512 /media/usb/ubi.cfg

Enfin, on peut flasher l'image du volume UBI sur la NAND:

# ubiformat /dev/mtd3 -f /media/usb/ubi.img

La commande ubiformat permet de conserver l'historique des cycles d'écriture des cellules NAND. Voilà pourquoi nous l'utilisons à la place de la commande nandwrite.

Configurer U-Boot pour que tout le bordel se lance

Maintenant que nous avons une NAND équipée, il convient d'indiquer à U-Boot comment booter avec. On peut le faire grâce aux commandes qui suivent (3 commandes, la dernière, affichée sur 2 lignes est une seule commande) :

setenv ubiargs ubi.mtd=rootubi root=ubi0:rootfs rootfstype=ubifs
setenv bootcmd_nand 'nand read 0x00800000 uImage; nand read 0x01100000 uInitrd'
setenv bootcmd 'setenv bootargs $(bootargs_console) $(mtdparts) $(ubiargs); run bootcmd_nand;
bootm 0x00800000 0x01100000'

(ne pas lancer saveenv à ce stade: des fois que ça casserait !).

ubiargs permet d'indiquer l'emplacement de la racine au noyau Linux. La commande bootcmd_nand permet de charger le noyau et l'image initrd en mémoire. Enfin, la commande bootcmd met tout ça en musique (paramètres, chargement des images en RAM et lancement du boot).

Normalement, vous devriez pouvoir bouter sur la NAND avec cette configuration. Quelques erreurs sont possibles (c'est ce qui m'est arrivé) et sont liées bien souvent à l'absence des points de montage (genre oubli du répertoire /selinux ou /var).

Une fois que tout est paré, n'oubliez pas de rendre cette configuration par défaut dans U-Boot grâce à la commande ` saveenv `\.

Vous disposez maintenant d'un système complet, votre dernière étape consiste à rendre le système root en lecture seule en modifiant /etc/fstab, si vous le désirez !

Notes: Bug et Sauvegarde

Problème de montage

Debian Squeeze est frappée d'un petit bug pas hyper gênant… mais gênant quand même. Le paquet en cause est initscripts mais le bug n'est (pas encore) remonté. Il se manifeste par le fait que la partition /var située sur la carte SD ne se démonte pas correctement. Ainsi, à chaque reboot, fsck entre en jeu et indique qu'un système n'a pas été correctement démonté.

Le bug est lié au script d'init: ` /etc/init.d/umountfs `\ et la cause est l'utilisation d'un point de montage UBI comme root. En effet, lorsqu'on utilise un périphérique UBI comme racine, le point de montage est référencé non pas en /dev/ubi0_0 mais en ubi0:0. Du coup, une expression rationnelle dans le script qui ne tient pas compte de nommage fait que tous les montages apparaissent comme montages protégés, à ne pas démonter.

Pour corriger le bug rapidement, il suffit de modifier la ligne (22):

PROTECTED_MOUNTS="$(sed -n '0,/^\/[^ ]* \/ /p' /proc/mounts)"

par:

PROTECTED_MOUNTS="$(sed -n '0,/^\/\|ubi[^ ]* \/ /p' /proc/mounts)"

Sauvegarde

N'oubliez pas de conserver ailleurs votre image UBI que vous pourrez re-flasher à tout moment en cas de problème. Je vous conseille également de conserver votre partition initiale, sur votre carte MMC. Elle permettra, en cas de besoin de flasher l'image UBI à partir de Debian plutôt que depuis U-Boot.