# Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques

Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques.

## Le contrôleur de périphériques

Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le contrôleur de périphériques. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation.
Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible.

### Les registres d’interfaçage

Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des registres d'interfaçage entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état.
* Les registres de données permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données.
* Les registres de commande sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres.
* Enfin, beaucoup de périphériques ont un registre d'état, lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique.
Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques.
Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le pooling, l'usage d'interruptions, et le Direct Memory Access.
La solution la plus simple, appelée Pooling, est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le Pooling est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées.

### Les interruptions de type IRQ

La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les interruptions. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante :
* arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ;
* exécute un petit programme nommé routine d'interruption ;
* restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était.
Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées IRQ, sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire.
Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. A la place, le contrôleur de périphérique prévient le processeur avec une interruption. Par exemple, quand vous tapez sur votre clavier, celui-ci émet une interruption à chaque appui/relevée de touche. Ainsi, le processeur est prévenu quand une touche est appuyée, le système d'exploitation qu'il doit regarder quelle touche est appuyée, etc. Pas besoin d'utiliser du pooling, pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. A la place, le périphérique déclenche une interruption quand il a quelque chose à dire.

### Les FIFO internes au contrôleur de périphérique

Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance.
Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu !
En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine.
Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du pooling, avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique.

### Un contrôleur de périphérique peut gérer plusieurs périphériques

Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un bus secondaire, et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de contrôleur de bus que de contrôleur de périphérique dans ce cas précis, mais passons.
Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre.
Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM.
Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique.

## Les entrées d'interruption du processeur

Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables.

### L'entrée d'interruption : niveaux logiques ou fronts

Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l'entrée d'interruption, souvent notée INTR ou INT.
L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d'entrée déclenchée par niveau logique et d'entrée déclenchée par front montant/descendant. Les noms sont barbares mais recouvrent des concepts très simples.
Le plus simple est le cas de l'entrée déclenchée par niveau logique : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence.
Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique.
Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption.
A l'opposé, avec une entrée déclenchée par front montant/descendant, on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro.
Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus.

### Les deux entrées d'interruption : masquable et non-masquable

Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu.
Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption
Les interruptions non-masquables ne doivent pas être masquées, quelle que soit la situation. Les interruptions non-masquables sont généralement générées en cas de défaillances matérielles graves, qui demandent une intervention immédiate du processeur. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc.
Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer.
Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas.

## Le contrôleur d'interruption

Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué.
Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc.
Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter.
Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un numéro d'interruption qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de $N$ entrées à $log_{2}{(N)}$ entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur.
Pour implémenter cette solution, on a inventé le contrôleur d'interruptions. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption.
L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON.
Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Et cette communication ne peut pas passer par l'entrée d'interruption, mais passe par un mécanisme dédié. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Avec l'autre solution, le contrôleur d'interruption est mappé en mémoire, il est connecté au bus et est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données.

### Les contrôleurs d'interruption en cascade

Il est possible d'utiliser plusieurs contrôleurs d'interruption en cascade. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, où on avait un contrôleur d'interruption maitre et un esclave. Les deux contrôleurs étaient identiques : des Intel 8259, qui géraient 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Cette sortie indique au processeur (pour le contrôleur maître) ou au contrôleur maître (pour le contrôleur esclave) qu'une des 8 interruptions possible au moins est active. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, et le contrôleur maitre gérait le reste.
Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption.
En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption.
Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade.
Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent.

### Les Message Signaled Interrupts

