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

samedi 1 septembre 2018

Chapitre 43 : Baremetal: affichage des registres et de la mémoire


Dans ce projet, nous allons préparer l’affichage des registres et de la mémoire grâce à la mini uart dont nous avons testé le fonctionnement au chapitre précèdent. En fait il nous suffit de récupérer les procèdures vues dans les premiers chapitres de ce blog et de remplacer les appels système d’affichage par des appels à la procedure d’envoi des chaines par la mini uart.
Nous stockons ces procédures dans le module util2.s et nous en profitons aussi pour écrire une fonction de lecture de chaine de caractère en utilisant la fonction de lecture d’un caractère de la miniuart. Cette fonction restera assez simple et ne prendra pas en compte l’affichage d’un curseur, le déplacement arrière de celui-ci dans la chaine pour insérer un corriger un caractère. Elle ne prend en compte que le retour arrière pour supprimer le dernier caractère saisie. Nous ajoutons aussi une procédure de comparaison de chaine que nous stockons dans le module utilchaine.s
Dans le module kernel.s, nous ajoutons le test des affichages des registres et de la mémoire puis nous créons une boucle de saisie des commandes. Pour cela nous affichons un message d’invite puis appelons la procédure de saisie d’une chaine puis la procèdure de comparaison pour déterminer quelle commande a été saisie. Ici il n’ a que la commande fin de possible.
Nous effectuons aussi une autre modification du fichier linker.ld pour modifier l’adresse de début d’exécution pour la mettre à la valeur 0X8000 , valeur indiquée dans de nombreux exemples sur Internet. Mais attention il faut indiquer au GPU cette nouvelle adresse. Il faut donc dans le fichier config.txt remplacer l’instruction kernel_old=1 par l’instruction kernel_address=0x8000.
Maintenant il ne reste plus qu’à relancer la chaine de compilation par make, corriger les erreurs éventuelles, copier le fichier kernel.img sur la carte SD (attention pas celle de votre linux habituel mais celle préparée au chapitre précèdent), placer cette dernière dans le Raspberry, brancher le câble TTL USB au micro ordinateur, lancer putty connexion série et allumer le Raspberry.
Vous devez avoir ce résultat :



Vous pouvez taper quelques commandes !! pour voir l’affichage mémoire du buffer puis la commande fin pour avoir les messages finaux. Bien sûr vous pouvez les adapter à votre gout.
A l’adresse 80D8, j’ai le vidage du registre d’état avec les 5 premiers bits qui indique le mode actuel du processeur soit la valeur 10011 correspondant au mode superviseur : intéressant non ? Pour les autres affichages on voit que l’adresse de la data et de bss correspondent bien au plan de chargement. Cela va nous faciliter la vie !!
Tout le projet est à récupérer ici.