next up previous contents
suivant: Intégration et utilisation sur monter: Développement d'un système d'exploitation précédent: Couche indépendante du hardware   Table des matières

Sous-sections

Couche dépendante du hardware

Comme nous l'avons déjà dit, cette couche fournit une interface indépendante du matériel pour la couche supérieure. Cette couche matérielle doit effectuer des opérations de configuration et d'initalisation de la machine et fournit un ensemble de services spécifiques au matériel. Les services varient des interruptions au changement de contexte, en passant par l'utilisation du port série et de l'écran LCD. Tous les fichiers concernant cette couche se trouvent dans le répertoire arch et les fichiers d'en-tête se trouvent dans le répertoire include/asm.

Pour faciliter le portage sur d'autres types de machines et minimiser la duplication de code, nous avons créé une hiérarchie à trois niveaux :

  1. l'architecture : ce niveau contient tout ce qui est commun aux différents processeurs d'une même architecture. Le principal des fonctions se trouve dans cette partie. En effet, les processeurs d'une même architecture, ont en général le même fonctionnement, seules quelques différences mineures les séparent soit au niveau de la puissance, soit au niveau de certaines fonctionnalités différentes. Expresso est porté sur la famille m68knommu des processeurs Motorala 68000 sans unité de gestion de mémoire. Cette partie contient les fontions d'affichage sur le LCD, la gestion de l'UART, des timers, du digitizer et fournit une interface commune concernant la gestion des interruptions.
  2. la plateforme : cette partie gère tout ce qui est spécifique aux processeurs. Expresso est pour l'instant porté sur 3 processeurs : 68328, 68EZ328 et 68VZ328. Les deux grands rôles de ce niveau sont la gestion des interruptions et quelques fonctions concernant le processeur comme le reset du Timer ou l'initialisation de celui-ci.
  3. le modèle : contient tout ce qui est spécifique au modèle de matériel utilisé. Par exemple si nous prenons le cas du processeur 68VZ328, il existe plusieurs modèles de Palm qui l'utilisent, chacun avec une mémoire de taille différente, des périphériques différents, etc. Ce niveau se compose habituellement de deux fichiers, un fichier contenant le code de démarrage spécifique au modèle de matériel utilisé et un fichier contenant la carte mémoire du modèle.

Maintenant que nous avons donné la structure de cette couche, nous allons en décrire les différents points importants et analyser les différents services qui sont offerts par celle-ci.

Démarrage du système

Le démarrage du système est spécifique au processeur utilisé. Le code de démarrage consiste à :

Pour placer le code et les données au bon endroit dans la mémoire, il faut indiquer au linker la localisation des différentes parties de la mémoire. La mémoire est divisée en différentes sections. Certaines de ces sections sont obligatoires (text,data,bss) et d'autres soit au choix de l'utilisateur :

Le linker a besoin d'une carte de la mémoire (memory map) qui lui fournit l'adresse de base et la taille de chaque zone de la mémoire. Cette carte est fournie sous forme d'un fichier de lien (.ld). Ce fichier n'entraine pas la copie des données au bon endroit mais décrit seulement la configuration de la mémoire.

Un fichier de lien contient deux sections principales : MEMORY et SECTIONS :

Particularités du xcopilot

Expresso est chargé en ROM dans le xcopilot. Il faut donc copier certaines données en RAM, c'est le fichier de démarrage qui effectue cela. Commençons par détailler la carte mémoire du xcopilot.

Carte mémoire

Xcopilot émule un Palm III avec une mémoire de 1Mb. et une rom de 1Mb aussi. La mémoire physique est découpée ainsi :

Figure: Mémoire du Xcopilot
\begin{figure}\begin{center}
\epsfig{file=implement/mem_xcop, height=4cm}
\end{center}\end{figure}

La mémoire physique est fixée par le hardware et nous ne pouvons pas la modifier. Par contre, nous avons divisé la ROM en 3 parties dans le fichier de lien :

Nous avons aussi divisé la RAM en 3 parties :

Voici une partie du fichier de lien du xcopilot (rom.ld) qui montre la structure de ce type de fichier :

