Linux est de plus en plus présent dans notre vie quotidienne, dans des équipements certes techniques mais relativement communs et surtout n’ayant pas une apparence d’ordinateur.
En effet, quel est le point commun entre l’iPod d’Apple (baladeur MP3 portable), la FreeBox3 de Free (équipement offrant l’accès à l’offre internet/téléphonie/télévision), le Navigator de TomTom (système de guidage automobile), les Zaurus de Sharp (assistants personnels), les A760, A780 et E680 de Motorola (téléphones mobiles), le NSLU2 de Linksys (disque dur réseau) ? Tous embarquent, ou peuvent embarquer Linux ou µCLinux sur un processeur à cœur ARM.
1.Introduction
L’architecture ARM bien que peu connue du grand public est l’une des plus utilisées au monde. En effet, les cœurs ARM sont disponibles sous licence de la part d’ARM et sont intégrés par la plupart des fondeurs dans leurs propres processeurs. Les divers sondages et études de marché de www.linuxdevices.com confirment la popularité des cœurs ARM auprès des développeurs d’architectures embarquées.
Développé initialement par Acorn (fabricant britannique d’ordinateurs) entre 1983 et 1985, ARM signifie alors Acorn RISC Machine. L’ARM1 est un processeur 32 bits simple, peu coûteux et est utilisé par Acorn en tant que coprocesseur du 6502 qui équipe ses ordinateurs BBC Micro. Le pari est osé mais part d’hypothèses simples : en créant un processeur RISC, le cœur serait plus petit et donc moins cher, les performances seraient théoriquement meilleures car chaque instruction s’exécuterait en un nombre de cycle réduit, et le design serait plus simple et donc plus rapide à mettre au point.
Acorn, racheté par Olivetti, sort son premier ordinateur fonctionnant intégralement sur un ARM 2 à 8 MHz en 1987. Il s’agit de l’Archimedes. Acorn est alors l’un des leaders du marché de l’informatique personnelle et éducative en Grande-Bretagne. La plupart des logiciels sont développés par des petites sociétés gravitant autour d’Acorn.
En 1989, Acorn sort l’ARM3 qui dispose de 4 kilo-octets de cache et fonctionne à 25 Mhz. VLSI, le partenaire fondeur d’Acorn parvient alors à convaincre d’autres fabricants d’utiliser la technologie ARM dans leurs produits. Cela marque les débuts d’ARM, Advanced RISC Machine, société créée par Acorn, Apple et VLSI et chargée de développer la technologie ARM. ARM développe l’ARM6 ainsi qu’un coprocesseur arithmétique flottant et un contrôleur graphique amélioré. L’objectif était, entre autres, de créer un processeur performant et optimisé pour une utilisation nomade, l’ARM610, qui allait équiper le fameux Newton d’Apple. Petit à petit, les fabricants de semi-conducteurs font l’acquisition de licences ARM pour créer leurs nouveaux produits, souvent poussés par leurs clients, les fabricants d’équipements informatiques grands publics et d’appareils embarqués.
En ayant comme objectif permanent l’obtention de hautes performances pour un faible prix, de minimiser la consommation et de permettre de réduire les cycles de développements d’un composant personnalisé, ARM a fini par convaincre une cinquantaine de fabricants de semi-conducteurs, soit la quasi-totalité des grands noms du domaine.
Les quatre principaux cœurs actuellement disponibles sur le marché sont : l’ARM7, l’ARM9, l’ARM11 et le XScale. Des déclinaisons existent dans chaque gamme en fonction de la présence de MMU, et de divers accélérateurs. La gamme ARM11 ouvre de plus les portes du multicore (jusqu’à 4) sur ARM.
Avant d’aller plus loin, il est important de parler de deux technologies développées par ARM : le thumb qui est un jeu d’instructions 16 bits que la majorité des ARM acceptent et qui peut être intéressant pour certaines applications, le Jazelle qui est un core sachant exécuter du bytecode java qui est intéressant pour les applications de type téléphone mobile par exemple.
Les cœurs ARM ont sept modes d’exécution :
- User : sans privilège, pour la majorité des tâches ;
- FIQ : interruptions de haute priorité (Fast) ;
- IRQ : interruptions de " faible " priorité (Normale) ;
- Superviseur : reset et SWI (software interrupt) ;
- Abort : violation d’accès mémoire ;
- Undef : instruction non définie ;
- System : privilèges maximum (partageant les mêmes registres que le mode User).
Le schéma suivant (directement copié d’une présentation d’un ingénieur d’Intel) présente les différents registres disponibles et notamment les registres propres à chaque mode ainsi que les registres transversaux :

