Copyright © 1999 Groupe de Documentation de FreeBSD. Tous droits réservés.
29 Mai 1996
Ce document décrit comment ajouter un module de gestion de périphérique à FreeBSD. Il n'est pas destiné pour être un cours d'instruction sur des modules de gestion de périphérique d'Unix en général. Il est destiné pour les auteurs de module de gestion de périphérique, au courant du modèle de module de gestion de périphérique d'Unix, pour travailler sur FreeBSD.
La redistribution du code source (SGML), modifié ou non, et compilé (HTML, PostScript, etc.) est soumise aux conditions suivantes :
Le copyright ci-dessus, la présente liste de conditions et l'avertissement qui la suit doivent figurer dans le code source.
Le code source distribué sous forme compilée doit faire apparaître le copyright ci-dessus, la présente liste de conditions et l'avertissement qui la suit.
CE DOCUMENT EST FOURNI ``TEL QU'EN L'ÉTAT'' PAR LE PROJET DE DOCUMENTATION FRANÇAISE DE FreeBSD ET IL N'EST DONNÉ AUCUNE GARANTIE, IMPLICITE OU EXPLICITE, QUANT À SON UTILISATION COMMERCIALE, PROFESSIONNELLE OU AUTRE. LES COLLABORATEURS DU PROJET DE DOCUMENTATION FRANÇAISE DE FreeBSD NE PEUVENT EN AUCUN CAS ÊTRE TENUS POUR RESPONSABLES DE QUELQUE DOMMAGE OU PRÉJUDICE DIRECT, INDIRECT, SECONDAIRE OU ACCESSOIRE (Y COMPRIS LES PERTES FINANCIèRES DUES AU MANQUE À GAGNER, À L'INTERRUPTION D'ACTIVITÉS, OU LA PERTE D'INFORMATIONS ET AUTRES) DÉCOULANT DE L'UTILISATION DE LA DOCUMENTATION OU DE L'IMPOSSIBILITÉ D'UTILISER CELLE-CI, ET DONT L'UTILISATEUR ACCEPTE L'ENTIÈRE RESPONSABILITÉ.
Version française de Tuyet Tram Dang
Ngoc <dntt@prism.uvsq.fr>
.
Dû aux changements de FreeBSD avec le temps, ce guide est seulement
précis en ce qui concerne FreeBSD 2.x. Un guide de rechange pour FreeBSD 3.x et
au-delà est en train d'être écrit. Contactez Jeroen Ruigrok <asmodai@wxs.nl>
si vous
voulez l'aider à ce sujet.
Le noyau de FreeBSD est très bien documenté, malheureusement il est entièrement écrit en `C'.
Structure cdevsw
d_open()
d_open()
prend plusieurs arguments, la liste formelle
ressemble à quelque chose comme :
int d_open(dev_t dev, int flag, int mode, struct proc *p)
d_open()
est appelé à chaque ouverture du
périphérique.
L'argument dev contient le nombre
majeur et mineur du périphérique ouvert. Ils sont disponibles par les
macros major()
et minor()
Les arguments flag et mode sont comme décrits sur la page de manuel de open. Il est recommandé que vous examiniez ces derniers pour vous assurer des droits d'accès dans <sys/fcntl.h> et faire ce qui est exigé. Par exemple si flag est (O_NONBLOCK | O_EXLOCK) l'ouverture échouerait si il bloquait ou si l'accès exclusif ne pouvait pas être accordé.
L'argument p contient toutes les informations à propos du processus actuel.
d_close()
d_close()
prend la même liste d'argument que d_open()
:
int d_close(dev_t dev , int flag , int mode , struct proc *p)
d_close()
est seulement appelé à la
dernière fermeture de votre périphérique (par
périphérique mineur). Par exemple dans le fragment suivant de code, d_open()
est appelé 3 fois, mais d_close()
seulement une fois.
... fd1=open("/dev/mydev", O_RDONLY); fd2=open("/dev/mydev", O_RDONLY); fd3=open("/dev/mydev", O_RDONLY); ... <useful stuff with fd1, fd2, fd3 here> ... close(fd1); close(fd2); close(fd3); ...
Les arguments sont semblables à ceux décrits ci-dessus pour d_open()
.
d_read()
et d_write()
d_read()
et d_write prennent les listes suivantes
d'argument:
int d_read(dev_t dev, struct uio *uio, int flat) int d_write(dev_t dev, struct uio *uio, int flat)
Les points d'entrée de d_read()
et de d_write()
sont appelés quand read et write sont
appelés sur votre périphérique depuis l'espace utilisateur. Le
transfert des données peut être manipulé par la routine du noyau
uiomove()
.
d_ioctl()
Sa liste d'argument est comme suit:
int d_ioctl(dev_t dev, int cmd, caddr_t arg, int flag, struct proc *p)
d_ioctl()
est un fourre-tout pour les exécutions
qui ne semblent pas raisonnable dans un paradigme lecture/écriture. Le plus
célèbre de tout les ioctl est probablement celui sur des
périphériques tty, par le stty. Le point
d'entrée d'ioctl est appelé depuis l'ioctl()
de sys/kern/sys_generic.c
Il y a quatre types différents d'ioctl qui peuvent être implémentés. <sys/ioccom.h> contient des macros pratiques de pour définir ces ioctls.
_IO(g, n) pour les opérations de type contrôle.
_IOR(g, n, t) pour des opérations lisant des données d'un périphérique.
_IOW(g, n, t) pour les opérations écrivant des données sur un périphérique.
_IOWR(g,n,t) pour les opérations écrivant sur un périphérique puis lisent les données.
Ici g se rapporte à
un groupe /. C'est une valeur de 8
bits, en général indicative du périphérique ; par exemple,
't' est utilisé dans des ioctls de tty. n se rapporte au
nombre de l'ioctl dans le groupe. Sur SCO, ce seul nombre dénote l'ioctl. t est le type de
données qui sera passé au pilote de périphérique; ceci est
alors remis à un opérateur sizeof()
du noyau.
L'appel système ioctl()
fera soit un copyin()
soit un copyout()
ou les
deux à votre pilote, puis vous renverra un pointeur à la structure de
données dans l'argument arg de l'appel
d'd_ioctl. Actuellement la taille de données est limitée à une page
(4k sur l'i386).
d_poll()
(3.0 et plus) ou d_select()
(2.2)la liste d'argument de d_poll()
est comme suit :
void d_poll(dev_t dev, int events, struct proc *p)
d_poll()
est employé pour découvrir si un
périphérique est prêt pour les E/S. Par exemple, attendre que des
données du réseau soient disponibles, ou que l'utilisateur presse une
touche. Cela correspond à un appel de poll()
dans
l'espace utilisateur.
L'appel à d_poll()
devrait vérifier les
événements indiqués dans le masque d'évènement. Si
aucun des événements demandés n'est en activité, mais
qu'elles pourraient devenir actif plus tard, il devrait enregistrer ceci pour les actions
futures du noyau. d_poll()
fait ceci en appelant selrecord()
avec une structure selinfo pour ce
périphérique. La somme de toutes ces activités ressemblent à
quelque chose comme ceci:
static struct my_softc {
struct queue rx_queue; /* As example only - not required */
struct queue tx_queue; /* As example only - not required */
struct selinfo selp; /* Required */
} my_softc[NMYDEV];
...
static int
mydevpoll(dev_t dev, int events, struct proc *p)
{
int revents = 0; /* Events we found */
int s;
struct my_softc *sc = &my_softc[dev];
/* We can only check for IN and OUT */
if ((events & (POLLIN|POLLOUT)) == 0)
return(POLLNVAL);
s = splhigh()
;
/* Writes are if the transmit queue can take them */
if ((events & POLLOUT) &&
!IF_QFULL(sc->tx_queue))
revents |= POLLOUT;
/* ... while reads are OK if we have any data */
if ((events & POLLIN) &&
!IF_QEMPTY(sc->rx_queue))
revents |= POLLIN;
if (revents == 0)
selrecord(p, &sc->selp);
splx(s);
return revents;
}
d_select()
est utilisé dans la version 2.2 et
précédentes de FreeBSD. Au lieu de 'events', il prend un simple entier
'rw', qui peut être FREAD pour la lecture (comme dans POLLIN ci-dessus), FWRITE
pour l'écriture (comme dans POLLOUT ci-dessus), et 0 pour 'exception' - lorsque
quelque chose d'exceptionnel se produit, comme une carte étant
insérée ou retirée pour le pilote de pccard.
Pour 'select', le fragment correspondant à la description ci-dessus ressembleraient à ceci:
static int
mydevselect(dev_t dev, int rw, struct proc *p)
{
int ret = 0;
int s;
struct my_softc *sc = &my_softc[dev];
s = splhigh()
;
switch (rw) {
case FWRITE:
/* Writes are if the transmit queue can take them */
if (!IF_QFULL(sc->tx_queue))
ret = 1;
break;
case FREAD:
/* ... while reads are OK if we have any data */
if (!IF_QEMPTY(sc->rx_queue))
ret = 1;
break;
case 0:
/* This driver never get any exceptions */
break;
}
if(ret == 0)
selrecord(p, &sc->selp);
splx(s);
return(revents);
}
d_strategy()
La liste d'argument de d_strategy()
est comme suit :
void d_strategy(struct buf *bp)
d_strategy()
est utilisé pour les
périphériques utilisant des E/S de type disperser-regrouper (scatter-gather). C'est ce qu'il y a de plus courant dans un
périphérique de bloc. C'est sensiblement différent du modèle
de système V, où seulement le pilote de bloc fait une E/S de type
disperser-regrouper. Sous BSD, les périphériques de caractère sont
parfois sommé d'exécuter une E/S de type disperser-regrouper par
l'intermédiaire des appels systèmes readv()
et writev()
.
Cette structure est obligatoire, mais généralement elle est créée par config à partir du fichier de configuration de noyau. Elle est requise pour chaque périphérique, c'est à dire que si vous avez un pilote de périphérique contrôlant deux "serial boards", vous aurez deux structures isa_device. Si vous construisez un périphérique comme un LKM, vous devrez créer votre propre structure isa_device afin de refléter votre configuration (lignes 85 - 131 de pcaudio_lkm.c). Il y a une équivalence directe entre le fichier de configuration et la structureisa_device. La définition de /usr/src/sys/i386/isa/isa_device.h est :
struct isa_device { int id_id; /* device id */ struct isa_driver *id_driver; int id_iobase; /* base i/o address */ u_short id_irq; /* interrupt request */ short id_drq; /* DMA request */ caddr_t id_maddr; /* physical i/o memory address on bus (if any)*/ int id_msize; /* size of i/o memory */ inthand2_t *id_intr; /* interrupt interface routine */ int id_unit; /* unit number */ int id_flags; /* flags */ int id_scsiid; /* scsi id if needed */ int id_alive; /* device is present */ #define RI_FAST 1 /* fast interrupt handler */ u_int id_ri_flags; /* flags forregister_intr()
*/ int id_reconfig; /* hot eject device support (such as PCMCIA) */ int id_enabled; /* is device enabled */ int id_conflicts; /* we're allowed to conflict with things */ struct isa_device *id_next; /* used in isa_devlist inuserconfig()
*/ };
Cette structure est définie dans /usr/src/sys/i386/isa/isa_device.h, est est requise pour chaque pilote de périphérique. La définition est :
struct isa_driver { int (*probe) __P((struct isa_device *idp)); /* test whether device is present */ int (*attach) __P((struct isa_device *idp)); /* setup driver for a device */ char *name; /* device name */ int sensitive_hw; /* true if other probes confuse us */ };
C'est la structure employée par le code sondage/attachement (probe/attach) pour détecter et initialiser votre périphérique. Le membre probe est un pointeur à votre fonction permettant de sonder les périphériques. Le membre attach est un pointeur vers votre fonction d'attache. Le membre name est un pointeur de caractère sur le nom de deux ou trois lettres de votre pilote. C'est le nom enregistré pendant le processus de sondage/attachement (et probablement aussi dans lsdev). Le membre sensitive_hw est un indicateur qui aide le code de sondage à déterminer l'ordre du sondage.
Un instantiation typique est:
struct isa_driver mcddriver = { mcd_probe, mcd_attach, "mcd" };
probe()
probe()
prend un pointeur sur une structure isa_device
comme argument et renvoie un int. La valeur de retour est ``zéro'' ou
``non-zéro '' quant à l'absence ou à la présence de votre
périphérique. Ce point d'entrée peut être
déclaré comme static parce qu'il est
accessible par l'intermédiaire du membre probe de la structre
isa_driver. Cette fonction est destinée à détecter la
présence de votre périphérique seulement et ne devrait faire aucune
configuration du périphérique elle-même.
attach()
attach()
prend également un pointeur sur une
structure isa_device comme argument et renvoie un int. La valeur de retour est
également ``zéro'' ou ``non-zéro'' indiquant si l'attache a
réussie. Cette fonction est destinée pour faire n'importe quelle
initialisation spéciale du périphérique aussi bien que pour
confirmer que le périphérique est utilisable. Il devrait aussi être
déclaré static parce qu'il est
accesible par le membre attach de la structure
isa_driver .
Structure struct pci_device
nom : Le nom abrégé du périphérique.
sonde: Contrôle si le pilote peut supporter un périphérique avec
ce type. L'étiquette peut être employée pour obtenir plus
d'information avec pci_read_conf()
. Voir ci-dessous. Elle
renvoie une chaîne de caractères avec le nom du périphérique,
ou un pointeur NULL si le pilote ne peut pas supporter ce périphérique.
attache: Assigne une structure de contrôle et la prépare. Cette fonction peut utiliser les fonctions de mapping PCI. Voir ci-dessous. (identification de configuration) ou type.
compte: Un pointeur sur un compteur d'unité. Il est employé par le configurateur de PCI pour assigner des numéros.
Structure struct scsi_adapter
Structure struct scsi_device
Structure struct scsi_ctlr_config
Structure struct scsi_device_config
Structure struct scsi_link
Structure struct slot_cont
Structure struct pccard_drv
Structure struct pccard_dev
Structure struct slot
Dans FreeBSD, le support des bus d'ISA et EISA est spécifique à i386. Tandis que FreeBSD lui-même est actuellement disponible sur la plateforme i386, un certain effort a été fait pour faire du code portable pour PCI, PCCARD, et SCSI. Le code spécifique à ISA et EISA réside dans /usr/src/sys/i386/isa et /usr/src/sys/i386/eisa respectivement. Le code indépendant de la machine de PCI, de PCCARD, et de SCSI réside dans /usr/src/sys/{pci,pccard,scsi}. Le code spécifique i386 quand à lui réside dans /usr/src/sys/i386/{pci, pccard, scsi}.
Dans FreeBSD, un module de gestion de périphérique peut être soit sous forme binaire soit sous forme de sources. Il n'y a aucun endroit ``officiel'' pour mettre les binaires des pilotes de périphériques. Les systèmes BSD utilisent quelque chose comme sys/i386/OBJ. Puisque la plupart des pilotes sont distribués dans les sources, la discussion suivante se rapporte à un source pilote de périphérique. Des binaires de pilotes de périphériques sont parfois fournis par les constructeurs de matériel qui souhaitent maintenir les sources de manière propriétaire.
Un pilote typique a son code source sous forme de fichier C, comme dev.c. Le pilote peut également inclure des fichiers; devreg.h contient typiquement des déclarations publiques de registre de périphérique, des macros, et d'autres déclarations spécifique au pilote de périphérique. Quelques pilotes appellent parfois ce fichier devvar.h. Quelques pilotes, tels que le dgb (pour le Digiboard PC/Xe), exigent que du microcode soit chargé sur la carte. Pour le pilote de dgb le microcode est compilé et reporté dans un fichier d'en-tête par file2c.
Si le pilote de périphérique a des structures de données et des ioctl qui sont spécifiques au pilote de périphérique ou périphérique, et doivent être accessibles de l'espace-utilisateur, elles devraient être mises dans un fichier d'en-tête séparé qui résidera dans /usr/include/machine/ (certaines de ces derniers résident dans /usr/include/sys/). Ceux-ci est typiquement nommé quelque chose comme ioctl_dev.h ou devio.h.
Si un pilote écrit depuis l'espace d'utilisateur est identique à un périphérique qui existe déjà, il faut prendre garde à utiliser les mêmes interfaces ioctl et structures de données. Par exemple, de l'espace utilisateur, un lecteur de SCSI CDROM devrait être identique à un lecteur de cdrom IDE; ou une ligne série sur une carte intelligente multiport (Digiboard, Cyclades...) devrait être identique à un périphérique sio. Ces périphériques ont une interface définie relativement bonne et devraient être utilisées.
Il y a deux méthodes pour lier un pilote dans le noyau, statiquement et le modèle LKM. La première méthode est assez standard à travers la famille *BSD. L'autre méthode a été initialement développée par Sun (je crois), et a été mis en application dans BSD en utilisant le modèle de Sun. Je ne crois pas que l'implémentation actuelle utilise encore le moindre code de Sun.
Les étapes exigées pour ajouter votre pilote au noyau standard de FreeBSD sont
Ajout à la liste des pilotes de périphérique
Ajout d'une entrée au [bc]devsw
Ajout d'une entrée du pilote de périphérique au fichier de configuration du noyau
config, compilation et installation du noyau
créer les fichiers spéciaux requis
redémarrage
Le modèle standard pour ajouter un module de gestion de périphérique au noyau de Berkeley est d'ajouter votre pilote à la liste des périphériques connus. Cette liste dépend de l'architecture du CPU. Si le périphérique n'est pas spécifique i386 (PCCARD, PCI, SCSI), le fichier est dans /usr/src/sys/conf/files. Si le périphérique est spécifique i386, utilisez /usr/src/sys/i386/conf/files.i386. Une ligne typique ressemblerait à :
i386/isa/joy.c optional joy device-driver
Le premier champ relatif est le chemin du module de pilote par rapport à /usr/src/sys. Pour le cas d'un pilote binaire, le chemin d'accès serait quelque chose comme i386/OBJ/joy.o.
Le deuxième champ indique à config(8) que c'est un pilote facultatif. Quelques périphériques sont obligatoires pour que le noyau puisse être construit.
Le troisième champ est le nom du périphérique.
Le quatrième champ indique à config que c'est un pilote de périphérique (par opposition à juste facultatif). Ceci dit à config de créer des entrées pour le périphérique dans dans des structures de /usr/src/sys/compile/KERNEL/ioconf.c.
Il est également possible de créer un fichier /usr/src/sys/i386/conf/files.KERNEL dont le contenu ignorera le fichier par défaut files.i386, mais seulement pour le noyau ``KERNEL''.
Maintenant vous devez éditer /usr/src/sys/i386/i386/conf.c pour faire une entrée pour votre pilote. Quelque part au début, vous devez déclarer vos points d'entrée. L'entrée pour le pilote du joystick est:
#include "joy.h" #if NJOY > 0 d_open_t joyopen; d_close_t joyclose; d_rdwr_t joyread; d_ioctl_t joyioctl; #else #define joyopen nxopen #define joyclose nxclose #define joyread nxread #define joyioctl nxioctl #endif
Cela définit vos points d'entrée, ou points d'entrée nuls qui renverront ENXIO quand appelé (clause #else).
Le fichier d'en-tête ``joy.h'' est automatiquement produit par config quand l'arborescence de construction du noyau est créé. Cela se réduit habituellement à une seule ligne comme :
#define NJOY 1
ou
#define NJOY 0
ce qui définit le nombre de vos périphériques dans votre noyau.
Vous devez de plus ajouter un slot au cdevsw[], ou au bdevsw[], selon que ce soit un périphérique caractère, périphérique bloc, ou les deux si c'est un périphérique bloc avec une interface brute. L'entrée pour le pilote du joystick est:
/* open, close, read, write, ioctl, stop, reset, ttys, select, mmap, strat */ struct cdevsw cdevsw[] = { ... { joyopen, joyclose, joyread, nowrite, /*51*/ joyioctl, nostop, nullreset, nodevtotty,/*joystick */ seltrue, nommap, NULL}, ... }
L'ordre est ce qui détermine le nombre majeur de votre périphérique. C'est pourquoi il y aura toujours une entrée pour votre pilote, que ce soit des points d'entrée nuls, ou des points d'entrée actuels. Il est probablement intéressant de noter que c'est sensiblement différent de SCO et d'autres dérivés du système V, où n'importe quel périphérique (dans la théorie) peut avoir n'importe quel nombre majeur. C'est en grande partie un avantage sur FreeBSD, sur la manière dont les fichiers spéciaux de périphérique sont créés. Nous reviendrons en détail sur ceci plus tard.
Ceci ajoute simplement une ligne décrivant votre périphérique. La ligne de description du joystick est :
device joy0 at isa? port "IO_GAME"Ceci indique que nous avons un périphérique appelé ``joy0'' sur le bus ISA en utilisant le port E/S ``IO_GAME'' (IO_GAME est une macro définie dans /usr/src/sys/i386/isa/isa.h).
Une entrée légèrement plus compliquée est pour le pilote ``ix'' :
device ix0 at isa? port 0x300 net irq 10 iomem 0xd0000 iosiz 32768 vector ixintrCeci indique que nous avons un périphérique appelé `ix0 ' sur le bus ISA. Il utilise le port E/S 0x300. Son interruption sera masqué par d'autres périphériques dans la classe réseau. Il utilise l'interruption 10. Il utilise 32k de mémoire partagée à l'adresse physique 0xd0000. Il le définit également son pilote d'interruption comme étant ``
ixintr()
''Maintenant avec notre fichier de configuration en main, nous pouvons créer un répertoire de compilation du noyau. Cela peut être fait en tapant :
# config KERNELoù KERNEL est le nom de votre fichier de configuration. La configuration crée un arbre de compilation pour votre noyau dans /usr/src/sys/compile/KERNEL. Elle crée le fichier makefile, quelques fichiers C, et quelques fichiers H avec des macros définissant le nombre de chaque périphérique à inclure dans votre votre noyau.
Maintenant vous pouvez aller dans le répertoire de compilation et construire votre noyau. À chaque fois que vous lancerez config, votre arbre de construction précédent sera retiré, à moins que vous ne lancez config avec un -n. Si vous avez configuré et compilé un noyau GENERIC, vous pouvez faire un ``make links'' afin d'éviter de compiler certains fichiers à chaque itération. Typiquement, je lance :
# make depend links allsuivi d'un ``make install'' quand le noyau me convient.
Sur FreeBSD, vous avez la responsabilité de faire vos propres fichiers spéciaux de périphérique. Le nombre majeur de votre périphérique est déterminé par le nombre de slots dans le commutateur de périphérique. Le nombre mineur est dépendant du pilote, naturellement. Vous pouvez soit exécuter mknod depuis la ligne de commande, soit laisser faire le travail à /dev/MAKEDEV.local, ou même /dev/MAKEDEV. Je crée parfois un script MAKEDEV.dev qui peut être soit lancé de manière autonome soit collé dans /dev/MAKEDEV.local.
C'est la partie facile. Il y a un certain nombre de méthodes pour faire ceci,
reboot, fastboot, shutdown - r, couper le courant, etc. Au démarrage, vous devriez
voir votre XXprobe()
appelé, et si tout marche,
votre attach()
aussi.
Il n'y a vraiment aucune procédure définie pour écrire un pilote de LKM. Ce qui suit est ma propre conception après expérimentation avec l'interface de périphérique LKM et en regardant le modèle standard de module de gestion de périphérique, c'est une manière d'ajouter une interface LKM à un pilote existant sans toucher aux sources (ou binaire) initiaux de pilote . On recommande cependant, que si vous projetez de distribuer les sources de votre pilote, que les parties spécifiques LKM devraient faire partie du pilote lui-même, compilé de manière conditionnelle par la macro LKM (c.-à-d. #ifdef LKM).
Cette section se concentrera sur la manière d'écrire la partie spécifique LKM du pilote. Nous supposerons que nous avons écrit un pilote qui atterrira dans le modèle standard de gestion de périphérique, que nous voudrions maintenant mettre en application comme étant LKM. Nous utiliserons le pilote de pcaudio comme pilote d'exemple, et développerons une entrée LKM. La source et le fichier makefile pour le LKM pcaudio , ``pcaudio_lkm.c'' et ``Makefile'', devraient être dans placé /usr/src/lkm/pcaudio. Ce qui suit est le code commenté de pcaudio_lkm.c.
Lignes 17 - 26
Ceci inclut le fichier ``pca.h'' et fait une compilation conditionnelle du reste de LKM suivant que vous avez défini ou non le pilote de périphérique pcaudio. Cela imite le comportement de config. Dans un pilote de périphérique standard, config produit le fichier pca.h depuis le nombre de périphériques pca le fichier de config.
17 /* 18 * figure out how many devices we have.. 19 */ 20 21 #include "pca.h" 22 23 /* 24 * if we have at least one ... 25 */ 26 #if NPCA > 0
Lignes 27 - 37
Les fichiers d'en-tête requis depuis divers répertoire d'inclusion.
27 #include <sys/param.h> 28 #include <sys/systm.h> 29 #include <sys/exec.h> 30 #include <sys/conf.h> 31 #include <sys/sysent.h> 32 #include <sys/lkm.h> 33 #include <sys/errno.h> 34 #include <i386/isa/isa_device.h> 35 #include <i386/isa/isa.h> 36 37
Lignes 38 - 51
déclarent vos points d'entrée comme externs .
38 /* 39 * declare your entry points as externs 40 */ 41 42 extern int pcaprobe(struct isa_device *); 43 extern int pcaattach(struct isa_device *); 44 extern int pcaopen(dev_t, int, int, struct proc *); 45 extern int pcaclose(dev_t, int, int, struct proc *); 46 extern int pcawrite(dev_t, struct uio *, int); 47 extern int pcaioctl(dev_t, int, caddr_t); 48 extern int pcaselect(dev_t, int, struct proc *); 49 extern void pcaintr(struct clockframe *); 50 extern struct isa_driver pcadriver; 51
Lignes 52 - 70
Cela crée la table d'entrée de commutateur de périphérique pour votre pilote. Cette table est en gros entièrement mise dans le système de commutation de périphériques à l'emplacement indiqué par votre nombre majeur. Dans le modèle standard, c'est dans /usr/src/sys/i386/i386/conf.c. NOTE: vous ne pouvez pas sélectionner un nombre majeur de périphérique plus grand que ce qui existe dans conf.c, par exemple il y a 67 slots pour des périphériques caractère, vous ne pouvez pas utiliser un périphérique (caractère) de numéro majeur 67 ou plus, sans avoir d'abord réservé de l'espace dans conf.c.
52 /* 53 * build your device switch entry table 54 */ 55 56 static struct cdevsw pcacdevsw = { 57 (d_open_t *) pcaopen, /* open */ 58 (d_close_t *) pcaclose, /* close */ 59 (d_rdwr_t *) enodev, /* read */ 60 (d_rdwr_t *) pcawrite, /* write */ 61 (d_ioctl_t *) pcaioctl, /* ioctl */ 62 (d_stop_t *) enodev, /* stop?? */ 63 (d_reset_t *) enodev, /* reset */ 64 (d_ttycv_t *) enodev, /* ttys */ 65 (d_select_t *) pcaselect, /* select */ 66 (d_mmap_t *) enodev, /* mmap */ 67 (d_strategy_t *) enodev /* strategy */ 68 }; 69 70
Lignes 71 - 131
cette section est analogue à la déclaration de fichier de configuration de votre périphérique. Les membres de la structure isa_device sont remplis grace à ce qu'il connaît de votre périphérique, port E/S, segment partagé de mémoire, etc... Nous n'aurons probablement jamais un besoin de deux périphériques pcaudio dans le noyau, mais cet exemple montre comment périphériques multiples peuvent être supportés.
71 /* 72 * this lkm arbitrarily supports two 73 * instantiations of the pc-audio device. 74 * 75 * this is for illustration purposes 76 * only, it doesn't make much sense 77 * to have two of these beasts... 78 */ 79 80 81 /* 82 * these have a direct correlation to the 83 * config file entries... 84 */ 85 struct isa_device pcadev[NPCA] = { 86 { 87 11, /* device id */ 88 &pcadriver, /* driver pointer */ 89 IO_TIMER1, /* base io address */ 90 -1, /* interrupt */ 91 -1, /* dma channel */ 92 (caddr_t)-1, /* physical io memory */ 93 0, /* size of io memory */ 94 pcaintr , /* interrupt interface */ 95 0, /* unit number */ 96 0, /* flags */ 97 0, /* scsi id */ 98 0, /* is alive */ 99 0, /* flags for register_intr */ 100 0, /* hot eject device support */ 101 1 /* is device enabled */ 102 }, 103 #if NPCA >1 104 { 105 106 /* 107 * these are all zeros, because it doesn't make 108 * much sense to be here 109 * but it may make sense for your device 110 */ 111 112 0, /* device id */ 113 &pcadriver, /* driver pointer */ 114 0, /* base io address */ 115 -1, /* interrupt */ 116 -1, /* dma channel */ 117 -1, /* physical io memory */ 118 0, /* size of io memory */ 119 NULL, /* interrupt interface */ 120 1, /* unit number */ 121 0, /* flags */ 122 0, /* scsi id */ 123 0, /* is alive */ 124 0, /* flags for register_intr */ 125 0, /* hot eject device support */ 126 1 /* is device enabled */ 127 }, 128 #endif 129 130 }; 131
Lignes 132 - 139
Ceci appelle la macro MOD_DEV du préprocesseur C, qui installe un module de gestion de périphérique de LKM, par opposition à un système de fichiers LKM, ou un appel système de LKM.
132 /* 133 * this macro maps to a function which 134 * sets the LKM up for a driver 135 * as opposed to a filesystem, system call, or misc 136 * LKM. 137 */ 138 MOD_DEV("pcaudio_mod", LM_DT_CHAR, 24, &pcacdevsw); 139
Lignes 140 - 168
c'est la fonction qui sera appelée lorsque le pilote sera chargé. Cette fonction essaye de fonctionner comme /sys/i386/isa/isa.c qui fait les appels de probe/attach pour un pilote au moment du redémarrage. La plus grande astuce ici est qu'il met en correspondance l'adresse physique du segment partagé de mémoire, qui est indiqué dans la structure isa_device à une adresse virtuelle du noyau. Normalement, l'adresse physique est mise dans le fichier de configuration qui construit la structure isa_device dans /usr/src/sys/compile/KERNEL/ioconf.c. La séquence probe/attach de /usr/src/sys/isa/isa.c traduit l'adresse physique en une virtuelle de sorte que dans les sous-programmes de probe/attach vous puissiez faire des choses comme
(int *)id->id_maddr = something;
et se réfère juste au segment partagé de mémoire par l'intermédiaire de pointeurs.
140 /*
141 * this function is called when the module is
142 * loaded; it tries to mimic the behavior
143 * of the standard probe/attach stuff from
144 * isa.c
145 */
146 int
147 pcaload()
{
148 int i;
149 uprintf("PC Audio Driver Loaded\n");
150 for (i=0; i<NPCA; i++){
151 /*
152 * this maps the shared memory address
153 * from physical to virtual, to be
154 * consistent with the way
155 * /usr/src/sys/i386/isa.c handles it.
156 */
157 pcadev[i].id_maddr -=0xa0000;
158 pcadev[i].id_maddr += atdevbase;
159 if ((*pcadriver.probe)(pcadev+i)) {
160 (*(pcadriver.attach))(pcadev+i);
161 } else {
162 uprintf("PC Audio Probe Failed\n");
163 return(1);
164 }
165 }
166 return 0;
167 }
168
Lignes 169 - 179
c'est la fonction appelée quand votre pilote n'est pas chargé; il affiche juste un message à cet effet.
169 /*
170 * this function is called
171 * when the module is unloaded
172 */
173
174 int
175 pcaunload()
{
176 uprintf("PC Audio Driver Unloaded\n");
177 return 0;
178 }
179
Lignes 180 - 190
c'est le point d'entrée qui est indiqué sur la ligne de commande de modload. Par convention il est nommé <dev>_mod. C'est ainsi qu'il est défini dans bsd.lkm.mk, le makefile qui construit le LKM. Si vous nommez votre module suivant cette convention, vous pouvez faire ``make load'' et ``make unload'' de /usr/src/lkm/pcaudio.
Note : Il y a eu tellement de révisions entre la version 2.0 et 2.1. Il peut ou ne peut ne pas être possible d'écrire un module qui est portable pour chacune des trois versions.
180 /* 181 * this is the entry point specified 182 * on the modload command line 183 */ 184 185 int 186 pcaudio_mod(struct lkm_table *lkmtp, int cmd, int ver) 187 { 188 DISPATCH(lkmtp, cmd, ver, pcaload, pcaunload, nosys); 189 } 190 191 #endif /* NICP > 0 */
Cette structure contient quelques informations sur l'état du périphérique et de son pilote. Elle est définie dans /usr/src/sys/sys/devconf.h comme ci-dessous :
struct devconf { char dc_name[MAXDEVNAME]; /* name */ char dc_descr[MAXDEVDESCR]; /* description */ int dc_unit; /* unit number */ int dc_number; /* unique id */ char dc_pname[MAXDEVNAME]; /* name of the parent device */ int dc_punit; /* unit number of the parent */ int dc_pnumber; /* unique id of the parent */ struct machdep_devconf dc_md; /* machine-dependent stuff */ enum dc_state dc_state; /* state of the device (see above) */ enum dc_class dc_class; /* type of device (see above) */ size_t dc_datalen; /* length of data */ char dc_data[1]; /* variable-length data */ };
Cette structure contient toutes les informations sur un processus. Elle est dans définie /usr/src/sys/sys/proc.h:
/* * Description of a process. * * This structure contains the information needed to manage a thread of * control, known in UN*X as a process; it has references to substructures * containing descriptions of things that the process uses, but may share * with related processes. The process structure and the substructures * are always addressable except for those marked "(PROC ONLY)" below, * which might be addressable only on a processor on which the process * is running. */ struct proc { struct proc *p_forw; /* Doubly-linked run/sleep queue. */ struct proc *p_back; struct proc *p_next; /* Linked list of active procs */ struct proc **p_prev; /* and zombies. */ /* substructures: */ struct pcred *p_cred; /* Process owner's identity. */ struct filedesc *p_fd; /* Ptr to open files structure. */ struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */ struct vmspace *p_vmspace; /* Address space. */ struct sigacts *p_sigacts; /* Signal actions, state (PROC ONLY). */ #define p_ucred p_cred->pc_ucred #define p_rlimit p_limit->pl_rlimit int p_flag; /* P_* flags. */ char p_stat; /* S* process status. */ char p_pad1[3]; pid_t p_pid; /* Process identifier. */ struct proc *p_hash; /* Hashed based on p_pid for kill+exit+... */ struct proc *p_pgrpnxt; /* Pointer to next process in process group. */ struct proc *p_pptr; /* Pointer to process structure of parent. */ struct proc *p_osptr; /* Pointer to older sibling processes. */ /* The following fields are all zeroed upon creation in fork. */ #define p_startzero p_ysptr struct proc *p_ysptr; /* Pointer to younger siblings. */ struct proc *p_cptr; /* Pointer to youngest living child. */ pid_t p_oppid; /* Save parent pid during ptrace. XXX */ int p_dupfd; /* Sideways return value from fdopen. XXX */ /* scheduling */ u_int p_estcpu; /* Time averaged value of p_cpticks. */ int p_cpticks; /* Ticks of cpu time. */ fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */ void *p_wchan; /* Sleep address. */ char *p_wmesg; /* Reason for sleep. */ u_int p_swtime; /* Time swapped in or out. */ u_int p_slptime; /* Time since last blocked. */ struct itimerval p_realtimer; /* Alarm timer. */ struct timeval p_rtime; /* Real time. */ u_quad_t p_uticks; /* Statclock hits in user mode. */ u_quad_t p_sticks; /* Statclock hits in system mode. */ u_quad_t p_iticks; /* Statclock hits processing intr. */ int p_traceflag; /* Kernel trace points. */ struct vnode *p_tracep; /* Trace to vnode. */ int p_siglist; /* Signals arrived but not delivered. */ struct vnode *p_textvp; /* Vnode of executable. */ char p_lock; /* Process lock (prevent swap) count. */ char p_pad2[3]; /* alignment */ /* End area that is zeroed on creation. */ #define p_endzero p_startcopy /* The following fields are all copied upon creation in fork. */ #define p_startcopy p_sigmask sigset_t p_sigmask; /* Current signal mask. */ sigset_t p_sigignore; /* Signals being ignored. */ sigset_t p_sigcatch; /* Signals being caught by user. */ u_char p_priority; /* Process priority. */ u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */ char p_nice; /* Process "nice" value. */ char p_comm[MAXCOMLEN+1]; struct pgrp *p_pgrp; /* Pointer to process group. */ struct sysentvec *p_sysent; /* System call dispatch information. */ struct rtprio p_rtprio; /* Realtime priority. */ /* End area that is copied on creation. */ #define p_endcopy p_addr struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */ struct mdproc p_md; /* Any machine-dependent fields. */ u_short p_xstat; /* Exit status for wait; also stop signal. */ u_short p_acflag; /* Accounting flags. */ struct rusage *p_ru; /* Exit information. XXX */ };
La structure struct buf est employée pour s'interfacer avec le cache de la mémoire tampon. Elle est dans définie /usr/src/sys/sys/buf.h :
/* * The buffer header describes an I/O operation in the kernel. */ struct buf { LIST_ENTRY(buf) b_hash; /* Hash chain. */ LIST_ENTRY(buf) b_vnbufs; /* Buffer's associated vnode. */ TAILQ_ENTRY(buf) b_freelist; /* Free list position if not active. */ struct buf *b_actf, **b_actb; /* Device driver queue when active. */ struct proc *b_proc; /* Associated proc; NULL if kernel. */ volatile long b_flags; /* B_* flags. */ int b_qindex; /* buffer queue index */ int b_error; /* Errno value. */ long b_bufsize; /* Allocated buffer size. */ long b_bcount; /* Valid bytes in buffer. */ long b_resid; /* Remaining I/O. */ dev_t b_dev; /* Device associated with buffer. */ struct { caddr_t b_addr; /* Memory, superblocks, indirect etc. */ } b_un; void *b_saveaddr; /* Original b_addr for physio. */ daddr_t b_lblkno; /* Logical block number. */ daddr_t b_blkno; /* Underlying physical block number. */ /* Function to call upon completion. */ void (*b_iodone) __P((struct buf *)); /* For nested b_iodone's. */ struct iodone_chain *b_iodone_chain; struct vnode *b_vp; /* Device vnode. */ int b_pfcent; /* Center page when swapping cluster. */ int b_dirtyoff; /* Offset in buffer of dirty region. */ int b_dirtyend; /* Offset of end of dirty region. */ struct ucred *b_rcred; /* Read credentials reference. */ struct ucred *b_wcred; /* Write credentials reference. */ int b_validoff; /* Offset in buffer of valid region. */ int b_validend; /* Offset of end of valid region. */ daddr_t b_pblkno; /* physical block number */ caddr_t b_savekva; /* saved kva for transfer while bouncing */ void *b_driver1; /* for private use by the driver */ void *b_driver2; /* for private use by the driver */ void *b_spc; struct vm_page *b_pages[(MAXPHYS + PAGE_SIZE - 1)/PAGE_SIZE]; int b_npages; };
Cette structure est utilisée pour déplacer des données entre le
noyau et les espaces utilisateur par les appels système de read()
et de write()
. Il est dans
défini /usr/src/sys/sys/uio.h :
struct uio { struct iovec *uio_iov; int uio_iovcnt; off_t uio_offset; int uio_resid; enum uio_seg uio_segflg; enum uio_rw uio_rw; struct proc *uio_procp; };
FreeBSD Kernel Sources http://www.freebsd.org
NetBSD Kernel Sources http://www.netbsd.org
Writing Device Drivers: Tutorial and Reference; Tim Burke, Mark A. Parenti, Al, Wojtas; Digital Press, ISBN 1-55558-141-2.
Writing A Unix Device Driver; Janet I. Egan, Thomas J. Teixeira; John Wiley & Sons, ISBN 0-471-62859-X.
Writing Device Drivers for SCO Unix; Peter Kettle;
Ce document, ainsi que d'autres peut être téléchargé sur ftp.FreeBSD.org/pub/FreeBSD/doc/.
Pour toutes questions à propos de FreeBSD, lisez la documentation avant de contacter <questions@FreeBSD.org>.
Pour les questions sur cette documentation, contactez <doc@FreeBSD.org>.