Programmation modules sous Linux  (partie 2)

III . Pilote PCI
1 . Structure
2 . Description
3 . Enregistrement/Suppression
4 . La méthode probe
5 . Fonctions utilitaires
6 . Lecture de registres PCI
7 . Espaces mémoire et E/S
8 . Méthodes open et release
9 . Exemple générique
10 . Bibliographie

1 . Structure d'un pilote PCI

L'API du noyau LINUX inclut de nombreuses fonctions dédiées facilitant fortement la gestion de ce type de périphérique. Les prototypes de ces fonctions sont définis dans le fichier <linux/pci.h>.

On définit une structure pci_driver qui pointe sur les routines de contrôle du pilote :

static struct pci_driver mondriver_pci =
{
  name: "mondriver",
  id_table: mondriver_id_table, /* Liste des devices supportés */
  probe: mondriver_probe, /* Détection device */
  remove: mondriver_remove, /* Libération device */
};


Le BIOS PCI se charge de l'affectation des paramètres dynamiques au démarrage du système. Cependant, le BIOS n'effectue pas le démarrage du périphérique et la méthode mondriver_probe() devra se charger de cette initialisation. De la même manière, la méthode mondriver_remove devra se charger des libérations des ressources et autres arrêts matériels.


2 . Description du pilote PCI

Le tableau mondriver_id_table décrit les périphériques PCI (identifiants standardisés) que supporte le pilote.
À chaque fois qu’un périphérique définit dans le tableau mondriver_id_table change d’état (détection, suppression...), la fonction correspondante de la structure pci_driver est appelée (mondriver_probe(), mondriver_remove()...).
Dans cet exemple, le pilote supporte un seul type de périphérique défini logiquement par son VENDOR_ID et son DEVICE_ID :

typedef enum { MY_BOARD = 0, } board_t;

static struct { const char *name;} board_info[] __devinitdata =
{ {"Mon périphérique PCI"} };

static struct pci_device_id mondriver_id_table[] __devinitdata =
{
  {VENDOR_ID, DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MY_BOARD},
  {0,} /* 0 termine la liste */
};

MODULE_DEVICE_TABLE(pci, mondriver_id_table);


3 . Enregistrement et suppression

L'enregistrement et la suppression du pilote se font respectivement dans les fonction module_init() et module_exit() en utilisant les primitives :

Enregistrement :
int pci_register_driver(struct pci_driver *drv);
int pci_module_init(struct pci_driver *drv);


Remarque : pci_module_init() inclus l'appel à pci_register_driver() pour les pilotes sous forme de module.

Suppression :
void pci_unregister_driver(struct pci_driver *drv);


4 . La méthode probe

La fonction mondriver_probe() doit contenir tout le code nécessaire à l’initialisation d’un périphérique (ex : allocations privées, initialisations matérielles, réservation des ressources...).
Les variables propres au périphérique sont en général stockées dans une structure privée qui sera disponible sous forme d'un pointeur calculé à partir du paramètre dev de type pci_dev.

struct struct_private {
  /* Champs généraux */
  struct list_head link; /* Double linked list */
  struct pci_dev *dev; /* PCI device */
  board_t type; /* Board type */
  int minor; /* Minor number */
  devfs_handle_t devfs_handle /* Devfs file handle */
  void *mmio[DEVICE_COUNT_RESOURCE]; /* Remaped I/O memory */
  u32 mmio_len[DEVICE_COUNT_RESOURCE]; /* Size of remaped I/O memory */
  u32 io[DEVICE_COUNT_RESOURCE]; /* Ports I/O */
  u32 io_len[DEVICE_COUNT_RESOURCE]; /* Size of ports I/O */

   /* Champs spécifiques */
  u32 iobase;
};

La liste des champs spécifiques peut être enrichie suivant le type de périphérique contrôlé.

De manière génrale, la méthode probe réalise les tâches suivantes :

