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

dimanche 30 septembre 2018

Chapitre 47 : Baremetal : lire un fichier sur la carte SD. Partie 1


Maintenant, nous allons nous attaquer à un gros morceau : la lecture d’un fichier de la carte SD en assembleur. En effet, si nous voulons continuer à écrire des programmes en assembleur sans faire appel à Linux, il nous faut arriver à lire et à écrire des données sur le seul support fixe dont nous disposons : la carte SD.
Ce projet va se décomposer en 3 parties : la lecture de bas niveau d’un bloc de données de la carte, l’exploitation des données des répertoires pour trouver l’emplacement du fichier, puis la lecture du fichier lui-même.
Pour la première partie, nous allons nous servir du périphérique EMMC du BCM2835. Hélas, la documentation de ce périphérique n’est pas complète et renvoi à un document de la société Arasan qui n’est pas disponible sur le net.
Heureusement, le consortium qui définit l’utilisation des cartes SD publie une documentation importante sur les protocoles à utiliser. Donc grâce à la doc du BCM2835, des docs de la carte SD, et d’exemples de programmes en C trouvés sur le net, je suis arrivé à écrire des routines qui fonctionnent plus ou moins. Pourquoi le moins : parce qu’il y a une grande variété de cartes, de commandes, d’états, de possibilités qu’il m’est impossible de tester tout cela sur mon raspberry avec un seul type de carte. Ce qui veut dire que ces routines seront à adapter suivant vos processeurs et type de cartes et les actions que vous souhaiteriez développer.
Vous me pardonnerez aussi l’imprécision de mes explications, mais certains points me restent encore obscurs !!!
Tout le projet est ici.
Dans cette première partie, nous allons lire un bloc de données brut de la carte SD à l’aide de routines écrites dans le module carteSD.s. Pour cela nous devons procéder à l’initialisation de la carte qui va se décomposer aussi en 3 parties : l’initialisation des pins du GPIO, l’initialisation de l’horloge et l’initialisation de la carte à l’aide de commandes particulières. En effet, une carte SD possède un micro contrôleur qui va dialoguer avec le driver maitre EEMC à l’aide de commandes que nous allons envoyer pour attendre une réponse qu’il faudra analyser.
Comme nous avons pu le voir précédemment pour le GPIO ou le timer, les périphériques sont gérés à partir de registres (en fait des zones particulières de 32 bits situées à des adresses bien particulières et indiquées dans la doc du BCM2835). Ces registres seront soit alimentés par nos routines pour lancer une commande avec ses arguments, soit lus pour déterminer l’état du périphérique (ok, en attente ou en erreur) ou récupérer (ou écrire) des données. Mais attention, les réponses du périphérique ne sont pas synchrones et il faudra souvent prévoir un temps d’attente entre l’écriture dans un registre de commande et la lecture de la réponse. Pour vérifier le contenu de ces registres, j’ai écrit une fonction dans le module util.s qui affiche le contenu en hexa et binaire.
Commençons à attribuer la fonction 0b111 aux pins du GPIO pour la liaison avec la carte SD :
Pin 47 :   pin CD de la carte (Card Detect) (voir la documentation SD.pdf)
Pin 48 :  pin clk (Clock)
Pin 49 : pin CMD (command)
Pin 50, 51, 52 et 53 : pin Dat0 Dat1 Dat2 Dat3
Ensuite on met à jour les registres GPPUD et GPPUDCLK1 en suivant la démarche indiquée dans la doc BCM2835. Je n’arrive pas trop à comprendre à quoi ça sert !!!!!
Nous récupérons le numéro de slot de la carte SD dans le registre EMMC_SLOTISR_VER et nous le conservons en mémoire. Puis nous effectuons un reset de la carte SD en positionnant les valeurs adhoc dans les registres EMMC_CONTROL0 et EMMC_CONTROL1. Et nous trouvons une séquence que nous allons retrouver plusieurs fois. Il s’agit d’une boucle d’attente qui teste les codes retour attendus. Si ces codes ne sont pas trouvés au bout d’un certain temps, une erreur est signalée. Un code retourné peut aussi signaler une erreur ou un problème de la carte.
Nous passons maintenant à un réglage de l’horloge à 400Khz. Je vous laisse le soin de regarder le code en détail mais qui est assez compliqué. Puis nous attaquons toute la séquence d’initialisation de la carte SD avec le lancement de plusieurs commandes et la vérification de leur bonne fin. Pour le rôle de chaque commande, je vous renvoie à la documentation de base sd.pdf. Attention, j’ai copié le nom des commandes d’un programme en C et ce nom ne correspond pas à celui de la documentation. Je l’ai ajouté pour certaines en commentaire.
Vous remarquerez l'utilisation de l'instruction tst qui permet de tester certains bits d'un registre sans le modifier (c'est comme un and sans modification)  mais attention à ne pas inverser le test : si les bits sont présents il faut utiliser un bne (not equal) et pas un beq !!
Au milieu de cet enchainement, il faut rechanger la fréquence d’horloge pour la passer à 25Mhz mais sur mon raspberry, cela entraine des erreurs sur les commandes suivantes. J’ai dû me contenter de mettre 20Mhz comme fréquence pour avoir un résultat sans erreur.
Lors du retour de cette routine d’initialisation, il faut tester la valeur du registre r0 pour arrêter la suite du programme en cas d’erreur.
Ici après cette séquence, nous nous contentons de lire le seul bloc 0 avec la routine lecture_bloc_sd et de stocker les données dans un buffer en mémoire. Nous affichons ce buffer à partir du 500 caractère pour vérifier que la marque du système de fichier est bien un fat32 ( code  0x55AA à l’adresse 0x1FE).
Remarque importante : il est possible de lire plusieurs blocs de 512 caractères d’affilée. La documentation précise que dans ce cas, il faut terminer la lecture par l’envoi de la commande CMD_STOP_TRANS. Lors de mes tests, cette commande est systématiquement en erreur sans que je trouve pourquoi !! je l’ai donc mise en commentaire car les blocs sont lus correctement.