\fbox{\begin{minipage}{10,5cm}
\begin{footnotesize}
\texttt{MEMORY \{ \\
\hspac...
...\_start = ALIGN ( 4 ) ;\\
\} > flash\\
...
}
\end{footnotesize}\end{minipage}}

Code de démarrage

Le fichier (head.S) est le fichier de démarrage du xcopilot. Analysons brièvement son contenu. Xcopilot a une façon particulière de démarrer. Il recherche une séquence spéciale de démarrage, la première chose à faire est d'insérer cette séquence :
.byte 0x4e, 0xfa, 0x00, 0x0a	/* Jmp +X bytes */
.byte 'b', 'o', 'o', 't'		/* Start code for xcopilot */

Ensuite, il faut initialiser un ensemble de registres du processeur.

\fbox{\begin{minipage}{14cm}
\begin{footnotesize}
\texttt{movew \char93 0, 0xfff...
...0, \%a4
lea \%a4@(-4), \%sp /* Init sp */\\
}
\end{footnotesize}\end{minipage}}













Le registre LSSA permet de configurer l'adresse de base qui sera affichée sur le LCD. En placant dans le label penguin_bits une image de 160*160 pixel, celle-ci est automatiquement affichée sur l'écran LCD.

Le registre PLLFSR permet de paramétrer la fréquence d'horloge suivant la formule :

\begin{displaymath}14\ *\ (P+1)\ +\ Q\ +\ 1\end{displaymath}

Ce qui donne pour les valeurs de P = 0x23 et de Q = 1 et une fréquence du crystal de 32,768 kHz, une fréquence d'horloge de 16Mhz.

Ensuite, il faut faire une boucle pour laisser le temps à la fréquence d'horloge de se stabiliser.

\fbox{\begin{minipage}{4cm}
\begin{footnotesize}
\texttt{\hspace*{0,5cm} moveq \...
...bw \char93 1, \%d0\\
\hspace*{0,5cm} bne L0
}
\end{footnotesize}\end{minipage}}













Comme le système d'exploitation est en ROM, il faut copier le contenu de la section .data en RAM. Ce qui est réalisé par le code suivant :

\fbox{\begin{minipage}{7,5cm}
\begin{footnotesize}
\texttt{\hspace*{0,5cm} movea...
... cmpal \%a1, \%a2\\
\hspace*{0,5cm} bhi LD1
}
\end{footnotesize}\end{minipage}}













La zone .bss n'est pas encore initialisée à 0, c'est à nous de le faire ici :

\fbox{\begin{minipage}{7cm}
\begin{footnotesize}
\texttt{\hspace*{0,5cm} moveal ...
...} cmpal \%a0, \%a1\\
\hspace*{0,5cm} bhi L1
}
\end{footnotesize}\end{minipage}}













Toute l'initialisation est maintenant terminée, on peut appeler la fonction start_kernel.

Particularités du Palm m500

Il existe peu d'informations sur les détails du montage électronique du Palm m500. La firme Palm ne donne pas ces informations et il est donc très difficile de les trouver. A l'heure actuelle, nous n'avons pas réussi à configurer l'UART du Palm m500. Le processeur de ce modèle possède deux UARTs, une sert pour le port infra-rouge et une autre pour le port universel qui fournit les signaux USB et RS232. Normalement, une broche du processeur doit permettre de sélectionner l'un ou l'autre de ce ces modules et la polarité des signaux. Nous avons trouvé l'affectation des dix ports du processeur dans les sources de l'émulateur palm (POSE). L'affectation complète est reprise en annexe mais nous pouvons remarquer quelques signaux particuliers au Palm m500:

Port Pin Fonction
B 6 LED de l'alarme
D 4 Bouton HotSync/Dock
D 7 Interruption de chute d'alimentation
F 1 Interruption du digitizer
G 3 Activation du port série pour le deuxième UART
K 4 Vibreur
M 5 Activation du port infra-rouge

Carte mémoire

Le Palm m500 possède une ROM de 4Mb et une RAM de 8Mb. La carte mémoire et le code de démarrage sont presque identiques à ceux du Xcopilot. La principale différence vient du fait que sur le Palm m500, Expresso est chargé en RAM et pas en ROM comme dans le Xcopilot. Donc la partie .text est placée en RAM et il n'y a plus besoin de copier la partie .data en RAM.

