mercredi 12 décembre 2018

Chapitre 52 : Baremetal Raspberry : executer un programme externe


Je n’ai toujours pas trouvé de solution au problème du clavier relié au port USB du Raspberry. Donc en attendant, nous allons voir comment exécuter un programme externe à partir de notre petit OS !! Pour cela nous créons un programme pgm1.s qui n’effectuera que l’envoi d’un message par l’Uart. Je laisse en plus quelques modules utilitaires pour vérification. Le programme est compilé et linké pour fournir un exécutable de type ELF32 (puisque mon raspberry est en 32 bits). Je n’ai pas regardé dans ce premier cas, toutes les contraintes à respecter et donc la seule est de linker les modules avec l’option –r qui va créer les données nécessaires à la <relocalisation> de l’exécutable. En effet, nous n’allons pas charger cet exécutable aux adresses définies par le linker (par exemple 8000h) mais à nos propres adresses et donc il est nécessaire de recalculer un certain nombre d’instructions pour avoir une exécution correcte.
L’exécutable (pgm1.elf) ainsi crée sera stocké dans le répertoire principal de la carte SD (avec le kernel.img) car lors d’un chapitre précédent sur la carte SD, je n’ai pas écrit de fonction pour lire un fichier dans des sous-répertoires de la carte SD (il faut bien que je vous laisse un peu de travail !!).
Vous remarquerez aussi que ce petit programme utilise les modules développes précédemment et donc va stocker en mémoire des instructions qui y figurent déjà. Il serait préférable de développer un mécanisme pour utiliser les fonctions déjà écrites et chargées dans le kernel.img. On verra peut être cela un autre jour !!!
L’utilitaire readelf (disponible sous raspbian ou windows) permet de visualiser les entités du programme. Voici par exemple le début des données de relocalisation :
 
Nous pouvons y voir que la fonction uart_send_string qui se trouve à l’emplacement 0x98c est utilisé au déplacement 0x140 du programme et que cette table commence à l’adresse 0x2ab4 et quelle contient 91 symboles qui seront à relocaliser.
Revenons au module kernel.s de notre OS. Nous y ajoutons une commande exec pour lancer l’exécution de notre petit programme externe (lancement par exec pgm1.elf) puis nous créerons un nouveau module execPgm.s qui contiendra les fonctions nécessaires à cette exécution.
Dans ce module, nous commençons à lire toutes les données du fichier contenant le programme à executer. Comme nous ne connaissons pas la taille, nous chargeons les données sur le tas. Le tas est la zone mémoire comprise entre la fin de la section bss et la pile et qui est complétement libre. Pour cela nous gérons une adresse du tas que nous ferons évoluer à chaque réservation de place nécessaire. Nous conservons l’adresse de début de stockage dans le registre r10 car cette adresse va servir de référence pour traiter toutes les donneés d’un programme de type ELF32.
Ensuite nous vérifions que les données lues correspondent à un format ELF, relogeable et pour un processeur ARM en récupérant les données dans l’entête du fichier grâce à la description d’une structure. Si le fichier est valide, nous récupérons aussi la valeur du point d’entrée du programme et les décalages des différentes tables (voir sur Internet la description d’un programme ELF32) à savoir la table des entêtes sections, la table des noms de section etc.
Puis nous allons analyser la table des entêtes sections pour identifier chaque section du programme. Pour certaines nous nous contentions de mettre à jour le décalage pour les retrouver et pour d’autres nous les recopions sur le tas (.text, .data). En effet, ces sections doivent être alignées sur des frontières particulières (multiple de 2 donnée par sectheaderElf_addralign). Bien entendu nous stockons ces nouvelles adresses dans la structure générale du programme.
Maintenant, il faut analyser la table des relocations pour recalculer les adresses de symboles en fonction des nouvelles adresses des sections .data et .text.  Pour chaque emplacement (décalage par rapport au début de la section) il faut retrouver le déplacement de la position du symbole pour remplacer son adresse. Suivant le type de relocation, il faut effectuer des opérations plus ou moins compliquées. Dans la documentation ARM nous retrouvons pour chaque type l’opération à effectuer. Par exemple pour le type R_ARM_ABS32 nous trouvons l’opération (S +A) | T, ce qui signifie ajouter la nouvelle valeur à l’ancienne et nous ne tenons pas compte de la valeur de T qui correspond à l’exécution d’instructions en thumb ce qui n’est jamais le cas ici.
Pour le type R_ARM_CALL c’est une opération plus compliquée et pour les autres types, je n’ai pas eu à les programmer car sur mes petits programmes je n’ai pas trouvé de cas. Si dans un programme, il y a un cas non traité, un message d’anomalie est affiché avec le code concerné et il faudra donc rechercher l’opération à effectuer et la programmer.
Dans ce programme, il y a un petit problème, car le résultat de la dernière opération est décalé de 4 octets et donc j’ai du les enlever avant d’exécuter le programme externe. Je n’ai pas trouvé l’origine de ce décalage : incompréhension ou erreur de ma part ?
Si tout est ok, on peut lancer le programme en effectuant simplement un saut au point d’entrée qui sera calculé.
Il faudrait améliorer le programme pour sauvegarder l’adresse de la pile  dans une zone mémoire fixe au cas où le programme appelé retourne une adresse de pile fausse !! Et aussi sauver et restaurer les registres si nécessaire.
Après l’exécution, nous remettons l’adresse du tas au début pour permettre l’exécution d’un autre programme sans perte d’espace mémoire.
Et tout le projet est ici.