samedi 22 septembre 2018

Chapitre 46 : Baremetal : clignotement de la led en mode user


Dans le chapitre précédent, nous avons vu que l’instruction qui permettait l’attente d’une interruption n’était valide qu’en mode SVC superviseur. Pour voir le clignotement de la Led en mode User j’ai modifié le projet précédent pour appeler la procédure de gestion de la LED depuis la procédure test_interruption_timer. Mais en fait dans cette procédure nous sommes en mode IRQ puisque c’est la routine de gestion de l’exception irq initié par l’horloge du timer qui l’a appelée.
Donc dans cette routine nous allons nous contenter de gérer un top qui à chaque interruption va le basculer de 0 en 1 puis de 1 en 0. Et dans la procédure de gestion de la led, nous allons dans une boucle tester la valeur de ce top. S’il est égal à zéro, nous éteindrons la led et s’il est égal à 1 nous l’allumerons. Nous effectuerons ce processus 5 fois. Et comme cette procédure est appelée par la routine kernel, nous resterons en mode user.
Vous remarquerez que le top led est utilisé dans 2 modules différents. C’est pourquoi il est déclaré dans le module kernel comme global. Si vous regardez le plan de chargement (dans map1) vous le verrez apparaitre alors que les autres données n’y figurent pas.
Nous en profitons pour améliorer la gestion des autres exceptions en ajoutant un message pour les signaler et nous prenons comme options par défaut, d’effectuer un reset du Raspberry pour éviter un blocage du processeur.
Pour tester un cas d’exception, nous ajoutons une commande test qui effectuera un appel à une routine traitementTest dans laquelle nous générons volontairement une erreur en dépilant un nombre différent de registres que nous avons empilés.
Les tests étant concluants, tout le projet est à récupérerici.
Remarque 1 : les autres exceptions n’ont pas été testées et donc il faudra surveiller les reset intempestifs du Raspberry.

lundi 17 septembre 2018

Chapitre 45 :Clignotement de la led activité du Raspberry avec le timer du BCM2835