\fbox{\begin{minipage}{11cm}
\begin{footnotesize}
\texttt{MEMORY \{ \\
\hspace*...
..._etext = . ;\\
\hspace*{0,5cm}} > ram\\
\}
}
\end{footnotesize}\end{minipage}}













Code de démarrage

Le code de démarrage est encore plus simple que pour le Xcopilot. Il suffit d'initialiser quelques registres et de faire la temporisation pour que les signaux d'horloge se stabilisent. Comme l'OS est chargé en RAM, il ne faut pas copier la partie .data en RAM. La partie bss doit être initialisée à 0 comme pour le Xcopilot.

\fbox{\begin{minipage}{8,5cm}
\begin{footnotesize}
\texttt{\hspace*{0,5cm} movew...
...bw \char93 1, \%d0\\
\hspace*{0,5cm} bne L0
}
\end{footnotesize}\end{minipage}}













Gestion des exceptions

Il faut tout de suite noter la différence entre exception, interruption et trap. Une interruption permet à un périphérique de prévenir le processeur qu'un évènement s'est produit, ceci se passe au niveau hardware. Une trap à le même rôle mais au niveau software, une condition d'exception survient et une trap est générée par le processeur ou par l'utilisateur. Le terme exception regroupe les deux concepts précédents pour représenter la partie commune aux deux mécanismes.

La gestion des exceptions est aussi dépendante du processeur mais ne varie que très peu entre les différents DragonBall. La gestion des exceptions est réalisée par les fichiers traps.c, ints.c et entry.S.

Les interruptions dans un système d'exploitation temps-réel sont très importantes. Elles permettent de réagir à des évènements dans des délais très courts et sont utilisées pour gérer des tâches très prioritaires. Nous avons donc essayé de mettre en place un mécanisme puissant et rapide pour que l'utilisateur puisse facilement créer des routines d'interruption et les installer dans le système.

Initialisation des exceptions

Comme nous l'avons déjà vu dans le chapitre concernant l'architecture du palm, lorsqu'une exception survient, le processeur génère un vecteur d'exception et regarde dans la table des vecteurs pour déterminer l'adresse du handler. Chaque exception possède son propre vecteur et il est important de placer celui-ci au bon endroit. Nous allons utiliser la table de vecteurs d'interruption en RAM, donc toutes les adresses des handlers seront placée en RAM dans la section .ramvec.

Quand le processeur démarre, il place la valeur du premier mot mémoire dans le registre de pile (SSP) et le deuxième mot contient l'adresse de démarrage qui est placée dans le PC.

\fbox{\begin{minipage}{9cm}
\begin{footnotesize}
\texttt{.section .romvec\\
.lo...
...amend-4, \_start /* Stack \& reset vector */
}
\end{footnotesize}\end{minipage}}













Ensuite on initialise la table des vecteurs d'exception dans la ROM, ce code est placé dans la section .romvec. Le handler par défaut est trap qui est un handler vide. Les deux premiers vecteurs sont les mêmes que pour le démarrage, d'ailleurs si le vecteur d'interruption est placé à l'adresse 0, c'est celui-ci qui sert de valeur de démarrage pour le SSP et le PC.

\fbox{\begin{minipage}{11cm}
\begin{footnotesize}
\texttt{e\_vectors:\\
\hspace...
...m} .long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
}
\end{footnotesize}\end{minipage}}













Mais la table des vecteurs en RAM n'a pas encore été initialisée, c'est la fonction init_IRQ() qui est chargée d'initialiser cette table. La fonction place juste le nom du stub au bon endroit. Voici un apercu de la fonction :

\fbox{\begin{minipage}{12cm}
\begin{footnotesize}
\texttt{void init\_IRQ(void) \...
...gonBall IVR (interrupt base) to 64 */ \\
\}
}
\end{footnotesize}\end{minipage}}













Le registre IVR (Interrupt vector register) permet de programmer les 5 bits de poids fort des vecteurs d'interruption. Les 3 bits de poids faible contiennent le niveau d'interruption. La plupart des vecteurs d'interruption sont utilisés par le processeur et ne peuvent pas être redéfinis. Il reste donc un petit espace entre 0x100 et 0x400 où l'on peut configurer le IVR. En placant 0x40 dans ce registre, la base du vecteur d'interruption est de 0x100 (0x40 « 2) qui est le début des vecteurs d'interruption utilisateurs.

