Gulliver, le Linux User's Group (LUG) de Rennes
et ses environs, a
reçu
sous
forme de don un certain nombre de machines Explora 401 de type "NCD",
pour Network Computer Device, machines de petites capacités dont
l'unique fonctionnalité est d'embarquer un serveur X pour faire de
l'affichage déporté d'application tournant sur un serveur disposant,
lui, de grosses capacités.
Ces machines sont fournies avec un système
d'exploitation (ou OS,
Operating System) propriétaires, qui est téléchargé par le NCD chaque
fois qu'il doit démarrer. Elles n'embarquent donc pas de disque dur,
mais seulement 16Mo de RAM, plus une carte réseau, une carte graphique,
2 connecteurs PS/2 clavier et souris, un port série, un port PCMCIA et
enfin un processeur PowerPC 403GA.
Le but du LUG est de mettre en avant des logiciels
libres, il
n'existait malheureusement pas d'OS libre, tel que Linux, capable de
fonctionner avec, il se retrouvait avec des machines inutilisables.
J'ai donc entrepris de réaliser un OS à base du noyau Linux, capable
de
fonctionner sur cette machine.
Le nom "Houyhnhnms" vient des aventures de Gulliver,
le héros de
Jonathan Swift, et correspond à une des terres découvertes par
Gulliver. Il est particulièrement imprononçable, ce qui en fait un nom
parfait. ;)
Ce document décrit le portage du noyau Linux sur les
machines de type
Explora 401, et est réalisé dans le but d'aider quiconque souhaiterait
se lancer dans une aventure de ce type. Ce document ne se veut pas
parfait : si vous y voyez des erreurs,
n'hésitez pas à m'en faire part.
Note importante : Une grande partie du
travail a consisté à adapter ce qui a déjà été fait pour l'architecture
PowerPC, voire même à casser pour enlever des morceaux. Les
explications données ici concernent une partie du travail que je n'ai
pas réalisé, mais qu'il est utile ou intéressant de détailler. Je ne
prétends pas avoir écrit le code PPC du noyau !!
Il y a 2 niveaux de portage pour un noyau d'un système d'exploitation tel que Linux : le portage pour le processeur proprement dit, mais également pour ce qui tourne autour, l'architecture. Par chance, le noyau a été porté pour une machine du même type, l'Explora451 disposant d'un PPC40GCX. La tache était donc déjà plus facile.
Cependant, le processeur PPC403GA a une particularité importante qui le distingue de son frère le 403GCX : contrairement à la plupart des processeurs récents, il n'embarque pas de module de gestion mémoire (MMU, pour Memory Management Unit) permettant d'isoler les programmes les uns des autres, de leur fournir un espace d'adressage particulier, etc. Un "patch" intégré au noyau linux depuis la version 2.6, nommé uClinux, est justement fait pour ce type de processeur, mais n'a jamais été fabriqué pour fonctionner sur des machines PowerPC. Son adaptation est donc nécessaire. Les programmes utilisant ce patch ont également un format particulier, généré par un programme qui n'a pas été non-plus fait pour fonctionner avec les PowerPC. Là aussi, une adaptation est nécessaire.
Un noyau Linux est complètement inexploitable s'il est tout seul. Il est en général accompagné au moins d'une bibliothèque C apportant les fonctions de base à tout programme C, et d'un ensemble de programmes, mais également de tout ce qu'il faut pour développer de nouvelles applications pour cette machine. Voici donc ce qui compose exactement le projet :
Une chaine de compilation GCC : assembleur, préprocesseur, compilateur C.
Un noyau Linux à adapter. Mon choix s'est porté sur une version relativement récente au moment des développements : la version 2.6.9
Un système de fichier racine
Une bibliothèque C capable de rentrer dans une empreinte mémoire réduite. uClibc a été développé dans cette optique.
Le programme elf2flt pour générer des programmes au format "FLAT" fonctionnant pour uClinux
BusyBox, un programme permettant de prendre l'aspect d'un shell mais également de toutes les commandes shell Unix basiques (cp, ln, mv, uname, mkdir, ....) , selon la manière dont il est exécuté.
La chaine de compilation se décompose en:
Une suite d'outils permettant de gérer les formats binaires, et un assembleur, ces outils s'appellent les "binutils"
Un compilateur C, celui qui est inclus dans GCC
Une bibliothèque C, la libc.
Pour construire les binutils :
wget http://ftp.gnu.org/gnu/binutils/binutils-2.14.tar.gz
tar -xvzf binutils-2.14
cd binutils-2.14
./configure --prefix=/PATH/TO/TARGET/DIR/ --target=powerpc-elf-linux
make all
make install
Cela permet d'obtenir les outils installés dans /PATH/TO/TARGET/DIR/
Pour construire le compilateur C, il est nécessaire de référencer les
binutils fraichement compilés sur lesquels la compilation s'appuie.
export PATH=$PATH:/PATH/TO/TARGET/DIR/bin/Tout ceci permet effectivement de construire un compilateur C, mais le lien ne sera pas fait avec les bibliothèques C, et la construction échouera quelque part au milieu. Ce qui n'est pas grave, car le noyau n'a pas besoin des bibliothèques C. En effet, il doit contenir toutes les routines C dont il a besoin sans avoir à faire appel à des fichiers externes, notion définie par le noyau lui-même.
tar -xvzf gcc-3.3.2.tar.gz
cd gcc-3.3.2
./configure --with-gcc-version-trigger=/data/avila/TOOLCHAIN_PPC/gcc-3.3.2/gcc/version.c \
--prefix=/PATH/TO/TARGET/DIR/ \
--target=powerpc-elf-linux --enable-languages=c \
--disable-threads \
--disable-shared \
make all install
La compilation des programmes utilisateurs, plus loin, nécessitera elle de compiler une bibilothèque C, uClibc.
+ crypto La bibliothèque qui contient les routines de cryptographie
+ Documentation Surement la Documentation, mais le code se suffit à lui-même
+ drivers Tous les pilotes de périphériques, classés par catégories
+ video
+ usb
+ serial
+ char
+ ...
+ fs L'épine dorsale de la gestion des fichiers, le VFS (virtual filesystem), la gestion des formats des programmes
+ ext2 Le système de fichier EXT2
+ ext3 Le système de fichier journalisé EXT3, basé sur EXT2
+ romfs Un système de fichier statique, lecture seule.
+ jffs Le système de fichier le plus employé dans l'embarqué
+ ... Tous les sytèmes de fichiers exotiques, ou provenant de systèmes tiers.
+ include Tous les fichiers d'en-têtes
+ asm-m68k Les en-têtes spécifiques à l'architecture 68000, plus quelques inline en assembleur.
+ asm-m68knommu Les en-têtes spécifiques à l'architecture 68000 sans MMU, plus quelques inline en assembleur.
+ asm-ppc Les en-têtes spécifiques à l'architecture PowerPC, plus quelques inline en assembleur.
+ asm Un lien symbolique vers l'architecture sélectionnée en cours de compilation.
+ config Les fichiers de configuration,
+ linux Les en-têtes spécifiques au coeur du noyau, (pour /kernel, /lib, ...)
+ ... Les en-têtes de fichiers des sous-systèmes (sound, pcmcia,...)
+ init Les fichier de boot du noyau
+ ipc Les fichiers relatifs aux communications inter-processus.
+ kernel Les fichiers principaux de la gestion des processus.
+ lib Bibliothèque contenant des routines génériques, ainsi que la compression ZLIB.
+ mm La gestion mémoire (memory management) dans le noyau
+ net La gestion du réseau (network)
+ usr Une partie qui se retrouvera côté utilisateur (hors-noyau).
+ arch Toutes les spécificités de chaque architecture supportée.
+ m68k Architecture Motorola 68000
+ m68knommu Architecture Motorola 68000 ne disposant pas de MMU
+ ppc Architecture PowerPC
+ kernel Les procédures appelées du noyau et spécifiques à l'architecture PowerPC
+ mm Procédures de gestion mémoire spécifiques à l'architecture PowerPC
+ 8xx_io Gestion des I/O pour les processeurs PowerPC 8xx
+ 4xx_io Gestion des I/O pour les processeurs PowerPC 4xx
+ configs Des fichiers .config prédéfinis pour chaque sous-architecture
+ boot Code lié aux procédures de boot des machines PPC
+ amiga Spécifique aux machines Amiga
+ platforms Les routines spécifiques à chaque plate-forme. Quasiment 1 fichier par plate-forme supportée.
+ 4xx Spécificités des processeurs PowerPC 4xx, comme celui de l'Explora
+ oprofile
+ xmon Monitorer le PowerMac
+ syslib Des routines utiles pour le boot, les io, dma, ... (le nom du répertoire n'est pas très heureux)
+ 8260_io
+ ...
+ script Des scripts de compilation, configuration, etc.
+ security Ce qui concerne la sécurité
+ sound Le son (alsa, OSS)
Pour insérer une nouvelle architecture, il est nécessaire de ranger proprement ses affaires, pour ne pas "polluer" l'existant.
On insère donc naturellement:
Un répertoire dans arch, contenant tous les fichiers C et assembleur spécifiques : arch/nommu
Un répertoire d'include, contenant les headers, les macro du préprocesseur, etc : include/asm-nommu, ainsi que le lien symbolique "asm" pointant vers ce répertoire pour la compilation.
Pour faire simple et limiter le temps de développement, on repart de
ce
qui a été fait pour l'architecture PowerPC, en copiant arch/mmu vers
arch/nommu et include/ppc vers include/ppcnommu. Les liens sont
modifiés, ainsi que les références absolues comme par exemple celle
à "arch/ppc".
Encore une fois, Linux est particulièrement efficace en ce qui concerne les architectures multiples. La configuration est gérée par des fichiers "Kconfig" (rien à voir avec KDE), qui contiennent toutes les définitions d'options, leur type (n-choix, booléen, chaine), leurs contraintes (on ne compile pas un module de carte son si le son n'est pas activé), une aide contextuelle associée, et un rangement de ces options en catégories et sous-catégories.
Chaque architecture possède ainsi un fichier Kconfig, qui inclut à son tour les fichiers Kconfig standard du noyau à sa guise : sound/Kconfig, fs/Kconfig, drivers/video/Kconfig, etc.
Le fichier Kconfig global de l'architecture est utilisé pour présenter à l'utilisateur des choix à l'aide d'un script en mode console, ou bien graphique (le luxe !)
Ainsi :
make xconfig
lance le programme permettant de paramétrer son noyau avec une
application QT.
Une fois les choix effectués, ils sont enregistrés dans un fichier
.config et utilisés pour regénérer les headers.
Le fichier Kconfig a donc été adapté, pour ajouter les lignes qui sont
relatives à l'Explora401 et à cette architecture. Principalement :
config MMU
bool
default n
(...)
config KERNEL_START_BOOL
bool "Set custom kernel base address"
default y
depends on ADVANCED_OPTIONS
config KERNEL_START
hex "Virtual address of kernel base" if KERNEL_START_BOOL
default "0x00000000"
config KERNEL_PHYSICAL_OFFSET
hex "Address of kernel load (decompression point)"
default "0x81000000"
config MEMORY_START
hex "Start of physical memory (also sets PAGE_OFFSET)"
default "0x81000000"
(...)
config BOOT_LOAD
hex "Link/load address for booting" if BOOT_LOAD_BOOL
default "0x81800000" if EXPLORA401
default "0x00400000" if 40x || 8xx || 8260
default "0x01000000" if 44x
default "0x00800000"
config BLK_DEV_INITRD
bool "Initial Ram Disk"
depends on EXPLORA401
Il est également nécessaire de préciser à la compilation quel type d'architecture on souhaite compiler, et quel est le préfixe pour la chaine de compilation. Pour cela le fichier Makefile racine doit être adapté :
ARCH ?= ppcnommu
CROSS_COMPILE ?= powerpc-linux-
Chaque architecture PPC doit disposer de son entrée dans arch/ppcnommu/platforms/ afin d'y mettre ses spécificités.
Comme précisé plus-haut, le petit-frère de l'explora401, l'Explora451, dispose également d'un portage bien à lui, mais pour un noyau 2.4. Afin de réutiliser le travail réalisé pour cette machine, le fichier explora.c a été recopié vers arch/ppcnommu/platforms/4xx/explora401.c, ainsi que explora.h.
Le makefile associé au choix d'architecture doit également refléter ce
choix d'architecture. Encore une fois, le noyau est prévu pour cela,
puisque la définition de la variable qui reflète l'architecture est
définie également dans le Makefile. Ainsi, si tous les fichiers
contenus dans la variable "obj-y" sont compilés, la ligne suivante
obj-$(CONFIG_EXPLORA401) += explora401.o
permet de demander de compiler le fichier explora.c et le tour est joué.
Ce fichier doit néanmoins être adapté pour satisfaire
Les spécificités de l'explora 401 par rapport au 451
Les spécificités du noyau 2.6 par rapport au 2.4
Le démarrage du noyau est découpé en 2 étapes principales, le "gros" de celui-ci se retrouvant compressé pour prendre moins d'espace mémoire, ce qui est parfois nécessaire sur les architectures ne pouvant pas charger un gros noyau, ou quand celui-ci est téléchargé du réseau (comme ici). Il est alors nécessaire de garder non-compressée la partie du noyau qui décompressera le reste du noyau à l'endroit voulu. On appelle 1er étage la partie qui décompresse le gros du noyau, le 2nd étage. Pour résumer, un schéma :

