wiki:AS6-TME-B2

Version 21 (modified by franck, 3 years ago) (diff)

--

Gestion des interruptions

Gestionnaire d'interruptions

Rappel de cours

Il est fortement recommandé de lire les transparents, toutefois, mais nous avons mis ci-après quelques rappels utiles pour répondre aux questions du TD.

Dans cette séance, nous allons manipuler 3 contrôleurs de périphériques: Le TTY que vous connaissez déjà et deux autres, l'ICU et le TIMER. Ces trois contrôleurs s'utilisent grâce à des registres mappés (placés) dans l'espace d'adressage du MIPS. Les registres du TTY sont placés à partir de l'adresse 0xd0200000, ceux de l'ICU à partir de l'adresse 0xd2200000 et enfin ceux du TIMER à partir de l'adresse 0xd3200000. Le rôle de ces registres est rappelé en partie dans ce texte et pour plus de détails, vous devez revoir le cours. Le choix des adresses de ces contrôleurs est fait par le créateur du matériel, elles ne peuvent pas être changées par le logiciel. Ces adresses sont données dans le fichier ldscript du kernel (kernel.ld) parce qu'elles ne sont utilisables que si le MIPS est en mode kernel (adresses > 0x80000000).

device_registers.png

Les IRQ (Interrupt ReQuest)s sont des signaux électriques à 2 états (ON/OFF ou Actif/Inactif ou encore Levé/Baissé). Les IRQ sont levés par les contrôleurs de périphériques pour prévenir d'un événement (fin de commande, arrivée d'une donnée, etc.). Les IRQs provoquent l'exécution d'ISR (Interrupt Service Routine) par le noyau. Les ISR sont des fonctions qui reçoivent en argument un identifiant du contrôleur de périphérique qui a levé l'IRQ. Une ISR doit faire deux choses, (1) accéder aux registres du contrôleur de périphérique concerné pour faire ce que le périphérique demande et (2) acquitter l'IRQ, c'est-à-dire demander au contrôleur de périphérique de baisser/désactiver son IRQ (puisque celle-ci a été traitée). La demande d'acquittement est spécifique à chaque contrôleur de périphérique. Pour le TTY, il faut lire le registre TTY_READ. Pour le TIMER, il faut écrire dans le registre TIMER_RSTIRQ.

Les IRQ sont des signaux d'état qui doivent rester levés/activés tant qu'ils n'ont pas été acquittés par une ISR. Quand une IRQ se lève, la conséquence est que le programme en cours d'exécution sur le processeur recevant l'IRQ est interrompu et qu'il est dérouté vers le noyau pour que ce dernier exécute l'ISR prévue pour l'IRQ. Notez que ce n'est pas le processeur qui est interrompu, c'est bien le programme, car le processeur est seulement dérouté vers le noyau, mais il continue à travailler.

Sur le schéma de la plateforme des TP, on peut voir que seuls les composants TTY et TIMER peuvent lever des IRQ. Les IRQ de ces contrôleurs de périphériques sont envoyés au composant ICU qui va les combiner pour produire un unique signal IRQ pour le processeur.

Archi_TP_B2.png

Une IRQ peut être masquée, c'est-à-dire que le processeur ne va pas interrompre le programme en cours. Le masquage peut être demandé à plusieurs endroits : dans le composant ICU et dans le processeur lui-même. Le masquage est demandé par le noyau, le plus souvent de manière temporaire, quand il doit exécuter un code critique qui ne doit surtout pas être interrompu.

IRQ_routage.png

