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

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

5.3. Idiosyncrasies du type périphérique

5.4. Idiosyncrasies du type bus

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