Pilotes de périphériques Une introduction aux drivers Cours IEM Prof. Daniel Rossier Version 0.1 1
Plan Introduction Architecture générale Interactions des espaces utilisateur/noyaux Catégorie de drivers Gestion du driver dans le noyau Registration, sondes, callbacks Modèle général d'un système et ses périphériques Classes, bus, devices 2
Pilotes de périphériques Drivers pour le pilotage de périphériques Disque dur Souris, clavier, etc. PCI, IDE, etc. Systèmes de fichiers (ext2fs, NTFS, FAT-32, etc.) Drivers pour des pseudo-devices Génération de données Terminaux virtuels Multiplexage audio/vidéo Systèmes de fichiers 3
Contraintes de développement (1/2) Le développement d'un driver est généralement beaucoup plus compliqué que le développement d'une application logicielle "traditionnelle". Le périphérique fonctionne de manière "indépendante" et peut provoquer des interruptions. Plusieurs processus peuvent utiliser le périphérique (carte réseau, affichage vidéo, disque dur, etc.) Nécessité de gérer la réentrance des fonctions du driver. Le driver peut être éventuellement soumis à des contraintes temps-réel sévères. Réaction à des événements externes Feedback requis dans des délais très courts. 4
Contraintes de développement (2/2) Les périphériques peuvent "apparaître" ou "disparaître" de manière inopportune. Le driver doit garantir la cohésion des données internes. Les drivers - et le code du noyau en général - ne peuvent pas utiliser d'appels systèmes! Pas de printf() Difficile à debugger Une interruption matérielle peut survenir pour desservir une requête venant d'un driver, mais le processus en cours d'exécution n'est pas celui concerné par cette requête! Nécessité de gérer différents contextes (piles, threads, etc.) 5
Développement de drivers Il est indispensable de pouvoir naviguer aisément dans les sources du noyau. Linux Cross-Reference (LXR) http://lxr.linux.no Des outils de debugging sont nécessaire. kgdb Sonde JTAG Système de fichiers /proc et /sys Utilisation de l'utilitaire strace pour visualiser l'utilisation des appels système. "Système D" 6
Linux et les drivers Un driver apparaît sous la forme d'un module. #define MODULE #include <linux/module.h> int init_module (void) { printk("hello kernel n"); return 0; } void cleanup_module(void) { printk("goodbye Kerneln"); } Un module peut être chargé statiquement ou dynamiquement. Durant la compilation du noyau Durant l'exécution du noyau 7
Organisation de la mémoire Le driver est du code exécuté dans l'espace noyau. Espace mémoire Kernel (OS, data, etc.) Les fonctions du driver sont invoquées à partir d'appels système, ou par les sous-systèmes du noyau. Il y a toujours un processus qui tourne. Peut-être pas toujours le bon ;-) Espace d'adressage Stack Heap BSS Data Code 8
Espace noyau et sous-systèmes 9
Interactions entre espaces User/Kernel Applications (User Space) System Call Interface VFS Socket Kernel Space Buffer Cache File Systems Network Protocol Block Device Driver Character Device Driver Network Device Driver Hardware 10
Interactions User/Kernel Les données doivent être transférées de l'espace utilisateur vers le noyau, et/ou vice-versa. Le driver utilise ses propres buffers et autres structures de données. L'espace d'adressage est accessible dans son entièreté. Les données utilisateur sont visibles. Une reconfiguration MMU peut survenir (plutôt rare). Les adresses doivent être vérifiées! Il est possible de passer n'importe quelle adresse dans un appel système. Exemple: write(fd, (char *) 0x003, 10); 11
Transferts de données User <-> Kernel Fonctions de transfert entre espace utilisateur et espace noyau. Typiquement, l'adresse de la zone mémoire allouée dans l'espace utilisateur est passée comme argument lors de l'invocation de l'appel système. 12
Utilisation des entrées "/dev" L'accès à un driver depuis une application passe par l'utilisation de fichiers spéciaux se trouvant dans le répertoire /dev. Gestion statique des entrées de /dev /dev est un répertoire devant faire partie du rootfs Les entrées sont crées manuellement à l'aide de mknod mknod [options] nom {bc} numéro_majeur numéro_mineur Gestion dynamique des entrées de /dev hotplug udev 13
Utilisation d'un majeur/mineur Un driver est identifié par le noyau à l'aide de son majeur (major). Le mineur (minor) est transmis au driver; il n'est pas utilisé par le noyau directement. Il sert à identifier un périphérique parmi d'autres présents sur un même bus. Major Minor 14
Gestion des majeurs La liste des majeurs se trouvent dans Documentation/devices.txt Dernière version: http://www.lanana.org/docs/device-list L'allocation des majeurs/mineurs peuvent se faire dynamiquement. 15
Pseudo-système de fichier /proc et /sys /proc et /sys sont deux systèmes de fichiers spéciaux permettant d'obtenir des informations du noyau. /proc Processus et threads en cours d'exécution Etat courant de l'os Description du matériel en exploitation Peut être utilisé comme outil de configuration dynamique /sys Description de l'arborescence du matériel (physique/logique) Permet la découverte automatique des composants Facilite la gestion du hotplug Facilite la gestion de l'alimentation (power management) 16
Cycle de développement d'un driver Edition et compilation avec les outils traditionnels Test du driver sur l'hôte, la cible ou dans un émulateur Edition du code source du driver Compilation du driver Création du point d'entrée /dev (mknod) Chargement dynamique (insmod "driver.o") Accès aux drivers (read(), write()) Déchargement du driver de la mémoire (rmmod) 17
Types de driver Driver de type "caractère" (Character Device) Driver de type "bloc" (Block Device) Driver de type "réseau" (Network Device) 18
Driver de type Character Transfert d'octets depuis l'application vers le périphérique et vice-versa. Pas de mécanismes de cache de buffers sophistiqués Accès séquentiels, et non aléatoires Généralement considéré pour des périphériques "lents" Utilisation via les appels systèmes open(), write(), read(), close() 19
Registration et callbacks Le driver doit être enregistré afin d'informer le noyau de sa présence et de ses capacités. Les fonctions pouvant être utilisées par le noyau doivent être déclarées dans les structures correspondantes. Ces fonctions seront appelées par le noyau (sous-systèmes ou d'autres drivers) selon besoin. 20
Structure file_operations La structure file_operations contient les références vers les fonctions (callbacks) qui seront appelées par le noyau. Fonctions open() / release() Fonctions read() / write() linux/fs.h: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char user *, size_t, loff_t *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *);... }; 21
Driver de type character Interaction entre espace utilisateur et noyau 22
Exemple - Driver de type character struct cdev *my_dev; ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) { return 0; } struct file_operations fops = {.read = device_read,.open = device_open, }; int init init_module(void) { dev_t dev; dev = MKDEV(126, 0); my_dev = cdev_alloc(); cdev_init(my_dev, &fops); } my_dev->owner = THIS_MODULE; my_dev->ops = &fops; cdev_add(my_dev, dev, 1); return 0; void exit cleanup_module(void) { cdev_del(my_dev); } MODULE_LICENSE("GPL"); 23
Driver de type character Ecrire un petit driver de type character qui affiche le contenu d'une chaîne de caractères, et qui lit une chaîne du noyau. Ecrire d'abord le programme sur papier Le tester dans l'émulateur 24
Driver de type Block Transfert de bloc(s) de données vers et depuis le périphérique Périphérique permettant le transfert en bloc (lent par rapport aux accès mémoire, mais rapide par rapport aux interfaces) Les accès peuvent être bufferisés et gérés par des caches impliquant des structures du noyau et les sous-systèmes (gestion mémoire, ordonnanceur, etc.) Les fonctions de transferts de blocs sont appelées par le noyau directement sous forme de requêtes. Fonction request() Une file de requêtes (request queue) est allouée par le driver et permet de gérer les requêtes. 25
Driver de type Block Processus read/write syscall Driver Filesystem Driver de type Block Gestion des caches de buffers Invocation de la fonction request Ordonnancement des requêtes I/O 26
Exemple (1/3) - Extrait définition /* Our request queue. */ static struct request_queue *Queue; /* The internal representation of our device. */ static struct sbd_device { unsigned long size; spinlock_t lock; u8 *data; struct gendisk *gd; } Device; 27
Exemple (2/3) - Extrait transfert /* Handle an I/O request. */ static void sbd_transfer(struct sbd_device *dev, unsigned long sector, unsigned long nsect, char *buffer, int write) { unsigned long offset = sector*hardsect_size; unsigned long nbytes = nsect*hardsect_size; } if (write) memcpy(dev->data + offset, buffer, nbytes); else memcpy(buffer, dev->data + offset, nbytes); static void sbd_request(request_queue_t *q) { struct request *req; while ((req = elv_next_request(q))!= NULL) { sbd_transfer(&device, req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req)); end_request(req, 1); } } 28
Exemple (3/3) - Extrait initialisation static int init sbd_init(void) { } Device.size = nsectors*hardsect_size; spin_lock_init(&device.lock); Device.data = vmalloc(device.size); if (Device.data == NULL) return -ENOMEM; /* * Get a request queue. */ Queue = blk_init_queue(sbd_request, &Device.lock); if (Queue == NULL) goto out; blk_queue_hardsect_size(queue, hardsect_size); /* Get registered. */ major_num = register_blkdev(major_num, "sbd"); if (major_num <= 0) { printk(kern_warning "sbd: unable to get major number\n"); goto out; } 29
Driver de type Network Les données sont organisées sous forme de paquets et peuvent "traverser" plusieurs couches de protocoles (protocol stack). Les appels systèmes ne sont pas les mêmes que précédemment: sendmsg(), recv(), accept(), bind(), listen(), etc. Les sockets de communication sont les objets utilisés dans l'espace utilisateur pour interagir avec les drivers de ce type. 30
Sous-système réseau Application layer Kernel Space 31 System call interface Protocol independent interface Network protocols Device independent interface Device drivers Physical device hardware Socket API (./net/socket.c) proto (linux/include/net/sock.h) protocole spécifique (linux/net/ipv4/tcp_ipv4.c) ip_output() / ip_receive() net_open() / net_send_packet() net_send_rx()
Modèle d'un driver de type Network Exemple: Carte CS8900 (Ethernet) Kernel Area Network Device Driver cs89x0_probe() protocol stack ip_output() net_open() net_send_packet() ip_rcv() net_rx() net_interrupt() net_close() net_bh() 32
Modèle de périphériques Sous Linux, la manière de représenter un périphérique a beaucoup évolué au fil des dernières années. L'apparition des mécanismes de hotplug et de gestion d'alimentation (power management) a fortement contribué à cette évolution. L'abstraction du matériel joue un rôle prépondérant. Le modèle actuel d'un périphérique est composé de trois objets fondamentaux: Le bus le device la classe 33
Modèle: bus, device et classe Exemple d'un modèle de souris Constations: Notion de hiérarchie Références multiples, pas d'arborescence évidente 34
Une notion sous-jacente: Kobject Un Kobject est une structure générique très utilisé dans le noyau, permettant de maintenir des références sur les différents structures du noyau. Un Kobject comprend un compteur de références. L'allocation et la restitution sont contrôlées par le noyau. Un Kobject peut être exporté dans l'espace utilisateur, via une entrée dans sysfs. Les Kobjects permettent de modéliser un système avec tous ces périphériques et drivers. Permet de faciliter la gestion du hotplug. Permet de faciliter la gestion des drivers. 35
Structure Kobject Un Kobject pointe vers un Kobject parent. Un Kobject peut contenir un Kset. Un Kset est une collection de Kobject. 36
Kobjects et Ksets Imbrication de Kobjects avec des Ksets. Un Kobject peut être embarqué dans différents Kset. 37
Utilisation d'un Kobject Une structure contient un Kobject. Exemple: cdev Récupération de "l'objet" à partir du Kobject: Utilisation de la macro container_of 38
Bus Le bus est représenté comme un canal entre le processeur et un ou plusieurs périphériques. Le bus peut être virtuel. 39
Device Le device représente le périphérique directement. Il s'agit de la structure de plus bas niveau. 40
Class La classe est l'objet de plus haut niveau dans le modèle du système. Elle identifie un type de périphérique, par exemple un disque, en faisant abstraction des détails de bas niveau (SCSI, IDE, etc). 41
La structure device_driver device_driver est la structure représentant le driver lui-même. Elle peut contenir des données privées gérées par le driver. Elle contient une référence vers la fonction de sonde automatique (probe). 42
Sonde automatique (probe) C'est une fonction de type callback invoqué par le noyau lors de la détection d'un périphérique par le driver. La fonction probe est invoqué lorsque le bus inspecte la présence de périphériques. La fonction probe permet de tester si le driver peut fonctionner ou non avec le périphérique détecté. 43
Driver de type character avec sonde Modifier le driver de type character de telle sorte à ce que celui-ci soit enregistré comme driver d'un composant I 2 C. Faites d'abord la modification sur papier Tester dans l'émulateur la présence de l'entrée correspondante dans /sys 44
Références Linux Device Drivers (3rd Edition), Jonathan Corbet, Alessandro Rubini & Greg Kroah-Hartman Version online: http://lwn.net/images/pdf/ldd3 45