376 lines
11 KiB
Text
376 lines
11 KiB
Text
<!--
|
||
The FreeBSD Documentation Project
|
||
The FreeBSD French Documentation Project
|
||
|
||
$Id: chapter.sgml,v 1.1 2002-02-14 14:25:02 gioria Exp $
|
||
Original revision: 1.3
|
||
$FreeBSD$
|
||
-->
|
||
|
||
<chapter id="pci">
|
||
<title>Les périphériques PCI</title>
|
||
|
||
<para>Ce chapître traitera des mécanismes de FreeBSD pour
|
||
écrire un pilote de périphérique pour un périphérique sur
|
||
bus PCI.</para>
|
||
|
||
<sect1>
|
||
<title>Rechercher et rattacher</title>
|
||
|
||
<para>Informations ici sur comment le code du bus PCI fait un cycle
|
||
sur les périphériques non rattachés et voir si le nouvellement chargé
|
||
pilote de périphérique chargeable dans le noyau (kld)
|
||
sera rattaché à l'un d'eux.</para>
|
||
|
||
<programlisting>/*
|
||
* Simple KLD pour jouer avec les fonctions PCI.
|
||
*
|
||
* Murray Stokely
|
||
*/
|
||
|
||
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
|
||
|
||
#include <sys/types.h>
|
||
#include <sys/module.h>
|
||
#include <sys/systm.h> /* uprintf */
|
||
#include <sys/errno.h>
|
||
#include <sys/param.h> /* defines used in kernel.h */
|
||
#include <sys/kernel.h> /* types used in module initialization */
|
||
#include <sys/conf.h> /* cdevsw struct */
|
||
#include <sys/uio.h> /* uio struct */
|
||
#include <sys/malloc.h>
|
||
#include <sys/bus.h> /* structs, prototypes for pci bus stuff */
|
||
|
||
#include <pci/pcivar.h> /* For get_pci macros! */
|
||
|
||
/* Prototypes des fonctions */
|
||
d_open_t mypci_open;
|
||
d_close_t mypci_close;
|
||
d_read_t mypci_read;
|
||
d_write_t mypci_write;
|
||
|
||
/* Points d'entrée du pilote de périphérique caractère */
|
||
|
||
static struct cdevsw mypci_cdevsw = {
|
||
mypci_open,
|
||
mypci_close,
|
||
mypci_read,
|
||
mypci_write,
|
||
noioctl,
|
||
nopoll,
|
||
nommap,
|
||
nostrategy,
|
||
"mypci",
|
||
36, /* reserved for lkms - /usr/src/sys/conf/majors */
|
||
nodump,
|
||
nopsize,
|
||
D_TTY,
|
||
-1
|
||
};
|
||
|
||
/* variables */
|
||
static dev_t sdev;
|
||
|
||
/* Nous sommes plus interresses dans la recherche/attachement
|
||
que dans l'ouverture/fermeture/lecture/ecriture a ce point */
|
||
|
||
int
|
||
mypci_open(dev_t dev, int oflags, int devtype, struct proc *p)
|
||
{
|
||
int err = 0;
|
||
|
||
uprintf("Peripherique \"monpci\" ouvert avec succes.\n");
|
||
return(err);
|
||
}
|
||
|
||
int
|
||
mypci_close(dev_t dev, int fflag, int devtype, struct proc *p)
|
||
{
|
||
int err=0;
|
||
|
||
uprintf("Peripherique \"monpci.\ "ferme\n");
|
||
return(err);
|
||
}
|
||
|
||
int
|
||
mypci_read(dev_t dev, struct uio *uio, int ioflag)
|
||
{
|
||
int err = 0;
|
||
|
||
uprintf("lecture dans monpci!\n");
|
||
return err;
|
||
}
|
||
|
||
int
|
||
mypci_write(dev_t dev, struct uio *uio, int ioflag)
|
||
{
|
||
int err = 0;
|
||
|
||
uprintf("Ecriture dans monpci!\n");
|
||
return(err);
|
||
}
|
||
|
||
/* PCI Support Functions */
|
||
|
||
/*
|
||
* Retourne la chaine d'identification si ce peripherique est le notre
|
||
*/
|
||
static int
|
||
mypci_probe(device_t dev)
|
||
{
|
||
uprintf("MonPCI Probe\n"
|
||
"ID Fabricant: 0x%x\n"
|
||
"ID Peripherique : 0x%x\n",pci_get_vendor(dev),pci_get_device(dev));
|
||
|
||
if (pci_get_vendor(dev) == 0x11c1) {
|
||
uprintf("Nous avons le WinModem, recherche reussi!\n");
|
||
return 0;
|
||
}
|
||
|
||
return ENXIO;
|
||
}
|
||
|
||
/* La fonction Attach n'est appelée que si
|
||
la recherche est reussie*/
|
||
|
||
static int
|
||
mypci_attach(device_t dev)
|
||
{
|
||
uprintf("Rattachement de MonPCI pour: ID Peripherique: 0x%x\n",pci_get_vendor(dev));
|
||
sdev = make_dev(<literal>&</literal>mypci_cdevsw,
|
||
0,
|
||
UID_ROOT,
|
||
GID_WHEEL,
|
||
0600,
|
||
"monpci");
|
||
uprintf("Peripherique Monpci charge.\n");
|
||
return ENXIO;
|
||
}
|
||
|
||
/* Detach le peripherique. */
|
||
|
||
static int
|
||
mypci_detach(device_t dev)
|
||
{
|
||
uprintf("Monpci detache!\n");
|
||
return 0;
|
||
}
|
||
|
||
/* Appele lors de l'arret du systeme apres sync. */
|
||
|
||
static int
|
||
mypci_shutdown(device_t dev)
|
||
{
|
||
uprintf("Monpci arrete!\n");
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* routine de suspension du peripherique
|
||
*/
|
||
static int
|
||
mypci_suspend(device_t dev)
|
||
{
|
||
uprintf("Monpci suspendu!\n");
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* routine de reprise du peripherique
|
||
*/
|
||
|
||
static int
|
||
mypci_resume(device_t dev)
|
||
{
|
||
uprintf("Monpci resume!\n");
|
||
return 0;
|
||
}
|
||
|
||
static device_method_t mypci_methods[] = {
|
||
/* Interface Peripherique*/
|
||
DEVMETHOD(device_probe, mypci_probe),
|
||
DEVMETHOD(device_attach, mypci_attach),
|
||
DEVMETHOD(device_detach, mypci_detach),
|
||
DEVMETHOD(device_shutdown, mypci_shutdown),
|
||
DEVMETHOD(device_suspend, mypci_suspend),
|
||
DEVMETHOD(device_resume, mypci_resume),
|
||
|
||
{ 0, 0 }
|
||
};
|
||
|
||
static driver_t mypci_driver = {
|
||
"monpci",
|
||
mypci_methods,
|
||
0,
|
||
/* sizeof(struct mypci_softc), */
|
||
};
|
||
|
||
static devclass_t mypci_devclass;
|
||
|
||
DRIVER_MODULE(mypci, pci, mypci_driver, mypci_devclass, 0, 0);</programlisting>
|
||
|
||
<para>Informations complémentaires
|
||
<itemizedlist>
|
||
<listitem><simpara><ulink url="http://www.pcisig.org">PCI
|
||
Special Interest Group</ulink></simpara></listitem>
|
||
|
||
<listitem><simpara>PCI System Architecture, Fourth Edition by
|
||
Tom Shanley, et al.</simpara></listitem>
|
||
|
||
</itemizedlist>
|
||
</para>
|
||
</sect1>
|
||
|
||
<sect1>
|
||
<title>Les ressources du bus</title>
|
||
|
||
<para>FreeBSD fournit un mécanisme orienté objet pour demander
|
||
des ressources du bus parent. Pratiquement tous les périphériques
|
||
seront un fils membre d'un type de bus (PCI, ISA, USB, SCSI, etc)
|
||
et ces périphériques nécessite des ressources issues de leur bus parent
|
||
(comme des segments de mémoire, des interruptions or des canaux DMA).</para>
|
||
|
||
<sect2>
|
||
<title>Registres d'adresse de base</title>
|
||
|
||
<para>Pour faire de particulièrement utile avec un périphérique PCI,
|
||
vous aurez besoin d'obtenir les <emphasis>registres d'adresse de base</emphasis>
|
||
(Base Address Registers ou BARs) de l'espace de configuration PCI.
|
||
Les détails spécifiques au PCI sur l'obtention du registre d'adresse de base
|
||
sont masqués dans la fonction <function>bus_alloc_resource()</function>.</para>
|
||
|
||
<para>Par exemple, un pilote typique aura sa fonction <function>attach()</function>
|
||
similaire à ceci : </para>
|
||
|
||
<programlisting> sc->bar0id = 0x10;
|
||
sc->bar0res = bus_alloc_resource(dev, SYS_RES_MEMORY, &(sc->bar0id),
|
||
0, ~0, 1, RF_ACTIVE);
|
||
if (sc->bar0res == NULL) {
|
||
uprintf("Allocation memoire du registre PCI de base 0 echouee!\n");
|
||
error = ENXIO;
|
||
goto fail1;
|
||
}
|
||
|
||
sc->bar1id = 0x14;
|
||
sc->bar1res = bus_alloc_resource(dev, SYS_RES_MEMORY, &(sc->bar1id),
|
||
0, ~0, 1, RF_ACTIVE);
|
||
if (sc->bar1res == NULL) {
|
||
uprintf("Allocation memoire du registre PCI de base 1 echouee!\n");
|
||
error = ENXIO;
|
||
goto fail2;
|
||
}
|
||
sc->bar0_bt = rman_get_bustag(sc->bar0res);
|
||
sc->bar0_bh = rman_get_bushandle(sc->bar0res);
|
||
sc->bar1_bt = rman_get_bustag(sc->bar1res);
|
||
sc->bar1_bh = rman_get_bushandle(sc->bar1res);
|
||
|
||
</programlisting>
|
||
|
||
<para>Des références pour chaque registre d'adresse de base sont gardées
|
||
dans la structure <structname>softc</structname> afin qu'elle puisse
|
||
être utilisée pour écrire dans le périphérique plus tard.</para>
|
||
|
||
<para>Ces références peuvent alors être utilisées pour lire ou écrire
|
||
dans les registres du périphérique avec les fonctions <function>bus_space_*</function>.
|
||
Par exemple, un pilote peut contenir une fonction raccourci
|
||
pour lire dans un registre spécifique à une carte comme cela :
|
||
</para>
|
||
|
||
<programlisting>uint16_t
|
||
board_read(struct ni_softc *sc, uint16_t address) {
|
||
return bus_space_read_2(sc->bar1_bt, sc->bar1_bh, address);
|
||
}
|
||
</programlisting>
|
||
|
||
<para>De fa<66>on similaire, une autre peut écrire dans les registres avec : </para>
|
||
|
||
<programlisting>void
|
||
board_write(struct ni_softc *sc, uint16_t address, uint16_t value) {
|
||
bus_space_write_2(sc->bar1_bt, sc->bar1_bh, address, value);
|
||
}
|
||
</programlisting>
|
||
|
||
<para>Ces fonctions existent en versions 8bit, 16bit et 32bit
|
||
et vous devriez utiliser
|
||
<function>bus_space_{read|write}_{1|2|4}</function>
|
||
en conséquence.</para>
|
||
|
||
</sect2>
|
||
<sect2>
|
||
<title>Les interruptions</title>
|
||
|
||
<para>Les interruptions sont alloués à partir du code orienté objet du
|
||
bus de fa<66>on similaire aux ressources mémoire. D'abord une ressource
|
||
IRQ doit être allouée à partir du bus parent, et alors le
|
||
gestionnaire d'interruption doit être règlé pour traiter cet IRQ.</para>
|
||
|
||
<para>A nouveau, un exemple de fonction
|
||
<function>attach()</function> en dit plusqu'un long discours.</para>
|
||
|
||
<programlisting>/* Recupere la ressource IRQ */
|
||
|
||
sc->irqid = 0x0;
|
||
sc->irqres = bus_alloc_resource(dev, SYS_RES_IRQ, &(sc->irqid),
|
||
0, ~0, 1, RF_SHAREABLE | RF_ACTIVE);
|
||
if (sc->irqres == NULL) {
|
||
uprintf("Allocation IRQ echouee!\n");
|
||
error = ENXIO;
|
||
goto fail3;
|
||
}
|
||
|
||
/* Maitnenant nous choisissons notre gestionnaire d'interruption */
|
||
|
||
error = bus_setup_intr(dev, sc->irqres, INTR_TYPE_MISC,
|
||
my_handler, sc, &(sc->handler));
|
||
if (error) {
|
||
printf("Ne peut regler l'IRQ\n");
|
||
goto fail4;
|
||
}
|
||
|
||
sc->irq_bt = rman_get_bustag(sc->irqres);
|
||
sc->irq_bh = rman_get_bushandle(sc->irqres);
|
||
</programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2>
|
||
<title>DMA</title>
|
||
<para>Sur les PC, les périphériques qui veulent utiliser la gestion de
|
||
bus DMA doivent travailler avec des adresses physiques. C'est un problème
|
||
puisque FreeBSD utilise une mémoire virtuelle et travaille presque
|
||
exclusivement avec des adresses virtuelles. Heureusement, il y a une
|
||
fonction <function>vtophys()</function> pour nous aider.</para>
|
||
|
||
<programlisting>#include <vm/vm.h>
|
||
#include <vm/pmap.h>
|
||
|
||
#define vtophys(virtual_address) (...)
|
||
</programlisting>
|
||
|
||
<para>La solution est toutefois un peu différente sur Alpha, et
|
||
ce que nous voulons réellement est une fonction appelée
|
||
<function>vtobus()</function>.</para>
|
||
|
||
<programlisting>#if defined(__alpha__)
|
||
#define vtobus(va) alpha_XXX_dmamap((vm_offset_t)va)
|
||
#else
|
||
#define vtobus(va) vtophys(va)
|
||
#endif
|
||
</programlisting>
|
||
|
||
</sect2>
|
||
|
||
<sect2>
|
||
<title>Désallouer les resources</title>
|
||
|
||
<para>Il est très important de désallouer toutes les ressources
|
||
qui furent allouées pendant <function>attach()</function>.
|
||
Unsoin tout particulier doit être pris pour désallouer
|
||
les bonnes choses même lors d'un échec afin que le système reste
|
||
utilisable lorsque votre driver meurt.</para>
|
||
</sect2>
|
||
</sect1>
|
||
|
||
</chapter>
|
||
|
||
|