Périphériques d`entrée
Transcription
Périphériques d`entrée
Raquettes et Horloge Chapitre 8 Périphériques d’entrée J’ai toujours rêvé pourvoir utiliser facilement mon ordinateur comme mon telephone. Mon reve est devenu réalité. Je ne sais plus utiliser mon téléphone. Bjarne Stroustrup Nous allons ici détailler comment utiliser les périphériques d’entrée de données. La méthode initiale est proche de celle présentée au chapitre précédent par lecture dans les ports associés aux périphériques. Cependant, et pour éviter de développer des pilotes en attente infinie sur ces ports, nous allons présenter comment un périphérique peut interrompre/réveiller le système lorsque des données à lire sont présentes. Pour agrémenter cette technique, nous allons développer un pilote pour clavier permettant de déplacer les raquettes de nos joueurs de SuperPong. Sommaire 8.1 8.2 8.3 8.4 8.5 La notion d’interruption . . . . . . . . Implémentation de l’interface . . . . . . 8.2.1 le contrôleur d’interruption . . 8.2.2 Le tableau des handler . . . . . 8.2.3 Les handlers . . . . . . . . . . . Utilisations de l’interface : Nos handlers 8.3.1 Une horloge . . . . . . . . . . . 8.3.2 le clavier . . . . . . . . . . . . . Fil Rouge : Maniement de la raquette Résumé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 75 75 78 81 84 85 86 88 88 L e chapitre précédent nous a présenté les techniques permettant d’afficher des résultats d’un calcul (notre jeu) sur un périphérique de sortie (l’écran). Dans ce chapitre, nous allons détailler comment récupérer des données issue de périphériques dont le calcul a besoin (les caractères tapez au clavier). Pour cela, nous disposons de deux techniques génériques différentes : l’attente active et l’attente passive. L’attente active consiste, lorsque le calcul a besoin d’une donnée, à interroger le périphérique de manière continuelle, tant que la donnée n’est pas présente. L’attente passive consiste à interroger le périphérique que lorsque ce dernier à signaler que la donnée est présente. Typiquement, lorsque vous attendez des amis pour dîner, soit vous allez ouvrir votre porte d’entrée toutes les minutes pour savoir si ces derniers sont arrivés (attente active) soit, vous installer une sonnette. Dans ce dernier cas, en permettant à vos amis de signaler leurs présences (attente passive), cela vous libère pour vous concentrer sur le repas... 73 74 Périphériques d’entrée Les techniques d’interruptions présentées dans ce chapitre permettent de mettre un code de traitement en attente passive sur les données envoyer par le périphérique. En terme de fonctionnalité, nous souhaitons pouvoir associer un numéro d’interruption affecté à un périphérique à un code de traitement. Nous souhaitons également pouvoir désactiver et réactiver une interruption. L’interface reprenant ces fonctionnalités est présentée en N.1. 1 i r q _ s e t _ r o u t i n e ( i nt numero , ir q_ ha ndler _ t f o n c t i o n ) ; 3 i r q _ g e t _ r o u t i n e ( i nt numero ) ; 5 i r q _ d i s a b l e ( i nt numero ) ; 7 i r q _ e n a b l e ( i nt numero ) ; Listing 8.1: Interface 8.1 La notion d’interruption Jusqu’à présent, le processeur exécute de manière continue le code de notre programme. Les interruptions permettent, comme leurs noms l’indique, d’interrompre l’exécution du code courant sur le processeur en vue d’effectuer un traitement spécifique pour traiter l’interruption. Nous distinguons trois sortes d’interruptions : – les interruptions processeurs, ou exceptions, qui sont générés en interne par le processeur lorsqu’un événement critique lié au code en cours d’exécution se produit. Typiquement une division par zéro. Sur l’architecture x86, nous disposons de 32 exceptions numérotées de 0 à 31. – les interruptions logiciels qui sont générés explicitement par le code en cours d’exécution. Typiquement, lors d’un processus de déverminage, l’instruction int8 est écrite dans le code binaire à l’endroit ou le breakpoint du code source a été posé. L’instruction int8 est une interruption logiciel qui arrêtera le code en cours d’exécution. – les interuptions matérielles, qui sont provoqué par le matériel indiquant que quelque chose vient de se produire sur le périphérique. Typiquement lors de l’appui d’une touche du clavier. Elles sont aux nombres de 15. On peut associer à chaque interruption un code à exécuter. On par alors de handler ou routine d’interuption. Le handler exécute les premières instructions de traitement de l’interruption, il se charge de dialoguer avec le périphérique et gérer l’événement qui à produit l’interruption (e.g. touche appuyée sur le clavier). Comme il existe plusieurs périphériques, les interruptions sont numérotées. La correspondance entre le numéro de l’interruption et l’handler associé se fait au moyen d’un tableau. (sur PC ce tableau possède 256 entrées). Lorsque l’interuption n se produit, l’entrée n du tableau contient l’adresse de la première instruction du handler à exécuter. Sur Pc, ce tableau s’appelle l’IDT (Interurpt Descriptor Table). Ce tableau réside en mémoire centrale, à une adresse quelconque. Contrairement à la zone mémoire du contrôleur vidéo qui est normalisé et placée à un endroit prédéterminé, l’adresse de l’IDT est variable et au choix du programmeur du système d’exploitation. 8.2. Implémentation de l’interface Fig. 8.1: Cascade des 2 8259 Il faut donc, pour que le processeur puisse y faire référence lorsque qu’une interruption se produit, que le processeur connaisse l’adresse du début du tableau et sa taille (pour éviter des débordements mémoires). C’est le rôle dévolue à un des registres du processeur nommé idtr. En résumé : un périphérique à la possibilité d’interrompre le code en cours d’exécution sur le processeur en levant une interruption. Pour distinguer le périphérique ayant levé l’interruption, ces dernières sont numérotées. Le code interrompu laisse la place à un code spécifique de gestion de l’interruption nommé handler. A chaque interruption est associé son handler. Pour que le processeur puisse faire cette relation, il charge l’handler situé à l’indice n de l’intruption n du tableau nommé idt située à l’adresse contenue dans le registre idtr. Sur PC, le microcontrôleur chargé de faire la relation entre périphérique et interruption est le PIC 8259. 8.2 8.2.1 Implémentation de l’interface le contrôleur d’interruption Lors des tous premier PC sur bus ISA (8bits), les concepteurs ont défini la possibilité d’utiliser 8 interruptions matérielles (IRQ). Ces interruptions ont été confiées à un microcontrôleur nommé 8259. Ce contrôleur peut donc gérer 8 interruptions avec des priorités allants du port 1 à 8. En simplifiant, ce microcontrôleur est un tampon de traitement entre le périphérique et le micro-processeur, il se charge entre autres d’affecter des priorités aux interruptions se produisant en même temps. Avec l’apparition des bus 16bits, les concepteurs ont voulus rajouter 8 autres entrées. Pour éviter tout problème de gestion en parallèle des interruptions ils ont préférés cascader deux 8259 que les paralléliser. La cascade des microcontrôleur se fait sur l’interruption 2 du premier. Il n’y a donc plus que 15 interruptions gérables par les deux 8259. Une conséquence directe sur la gestion des priorités est que le saut du premier 8259 appelé maître (Master) au second, appelé esclave (slave) produit les priorités suivantes (de la plus grande à la plus faible) : 0 > 1 > 8 > 9 > 10 > 11 > 12 > 13 > 14 > 15 > 3 > 4 > 5 > 6 > 7 Classiquement, sur un PC, les IRQs sont associées comme suit : 75 76 Périphériques d’entrée 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 8.2.1.1 Horloge interne clavier contrôleur d’interruptions programmable Renvoi vers les IRQ 8 à 15 port de communication COM2/COM4 port de communication COM1/COM3 libre contrôleur de disquettes port imprimante LPT1 CMOS (Horloge temps réel) libre libre libre port souris PS2/libre processeur numérique de données (Coprocesseur mathématique) contrôleur de disques durs primaire (IDE) contrôleur de disques durs secondaire (IDE) Initialisation du contrôleur d’interruption L’initialisation des contrôleurs revient donc à programmer ce mode cascade. Ce code est décrit en 8.2 dont son principe est détaillé après. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 sexta nt_ r et_ t i8 2 5 9 _ setup ( ) { // Enoyer ICW1 e c r i r e O c t e t ( 0 x11 , PIC_MASTER) ; e c r i r e O c t e t ( 0 x11 , PIC_SLAVE) ; // Envoyer ICW2 e c r i r e O c t e t ( 0 x20 , PIC_MASTER+1) ; e c r i r e O c t e t ( 0 x28 , PIC_SLAVE+1) ; // Envoyer ICW3 mast er e c r i r e O c t e t ( 0 x4 , PIC_MASTER+1) ; // Envoyer ICW3 s l a v e e c r i r e O c t e t ( 0 x2 , PIC_SLAVE+1) ; // Envoyer ICW4 e c r i r e O c t e t ( 0 x1 , PIC_MASTER+1) ; e c r i r e O c t e t ( 0 x1 , PIC_SLAVE+1) ; // Envoyer OCW1 // C l o s i n g a l l IRQs : w a i t i n g f o r a c o r r e c t h a n d l e r The o n l y IRQ // Enabled i s t h e c a s c a d e e c r i r e O c t e t ( 0xFB , PIC_MASTER+1) ; e c r i r e O c t e t ( 0 xFF , PIC_SLAVE+1) ; return SEXTANT_OK ; 22 23 } Listing 8.2: Extrait de Sextant/interruptions/i8259.c Pour Info, voici le détaille des opérations : – ICW1 7 6 5 4 3 2 1 0 0 0 0 1 trig 0 M/S ICW4 8.2. Implémentation de l’interface – – – – – Trig 0 = Edge triggered, 1 = Level Triggered – M/S 0 = Master/Slave configuration, 1 = Master only – ICW4 0 = No ICW4, 1 = ICW4 will be send Sane master value : 00010001b (0x11) Sane slave value : 00010001b (0x11) ICW2 7 6 5 4 3 2 1 0 Off 7 Off 6 Off 5 Off 4 Off 3 0 0 0 – Off 7 .. Off 3 Offeset into the IDT for interupt service routines The last 3 bits are always 0 because it needs to be 3 bit alligned Sane master value : 00100000b (0x20) Sane slave value : 00101000b (0x28) ICW3 (master) 7 6 5 4 3 2 1 0 S7 S6 S5 S4 S3 S2 S1 S0 – S7..S0 0 = IR Line is connected to peripheral device, 1 = IR Line is connected to Slave 8259A Sane master value : 00000100b (0x04) ICW3 (slave) 7 6 5 4 3 2 1 0 0 0 0 0 0 S ID 2 ID 1 ID 0 – ID2..ID0 IRQ on master this slave is connected to Sane slave value : 00000010b (0x02) ICW4 (optional) 7 6 5 4 3 2 1 0 0 0 0 SFNM BUF M/S AEOI Mode – SFNUM 0 = No Special Fully Nested Mode, 1 = Special Fully Nested Mode BUF – 0 = No Buffered Mode, 1 = Buffered Mode – M/S 0 = Slave PIC, 1 = Master PIC – AEOI 0 = Manual EOI, 1 = Automatic EOI – Mode 0 = MCS-80/85 Mode, 1 = 8086/88 Mode Sane master value : 00000101b (0x05) Sane slave value : 00000001b (0x01) 8.2.1.2 Masquage des interruptions du PIC 8259 Lorsqu’une interruption est levée, le processeur arrête le traitement en cours pour exécuter le handler associé. Cependant, il est quelque fois important de pouvoir exécuter un code sans jamais être interrompue. Pour le PIC 8259, il est possible de définir des masques sur les interruptions permettant de les activer ou les désactiver. Sur le principe, le PIC n’écoute que sur les ports dont le masque est égal à 0. Par exemple si vous voulez désactiver les ports de 8 à 15, il faudra envoyer au PIC Slave le code 11111111b soit 0xFF. Si vous ne voulez écouter que l’horloge et le clavier vous devrez envoyer les instructions du listing 8.3. Enfin, les fonctions décrites dans le listing 8.4 permettent d’activer ou de désactiver une interruption dont le numéro est passée en paramètre. 1 2 e c r i r e O c t e t ( 0xFC , 0 x21 ) ; /∗ mast er PIC ∗/ e c r i r e O c t e t ( 0xFF , 0xA1 ) ; /∗ s l a v e PIC ∗/ Listing 8.3: Exemple de masquage des interuptions 77 78 Périphériques d’entrée 1 2 3 4 5 6 7 8 sexta nt_ r et_ t i 8 2 5 9 _ e n a b l e _ i r q _ l i n e ( i nt numirq ) { i f ( numirq < 8 ) /∗ i r q on mast er PIC ∗/ e c r i r e O c t e t ( ( l i r e O c t e t (PIC_MASTER+1) & ~(1 << numirq ) ) , → →PIC_MASTER+1) ; else /∗ i r q on s l a v e PIC ∗/ e c r i r e O c t e t ( ( l i r e O c t e t (PIC_SLAVE+1) & ~(1 << ( numirq −8) ) ) , → →PIC_SLAVE+1) ; return SEXTANT_OK ; 10 11 } 13 sexta nt_ r et_ t i 8 2 5 9 _ d i s a b l e _ i r q _ l i n e ( i nt numirq ) { i f ( numirq < 8 ) /∗ i r q on mast er PIC ∗/ e c r i r e O c t e t ( ( l i r e O c t e t (PIC_MASTER+1) | ( 1 << numirq ) ) , → →PIC_MASTER+1) ; else /∗ i r q on s l a v e PIC ∗/ e c r i r e O c t e t ( ( l i r e O c t e t (PIC_SLAVE+1) | ( 1 << ( numirq −8) ) ) , → →PIC_SLAVE+1) ; 14 15 16 17 18 19 20 return SEXTANT_OK ; 22 23 } Listing 8.4: Exemple de masquage des interuptions Le code présenté dans le listing 8.4 est directement appelé par les fonctions irq_disable(int) et irq_enable(int). Nous venons de voir comment programmer les PIC pour activer, désactiver les interruptions matérielles, nous allons voir dans la section suivante comment faire l’association entre le handler et l’interruption levée. 8.2.2 Le tableau des handler Le tableau ou seront contenus les adresses des handlers s’appel IDT pour Interrupt Descriptor Table. Chaque entrée dans de tableau est de type x86_idt_entry (ou idt pour interrupt descriptor table). Cette structure est composée de 8 octets définit far le schéma de la figure 8.2.3 et repris dans la structure définie dans le listing 8.5. Le tableau contenant les 256 entrées pour accueillir nos handler est nommé idt. 4 6 7 8 #define IDTE_NUM struct x86_idt_entry { /∗ Low dword ∗/ 256 /∗ 0 x100 ∗/ 8.2. Implémentation de l’interface Fig. 8.2: Strcuture d’une idte 9 10 ui16_t o f f s e t _ l o w ; →segment ∗/ ui16_t s e g _ s e l ; /∗ 1 5 . . 0 , o f f s e t o f t h e r o u t i n e i n t h e → /∗ 3 1 . . 1 6 , t h e ID o f t h e segment ∗/ 21 /∗ High dword ∗/ ui8_t r e s e r v e d : 5 ; /∗ 4 . . 0 ∗/ ui8_t f l a g s : 3 ; /∗ 7 . . 5 ∗/ ui8_t type : 3 ; /∗ 1 0 . . 8 ( i n t e r r u p t g a t e , t r a p g a t e . . . ) ∗/ ui8_t o p_ size : 1 ; /∗ 11 (0=16 b i t s i n s t r u c t i o n s , 1=32 b i t s → → i n s t r . ) ∗/ ui8_t z e r o : 1 ; /∗ 12 ∗/ ui8_t d p l : 2 ; /∗ 1 4 . . 1 3 ∗/ ui8_t p r e s e n t : 1 ; /∗ 15 ∗/ ui16_t o f f s e t _ h i g h ; /∗ 3 1 . . 1 6 ∗/ } __attribute__ ( ( packed ) ) ; 23 s t a t i c struct x86_idt_entry 12 13 14 15 16 17 18 19 20 i d t [IDTE_NUM ] ; Listing 8.5: Extrait de Sextant/interruptions/idt.h Dans la structure x86_idt_entry nous retrouvons principalement les éléments suivants : – Target Segment Selector : numéro du code segment ou se trouve les fonctions. Dans notre cas Grub s’est chargé de mettre en place le flat memory avec pour code segment 0x08. – Offset in Target Segment l’adresse de la fonction handler – P - Present, mis à 1 pour valider l’entrée. – DPL : doit être à 0 pour les interruptions matérielles et CPU, et 3 pour les interruptions logicielles. – Type : Soit Trap Gate (0x0F), Interupt Gate (0x0E) ou Task Gate (0x05) – DWord-Count doit être à 0 L’initialisation de ce tableau est effectué par la fonction sextant_ret_t idt_setup(). Cette fonction d’initialisation s’occupe également d’inscrire dans le registre idtr l’adresse et la taille du tableau idt. Cette opération se fait via une instruction spéciale du processeur nommée lidt. 1 2 3 4 6 sexta nt_ r et_ t i d t _ s e t u p ( ) { struct x 8 6 _ i d t _ r e g i s t e r i d t r ; i nt i ; for ( i = 0 ; i < IDTE_NUM ; i ++) 79 80 Périphériques d’entrée { 7 struct x86_idt_entry ∗ i d t e = i d t + i ; 8 i d t e −>s e g _ s e l = 0 x08 ; i d t e −>r e s e r v e d = 0 ; i d t e −>f l a g s = 0; i d t e −>type = 0xE ; i d t e −>o p_ size = 1; i d t e −>z e r o = 0; i d t _ s e t _ h a n d l e r ( i , ( vaddr_t )NULL1, 0 ) ; 10 11 12 13 14 15 16 } 17 20 21 22 // e c r i t u r e de l ’IDTR i d t r . base_addr idtr . limit asm v o l a t i l e ( " l i d t ␣%0\n" : : "m" ( i d t r ) : "memory" ) ; 24 return SEXTANT_OK ; 26 27 = ( ui32_t ) i d t ; = sizeof ( idt ) − 1 ; } Listing 8.6: Extrait de Sextant/interruptions/idt.h. Pour inscrire l’adresse du handler à exécuter pour l’interuption n dans le tableau idt, nous avons développés deux fonctions idt_set_handler et idt_get_handler dont le code est présentée dans le listing 8.7 2 3 4 5 6 8 9 10 11 13 14 15 16 17 18 19 20 21 22 23 24 25 26 sexta nt_ r et_ t i d t _ s e t _ h a n d l e r ( i nt index , vaddr_t ha ndler _ a ddr ess , i nt l o w e s t _ p r i v i l e d g e /∗ 0 . . 3 ∗/ ) { struct x86_idt_entry ∗ i d t e ; i f ( ( i n d e x < 0 ) | | ( i n d e x >= IDTE_NUM) ) return −SEXTANT_ERROR; i f (( lowest_priviledge < 0) | | ( lowest_priviledge > 3) ) return −SEXTANT_ERROR; i d t e = i d t + index ; i f ( h a n d l e r _ a d d r e s s != ( vaddr_t )NULL1) { i d t e −>o f f s e t _ l o w = h a n d l e r _ a d d r e s s & 0 x f f f f ; i d t e −>o f f s e t _ h i g h = ( h a n d l e r _ a d d r e s s >> 1 6 ) & 0 x f f f f ; i d t e −>d p l = lowest_priviledge ; i d t e −>p r e s e n t = 1; /∗ True , i l y a un h a n d l e r ∗/ } e l s e /∗ D e s a c t i v e l ’ e n t r e e de l ’IDT ∗/ { i d t e −>o f f s e t _ l o w = 0 ; i d t e −>o f f s e t _ h i g h = 0 ; i d t e −>d p l = 0; i d t e −>p r e s e n t = 0; /∗ False , i l n ’ y a pas d ’ h a n d l e r ∗/ 8.2. Implémentation de l’interface 81 } 27 return SEXTANT_OK ; 29 30 } 33 sexta nt_ r et_ t idt_ g et_ ha ndler ( i nt index , vaddr_t ∗ ha ndler _ a ddr ess , i nt ∗ l o w e s t _ p r i v i l e d g e ) { i f ( ( i n d e x < 0 ) | | ( i n d e x >= IDTE_NUM) ) return −SEXTANT_ERROR; 34 35 36 37 38 i f ( h a n d l e r _ a d d r e s s != NULL1) ∗ handler_address = i d t [ index ] . offset_low | ( i d t [ i n d e x ] . o f f s e t _ h i g h << 1 6 ) ; i f ( l o w e s t _ p r i v i l e d g e != NULL1) ∗ lowest_priviledge = i d t [ index ] . dpl ; 40 41 42 43 44 return SEXTANT_OK ; 46 47 } Listing 8.7: Extrait de Sextant/interruptions/idt.cpp Nous avons à ce stade mise en place la gestion de interruptions sur le Pic 8259, nous avons développées le tableau idt permettant de faire la liaison entre numéro d’interruption et code du handler et affecté au registre idtr du processeur l’adresse du début et la taille du tableau idt. Il ne nous reste plus qu’a inscrire dans le tableau l’adresse de nos handler. Cependant, nous ne pouvons directement affecter l’adresse de la fonction. En effet, il ne faut pas oublier que notre handler viendra interrompre un code en cours d’exécution. Il faut avant d’exécuter notre handler, sauvegarder le contexte du processeur pour lui permettre de reprendre l’exécution du code interrompue après la fin de l’exécution du handler. Cette gestion de l’état du processuer est détaillée ci apres. 8.2.3 Les handlers Nous avons activé le 8259 pour qu’il puisse envoyer des Interruptions matérielles. Nous avons développé idt qui est un tableau permettant au processeur de faire la liaison entre interruptions et code à exécuter. Il ne reste plus qu’a implémenter le code de nos handlers. Cependant, A chaque fois qu’une interruption se produit il faut sauvegarder les registres les plus importants. Pour nous aider dans cette tache, une norme à été définie. Cette norme précise en autre qu’une ISR (Interrupt Service Routine) doit toujours terminer son exécution par l’instruction iret. Or écrire la fonction C du listing 8.8 produira le code assembleur 8.9. 1 2 3 4 /∗ How NOT t o w r i t e an i n t e r r u p t h a n d l e r void i n t e r r u p t _ h a n d l e r ( void ) { __asm__( " pushad" ) ; /∗ Save r e g i s t e r s . ∗/ ∗/ 82 Périphériques d’entrée /∗ do s o m e t h i n g ∗/ __asm__( " popad" ) ; /∗ R e s t o r e r e g i s t e r s . ∗/ __asm__( " i r e t " ) ; /∗ This w i l l t r i p l e −f a u l t ! ∗/ 5 6 7 8 } Listing 8.8: Code ISR - 1 2 3 4 5 6 7 8 9 10 11 push %ebp mov %esp ,% ebp sub $<s i z e o f l o c a l v a r i a b l e s >,%esp pushad # C co de comes h e r e popad iret # ’ l e a v e ’ i f you use l o c a l v a r i a b l e s , ’ pop␣%ebp ’ o t h e r w i s e . leave ret Listing 8.9: Code ISR1 compile Il est donc nécessaire de développer une partie spécifique du code en assembleur. Ce code englobant nos handlers sera chargé de sauvegarder le contexte, d’appeler notre handler, puis à la fin de ce dernier, de restaurer le contexte et d’appeler le code iret. Ce code assembleur est donné en 8.10. 2 3 5 6 7 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // Fake e r r o r code p u s h l $0 // Sau vegarde du c o n t e x t p u s h l %ebp movl %esp , %ebp p u s h l %e d i p u s h l %e s i p u s h l %edx p u s h l %ecx p u s h l %ebx p u s h l %eax s u b l $2 ,% esp pushw %s s pushw %ds pushw %e s pushw %f s pushw %g s // Envoyer EOI au PIC movb $0x20 , %a l outb %a l , $0x20 // A p p e l e r un h a n d l e r avec l ’IRQ en argument pushl $id l e a l ir q_ ha ndler _ a r r a y ,% e d i c a l l ∗ i d ∗4(% e d i ) a ddl $4 , %esp 8.2. Implémentation de l’interface 29 30 31 32 33 34 35 36 37 38 39 40 41 42 // Recharge l e c o n t e x t e popw %g s popw %f s popw %e s popw %ds popw %s s a ddl $2 ,% esp po pl %eax po pl %ebx po pl %ecx po pl %edx po pl %e s i po pl %e d i po pl %ebp 45 // e r r o r code a ddl $4 , %esp 47 iret 44 Listing 8.10: Code ISR1 compilé Ce Code est répété 16 (256 ?) fois. Pour chaque bout de code, une fois les sauvegarde faite, il appelle call *\id*4(%edi) ou id est le numéro de répétition et %edi le début d’un tableau irq_handler_array contenant les adresses de nos handlers C (plus précisément l’adresse du tableau idt). La fonction irq_set_routine du listing 8.11 permet d’inscrire dans le tableau nos handlers. Elle inscrit dans un premier temps l’adresse du handler : irq_handler_array[irq_level] = routine; Puis dans un second temps, elle permet d’inscrire dans l’idt l’adresse du début de la routine assembleur (la x ieme case du tableau de routine : idt_set_handler(IRQ_BASE + irq_level,(vaddr_t)irq_wrapper_array[irq_level],0); 1 2 3 4 6 7 8 9 10 11 12 13 14 15 16 17 18 19 sexta nt_ r et_ t i r q _ s e t _ r o u t i n e ( i nt i r q _ l e v e l , ir q_ ha ndler _ t r o u t i n e ) { sexta nt_ r et_ t r e t v a l ; ui32_t f l a g s ; i f ( ( i r q _ l e v e l < 0 ) | | ( i r q _ l e v e l >= IRQ_NUM) ) return −SEXTANT_ERROR; disable_IRQs ( f l a g s ) ; r e t v a l = SEXTANT_OK ; // P o s i t i o n n e l ’ a d r e s s e du h a n d l e r dans l e t a b l e a u d e s i r q irq_handler_array [ i r q _ l e v e l ] = r o utine ; // P o s i t i o n n e l ’ a d r e s s e du code e n g l o b a n t l e h a n d l e r dans → → l e t a b l e a u des han dlers i f ( r o u t i n e != NULL1) { r e t v a l = i d t _ s e t _ h a n d l e r (IRQ_BASE + i r q _ l e v e l , ( vaddr_t ) irq_wrapper_array [ i r q _ l e v e l ] , 0 ) ; i f ( r e t v a l != SEXTANT_OK) i r q _ h a n d l e r _ a r r a y [ i r q _ l e v e l ] = NULL2 ; } else { 83 84 Périphériques d’entrée Fig. 8.3: Structure des fichiers r e t v a l = i d t _ s e t _ h a n d l e r (IRQ_BASE + i r q _ l e v e l , ( vaddr_t ) NULL1, 0 ) ; 20 21 } // Enfin , reprogramme l e PIC 8259 i f ( i r q _ h a n d l e r _ a r r a y [ i r q _ l e v e l ] != NULL1) i8259_enable_irq_line ( i r q _ l e v e l ) ; else i8259_disable_irq_line ( irq_level ) ; r esto r e_ IRQ s ( f l a g s ) ; return r e t v a l ; 22 23 24 25 26 27 28 29 30 } Listing 8.11: Extrait du code de sextant/interuptions/irq.c 8.3 Utilisations de l’interface : Nos handlers Par choix de conception, nos handlers seront tous basés sur le même principe. Un handler lié à l’événement dialoguera avec le matériel pour échanger des informations et sauvegarder dans une variable ou structure ces informations. Le driver sera ensuite chargé d’aller lire régulièrement dans cette structure les informations. Par exemple pour l’horloge, nous aurons un handler dont le code sera placé dans sextant/interuptions/handler appelé à chaque tic d’horloge chargé d’incrémenter une variable secondes. Un driver, nommé drivers/horloge.cpp sera quant à lui chargé de lire à la demande la variable seconde. En conséquence, une partie du code, le handler, est synchrone avec le matériel. Le code du driver est quant à lui asynchrone. Ce choix de conception permet de plus facilement gérer les périphériques mais nous fait passer en mode poll c’est a dire que les drivers doivent faire des attentes actives sur les événements (le driver consulte continuellement l’événement tant que ce dernier n’est pas apparue). Nous verrons dans le chapitre ordonnancement comment éviter cette attente active. 8.3. Utilisations de l’interface : Nos handlers 8.3.1 Une horloge Nous allons icic décrire l’implémentation d’un Horloge. Cette implémentation est subdivisée en trois partie : – L’initialisation du PIC 8254 – L’implémentation du handler – L’implémentation du driver. Le code présenté dansle listing 8.12 permet d’initialiser le PIC 8254. Il permet également de régler la fréquence des interruptions levées par le PIC 8254. Cette fréquence (passé en paramètre de la méthode Timer::i8254_set_frequency(unsigned int freq)) s’exprime en milliseconde. 2 3 sexta nt_ r et_ t Timer : : i 8 2 5 4 _ s e t _ f r e q u e n c y ( unsigned i nt f r e q ) { unsigned i nt nb_tick ; i f ( f r e q <= 0 ) return −SEXTANT_ERROR; // Frequ ence maxi nb_tick = I8254_MAX_FREQ / f r e q ; // Le n b _ t i c k e s t une v a l e u r e n t r e 1 e t 65536 i f ( nb_tick > 6 5 5 3 6 ) return −SEXTANT_ERROR; i f ( nb_tick <= 0 ) return −SEXTANT_ERROR; // LePIC 8254 i n t e r p r e t e 0 comme l a b a s e minimale . // De p l u s 65536 ne p e u t e t r e code s u r 16 b i t s . i f ( nb_tick == 6 5 5 3 6 ) nb_tick = 0 ; // Code d ’ i n i t i a l i s a t i o n du PIC 8254 e c r i r e O c t e t ( 0 x34 , I8254_CONTROL) ; e c r i r e O c t e t ( ( nb_tick & 0xFF ) , I8254_TIMER0) ; e c r i r e O c t e t ( ( nb_tick >> 8 ) & 0xFF , I8254_TIMER0) ; 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 return SEXTANT_OK ; 23 24 } Listing 8.12: Extrait du code de driver/Timer.cpp Une fois le PIC 8254 initialisé, il nous reste à implémenter le handler dans : sextant/interuptions/handler/handler_tic.cpp. Ce code reste très simple et est présneté dans le listing 8.13.Le handler met à jour la variable seconde qui est accédé par notre driver drivers/Timer.cpp. 2 3 5 6 7 8 9 i nt compt = 0 ; i nt s e c o n d e s =0; void t i c T a c ( i nt o ) { compt++; i f ( compt%1000==0) { s e c o n d e s++; compt=0 ; } 85 86 Périphériques d’entrée Fig. 8.4: Eclipse et les variables partagée 10 } Listing 8.13: Extrait du code de sextant/interuptions/handler/timer.cpp Petite astuce Eclipse : Lorsque vous rechercher les variables partagées et vous savoir qui utilise une variable, fait un click droit sur le nom de la variable puis aller dans Declarations->Project. Eclipse vous ouvre une vue avec les fonctions déclarant utiliser cette variable. Enfin, le Timer proprement dit consistera à simplement proposer une méthode permettant de récupérer le nombre de seconde écoulé depuis l’initialisation du PIC 8254 8.15. 1 2 3 i nt Timer : : g e t S e c o n d e s ( ) { return s e c o n d e s ; } Listing 8.14: Extrait du code de driver/Timer.cpp Enfin, reste simplement à initialiser le tout dans le main en exécutant la suite d’instruction présentée dans le listing ??. idt_setup ( ) ; irq_setup ( ) ; // I n i t i a l i s a t i o n de l a f r e q u e n c e de l ’ h o r l o g e timer . i8254_set_frequency (1000) ; // A u t o r i s e l e s i n t e r r u p t i o n s asm v o l a t i l e ( " s t i \n" ) ; // P o s i t i o n n e l e Handler i r q _ s e t _ r o u t i n e (IRQ_TIMER, t i c T a c ) ; 1 2 3 4 5 6 7 8 Listing 8.15: Extrait du code de sextant/main.cpp Nous verrons dans la section Fil rouge l’implémentation d’un application Horloge permettant d’afficher à l’écran une horloge digitale classique. 8.3.2 le clavier La gestion du clavier reprend les mêmes principes que celle du Timer. Une première partie est consacrée au handler 8.16 la seconde au driver proprement dit 8.17. 8.3. Utilisations de l’interface : Nos handlers Dans le handler, nous pouvons voir que ce dernier va lire dans le port 0x60, contenant le caractère tapé au clavier. Ce caractère est interprété et sauvegardé dans un tampon buf qui n’est autre qu’un tableau de caractères. Une autre varaibale, posBuf, sert à définir la position libre dans le tampon qui contiendra le prochain caractère. Enfin, cette position est initialisé à zéro avant toute affectation. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void h a n d l e r _ c l a v i e r ( i nt i r q ) { i nt c = l i r e O c t e t ( 0 x60 ) ; i nt c c ; // . . . i f ( c < 0 x80 ) { i f ( c==’ \n ’ ) c=c ; cc = writechar ( c ) ; switch ( c c ) { case BS : break ; case EN: modifBuf = true ; buf [ posBuf ]= ’ \n ’ ; i f ( posBuf <256) posBuf++; buf [ posBuf ]= ’ \0 ’ ; break ; case TAB: break ; case 0 : break ; default : modifBuf = true ; buf [ posBuf ]= c c ; i f ( posBuf <256) posBuf++; buf [ posBuf ]= ’ \0 ’ ; break ; } } } Listing 8.16: Extrait du code de sextant/interuptions/handler/keyboard.cpp Dans le fichier Clavier.cpp, vous trouverez le driver de gestion du clavier. Ce driver expose deux méthodes : getChar et getString. Le driver partage les variables buf, modifBuf et posBuf. La méthode bufCopie(ad1,ad2), copie le tableau situé à l’adresse ad2 dans le tableau situé à l’adresse ad1. Ainsi ; bufCopie(buf,buf1)+ permet dedécaller le tableau buf d’une case vers la gauche (buf[0]=buf[1], buf[1]=buf[2] etc...). 2 3 4 5 6 7 char C l a v i e r : : g e t c h a r ( ) { while ( ! modifBuf ) ; modifBuf = f a l s e ; bufCo pie ( buf , buf +1) ; posBuf = posBuf −1; return buf [ 0 ] ; 87 88 Périphériques d’entrée Fig. 8.5: Grille de Jeu avec Raquettes et Horloge 8 10 11 12 13 14 15 16 } char∗ C l a v i e r : : g e t S t r i n g ( ) { while ( ! modifBuf ) ; modifBuf = f a l s e ; bufCo pie ( c h a i n e , buf ) ; posBuf = 0 ; return c h a i n e ; } Listing 8.17: Extrait du code de driver/Clavier.cpp 8.4 Fil Rouge : Maniement de la raquette Nous allons rajouter les raquettes à notre Jeu. Pour cela nous avons un classe SuperPong qui va englober la grille de jeu et les règles du jeu. Va être ajouter à la grille les raquettes. Ses raquettes peuvent être vu comme des murs mobiles. 8.5 Résumé Dans ce chapitre nous avons détaillé deux concepts fondamentaux, l’attente active et attente passive. Nous avons défini une interface, puis proposer une implémentation permettant de proposer un service d’attente passive sur des événements matériels. Nous avons principalement décrit le rôle ducontroleur de périphérique, puis le mécanisme de tableau permettant au processeur d’associer un numéro d’interruption à un handler. A cette occasion, nous avons également abordée la notion d’état du processeur. À la fin de cette présentation nous avons développez deux handlers, le handler clavier et dutimer,et développé deux applications qui s’exécute en parallèle. Ce parallélisme reste primitif 8.5. Résumé puisque les deux applications réalisent une attente active dans une boucle infinie. Le parallélisme est réalisé car les applications rendent la main lorsque aucun code n’est a exécuté (absence d’événement matériel). En revanche, si une application prend la main et ne la rend plus (comme la gestion d’une balle), le jeu se bloque. Plus précisément, seul le code de la balle s’exécute. Les deux chapitres suivant présentent la notion d’activité puis de multi activités. 89