Sur le schéma ci-dessus, on voit que l'IRQ du TTY0 est reliée à l'entrée n°10 de l'ICU, c'est un choix matériel qui n'est pas modifiable par logiciel. Son état est donc enregistré dans le bit n°10 du registre ICU_STATE. Il y a un AND avec le bit 10 du registre ICU_MASK. Si le bit 10 du registre ICU_MASK est à 0, alors la sortie du AND est 0 et l'IRQ est masquée (donc invisible pour le processeur). Le registre ICU_HIGHEST contient toujours le numéro de l'IRQ active la plus prioritaire, comme il n'y en a qu'une dans cet exemple, ICU_HIGHEST contient 10 (l'IRQ prioritaire, pour cette ICU, est l'IRQ active dont le numéro est le plus petit). L'IRQ de l'ICU est reliée à l'entrée 0 des 6 IRQs possibles du MIPS et sa valeur s'inscrit dans le registre HWI0 du registre c0_cause. Il y a un AND avec le bit HWI0 du registre c0_status. Si le bit HWI0 du registre c0_status est à 0, alors la sortie du AND est 0 et l'IRQ est aussi masquée. Enfin, il y a un dernier AND avec le bit 0 de c0_status (correspondant au bit IE pour Interrupt Enable) qui permet de masquer globalement les IRQ et avec le NOT du bit 1 de c0_status (correspondant au bit EXL EXception Level).

Quand le signal IRQ vue par le MIPS s'active (passe à 1), c'est que l'IRQ levée par le contrôleur de périphérique doit être prise en charge. Le programme en cours d'exécution est interrompu et dérouté vers kentry à l'adresse 0x80000180 et en même temps C0_EPC ← PC+4, c0_cause.XCODE ← 0, c0_status.EXL ← 1. Notez que le nom officiel de c0_status est C0_SR, mais dans ce document, on utilise c0_status pour plus de clarté.

Dans le schéma ci-après, à gauche c'est le matériel et à droite c'est un extrait de la RAM contenant les structures de données utilisées par le noyau pour la gestion des IRQ.

  • À gauche, on voit que les IRQ venant des contrôleurs de périphériques sont connectés aux entrées d'IRQ de l'ICU. Il y a 32 entrées possibles. Sur notre plateforme, par exemple l'IRQ du TTY2 est connectée à l'entrée 12 de l'ICU. Ce numéro d'entrée est le numéro qui identifie le contrôleur de périphérique. Notez que le registre ICU_MASK est en lecture seul, c'est-à-dire qu'il ne peut pas être écrit directement. Pour modifier le contenu du registre ICU_MASK, il faut utiliser deux autres registres de l'ICU: ICU_SET et ICU_CLEAR. ICU_SET permet de mettre à 1 les bits de ICU_MASK, et ICU_CLEAR permet de les mettre à 0. Pour mettre à 1 le bit i du registre ICU_MASK, il faut écrire 1 dans le bit i du registre ICU_SET. Pour mettre à 0 le bit j du registre ICU_MASK, il faut aussi écrire 1, mais dans le bit j du registre ICU_CLEAR.

  • À droite, il y a les deux tableaux que le noyau utilise pour connaitre l'ISR à exécuter pour chaque numéro d'IRQ. Ce couple de tableaux se nomme vecteur d'interruption et comme il y a 32 entrées d'IRQ dans l'ICU, ces tableaux ont 32 cases chacun. Ici, le vecteur d'interruption est composé des tableaux IRQ_VECTOR_ISR[] et IRQ_VECTOR_DEV[]. Le vecteur d'interruption est indexé par les numéros d'IRQ. Il contient deux informations: (1) dans la case n°i du tableau IRQ_VECTOR_ISR[], on trouve le pointeur sur la fonction ISR à appeler si l'IRQ n°i est levée, et (2) dans la case n°i du tableau IRQ_VECTOR_DEV[], on trouve le numéro de l'instance du périphérique. Cette dernière information est nécessaire dans le cas des contrôleurs de périphérique multi-instances comme le TTY afin de savoir quel jeu de registres la fonction ISR doit utiliser. En effet, il y a une fonction ISR unique à exécuter quel que soit le numéro du TTY, l'adresse de cette fonction est placée dans les cases 10, 11, 12, et 13 du tableau IRQ_VECTOR_ISR[] (si on a 4 TTYs) et dans les cases 10, 11, 12, et 13 du tableau IRQ_VECTOR_DEV[], on a les valeurs 0, 1, 2 et 3 qui correspondent bien au numéro d'instance des TTYs.

