Linux sur ARM
Signature : | Mis en ligne le : 19/10/2008
Catégorie(s) :
  • GNU/Linux Magazine HS
  • | Domaine :
    Commentez
    Article publié dans :
    Achetez
    Linux Magazine HS 25 :
    Version Papier
    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 :

    /img-articles/lmhs/25/art-4/fig-1.jpg

    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 :

    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 :

    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.

    /img-articles/lmhs/25/art-4/fig-2.jpg

    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.

    /img-articles/lmhs/25/art-4/fig-3.jpg

     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 :

    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 :

    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 :

    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 :

    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 :

    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 :

    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 :

    Une fois compilé, nous copions examples/hello_world.bin dans le répertoire du serveur TFTP. Puis sur la carte :

    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.

    • 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) :

    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).

    Nous utilisons les 5 UARTS, et affectons le DBGU (UART orienté vers la fonction de debug) à ttyS0 et à la console.

    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.

    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).

    Le contrôleur OHCI a un seul port.

    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.

    Le contrôleur SDCard/MMC est connecté en 4 fils, et utilise la GPIO C2 pour détecter l’insertion d’un carte.

    La fonction d’initialisation de notre carte qui ajoute chaque device avec sa structure de configuration.

    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.

    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

    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.

    /img-articles/lmhs/25/art-4/fig-4.jpg

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

    /img-articles/lmhs/25/art-4/fig-5.jpg

     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 : 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.

    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 => protect off 10040000 1019FFFF => 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 : 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/ 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 : 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 : 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 20000000 rootfs_cpuat91.jffs2 => protect off 101A0000 107FFFFF => erase 101A0000 107FFFFF => 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.
    Vous souhaitez commenter cet article ?
    Brèves Flux RSS
    Édito : GNU/Linux Magazine Hors-Série N°72
    Édito : Linux Pratique N°84
    Édito : MISC N°74
    Édito : GNU/Linux Magazine N°173
    Édito : MISC Hors-Série N°9
    Communication RSS Com. RSS Presse
    HACKITO ERGO SUM
    GNU/Linux Magazine, partenaire du SymfonyLive Paris
    Opensilicium, partenaire de RTS EMBEDDED
    Linux Pratique et Linux Essentiel, Partenaire de l’Open World Forum
    Gnu/Linux Magazine, Partenaire des JDEV 2013
    Rechercher un article dans notre base documentaire :
    En kiosque Flux RSS

    Le tout nouveau GNU/Linux Magazine HS est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Misc est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...

    Le tout nouveau Open Silicium est disponible dès maintenant chez votre marchand de journaux et sur notre site marchand.

    Découvrez le sommaire de ce numéro et un aperçu de ce magazine...

    Lire la suite...