Les interruptions précédentes demandent d'ajouter du matériel supplémentaire, relié au processeur : une entrée d'interruption, un contrôleur d'interruption, de quoi récupérer le numéro de l'interruption. Et surtout, il faut ajouter des fils pour que chaque périphérique signale une interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande d'ajouter 5 fils sur la carte mère, pour les connecter au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important.
Il existe cependant un type d'interruption qui permet de se passer des fils d'interruptions : les interruptions signalées par message, Message Signaled Interrupts (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens.
Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une adresse réservée. Le périphérique écrit un message qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. L'adresse réservée est toujours la même, ce qui fait que le processeur sait à quelle adresse lire ou récupérer le message, et donc le numéro d'interruption. Le message est généralement assez court, il faut rarement plus, ce qui fait qu'une simple adresse suffit dans la majorité des cas. Il fait 16 bits pour le port PCI ce qui tient dans une adresse, le bus PCI 3.0 MSI-X alloue une adresse réservée pour chaque interruption.
Le contrôleur d'interruption détecte toute écriture dans l'adresse réservée et déclenche une interruption si c'est le cas. Sauf que le contrôleur n'a pas autant d'entrées d'interruption que de périphériques. À la place, il a une entrée connectée au bus et il monitore en permanence le bus. Si une adresse réservée est envoyée sur le bus d'adresse, le contrôleur de périphérique émet une interruption sur l'entrée d'interruption du processeur. Les gains en termes de fils sont conséquents. Au lieu d'une entrée par périphérique (voire plusieurs si le périphérique peut émettre plusieurs interruptions), on passe à autant d'entrée que de bits sur le bus d'adresse. Et cela permet de supporter un plus grand nombre d'interruptions différentes par périphérique.
Un exemple est le cas du bus PCI, qui possédait 2 fils pour les interruptions. Cela permettait de gérer 4 interruptions maximum. Et ces fils étaient partagés entre tous les périphériques branchés sur les ports PCI, ce qui fait qu'ils se limitaient généralement à un seul fil par périphérique (on pouvait brancher au max 4 ports PCI sur une carte mère). Avec les interruptions par message, on passait à maximum 32 interruptions par périphérique, interruptions qui ne sont pas partagées entre périphérique, chacun ayant les siennes.

### Les généralités sur les interruptions matérielles

Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver.
En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un système de priorité d'interruption est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente.
La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée.
Ensuite, il faut aussi parler du masquage des interruptions, qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du watchdog timer, alors que les deux sont masquables. On parle alors de masquage d'interruption sélectif. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables.
Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un bit MASK dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre.
Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le registre de masque d'interruption. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée.

## Le Direct memory access

Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le bus mastering, qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur.
Le Direct Memory Access, ou DMA, est une technologie de bus mastering qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique.
Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué.

### Le contrôleur DMA

Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le contrôleur DMA. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire.
Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini.
Le contrôleur DMA contient généralement deux compteurs : un pour l'adresse de la source, un autre pour le nombre de bytes restants à copier. Le compteur d'adresse est initialisé avec l'adresse de départ, celle du bloc à copier, et est incrémenté à chaque envoi de données sur le bus. L'autre compteur est décrémenté à chaque copie d'un mot mémoire sur le bus. Le second compteur, celui pour le nombre de bytes restant, est purement interne au contrôleur mémoire. Par contre, le contenu du compteur d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire.
Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore.
Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse.

### Les modes de transfert DMA

Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode block, le mode cycle stealing, et le mode transparent.
Dans le mode block, le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge !
Dans le mode cycle stealing, on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert.
Et enfin, on trouve le mode transparent, dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas.

### Les limitations en termes d’adressage

Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres.
La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins.
Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA.
La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un tampon DMA ou encore un bounce buffer. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation.

### Les limitations en termes d’alignement

La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc.
À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets.
Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA.

### DMA et cohérence des caches

Le contrôleur DMA pose un problème sur les architectures avec une mémoire cache. Le problème est que le contrôleur DMA peut modifier n'importe quelle portion de la RAM, y compris une qui est mise en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Dans ce cas, le cache contient une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA.
Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres.
Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants.
La technologie Data Direct I/O d'Intel permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2.

### Le 8237 et son usage dans les PC

Le 8237 d'Intel était un contrôleur DMA (Direct Memory Access) présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses.

#### L'usage du 8237 sur les premiers IBM PC

Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (bank switching), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA.
Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut.
Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 canaux DMA, qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données.

#### Les deux 8237 des bus ISA

Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA :
Premier 8237 (esclave) :
* Rafraichissement mémoire ;
* Hardware utilisateur, typiquement une carte son ;
* Lecteur de disquette ;
* Disque dur, port parallèle, autres ;
Second 8237 (maitre) :
* Utilisé pour mise en cascade ;
* Disque dur (PS/2), hardware utilisateur ;
* Hardware utilisateur ;
* Hardware utilisateur.
Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts.

### L'usage du DMA pour les transferts mémoire-mémoire

Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA.
Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante).
Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un local store dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce local store, rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les local stores. Chaque SPE incorporait son propre contrôleur DMA.

