wiki:AS6-TME-B2

Version 27 (modified by franck, 2 years ago) (diff)

--

  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. À quel moment doit-on initialiser le vecteur d'interruption ?
  16. En quoi consiste la liaison des interruptions (interrupt binding en anglais) ?
  17. Comment le noyau sait-il que la cause de son invocation est une interruption ?
  18. Quelle instruction permet de sortir du noyau pour revenir dans le code interrompu ? et que fait-elle précisément ?
  19. Rappeler la différence entre un registre temporaire et un registre persistant.
  20. 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 ?
  21. Que fait le processeur lorsqu'il reçoit une IRQ masquée ?
  22. Que signifie acquitter une IRQ ?
  23. Qui demande l'acquittement à qui ?
  24. Comment demande-t-on l'acquittement ?
  25. Est-ce qu'une IRQ peut se désactiver sans intervention du processeur ?
  26. Est-ce qu'une IRQ peut ne pas être attendue par le noyau ?
  27. Quelle est la valeur du champ XCODE du registre c0_cause à l'entrée dans le noyau en cas d'interruption ?
  28. Quelle est la valeur écrite dans le registre c0_EPC à l'entrée dans le noyau en cas d'interruption ?
  29. 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 ?
  30. 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 ?
  31. À 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 ?
  32. 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 ?
  33. Si ICU_HIGHEST contient 10 (dans le cas de notre plateforme) que doit faire la fonction irq_handler()
  34. Que fait la fonction icu_set_mask (int icu, int irq) ?
  35. 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.
  36. 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.

Travaux pratiques

La plateforme

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

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 MULTITTY
  • 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;
        }
    }
}