Bon ! il faut bien un jour se lancer dans la compréhension et l’exploitation des interruptions. Cela n’a pas été facile mais le projet que je vais vous présenter fonctionne correctement sur mon raspberry(mais j’ai encore de nombreuses interrogations non résolues à ce jour). Nous allons nous servir du timer ARM du BCM2835 (attention il y a plusieurs timers et nous utiliserons celui de la page 196 de la doc du BCM2835) pour générer une interruption à intervalle régulier (environ 3s) pour allumer et éteindre la led activité suivant le même principe que le chapitre précédent.
La théorie est simple !! : le timer génère toutes les 3 secondes une interruption qui sera gérée par un contrôleur d’interruption puis prise en compte par le processeur qui changera de mode de fonctionnement et appellera la bonne routine de traitement de l’interruption.
Donc nous aurons un module qui contiendra les routines du timer, un autre celles du contrôleur d’interruption (IC) un autre la gestion des adresses à appeler suivant le type et le mode (appelé vecteur des interruptions) et bien sûr les modules boot.s et kernel.s comme dans les exemples précédents. Tout le projet est ici.
Dans le module timer, nous trouvons une fonction d’initialisation du timer qui fixe la fréquence, et qui autorise l’envoi d’une interruption. J’ai eu du mal à calculer correctement la fréquence en effet il faut diviser la fréquence de base de l’horloge soit 250 000 000 hertz par le facteur de division +1 (ici 7F  soit 127 + 1 = 128) ce qui donne une fréquence de 1 953 125hz qu’il faut diviser par 256 (indiqué par le code 10 se trouvant sur les bits 2et 3 du registre de contrôle du timer) ce qui donne 7629 hz. Le compteur à une valeur de 22887 ce qui donne un temps de 22887/7629 = 3 secondes
Nous trouvons aussi une fonction de test de l’interruption et de sa remise à zéro.
Dans le module du Contrôleur d’Interruption (IC page 109 doc BCM2835) nous trouvons des fonctions simples d’initialisation, d’activation, de recherche de l’identification de l’interruption et de la fin de l’interruption.
Voyons la partie la plus compliquée : la mise en place du vecteur d’interruption module interruptions.s
Le processeur ARM peut fonctionner dans plusieurs modes : le mode user que nous avons utilisé par défaut dans tous les premiers chapitres de ce blog, le mode superviseur que nous utilisons depuis que nous développons sans OS, puis les modes abort, indéfini, irq et fiq. Pour ces derniers modes, le processeur y passera en fonction du type d’interruptions reçues : instruction non autorisé, problème mémoire, interruptions générés par les périphériques (irq=normale, Fiq= rapide ). Notre timer génère une interruption de type irq.
Pour chaque mode, nous devons prévoir la procédure à appeler lors d’une interruption. Pour cela le processeur se branche sur les adresses en début de la mémoire. Dans notre module, nous commençons donc par recopier une table des adresses des procédures pour tous les modes. Bien sûr pour les modes autre que irq, nous mettons le minimum d’instructions à exécuter (quoiqu’il vaudrait mieux envoyer un message pour indiquer ce qui se passe !!!).
Pour écrire ce module, je me suis servi d’un exemple trouvé dans un livre et cela commence par une instruction que je n’avais jamais vue : mcr p15,0,r0,c12,c0,0 !!!!  Dans la documentation ARM, on trouve d’autres instructions de ce type !! qui servent à piloter des fonctions très particulières du processeur. J’ai mis cette instruction en commentaire car elle est peut être utile pour d’autres processeurs. Ensuite nous trouvons la recopie à l’adresse zéro de la mémoire de la table des adresses d’interruptions puis pour chaque mode la procédure à exécuter. Vous remarquerez qu’il y a une procédure reset qui appellera notre première routine _start.
Vous remarquerez qu’à la place de push et pop, nous utilisons les instructions stmfd et ldmfd qui font la même chose. Remarquez que les adresses de retour au programme appelant sont différentes suivant le mode.
Enfin, nous ne complétons que la fonction irq_handler en appelant les fonctions du contrôleur d’interruptions ou du timer pour gérer l’interruption.
Ce module sera appelé dans le module boot_s car le vecteur d’interruptions doit être chargé au plus tôt.
Ensuite il nous reste à modifier le module kernel.s pour modifier l’allumage et l’extinction de la led ; Pour cela on utilise l’instruction mcr p15,0,r0,c7,c0,4 qui met le processeur en attente d’une interruption. Hélas, je me suis aperçu que cette instruction ne fonctionne qu’en mode superviseur. Si on veut utiliser le mode user, il faudra modifier la partie allumage extinction pour être appelée de la fonction irq_handler ou de la fonction test_interruption_timer. Je vais essayer cela dans le prochain post.
La mise au point de tout cela a été laborieuse car en cas de non fonctionnement, il est pas facile de savoir quelle partie est en cause : est ce le timer qui ne génère pas l’interruption ou le contrôleur qui ne la pas autorisé ou le handler qui fonctionne mal ?  Donc prudence lors des modifications. De plus lors de mes premiers tests, j’ai réussi à planter mon portable sous windows10 (soit 10 minutes au moins pour redémarrer !! ) malgré la liaison série entre les deux. Je pense que la procédure mini_uart d’envoi de caractère ne vérifiant pas la validité des caractères, j’ai du envoyer des cochonneries qui ont fait planter window10 !!!
Mes explications sur le sujet des interruptions étant simplistes, je vous renvoie à toute la documentation sur internet traitant des interruptions des processeurs ARM

mercredi 5 septembre 2018

Chapitre 44: Baremetal : faire clignoter la Led Activité du Raspberry


Maintenant, qu’aucune contrainte de l’OS ne nous bloque, nous pouvons facilement faire clignoter la led d’activité (led verte ). Les autres led du Raspberry modèle 1 ne peuvent pas être utilisées car elles indiquent la présence de l’alimentation électrique, la connexion Ethernet et sa vitesse (10 ou 100 Mbts) et ne sont donc pas programmables.
La led activité est accessible à travers la programmation du pin GPIO N° 16 pour mon raspberry (modèle 1B).
Dans le module kernel.s, nous ajoutons un test sur la commande led pour appeler la routine traitementLed qui allume et éteint la led 5 fois.
Il est possible d’améliorer le programme pour faire clignoter la led à un autre rythme. Le délai d’allumage et d’extinction s’effectue à travers une simple boucle.
 Pour la prochaine fois, je vais essayer de me servir du timer du BCM2835 en faisant appel aux interruptions.
Tout le projet est à récupérer ic