Programmation
modules sous Linux (partie 2) |
III . 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.
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);
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
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).
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)
}
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);
}
}
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);
#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);
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 |