Registres : R0 à R12 : registres – R13 : Stack Pointer – R14 : Link Register – R15 : Program Counter – CPSR : Current Program Status Register – SPSR : Saved Program Status Register.
Le registre PSR contient un certain nombre d’informations intéressantes telles que :
- Les flags de status : Negative, Zero, Carry, oVerflow, Q (saturation), J (mode Jazelle) ;
- Les flags de gestion des interruptions : I et F (respectivement IRQ et FIQ) ;
- Le flag du jeu d’instruction en cours : T (0 = ARM, 1 = Thumb) ;
- Le mode actuel : codé sur 4 bits.
Avant d’aller plus loin, nous allons faire un rapide tour d’horizon des instructions disponibles sur un cœur ARM. Nous ne présenterons que les quelques instructions qui seront nécessaires pour comprendre la suite. Les documents disponibles à l’adresse suivante récapitulent plus précisément toutes ces instructions de manière exhaustive :
http://www.armsemi.com/documentation/Instruction_Set/index.html
Tous les exemples suivants sont issus du code source du bootloader u-boot.
- Instruction Branch (b) : elle permet de changer la valeur du program counter et donc d’appeler une fonction située dans la zone +/- 32 Mo de l’instruction courante. Sa dérivée est l’instruction Branch with Link (bl) qui sauvegarde auparavant l’adresse de l’instruction suivante dans le Link Register afin de pouvoir y revenir une fois la fonction appelée terminée.
Exemples :
aute à la fonction reset
exécute la fonction cpu_init_crit qui aura la possibilité de revenir à l’instruction suivante afin de continuer l’exécution de l’initialisation.
- Instruction Move (mov) : elle permet d’affecter une valeur à un registre.
Exemples :
affecte le contenu du registre lr (Link Register) au registre pc (Program Counter). Cela a pour effet de revenir à l’instruction suivant l’appel de la fonction en cours (l’équivalent du return en C).
affecte la valeur 0 au registre R2.
- Instruction Load (ldr) : elle permet de charger dans un registre la donnée présente à une adresse.
Exemples :
|
|
ldr pc, _undefined_instruction .../... _undefined_instruction: .word undefined_instruction |
l’adresse représentée par _undefined_instruction va contenir la valeur d’adresse de la fonction undefined_instruction. L’instruction ldr servira à charger cette valeur dans le program counter et donc à appeler la fonction.
va charger dans r1 la constante 0x12345678.
va charger dans r1 la donnée pointée par l’adresse contenue dans r2.
va charger dans r0 la donnée pointée par l’adresse contenue dans r1 + 8 octets.
Pourquoi une telle manipulation alors qu’il aurait semblé possible de faire un mov pc, adresse de la fonction undefined_instruction ?
Tout cela est dû au codage des instructions dans la mémoire : une instruction peut contenir une constante (dite " immédiate "), mais seulement 12 bits (sur les 32 de l’instruction) sont affectés à la représentation de cette constante. Il faut donc bien penser à la valeur de la constante et à sa représentation car toutes les constantes ne peuvent être représentées de cette manière. Sur les 12 bits, les 4 premiers sont la " valeur de rotation " qui sera multipliée par deux (car il s’agit tout le temps d’un décalage d’un nombre pair de bits), les 8 suivants sont l’octet de donnée qui subit la rotation.
La donnée 32 bits représentée est donc obtenue par une rotation circulaire vers la droite (les bits en position 0 sont replacés en position 31).
Quelques exemples pour mieux comprendre :
- #0x00008000 est représenté par : 0x80 ror 24, codé par 0xC80 ;
- #0x10008000 n’est pas représentable ;
- #4096 est représenté par : 0x40 ror 26, codé par 0xD40 ;
- #0x00FF0000 est représenté par : 0xFF ror 16, codé par 0x8FF.
- Instruction Store (str) : elle permet de stocker à une adresse la valeur contenue dans un registre.
Exemples :
stocke dans r0 la valeur pointée par l’adresse r1.
- Les instructions complémentaires sont assez classiques (se référer au document ARM pour le jeu d’instruction complet) :
- ADD : addition
- SUB : soustraction
- MUL : multiplication
- AND : et logique
- EOR : ou exclusif
- ORR : ou logique
- BIC : bit clear
- CMP : comparaison
Nous savons maintenant que l’ARM a 7 modes de fonctionnement et avons quelques notions d’assembleur, il est temps de voir comment le processeur passe d’un mode à l’autre.
La table des vecteurs est située en 0x0000 0000 ou en 0xFFFF 0000. Elle fournit les pointeurs vers divers gestionnaires d’exception : Reset, Undefined Instruction, Software Interrupt, Prefetch Abort, Data Abort, IRQ et FIQ.
Lors du démarrage d’un système le processeur va directement au vecteur de reset et exécute la fonction associée. Ainsi, en désassemblant les premières instructions d’un bootloader, on se retrouve avec la forme suivante :
|
|
00000000 <_start>: 0: ea000012 b 50 <reset> 4: e59ff014 ldr pc, [pc, #20] ; 20 <_undefined_instruction> 8: e59ff014 ldr pc, [pc, #20] ; 24 <_software_interrupt> c: e59ff014 ldr pc, [pc, #20] ; 28 <_prefetch_abort> 10: e59ff014 ldr pc, [pc, #20] ; 2c <_data_abort> 14: e59ff014 ldr pc, [pc, #20] ; 30 <_not_used> 18: e59ff014 ldr pc, [pc, #20] ; 34 <_irq> 1c: e59ff014 ldr pc, [pc, #20] ; 38 <_fiq> |
Nous retrouvons bien notre table de vecteur avec un branchement vers une fonction reset en 0x0. Le traitement des autres exceptions est fait en chargeant l’adresse de la fonction à appeler dans le program counter.
2.Bases électroniques disponibles pour embarquer Linux
La majorité des grands fabricants de microprocesseurs ont fait l’acquisition d’une licence ARM et proposent des produits plus ou moins dédiés à certains marchés en fonction des périphériques qu’ils intègrent. Nous différencions les microprocesseurs des microcontrôleurs par la présence d’un bus externe d’adresses et de données sur les premiers, lequel bus offre la possibilité d’interfacer des mémoires externes et d’autres périphériques selon les besoins.

Exemple du passage entre deux modes
En effet, ces dernières années ont vu l’arrivée de microcontrôleurs à base de cœur ARM7, l’objectif étant de concurrencer les " anciens " microcontrôleurs tels que les 8051, les PIC, AVR ou MSP430. Mais bien entendu, pas question de faire tourner un Linux ou un µCLinux dessus vu que la majorité de ces composants intègrent seulement quelques dizaines de Ko de RAM et de Flash et n’offrent pas de possibilité d’extension (eCos ou RTEMS sont par contre des solutions qui peuvent être intéressantes pour ces processeurs). Il existe toutefois un environnement de développement libre, basé sur les outils GNU et sur Eclipse qui permet de développer sur ces microcontrôleurs. Une documentation très complète sur le sujet est disponible à l’adresse : http://www.olimex.com/dev/pdf/ARM%20Cross%20Development%20with%20Eclipse%20version%203.pdf
Fabricants proposant des microcontrôleurs ARM7 (liste non exhaustive) :
Philips (gamme LPC1), Atmel (gamme SAM7), ST (gamme STR7), Analog Devices (gamme AduC7000), OKI (gamme ML67)...
Fabricants proposant des microprocesseurs ARM7, 9, XScale ou 11 :
Intel (gamme Xscale – ARMv5 avec des améliorations propres à Intel et surtout une consommation optimisée et des fréquences souvent plus importantes que la concurrence), Atmel (gamme AT91), Philips (gamme LPC2 et 3), Freescale (ex : Motorola, gamme MX), Cirrus Logic (gamme EP93xx), TI (gamme OMAP), Samsung (gamme S3C), Sanyo, Sharp...
Toutefois, développer une carte électronique intégrant un de ces processeurs est certes faisable mais relativement long et coûteux. Il existe des solutions intermédiaires qui sont souvent basées sur des SOM : System On Modules.
De nombreux fabricants sont présents sur ce marché, je ne citerai que les plus connus.
- Karo (www.karo-electronics.de) : constructeur allemand offrant une gamme de modules à base de processeurs PXA de Intel (cœurs XScale) ;
- Arcom (www.arcom.com) : constructeur anglais offrant une gamme de cartes au format PC104 dont certaines à base de processeurs PXA et IXP de Intel ;
- Gumstix (www.gumstix.com) : constructeur américain offrant une gamme de modules à base de processeurs PXA de Intel ;
- Cogent (www.cogcomp.com) : constructeur américain offrant une gamme de modules à base de processeurs ARM9 de Atmel et Freescale et de processeurs PXA de Intel ;
- RLC (www.rlc.com) : constructeur américain offrant une gamme de cartes à base de PXA de Intel... mais fonctionnant sous Windows CE.
Pour la suite, je me baserai sur deux kits de développements de la société dans laquelle je travaille : un kit à base de processeur AT91RM9200 (ARM920T 180 Mhz) de Atmel et un kit à base de processeur PXA255 (Xscale 400 Mhz) de Intel, mais les explications sont valables pour à peu près toutes les architectures de même style.
3.Présentation du matériel
Deux cartes serviront de support aux exemples fournis :
- le kit AT91 (présenté dans l’article de Pierre Ficheux dans le hors-série précédent) : composé d’un module intégrant le processeur AT91RM9200, 32 Mo de SDRAM, 8 Mo de Flash NOR et un PHY Ethernet 10/100 et d’une carte mère fournissant une connectique traditionnelle : 5 ports série sur connecteurs SUBD9, 1 port Ethernet sur un connecteur RJ45, un connecteur SDCard/MMC, et deux connecteurs USB périphérique et hôte.
- le kit PXA255 (photo suivante) : composé d’un module intégrant le processeur PXA255, 64 Mo de SDRAM, 32 Mo de Flash NOR et d’une carte mère fournissant : 1 contrôleur Ethernet 10/100 sur un connecteur RJ45, 4 ports série sur des connecteurs SUBD9, 1 port USB périphérique et une interface vers un écran TFT 16 bits avec une interface pour écran tactile résistif 4 fils et un codec audio AC97.

Le kit PXA255
4.Outils nécessaires
Afin de faire tourner Linux sur ces processeurs, il est nécessaire de disposer d’un certain nombre d’outils, tous disponibles sous licence GPL : les Binutils (assembleur & co) en version 2.16.x, les compilateurs (gcc & co) en version 3.4.x, une librairie C (glibc) en version 2.3.x, tous disponibles sur internet. L’idéal est d’utiliser Crosstool (http://www.kegel.com/crosstool/) pour créer la suite d’outils adaptés pour le processeur que l’on souhaite utiliser. L’utilisation de Crosstool a été couverte par un article de Pierre Ficheux dans un numéro précédent ( http://pficheux.free.fr/articles/lmf/cross_compilation/cross_compilation_images.pdf ). Il n’est pas nécessaire de revenir sur le sujet.
Une dernière précision : les processeurs ARM n’ont, en général, pas de logique câblée des flottants. Ainsi les calculs flottants passent par une émulation.
Historiquement cette émulation a été confiée au noyau Linux qui installait un gestionnaire d’instruction inconnue et " attrapait " donc les instructions flottantes que le processeur ne savait exécuter, les émulait et renvoyait le résultat comme l’aurait fait un vrai coprocesseur. Cette méthode présente l’avantage de ne pas avoir à se soucier des flottants dans ses applications et de se reposer sur le noyau qui récupérera la situation dans tous les cas. Elle présente aussi l’inconvénient d’être lente du fait du passage en exception.
Plus récemment, Nicolas Pitre a implémenté une gestion des calculs flottants suivant la norme IEEE 754 directement dans la librairie C. Cela permet d’avoir une exécution entièrement en entiers et de ne plus générer d’exception et donc d’obtenir une exécution plus performante (x7 à x9 selon les benchs utilisés) : http://www.spinics.net/lists/arm/msg07268.html
Crosstool permet par défaut de générer un toolchain utilisant la librairie glibc. Si vous préférez utiliser la librairie uclibc (plus légère), il faudra se tourner vers les outils adéquats : http://buildroot.uclibc.org/
Enfin, il est nécessaire d’avoir installé et configuré :
- Un serveur TFTP ;
- Un terminal série (minicom, kermit & co) ;
- Les outils permettant de transmettre des données sur le port série (http://www.ohse.de/uwe/software/lrzsz.html).
5.Adaptation du Bootloader
L’objectif de cette étape est d’adapter un bootloader à la carte et d’en faire une version fiable qui sera installée dans le premier secteur de la flash, lequel sera ensuite protégé et, théoriquement, jamais effacé afin de ne pas risquer de perdre ce fameux premier secteur sans lequel le processeur ne peut démarrer.
Tous les processeurs ne sont pas égaux dans ce domaine :
- Certains disposent d’une bootrom interne qui contient un firmware impossible à effacer. Ce firmware permet d’avoir une possibilité d’accès au processeur même lorsque le premier secteur de la flash externe a été effacé. C’est par exemple le cas de l’AT91RM9200 d’Atmel ;
- D’autres ne savent démarrer que de la flash externe. Dans ce cas, il est nécessaire de recourir à des outils adaptés pour prendre le contrôle du processeur et programmer le premier secteur de la flash. Un tel outil est un adaptateur JTAG sur le port parallèle, relativement simple et peu coûteux à fabriquer (cf. http://jtag-arm9.sourceforge.net/hardware.html). Le logiciel JTAG, disponible à http://openwince.sf.net/jtag/, permet de piloter bon nombre d’adaptateurs JTAG et reconnaît la plupart des processeurs et flashs actuellement utilisés.
5.1 Pourquoi un bootloader ?
C’est la première étape, qui peut être considérée comme optionnelle dans certains cas, mais apparaît indispensable pour faciliter l’étape suivante du développement et surtout créer un produit stable qui peut être facilement mis à jour sans prendre de risque.
De nombreux bootloaders sont disponibles sur internet et chez certains fournisseurs de produits commerciaux, alors pourquoi avoir choisi u-boot ? Parmi tous les bootloader testés, deux sortent du lot : RedBoot du projet eCos et u-boot, " The Universal Bootloader ". Ce dernier a été préféré pour sa simplicité et son organisation modulaire facilement compréhensible.
5.2 Tour du propriétaire
Après avoir téléchargé et extrait les sources du bootloader, prenons le temps d’en faire un tour rapide afin de mieux comprendre l’étendue du travail à faire :
- board : contient un répertoire par carte électronique supportée, chacun de ces répertoires comprenant les fichiers propres à sa carte, soit au minimum :
- config.mk (contenant l’adresse de base de chargement en RAM du bootloader) ;
- nom_carte.c (C contenant les initialisations " haut niveau " spécifiques à la carte) ;
- memsetup.S ou lowlevel_init.S (assembleur contenant les initialisations " bas niveau " spécifiques à la carte) ;
- Makefile ;
- D’autres fichiers source peuvent venir compléter cette base, selon les spécificités de la carte que l’on souhaite supporter dans le bootloader, par exemple le support de la flash dans flash.c.
- cpu : contient un répertoire par type de processeur supporté (ARM, Xscale, PowerPC, i386, MIPS, Microblaze, Coldfire, Nios). Dans notre cas, nous nous orienterons vers le répertoire arm720t qui correspond au cœur ARM autour duquel est conçu le processeur cible. Ce répertoire contient les fichiers propres au processeur, soit au minimum :
- config.mk (contenant les flags de compilation et autres optimisations spécifiques au processeur),
- cpu.c (fichier c contenant les fonctions " haut niveau " propres au processeur : cpu_init, cleanup_before_linux, do_reset, gestion du coprocesseur et du cache),
- serial*.c (fichier c offrant l’accès à au moins un port série du processeur : serial_setbrg, serial_init, serial_putc, serial_tstc, serial_getc, serial_puts),
- start.S (fichier assembleur contenant les fonctions " bas niveau " propre au processeur et qui se retrouvera en adresse 0, c’est-à-dire qui sera exécuté à la mise sous tension de l’équipement : démarrage, appel des fonctions critiques d’initialisation du processeur (Cache, MMU, PLL & co en général), appel des fonctions propres à la carte initialisant notamment le contrôleur mémoire; relocation du bootloader en SDRAM, initialisation de la pile, exécution du code générique du bootloader).
- common : contient les fichiers génériques du bootloader, à savoir les commandes exécutables ou le support générique de certaines fonctions ou périphériques.
- drivers : contient les drivers de périphériques plus ou moins génériques (contrôleurs Ethernet, USB...)
- lib_arch : un répertoire par architecture, comprenant des fonctions génériques (" main " de l’architecture, fonctions communes à tous les processeurs de l’architecture...)
- lib_generic : contient les fonctions génériques à toutes les architectures (bzlib, zlib, crc32, printf...)
- net : contient les fonctions réseau disponibles dans u-boot : bootp, fonctions Ethernet génériques, NFS, RARP, TFTP.
- tools : contient divers outils (création de logos, génération d’adresse MAC, génération d’images binaires au format uimage, support gdb)
- arch_config.mk : un fichier de configuration par architecture, contenant les flags propres à l’architecture.
5.3 Personnalisation du bootloader
La personnalisation du bootloader est relativement simple lorsque l’architecture sur laquelle on souhaite l’utiliser est déjà supportée. Il suffit de copier le répertoire correspondant à la carte existante dans le répertoire
board, de modifier les fichiers qu’il contient et de créer le fichier de configuration adapté à notre architecture.
Par exemple, pour la carte CPUPXA255, nous avons un répertoire board/cpupxa255 qui contient :
- config.mk : dans lequel nous avons adapté l’adresse de base à laquelle sera linké le bootloader (qui correspond à une adresse en SDRAM vers laquelle le bootloader sera recopié au boot) ;
- cpupxa255.c : dans lequel nous précisons le numéro de notre architecture (numéro devant concorder avec le numéro de l’architecture pour laquelle sera compilé notre noyau : cf. http://www.arm.linux.org.uk/developer/machines/ pour la liste des architectures ARM existantes.
- lowlevel_init.S : qui contient des routines bas niveau d’initialisation de notre architecture, comme par exemple l’initialisation de la SDRAM, des PLL et des GPIO, ces fonctions étant appelées dès la mise sous tension. Ce fichier et le fichier start.S contenu dans le répertoire cpu/pxa/ sont les deux fichiers qui justifient de comprendre un minimum l’assembleur du processeur afin de pouvoir les modifier.
Ensuite, nous avons créé un fichier
include/configs/cpupxa255.h qui contient un certain nombre de paramètres définissant notre architecture et les fonctions que nous souhaitons embarquer dans le bootloader : ce fichier contient un grand nombre de
#define. Voici par exemple la configuration du contrôleur réseau :
|
|
#define CONFIG_DRIVER_DM9000 1 #define CONFIG_DM9000_BASE 0x0C000000 #define DM9000_IO CONFIG_DM9000_BASE #define DM9000_DATA (CONFIG_DM9000_BASE+4) #define CONFIG_DM9000_USE_32BIT |
Nous utilisons un DM9000 (u-boot intégrera donc son driver). Son adresse de base est 0x0C000000, le registre de commande est à l’adresse de base, le registre de données est 4 octets plus loin et le composant est interface sur 32 bits.
Nous définissons aussi dans ce fichier les commandes que u-boot va embarquer :
|
|
#define CONFIG_COMMANDS (CONFIG_CMD_DFL | CFG_CMD_PING | CFG_CMD_MMC | CFG_CMD_JFFS2 | CFG_CMD_FAT) |
En plus de toutes les commandes par défaut, nous souhaitons intégrer la commande ping, les commandes de gestion des cartes MMC et les commandes d’accès aux systèmes de fichiers JFFS2 et FAT.
Il est très important de prendre le temps de lire les documentations disponibles dans le dossier doc de u-boot, de lire les fichiers de configuration d’autres architectures et enfin de lire le code des drivers (dossier drivers) et fonctions (dossier common) que l’on souhaite utiliser afin de bien saisir la portée des définitions du fichier de configuration.
Une fois l’architecture intégrée à u-boot, il est nécessaire de modifier le fichier Makefile situé à la base de l’arborescence d’u-boot afin d’y intégrer notre architecture.
5.4 Compilation
Les commandes de construction d’u-boot sont assez simples :
|
|
$ make clobber $ make cpupxa255_config $ make |
On obtient un fichier u-boot.bin qui peut être programmé sur la carte.
5.5 Installation
Dans le cas où le processeur dispose d’une bootrom interne, il est nécessaire de suivre les instructions du fondeur pour communiquer avec cette bootrom, ce qui passe en général par l’installation en RAM d’un petit bout de code qui va initialiser la SDRAM et le bus et permettre un dialogue au travers du port série afin de recevoir des données et de les programmer en flash. Le passage en mode bootrom se fait en général au moyen d’un jumper qui vient positionner une GPIO dans un état qui fait démarrer le processeur sur sa ROM interne.
Dans le cas où il est nécessaire d’utiliser un adaptateur JTAG, il convient de s’assurer de la compatibilité des niveaux de tension de l’adaptateur avec ceux du processeur et que les logiciels que l’on souhaite utiliser sont bien compatibles avec l’architecture matérielle de la carte.
La session suivante est un exemple de programmation du module CPUPXA255 avec un adaptateur JTAG sur le port parallèle issu du schéma du Byteblaster d’Altera :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
|
$ jtag JTAG Tools 0.6 Copyright (C) 2002, 2003 ETC s.r.o. JTAG Tools is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. There is absolutely no warranty for JTAG Tools. Warning: JTAG Tools may damage your hardware! Type «quit» to exit! Type «help» for help. jtag> cable parallel 0x378 ByteBlaster Initializing Altera ByteBlaster/ByteBlaster II/ByteBlasterMV Parallel Port Download Cable on parallel port at 0x378 jtag> detect IR length: 5 Chain length: 1 Device Id: 01101001001001100100000000010011 Manufacturer: Intel Part: PXA250 Stepping: PXA255A0 Filename: /usr/local/share/jtag/intel/pxa250/pxa250c0 jtag> initbus pxa2x0 jtag> detectflash 0 Query identification string: Primary Algorithm Command Set and Control Interface ID Code: 0x0001 (Intel/Sharp Extended Command Set) Alternate Algorithm Command Set and Control Interface ID Code: 0x0000 (null) Query system interface information: Vcc Logic Supply Minimum Write/Erase or Write voltage: 2700 mV Vcc Logic Supply Maximum Write/Erase or Write voltage: 3600 mV Vpp [Programming] Supply Minimum Write/Erase voltage: 0 mV Vpp [Programming] Supply Maximum Write/Erase voltage: 0 mV Typical timeout per single byte/word program: 64 us Typical timeout for maximum-size multi-byte program: 128 us Typical timeout per individual block erase: 1024 ms Typical timeout for full chip erase: 0 ms Maximum timeout for byte/word program: 256 us Maximum timeout for multi-byte program: 1024 us Maximum timeout per individual block erase: 4096 ms Maximum timeout for chip erase: 0 ms Device geometry definition: Device Size: 16777216 B (16384 KiB, 16 MiB) Flash Device Interface Code description: 0x0002 (x8/x16) Maximum number of bytes in multi-byte program: 32 Number of Erase Block Regions within device: 1 Erase Block Region Information: Region 0: Erase Block Size: 131072 B (128 KiB) Number of Erase Blocks: 128 jtag> flashmem 0 images/u-boot.bin Manufacturer: Intel Chip: 28F128J3A program: block 0 unlocked erasing block 0: 0 addr: 0x0001F7AC (done) verify: addr: 0x0001F7A8 Done. jtag> |
5.6 Utilisation
u-boot a été conçu dans l’objectif d’apporter un maximum de souplesse dans la phase de démarrage d’une carte : il offre donc toutes les fonctions permettant de tester le bas niveau de la carte, en plus des fonctions plus classiques de chargement de programmes par le port série ou le réseau et de gestion de périphériques de stockage.
Voici par exemple le résultat de la commande
help sur une architecture ARM920t :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
? - alias for ‘help’ base - print or set address offset boot - boot default, i.e., run ‘bootcmd’ bootd - boot default, i.e., run ‘bootcmd’ bootm - boot application image from memory bootp - boot image via network using BootP/TFTP protocol cmp - memory compare coninfo - print console devices and information cp - memory copy crc32 - checksum calculation date - get/set/reset date & time dcache - enable or disable data cache echo - echo args to console eeprom - EEPROM sub-system erase - erase FLASH memory fatinfo - print information about filesystem fatload - load binary file from a dos filesystem fatls - list files in a directory (default /) flinfo - print FLASH memory information fsinfo - print information about filesystems fsload - load binary file from a filesystem image go - start application at address ‘addr’ help - print online help icache - enable or disable instruction cache icrc32 - checksum calculation iloop - infinite loop on address range imd - i2c memory display imls - list all images found in flash imm - i2c memory modify (auto-incrementing) imw - memory write (fill) inm - memory modify (constant address) iprobe - probe to discover valid I2C chip addresses itest - return true/false on integer compare loadb - load binary file over serial line (kermit mode) loop - infinite loop on address range ls - list files in a directory (default /) md - memory display mm - memory modify (auto-incrementing) mtest - simple RAM test mw - memory write (fill) nfs - boot image via network using NFS protocol nm - memory modify (constant address) printenv- print environment variables protect - enable or disable FLASH write protection rarpboot- boot image via network using RARP/TFTP protocol reset - Perform RESET of the CPU run - run commands in an environment variable saveenv - save environment variables to persistent storage setenv - set environment variables sntp - synchronize RTC via network tftpboot- boot image via network using TFTP protocol usb - USB sub-system usbboot - boot from USB device version - print monitor version |
Nous avons donc à notre disposition :
- Des commandes de gestion mémoire : cp, cmp, crc32, md, mm, mtest, mw, nm ;
- Des commandes de gestion d’une eeprom externe : eeprom write, read ;
- Des commandes de gestion de date : sntp, date ;
- Des commandes de gestion du bus i2c : iprobe, imd, imm, imw, inm ;
- Des commandes de gestion de mémoire flash : flinfo, imls, erase, protect ;
- Des commandes d’accès au système de fichiers JFFS2 : fsload, fsinfo, ls ;
- Des commandes de gestion de l’USB : usb reset, stop, tree, info, scan, part, read ;
- Des commandes d’accès au système de fichiers FAT : fatinfo, fatload, fatls ;
- D’une commande de chargement par le port série : loadb ;
- D’une commande d’exécution d’une application : go ;
- De commandes de gestion de l’environnement : setenv, printenv, saveenv ;
- De commandes réseau : tftpboot, tftp.
Pour en savoir plus sur une commande, et si CFG_
LONGHELP est défini dans le fichier de configuration, il est nécessaire de taper
help commande.
Par exemple pour l’USB :
|
|
usb reset - reset (rescan) USB controller usb stop [f] - stop USB [f]=force stop usb tree - show USB device tree usb info [dev] - show available USB devices usb scan - (re-)scan USB bus for storage devices usb device [dev] - show or set current USB storage device usb part [dev] - print partition table of one or all USB storage devices usb read addr blk# cnt - read `cnt’ blocks starting at block `blk#’ to memory address `addr’ usbboot loadAddr dev:part |
Les paramètres d’environnement importants à connaître sont :
- setenv ethaddr aa:bb:cc:dd:ee:ff
- setenv serverip ipduserveurtftp
- setenv ipaddr ipdelacarte
5.7 u-boot : environnement de test
u-boot permet d’exécuter des applications ce qui est très pratique pour tester des fonctionnalités avant d’avoir monté le système d’exploitation ou en travaillant comme sur un microcontrôleur, sans se soucier de problèmes de MMU & co.
Voici l’exemple du classique
helloworld qui est disponible dans le répertoire
examples de u-boot :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
/* * (C) Copyright 2000 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. */ #include <common.h> #include <exports.h> int hello_world (int argc, char *argv[]) { int i; /* Print the ABI version */ app_startup(argv); printf («Example expects ABI version %d\n», XF_VERSION); printf («Actual U-Boot ABI version %d\n», (int)get_version()); printf («Hello World\n»); printf («argc = %d\n», argc); for (i=0; i<=argc; ++i) { printf («argv[%d] = \»%s\»\n», i, argv[i] ? argv[i] : «<NULL>»); } printf («Hit any key to exit ... «); while (!tstc()) ; /* consume input */ (void) getc(); printf («\n\n»); return (0); } |
Une fois compilé, nous copions examples/hello_world.bin dans le répertoire du serveur TFTP.
Puis sur la carte :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
U-Boot 1.1.4 (Feb 24 2006 - 12:54:27) U-Boot code: A1000000 -> A101F8E4 BSS: -> A105435C RAM Configuration: Bank #0: a0000000 64 MB Bank #1: a4000000 0 kB Bank #2: a8000000 0 kB Bank #3: ac000000 0 kB Flash: 32 MB In: serial Out: serial Err: serial Hit any key to stop autoboot: 0 CPUPXA255> tftp a2000000 hello_world.bin dm9000 i/o: 0xc000000, id: 0x90000a46 MAC: 00:d0:ca:f1:3c:d2 operating at 100M full duplex mode TFTP from server 192.168.1.5; our IP address is 192.168.1.99 Filename ‘hello_world.bin’. Load address: 0xa2000000 Loading: # done Bytes transferred = 499 (1f3 hex) CPUPXA255> go a2000000 ## Starting application at 0xA2000000 ... Example expects ABI version 2 Actual U-Boot ABI version 2 Hello World argc = 1 argv[0] = “a2000000” argv[1] = “<NULL>” Hit any key to exit ... ## Application terminated, rc = 0x0 CPUPXA255> |
Il devient ainsi très facile de développer des petites applications de test ou de démonstration. L’accès au matériel est direct et se fait simplement en utilisant les adresses physiques des registres.
6.Adaptation du Noyau
Nous avons à présent une carte sur laquelle un bootloader est installé et a permis de valider la stabilité du matériel. Il est grand temps d’installer un noyau Linux dessus afin d’exploiter tout le potentiel de l’architecture.
6.1 Sources du noyau
Depuis la génération 2.6 du noyau, un effort particulier est fait pour que les patches ARM intègrent au plus vite le noyau officiel. Ainsi, les patches soumis au maintainer de l’architecture ARM, Russell King, sont disponibles sur l’interface web de gestion du suivi des patches :
http://www.arm.linux.org.uk/developer/patches/
Un patch suit le parcours Incoming => Pending => Applied ou Rejected.
Avant d’en arriver là, les personnes s’occupant d’une architecture réalisent souvent des patches qui supportent plus complètement leur matériel mais ne sont pas encore suffisamment finalisés pour intégrer le noyau officiel.
Par exemple :
6.2 Ajout de notre architecture
Une fois dans l’arborescence du noyau Linux, tout ce qui concerne l’ARM se situe dans
arch/arm, les drivers restant dans les répertoires habituels :
drivers & sound.
|
|
arch/arm $ ls boot mach-clps711x mach-iop3xx mach-realview nwfpe common mach-clps7500 mach-ixp2000 mach-rpc oprofile configs mach-ebsa110 mach-ixp4xx mach-s3c2410 plat-omap Kconfig mach-epxa10db mach-l7200 mach-sa1100 tools Kconfig.debug mach-footbridge mach-lh7a40x mach-shark vfp kernel mach-h720x mach-omap1 mach-versatile lib mach-imx mach-omap2 Makefile mach-aaec2000 mach-integrator mach-pxa mm |
- boot : comprend les routines de décompression du noyau, c’est ce qui sera appelé par le bootloader lors du chargement du noyau ;
- configs : contient les fichiers de configuration par défaut des architectures supportées. Ce sont les fichiers qui une fois copiés à la racine du noyau et renommés en .config permettent de générer un noyau adapté à ces architectures. Ils servent notamment au projet ARM Linux Kernel Autobuild : http://armlinux.simtec.co.uk/kautobuild/ .
- kernel : contient les fichiers clefs du noyau propres aux architectures ARM ;
- lib : la librairie pour ARM ;
- mach-xxx : les fichiers de support spécifique de l’architecture xxx ;
- mm : tout ce qui est gestion de la mémoire, mmu & caches pour chaque version d’ARM ;
- nwfpe : une des deux émulations de flottants présentes dans le noyau ;
- vfp : Vector Floating Point – le dernier coprocesseur d’ARM.
Les deux répertoires dans lesquels nous pouvons être amenés à travailler, sous réserve que le processeur sur lequel nous travaillons est déjà supporté par le noyau, sont :
boot pour la phase de décompression du noyau et
mach-xxx pour le support spécifique de notre carte.
Prenons par exemple le cas de la carte CPUAT91 (noyau 2.6.14). Le répertoire contenant l’architecture est :
mach-at91rm9200. Nous créons un fichier
board-cpuat91.c qui a le contenu suivant (seules les sections intéressantes sont reproduites ici) :
|
|
static void __init cpuat91_init_irq(void) { /* Initialize AIC controller */ at91rm9200_init_irq(NULL); /* Set up the GPIO interrupts */ at91_gpio_irq_setup(PQFP_GPIO_BANKS); } |
La fonction d’initialisation des interruptions, dans laquelle nous précisions que nous sommes sur un processeur en boîtier PQFP (car le BGA a plus de GPIO et donc un bank supplémentaire pouvant générer des interruptions).
|
|
/* * Serial port configuration. * 0 .. 3 = USART0 .. USART3 * 4 = DBGU */ /* ttyS0, ..., t tyS4 */ #define CPUAT91_UART_MAP {4,0,1,2,3} /* ttyS0 */ #define CPUAT91_SERIAL_CONSOLE 0 |
Nous utilisons les 5 UARTS, et affectons le DBGU (UART orienté vers la fonction de debug) à ttyS0 et à la console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
static void __init cpuat91_map_io(void) { int serial[AT91C_NR_UART] = CPUAT91_UART_MAP; int i; at91rm9200_map_io(); /* Initialize clocks: 18.432 MHz crystal */ at91_clock_init(18432000); #ifdef CONFIG_SERIAL_AT91 at91_console_port = CPUAT91_SERIAL_CONSOLE; memcpy(at91_serial_map, serial, sizeof(serial)); /* Register UARTs */ for (i = 0; i < AT91C_NR_UART; i++) { if (at91_serial_map[i] >= 0) at91_register_uart(i, at91_serial_map[i]); } #endif } |
Nous utilisons un quartz à 18.432 MHz. Le noyau doit le savoir car cela impacte le réglage des PLL pour les périphériques du processeur.
|
|
static struct at91_eth_data __initdata cpuat91_eth_data = { .is_rmii = 1, }; |
Le contrôleur Ethernet est câblé en RMII (moitié moins de fils qu’un MII, mais une fréquence d’échange des données double).
|
|
static struct at91_usbh_data __initdata cpuat91_usbh_data = { .ports = 1, }; |
Le contrôleur OHCI a un seul port.
|
|
static struct at91_udc_data __initdata cpuat91_udc_data = { .vbus_pin = AT91_PIN_PC15, .pullup_pin = AT91_PIN_PC14, }; |
Nous utilisons la GPIO C14 pour activer ou désactiver la résistance permettant à un hôte de détecter notre carte lorsqu’elle est connectée. Et nous utilisons la GPIO C15 pour détecter la présence des 5 volts fournis par l’hôte et ainsi détecter que la carte vient d’être connectée.
|
|
static struct at91_mmc_data __initdata cpuat91_mmc_data = { .is_b = 0, .wire4 = 4, .det_pin = AT91_PIN_PC2, }; |
Le contrôleur SDCard/MMC est connecté en 4 fils, et utilise la GPIO C2 pour détecter l’insertion d’un carte.
|
|
static void __init cpuat91_board_init(void) { /* Ethernet */ at91_add_device_eth(&cpuat91_eth_data); /* USB Host */ at91_add_device_usbh(&cpuat91_usbh_data); /* USB Device */ at91_add_device_udc(&cpuat91_udc_data); /* MMC */ at91_add_device_mmc(&cpuat91_mmc_data); } |
La fonction d’initialisation de notre carte qui ajoute chaque device avec sa structure de configuration.
|
|
MACHINE_START(CPUAT91, «EUKREA CPUAT91 SBC») /* Maintainer: EUKREA Electromatique - Eric Benard */ .phys_ram = AT91_SDRAM_BASE, .phys_io = AT91C_BASE_SYS, .io_pg_offst = (AT91C_VA_BASE_SYS >> 18) & 0xfffc, .boot_params = AT91_SDRAM_BASE + 0x100, .timer = &at91rm9200_timer, .map_io = cpuat91_map_io, .init_irq = cpuat91_init_irq, .init_machine = cpuat91_board_init, MACHINE_END |
Et enfin la définition de notre machine :
- CPUAT91 est le numéro sous lequel notre plate-forme est enregistrée et que le bootloader doit passer en paramètre ;
- phys_ram est l’adresse de base de la SDRAM ;
- boot_params est l’adresse à laquelle le bootloader va déposer la ligne de configuration du boot du noyau ;
- timer, map_io, init_irq et init_machine sont les fonctions qui remplissent chacune des fonctionnalités décrites par leur nom.
Il nous reste à modifier les fichiers
Kconfig et
Makefile avant de pouvoir sélectionner notre architecture et compiler le noyau.
|
|
Kconfig : config MACH_CPUAT91 bool «Eukrea CPUAT91» depends on ARCH_AT91RM9200 help Select this if you are using Eukrea’s AT91RM9200 |
|
|
Makefile : obj-$(CONFIG_MACH_CPUAT91) += board-cpuat91.o led-$(CONFIG_MACH_CPUAT91) += leds.o |
Profitons du fait que nous parlons de l’inclusion du fichier leds.o dans le noyau pour décrire cette option : elle permet d’affecter deux GPIO, idéalement connectées à deux LED (une rouge et une verte si possible ;-) qui permettront de visualiser simplement : que le noyau est en vie (option LEDS_TIMER) et la charge CPU (option LEDS_CPU). Une fois ces options validées et les bonnes GPIO affectées dans le fichier leds.c, la LED verte clignotera environ toutes les secondes et la LED rouge sera d’autant plus allumée que le processeur est sollicité. La fonction peut à première vue paraître gadget, mais en fait elle permet souvent de voir immédiatement si une bêtise a été faite quelque part ou si un bout de code doit être optimisé car la LED est rouge de colère.
Dans le cas d’un processeur PXA, le fichier d’initialisation de l’architecture contient :
- Les routines de détection de la connexion à un hôte USB ;
- Les adresses de base et l’affectation de l’IRQ du contrôleur Ethernet ;
- Les routines de gestion du backlight (on/off/niveau) et de l’alimentation de l’écran ;
- La configuration du framebuffer (nombre de couleurs, marges, fréquence, polarité des signaux, timings, etc.) ;
- Les routines d’initialisation du contrôleur SD/MMC ;
- La routine principale qui initialise les GPIO et affecte les fonctions à chaque GPIO.
Ce dernier point est important car sur des processeurs complexes tels que les PXA, les IXP, les AT91, les EP93xx, les MX ou les OMAP, les fonctionnalités sont très souvent multiplexées sur chaque GPIO. Ainsi, on peut parfois arriver à une impossibilité car la fonctionnalité A affectée à la GPIO n bloque la fonctionnalité C de cette même GPIO. Ce multiplexage peut parfois rendre la conception d’une carte électronique plus complexe en raison de la multitude de choix possibles (une même fonction est par exemple présente sur 7 GPIO différentes sur un PXA270 !).
6.3 Configuration et compilation
|
|
$ make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig |
On pourra éditer le fichier Makefile à la racine du noyau pour forcer ARCH et CROSS_COMPLE aux bonnes valeurs et pouvoir simplement taper make menuconfig.

Sélection de l’architecture (AT91RM9200 dans notre cas) et de l’implémentation (CPUAT91 dans notre cas)

Activation de l’option des LED Timer et CPU
Un point clef de la configuration du noyau est la partie MTD : Memory Technology Devices. Le menu est situé sous Device Drivers > Memory Technology Devices (MTD). Ce support est nécessaire pour pouvoir utiliser la flash du système comme périphérique de stockage.
Les options importantes sont :
[*] MTD partitioning support
[*] Command line partition table parsing
<*> Direct char device access to MTD devices
<*> Caching block device access to MTD devices
qui permettent respectivement :
- D’autoriser le partitionnement de la flash ;
- De prendre la configuration du partitionnement passée dans la ligne de configuration du noyau ;
- D’offrir un device d’accès à la flash en mode char (pour envoyer des commandes : effacement, protection, etc.)
- D’offrir un device d’accès à la flash en mode block (pour recevoir un système de fichiers qui peut être géré comme un disque dur ou une clef USB).
Le menu RAM/ROM/Flash chip drivers permet de choisir le type de mémoires que MTD va supporter. Nous sélectionnons les options suivantes :
<*> Detect flash chips by Common Flash Interface (CFI) probe
[*] Flash chip driver advanced configuration options
[*] Specific CFI Flash geometry selection
[*] Support 16-bit buswidth
[*] Support 1-chip flash interleave
<*> Support for Intel/Sharp flash chips
qui permettent respectivement :
- D’autoriser l’autodétection des flashs par la Common Flash Interface (standard que les fabricants de flashs respectent... plus ou moins ;-) ;
- De supporter une flash sur 16 bits ;
- De supporter les flashs Intel/Sharp.
Enfin, le menu Mapping drivers for chip access permet de définir le mapping mémoire de notre flash :
<*> CFI Flash device in physical memory map
(0x10000000) Physical start address of flash mapping
(0x1000000) Physical length of flash mapping
(2) Bank width in octets
Nous informons la couche MTD que :
- Notre flash est mappée physiquement en 0x10000000 ;
- Qu’elle a une taille de 0x1000000 ;
- Qu’elle est interfacée sur une largeur de données de 16 bits.
Une fois notre noyau configuré, il est temps de le compiler par la commande :
|
|
$ make uImage .../... Image Name: Linux-2.6.14-csb Created: Sun Mar 12 18:10:37 2006 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1260196 Bytes = 1230.66 kB = 1.20 MB Load Address: 0x20008000 Entry Point: 0x20008000 Image arch/arm/boot/uImage is ready |
Le bon déroulement de cette compilation demande que le binaire
mkimage soit disponible dans le chemin courant (ce binaire est compilé lors de la génération de u-boot, et est disponible dans le répertoire
tools de u-boot).
mkimage permet d’encapsuler l’image noyau dans un fichier contenant les informations nécessaires à u-boot pour charger et exécuter le noyau.
|
|
$ mkimage Usage: mkimage -l image -l ==> list image header information mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image -A ==> set architecture to ‘arch’ -O ==> set operating system to ‘os’ -T ==> set image type to ‘type’ -C ==> set compression type ‘comp’ -a ==> set load address to ‘addr’ (hex) -e ==> set entry point to ‘ep’ (hex) -n ==> set image name to ‘name’ -d ==> use image data from ‘datafile’ -x ==> set XIP (execute in place) |
6.4 Installation et démarrage
L’installation consiste simplement à charger l’image
uImage sur la carte (en SDRAM dans un premier temps) puis à la copier en flash, à l’adresse souhaitée.
Dans notre cas, avec 8 Mo de flash et des secteurs de 128 ko, la flash sera partitionnée de la manière suivante (par exemple) :
- Secteur 1 : u-boot - 128 ko ;
- Secteur 2 : environnement de u-boot - 128 ko ;
- Secteurs 3 à 14 uImage - 1 408 ko ;
- Secteurs 15 à 64 rootfs - 6 528 ko.
=>
tftp 20000000 uImage
|
|
TFTP from server 192.168.0.1; our IP address is 192.168.0.96 Filename ‘uImage’. Load address: 0x20000000 Loading: ########################################################################################################################################################################################## ############################################# done Bytes transferred = 1228092 (12bd3c hex) |
=>
protect off 10040000 1019FFFF
|
|
........... Un-Protected 11 sectors => erase 10040000 1019FFFF ........... done Erased 11 sectors |
=>
cp.b 20000000 10040000 $(filesize)
Il reste à définir les paramètres du noyau :
=> setenv bootargs root=/dev/mtdblock2 rootfstype=jffs2 console=ttyS0,115200 mtdparts=phys_mapped_flash:256k(u-boot),1408k(kernel),-(rootfs) mem=32M
Décodons un peu cette ligne :
- mem=32M : nous avons 32 Mo de SDRAM
- mtdparts=phys_mapped_flash:256k(u-boot), 1408k(kernel),-(rootfs) : nous définissons 3 partitions :
- 0 : u-boot sur 256 ko;
- 1 : kernel sur 1408 ko ;
- 2 : rootfs sur le reste de la flash.
- console=ttyS0, 115200 : nous utiliserons le premier port série comme console du noyau, à 115200 bps
- rootfstype=jffs2 : le système de fichier que nous allons utiliser est un jffs2
- root=/dev/mtdblock2 : la partition racine est /dev/mtdblock2 (issue du partitionnement que nous avons fait passer au noyau ci-dessus).
=>
setenv bootcmd bootm 10040000La variable d’environnement
bootcmd contient la succession d’opérations qui est exécutée à l’appel de la commande
boot.
=>
setenv bootdelay 1
La variable bootdelay contient le nombre de seconde qui s’écoulera au démarrage de
u-boot avant qu’il n’exécute automatiquement la commande bootcmd. Cette séquence de boot automatique est interruptible en envoyant un caractère sur la console série.
=>
saveenv
On sauvegarde nos modifications dans l’environnement de u-boot.
Au boot suivant nous obtenons bien le log voulu :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
U-Boot 1.1.3 (Jul 7 2005 - 03:37:25) U-Boot code: 21F00000 -> 21F1F748 BSS: -> 21F6220C RAM Configuration: Bank #0: 20000000 32 MB Flash: 8 MB In: serial Out: serial Err: serial Press SPACE to abort autoboot in 1 seconds ## Booting image at 10040000 ... Image Name: Linux-2.6.12-csb Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1228264 Bytes = 1.2 MB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK OK Starting kernel ... Uncompressing Linux......................... ............................................ ........... done, booting the kernel. .../... Kernel command line: root=/dev/mtdblock2 rootfstype=jffs2 console=ttyS0,115200 mtdparts=phys_mapped_flash:256k(u-boot),1408k(kernel),-(rootfs) mem=32M .../... ttyS0 at MMIO 0xfefff200 (irq = 1) is a AT91_SERIAL ttyS1 at MMIO 0xfefc0000 (irq = 6) is a AT91_SERIAL ttyS2 at MMIO 0xfefc4000 (irq = 7) is a AT91_SERIAL ttyS3 at MMIO 0xfefc8000 (irq = 8) is a AT91_SERIAL ttyS4 at MMIO 0xfefcc000 (irq = 9) is a AT91_SERIAL .../... physmap flash device: 1000000 at 10000000 phys_mapped_flash: Found 1 x16 devices at 0x0 in 16-bit bank Intel/Sharp Extended Query Table at 0x0031 Using buffer write method cfi_cmdset_0001: Erase suspend on write enabled 3 cmdlinepart partitions found on MTD device phys_mapped_flash Creating 3 MTD partitions on “phys_mapped_flash”: 0x00000000-0x00040000 : “u-boot” 0x00040000-0x001a0000 : “kernel” 0x001a0000-0x00800000 : “rootfs” .../... VFS: Mounted root (jffs2 filesystem). Freeing init memory: 92K Warning: unable to open an initial console. Kernel panic - not syncing: No init found. Try passing init= option to kernel. |
Seuls les points importants du log ont été conservés :
- La ligne de commande passée au noyau par notre bootloader ;
- Les 5 ports série que nous avons configurés dans le fichier définissant notre architecture ;
- Le mapping de la flash et son partitionnement ;
- Le root qui a été monté au format jffs2 ;
Et le kernel panic qui nous informe qu’aucun init n’a été trouvé ce qui n’est guère étonnant vu que nous n’avons pas installé de système de fichiers dans la flash.
7.Génération et installation de la mini-distribution
7.1 Génération
Rien de bien nouveau sur le sujet qui a été abordé par la passé par Pierre Ficheux (http://pficheux.free.fr/articles/lmf/embedded/), un seul binaire est nécessaire : Busybox, accompagné de quelques fichiers de configuration et d’une arborescence unixienne classique. On peut arriver sans trop d’effort à une configuration contenant toute la
glibc, Busybox configuré avec la plupart des applications qu’il proposait, le tout pour 4 Mo. Il suffit alors de convertir cette arborescence en une image jffs2 pouvant être installée sur la carte. Il est nécessaire de récupérer l’outil
mkfs.jffs2 disponible sur le site de MTD :
http://www.linux-mtd.infradead.org/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
$ du rootfs_cpuat91 3276 rootfs_cpuat91/lib 20 rootfs_cpuat91/etc/init.d 8 rootfs_cpuat91/etc/network 120 rootfs_cpuat91/etc 4 rootfs_cpuat91/mnt/usb 4 rootfs_cpuat91/mnt/nfs 4 rootfs_cpuat91/mnt/mmc 16 rootfs_cpuat91/mnt 16 rootfs_cpuat91/root 4 rootfs_cpuat91/sys 8 rootfs_cpuat91/usr/share/terminfo/a 8 rootfs_cpuat91/usr/share/terminfo/r 16 rootfs_cpuat91/usr/share/terminfo/s 8 rootfs_cpuat91/usr/share/terminfo/d 16 rootfs_cpuat91/usr/share/terminfo/v 12 rootfs_cpuat91/usr/share/terminfo/x 8 rootfs_cpuat91/usr/share/terminfo/l 80 rootfs_cpuat91/usr/share/terminfo 8 rootfs_cpuat91/usr/share/udhcpc 92 rootfs_cpuat91/usr/share 4 rootfs_cpuat91/usr/bin 4 rootfs_cpuat91/usr/sbin 104 rootfs_cpuat91/usr 760 rootfs_cpuat91/bin 4 rootfs_cpuat91/sbin 4 rootfs_cpuat91/tmp 4 rootfs_cpuat91/proc 4 rootfs_cpuat91/dev 4320 rootfs_cpuat91 |
Une fois compilé (pour une exécution sur l’hôte de développement), on peut générer l’image grâce à la commande suivante :
|
|
$ mkfs.jffs2 --little-endian --squash-uids -e 0x20000 -D confs/device_table.txt -d rootfs_cpuat91 -o rootfs_cpuat91.jffs2 $ ls -al rootfs_cpuat91.jffs2 -rw-r--r-- 1 ebenard users 2260368 mar 12 19:35 rootfs_cpuat91.jffs2 |
Il est à noter que nous utilisons un fichier
device_table.txt qui contient la définition de tous les devices à créer dans le
/dev de notre système de fichiers. Cela a l’énorme avantage de permettre de travailler en utilisateur normal et non en root.
Exemple d’entrées dans le fichier
device_table.txt :
|
|
#<name> <type> <mode> <uid> <gid> <major> <minor> <start> <inc> <count> /dev/mem c 640 0 0 1 1 0 0 - /dev/mtd c 640 0 0 90 0 0 2 3 /dev/mtdblock b 640 0 0 31 0 0 1 3 /dev/ttyS c 666 0 0 4 64 0 1 5 |
La syntaxe est assez simple : tout d’abord le chemin vers le device, puis son type (
c : char,
b : bloc,
d : directory – pour créer un point de montage par exemple), puis les droits, le
userid, le
groupid, puis le
major et le
minor et enfin le numéro de départ, le pas d’incrémentation et le nombre de devices à créer (pratique pour créer 5 ports série en une ligne par exemple). Les 4320 octets contenus dans le répertoire ont donc été convertis en une image jffs2 de 2207 octets. En effet, jffs2 est un système de fichiers compressé en plus d’être journalisé et de gérer le nivellement des secteurs de flashs (c’est-à-dire de prendre en compte le fait qu’un secteur de flash a une durée de vie limitée à, par exemple, 100 000 cycles effacement/écriture).
On se référera à l’article de Pierre Ficheux et Patrice Kadionik pour plus d’informations sur les flashs (http://pficheux.free.fr/articles/lmf/linux_everywhere/linux_everywhere_img_final.pdf).
7.2 Installation
L’installation consiste simplement à charger l’image
uImage sur la carte (en SDRAM dans un premier temps) puis à la copier en flash, à l’adresse souhaitée.
|
|
TFTP from server 192.168.0.1; our IP address is 192.168.0.96 Filename ‘rootfs_cpuat91.jffs2’. Load address: 0x20000000 Loading: ####################################################################################################################################################################################### ################################################################ ################################################################ ################################################################ ########################################################### done Bytes transferred = 2393788 (2486bc hex) |
=>
tftp 20000000 rootfs_cpuat91.jffs2
=>
protect off 101A0000 107FFFFF
|
|
................................................... Un-Protected 51 sectors |
=>
erase 101A0000 107FFFFF
|
|
................................................... done Erased 51 sectors |
=>
cp.b 20000000 101A0000 $(filesize)
Nous disposons dès lors d’un système de fichiers.
Au prochain boot, notre noyau sera heureux et pourra exécuter un init et nous présenter une console de login ou directement un shell selon la configuration souhaitée.
8.Conclusion
Après avoir un peu exploré l’assembleur des processeurs ARM (en tout cas suffisamment pour se débrouiller avec les fichiers assembleur que l’on trouve dans u-boot et pour pouvoir commencer à comprendre un morceau d’un binaire désassemblé), nous avons vu comment adapter u-boot et le noyau Linux pour les faire tourner sur une carte intégrant un cœur ARM.
J’espère que ces quelques explications vous donneront l’envie d’expérimenter par vous-même et de construire le produit de vos rêves autour d’un ARM embarquant Linux ;-)
LIENS
Histoire d’ARM :
Outils nécessaires :
Bootloader :
Linux :
Lexique :
- PLL : Phase-Locked Loop – boucle à verrouillage de phase – permet de générer des fréquences différentes à partir d’une fréquence source issue par exemple d’un quartz.
- GPIO : General Purpose Input Output – entrée sortie numérique.
- OHCI : Open Host Controller Interface – norme de contrôleurs hôtes USB.
- PQFP : Plastic Quad Flat Package – format de composant carré avec des broches sur les quatre côtés, destiné au montage en surface (sans perçage).
- BGA : Ball-Grid Array – format de composant sans aucune broche apparente mais avec des billes sur la face inférieure qui, une fois fondues, assureront le contact avec le circuit imprimé.
- JTAG : Interface qui suit la norme IEEE 1149.1 et qui permet de prendre le contrôle d’un composant afin soit de le programmer, soit de le tester en faisant bouger l’état de ses entrées/sorties.