Allocation de la structure privée de type struct_private avec kmalloc.
Lien de la strucutre privée à la structure du périphérique dev
Initialisation des valeurs de la structure de périphérique
Initialisation du périphérique au niveau matériel
Réservation des plages et ports d'entrée/sortie liées au périphérique
Remplissage de la structure privée avec les valeurs d'adresses BAR accessibles
Initialisation des interruptions pour le périphérique
Validation du contrôle de bus (bus mastering) pour le périphérique
Lien de la structure privée aux autres structures de ce type
Création du point d'entrée pour le système de fichier devfs


5 . Fonctions utilitaires

Un ensemble de fonctions utilitaires permet de manipuler les ressources et les en-têtes PCI :

void *pci_get_drvdata(struct pci_dev *dev) : récupère une donnée privée sur le descripteur du périphérique,

void pci_set_drvdata(struct pci_dev *dev, void *data) : attache une donnée privée sur le descripteur du périphérique,

int pci_enable_device(struct pci_dev *dev) : active le périphérique au niveau matériel,

void pci_disable_device(struct pci_dev *dev) : désactive le périphérique au niveau matériel,

int pci_{read,write}_config_{byte,word,dword}(struct pci_dev *dev, int addr, u{8,16,32} *val) : lit/écrit 8, 16 ou 32 bits dans les en-têtes PCI,

int pci_find_capability(struct pci_dev *dev, int capability) : détermine si le périphérique supporte une fonctionnalité PCI donnée,

int pci_request_regions(struct pci_dev *dev, char *name) : réserve automatiquement tous les ports et plages mémoire d’E/S du périphérique,

int pci_release_regions(struct pci_dev *dev) : libère automatiquement tous les ports et plages mémoire d’E/S du périphérique,

unsigned long pci_resource_{start,end,len,flags}(struct pci_dev *dev, int bar) : retourne l’adresse de début/fin, la longueur ou les caractéristiques d’une ressource d’E/S (port ou mémoire).


6 . Lecture de registres PCI

Exemple :

//qq registres PCI
static u8 mypin; static u8 myirq;
static u16 vendorid; static u16 deviceid;

/* Lecture de quelques registres PCI */
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &mypin);
pci_read_config_word(dev, PCI_VENDOR_ID, &vendorid);
pci_read_config_word(dev, PCI_DEVICE_ID, &deviceid);

printk(KERN_DEBUG "mondriver: mypin = %d\n", (u8)mypin);
printk(KERN_DEBUG "mondriver: vendorid = %#04x\n", (u16)vendorid);
printk(KERN_DEBUG "mondriver: deviceid = %#04x\n", (u16)deviceid);

// Si le peripherique ne gere pas les interruptions, la valeur mypin sera egale a zero
if(mypin)
{
  // la ligne d'interruption allouee par le BIOS est disponible dans le registre PCI_INTERRUPT_LINE
  pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
  printk(KERN_DEBUG "mondriver: myirq = %d\n", (u8)myirq);
  // on pourra alors installer une fonction d'interruption avec request_irq() pour ce
  // peripherique, en preconisant le mode SA_SHIRQ (interruption partagee)
}


7 . Espace mémoire et E/S

Exemple :

for (i=0; i < DEVICE_COUNT_RESOURCE; i++)
{
  // on affiche les ressources du peripherique
  if (pci_resource_start(dev, i) != 0)
    printk(KERN_DEBUG "pci6208: BAR %d (%#08x-%#08x), len=%d, flags=%#08x\n", i, (u32) pci_resource_start(dev, i),
      (u32) pci_resource_end(dev, i), (u32) pci_resource_len(dev, i), (u32) pci_resource_flags(dev, i));
  // plages memoires ?
  if (pci_resource_flags(dev, i) & IORESOURCE_MEM)
  {
    pci6208_private->mmio[i] = ioremap(pci_resource_start(dev, i), pci_resource_len(dev, i));
    if (pci6208_private->mmio[i] == NULL)
    {
      printk(KERN_WARNING "pci6208: unable to remap I/O memory\n");
      ret = -ENOMEM;
      goto cleanup_ioremap;
    }
    pci6208_private->mmio_len[i] = pci_resource_len(dev, i);
  }
  // ports E/S ?
  if (pci_resource_flags(dev, i) & IORESOURCE_IO)
  {
    pci6208_private->io[i] = pci_resource_start(dev, i);
    pci6208_private->io_len[i] = pci_resource_len(dev, i);
  }
}