jeudi 15 novembre 2018

Chapitre 51 : Baremetal afficher une icone dans écran externe. Comptage de cycles machines


Pour clore ces chapitres, il me restait à intégrer la gestion d’un clavier pour avoir un système totalement autonome. Vu la complexité de cette gestion car les claviers connectés au Raspberry doivent passer par le port USB et donc par son protocole, j’ai essayé de trouver sur Internet des programmes qui avaient développé cette possibilité. J’ai trouvé 2 drivers sur github écrits en langage C++ mais à ce jour, je ne suis pas arrivé à faire fonctionner ces drivers sur mon Raspberry. En attendant de trouver une correction je vous propose de compléter le chapitre précédent en mettant en place une routine qui dessine sur l’écran une petite image. Puis de déplacer cette image sur l’écran. Pour faire simple, l’image est décrite sous forme d’une matrice de x,y bytes qui prennent la valeur 0 ou 1 suivant que la couleur du pixel doit être celle du fond ou celle de l’image. Avant cette description, on trouve 2 mots indiquant la largeur et la hauteur de l’image. Voir la description de zImgCroix dans le module images.s.
Dans le module FrameBuffer.s nous ajoutons une routine pour afficher l’image et qui consiste en 2 boucles qui balayent la largeur et la hauteur de l’image. Il faut bien calculer la position du pixel pour le stockage dans la zone mémoire du FrameBuffer (ne pas oublier que chaque pixel occupe 4 octets dans la mémoire !!).
Dans le module kernel.s, nous ajoutons un appel à l’affichage de la croix en précisant sa position en pixel et sa couleur.
Puis nous ajoutons une boucle pour montrer le déplacement d’une autre croix. Dans celle-ci, nous commençons par afficher l’image puis nous attendons un certain temps, nous effaçons l’image en la redessinant avec la couleur du fond puis nous bouclons.
Nous pouvons effectuer quelques tests pour varier la couleur, le sens du déplacement et la vitesse. Vous pouvez aussi dessiner d’autres formes.
Plusieurs axes d’améliorations sont possibles : compacter chaque ligne de la table sur un seul mot de 32 bits (mais cela limite une image à 32 bits de largeur). A chaque déplacement, le pixel du fond est remplacé par la couleur noire et donc si l’on veut que l’image se déplace sur une autre image il faut sauvegardé l 'image du fond et la restaurer après l’effacement !!
Dans cet exemple, j’ai aussi ajouté dans le module util6.s 2 fonctions pour mesurer le nombre de cycles d’un lot d’instructions. Pour cela nous faisons appel aux fonctions spéciales du microprocesseur mcr et mrc. Je ne suis pas du tout sûr des résultats car 1) le nombre de cycles trouvé est variable si on exécute plusieurs fois les mêmes instructions et 2) il me parait très grand pour certaines exécutions. Tout cela reste à approfondir !!
Voici le projet complet !!

