La problématique
La mise à jour des systèmes embarqués est encore aujourd'hui une problématique sérieuse pour les développeurs due à la complexité de leur mise en œuvre. Le système se trouvant en général chez le client ou chez le consommateur, la robustesse du déploiement d'une mise à jour est un point clé. Une mise à jour incorrecte peut en effet rendre le système inutilisable et nécessiter un retour au Service Après Vente, très coûteux pour le constructeur et avec un impact négatif pour le client.
Il existe une grande diversité de solutions pour pallier à ces problèmes de déploiement que l'on peut observer régulièrement sur nos téléphones ou sur nos Box internet. Ces solutions, malgré leur diversité, restent pour la plupart très spécifique et/ou fermées.
Le but de cet article est de présenter une solution simple et robuste pour mettre à niveau un système embarqué. Cette solution utilise le système de démarrage U-boot.
La solution
La solution la plus commune et la plus simple est d'utiliser le multi-partitionnement du système embarqué pour installer une mise à jour. Le principe est de disposer de plusieurs partitions sur lesquelles on peut installer différentes versions du système. Par exemple en prenant un double partitionnement :
- le système embarqué démarre en utilisant la partition 1. Il installe une mise à jour sur la partition 2.
- Au redémarrage, le système utilisera la partition 2 et la mise à jour suivante sera installée sur la partition 1.
- Et ainsi de suite...
L'intérêt de cette approche est de gérer les problèmes de mise à jour en permettant de booter sur la partition inchangée si besoin. Cependant, il n'existe pas de solution existante sous U-Boot pour gérer cette alternance de partitions au démarrage.
Cet article à donc pour but de présenter les fonctionnalités d'un système de gestion des partitions ainsi que son fonctionnement sous U-boot. Pour cela, nous allons utiliser l'exemple d'une mémoire Flash de 128Mo contenant quatre partitions : les deux partitions énoncées plus haut, auxquelles s'ajoutent une partition de secours et une dernière partition pour stocker les données partagées.
1. Les fonctionnalités
Le gestionnaire est un couple de deux scripts (un sous Linux et un sous U-boot) offrant les possibilités suivantes :
- Capacité de retour vers une ancienne version
- Fortement configurable (de 1 à plusieurs partitions)
- Avoir une partition optionnelle dédiée à un système de secours
- Les partitions peuvent être réparties sur plusieurs supports de stockage
- Tolérance aux erreurs d'écriture et aux coupures de courant
Ce système entraîne certaines contraintes :
- Pour n partitions, une capacité n à n+1 fois plus grande est nécessaire
- La machine doit utiliser U-boot et Linux
- Linux doit avoir les outils fw_printenv et fw_setenv pour dialoguer avec U-boot
- L'environnement de U-boot doit être redondant pour tolérer les erreurs
2. Le fonctionnement
Le système décrit ici se base sur un script U-boot et l'utilisation des variables d'environnement de U-boot. Plusieurs informations sont stockées sur chacune des partitions : le nombre de tentatives de démarrage, l'état de la partition ("bon", "mauvais", "en cours de mise à jour" ou "à tester") et un texte donnant la description du système installé.
Le système a deux cas d'utilisation :
- un premier pendant l'installation d'une mise à jour depuis Linux.
- un second au cours du démarrage
a. Cas de l'installation d'une mise à jour sous Linux
Au début de cette installation, Linux marque la plus ancienne partition comme étant dans l'état "en cours de mise à jour" pour signaler que la partition ne doit plus être utilisée, et à la fin de cette installation, Linux marque la partition à l'état de "à tester" pour signaler qu'il faudra tenter d'utiliser cette partition au prochain démarrage.
b. Cas de démarrage de la machine sous U-boot
Sous U-boot, le système doit tenter de démarrer une partition mise à jour (marquée "à tester"). En cas de réussite, il faudra alors marquer cette partition à l'état de "bon" par le système Linux (ce marquage pouvant être automatique).
Le nombre de tentatives de démarrage depuis l'état "à tester" est noté dans la table de partition. Au bout d'un certain nombre d'échecs la partition "à tester" est abandonnée par U-Boot qui démarrera alors sur la partition "bon" ayant la version la plus récente. Le système Linux sur cette partition marquera alors la partition "à tester" comme "mauvais"
Dans le pire des cas, si aucune partition n'a pu être démarrée, c'est le système de secours qui sera démarré pour faire de la maintenance.
Ce gestionnaire permet ainsi de toujours pouvoir démarrer la machine embarquée sur une version du système stable ou, dans le pire des cas, sur un système de secours.
L'ensemble des scripts constituant cette solution est disponible ici.
Conclusion
Cet article présente donc une méthode assez générale de gestion des partitions et des cas d'erreurs pour une mise à jour de Linux, sous U-boot. Cependant, les principes mis en oeuvre ici peuvent être repris et implémentés pour d'autre type de gestionnaire de démarrage comme BareBox.
Le mieux est encore d'avoir un u-boot par partition et le switch de l'une à l'autre hardwarement commandé (CPLD en travers du bus de la flash de boot), avec pour u-boot (ou une génération à des adresses fixes liées au vecteur de reset est souvent requise) un switch hardware, par exemple de 2 secteurs de flash nor (présentant l'un ou l'autre par tripatouillage de quelques lignes d'adresse).
Car il ne faut pas oublier que nombre de correctifs (errata processeur, mapping, reader de file-system...) imposent de pouvoir aussi changer u-boot de manière sécurisée. De même que des changements d'interface u-boot/kernel, même si l'usage des device-tree a bien réduit les dépendances.
Cela est particulièrement important sur des produits à durée de vie longue, susceptibles de connaitre des évolutions importantes (hardware de "reserve" activé après mise sur le marché, potentiellement demandeur de "workaround" dans le boot loader), si ce n'est les évolutions du noyau (interfaces au boot loader, file-systems) au fur et a mesure des mises à niveau logicielles...
Ce besoin est directement induit par la structure à un seul niveau de boot de u-boot, qui complique un petit peu le problème en le renvoyant hélas impérativement au hardware: Bien des boot loader passés utilisés dans l'industrie s'inspiraient un peu des 2 niveaux de celui d'un PC (dont le bios n'évolue généralement jamais, seul le ntldr/grub le faisant régulièrement).
Cela permettait un boot loader de niveau 1 aussi minimaliste que figé... tout le potentiellement variable (reader FS, mapping, quasi totalité des erratas, interface/paramètres OS) étant dans un boot loader de niveau 2, avec un exemplaire par partition (dans une sous-partition raw excluant les pb de FS-reader). Le switch de partition étant controllé par le niveau 1.