On peut ainsi voir que l'exception de changement de contexte est le vecteur 47 ce qui correspond à la trap 15. Le vecteur 68 corresond à l'interruption de niveau 4 et est utilisée pour l'interruption d'horloge et l'UART.

Gestion des exceptions

Notre but est de créer un mécanisme efficace et suffisamment abstrait pour faciliter l'utilisation des exceptions et plus particulièrement des interruptions. Nous avons pour cela créé une structure représentant une routine d'interruption irq_node :

\fbox{\begin{minipage}{5cm}
\begin{footnotesize}
\texttt{typedef struct irq\_nod...
...e*{0,5cm} CHAR *devname;\\
\} irq\_node\_t;
}
\end{footnotesize}\end{minipage}}













Cette structure est composée d'un handler, d'un drapeau et d'un nom. Le drapeau peut avoir trois valeurs :

Les processeurs de type DragonBall acceptent 32 interruptions internes différentes comme le timer, l'UART, le digitizer, l'horloge temps-réel, IRQ1, IRQ2, watchdog, etc. Chacune de ces interruptions possède un niveau entre 1 et 7.

Expresso fournit un ensemble d'opérations sur ces structures :

Quand une interruption survient, il faut sauvegarder l'état de la tâche en cours puis appeler la routine d'interruption. C'est le rôle du stub de faire cela. Pour faciliter l'utilisation des interruptions, tous les stubs sont déjà créés et placés dans le fichier entry.S. Plus précisémment, le stub doit effectuer les opérations suivantes.

Pour simplifier l'écriture de stub, Expresso fournit :

Donc l'écriture d'un stub revient à ceci :

\fbox{\begin{minipage}{10cm}
\begin{footnotesize}
\texttt{stub :\\
\hspace*{0,5...
...ur de la pile\\
\hspace*{0,5cm} jmp exitInt
}
\end{footnotesize}\end{minipage}}

Utilisation des interruptions

L'abstraction offerte par le mécanisme précédent permet de créer et d'installer facilement des routines d'interruption. Nous allons regarder 3 interruptions particulières.

Interruption d'horloge

Pour faciliter la portabilité du code, nous avons créé une routine d'interruption d'horloge générale qui fait appel à des fonctions spécifiques de la plateforme. Tout d'abord on fait appel à mach_tick2() qui permet simplement de remettre le timer à 0 avec la commande TSTAT2 = 0, ensuite la fonction processTick() effectue les opérations de temporisations d'Expresso comme nous l'avons vu dans le chapitre précédent.

\fbox{\begin{minipage}{6cm}
\begin{footnotesize}
\texttt{void timer\_interrupt(I...
...2();\\
\hspace*{0,5cm} processTick();\\
\}
}
\end{footnotesize}\end{minipage}}













Avant d'installer la routine, il faut initialiser l'interruption en configurant les différents registres du timer pour obtenir une fréquence de 100 Hz. Ensuite l'installation de la routine se fait avec la fonction request_irq avec comme paramètre le numéro correspondant à l'interruption TMR2_IRQ_NUM, la routine décrite plus haut, le flag IRQ_FLG_LOCK pour ne pas qu'on puisse remplacer la routine et son nom timer 2. La dernière opération est l'activation de la routine.

\fbox{\begin{minipage}{12cm}
\begin{footnotesize}
\texttt{void time\_init(void) ...
...*{0,5cm} enable\_irq(TMR2\_IRQ\_NUM); \\
\}
}
\end{footnotesize}\end{minipage}}

Interruption du digitizer

Lorsque l'utilisateur presse le stylet sur l'écran tactile, le digitizer permet d'obtenir la position (x,y) du stylet sur cet écran. Le procédé pour récupérer les coordonnées est constitué d'un ensemble d'initialisations et d'envois de valeurs sur les ports du processeur qui sont très spécifiques. Nous n'allons pas entrer dans ces détails, il suffit d'aller dans le code pour voir ceux-ci.

Le digitizer utilise le module SPIM du processeur qui permet de communiquer avec le module hardware du digitizer. La routine consiste à demander successivement la valeur en ordonnée puis en absice de la position du stylet. Ensuite cette coordonnée est affichée.