mardi 30 octobre 2018

Chapitre 50 : Raspberry Baremetal : afficher dessin et texte sur écran externe


 Voilà un an que j'écris sur ce blog sur l'assembleur arm pour le Rapsberry et c'est le 50ième post publié : ça s'arrose non !!

Dans ce post, nous allons voir comment utiliser le framebuffer à partir de la mailbox pour tracer des lignes et écrire du texte sur un écran relié au Raspberry par le câble HDMI.
Attention pour ces tests, j’utilise un écran  dont la définition est de 800 sur 480 et donc il vous faudra adapter certains paramètres à votre propre écran. Mon écran nécessite aussi un certain paramétrage qui doit être ajouter dans le fichier config.txt stocké sur la carte SD et qui est traité lors de l’initialisation du Raspberry par le GPU (voir la documentation sur le Raspberry).
Et comme nous savons lire maintenant des fichiers de la carte SD, j’ai ajouté dans les modules de ce projet, le module configuration.s qui permet la lecture de mon propre fichier de configuration et la récupération d’un paramètre (ici c’est debug). Ce fichier et ces paramètres bien que simples peuvent être complétés avec d’autres paramètres pour configurer vos propres modules.
Revenons à l’affichage sur l’écran. Pour cela j’ai ajouté le module framebuffer.s qui contiendra les routines nécessaires. Tout d’abord nous trouvons une routine d’initialisation qui configure le framebuffer pour les dimensions de l’écran et qui récupère les différentes informations nécessaires et en particulier l’adresse de la zone mémoire dans laquelle il faudra écrire les descriptions de chaque pixel de l’écran. Pour cela nous envoyons un message au firmware par l’intermédiaire de la mailbox. Vous remarquerez que par rapport à notre première approche de la mailbox au chapitre …  Nous envoyons la totalité des messages en une seule fois. Les codifications des messages sont stockées dans une table de 35 postes et miraculeusement cela fonctionne.
Ensuite, nous trouvons les routines de dessin d’un pixel, d’une droite horizontale et d’une verticale. Je vous laisse le soin de réécrire les routines vues au chapitre pour les dessins de rectangles vides et pleins, les cercles et les polygones.
Mais ici nous trouvons en plus une routine pour écrire du texte sur l’écran en utilisant un fichier binaire de l’exemple du tutoriel (en C) trouvé sur :
Merci à ce développeur pour cet apport. L’utilisation de cette police nécessite la récupération des informations stockées en début du fichier (fichier qui sera inclus dans notre code avec la pseudo instruction .incbin) à l’aide d’une structure (police décrite dans structures.inc). Si j’ai bien compris, cette police est une police utilisée par linux et il doit donc être possible de récupérer d’autres polices. Je laisse le soin aux courageux d’explorer cette possibilité.
Cette police donne pour chaque caractère (code < 80h) la description de chaque pixel suivant une image de 8 * 16 pixels. La routine décrite ici va récupérer pour chaque caractère cette description puis l’analyser et positionner dans la zone mémoire du framebuffer le code du pixel (soit noir pour le fond soit un code couleur pour le texte). Compte tenu des 3 boucles incluses l’une dans l’autre, j’ai essayé d’optimiser cette séquence mais il a fallu utiliser tous les registres en 2 routines pour cela.
Mais cela reste imparfait puisqu’il faut sauvegarder et restaurer 11 registres sur la pile à chaque caractère traité ce qui est couteux (une trentaine de cycles pour chaque instruction push et pop !! )
Vous remarquerez que comme je l’avais déjà indiqué lors de nos premières utilisations du Framebuffer, il faut trouver toutes les descriptions de polices pour des tailles et types différentes !!   bon courage à ceux qui veulent persister dans cette voie !!
Et voilà le projet ici.

jeudi 18 octobre 2018

Chapitre 49 : Baremetal Raspberry : écrire un fichier sur la carte SD