IRQ_VECTOR.png

Enfin, nous rappelons les 3 registres du coprocesseur système (c0) qui sont utilisés au moment de l'entrée dans le noyau, quelle que soit la cause : syscall (vu la semaine dernière), interruption (TD de cette semaine) et exception (dans le cas de problèmes lors de l'exécution du programme comme la division par 0). On rappelle aussi que les seules instructions qui peuvent manipuler ces registres sont mtc0 et mfc0 pour, respectivement, les écrire et les lire.

C0_registers.png

Les bits HWI0 des registres c0_status (aussi nommé c0_sr) et c0_cause contiennent respectivement le mask et le l'état de l'entrée n°0 d'interruption du MIPS. Les bits UM, IE et EXL sont liés au mode d'exécution du MIPS: UM est le bit de mode du MIPS (1=User Mode, 0=Kernel Mode), IE est le bit de masque général des interruptions (1=autorisées, 0=masquées) et enfin EXL est le bit que le MIPS met à 1 à l'entrée dans le noyau pour informer d'un niveau exceptionnel et dans ce cas les bits UM et IE ne sont plus significatifs, si EXL est à 1 alors le MIPS est en mode kernel, et les interruptions sont masquées.

Questions

La majorité des réponses aux questions ci-après sont dans le rappel du cours donné au début de cette page, c'est voulu.

  1. À quelles adresses dans l'espace d'adressage sont placés les registres des 3 contrôleurs de périphériques de la plateforme et comment le kernel les connaît ?
  2. Que signifie l'acronyme I.R.Q. ?
  3. Une IRQ est un signal électrique, combien peut-il avoir d'états ?
  4. Qu'est-ce qui provoque une IRQ ?
  5. Les IRQ relient des composants sources et des composants destinataires, quels sont ces composants ? Donnez un exemple.
  6. Que signifie masquer une IRQ ?
  7. Quels composants peuvent masquer une IRQ ?
  8. Est-ce qu'une application utilisateur peut demander le masquage d'une IRQ ?
  9. Que signifie l'acronyme I.S.R. ?
  10. Dans la plateforme des TPs, sur quelles entrées de l'ICU sont branchées les IRQ venant des TTYs et du TIMER ?
  11. Quelle valeur faut il avoir dans le registre ICU_MASK si on veut recevoir seulement les IRQ venant des 4 TTYs, dans le cas de la plateforme utilisée en TP ? Donnez le nombre en binaire et en hexadécimal.
  12. L'écriture dans ICU_MASK n'est pas possible, comment modifier ce registre pour mettre à 1 le bit 0 ?
  13. Sur une plateforme (autre que celle des TP) sur laquelle on aurait un TTY0 sur l'entrée 5, un TIMER sur l'entrée 2, et un autre TTY1 sur l'entrée 14. Que doit-on faire pour que seuls le TTY1 et le TIMER soient démasqués et que TTY0 soit masqué ?
    Si les 3 IRQ se lèvent au même cycle, quelles seront les valeurs des registres ICU_STATE, ICU_MASK et ICU_HIGHEST ?
  14. Dans quel mode est le processeur quand il traite une IRQ ?
  15. Que fait le processeur lorsqu'il reçoit une IRQ masquée ?
  16. Que signifie acquitter une IRQ ?
  17. Qui demande l'acquittement à qui ?
  18. Comment demande-t-on l'acquittement ?
  19. Est-ce qu'une IRQ peut se désactiver sans intervention du processeur ?
  20. Est-ce qu'une IRQ peut ne pas être attendue par le noyau ?
  21. Quelle est la valeur du champ XCODE du registre c0_cause à l'entrée dans le noyau en cas d'interruption ?
  22. Quelle est la valeur écrite dans le registre c0_EPC à l'entrée dans le noyau en cas d'interruption ?
  23. Que se passe-t-il dans le registre c0_status à l'entrée dans le noyau en cas d'interruption et quelle est la conséquence ?
  24. La routine kentry (entrée du kernel à l'adresse 0x80000180) appelle le gestionnaire d'interruption quand le MIPS reçoit une IRQ non masquée, que fait ce gestionnaire d'interruption ?
  25. À l'entrée dans le noyau, kentry analyse le champ XCODE du registre de c0_cause et si c'est 0 alors il saute au code donné ci-après (ce n'est pas exactement le code que vous pouvez voir dans les fichiers sources pour que ce soit plus facile à comprendre).
    cause_irq:
        addiu   $29,    $29,    -23*4       // 23 registers to save (18 tmp regs+HI+LO+$31+EPC+SR)
        mfc0    $27,    $14                 // $27 <- EPC (addr of syscall instruction)
        mfc0    $26,    $12                 // $26 <- SR (status register)
        sw      $31,    22*4($29)           // $31 because, it is lost by jal irq_handler
        sw      $27,    21*4($29)           // save EPC (return address of IRQ)
        sw      $26,    20*4($29)           // save SR (status register)
        mtc0    $0,     $12                 // SR <- kernel-mode without INT (UM=0 ERL=0 EXL=0 IE=0)
        sw      $1,     1*4($29)            // save all temporary registers including HI and LO
        sw      $2,     2*4($29)            
        [etc. pour les autres sauvegardes des registres temporaires]                              
    
        jal     irq_handler                 // call the irq handler fontion écrite en C
    
        lw      $1,     1*4($29)            // restore all temporary registers including HI and LO
        lw      $2,     2*4($29)
        [etc. pour les autres restaurations des registres temporaires]   
        lw      $26,    20*4($29)           // get old SR
        lw      $27,    21*4($29)           // get return address of syscall
        lw      $31,    22*4($29)           // restore $31
        mtc0    $26,    $12                 // restore SR
        mtc0    $27,    $14                 // restore EPC
        addiu   $29,    $29, 23*4           // restore the stack pointer
        eret                                // jr C0_EPC AND C0_SR.EXL <= 0
    
    Pourquoi, ne pas sauver les registres persistants ?
  26. La fonction irq_handler() a pour mission d'appeler la bonne ISR. Dans le code qui suit (extrait du fichier kernel/harch.c), on voit d'abord la déclaration de la structure qui décrit les registres présents dans l'ICU. En fait c'est un tableau de structures parce qu'il y a autant d'instances d'ICU que de processeurs (donné par NCPUS), ici, il y a un seul processeur MIPS, donc NCPUS=1.
    struct icu_s {
        int state;          // state of all IRQ signals
        int mask;           // IRQ mask to chose what we need for this ICU
        int set;            // IRQ set   --> enable specific IRQs for this ICU
        int clear;          // IRQ clear --> disable specific IRQs for this ICU
        int highest;        // highest pritority IRQ number for this ICU
        int unused[3];      // these 3 registers are not used
    }; 
    extern volatile struct icu_s __icu_regs_map[NCPUS];
    
    static int icu_get_highest (int icu) {
        return __icu_regs_map[icu].highest;
    }
    
    static void icu_set_mask (int icu, int irq) {
        __icu_regs_map[icu].set = 1 << irq;
    }
    
    void irq_handler (void) {
        int irq = icu_get_highest (cpuid());       
        irq_vector_isr[irq] (irq_vector_dev[irq]);
    }
    
    
    La déclaration extern volatile struct icu_s __icu_regs_map[NCPUS]; informe le compilateur que le symbole __icu_regs_map est défini ailleurs et que c'est un tableau de structures de type struct icu_s. Ainsi, le compilateur gcc sait comment utiliser la variable __icu_regs_map.

    Dans quel fichier est défini __icu_regs_map ?
    Que font les fonctions icu_get_highest(), icu_set_mask() et irq_handler()?
    Comment s'appelle le couple de tableaux irq_vector_isr[irq] et irq_vector_dev[irq] ?
    Combien ont-il de cases ?
  27. Si ICU_HIGHEST contient 10 (dans le cas de notre plateforme) que doit faire la fonction irq_handler()
  28. Que fait la fonction icu_set_mask (int icu, int irq) ?
  29. Les registres du TIMER sont définis dans le code du noyau de la façon suivante :
    struct timer_s {
        int value;          // timer's counter : +1 each cycle, can be written
        int mode;           // timer's mode : bit 0 = ON/OFF ; bit 1 = IRQ enable
        int period;         // timer's period between two IRQ
        int resetirq;       // address to acknowledge the timer's IRQ
    };
    extern volatile struct timer_s __timer_regs_map[NCPUS];
    
    Écrivez le code de la fonction static void timer_init (int timer, int tick) qui initialise la période du timer n° timer avec l'entier nommé tick et active les IRQ si la période donnée est non nulle.
  30. La configuration des périphériques et des interruptions est faite dans la fonction arch_init() appelée par kinit().
    Écrivez les instructions C permettant d'ajouter le TIMER dans le noyau avec un tick de 1000000 (1 million de cycles). Il faut (1) initialiser le timer ; (2) démasquer l'IRQ venant du timer dans l'ICU, elle connectée sur son entrée n°0 ; (3) initialiser le vecteur d'interruption avec la fonction timer_isr pour ce timer 0.

Questions de cours

Les questions de cette partie ont pour objectif de vérifier que vous avez compris le cours. Les réponses sont donc dans les slides du cours sur les interruptions. L'ordre des questions suit globalement celui des slides du cours. Il y a beaucoup de questions, mais la plupart sont simples, certaines sont plus délicates. Ce n'est pas grave, si vous ne savez pas tout, mais vous devriez être en mesure de répondre à un grand nombre.

  1. À quoi servent les interruptions ?
  2. Une interruption en informatique est à la fois une suspension temporaire d'un programme et un signal électrique. Comment s'appelle le signal d'interruption et comment s'appelle le code permettant de la traiter ?
  3. Quel état peut prendre un signal d'interruption ?
  4. Quel type de composant émet un signal d'interruption ?
  5. Est-ce que tous les composants génèrent des signaux d'interruption ? Si la réponse est non, donnez un exemple ?
  6. Est-ce qu'un composant peut produire plusieurs signaux d'interruption ?
  7. À qui est destiné un signal d'interruption ?
  8. Est-ce qu'une application utilisateur sait quand elle va être interrompue ?
  9. Est-ce qu'une application utilisateur sait quand elle a été interrompue ?
  10. Que signifie IPI et à quoi ça sert ?
  11. Est-ce qu'un programme utilisateur peut interdire les interruptions en général ?
  12. Est-ce que le noyau du système d'exploitation peut interdire les interruptions en général ?
  13. Qui exécute le code de traitement du signal d'interruption ?
  14. Quand un signal d'interruption s'active est ce que le noyau sait toujours quoi faire ?
  15. Que signifie acquitter un signal d'interruption ? (on dit aussi acquitter une interruption)
  16. Comment fait-on pour acquitter une interruption ?
  17. Que signifie l'expression «vol de cycles» ?
  18. Est-ce l'application qui a provoqué l'activation d'un signal d'interruption qui est volée ?
  19. Pour le composant TTY, à quel moment produit-il un signal d'interruption ?
  20. Pour le composant TTY, comment fait-on pour acquitter une d'interruption ?
  21. Si plusieurs caractères ASCII sont en attente dans d'être lus dans un TTY, quelle conséquence cela a-t-il sur le signal d'interruption ?
  22. À quoi sert le composant TIMER ?
  23. Comment fait-on pour le configurer ?
  24. Comment fait-on pour acquitter une interruption pour le composant TIMER ?
  25. Est-ce que le registre TIMER_VALUE peut activer (on dit aussi lever) un signal d'interruption ?
  26. Que signifie ICU et à quoi cela sert-il ?
  27. Que signifie masquer une interruption ?
  28. Dans l'ICU, que contient le registre ICU_STATE ?
  29. Dans l'ICU, que contient le registre ICU_MASK ?
  30. Dans l'ICU, comment fait-on pour modifier la valeur de ICU_MASK ?
  31. Dans l'ICU, que contient le registre ICU_HIGHEST ?
  32. Dans l'ICU, il y a autant de jeu de registres que de processeur, pourquoi ?
  33. Qu'est-ce que la fonction kinit() ?
  34. Que contient le vecteur d'interruption ?
  35. À quel moment doit-on initialiser le vecteur d'interruption ?
  36. En quoi consiste la liaison des interruptions (interrupt binding en anglais) ?
  37. À quelle adresse saute-t-on lorsqu'un signal interruption s'active et qui n'est pas masqué ?
  38. Expliquer le rôle des bits UM, EXL et IE du registre C0_SR.
  39. Comment le noyau sait-il que la cause de son invocation est une interruption ?
  40. Quel est le rôle du gestionnaire d'interruption ?
  41. Quelle instruction permet de sortir du noyau pour revenir dans le code interrompu ? et que fait-elle précisément ?
  42. Rappeler la différence entre un registre temporaire et un registre persistant.
  43. Dans le gestionnaire d'interruption, on sauve les registres temporaires seulement avant d'appeler la fonction de traitement d'un signal d'interruption ?
  44. Pour qu'une IRQ soit effectivement prise en compte, il faut que le périphérique la lève et qu'elle ne soit pas masquée. Il y a plusieurs endroits où on peut masquer une IRQ, lesquels ?

Travaux pratiques

La plateforme

Le but de ce TP est d'analyser, de modifier et d'utiliser le gestionnaire d'interruption.

Nous allons utiliser la même plateforme que la dernière fois, mais malheureusement celle-ci avait un bug matériel.
Vous allez donc devoir prendre un nouveau simulateur almo1.x.gz.
vous devez télécharger ce fichier, le dézipper et le placer dans le répertoire AS6/bin à la place du précédent.

La plateforme que nous allons utiliser contient :

  • un processeur
  • une mémoire multisegment pour le code et les données du noyau et de l'utilisateur.
  • une ROM pour le boot
  • un contrôleur multittys (4)
  • un timer
  • une icu

Sur cette plateforme, les composants produisant des IRQ sont le timer et les 4 ttys. Ces IRQ sont destinées au processeur, mais elles passent par l'ICU. l'ICU permet de masquer individuellement chaque IRQ et si plusieurs sont levées simultanément alors l'ICU permet de dire quelle est celle prioritaire. La manière dont sont routées les IRQ n'est pas modifiable par logiciel, les IRQ sont des signaux électriques câblés par les architectes. Sur almo1:

  • L'IRQ du timer entre sur l'entrée n°1 de l'ICU.
  • Les IRQ de TTY entrent respectivement sur les entrées 3, 4, 5 et 6 de l'ICU.

Question : faire un dessin représentant la plateforme avec les signaux IRQ.

Analyse de code

L'archive du code du tp2 (tp2.tgz) doit être placés à coté du code de tp1 et dézippé.

Pour ce TP, il n'y aura qu'une seule étape parce que j'ai voulu ajouter un minimum de code. En effet, la gestion des IRQ est liée à la gestion des périphériques puisque c'est eux c'est essentiellement eux qui les produise. Or, la gestion des périphériques de faire par le mécanisme des pilotes de périphériques. J'en présenterai une version simplifiée lorsque nous verrons les périphériques initiateurs. Pour l'heure, il s'agit de comprendre juste comment sont traités les IRQ et de jouer un peu avec.

Par rapport à l'étape 6 sur tp1, voici les changements

7_isr/
├── common
│   └── syscalls.h  : pas de changement parce qu'il y a pas d'ajout d'appel système
├── kernel
│   ├── harch.c     : ajout des fonctions d'accès aux nouveaux périphériques, des isr et de la fonction arch_init()
│   ├── harch.h     : ajout de la déclaration de la fonction arch_init, les autres fonctions de harch.c ne sont pas exportés.
│   ├── hcpu.h      : pas de changement
│   ├── hcpu.S      : ajout du gestionnaire d'interruption dans kentry
│   ├── kernel.ld   : ajout des informations sur les segments des nouveaux périphériques
│   ├── kinit.c     : ajout de l'appel à arch_init() et acceptation des interruptions
│   ├── klibc.c     : pas de changement
│   ├── klibc.h     : pas de changement
│   ├── kpanic.c    : pas de changement
│   ├── kpanic.h    : pas de changement
│   ├── ksyscalls.c : pas de changement
│   └── Makefile    : ajout des #defines pour indiquer le nombres de périphériques ajoutés 
├── Makefile        : changement mais rien d'important
├── uapp            
│   ├── main.c      : pas d'appel à exit() parce qu'actuellement le noyau n'est pas interruptible
│   └── Makefile    : pas de changement
└── ulib
    ├── crt0.c      : pas de changement
    ├── libc.c      : pas de changement
    ├── libc.h      : pas de changement
    ├── Makefile    : pas de changement
    └── user.ld     : pas de changement

Exercice

Je vous propose de modifier un peu le code de harch.c pour gérer l'interruption de sur le TTY1 et faire un petit programme de dessin...

  1. Vous devez modifier la fonction arch_init() pour demander démasquer l'interruption du TTY1 dans l'ICU et programmer le vecteur d'interruption.
  2. Vous allez modifier la fonction isr_tty() pour y créer un petit jeu de dessin.
    • L'idée est de gérer les touches de flèche du clavier et de déplacer le curseur sur la fenêtre.
    • Ensuite vous pouvez utiliser les touches U (pour UP) et D pour down pour lever ou baisser un crayon.
    • Si vous vous déplacez avec le crayon baissé ça écrit des '*'.
    • Vous pouvez ajouter d'autres fonctionnalités si ça vous amuse :-)

Pour déplacer le curseur, nous allons utiliser les commandes d'échappement `VT100` comprises par les fenêtres xterm.

Vous allez aussi devoir créer un automate d'état fini dans la fonction tty_isr, c'est assez simple ici. Je vous donne un début de code que vous pouvez analyser et modifier. Normalement, on ne fait pas un jeu dans une isr, mais un automate oui, c'est possible.

Quand vous tapez sur une flèche ⟶ le clavier envoie 3 code ascii : le code ESC (27), '[', ’C' L'automate a trois états :

  • 0 = on attend n'importe quel caractère
  • 1 = on a reçu ESC et on attend '['
  • 2 = on attend 'A' ou 'B' ou 'C' ou 'D'
static void tty_isr (int irq)
{
    static int state[NTTYS];

    char c = __tty_regs_map[ (irq - 3) % NTTYS].read;
    switch (irq) {
    case 3:
        kprintf (0, "code ascii %d", c);
        if ((c >= 32) && (c <= 126)) 
            kprintf (0, " = %c", c);
        kprintf (0, "\n");
        break;
    case 4:
        switch (c) {
        case 27  :
            state[1] = 1;
            break;
        case '[' :
            if (state[1] == 1)
                state[1] = 2;
            else {
                state[1] = 0;
            }
            break;
        case 'B' : 
            if (state[1] == 2) {
                kprintf (1, "%c[B", 27);
            } 
            state[1] = 0;
            break;
        case 'C' : 
            if (state[1] == 2) {
                kprintf (1, "%c[C", 27);
            } 
            state[1] = 0;
            break;
        }
    }
}

}}} }}}