ELE-784 Ordinateurs et programmation système Cours #7 Communication avec le matériel et traitement des interruptions Bruno De Kelper Site internet : http://www.ele.etsmtl.ca/academique/ele784/ Cours # 7 ELE784 - Ordinateurs et programmation système 1 Plan d aujourd hui 1. (chap. 9) 1. Ports I/O et mémoire I/O 2. Utilisation des ports I/O 3. Utilisation de la mémoire I/O 2. (chap. 10) 1. Installation d une routine d interruption 1. Auto-détection du numéro d interruption (IRQ) 2. Mécanisme interne du traitement des interruptions 3. Activer / désactiver les interruptions 2. Implémentation d une routine d interruption 3. Moitié haute et moitié basse 1. Tasklet 2. Workqueues 4. Partage d interruption 5. I/O piloté par interruption Réf. : Linux Device Drivers, 3 ième éd., J. Corbet, A. Rubin, G. Kroah-Hartman, O Reilly Media, chap. 9-10. Cours # 7 ELE784 - Ordinateurs et programmation système 2 Cours # 7 ELE784 - Ordinateurs et programmation système 1
- Un pilote est une couche d abstraction entre le logiciel et le matériel qui permet au logiciel de "voir" le matériel au travers de certaines fonctionnalités. - En tant que tel, le pilote doit "parler" aux deux : logiciel et matériel. - Le pilote doit transmettre les requêtes du logiciel vers le matériel, au travers d une interface bien définie, et répondre aux événements provenant du matériel. - Les cours précédents ont essentiellement traités de l interface entre le pilote et le logiciel (application-usager) et il convient maintenant de traiter la communication avec le matériel. 1.1 - Ports I/O et mémoire I/O - Tous les périphériques sont contrôlés au travers de leurs registres-internes qui sont "adressés" par le processeur selon l une de deux méthodes : Port I/O Mémoire I/O Lignes de contrôle I/O CPU Bus-Donnée Bus-Adresse Lignes de contrôle Mémoire Périphérique Mémoire CPU Bus-Donnée Bus-Adresse Lignes de contrôle Mémoire Périphérique Mémoire Cours # 7 ELE784 - Ordinateurs et programmation système 3 1.1 - Ports I/O et mémoire I/O - Dans le 1 er cas Port I/O, le processeur utilise des lignes et des instructions spéciales pour accéder aux registres des périphériques, tel que pour les processeurs de la famille x86. - Dans le 2 ième cas Mémoire I/O, les registres des périphériques sont cartographiés en mémoire et sont perçus par le processeur comme des cases de mémoire ordinaires. - La plupart des périphériques sont conçus pour l accès par Port I/O, même dans les systèmes basés sur un processeur utilisant la méthode Mémoire I/O. - Mais quelques périphériques utilisent l accès par Mémoire I/O, par exemple les périphériques PCI et les cartes graphiques, même dans des systèmes basés sur l approche Port I/O. - Du côté du processeur, celui-ci est conçu pour une approche ou pour l autre, peu importe le type de périphérique contenu dans le système. - Un processeur de type Port I/O accède alors à un périphérique en Mémoire I/O au travers d une circuiterie spéciale, et vice-versa. Cours # 7 ELE784 - Ordinateurs et programmation système 4 Cours # 7 ELE784 - Ordinateurs et programmation système 2
1.1 - Ports I/O et mémoire I/O - Malgré tout, l accès aux registres des périphériques doit être effectuée avec précaution. - En effet, contrairement aux accès à la mémoire, les accès aux registres occasionnent des effets secondaires puisqu ils dictent le comportement du matériel. - Ainsi, les optimisations effectuées par le compilateur ou le processeur luimême, tels que la mise en mémoire-cache et le réarrangement de la séquence d exécution des instructions, peuvent perturber grandement les accès aux registres des périphériques. Optimisation Mise en mémoire-cache Réarrangement des séquences d exécution Solution pour les accès aux registres Le matériel est configuré automatiquement ou par Linux pour éviter la mise en mémoire-cache lors des accès aux registres de périphériques. L utilisation de Barrières permet d assurer que l ordre d exécution est respecté. (voir cours #2, acétates 24-26) Cours # 7 ELE784 - Ordinateurs et programmation système 5 1.1 - Ports I/O et mémoire I/O Exemple : Utilisation d une Barrière pour l accès à un périphérique Configure le périphérique Barrière d écriture writel (dev->registers.addr, io_destination_address); writel (dev->registers.size, io_size); writel (dev->registers.operation, DEV_READ); wmb ( ); writel (dev->registers.control, DEV_GO); Démarre l exécution - Il est important de noter que l utilisation de Barrières dégrade la performance, puisqu elles empêchent certaines optimisations, et doivent être utilisées avec parcimonie. - Aussi, plusieurs "primitives" de synchronisation du Noyau, tels que les verrous tournants (spinlock) et les opérations atomiques, agissent comme des barrières. - Certains bus de périphériques ont aussi des problèmes de "cache" spécifiques qui doivent être pris en compte. Cours # 7 ELE784 - Ordinateurs et programmation système 6 Cours # 7 ELE784 - Ordinateurs et programmation système 3
1.2 - Utilisation des Ports I/O Allocation d un Port I/O : - Avant d utiliser un Port I/O, le pilote doit en demander l accès exclusif au Noyau à l aide de : Nombre de registres (inclus dans linux/ioport.h) Adresse du 1 er registre struct resource *request_region (unsigned long first, unsigned long n, Retour non-null si la const char *name); demande est acceptée Nom choisit pour l unité-matériel - Le pilote peut aussi vérifier la disponibilité du Port I/O avec : Code d erreur int check_region (unsigned long first, unsigned long n); Adresse du 1 er registre - Finalement, le Port I/O sera retourné au Noyau avec : Nombre de registres void release_region (unsigned long start, unsigned long n); Adresse du 1 er registre Nombre de registres Cours # 7 ELE784 - Ordinateurs et programmation système 7 1.2 - Utilisation des Ports I/O Manipulation d un Port I/O : - Lors de l écriture ou la lecture de registres de périphériques, il faut tenir compte de leur taille respective (8, 16 ou 32 bits). - Aussi, Linux fournit des fonctions de lecture et d écriture standardisées pour l accès aux registres en Port I/O : Pour la lecture : La donnée lue (proviennent de asm/io.h) unsigned inb (unsigned port); unsigned inw (unsigned port); unsigned inl (unsigned port); L adresse du registre Pour l écriture : La donnée à écrire void outb (unsigned char byte, unsigned port); void outw (unsigned short word, unsigned port); void outl (unsigned longword, unsigned port); L adresse du registre Cours # 7 ELE784 - Ordinateurs et programmation système 8 Cours # 7 ELE784 - Ordinateurs et programmation système 4
1.2 - Utilisation des Ports I/O Accès à un Port I/O à partir de l espace-usager : - Les fonctions d écriture et de lecture des registres de périphérique sont aussi accessibles à partir d une application-usager, moyennant l inclusion de sys/io.h. - Par contre, les accès directs aux Ports I/O par une application-usager sont par définition un risque élevé de sécurité. - Ainsi, ces fonctions sont accessibles seulement si : - L application-usager est compilée avec l option -O pour forcer l expansion des fonctions incorporées (inline). Permission sur un Port Permission sur tous les Ports - L appel-system ioperm () ou iopl () est utilisé pour obtenir la permission d effectuer des opérations sur un Port (spécifique à la famille x86). - L application-usager doit être exécutée en tant que ROOT pour avoir le droit d utiliser ioperm () ou iopl (). (voir exemples misc-prog/inp.c et misc-prog/outp.c) Cours # 7 ELE784 - Ordinateurs et programmation système 9 1.2 - Utilisation des Ports I/O Opérations "chaîne" sur un Port : - Certains registres de périphérique peuvent être alimentés par un flux de données. - Ce genre de transaction peut être effectuée plus efficacement par les fonctions d écriture et de lecture suivantes : Pour la lecture : void insb (unsigned port, void *addr, unsigned long count); void insw (unsigned port, void *addr, unsigned long count); void insl (unsigned port, void *addr, unsigned long count); Pour l écriture : Adresse du registre Adresse du tampon Nombre de données void outsb (unsigned port, void *addr, unsigned long count); void outsw (unsigned port, void *addr, unsigned long count); void outsl (unsigned port, void *addr, unsigned long count); - Elles sont implémentées soit par des instructions-machine, lorsque le processeur les possède, ou par des boucles très serrées. Cours # 7 ELE784 - Ordinateurs et programmation système 10 Cours # 7 ELE784 - Ordinateurs et programmation système 5
1.2 - Utilisation des Ports I/O Temps-mort d accès aux I/O : - Sur certaines plateformes (ex. : i386), le bus d accès aux périphériques est trop lent par rapport au processeur et celui-ci doit ralentir les accès consécutifs. - La solution à ce problème est d ajouter de petits délais entre chaque accès consécutifs. - Une méthode, sur les plateformes x86, est d écrire 1 byte au Port 0x80 qui n est habituellement pas utilisé (ex. : "outb (0, 0x80);" ). - Une autre approche est d utiliser les fonctions d écriture et de lecture avec pause suivantes : unsigned inb_p (unsigned port); unsigned inw_p (unsigned port); unsigned inl_p (unsigned port); void outb_p (unsigned char byte, unsigned port); void outw_p (unsigned short word, unsigned port); void outl_p (unsigned longword, unsigned port); Cours # 7 ELE784 - Ordinateurs et programmation système 11 1.3 - Utilisation des Mémoires I/O - La Mémoire I/O est simplement une plage d adresses de mémoire qui est réservée pour donner accès aux registres des périphériques. - Ainsi, chaque registre de périphérique est vu par le processeur comme s il était une case de mémoire et il y accède exactement de la même façon qu il accède à la mémoire. - D ailleurs, contrairement aux Ports I/O, il est possible d accéder à un registre en Mémoire I/O au travers d un pointeur; mais cette pratique est déconseillée. - Par contre, la lecture ou l écriture dans ces cases-mémoire spéciales à des effets secondaires, contrairement aux cases de mémoire ordinaires. - Dans un même système, les deux approches peuvent se côtoyer, avec certains périphériques en Port I/O (ex. : bus ISA) et d autres en Mémoire I/O (ex. : bus PCI). Cours # 7 ELE784 - Ordinateurs et programmation système 12 Cours # 7 ELE784 - Ordinateurs et programmation système 6
Allocation d une Mémoire I/O : 1.3 - Utilisation des Mémoires I/O - Dépendant de la plateforme et du bus, la Mémoire I/O peut être accédée au travers des tables de pages de mémoire. - Dans ce cas, avant toutes choses, le Noyau doit rendre ces adresses physiques visibles au pilote en leur assignant des adresses virtuelles avec : Adresse des registres Nombre de cases de mémoire NULL si pas disponible utilisées par les registres void *ioremap (unsigned long phys_addr, unsigned long size); - La fonction ioremap_nocache () ci-dessous à le même effet mais les adresses obtenues ne seront pas disponibles pour être misent en mémoire-cache. void *ioremap_nocache (unsigned long phys_addr, unsigned long size); - Finalement, lorsque le pilote n a plus besoin du/des périphériques, les adresses-virtuelles sont libérées avec : void iounmap (void * addr); Adresse à libérer Cours # 7 ELE784 - Ordinateurs et programmation système 13 Allocation d une Mémoire I/O : 1.3 - Utilisation des Mémoires I/O - Comme pour le Port I/O, avant d utiliser une Mémoire I/O, le pilote doit en demander l accès exclusif au Noyau à l aide de : Nombre de cases (inclus dans linux/ioport.h) Adresse de la mémoire struct resource *request_mem_region (unsigned long start, unsigned long len, Retour non-null si la char *name); demande est acceptée Nom choisit pour l unité-matériel - Le pilote peut aussi vérifier la disponibilité de la Mémoire I/O avec : Code d erreur int check_mem_region (unsigned long start, unsigned long len); Adresse de la mémoire - Finalement, le Port I/O sera retourné au Noyau avec : Nombre de cases void release_mem_region (unsigned long start, unsigned long len); Adresse de la mémoire Nombre de cases Cours # 7 ELE784 - Ordinateurs et programmation système 14 Cours # 7 ELE784 - Ordinateurs et programmation système 7
1.3 - Utilisation des Mémoires I/O Accès à la Mémoire I/O : - Comme pour les Ports I/O, il faut tenir compte de la taille respective (8, 16 ou 32 bits) des registres. Pour la lecture : La donnée lue unsigned int ioread8 (void *addr); unsigned int ioread16 (void *addr); unsigned int ioread32 (void *addr); (proviennent de asm/io.h) L adresse du registre Pour l écriture : La donnée à écrire void iowrite8 (u8 value, void *addr); void iowrite16 (u16 value, void *addr); void iowrite32 (u32 value, void *addr); L adresse du registre - Même si les adresses des registres peuvent être accédées directement par un pointeur, cette pratique n est pas "portable" et il est préférable d utiliser les fonctions d accès ci-dessus. Cours # 7 ELE784 - Ordinateurs et programmation système 15 1.3 - Utilisation des Mémoires I/O Opérations "répétées" sur une Mémoire I/O : - Comme pour les Ports I/O, certains registres de périphérique peuvent être alimentés par un flux de données. - Ce genre de transaction peut être effectuée plus efficacement par les fonctions d écriture et de lecture suivantes, équivalentes aux opérations "chaines" des Ports I/O : Pour la lecture : void ioread8_rep (void *addr, void *buf, unsigned long count); void ioread16_rep (void *addr, void *buf, unsigned long count); void ioread32_rep (void *addr, void *buf, unsigned long count); Pour l écriture : Adresse du registre Adresse du tampon Nombre de données void iowrite8_rep (void *addr, const void *buf, unsigned long count); void iowrite16_rep (void *addr, const void *buf, unsigned long count); void iowrite32_rep (void *addr, const void *buf, unsigned long count); Cours # 7 ELE784 - Ordinateurs et programmation système 16 Cours # 7 ELE784 - Ordinateurs et programmation système 8
1.3 - Utilisation des Mémoires I/O Opérations "blocs" sur une Mémoire I/O : - En plus des opérations de lecture et d écriture habituelles, le Noyau fournit des opérations qui permettent d agir sur un bloc de registres à des adresses contigües : Pour initialiser un bloc contigüe de registres : void memset_io (void *addr, u8 value, unsigned int count); Adresse du bloc de registres Pour copier un bloc contigüe de registres : À partir des registres : (équivalent au memset() ) Valeur d initialisation void memcpy_fromio (void *dest, void *source, unsigned int count); Vers les registres : Adresse du tampon Adresse du bloc de registres Nombre de registres (équivalent au memcpy() ) void memcpy_toio (void *dest, void *source, unsigned int count); Adresse du bloc de registres Adresse du tampon Nombre de registres Nombre de registres Cours # 7 ELE784 - Ordinateurs et programmation système 17 1.3 - Utilisation des Mémoires I/O Port I/O vue comme de la Mémoire I/O : - Certaines versions d un périphérique sont en Port I/O tandis que d autres versions du même périphérique sont en Mémoire I/O. - Les registres des différentes versions sont les mêmes, seul la méthode d accès change. - Dans le but de simplifier l accès à ces périphériques, indépendamment de la méthode d accès, le Noyau fournit une fonction qui permet l accès en Mémoire I/O à des périphériques qui sont en Port I/O : Pour cartographier en Mémoire I/O : void *ioport_map (unsigned long port, unsigned int count); NULL si pas accessible Pour libérer la Mémoire I/O : void ioport_unmap (void *addr); Adresse du bloc de registres Adresse du bloc de registres Nombre de registres - Malgré tout, le Port I/O doit être attribué avec request_region (), comme précédemment. Cours # 7 ELE784 - Ordinateurs et programmation système 18 Cours # 7 ELE784 - Ordinateurs et programmation système 9
- Une interruption est un mécanisme matériel qui permet à un périphérique de demander de l attention du processeur. - Une interruption est toujours déclenchée à la suite d un événement qui s est produit dans le périphérique et qui requière un traitement spécifique. - Ainsi, une interruption est un signal transmit au processeur et qui déclenche l exécution d une routine d interruption (ISR Interrupt Service Routine). Périphérique Signal d interruption Étapes 1 Le périphérique transmet un signal d interruption au processeur. 2 Le processeur récupère le vecteur d interruption dans la table des vecteurs. 3 Le processeur exécute la routine d interruption. 1 2 CPU 3 Vecteur d interruption Routine d interruption Mémoire Table des vecteurs d interruption Routines d interruption (ISR) Note : La table des vecteurs d interruption est une table de pointeurs vers des routines d interruption. Cours # 7 ELE784 - Ordinateurs et programmation système 19 - L infrastructure matérielle des interruptions dépend du type de processeur : CPU Registre de statut CPU Registre de statut IRQ0 IRQ1 IRQ2 IRQ3 IRQ4 IRQ15 Masque d interruption Interruption PIC Interruptions partagées Registre de masque IRQ0 IRQ1 IRQ2 IRQ3 IRQ4 IRQ15 Dans les deux cas, les périphériques sont reliés à des lignes d interruptions spécifiques, qui peuvent être partagées, et auxquelles est accordé un niveau de priorité. Interruptions partagées Activation d interruption Masque d interruption Cours # 7 ELE784 - Ordinateurs et programmation système 20 Cours # 7 ELE784 - Ordinateurs et programmation système 10
- Pour qu un périphérique génère une interruption, lors d un événement, il doit être configuré correctement à l aide de ses registres internes. Par exemple : base_addr = Port parallèle en mode SPP (Standard Parallel Port) 0x378 (LPT1) Control Port (base_addr+2) ou IRQ Enable 0x278 (LPT2) Status Port (base_addr+1) Data Port (base_addr+0) Cours # 7 ELE784 - Ordinateurs et programmation système 21 - Pour utiliser les interruptions d un périphérique, il faut : 1 Connaître le périphérique : - L adresse de ses registres - Signification de ses registres - Son fonctionnement manufacturier 2 Savoir sur quelle(s) ligne(s) d interruption il est connecté. 3 Concevoir une routine d interruption pour répondre à cette interruption. 4 Connecter la routine d interruption à la table des vecteurs d interruptions. 5 Initialiser correctement le périphérique Logiciel Spécifications du Architecture du système - L initialisation du périphérique se fait en écrivant dans ses registres internes (voir "", section 1.0, cours # 7). - Les interruptions se produisent n importe quand (imprévisible) et sont exécutées concurremment avec les autres tâches (voir "synchronisation", cours # 2). - Habituellement, toutes les autres interruptions sont bloquées lors du traitement d une interruption. - Les routines d interruptions (ISR) doivent être le plus rapide possible. Cours # 7 ELE784 - Ordinateurs et programmation système 22 Cours # 7 ELE784 - Ordinateurs et programmation système 11
2.1 - Installation d une routine d interruption - Pour "attacher" une routine d interruption (ISR) à un signal d interruption (IRQ), le Pilote doit en faire la demande au Noyau avec : Numéro de l interruption (IRQ) Pointeur vers la routine d interruption (ISR) int request_irq (unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id); Code d erreur Drapeaux d options SA_INTERRUPT SA_SHIRQ SA_SAMPLE_RANDOM Nom du propriétaire de l interruption Pour les ISR rapides. Permet le partage Peut servir pour la génération de nombres aléatoires Code d identification servant au partage d interruption - La routine d interruption (ISR) est "détachée" du signal d interruption (IRQ) avec : Numéro de l interruption (IRQ) Code d identification void free_irq (unsigned int irq, void *dev_id); Cours # 7 ELE784 - Ordinateurs et programmation système 23 2.1 - Installation d une routine d interruption - Le nombre de lignes d interruption est limité et elles doivent être économisées. - Il convient donc de réserver une ligne d interruption seulement lorsqu on en a réellement besoin. - Donc, le meilleur moment pour réserver et libérer les lignes d interruptions est : request_irq () free_irq () - Le 1 er Open () du pilote - Avant de démarrer le périphérique - Le dernier Close () du pilote - Après avoir arrêté le périphérique 2.1.1 - Auto-détection du numéro d interruption (IRQ) : - Le numéro d interruption (IRQ) associé à un périphérique dépend de l architecture du système et doit être déterminé dynamiquement par le Pilote. - Certains périphériques peuvent "dire" au Pilote quel est leur numéro d IRQ (en lisant un de leurs registres internes). Cours # 7 ELE784 - Ordinateurs et programmation système 24 Cours # 7 ELE784 - Ordinateurs et programmation système 12
2.1 - Installation d une routine d interruption 2.1.1 - Auto-détection du numéro d interruption (IRQ) : - D autres utilisent un numéro d IRQ parmi quelques numéros standards et qui est identifié par une option du Pilote. Par exemple : Code pour choisir le numéro d IRQ selon l adresse de base du port parallèle OU Utilisation d une option du Pilote Numéro d IRQ du port parallèle if (parallelport_irq < 0) switch (short_base) { case 0x378 : parallelport_irq = 7; break; case 0x278 : parallelport_irq = 2; break; case 0x3bc : parallelport_irq = 5; break; } insmod./mon_pilote.ko irq=3 - Dans bien des cas, par contre, la meilleure méthode est de tester toutes les interruptions pour déterminer laquelle est attachée au périphérique. Cours # 7 ELE784 - Ordinateurs et programmation système 25 2.1 - Installation d une routine d interruption 2.1.1 - Auto-détection du numéro d interruption (IRQ) : Auto-détection "à la main" : int trials[ ] = {3, 5, 7, 9, 0}; int tried[ ] = {0, 0, 0, 0, 0}; int i, count = 0; volatile int my_irq = 0; for (i = 0; trials[i]; i++) Attacher une ISR-Bidon à chaque IRQ disponible tried[i] = request_irq (trials[i], probing_isr, SA_INTERRUPT, "my probe", NULL); do { my_irq = 0; INITIALISE LE PÉRIPHÉRIQUE udelay(5); Attends un peu } while ((my_irq <= 0) && (count++ < 5)); for (i = 0; trials[i]; i++) if (tried[i] = = 0) free_irq(trials[i], NULL); Interruptions que l on veut tester if (my_irq < 0) printk("probe failed %i times, giving up\n", count); Puisque ce travail prend du temps, on le ferait dans la partie INIT du Pilote. Initialise le périphérique pour qu il générer des interruptions Détache la ISR-Bidon des IRQ Répète 5 x ou jusqu à ce qu on trouve Cours # 7 ELE784 - Ordinateurs et programmation système 26 Cours # 7 ELE784 - Ordinateurs et programmation système 13
2.1 - Installation d une routine d interruption 2.1.1 - Auto-détection du numéro d interruption (IRQ) : Auto-détection "à la main" : irqreturn_t probing_isr (int irq, void *dev_id, struct pt_regs *regs) { if (my_irq = = 0) my_irq = irq; On a trouvé } if (my_irq!= irq) On avait trouvé, mais my_irq = -irq; là ce n est plus sûr return IRQ_HANDLED; Cette ISR ne fait que placer le numéro de l ISR dans la variable globale my_irq Cours # 7 ELE784 - Ordinateurs et programmation système 27 2.1 - Installation d une routine d interruption 2.1.1 - Auto-détection du numéro d interruption (IRQ) : Auto-détection par le Noyau : - Le Noyau fournit deux (2) fonctions de bas niveau pour tester les interruptions, mais seulement celles qui ne sont pas partagées. - Elles fonctionnent selon le même principe que la détection "à la main", c est-àdire que le périphérique doit déclencher au moins une interruption pour que la détection se fasse. - Par contre, elles ne requièrent pas l utilisation d une interruption "bidon". unsigned long probe_irq_on (void); int probe_irq_off (unsigned long); Prend une "image" de l état actuel des interruptions non-assignées Compare "l image" avec l état actuel et retourne le numéro de celle qui a changée Cours # 7 ELE784 - Ordinateurs et programmation système 28 Cours # 7 ELE784 - Ordinateurs et programmation système 14
2.1 - Installation d une routine d interruption 2.1.1 - Auto-détection du numéro d interruption (IRQ) : Auto-détection par le Noyau : Puisque ce travail prend du temps, on Par exemple : int count = 0; le ferait dans la partie INIT du Pilote. unsigned long mask; Initialise le périphérique pour qu il générer des interruptions Répète 5 x ou jusqu à ce qu on trouve do { Prend une image mask = probe_irq_on ( ); INITIALISE LE PÉRIPHÉRIQUE udelay(5); Attends un peu my_irq = probe_irq_off (mask); Compare l image if (my_irq = = 0) { printk(kern_info "no irq reported by probe\n"); my_irq = -1; } } while ((my_irq < 0) && (count++ < 5)); if (my_irq < 0) printk("probe failed %i times, giving up\n", count); Cours # 7 ELE784 - Ordinateurs et programmation système 29 2.1 - Installation d une routine d interruption 2.1.2 Mécanisme interne du traitement des interruptions : - En fait, les interruptions n exécutent pas leur ISR immédiatement, mais chacune fait appel à la routine commune : do_irq (). IRQ0 IRQ1 IRQ2 IRQ3 IRQ4 IRQ15 Au cas où l interruption réveille une tâche do_irq () { Accuse réception de l interruption Capture un spinlock Permettre une seule Trouve la bonne ISR interruption à la fois Si ISR trouvée handle_irq_event (ISR) Libère le spinlock } handle_irq_event (ISR) { Si pas SA_INTERRUPT Débloque les interruptions Appelle ISR () schedule () } Pour les interruptions "lentes" ma_isr () { } Cours # 7 ELE784 - Ordinateurs et programmation système 30 Cours # 7 ELE784 - Ordinateurs et programmation système 15
2.1 - Installation d une routine d interruption 2.1.3 Activer / désactiver les interruptions : - Il peut arriver qu un Pilote doive désactiver les interruptions pendant une très courte période de temps. - C est le cas, par exemple, lorsque le Pilote tient un verrou tournant (spinlock). Activer / désactiver une seule interruption : - Les fonctions suivantes permettent d activer / désactiver une interruption particulière, sauf les interruptions qui sont partagées. void disable_irq (int irq); Désactive l interruption sur tous les processeurs, mais n attends pas. void enable_irq (int irq); Désactive l interruption sur tous les processeurs. Si l interruption est en exécution, attends qu elle termine. void disable_irq_nosync (int irq); Active l interruption sur tous les processeurs. Ces fonctions sont cumulatives. Elles peuvent être appelées à partir d une ISR. - Danger de verrou mortel (Deadlock) si la tâche qui désactive l interruption tient une ressource nécessaire à la ISR de cette interruption. Cours # 7 ELE784 - Ordinateurs et programmation système 31 2.1 - Installation d une routine d interruption 2.1.3 Activer / désactiver les interruptions : Activer / désactiver toutes les interruptions : - Les fonctions suivantes permettent d activer / désactiver toutes les interruptions pour le processeur courant, c est-à-dire le processeur qui exécute la fonction. void local_irq_save (unsigned long flags); void local_irq_restore (unsigned long flags); Désactive les interruptions sans sauvegarder le masque Réactive les interruptions sans tenir compte du masque Désactive les interruptions tout en sauvegardant le masque Réactive les interruptions en récupérant le masque void local_irq_disable (void); void local_irq_enable (void); - Ces fonctions ne sont pas cumulatives et les interruptions sont réactivées immédiatement, peu importe le nombre de fois qu elles ont étés désactivées. Cours # 7 ELE784 - Ordinateurs et programmation système 32 Cours # 7 ELE784 - Ordinateurs et programmation système 16
2.2 - Implémentation d une routine d interruption - Une ISR s exécute dans le contexte d interruption, contrairement à un appelsystème qui s exécute dans le contexte du processus. - Ainsi, une ISR ne peut pas : - Échanger des données avec l espace-usager - Bloquer ou attendre (dormir) - Appeler l ordonnanceur du Noyau (schedule ()) - Le rôle d une ISR est de : - Informer le périphérique que son interruption a été réceptionnée - Récupérer / transmettre la donnée, selon le cas - Réveiller la tâche qui attend pour cet événement Par exemple : IRQ_NONE ou IRQ_HANDLED Numéro de l interruption Pointeur vers un paramètre État des registres du processeur irqreturn_t my_interrupt (int irq, void *dev_id, struct pt_regs *regs) { RÉINITIALISE LE PÉRIPHÉRIQUE (RÉCUPÈRE LA DONNÉE) ou (TRANSMET UNE DONNÉE) wake_up_interruptible (&my_queue); return IRQ_HANDLED; } Cours # 7 ELE784 - Ordinateurs et programmation système 33 2.3 - Moitié haute et moitié basse - En temps normal, les interruptions sont désactivées pendant l exécution d une ISR. - Il est donc primordial que l exécution de la ISR soit la plus rapide possible, sinon le temps de réponse du système en est largement affecté. - Il est possible de réactiver les interruptions dans la ISR manuellement ou automatiquement afin de minimiser l impact lors des ISR "lentes". - Manuellement En utilisant les fonctions d activation (voir section 2.1.3) - Automatiquement En n utilisant pas l option SA_INTERRUPT lors l installation de l interruption (voir section 2.1) - Mais la façon la plus efficace de faire un travail important, en réponse à un événement, est de diviser le travail en deux (2) parties : - Moitié haute : - C est la ISR en tant que telle. - Elle doit être très rapide. - Elle déclenche la moitié basse. - Moitié basse : - Elle fait le gros du travail. - Elle s exécute avec les interruptions activées. Cours # 7 ELE784 - Ordinateurs et programmation système 34 Cours # 7 ELE784 - Ordinateurs et programmation système 17
2.3 - Moitié haute et moitié basse - La moitié haute se borne à faire le travail essentiel, c est-à-dire : - Sauvegarder les données du périphérique dans un tampon du Pilote. - Transmettre les données du tampon du Pilote vers le périphérique. - Démarrer l exécution de la moitié basse. - La moitié basse fait tout le reste à un moment plus opportun qui ne ralentit pas le système. - Cette façon de faire permet à la moitié haute de répondre à d autres interruptions pendant l exécution de la moitié basse. - Presque toutes les interruptions "sérieuses" sont conçues de cette façon. - Le Noyau de Linux fournit deux (2) mécanismes pour implémenter la moitié basse : - Tasklet : - Sont rapides, mais leur code doit être "atomique". - Sont souvent le mécanisme préféré pour les moitiés basses. - Workqueue : - Sont plus lentes, mais elles peuvent "dormir". Cours # 7 ELE784 - Ordinateurs et programmation système 35 2.3 - Moitié haute et moitié basse 2.3.1 Tasklet : - Un Tasklet est une fonction spéciale qui est cédulée pour exécution, dans le contexte d interruption, à un moment opportun pour le Noyau. - L ordonnancement d un Tasklet n est pas cumulatif; c est-à-dire qu il peut être cédulé plusieurs fois, mais ne sera exécuté qu une seule fois. - Donc, un Tasklet ne peut jamais s exécuté concurremment avec lui-même et un Pilote qui utilise plusieurs Tasklets doit implanter des mécanismes de blocages pour éviter les conflits. - Le Tasklet s exécute toujours sur le même processeur que l ISR qui l a déclenché et ne peut donc pas commencer son exécution avant la fin de l ISR. - Par contre, l ISR peut être exécutée de nouveau pendant l exécution du Tasklet et des mécanismes de synchronisation peuvent être nécessaires. - Un Tasklet est déclaré à l aide de la macro : DECLARE_TASKLET (name, function, data); Pointeur vers la fonction contenant le code du Tasklet Nom du Tasklet Paramètre fournit au Tasklet Cours # 7 ELE784 - Ordinateurs et programmation système 36 Cours # 7 ELE784 - Ordinateurs et programmation système 18
2.3 - Moitié haute et moitié basse 2.3.1 Tasklet : Exemple de déclaration d un Tasklet : Prototype de la fonction du Tasklet void my_do_tasklet (unsigned long); DECLARE_TASKLET (my_tasklet, my_do_tasklet, (unsigned long) &my_var); Nom du Tasklet Paramètre fournit au Tasklet Exemple d activation d un Tasklet par une ISR : Activation du Tasklet irqreturn_t my_isr (int irq, void *dev_id, struct pt_regs *regs) { TRAVAIL ESSENTIEL DE LA ISR compte_isr++; Permet de compter le nombre tasklet_schedule (&my_tasklet); de fois que l ISR est exécutée return IRQ_HANDLED; } Cours # 7 ELE784 - Ordinateurs et programmation système 37 2.3 - Moitié haute et moitié basse 2.3.1 Tasklet : Exemple de fonction d un Tasklet : void my_do_tasklet (unsigned long param) { struct my_struct *data = (struct my_struct *) param; int compte = compte_isr; Conserve le compte d ISR } compte_isr = 0; TRAVAIL PARTICULIER DU TASKLET wake_up_interruptible (&my_queue); Réveille une tâche, au besoin - Il est souvent utile que le Tasklet sache le nombre de fois que la ISR a été exécutée car s a l informe du travail qu il a à faire. - Aussi, souvent le Tasklet réveille une tâche qui attend pour les résultats de l événement qui a déclenché l interruption. Cours # 7 ELE784 - Ordinateurs et programmation système 38 Cours # 7 ELE784 - Ordinateurs et programmation système 19
2.3 - Moitié haute et moitié basse 2.3.1 Workqueue : - Une Workqueue est en fait une fil d attente de travail qui s exécute lorsque possible. - La tâche du Workqueue s exécute dans le contexte-processus et peut donc "dormir", si besoin est. - Par contre, la tâche du Workqueue ne peut pas échanger aisément des données avec l espace-usager. - Bien qu il est possible de créer une Workqueue, la pratique courante est d utiliser la Workqueue du Noyau en y ajoutant simplement des tâches. - Pour insérer une nouvelle tâche de travail dans une Workqueue, il faut déclarer une variable spéciale (struct work_struct) et initialiser la Workqueue. Déclaration d une tâche de Workqueue : static struct work_struct my_wq_task; Cours # 7 ELE784 - Ordinateurs et programmation système 39 2.3 - Moitié haute et moitié basse 2.3.1 Workqueue : Initialisation de la Workqueue : (est habituellement fait dans INIT du Pilote) INIT_WORK (& my_wq_task, (void (*)(void *)) my_do_wq_task, (void *) &my_var); Pointeur vers la variable de la tâche du Workqueue Pointeur vers la fonction de la tâche Exemple d activation d une tâche de Workqueue par une ISR : Pointeur vers le paramètre de la fonction Activation de la tâche de Workqueue irqreturn_t my_isr (int irq, void *dev_id, struct pt_regs *regs) { TRAVAIL ESSENTIEL DE LA ISR compte_isr++; Permet de compter le nombre schedule_work (&my_wq_task); de fois que l ISR est exécutée return IRQ_HANDLED; } - La fonction de la tâche du Workqueue est identique au Tasklet déjà vu. Cours # 7 ELE784 - Ordinateurs et programmation système 40 Cours # 7 ELE784 - Ordinateurs et programmation système 20
2.4 - Partage d interruption - Le nombre de périphériques qui utilisent les interruptions dépasse largement le nombre de ligne d interruption dans un système. - Ainsi, tous Pilote doit permettre le partage des lignes d interruptions qu il utilise avec d autres Pilotes, à chaque fois que le matériel le permet (certains périphériques ne permettent pas le partage d interruption). - Et, en fait, le partage d interruption est assez simple car il s agit d abord que l interruption soit installée de la façon suivante : (comme pour les interruptions ordinaires, voir section 2.1, acétate 23) int request_irq (unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), SA_SHIRQ, const char *dev_name, (void *) &my_struct); Drapeau d option pour le partage d interruption L installation réussit si : Code d identification unique, par exemple, l adresse d une variable personnelle du Pilote - La ligne d interruption est libre, ou - Tous les Pilotes qui y sont déjà reliés, l ont déclarée "partageable". Cours # 7 ELE784 - Ordinateurs et programmation système 41 2.4 - Partage d interruption - Lors de l interruption, le processus se déroule de la façon suivante : 1. Le Noyau appelle chaque ISR attachée à l interruption et leur passe en paramètre leur propre dev_id. 2. Chaque ISR doit déterminer si l interruption lui appartient et retourner : IRQ_NONE : Immédiatement, si l interruption ne lui appartient pas. IRQ_HANDLED : Après le traitement, si l interruption lui appartient. - Pour déterminer si l interruption lui appartient, la ISR du Pilote vérifie les registres du périphérique qu elle contrôle pour savoir s il a généré l interruption. - Lors de la désinstallation de l interruption avec free_irq (), le Noyau utilise le dev_id qui est fournit pour déterminer quelle ISR doit être déconnectée. Notes importantes : - L auto-détection du numéro d interruption doit être fait "à la main", le Noyau ne fournit rien pour le faire. - Le Pilote ne peut pas utiliser enable_irq () et disable_irq () pour activer / désactiver l interruption. Cours # 7 ELE784 - Ordinateurs et programmation système 42 Cours # 7 ELE784 - Ordinateurs et programmation système 21
2.4 - Partage d interruption Exemple de ISR sur une ligne d interruption partagée : irqreturn_t my_sh_interrupt (int irq, void *dev_id, struct pt_regs *regs) { int value = inb (status_reg); Vérifie le registre de statut du périphérique if (!(value & 0x80)) return IRQ_NONE; Si l interruption ne nous appartient pas, retourne immédiatement } RÉINITIALISE LE PÉRIPHÉRIQUE (RÉCUPÈRE LA DONNÉE) ou (TRANSMET UNE DONNÉE) wake_up_interruptible (&my_queue); return IRQ_HANDLED; Le reste du traitement ne change pas Cours # 7 ELE784 - Ordinateurs et programmation système 43 2.5 - I/O piloté par interruption - Toutes les interruptions n ont pas pour but la communication de données entre le périphérique et le Pilote. - Par contre, lorsque c est le cas, les I/O piloté par interruption sont particulièrement efficace lorsque jumelés avec une stratégie de temporisation. Read Input Buffer Évite que Read / Write soit appelé trop souvent Write Output Buffer PILOTE Input Tasklet Améliore la synchro entre Tasklet et ISR Output Tasklet In_ISR buffer Out_ISR buffer Input ISR Output ISR Cours # 7 ELE784 - Ordinateurs et programmation système 44 Périphérique Périphérique Cours # 7 ELE784 - Ordinateurs et programmation système 22