Dans ce projet, nous allons réaliser l’écriture d’un fichier sur la carte SD mais auparavant, nous allons reprendre la routine de lecture vue précédemment pour prendre en compte la présence de plusieurs clusters.
Dans un système de fichier  FAT32, un fichier est décomposé en clusters qui sont un ensemble de secteurs (blocs). Sur ma carte, un secteur contient 512 octets et un cluster 64 secteurs soit 32768 caractères. L’entrée du fichier dans le répertoire donne le N° du premier cluster. Si le fichier à une taille supérieure à 32768 caractères, les clusters supplémentaires nécessaires sont indiqués dans la table d’allocation des fichiers (FAT) se trouvant après les informations de la partition. J’ai eu un peu de mal à trouver cette table car il faut ajouter un nombre de secteurs réservés à l’adresse de la partition pour avoir l’emplacement du début de la table. Sur ma carte il y a 256 secteurs réservés mais ce chiffre est en hexadécimal et cette valeur bizarre m’a trompé plusieurs fois. Bref sur ma carte la table d’allocation commence au 2256h secteurs. Et le N° du cluster suivant est stocké sur 4 octets à la position indiqué par le N° du cluster précédant : c’est clair !!!
Prenons un exemple : un fichier a besoin de 2 clusters et le numéro du premier cluster stocké dans l’entrée est 5. Pour trouver le N° du 2iéme cluster il faut aller le chercher dans la table à la position 4* 5 soit 20 octets sur le secteur 2256h. Et on trouvera par exemple le N° du cluster 8 et il faudra aller chercher le suivant à la position 4 * 8 soit 32 octets sur le secteur 2256h. Et là on trouvera la valeur 0FFFFFFFh qui indique la fin de cette chaine. Un cluster libre est renseigné à 0.
 Ensuite avec chaque N° de cluster trouvé, il faut accéder aux données du fichier en calculant l’adresse comme suit :
Enlever 2 au N° du cluster trouvé (par exemple 5 -2 = 3), le multiplier par le nombre de secteurs par cluster (40h) et ajouter le tout à l’adresse du répertoire (sur ma carte c’est le secteur 4000) donc les données de ce cluster commence au secteur 4000h + (40h *3) = 40C0h et celui du cluster 8 au secteur 4000h + (40h *6) = 4180h.
Donc la routine de lecture précédente est modifiée pour balayer la chaine des clusters et lire les données de chaque cluster trouvé.
De plus j’en profite pour modifier la déclaration du buffer de lecture. En effet celui-ci sera déclaré par le module appelant et son adresse passée à la routine de lecture. Ainsi il sera possible de lire plusieurs fichiers. Vous remarquerez que contrairement à beaucoup de langage, nous n’avons pas de fonctions d’ouverture et de fermeture, et que nous lisons la totalité du fichier (il aurait été possible d’envisager une lecture bloc par bloc ou cluster par cluster).
Passons à l’écriture : Nous commençons par ajouter dans le module carteSd1.s une routine d’écriture d’un ou plusieurs blocs. Elle est pratiquement identique à la routine de lecture sauf le test du code de disponibilité de la carte et l’envoi des données du buffer vers la carte.
Dans le module repertoire.s, nous ajoutons les routines nécessaires à l’écriture. C’est compliqué car il y a plusieurs cas à gérer : le fichier existe déjà et dans ce cas si la nouvelle taille est supérieur à l’ancienne, il faut vérifier que cette taille ne nécessite pas un ou plusieurs clusters supplémentaires. Si c’est le cas, il faut parcourir la chaine des adresses des clusters pour trouver le dernier puis trouver des clusters vides et les ajouter à la chaine.
Attention : je n’ai pas programmé le cas où la taille est plus petite et qu’il faut libérer des clusters qui ne sont plus utilisés.
Ensuite, dans tous les cas, il faut écrire les données du buffer d’écriture dans les clusters et mettre à jour l’entrée du fichier dans le répertoire avec la nouvelle taille.
Si le fichier n’existe pas sur la carte, il faut tout créer : chercher un cluster vide dans la table d’allocation, allouer autant de clusters que nécessaire, écrire les données du buffer dans les clusters puis créer une entrée complète dans le répertoire principal.
Attention, je n’ai pas programmé l’écriture dans un sous répertoire !!! ni le retour arrière sur des données déjà écrites en cas d’erreur d’écriture d’une entité.
La création de l’entrée pose un petit problème : en effet il faut renseigner la date et l’heure de création et le timbre (timestamp) or le Raspberry ne comporte pas d’horloge interne alimentée par une pile pour conserver une date et une heure et nous n’avons pas d’accès à internet dans ce contexte baremetal pour y récupérer la date et heure Internet. Donc j’ai pris pour option de prendre la date et l’heure de l’entrée précédente !!!!!. Si vous continuez à développer dans ce contexte et si vous voulez avoir une date et une heure correcte, il vous faudra ajouter au Raspberry une carte horloge externe alimentée en permanence.
Je dois vous avouer que ces routines m’ont donné du mal à écrire et à mettre au point. Elles ne sont donc pas bien écrites et il faudrait les améliorer. L’utilisation de nombreux registres compliquent la compréhension de la programmation : il y a donc du travail à faire !!!
Pour tester tout cela, j’ai modifié le module kernel.s pour avoir des commandes plus évoluées comme :
                Lire nomdefichier  ou
                Ecrire nomdefichier1 nomdefichier2    (en fait c’est copier le fichier 1 dans le fichier 2)
                Ou  ecrire nomdefichier1 nomdefichier2   taille   pour tester les changements de taille.