\fbox{\begin{minipage}{9cm}
\begin{footnotesize}
\texttt{void digi\_interrupt(IN...
...gi\_interrupt()'',''raw y= '',new\_y);\\
\}
}
\end{footnotesize}\end{minipage}}













L'installation de la routine se fait de la même manière que pour l'interruption d'horloge :

\fbox{\begin{minipage}{11cm}
\begin{footnotesize}
\texttt{request\_irq(PEN\_IRQ\...
...digitizer'');\\
enable\_irq(PEN\_IRQ\_NUM);
}
\end{footnotesize}\end{minipage}}

Exception de changement de contexte

L'exception de changement de contexte est en fait une trap qui est générée par le scheduler avec la macro SWITCH_TASK. Lorsque cette trap (15) est déclenchée, la fonction switchContext est appelée. Cette fonction suppose qu'un pointeur vers la tâche de plus haute priorité est contenu dans la variable highReadyTask et qu'un pointeur vers la tâche courante est contenu dans la variable currentTask ainsi que les priorités respectives de ces tâches dans les variables highReadyPrio et currentPrio. Cette fonction doit successivement

Cette fonction est extrèmement importante car elle est exécutée à chaque ordonnancement. Son code est particulièrement court et écrit en assembleur pour optimiser le nombre d'instructions.

\fbox{\begin{minipage}{7cm}
\begin{footnotesize}
\texttt{switchContext:\\
\hspa...
...{0,5cm} RESTORE\_ALL \\
\hspace*{0,5cm} rte
}
\end{footnotesize}\end{minipage}}













L'UART

L'UART permet d'envoyer et de recevoir des caractères du port RS-232. L'initialisation principale a déjà été effectuée dans le code de démarrage, il ne reste plus qu'à autoriser l'interruption provenant du port série.

\fbox{\begin{minipage}{7,5cm}
\begin{footnotesize}
\texttt{void serialInit() \{\...
...
\hspace*{0,5cm} showSerialVersion();\\
\}
}
\end{footnotesize}\end{minipage}}













Pour envoyer des caractères, il suffit d'écrire le caractère dans le registre UTX_CHAR. Il faut quand même vérifier que le buffer n'est pas plein. Il faut aussi sauver le registre de status (save_flags) qui peut être modifié par l'UART et le restaurer après (restore_flags).

\fbox{\begin{minipage}{9cm}
\begin{footnotesize}
\texttt{static void rsPutChar(C...
...\hspace*{0,5cm} restore\_flags(flags);\\
\}
}
\end{footnotesize}\end{minipage}}













La fonction writeSerial(CHAR *p) permet d'envoyer une chaine de caractères sur le port série.

Le LCD

L'écriture sur l'écran LCD est très simple, en permanence une partie de la mémoire est copiée sur l'écran. Le registre LSSA contient l'adresse de base de cette zone, chaque point à l'écran correpond à un bit de la mémoire. L'écran du Palm a une résolution de 160 x 160 pixels donc la taille de la zone en mémoire est de 3200 bytes. Pour écrire un point à l'écran, il suffit de changer la valeur du bit correspondant en mémoire. Pour faciliter l'utilisation de l'écran, Expresso fournit des fonctions qui permettent de dessiner ou d'écrire des caractères sur le LCD.

En plus de ces fonctions de bas niveau, Expresso permet d'afficher des chaînes de caractères sur l'écran. Cela est très utile pour voir ce qui se passe lors de l'éxécution d'un programme. Expresso contient une structure qui garde en mémoire les messages que l'on affiche à l'écran. De cette manière, il est possible d'afficher un genre de console. Quand un message est envoyé sur l'écran, il s'affiche en dessous des autres et quand il n'y a plus de place, l'ensemble des messages remontent pour afficher les nouveaux.

Pour initialiser cette console rudimentaire, on utilise la fonction DbgLCD_init qui permet de définir la position et la taille de la console. La fonction DbgLCD_writeString permet d'écrire une chaine de caractères dans la console.


next up previous contents
suivant: Intégration et utilisation sur monter: Développement d'un système d'exploitation précédent: Couche indépendante du hardware   Table des matières
Fabian Skivee 2002-06-04