8 . Méthodes open et release

La méthode open se résume à la recherche du pointeur de la structure privée dans la liste des structures.

static int mondriver_open(struct inode *inode, struct file *file)
{
  int minor = MINOR(inode->i_rdev);
  struct list_head *cur;
  struct struct_private *mondriver_private;

  list_for_each(cur, &privates)
  {
    mondriver_private = list_entry(cur, struct struct_private, link);

    if (mondriver_private->minor == minor)
    {
      file->private_data = mondriver_private;
      return 0;
    }
  }

  return -ENODEV;
}


La méthode release se résume a affecter le pointeur NULL au pointeur de structure privée.

static int mondriver_release(struct inode *inode, struct file *file)
{
  file->private_data = NULL;
  return 0;
}

La méthode open recherche le pointeur de la structure privée dans la liste des structures. Il faut préalablement avoir fait le lien dans la méthode probe :

/* Lie la structure privee aux autres structures de ce type */
list_add_tail(&mondriver_private->link, &privates);

De la même manière, il faut supprimer le lien dans la méthode remove :

list_del(&mondriver_private->link);


9 . Exemple générique

#define __KERNEL__
#define MODULE

#include <linux/kernel.h> /* printk() */
#include <linux/module.h> /* modules */
#include <linux/init.h> /* module_{init,exit}() */
#include <linux/devfs_fs_kernel.h> /* devfs support */
#include <linux/slab.h> /* kmalloc()/kfree() */
#include <linux/pci.h> /* pci_module_init() */
#include <linux/list.h> /* list_*() */
#include <linux/interrupt.h>
#include <asm/uaccess.h> /* copy_{from,to}_user() */

#include "sample_pci.h" /* VENDOR_ID et DEVICE_ID */

/*
* Arguments
*/

static int major = 0; /* Major number */

//qq registres PCI
static u8 mypin;
static u8 myirq;
static u16 vendorid;
static u16 deviceid;

MODULE_DESCRIPTION("Generic PCI driver");
MODULE_AUTHOR("Julien Gaulmin + Pierre Ficheux + tv");
MODULE_SUPPORTED_DEVICE("All");
MODULE_LICENSE("GPL");
MODULE_PARM_DESC(major, "Static major number (none = dynamic)");
MODULE_PARM(major, "i");

/*
* Liste des devices supportes
*/

//Pour l'exemple, un seul peripherique est defini
typedef enum { MY_BOARD = 0,} board_t;

static struct
{
  const char *name;
} board_info[] __devinitdata = { {BOARD_INFO}, };

//Le peripherique est identifie par son VENDOR_ID et DEVICE_ID (voir sample_pci.h)
//ces identifiants peuvent s'obtenir en faisant un : $ cat /proc/pci

static struct pci_device_id genpci_id_table[] __devinitdata =
{
  {VENDOR_ID, DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MY_BOARD}, //un peripherique par ligne
  {0,} /* 0 termine la liste */
};

MODULE_DEVICE_TABLE(pci, genpci_id_table);

/*
* Variables specifiques au peripherique
*/

static LIST_HEAD(privates);

//structure privee propre au peripherique
//cette structure sera liee ulterieurement a struct pci_dev *dev

struct struct_private
{
  //champs generaux
  struct list_head link; /* Double linked list */
  struct pci_dev *dev; /* PCI device */
  board_t type; /* Board type */
  int minor; /* Minor number */
  devfs_handle_t devfs_handle; /* Devfs file handle */
  //...
  //champs specifiques
  //...

};

static struct file_operations genpci_fops =
{
  owner: THIS_MODULE,
  // read: genpci_read,
  // write: genpci_write,
  // open: genpci_open,
  // ioctl: genpci_ioctl,
  // release: genpci_release,
};

//Le BIOS PCI se charge de l'affectation des parametres dynamiques
//au demarrage du systeme.
//Cependant, le BIOS PCI n'effectue pas le demarrage du peripherique.
//C'est la methode probe() qui devra se charger de l'initialisation
//complete du peripherique sur le bus PCI.