J’ai aussi modifié la routine uart_send (dans le module mini_uart.s) pour vérifier les caractères envoyés car j’ai de temps en temps un redémarrage de window10 avec une erreur du driver prolific !!. Car je pensais que le problème venait de l’envoi de caractères non imprimables. Hélas cette correction n’est pas efficace !!!

Voilà voilà le projet complet.

jeudi 4 octobre 2018

Chapitre 48 : Baremetal lire un fichier sur la carte SD Partie 2 et 3


Maintenant que nous avons une routine pour lire un ou plusieurs blocs (ou secteurs) de la carte SD, il nous faut maintenant rechercher les informations sur le système de fichiers puis les informations permettant d’accéder au fichier recherché.
Attention : les contrôles de validité de la carte et du système de fichiers sont succincts !!. si nécessaire il faudra améliorer ces contrôles.
Avertissement : ce premier programme ne peut lire que des fichiers dont la taille est inférieure à 512 *64  = 32768 octets.
Comme la carte SD que nous utilisons est la carte de démarrage du Raspberry, le système de fichier est une FAT32 et donc je ne programmerais que l’accès à ses informations. Si vous voulez accéder à d’autres partitions, et bien il vous faudra rechercher les descriptions de chaque système et adapter ce projet en conséquence.  Tout le projet est ici.
Bien ! donc dans le module nouveau répertoire.s nous commençons à accéder au secteur 0 de la carte qui contient un code de vérification code  0x55AA à l’adresse 0x1FE et la table des partitions disponibles sur la carte. Sur ma carte il n’y a qu’une seule partition dont l’adresse est à la position 0x1C6.  Attention, il n’est pas possible de récupérer directement cette adresse dans un registre car elle n’est pas cadrée sur une frontière de mot et de plus toutes les adresses sont sous le format Little endian (voir les différents formats sur wikipedia).
Cette adresse est le nombre de secteurs de 512 octets avec laquelle nous allons accéder aux informations de la partition. Ces informations pour la FAT32 sont décrites dans le fichier structures.inc. Nous commençons par vérifier que la partition est bien une FAT32. C’est ici que vous devrez adapter la routine pour gérer d’autres types de partition. (voir sur internet la documentation pour les FAT12, FAT16, FAT32, NFTS et partition Linux)
Dans ces informations, nous trouvons les données qui vont nous permettre d’accéder au répertoire principal de la partition. Il faut effectuer un petit calcul, pour trouver le premier secteur de ce répertoire. Hélas en FAT32, il n’y a pas de donnée qui nous donne le nombre de secteurs à lire pour avoir tout le répertoire.
Le répertoire est composé d’entrées de 32 caractères qui donnent le nom court, l’extension, l’attribut, la date de création, le cluster de début du fichier et la taille en octet de chaque fichier stocké sur la carte (voir la description dans le fichier structures.inc).
Vous remarquerez que les fichiers supprimés ont leur premier caractère de leur entrée dans le répertoire avec la valeur E5 et donc on ne tient pas compte de ces entrées.
Dans la routine lecturerepertoire, nous nous contentons de balayer ces entrées et d’afficher le nom court, le cluster de début et la taille du fichier. Vous remarquerez que les noms sont affichés en majuscule alors que le gestionnaire de fichiers windows les affiche en minuscule. Et les noms longs sont convertis en un nom court ce qui va nous compliquer la tâche pour extraire un fichier particulier.
J’ai mis plus d’une semaine à trouver une anomalie bizarre lors de la lecture successive des blocs. En effet la deuxième lecture ne donnait pas le résultat attendu puis la 3ième lecture donnait un résultat. J’ai fini par trouver une erreur dans la routine de lecture des blocs vue au chapitre précédent. En effet à l’instruction 250 j’avais mis un test sur l’égalité infériorité de la récupération des 128 mots alors qu’il fallait tester l’infériorité stricte. Donc la routine lisait 129 mots de 4 octets, ce qui entrainait un décalage dans les lectures suivantes. Il faut donc rester très prudent sur les tests !!! (et je pense qu’il reste encore quelques erreurs subtiles dans ce code.
La 3ième routine consiste à rechercher un fichier dans le répertoire et d’afficher le début de son contenu. Dans celle-ci nous commençons par lire le répertoire principal et à stocker tous les secteurs en mémoire ! Cela est nécessaire car la gestion des noms longs oblige à effectuer une recherche vers le haut des entrées puis de revenir pour récupérer les caractères du nom long. Cette gestion complexe a été faite par Microsoft pour assurer le bon fonctionnement des logiciels de lecture de fichiers sous Msdos (noms courts limités à 8+3 caractères) puis sous Windows (noms longs < 256 caractères) il y a très très longtemps !!!.  
A partir de l’entrée du répertoire, l’attribut du fichier indique si le nom est court ou s’il est long. S’il est court, nous appelons une sous routine pour récupérer le nom complet en minuscule avec l’extension. S’il est long, nous appelons une autre sous routine qui va rechercher dans les entrées suivantes, la première entrée contenant le début du nom long puis remonter pour composer le nom au fur et à mesure. Vous remarquerez que chaque caractère est en minuscule codé sur 2 caractères (norme UTF-8).
Ensuite nous comparons le nom trouvé dans le répertoire et le nom recherché. Si égalité nous calculons le secteur de début puis à partir de sa taille le nombre de secteurs à lire. Pour trouver le secteur de début, il faut extraire le nombre de clusters qui se trouve sur 4 octets mais divisé en 2 octets se trouvant à 2 endroits différents !!! Et un cluster est un ensemble de secteurs et il nous faut donc multiplier ce nombre de clusters par la taille de chaque cluster et qui se trouve dans une donnée de la partition (ici nous trouvons 0x40 soit 64 secteurs par cluster). Ah oui, il faut aussi enlever 2 clusters au nombre de clusters trouvés (voir la documentation sur la structure d’un répertoire FAT32).
L’accès au fichier s’effectue ici correctement. Il y aura peut-être des cas où cela se passera moins bien car je n’ai peut-être pas vu dans ces calculs des options à prendre en compte. Par exemple la documentation de la FAT32 parle d’une table des clusters utilisés par les fichiers or  à ce jour je ne suis pas encore arrivé à trouver cette table. Ou elle est vide !! car peut être aucun fichier de la carte ne dépasse 1 cluster soit 64 secteurs soit 32768 caractères !! Ce n’est pas encore très clair pour moi mais j'y travaille.
Il restera encore à traiter le cas des répertoires secondaires !!   et puis l’écriture d’un fichier sur la carte et ça c’est du dur !!!