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

Documents pareils