Le guide de l'auteur de pilotes de périphériques pour FreeBSD

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 :

  1. Le copyright ci-dessus, la présente liste de conditions et l'avertissement qui la suit doivent figurer dans le code source.

  2. 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 .


1. Spécificité de FreeBSD2.x

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 si vous voulez l'aider à ce sujet.


2. Généralité

Le noyau de FreeBSD est très bien documenté, malheureusement il est entièrement écrit en `C'.


3. Types de pilotes de module de périphériques.

3.1. Caractère


3.1.2. Points d'entrée

3.1.2.1. 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.


3.1.2.2. 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().


3.1.2.3. 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().


3.1.2.4. 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).


3.1.2.8. 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);
}

3.1.2.10. 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().


3.2. Bloc


3.2.2. Points d'entrée

3.2.2.1. d_open()

Décrit dans la section périphérique de caractère.


3.2.2.2. d_close()

Décrit dans la section périphérique de caractère.


3.2.2.3. d_strategy()

Décrit dans la section périphérique de caractère.


3.2.2.4. d_ioctl()

Décrit dans la section périphérique de caractère.


4. Bus Supportés

4.1. ISA -- Architecture Standard d'Industrie (Industry Standard Architecture

4.1.1. Structures de données

4.1.1.1. Structure struct isa_device

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 for register_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 in userconfig() */
};

4.1.1.2. Structure struct isa_driver

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" };

4.1.2. Points d'entrée

4.1.2.1. 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.


4.1.2.2. 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 .


4.3. PCI -- Bus d'interconnexion Périphérique (Peripheral Computer Interconnect)

4.3.1. Structures de données

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.


5. Incorporation dans le noyau

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.


5.1. Modèle Standard

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


5.1.1. Ajout à la liste des pilotes de périphérique

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''.


5.1.2. Faire de la place dans conf.c

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.


5.1.3. Ajout de votre périphérique dans le fichier de configuration.

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 ixintr
Ceci 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()''


5.1.4. config du noyau.

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 KERNEL
où 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 all
suivi d'un ``make install'' quand le noyau me convient.


5.1.5. Créer les fichiers spéciaux de périphériques

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.


5.1.6. Redémarrage

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.


5.2. Module du noyau à chargement dynamique (LKM)

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 */

6. Support du noyau

6.1. Structures de données

6.1.1. Structure struct kern_devconf

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 */
};

6.1.2. Structure struct proc

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 */
};

6.1.3. Structure struct buf

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;
};

6.1.4. Structure struct uio

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;
};

6.3. Références.

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>.