<!--
     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&#233;riph&#233;riques PCI</title>

  <para>Ce chap&icirc;tre traitera des m&eacute;canismes de FreeBSD pour
    &eacute;crire un pilote de p&eacute;riph&eacute;rique pour un p&eacute;riph&eacute;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&eacute;riph&eacute;riques non rattach&eacute;s et voir si le nouvellement charg&eacute; 
      pilote de p&eacute;riph&eacute;rique chargeable dans le noyau (kld)
       sera rattach&eacute; &agrave; 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 &lt;sys/types.h&gt;
#include &lt;sys/module.h&gt;
#include &lt;sys/systm.h&gt;  /* uprintf */ 
#include &lt;sys/errno.h&gt;
#include &lt;sys/param.h&gt;  /* defines used in kernel.h */
#include &lt;sys/kernel.h&gt; /* types used in module initialization */
#include &lt;sys/conf.h&gt;   /* cdevsw struct */
#include &lt;sys/uio.h&gt;    /* uio struct */
#include &lt;sys/malloc.h&gt;
#include &lt;sys/bus.h&gt;	/* structs, prototypes for pci bus stuff */

#include &lt;pci/pcivar.h&gt; /* 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&eacute;e du pilote de p&eacute;riph&eacute;rique caract&egrave;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&eacute;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&eacute;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&eacute;canisme orient&eacute; objet pour demander
      des ressources du bus parent.  Pratiquement tous les p&eacute;riph&eacute;riques 
      seront un fils membre d'un type de bus (PCI, ISA, USB, SCSI, etc)
      et ces p&eacute;riph&eacute;riques n&eacute;cessite des ressources issues de leur bus parent
      (comme des segments de m&eacute;moire, des interruptions or des canaux DMA).</para>

    <sect2>
      <title>Registres d'adresse de base</title>

      <para>Pour faire de particuli&egrave;rement utile avec un p&eacute;riph&eacute;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&eacute;tails sp&eacute;cifiques au PCI sur l'obtention du registre d'adresse de base 
      sont masqu&eacute;s dans la fonction <function>bus_alloc_resource()</function>.</para>

      <para>Par exemple, un pilote typique aura sa fonction <function>attach()</function>
      similaire &agrave; ceci : </para>

<programlisting>    sc->bar0id = 0x10;
    sc->bar0res = bus_alloc_resource(dev, SYS_RES_MEMORY, &amp;(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, &amp;(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&eacute;f&eacute;rences pour chaque registre d'adresse de base sont gard&eacute;es 
      dans la structure <structname>softc</structname> afin qu'elle puisse
      &ecirc;tre utilis&eacute;e pour &eacute;crire dans le p&eacute;riph&eacute;rique plus tard.</para>

      <para>Ces r&eacute;f&eacute;rences peuvent alors &ecirc;tre utilis&eacute;es pour lire ou &eacute;crire 
      	dans les registres du p&eacute;riph&eacute;rique avec les fonctions <function>bus_space_*</function>.
      	Par exemple, un pilote peut contenir une fonction raccourci
        pour lire dans un registre sp&eacute;cifique &agrave; 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çon similaire, une autre peut &eacute;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&eacute;quence.</para>

    </sect2>
    <sect2>
      <title>Les interruptions</title>

      <para>Les interruptions sont allou&eacute;s &agrave; partir du code orient&eacute; objet du
         bus de façon similaire aux ressources m&eacute;moire. D'abord une ressource 
         IRQ doit &ecirc;tre allou&eacute;e &agrave; partir du bus parent, et alors le
        gestionnaire d'interruption doit &ecirc;tre r&egrave;gl&eacute; 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, &amp;(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, &amp;(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&eacute;riph&eacute;riques qui veulent utiliser la gestion de 
      bus DMA doivent travailler avec des adresses physiques. C'est un probl&egrave;me 
      puisque FreeBSD utilise une m&eacute;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 &lt;vm/vm.h&gt;
#include &lt;vm/pmap.h&gt;

#define vtophys(virtual_address) (...)
</programlisting>

      <para>La solution est toutefois un peu diff&eacute;rente sur Alpha, et
        ce que nous voulons r&eacute;ellement est une fonction appel&eacute;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&#233;sallouer les resources</title>

      <para>Il est tr&egrave;s important de d&eacute;sallouer toutes les ressources
        qui furent allou&eacute;es pendant <function>attach()</function>.
        Unsoin tout particulier doit &ecirc;tre pris pour d&eacute;sallouer
        les bonnes choses m&ecirc;me lors d'un &eacute;chec afin que le syst&egrave;me reste 
        utilisable lorsque votre driver meurt.</para>
    </sect2>
  </sect1>

</chapter>


