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 !!!