static int __devinit genpci_probe(struct pci_dev *dev, const struct pci_device_id *ent)
{
  int i, ret = 0;
  char devfs_name[20];
  struct struct_private *genpci_private;

  printk(KERN_INFO "genpci: found %s\n", board_info[ent->driver_data].name);
  printk(KERN_INFO "genpci: using major %d and minor %d for this device\n", major, (int)ent->driver_data);

  /* Allocation d'une structure privee */
  genpci_private = (struct struct_private *)kmalloc(sizeof(struct struct_private), GFP_KERNEL);
  if (genpci_private == NULL)
  {
    printk(KERN_WARNING "genpci: unable to allocate private structure\n");

    ret = -ENOMEM;
    goto cleanup_kmalloc;
  }

  /* on lie le pointeur a la structure du peripherique */
  pci_set_drvdata(dev, genpci_private);

  /* on initialise qq champs */
  genpci_private->dev = dev;
  genpci_private->minor = genpci_private->type = ent->driver_data;

  /* on initialise le peripherique au niveau materiel avant de l'utiliser */
  ret = pci_enable_device(dev);
  if (ret < 0)
  {
    printk(KERN_WARNING "genpci: unable to initialize PCI device\n");

    goto cleanup_pci_enable;
  }

  /* on reserve l'espace memoire du peripherique sur le bus PCI */

  // Cet espace est divise en 2 categories :
  // * les ports d'E/S (type IORESOURCE_IO)
  // * les plages memoires (type IORESOURCE_MEM)
  // Principe :
  // On effectue un mapping des registres sur des plages memoires (I/O memory).
  // Une plage memoire est similaire a de la RAM que le peripherique
  // met a la disposition sur le bus.
  // On procede en 2 etapes.

  // Etape 1 : on reserve les plages et ports E/S liees au peripherique
  // on peut verifier par un $ cat /proc/ioports

  ret = pci_request_regions(dev, "genpci");
  if (ret < 0)
  {
    printk(KERN_WARNING "genpci: unable to reserve PCI resources\n");

    goto cleanup_regions;
  }

  // Etape 2 :
  // On effectue un mapping pour rendre les plages accessibles.
  // Dans le cas de plages memoires (IORESOURCE_MEM),
  // on utilise la fonction ioremap().
  // On remplit la structure privee avec les valeurs d'adresses accessibles
  // depuis le pilote (cette partie peut etre specifique suivant la carte).

  for (i=0; i < DEVICE_COUNT_RESOURCE; i++)
  {
    // les ressources du peripherique ?
    // ces valeurs peuvent s'obtenir egalement par un $ cat /proc/pci
    //...

  }

  // L'acces aux registres PCI s'effectue par les fonctions suivantes :
  // pci_read_config_byte(), pci_read_config_word() et pci_read_config_dword()

  /* Lecture de quelques registres PCI */

  pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &mypin);
  pci_read_config_word(dev, PCI_VENDOR_ID, &vendorid);
  pci_read_config_word(dev, PCI_DEVICE_ID, &deviceid);

  printk(KERN_DEBUG "genpci: mypin = %d\n", (u8)mypin);
  printk(KERN_DEBUG "genpci: vendorid = %#04x\n", (u16)vendorid);
  printk(KERN_DEBUG "genpci: deviceid = %#04x\n", (u16)deviceid);

  // Si le peripherique ne gere pas les interruptions,
  // la valeur mypin sera egale a zero

  if(mypin)
  {
    // la ligne d'interruption allouee par le BIOS est disponible
    // dans le registre PCI_INTERRUPT_LINE

    pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &myirq);
    printk(KERN_DEBUG "genpci: myirq = %d\n", (u8)myirq);
    // on pourra alors installer une fonction d'interruption
    // avec request_irq() pour ce peripherique,
    // en preconisant le mode SA_SHIRQ (interruption partagee)

  }

  /* Validation du controle de bus (bus-mastering) */
  // Cette fonction permet aux peripheriques PCI de dialoguer
  // sans passer par le CPU.

  pci_set_master(dev);

  /* Creation du point d'entree dans le systeme devfs */
  sprintf(devfs_name, "genpci/device_%d", ent->driver_data);

  genpci_private->devfs_handle = devfs_register(NULL, devfs_name, DEVFS_FL_DEFAULT, major, ent->driver_data, S_IFCHR | S_IRUGO | S_IWUSR, &genpci_fops, dev);

  /* Lie la structure privee aux autres structures de ce type */
  list_add_tail(&genpci_private->link, &privates);

  return 0;

  cleanup_ioremap:
  pci_release_regions(dev);
  cleanup_regions:
  pci_disable_device(dev);
  cleanup_pci_enable:
  kfree(genpci_private);
  cleanup_kmalloc:
  return ret;
}