Le but du premier étage est donc de décompresser à un point fixe le 2nd étage puis de se lancer dedans. Il initialise également une partie du matériel.
Un point important dans la machine Explora401 est que la RAM n'est
pas
accessible à l'adresse 0 mais à l'adresse 0x8100000. Cela a des
conséquences que nous allons détailler.
Deux paramètres sont définis pour permettre de modifier ces valeurs; il
s'agit de KERNEL_PHYSICAL_OFFSET qui donne le point de décompression du
2nd étage, et BOOT_LOAD qui donne le poitn de décompression du 1er
étage, qui est le point ou sera chargé l'image du noyau, sur lequel on
peut rarement intervenir, puisqu'elle dépend d'un chargeur plus
bas-niveau.
La toute première instruction éxécutée par le noyau se trouve dans le fichier "arch/ppcnommu/boot/simple/head.S"
.globl start
start:
bl start_
.long 0x60000000
.long 0x60000000
.long 0x00000020
.ascii "XncdPPC\0"
.long 0,0
.long return, 0
return: blr
Cela permet de mettre en place des octets de signature utilisés par
le firmware du NCD pour
reconnaitre qu'il s'agit bien d'une image à charger et éxécuter la
première instruction. En l'occurence, celle-ci est un saut à
l'étiquette "_start", qui contient le vrai code. Cette adresse contient
ensuite un saut à l'adresse "relocate", contenu dans le fichier
"arch/ppcnommu/boot/simple/relocate.S"
.comm .stack,4096*2,4Celle-ci est mise en place avec le code suivant :
90: mr r9,r1 /* Save old stack pointer (in case it matters) */On peut maintenant sauter en toute quiétude dans la 1ière fonction C àl'aide d'un appel de fonction classique:
lis r1,.stack@h
ori r1,r1,.stack@l
addi r1,r1,4096*2
subi r1,r1,256
li r2,0x000F /* Mask pointer to 16-byte boundary */
andc r1,r1,r2
bl load_kernelCelle-ci se trouve dans "arch/ppcnommu/boot/simple/misc.c"
KERNEL_PHYSICAL_OFFSETPour cela, le noyau embarque le code de décompression ZIP dans son premier étage, dans le répertoire "lib/zlib_deflate/". Un appel :
gunzip( CONFIG_KERNEL_PHYSICAL_OFFSET, 0x400000 ,zimage_start, &zimage_size);permet de décompresser le noyau à l'adresse voulue.
GETSYM(r9,CONFIG_KERNEL_PHYSICAL_OFFSET)Fin du premier étage, longue vie au second étage !
mtlr r9
blr
powerpc-linux-objdump -d vmlinuxpermet de s'en assurer :
vmlinux: format de fichier elf32-powerpcA ce moment-là dans le code PPC 4xx, diverses routines permettent de mettre en place la MMU. Le code est içi laissé en l'état, en faisant le nettoyage de ce qui concerne la MMU, la TLB, etc. Ainsi les routines de synchronisation, flush de la tlb, etc. sont gardées mais en désactivant le code de façon à fonctionner correctement pour le 403GA. Cependant, une partie se montre intéressante, dans "initial_mmu":
Déassemblage de la section .text:
81000000 <_start>:
81000000: 7c 7f 1b 78 mr r31,r3
81000004: 7c 9e 23 78 mr r30,r4
81000008: 7c bd 2b 78 mr r29,r5
8100000c: 7c dc 33 78 mr r28,r6
81000010: 7c fb 3b 78 mr r27,r7
/* Establish the exception vector baseLe registre SPRN_EVPR est rempli avec l'adress haute (16 bits de poids fort) du noyau. Dans les processeurs PPC, ce registre est utilisé lors d'une interruption ou d'une exception pour calculer l'adresse à laquelle le processeur doit sauter. Par exemple, la "Machine Check Exception", (qui se produit très fréquemment lorsqu'on traffique son noyau, sigh) se voit attribuer du numéro 0x300, ce qui signifie que le processeur doit sauter à l'adresse SPRN_EVPR+0x300 en cas d'exception de ce type. Cela se produira bien sûr uniquement pour les exceptions/interruptions autorisées par le registre d'état du processeur (voir plus bas).
modified to access the CONFIG_KERNEL_PHYSICAL_OFFSET, and not KERNELBASE --mathieu */
lis r4,CONFIG_KERNEL_PHYSICAL_OFFSET@h /* EVPR only uses the high 16-bits */
addis r0,r4,0
mtspr SPRN_EVPR,r0
blr
turn_on_mmu:
lis r0,MSR_KERNEL@h
ori r0,r0,MSR_KERNEL@l
mtspr SRR1,r0
lis r0,start_here@h
ori r0,r0,start_here@l
mtspr SRR0,r0
SYNC
rfi /* enables MMU */
b . /* prevent prefetch past rfi */
Ce type de code est présent à plusieurs endroits dans le code PPC.
il permet de charger 2 registres spéciaux, SRR0 et SRR1 avec des
valeurs particulières et d'effectuer un RFI (return from interrupt).
Cette instruction va prendre le contenu de SRR0 et le mettre dans son
registre d'état, et ensuite placer son pointeur d'instruction sur le
contenu de SRR1. Ainsi, si le processeur était dans un état interrompu,
en positionnant les flags de SRR0 correctement, il revient dans son
flux d'éxécution "normal". Dans ce cas précis, MSR_KERNEL correspond à :
#define MSR_KERNEL (MSR_ME|MSR_RI|MSR_IR|MSR_DR|MSR_CE|MSR_DE)
(fichier "include/asm/reg_booke.h"), avec :
#define MSR_ME (1<<12) /* Machine Check Enable */
#define MSR_RI (1<<1) /* Recoverable Exception */
#define MSR_IR (1<<5) /* Instruction Relocate */
#define MSR_DR (1<<4) /* Data Relocate */
#define MSR_CE (1<<17) /* Critical Interrupt Enable */
#define MSR_DE (1<<9) /* Debug Exception Enable */
t correspond à l'état du noyau "normal", en opposition avec un état
MSR_USER, dans lequel il retourne quand il revient sur un programme :
MSR_USER (MSR_KERNEL|MSR_PR|MSR_EE)
Avec les définitions supplémentaires :
#define MSR_PR (1<<14) /* Problem State / Privilege Level */
#define MSR_EE (1<<15) /* External Interrupt Enable */
Ou il peut être interrompu parce que le programme a éxécuté une
instruction réservée au mode privilégié (noyau) ou une interruption
matérielle est survenue.
Le noyau initialise ensuite son premier contexte d'éxécution:
/* ptr to current */
lis r2,init_task@h
ori r2,r2,init_task@l
/* ptr to phys current thread */
tophys(r4,r2)
addi r4,r4,THREAD /* init task's THREAD */
mtspr SPRG3,r4
/* stack */
lis r1,init_thread_union@ha
addi r1,r1,init_thread_union@l
li r0,0
stwu r0,THREAD_SIZE-STACK_FRAME_OVERHEAD(r1)
bl early_init /* We have to do this with MMU on */
Un contexte d'éxécution est une portion de données dans laquelle le
noyau stocke une pile noyau de petite taille et non-extensible, ainsi
que les informations d'états, la sauvegarde de ses registres lorsqu'il
sort du contexte utilisateur, etc. Le contexte courant "init_task" est
un espace créé "en dur" à la compilation du noyau de façon à ne pas
avoir à allouer une quelconque zone à ce niveau du boot, ce qui serait
impossible, vu qu'aucun allocateur mémoire n'a été mis en place. A
partir de ce moment-là, en cas d'erreur ou d'interruption (Machine
Check Exception, Alignment Exception, Stack Overflow, Interrupt, Timer,
etc) il est possible de tracer le processus. Nous verrons plus loin de
quel manière.
On s'aperçoit içi de l'utilisation d'une première macro adaptée pour
le 403GA : "tophys", pour lequel il existe un pendant "tovirt". Ces
macro permettent de passer de la mémoire physique à la mémoire mappée,
ce qui n'a pour nous aucun sens. Ces définitions sont donc :
#define tophys(rd,rs) \
addis rd,rs,0
#define tovirt(rd,rs) \
addis rd,rs,0
(fichier "include/asm-ppnommu/ppc-asm.h")
Le processeur saute ensuite à early_init, qui est la première
routine C, qui est situé dans "arch/ppcnommu/kernel/setup.c". Ce code
initialise la BSS en mettant à 0 les octets entre "__bss_start" et
"_end", ce qui suppose que le plan mémoire place la BSS à la fin.
Retour à l'assembleur en renvoyant la taille de la mémoire ce qui
n'a aucune importance dans le cas du 403GA. il saute alors à
machine_init puis MMU_init qui sont situées également dans
"arch/ppcnommu/kernel/setup.c". Machine_init a pour rôle d'appeler
platform_init qui retrouve les informations du ramdisk et de la ligne
de commande, puis des initialisations très bas-niveau liées à
l'architecture. Il met en place "ppc_md" qui est une structure
contenant des pointeurs vers des routines spécifiques à l'architecture,
comme ". MMU_init récupère la taille mémoire, les mappings IO, et
initialise la MMU sur l'architecture PPC normale. Dans notre cas bien
sûr, la routine est grandement simplifiée !
De retour dans l'assembleur, il est temps de sauter vers
start_kernel, qui est le tout premier code C non-spécifique à
l'architecture. Il est situé dans "init/main.c". A partir de ce
moment-là, la routine d'initialisation est celle du noyau, avec des
appels aux routines spécifiques du PPC.
Une étape importante du noyau consiste à activer le timer du
processeur. A ce moment-là, un décrémenteur automatique est mis en
place par le processeur. A chaque TICK de l'horloge
interne, ce compteur est diminué de 1. Lorsque le compteur atteint 0,
le processeur fait une TimerInterruption et rentre dans le code
d'interruption approprié. Entre-temps, le timer s'est réinitialisé à sa
valeur initiale, aucun cycle n'a été perdu. De cette manière, il est
possible d'égréner le temps sans aucune perte.
Pour une raison que j'ignore, le timer n'est pas réinitialisé
correctement, et revient à une valeur très faible, ce qui conduit à
passer tout son temps dans l'interruption timer. Pour contrer cela, le
timer est réinitialisé de force, ce qui conduit à perdre des ticks. Le
temps s'écoule donc plus lentement.... Et les fonctions de temps sont
alors faussées !
Grâce à ces instructions :
boot_mapsize = init_bootmem_node(&contig_page_data, min_low_pfn,On peut ainsi créer un noeud mémoire et passer toute la mémoire de ce noeud à l'état disponible, en utilisant la notion de pfn.
CONFIG_MEMORY_START >> PAGE_SHIFT,
max_low_pfn);
/* remove the bootmem bitmap from the available memory */
mem_pieces_remove(&phys_avail, start, boot_mapsize, 1);
/* add everything in phys_avail into the bootmem map */
for (i = 0; i < phys_avail.n_regions; ++i)
{
printk("Free bootmem addr %p taille %p",phys_avail.regions[i].address,phys_avail.regions[i].size);
free_bootmem(phys_avail.regions[i].address,
phys_avail.regions[i].size);
}
init_bootmem_done = 1;
#define ___pa(vaddr) (vaddr)
#define ___va(paddr) (paddr)
#define __pa(x) ___pa((unsigned long)(x))
#define __va(x) ((void *)(___va((unsigned long)(x))))
PAGE_OFFSET est défini à l'adresse de début de la mémoire. Obtenir
l'indice dans la table des pages devient ici un jeu d'enfant:
#define MAP_NR(addr) (((unsigned long)(addr)-PAGE_OFFSET) >> PAGE_SHIFT)
Le PFN (page frame number) est quant à lui indicé par rapport au
début de la mémoire. Les conversions sont un jeu arithmétique:
#define virt_to_pfn(kaddr) (__pa(kaddr) >> PAGE_SHIFT)
#define pfn_to_virt(pfn) __va((pfn) << PAGE_SHIFT)
#define virt_to_page(addr) (mem_map + (((unsigned long)(addr)-PAGE_OFFSET) >> PAGE_SHIFT))
#define page_to_virt(page) ((((page) - mem_map) << PAGE_SHIFT) + PAGE_OFFSET)
#define pfn_to_page(pfn) virt_to_page(pfn_to_virt(pfn))
#define page_to_pfn(page) virt_to_pfn(page_to_virt(page))
Savoir si une page est valide revient à tester si elle ne dépasse
pas le nombre maximum de pages, qui est défini par l'architecture en
fonction de la mémoire totale.
#define VALID_PAGE(page) ((page - mem_map) < max_mapnr)
Vérifier si une adresse virtuelle est valide revient à revient si
elle est dans les bornes de la mémoire physique
#define virt_addr_valid(kaddr) (((void *)(kaddr) >= (void *)PAGE_OFFSET) && \
((void *)(kaddr) < (void *)memory_end))
Cette macro est requise, mais on fait confiance au noyau pour avoir
effectuer d'autres tests avant :
#define pfn_valid(pfn) (1)
Il existe différents formats d'éxécutables pris en charge par le noyau. Le code associé se trouve dans fs/. On y trouve ainsi:
binfmt_elf.c pour les binaires ELF (Executable and Linkable Format), maintenant standards pour les Unix.
binfmt_aout.c pour le format binaire "a.out", le plus ancien, mais limité.
binfmt_misc.c permettant de prendre en charge des formats externes, tels que les applets java.
binfmt_script.c permettant de gérer les scripts.
binfmt_som.c qui est un format utilisé sous HP-UX.
binfmt_flat.c qui est un format binaire "à plat", utilisé par les noyaux utilisant uClinux.
Les formats elf et aout ont l'inconvénient d'être verbeux et peut adaptés aux cibles embarqués. Le format "flat" a l'avantage d'être compact, et surtout de contenir toutes les informations pour effectuer une relocation.
En effet, les format ELF et consors sont basés sur le principe que la cible contient une MMU, et ainsi que chaque programme a son espace mémoire virtuel. Il est alors possible de fixer à l'avance à quel endroit doit se trouver le code à éxécuter (section TEXT), les données statiques pré-initialisées (DATA) et la section de données non-initialisées (BSS, en fait initialisée de force à 0 par le noyau).
NOTE : Dans le cas des bibliothèques partagées, c'est un peu plus compliqué, et certains symboles doivent être trouvés à des endroits précis. Passons.
Or le PPC403GA ne contient pas de MMU. Chaque programme se
retrouvera donc à une adresse X, impossible à déterminer à l'avance. Si
le programme est bati uniquement sur du code PIC (Position Independant
Code), c'est parfait, toutes les adresses dont le programme à besoin
(cibles des sauts, position des données) sont encodées par une adresse
relative, et quelque soit la position du programme, il retrouvera
parfaitement ses petits.
Ce n'est pas le cas du code généré par le 403GA, les adresses sont encodées "en dur", de manière absolue.
Il faut donc trouver un moyen pour "relocaliser" (relocation) les références à des
adresses absolue vers leur adresse absolue finale.
Pour cela, une liste des relocations à effectuer est fournie par le
fichier binaire du programme à éxécuter, dont voici une vue schématique
:

Dans binfmt_flat.c, chaque adresse est passée en revue et chacune est traitée:
1 for (i=0; i < relocs; i++) {
2 unsigned long addr, relval;
3 /* Get the address of the pointer to be
4 relocated (of course, the address has to be
5 relocated first). */
6 relval = ntohl(reloc[i]);
7 addr = flat_get_relocate_addr(relval);
8 rp = (unsigned long *) calc_reloc(addr, libinfo, id, 1);
9 if (rp == (unsigned long *)RELOC_FAILED)
10 return -ENOEXEC;
11 /* Get the pointer's value. */
12 addr = flat_get_addr_from_rp(rp, relval, flags);
13 if (addr != 0) {
14 /*
15 * Do the relocation. PIC relocs in the data section are
16 * already in target order
17 */
18 if ((flags & FLAT_FLAG_GOTPIC) == 0)
19 addr = ntohl(addr);
20 addr = calc_reloc(addr, libinfo, id, 0);
21 if (addr == RELOC_FAILED)
22 return -ENOEXEC;
23 /* Write back the relocated pointer. */
24 flat_put_addr_at_rp(rp, addr, relval);
25 }
"flat_get_relocate_addr" (ligne 7, fonction spécifique à
l'architecture) construit l'adresse qui devra contenir l'adresse
relocalisée. il y a nécessité de passer par une fonction spécifique à
l'architecture, car celle-ci intègre des drapeaux qui n'ont rien à voir
avec l'adresse cible.
Cette adresse qui contiendra l'adresse à
relocaliser doit elle-même être transformée en fonction de l'adresse de
chargement du programme. Cette adresse est décalée par la fonction
calc_reloc (ligne 8)
"flat_get_addr_from_rp" (ligne 12, fonction spécifique à l'architecture) récupère à l'adresse spécifiée la valeur de l'adresse cible relative. Il y a nécessité de passer par une fonction spécifique à l'architecture, car l'adresse cible peut être sur 16, 24, 32 bits, contenir des drapeaux, etc.
Cette adresse est décalée en fonction de sa position par la fonction calc_reloc (ligne 20)
La fonction "calc_reloc" n'est pas spécifique à l'architecture, et
calcule elle si l'adresse passée est dans la section TEXT ou DATA. Sur
certains processeurs sans MMU, seul la section DATA doit être
relocalisée, car la seciton TEXT peut être partagée par plusieurs
processus. C'est le cas des processeurs générant du code PIC, avec un
registre contenant l'origine de la section DATA.
Entrons dans les détails de notre cible ppcnommu. Dans
arch/ppcnommu/process.c, on trouve les définitions des fonctions
précédemment évoquées. La table de relocation contient des entrées sur
32 bits, dont 3 bits de drapeaux. Cela laisse 29 bits d'adresse, soit
512Mo, ce qui est largement suffisant pour adresser un programme
complet. Ces 3 bits de drapeaux sont positionnés en tête de l'entrée et
ont la signification suivante :
HA(X)= ((( X >> 16 ) + (( X & 0x8000 ) ? 1 : 0 )) & 0xFFFF )
On a donc besoin des 16 bits de poids faibles (à cause de "X &
0x8000", bien sûr !)
NOTE : Ce mécanisme est nécessaire uniquement pour les programmes
utilisateurs et pas pour le noyau. En effet, ce dernier est chargé à
une adresse prédéterminée, qu'il s'agisse du 1er ou du 2nd étage, et de
ce fait, les adresses sont connues à l'avance.
int sys_fork(int p1, int p2, int p3, int p4, int p5, int p6,
struct pt_regs *regs)
{
return (-EINVAL);
}
int sys_vfork(int p1, int p2, int p3, int p4, int p5, int p6,
struct pt_regs *regs)
{
CHECK_FULL_REGS(regs);
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->gpr[1],
regs, 0, NULL, NULL);
}
Inutile d'autoriser les programmes à éxécuter un appel système qui
leur permettra de courir à la catastrophe...
Afin de pouvoir utiliser ce noyau, il est nécessaire de pouvoir
lancer
des programmes, et pour cela, d'avoir un système de fichier racine.
Comme le NCD ne dispose pas de disque dur, ni de périphérique de
stockage de masse (à part le PCMCIA, mais il faut y rajouter une
carte), il est nécessaire :
Soit de se connecter par le réseau à un système de fichier racine.
Soit d'embarquer une image disque dans l'image du noyau, une
fonctionnalité apparue avec la série 2.6 du noyau. Cela s'appelle un
initrd, pour INITial Ram Disk.
C'est cette 2nde option, plus flexible, qui a été retenue.
Pour ce système de fichier, il faut choisir un type de système, parmi
tous ceux supportés par Linux, de préférence un système de fichier peu
gourmand en mémoire, ou l'écriture sera aisée, et ou toutes les
fonctionnalités du noyau serait disponible; La facilité de réalisation
aidant, le choix s'est porté sur le système "ext2", surement le plus
connu sous Linux.
Pour cela, il est nécessaire :
De réaliser une image disque sur la machine hôte.
De la poser dans le noyau au moment de la compilation.
De la faire reconnaitre et charger par le noyau.
Sous linux, il est possible de créer un système de fichier dans un fichier et de le monter en loopback pour y accéder. Les commandes suivantes :
cd PATH/TO/IMAGE
dd if=/dev/zero of=initrd.img bs=1k count=2048
/sbin/mkfs.ext2 -i 1024 -b 1024 -m 5 -F -v initrd.img
mount initrd.img TMP -t ext2 -o loop
permettent ainsi de créer et monter le fichier "initrd.img" dans le répertoire "TMP" comme étant un système de fichier ext2.
On peut alors le remplir avec le ce qui est nécessaire et une fois terminé, démonter le périphérique :
umount TMP
Tous les fichiers du système de fichier sont alors dans l'image "initrd.img"
Pour cela, rien de plus simple,
cp PATH/TO/initrd.img arch/ppcnommu/boot/images/ramdisk.image.gz
Une fois la recompilation du noyau effectuée, l'image est incluse dans le noyau comme ramdisk, et sera décompressée en temps voulue.
Il faut passer les paramètres sur la ligne de commande du boot:
ramdisk_blocksize=1024 root=/dev/ram0
L'image disque est alors reconnue et montée automatiquement, à condition d'avoir compilé en statique (pas en module) les drivers suivants (extrait du fichier .config du kernel):
CONFIG_EXT2_FS=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_SIZE=4096
Dans le cas ou ce serait compilé en module, non-seulement le noyau
ne
serait pas capable d'insérer un module à cause des relocations, mais en
plus pour charger le
fichier il lui faudrait le module ext2, qui est justement le module que
l'on cherche à charger !
On vient ainsi de se débarasser de l'infâme message "Unable to mount
root device", mais voilà qu'un autre surgit: "Kernel Panic ! Unable to
run init". C'est que notre image est vide, et donc le noyau ne trouve
pas de programme "init" à lancer. Voyons comment en construire un.
Notre chaine de compilation est capable de générer du code statique, mais le format cible reste du "elf" (executable and linkable format). Or comme vu précédemment, nous voulons obtenir du FLAT. Le programme permettant de faire cela s'appelle elf2flt, et il est lié fortement au patch uClinux.
Cependant, il ne gère pas le PowerPC. Des adaptations sont donc nécessaires, et reflètent ce que le noyau demande.
Elf2flt contient:
Un fichier shell "ld-elf2flt" qui se substitue au "ld" de la chaine de compilation.
Un script "ld" qui correspond au plan mémoire que le programme elf2flt attend pour générer du flat final
Un programme elf2flt.c, qui est lié à la bilibothèque "bfd" de la cible, et qui transforme les relocations de façon à donner au noyau ce qu'il attend.
Un autre programme, "flthdr", qui n'est pas absolument nécessaire au bon déroulement de la compilation, mais permet d'obtenir des informations sur les binaires générés, notamment les tailles des différentes sections, les drapeaux (flags) mis en place dans les binaires, etc.
Voici un résumé graphique de ce que fait le elf2flt :

Ce programme se substitue à ld et permet de lancer elf2flt sur le fichier elf généré. Il se sert également d'un fichier ".gdb" qui contient une partie des relocations résolues pour déterminer si le programme contient des bibliothèques partagées (en vérifiant la présence d'une GOT, Global Offset Table, utilisée dans les bibliothèques partagées). Actuellement, elles ne sont pas gérées par notre cible PowerPC.
Le script ld prend en compte tous les fichiers objets, qu'ils proviennent du programme proprement dit ou d'une archive (.a) contenant des routines supplémentaires, comme celles de la libc. Pour finir, il est nécessaire d'y ajouter un point d'entrée, nommé "_entry" et qui est contenu dans CRT0.S compilé vers CRT0.o, qui met en place les registres de pile et autres, et appelle à son tour le point d'entrée de la libc, qui appelera ensuite le "main" contenu dans le programme. La route est longue vers ce qui semble le point d'entrée "main" du programme !
Pour des raisons de tests, on peut se passer de CRT0.o à condition de définir soit-même son point d'entrée nommé _start.
Note: les bibliothèques partagées (.so) diffèrent des bibliothèques
statiques (.a) en cela que LD extrait tout le code nécessaire des
bibliothèques statiques et l'inclus directement dans le fichier objet
de sortie alors que les bibilothèques partagées contiennent uniquement
une référence au nom de la bibilothèque, qui sera partagée par tous les
programmes le nécessitant à l'éxécution.
Le script fourni permet de grouper les sections TEXT, DATA, BSS de manière facilement identifiables à ce qu'attend le programme elf2flt.
Exemple avec la section BSS:
.bss : {
. = ALIGN(0x4) ;
_sbss = ALIGN(0x4) ;
__bss_start = . ;
*(.dynsbss)
*(.sbss)
*(.sbss.*)
*(.scommon)
*(.dynbss)
*(.bss)
*(.bss.*)
*(.bss*)
*(.gnu.linkonce.b*)
*(COMMON)
. = ALIGN(0x10) ;
_ebss = . ;
_end = . ;
end = . ;
}
Une seule ligne a été rajoutée, car elle correspondait à une section manquante dans le script, mais qui est présente dans les binaires PowerPC.
*(.got2)
Comme actuellement la GOT n'est pas traitée (pas de bibliothèque partagée), ca ne change rien au final.
C'est ici qu'est la partie la plus importante du package elf2flt. Ce programme simple effectue les opérations suivantes:
Pour chacune des sections BSS, TEXT, DATA, il recherche les relocations qui sont nécessaires. Ces relocations indique au chargeur de programme qu'une adresse doit être recalculée, et que cette adresse ne peut pas être connue statiquement. En effet, le compilateur C génère des adresses absolue vers des sections autres que celle ou la relocation se trouve, et il n'est pas possible de déterminer l'adresse de
la section cible
l'adresse cible de la section
Le but du format FLAT est de mettre bout à bout TEXT/DATA/BSS afin de pouvoir calculer facilement ces relocations. Une table de relocation contient la liste des adresses à l'intérieur du fichier éxécutable qu'il faut relocaliser.
Tout cela a été vu dans la section noyau, penchons-nous sur la
génération de cette table de relocations. Comme nous ne considérons pas
le cas des bibliothèques partagées, les relocations prises en compte
sont les suivantes :
R_PPC_ADDR16_HA
R_PPC_ADDR16_LO
R_PPC_REL32
R_PPC_REL24
R_PPC_ADDR32
Les adresses relatives sur 24 bits sont les plus plus faciles à traiter. En effet, elles constituent une exception car elles ne correspondent pas à une adresse absolue dans le fichier, mais relative, dans la section courante TEXT vers la section TEXT. Cela signifie qu'on peut directement mettre la bonne valeur à l'adresse donnée pour la relocation. Une adresse REL32 correspondant à une adresse finale sur 32 bits, une adresse REL24 correspond à une adresse sur.... 24 bits !
L'adresse calculée est la différence entre l'adresse cible et l'adresse contenant la relocation.
Mais attention, c'est une adresse relative, et on peut parfois avoir besoin d'1 bit de différence; dans ce cas, la valeur qui est stockée dans l'adresse finale doit être ajoutée à l'adresse calculée.
Pour ces relocations, il n'est donc pas nécessaire d'ajouter une entrée de relocation :
relocation_needed = 0;
Pour les adresses relatives sur 32 bits, elles peuvent quand à elles
avoir pour cible une autre section que la section en cours. il est donc
nécessaire de créer une entrée spécifique pour que le noyau s'en
occupe. Ce qui donne :
case R_PPC_REL32:
{
relocation_needed = 1;
pflags=0x20000000;
sym_vma = bfd_section_vma(abs_bfd, sym_section);
sym_addr += sym_vma + q->addend;
break;
}
Ces deux-là fonctionnent ensemble de manière symétrique; L'adresse
finale est une adresse sur 16 bits, ce qui signifie que seuls 16 bits
de la cible devront être écrits. Les 16 bits suivants
ne doivent pas être touchés. Le problème est que la cible doit stocker
une adresse cible complète sur 32 bits, alors que l'espace laissé est
de 16 bits. Comme on l'a vu précédemment dans la paragraphe consacré au
noyau, la solution consiste donc à
rajouter une entrée de relocation bidon d'un type bidon contenant les
16 autres bits,
juste avant notre entrée de relocation. Cette relocation ne se sera pas
utilisée en tant que telle, mais permettra de calculer la valeur
complète.
C'est une adresse absolue sur 32 bits; on stocke donc directement l'adresse relative à l'adresse cible. Le noyau fera le reste.
Compiler uClibc directement nécessite d'appliquer des patchs pour le
faire cohabiter avec le compilateur, et ensuite avec busybox, etc. Un
outil existe déjà et a été développé spécifiquement pour cela, nommé
BuildRoot; il permet de construire de A à Z un système embarqué, en
téléchargeant les bonnes versions de la chaine de compilation, de
uClibc, elf2flt, busybox, construit le système de fichiers à embarquer,
le noyau, etc.
Malheureusement, il ne convient pas à notre besoin. (C'est la loi de
Murphy : tout ce qui a une chance de rater ratera au moment crucial).
En effet, il nous faut une chaine PowerPC sans MMU, qui n'a pas encore
été développée. A l'aide de BuildRoot, on peut néanmoins lancer la
compilation des uClibc, mais il faut inévitablement mettre "les
mains dans le cambouis" pour aller adapter le logiciel. Le choix de la
configuration "powerpc" permet d'obtenir une chaine de compilation et
uClibc, avec les patchs nécessaires. (modulo tous les soucis
d'installation du aux différences entre distributions, qui font la joie
du développement sous Unix)
Il faut maintenant adapter la compilation pour se passer de la MMU.
Ces outils reprennent le mode de configuration développé pour le noyau,
en s'appuyant sur des fichiers de configuration ".config" générés par
"make config". En éditant le fichier ".config", on peut donc ajouter
les spécificités du PowerPC.
TARGET_powerpc=y
TARGET_ARCH="powerpc"
ARCH_SUPPORTS_BIG_ENDIAN=y
ARCH_BIG_ENDIAN=y
# ARCH_HAS_NO_MMU is not set
# ARCH_HAS_MMU is not set
# UCLIBC_HAS_FLOATS is not set
# HAVE_NO_PIC is not set
# DOPIC is not set
# HAVE_NO_SHARED is not set
# ARCH_HAS_NO_LDSO is not set
# UCLIBC_CTOR_DTOR is not set
# HAS_NO_THREADS is not set
# UCLIBC_HAS_THREADS is not set
# UCLIBC_HAS_LFS is not set
MALLOC=y
Mais ce n'est pas tout. En effet, en fonctionnant tel quel, uClibc n'est pas capable de faire un vfork correct. En effet, dans cette version (0.9.27), uClibc ne fait pas de vfork avec le PowerPC:
pid_t vfork(void)
{
unsigned long __sc_ret, __sc_err;
register unsigned long __sc_0 __asm__ ("r0");
register unsigned long __sc_3 __asm__ ("r3");
#if 0
/* Sigh. The vfork system call on powerpc
* seems to be completely broken. So just
* use fork instead */
__sc_0 = __NR_vfork;
__asm__ __volatile__
("sc \n\t"
"mfcr %1 "
: "=&r" (__sc_3), "=&r" (__sc_0)
: "0" (__sc_3), "1" (__sc_0)
: __syscall_clobbers);
__sc_ret = __sc_3;
__sc_err = __sc_0;
if((__sc_err & 0x10000000) && (__sc_ret == ENOSYS))
#endif
{
__sc_0 = __NR_fork;
__asm__ __volatile__
("sc \n\t"
"mfcr %1 "
: "=&r" (__sc_3), "=&r" (__sc_0)
: "0" (__sc_3), "1" (__sc_0)
: __syscall_clobbers);
__sc_ret = __sc_3;
__sc_err = __sc_0;
}
__syscall_return (pid_t);
}
Or avec nos modifications, le vfork passe maintenant sans problème, ce qui autorise, à passer de
__sc_0 = __NR_fork;à
__sc_0 = __NR_vfork;
Et le tour est joué.
BusyBOx est un outil très utile permettant à l'aide d'un seul binaire, de construire un système Linux presque complet. En effet, selon la manière dont il est invoqué (le premier argument est le nom du programme binaire), il va se comporter comme si cette commande avait été appelée sous Unix. Par exemple, si le binaire s'appele "cp", busybox agira comme la commande "cp". Il suffit donc d'1 seul busybox dans tout le système de fichiers et d'un lien symbolique pour chacune des commandes qu'il supporte pour obtenir tout ce dont un système complet a besoin, en paratant des commandes de base, "cp", "mv", "mkdir", pour aller jusqu'à "init", "syslogd" et comble du luxe, un shell interactif nommé "msh", fonctionnant parfaitement sur un système sans MMU.
On laisse dérouler la compilation normalement jusqu'à l'étape finale de l'édition de lien. Si la chaine a été créée proprement, on doit pouvoir obtenir le binaire final sans souci; mais pour la beauté de la chose ;) on peut décomposer l'édition de lien :
powerpc-linux-gcc -O2 -static -elf2flt -nodefaultlibs -nostdlib -iwithprefix -Wall -Wa,-m403 -I. -Iinclude ./applets/busybox.c \
-o busybox \
-Wl,--start-group applets/applets.a archival/archival.a archival/libunarchive/libunarchive.a coreutils/coreutils.a console-tools/console-tools.a \
debianutils/debianutils.a editors/editors.a findutils/findutils.a init/init.a miscutils/miscutils.a \
modutils/modutils.a networking/networking.a networking/libiproute/libiproute.a networking/udhcp/udhcp.a \
procps/procps.a loginutils/loginutils.a shell/shell.a sysklogd/sysklogd.a util-linux/util-linux.a libpwdgrp/libpwdgrp.a \
coreutils/libcoreutils/libcoreutils.a libbb/libbb.a -Wl,--end-group -L. -L${ROOTUCLIBC}/uClibc-0.9.27/lib/ \
${CRT} -lc \
$ROOTGCC/gcc/libgcc/_divdi3.o \
/gcc/libgcc/_ashrdi3.o \
gcc/libgcc/_ashldi3.o \
gcc/libgcc/_moddi3.o \
gcc/libgcc/_udivdi3.o
cd bin
cp $BUSYBOX msh
cd bin
ln -s msh cat
ln -s msh chgrp
(...)
cd ../sbin
ln -s ../bin/msh init
ln -s ../bin/msh halt
ln -s ../bin/msh poweroff
ln -s ../bin/msh reboot
(...)
Attention, les liens symboliques doivent être relatifs au
système de fichier image, le système embarqué prenant comme racine une
racine différente de celle du système hôte.
CONFIG_CMDLINE="rw console=ttyS0,9600 ramdisk_blocksize=1024 root=/dev/ram0 init=/bin/msh"
Le noyau devrait ainsi chercher à lancer le programme "/bin/msh" au
boot. Ce qui devrait nous amener à :
RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize
mice: PS/2 mouse device common for all mice
RAMDISK: ext2 filesystem found at block 0
RAMDISK: Loading 2048KiB [1 disk] into ram disk... done.
VFS: Mounted root (ext2 filesystem).
Freeing unused kernel memory: 56k init
Using fallback suid method
BusyBox v1.00 (2005.09.24-12:34+0000) Built-in shell (msh)
Enter 'help' for a list of built-in commands.
#
C'en est fini !
Fini ? Non, pas tout-à-fait.
Ce système constitue maintenant une base solide pour porter des
applications, telle qu'un serveur X sur cette machine. Mais avant
cela, il reste à réussir à faire fonctionner les périphériques PS/2 :
clavier et souris, mais également la carte réseau.
Dans une prochaine partie, le portage de Nano-X pour l'affichage
graphique.
PowerPC 403GA 32 bits Risc Embedded Controller
System V Application Binary Interface PowerPC
Processor Supplement
Other docs about what's inside the NCD Explora 401:
Le projet Linux sur Explora 451 : http://explora-linux.sourceforge.net/EXPLORA.html
C'est plutôt simple pour la connexion, mais cela nécessite un cable série mâle/mâle(quelques ¤ en supermarché) comme celui-ci :

Côté NCD, rien de plus simple :

Il faut encore configurer le NCD pour aller chercher l'image à booter côté PC. Ca se passe dans le menu de paramètrage du NCD; pour cela, appuyer sur Esc au moment de l'allumage du NCD. Taper "se" pour entrer dans le setup. ("?" donne une liste des commandes, utile aussi)
Aller dans "Network" à l'aide des flèches,
Choisir "Get IP Address from NVRAM"
Choisir une adresse pour le terminal (Terminal IP Address)
Choisir une adresse d'ou télécharger le noyau (First Boot Host Address), ce qui devrait être l'adresse du serveur, donc plus simplement de l'hôte Linux ou le port série est racordé
Dans "boot",
mettre boot file = Xncdxpl, ou n'importe quel nom de fichier situé à la racine du serveur tftpboot
TFTF boot directory à vide.
Dans "Done":
Reboot
Là, c'est légèrement plu compliqué. il faut non-seulement s'occuper de voir "logiquement" la sortie série, mais également paramétrer un serveur tftp qui contient l'image à booter. On se sert également de Linux de ce côté, pour des raisons de facilité.
Pour la partie à booter, utilisez le serveur tftp de votre distribution
(tftfp-server sur Mandriva), et positionnez la racine du répertoire à
"/tftpboot/" . Copiez votre noyau dans ce répertoire (Xncdxpl ou
autre), et n'oubliez pas d'activer le serveur...
Il faut utiliser un programme permettant de se connecter sur le port
série. "minicom" convient parfaitement. Selon le port série utilisé sur
la machine hôte, utliser comme périphérique "/dev/ttyS0", ttyS1, etc.
Utiliser en 9600 bauds, et supprimer les chaines de connexion du modem, vu que ce n'est pas un modem ;)
Puisque les captures d'écran sont à la mode, en voici une :

Ce document devrait permettre de comprendre un peu mieux les rouages de uClinux et de son portage vers l'Explora401. Pour des questions, corrections, proposition de patch, envoyez un mail à mathieu.avila@laposte.net