## L'intégration dans le chipset de la carte mère

Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Dans le cas le plus simple où on n'a qu'une seul périphérique, il suffit de rajouter un contrôleur de périphérique entre le périphérique et le processeur. Il y a éventuellement besoin d'ajouter une entrée d'interruption sur le processeur pour. Mais dès que le processeur doit gérer plusieurs périphériques, les choses deviennent tout de suite plus complexes. Il faut rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom.
Un ordinateur contient plusieurs contrôleurs de périphériques, c'est la norme de nos jours et l'est depuis déjà plusieurs décennies. Par exemple, un ordinateur de type PC assez ancien avait un contrôleur de périphérique pour le clavier, un autre pour la souris, un autre pour le port parallèle, un autre pour le port série et quelques autres. Par la suite, d'autres contrôleurs se sont greffés aux précédents : un pour l'USB, un intégré à la carte vidéo, un intégré à la carte son, et ainsi de suite. Concrètement, n'importe quel ordinateur récent contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques.
Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, chacun ayant son propre boitier. Ils étaient soudés sur la carte mère et étaient relativement indépendants. Mais de nos jours, tout est intégré dans un circuit unique, appelé le chipset de la carte mère, et plus précisément le southbridge. Ce dernier intègre un contrôleur DMA et les contrôleurs d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le chipset, les contrôleurs S-ATA. Le contenu du chipset dépend fortement de la carte mère.

### Les chipsets sont des regroupements de circuits très divers

Le regroupement de nombreux circuits dans un chipset a de nombreux avantages. Déjà, cette technique permet de profiter des techniques avancées de miniaturisation et l'intégration. Les anciens ordinateurs devaient souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier, limitant les besoins en branchements, en soudures, en coût de production, et j'en passe.
Les chipsets intègrent un grand nombre de transistors, ce qui permet de mettre un paquet de circuits dedans. Donc au lieu d'avoir plusieurs circuits séparés sur la carte mère, on fusionne tout ce qui a trait aux bus/périphériques dans le chipset. Et l’intégration va plus loin que simplement gérer les périphériques et les bus courants. De nos jours, les chipsets soit incorporent les contrôleurs d'interruptions 8259 dans leurs circuits, soit ils les émulent avec des circuits équivalents. Idem avec les contrôleurs DMA. De nombreux contrôleurs de périphériques se retrouvent dans le chipset, comme des contrôleurs USB, Ethernet, et autres.
Mais au-delà de ça, la miniaturisation fait que les chipsets tendent à intégrer de plus en plus de fonctionnalités, qui n'ont rien à voir avec les périphériques proprement dit. Par exemple, ils intègrent souvent des timers, les fameux compteurs utilisés pour compter des durées. Des composants autrefois soudés sur la carte mère, comme la CMOS RAM ou la Real Time Clock, sont de nos jours intégrés aux chipset. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage.
Dire ce que fait un chipset est donc compliqué, car ses fonctions sont multiples. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips".

### Les chipsets servent de répartiteur entre CPU/RAM/IO

L'usage d'un chipset a un autre avantage, qui est de simplifier les interconnexions entre CPU, RAM, IO et périphériques. Sans chipset, il faut prévoir soit un bus de communication unique, soit un ensemble de liaisons point à point, soit un système complexe utilisant plusieurs bus distincts. Mais avec un chipset, les interconnexions sont centralisées. Le processeur, la mémoire, et les autres bus sont tous connectés au chipset, qui sert de point de relai entre les différents composants. Il n'y a pas besoin d'utiliser un grand nombre de bus dédiés ou de liaisons point à point pour gérer tout ce beau monde. Les interconnexions sont beaucoup plus simples et relier les différents composants est facile.
Du moins, c'était l'implémentation courante avant les années 2000. Mais nous avions vu dans la partie sur les bus, précisément dans le chapitre sur la carte mère, que le chipset de la carte mère a beaucoup évolué dans le temps. D'abord avec une séparation en northbridge et southbridge, puis avec une intégration de plus en plus poussée du chipset dans le processeur lui-même. Faisons quelques rappels rapides, mâtinés de quelques compléments.
Dans les années 2000, les chipsets monolithiques ont laissé à la place à des chipsets séparés en deux circuits intégrés distincts, appelés northbridge et southbridge. Le premier était relié au processeur, à la RAM et à la carte graphique, des composants rapides, qui demandaient des bus à haute fréquence. Il contenait typiquement un contrôleur mémoire, le contrôleur AGP/PCI-Express et d'autres circuits. Le southbridge était relié à des composants lents et fonctionnait à plus basse fréquence. Il contenait tout ce qui n'était pas intégré au northbridge.
Pour ce qui nous intéresse dans ce chapitre, à savoir les contrôleurs de périphériques/interruption/DMA, les deux étaient forts différentes. Si on omet le contrôleur AGP/PCI-Express, il ne contenait pas d'autres contrôleurs de périphériques. A l’opposé, c'est dans le southbridgeque se trouvent les contrôleurs de périphériques, ainsi que les timers. Le southbridge était relié à presque tous les autres bus de l'ordinateur.
Par la suite, le contrôleur mémoire a quitté le northbridge pour être intégré au processeur, puis les contrôleurs PCI-Express ont suivi, suivi par d'autres contrôleurs. Le northbridge est donc passé dans le processeur, mais le southbridge est resté. Le southbridge et le chipset sont deux concepts identiques de nos jours, et il n'est pas exagéré de dire que le chipset est surtout spécialisé dans la gestion des périphériques/IO et des timers, entre autres fonctions importantes.