// La methode remove() se charge des liberations des ressources
static void __devexit genpci_remove(struct pci_dev *dev)
{
  int i;
  struct struct_private *genpci_private = pci_get_drvdata(dev);

  printk(KERN_INFO "genpci: device removed\n");

  for (i=0; i < DEVICE_COUNT_RESOURCE; i++)
  {
    //...
  }

  pci_release_regions(dev);

  pci_disable_device(dev);

  kfree(genpci_private);

  devfs_unregister(genpci_private->devfs_handle);

  list_del(&genpci_private->link);
}


static struct pci_driver genpci_pci_driver =
{
  name: "genpci",
  id_table: genpci_id_table,
  probe: genpci_probe, /* Initialise le peripherique */
  remove: genpci_remove, /* Libere le peripherique */
};


/*
* Gestion module : Init et Exit
*/

static int __init genpci_init(void)
{
  int ret;

  /* Enregistre le pilote de peripherique */
  ret = devfs_register_chrdev(major, "genpci", &genpci_fops);
  if (ret < 0)
  {
    printk(KERN_WARNING "genpci: unable to get a major\n");

    return ret;
  }

  if (major == 0)
    major = ret; /* dynamic value */

  /* Enregistre et initialise le pilote PCI */
  ret = pci_module_init(&genpci_pci_driver);
  if (ret < 0)
  {
    printk(KERN_WARNING "genpci: unable to register PCI driver\n");

    devfs_unregister_chrdev(major, "genpci");

    return ret;
  }

  return 0;
}

static void __exit genpci_exit(void)
{
  /* Suppression du pilote */
  pci_unregister_driver(&genpci_pci_driver);

  devfs_unregister_chrdev(major, "genpci");
}


/*
* Points d'entree et de sortie du module
*/

module_init(genpci_init);
module_exit(genpci_exit);


10 . Bibliographie

Articles par Pierre Ficheux parus dans Linux Magazine France numéro 17 (mai 2000) et numéro 42 (Septembre 2002), disponibles en ligne sur www.ficheux.com

Ouvrage "Linux device drivers" par Alessandro Rubini (nouvelle édition pour noyau 2.4) en ligne sur www.xml.com.

La version française est disponible en version papier aux éditions O'Reilly sur : www.editions-oreilly.fr

PCI local bus sur www.pcguide.com

PCI bus overview sur www.quatech.com

PCI SIG (Special Interest Group) sur www.pcisig.com

Cours et exemples Noyau LINUX et pilotes de périphériques par Julien Gaulmin sur: www.courseforge.org.

Compléments : divers.

Remarques : Mes premières expériences avec les modules chargeables et les pilotes de périphérique remontent à 1997, en m'appuyant essentiellement sur le livre "Programmation linux 2.0" de Rémy Card, Eric Dumas et Franck Mével, les documentations kernel et évidemment les sources du noyau, pour réaliser des drivers pour cartes ISA. Pour rédiger ces documents, j'ai essentiellement puisé dans le livre de référence "Pilotes de périphériques" de Rubini & Corbet et les exemples de Julien Gaulmin et Pierre Ficheux (pilote PCI). La partie sur le bus PCI est un résumé du document de Patrice KADIONIK (disponible sur le site de www.enseirb.fr).


Précédent
Pilote caractère
Sommaire
 
 
© 2002 for www.tvtsii.net by tv