### Les premiers chipsets des PC x86

Les premières cartes mères PC n'avaient pas de chipset, mais intégraient plusieurs composants séparés soudés à la carte mère. La liste suivante, non-exhaustive, contient les composants principaux :
* un contrôleur mémoire pour DRAM ;
* le contrôleur de bus 8288 ;
* un contrôleur d'interface parallèle 8255 ;
* le contrôleur de clavier XT ;
* un contrôleur d'interruptions 8259 ;
* un controleur DMA 8237 ;
* un générateur d'horloge 8284 ;
* et le Programmable Interval Timer (le fameux 8254 qu'on a vu dans le chapitre sur les timers.
Le premier chipset inventé pour les processeurs x86 a été le 82C100, aussi appelé chipset NEAT. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Son successeur est le 82C206 Integrated Peripheral Controller (IPC). Les différences avec le précédent sont mineures. Il incorpore toujours un générateur d'horloge 8284 et le Programmable Interval Timer 8254. Par contre, le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Les deux contrôleurs d'interruption sont reliés en cascade. Les contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Par contre, la Real Time Clock et la CMOS RAM sont maintenant intégré dans le chipset.
Les deux chipsets ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre chips sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable.
Un autre exemple est le chipset utilisé avec le processeur Intel 486, sorti en 1989. Il était connecté au processeur, à la mémoire RAM, aux différents bus, mais aussi à la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Pour ce qui est des bus, il était connecté au bus PCI, au bus IDE pour le disque dur et au bus SMM. Il était indirectement connecté au bus ISA, un bus ancien conservé pour des raisons de rétrocompatibilité, mais qui avait été éclipsé par le PCI.
En premier lieu, il intégrait des contrôleurs de périphériques, notamment un contrôleur pour le bus PCI et un autre pour les disques durs IDE-ATA. Il avait aussi une sorte d'interface vers un contrôleur séparé, qui lui gérait le bus ISA. Il contenait aussi des interfaces mémoires, notamment un contrôleur mémoire relié à la mémoire RAM de l'ordinateur, ainsi qu'une interface avec la mémoire cache (qui était séparé du processeur et placé sur la carte mère). Enfin, il intégrait aussi des timers et des circuits multiplieurs d'horloge.
En clair, les premiers chipsets incorporaient déjà beaucoup de fonctions différentes, comme les chipset modernes. Ils regroupaient déjà ensemble tout ce qui a trait aux périphériques : un contrôleur DMA, un contrôleur d'interruption et plusieurs contrôleurs de périphériques (clavier, bus parallèle). De telles fonctions sont le rôle du southbridges dans les chipsets modernes. Ils incorporaient aussi des fonctions qui étaient quelques décennies plus tard intégrées dans le northbridge : le contrôleur mémoire et de bus CPU 8288. L'incorporation du générateur d'horloge, de timers, et de la RTC/CMOS RAM est aussi chose commune dans les chipset modernes. Les chipsets modernes incorporent aussi un circuit de surveillance matérielle pour vérifier les températures et tension, actionner les ventilateurs, éteindre l'ordinateur en cas de surchauffe, etc.