     Petit guide de programmation des ports d'entres / sorties sous Linux

  Adaptation franaise du Linux I/O port programming mini-HOWTO

  Riku Saikkonen

   <Riku POINT Saikkonen CHEZ hut POINT fi>

   Adaptation franaise: Jean-Franois Prvost

   Relecture de la version franaise: Guillaume Lelarge, Jean-Philippe
   Gurard

   Version : 3.0.fr.1.1

   24 avril 2006

   +------------------------------------------------------------------------+
   | Historique des versions                                                |
   |------------------------------------------------------------------------|
   | Version 3.0.fr.1.1         | 2006-04-24         | JPG                  |
   |------------------------------------------------------------------------|
   | Conversion en XML. Mise  jour de l'en-tte du document pour respecter |
   | les conventions actuelles du projet Traduc.org.                        |
   |------------------------------------------------------------------------|
   | Version 3.0.fr.1.0         | 2003-02-04         | JFP, GL, JPG         |
   |------------------------------------------------------------------------|
   | Premire traduction franaise.                                         |
   |------------------------------------------------------------------------|
   | Version 3.0                | 2000-12-13         | RS                   |
   +------------------------------------------------------------------------+

   Rsum

   Ce document prsente les diffrentes faons de programmer des entres /
   sorties pour les architectures Intel x86 ainsi que de les diffrentes
   mthodes permettant l'utilisation de temporisations trs courtes pour les
   applications Linux tournant en mode utilisateur.

   --------------------------------------------------------------------------

   Table des matires

   1. Introduction

                1.1. Droits d'utilisation

                1.2. Commentaires et corrections

   2. Utilisation des ports d'entres / sorties en langage C

                2.1. La mthode normale

                2.2. Une mthode alternative : /dev/port

   3. Interruptions (IRQ) et accs DMA

   4. Temporisation de haute prcision

                4.1. Temporisations

                4.2. Mesure du temps

   5. D'autres langages de programmation

   6. Quelques ports utiles

                6.1. Le port parallle

                6.2. Le port de manette de jeu

                6.3. Le port srie

   7. Conseils

   8. Problmes et solutions

   9. Code d'exemple

   10. Remerciements

   11. Adaptation franaise

                11.1. Traduction

                11.2. Relecture

1. Introduction

   Ce document traite de la faon de programmer des entres / sorties
   matrielles sur une architecture Intel x86 ainsi que de l'utilisation de
   temporisations trs courtes pour des applications s'excutant en mode
   utilisateur sous Linux. Ce document est un descendant du trs court Petit
   guide des ports d'entres-sorties (IO-Port mini-HOWTO) du mme auteur.

  1.1. Droits d'utilisation

   Copyright  1995-2000 Riku Saikkonen.

   Copyright  2002-2006 Jean-Franois Prvost, Guillaume Lelarge et
   Jean-Philippe Gurard pour la version franaise.

   Sauf mention du contraire, les Guides pratiques Linux sont la proprit de
   leurs auteurs respectifs. Les Guides pratiques Linux peuvent tre copis
   ou diffuss, en partie ou en intgralit, sous tout format qu'il soit
   physique ou lectronique, tant que ce texte d'information sur les droits
   d'utilisation est conserv dans toutes les copies. Les diffusions
   commerciales de ce document sont permises et encourages ; cependant,
   l'auteur souhaiterait tre tenu au courant de telles diffusions.

   Toute traduction, uvre drive ou compilation incluant un Guide pratique
   Linux doit tre diffuse en respectant les conditions de ce texte
   d'information sur les droits d'utilisation. Autrement dit, vous n'avez pas
   le droit de crer des uvres drives d'un Guide pratique et d'imposer des
   restrictions supplmentaires  sa diffusion. Des exceptions  ces rgles
   peuvent tre accordes dans certains cas ; contactez (en anglais) le
   coordinateur des Guides pratiques Linux  l'adresse ci-dessous.

   En rsum, nous souhaitons encourager la dissmination de ces informations
   au travers le plus grand nombre de circuits possibles. Cependant, nous
   souhaitons conserver nos droits d'auteurs sur ces Guides pratiques et nous
   souhaitons tre inform de tout projet de diffusion des Guides pratiques.

   Pour toute question, envoyez un courrier lectronique, en anglais,  Tim
   Bynum, coordinateur des Guides pratiques Linux,  l'adresse <tjbynum CHEZ
   metalab POINT unc POINT edu>.

   Unless otherwise stated, Linux HOWTO documents are copyrighted by their
   respective authors. Linux HOWTO documents may be reproduced and
   distributed in whole or in part, in any medium physical or electronic, as
   long as this copyright notice is retained on all copies. Commercial
   redistribution is allowed and encouraged; however, the author would like
   to be notified of any such distributions.

   All translations, derivative works, or aggregate works incorporating any
   Linux HOWTO documents must be covered under this copyright notice. That
   is, you may not produce a derivative work from a HOWTO and impose
   additional restrictions on its distribution. Exceptions to these rules may
   be granted under certain conditions; please contact the Linux HOWTO
   coordinator at the address given below.

   In short, we wish to promote dissemination of this information through as
   many channels as possible. However, we do wish to retain copyright on the
   HOWTO documents, and would like to be notified of any plans to
   redistribute the HOWTOs.

   If you have questions, please contact Tim Bynum, the Linux HOWTO
   coordinator, at <tjbynum AT metalab DOT unc DOT edu> via email.

  1.2. Commentaires et corrections

   Si vous avez des remarques ou des corrections, n'hsitez pas  m'crire en
   anglais  l'adresse <Riku POINT Saikkonen CHEZ hut POINT fi> ...

   N'hsitez pas  faire parvenir vos commentaires et suggestions concernant
   l'adaptation franaise de ce document au projet Traduc.org
   [http://traduc.org]  l'adresse : <commentaires CHEZ traduc POINT org>.

2. Utilisation des ports d'entres / sorties en langage C

  2.1. La mthode normale

   Les routines pour accder aux ports d'entres / sorties sont situes dans
   le fichier d'en-tte /usr/include/asm/io.h (ou linux/include/asm-i386/io.h
   dans les sources du noyau Linux). Ces routines sont des macros, il suffit
   donc de dclarer #include <asm/io.h>; dans votre code source sans avoir
   besoin de bibliothques additionnelles.

    cause d'une limitation de gcc (prsente dans toutes les versions que je
   connais, egcs y compris) vous devez compiler le code source qui fait appel
    ces routines avec le drapeau d'optimisation (gcc -O1 ou plus), ou
   alternativement en dclarant #define extern static avant la ligne #include
   <asm/io.h> (n'oubliez pas de rajouter ensuite #undef extern).

   Pour le dbogage, vous pouvez compiler avec les drapeaux suivants (tout du
   moins avec les versions les plus rcentes de gcc) : gcc -g -O. Il faut
   savoir que l'optimisation engendre un comportement parfois bizarre de la
   part du dbogueur. Si cela vous pose un rel problme, vous pouvez
   toujours utiliser les routines d'accs aux ports d'entres / sorties dans
   un fichier source spar, et ne compiler que ce fichier avec le drapeau
   d'optimisation activ.

    2.1.1. Les permissions

   Avant d'accder aux ports, vous devez donner  votre programme la
   permission de le faire. Pour cela, il vous faut faire appel  la fonction
   ioperm() (dclare dans unistd.h et dfinie dans le noyau) quelque part au
   dbut de votre programme (avant tout accs aux ports d'entres / sorties).
   La syntaxe est la suivante : ioperm(premier_port, , nombre, , activer), o
   premier_port est le numro du premier port auquel on souhaite avoir accs
   et nombre le nombre de ports conscutifs auxquels on veut avoir la
   permission d'accder. Par exemple, ioperm(0x300, 5, 1) donnerait accs aux
   ports 0x300 jusqu' 0x304 (au total 5 ports). Le dernier argument est une
   valeur boolenne spcifiant si on autorise l'accs aux ports (vrai [1]) ou
   si on le restreint (faux [0]). Pour activer l'accs  plusieurs ports non
   conscutifs, vous pouvez faire plusieurs appels  ioperm(). Reportez vous
    la page de manuel ioperm(2) pour plus de dtails sur la syntaxe.

   L'appel  ioperm() dans votre programme ncessite les privilges de super
   utilisateur (root). Il faut donc que votre programme soit excut en tant
   qu'utilisateur root, ou qu'il soit rendu setuid root. Vous pouvez
   abandonner les privilges d'utilisateur root aprs l'appel  ioperm(). Il
   n'est pas impratif d'abandonner de faon explicite les privilges d'accs
   aux ports en utilisant ioperm( ... , 0 )  la fin de votre programme, ceci
   est fait automatiquement lorsque le processus se termine.

   L'utilisation de setuid() par un utilisateur non privilgi ne supprime
   pas l'accs accord aux ports par ioperm(). En revanche, lors d'un fork(),
   le processus fils n'hrite pas des permissions de son pre (qui lui les
   garde).

   La fonction ioperm() permet de contrler l'accs aux ports de 0x000 
   0x3ff uniquement. Pour les ports suprieurs, vous devez utiliser iopl()
   qui ouvre un accs  tous les ports d'un coup. Pour donner  votre
   programme l'accs  tous les ports d'entres / sorties (soyez certains de
   ce que vous faites car l'accs  des ports inappropris peut avoir des
   consquences dsastreuses pour votre systme), il suffit de passer  la
   fonction un argument de valeur 3 (iopl(3)). Reportez-vous  la page de
   manuel iopl(2) pour plus de dtails.

    2.1.2. L'accs aux ports

   Pour lire un octet (8 bits) sur un port, un appel  inb(port) retourne la
   valeur de l'octet lu. Pour l'criture d'un octet, il suffit d'appeler la
   fonction outb(valeur, , port) (attention  l'ordre des paramtres). La
   lecture d'un mot (16 bits) sur les ports x et x+1 (un octet sur chaque
   port pour constituer un mot grce  l'instruction assembleur inw), faites
   appel  inw(x). Enfin, pour l'criture d'un mot sur les deux ports,
   utilisez outw(value, , x). Si vous n'tes pas certain quant  la fonction
    utiliser (octet ou mot), il est sage de se cantonner  l'appel de inb()
   et outb(). La plupart des priphriques sont conus pour des accs sur un
   octet. Notez que toutes les instructions d'accs aux ports ncessitent un
   temps d'excution d'au minimum une microseconde.

   Les macros inb_p(), outb_p(), inw_p() et outw_p() fonctionnent de manire
   identique  celles voques prcdemment  l'exception du fait qu'elles
   effectuent un court temps de pause additionnel aprs l'accs au port
   (environ une microseconde). Vous avez la possibilit d'allonger ce temps
   de pause  quatre microsecondes avec la directive #define REALLY_SLOW_IO
   avant de dclarer #include <asm/io.h>. Ces macros utilisent normalement
   une criture sur le port 0x80 pour leur temps de pause (sauf en dclarant
   un #define SLOW_IO_BY_JUMPING, qui est en revanche moins prcis). Vous
   devez donc au pralable autoriser l'accs au port 0x80 avec ioperm()
   (l'criture sur le port 0x80 ne devrait avoir aucun effet indsirable sur
   votre systme).

   Si vous tes  la recherche de mthodes plus souples d'utilisation, lisez
   la suite ...

   Des pages de manuel pour ioperm(2), iopl(2) et les macros dcrites
   ci-dessus sont disponibles dans les collections assez rcentes des pages
   de manuel Linux.

  2.2. Une mthode alternative : /dev/port

   Un autre moyen d'accder aux ports d'entres / sorties est d'ouvrir en
   lecture ou en criture le priphrique /dev/port (un priphrique en mode
   caractre, numro majeur 1, mineur 4) au moyen de la fonction open().
   Notons que les fonctions en f*() de la bibliothque stdio font appel  des
   tampons mmoires internes, il vaut donc mieux les viter. Il suffit
   ensuite, comme dans le cas d'un fichier, de se positionner sur l'octet
   appropri au moyen de la fonction lseek() (l'octet 0 du fichier quivaut
   au port 0x00, l'octet 1 au port 0x01, et ctera) et d'en lire (read()) ou
   crire (write()) un octet ou un mot.

   Il est vident que l'application doit avoir la permission d'accder au
   priphrique /dev/port pour que cette mthode fonctionne. Cette faon de
   faire reste certainement plus lente que la premire, mais elle ne
   ncessite ni optimisation lors de la compilation ni appel  ioperm().
   L'accs aux privilges de super-utilisateur n'est pas impratif non plus,
   si vous donnez les permissions adquates  un utilisateur ou un groupe
   pour accder  /dev/port (cela reste tout de mme une trs mauvaise ide
   du point de vue de la scurit du systme, puisqu'il devient possible de
   porter atteinte au systme, peut-tre mme d'obtenir le statut de root en
   utilisant /dev/port pour accder directement aux disques durs, cartes
   rseaux, et ctera).

   Il n'est pas possible d'utiliser les fonctions select(2) ou poll(2) pour
   lire /dev/port puisque l'lectronique du systme n'a pas la possibilit
   d'avertir le microprocesseur qu'une valeur a chang sur un port d'entre.

3. Interruptions (IRQ) et accs DMA

   Vous ne pouvez tout simplement pas utiliser directement les interruptions
   ou l'accs DMA depuis un processus en mode utilisateur. Pour cela, il vous
   faut dvelopper un pilote pour le noyau. Reportez-vous au Linux Kernel
   Hacker's Guide [http://www.tldp.org/LDP/khg/HyperNews/get/khg.html] pour
   plus de dtails et au code source du noyau pour des exemples.

   Vous avez cependant la possibilit de dsactiver les interruptions depuis
   une application en mode utilisateur, mais cela peut s'avrer dangereux
   (mme les pilotes du noyau ne le font que pour des priodes de temps trs
   brves). Aprs appel  iopl(3), vous pouvez dsactiver les interruptions
   en utilisant asm("cli"); et les ractiver avec asm("sti");.

4. Temporisation de haute prcision

  4.1. Temporisations

   Avant toutes choses, il est important de prciser l'impossibilit de
   garantir un contrle prcis des temps d'excution de processus en mode
   utilisateur du fait de la nature multitche du noyau Linux. Votre
   processus peut tre mis en sommeil  n'importe quel moment pour une dure
   allant de 10 millisecondes  quelques secondes (sur un systme dont la
   charge est trs importante). Malgr tout, pour la plupart des applications
   utilisant les ports d'entres / sorties, cela n'est pas trs important. Si
   vous voulez minimiser cet inconvnient, vous pouvez donner  votre
   processus une priorit plus haute (reportez-vous  la page de manuel de
   nice(2)) ou faire appel  l'ordonnancement temps-rel (voir ci-aprs).

   Si vous souhaitez obtenir une prcision de temporisation plus leve que
   celle qu'offre les processus en mode utilisateur usuels, sachez qu'il
   existe des possibilits de support  temps-rel  en mode utilisateur. Les
   noyaux Linux de la srie 2.x permettent de travailler en quasi temps-rel.
   Pour les dtails, reportez-vous  la page de manuel de
   sched_setscheduler(2). Il existe galement des versions spciales du noyau
   offrant un vrai ordonnancement temps-rel.

    4.1.1. Avec sleep() et usleep()

   Commenons tout d'abord par les appels de temporisation les plus simples.
   Pour des temporisation de plusieurs secondes, le meilleur choix est
   probablement la fonction sleep(). Pour des dures au minimum de l'ordre de
   dizaines de millisecondes (10 millisecondes semblent tre la dure
   minimum), usleep() devrait s'avrer suffisant. Ces fonctions librent
   l'accs au microprocesseur pour d'autres processus, vitant ainsi le
   gaspillage du temps machine. Les pages de manuel de sleep(3) et usleep(3)
   vous donneront plus de prcisions.

   Pour des temporisations de moins de 50 millisecondes (en fonction de la
   cadence du microprocesseur, de la machine ainsi que de la charge du
   systme), redonner le processeur aux autres processus prend normment de
   temps. En effet l'ordonnanceur des tches du noyau Linux (en tout cas pour
   les microprocesseurs de la famille x86) prend gnralement au moins 10 
   30 millisecondes avant de rendre le contrle au processus. De ce fait,
   dans les temporisations de courte dure, usleep(3) effectue en ralit une
   pause plus longue que celle spcifie en paramtre, prenant au moins
   10 millisecondes supplmentaires.

    4.1.2. nanosleep()

   Dans les noyaux Linux de la srie 2.0.x est apparu l'appel systme
   nanosleep() (voir la page de manuel de nanosleep(2)) permettant d'endormir
   ou de retarder un processus pendant un laps de temps trs court (quelques
   microsecondes ou plus).

   Pour des attentes =< 2 millisecondes, si (et seulement si) votre processus
   fonctionne en ordonnancement quasi temps-rel (au moyen de
   sched_setscheduler()), nanosleep() fait appel  une boucle d'attente ; si
   tel n'est pas le cas, le processus s'endort simplement tout comme avec
   usleep().

   La boucle d'attente utilise udelay() (une fonction interne au noyau
   utilise par beaucoup de pilotes), la dure de celle-ci tant calcule en
   fonction du nombre de BogoMips. La vitesse de ce type de boucle d'attente
   est une des grandeurs que les BogoMips permettent de mesurer de faon
   prcise. Voyez /usr/include/asm/delay.h pour plus de dtails quant  son
   fonctionnement.

    4.1.3. Temporisations grce aux ports d'entre / sortie

   Les accs aux ports d'entre / sortie sont un autre moyen d'obtenir des
   temporisations. L'criture ou la lecture d'un octet sur le port 0x80 (voir
   ci-dessus la procdure  suivre) devrait avoir pour consquence un retard
   d'une microseconde, indpendamment du type et de la cadence du
   microprocesseur. Vous pouvez donc procder de la sorte afin d'obtenir un
   retard de quelques microsecondes. L'criture sur ce port ne devrait pas
   avoir d'effets secondaires sur une machine classique, pour preuve certains
   pilotes du noyau font appel  cette mthode. C'est galement de cette
   manire que {in|out}[bw]_p() effectue une pause (voir asm/io.h).

   Plus prcisment, une opration de lecture ou d'criture sur la plupart
   des ports dans l'intervalle 0x000-0x3ff prend 1 microseconde. Par exemple,
   si vous utilisez directement le port parallle, il suffit d'utiliser des
   inb() additionnels sur ce port pour obtenir une temporisation.

    4.1.4. Temporisations en assembleur

   Si vous connaissez le type et la frquence du processeur de la machine sur
   laquelle votre programme va s'excuter, vous pouvez coder en dur les
   temporisations en faisant appel  certaines instructions assembleur.
   Rappelez-vous cependant qu' tout moment votre processus peut-tre mis en
   attente par l'ordonnanceur et, de ce fait, les temporisations peuvent
   s'avrer plus longues que souhaites.

   Pour les donnes du tableau ci-dessous, la frquence interne du
   microprocesseur dtermine le nombre de cycles d'horloge consomms. Par
   exemple, pour un microprocesseur  50 Mhz (un 486DX-50), un cycle
   d'horloge dure 1/50000000 de seconde (soit 200 nanosecondes).

   +------------------------------------------------------------------------+
   |     Instruction     | cycles d'horloge i386  |  cycles d'horloge i486  |
   |---------------------+------------------------+-------------------------|
   |    xchg %bx,%bx     |           3            |            3            |
   |---------------------+------------------------+-------------------------|
   |         nop         |           3            |            1            |
   |---------------------+------------------------+-------------------------|
   |     or %ax,%ax      |           2            |            1            |
   |---------------------+------------------------+-------------------------|
   |     mov %ax,%ax     |           2            |            1            |
   |---------------------+------------------------+-------------------------|
   |      add %ax,0      |           2            |            1            |
   +------------------------------------------------------------------------+

   Les cycles d'horloges du Pentium devraient tre les mmes que ceux du 486,
    l'exception du Pentium Pro / II, dont l'instruction add %ax, 0 peut ne
   consommer qu'un demi cycle d'horloge. Cette instruction peut-tre parfois
   tre combine avec une autre (cependant, du fait de l'algorithme
   d'excution hors de squence (out-of-order), il n'est pas ncessaire qu'il
   s'agisse d'une instruction conscutive dans le flot).

   Les instructions nop et xchg du tableau ne devraient pas avoir d'effets
   secondaires. Les autres, en revanche, peuvent modifier le registre d'tat,
   mais cela reste sans gravit puisque gcc devrait le dtecter. xchg %bx,%bx
   reste un bon compromis comme instruction de temporisation.

   Pour utiliser ces instructions, placez un appel asm("instruction") dans
   votre programme. La syntaxe d'appel de ces instructions est telle
   qu'numre dans le tableau ci-dessus. Si vous prfrez grouper plusieurs
   instructions dans le mme appel  asm, il vous suffit de les sparer par
   des points-virgules. Par exemple asm("nop ; nop ; nop ; nop") excute
   quatre fois l'instruction nop, effectuant une temporisation de quatre
   cycles d'horloge sur un i486 ou un Pentium (ou douze cycles sur un i386).

   Les instructions asm() sont directement intgres au code par gcc, vitant
   ainsi la perte de temps que pourrait engendrer un appel de fonction
   classique.

   Les temporisations de moins d'un cycle d'horloge sont impossibles sur les
   architectures x86 d'Intel.

    4.1.5. rdtsc pour Pentium

   Avec les microprocesseurs Pentium, vous avez la possibilit de connatre
   le nombre de cycles d'horloge couls depuis le dernier redmarrage avec
   le code C suivant (qui fait appel  l'instruction appele RDTSC) :

             extern __inline__ unsigned long long int rdtsc()
             {
             unsigned long long int x;
             __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
             return x;
             }


   Vous pouvez scruter cette valeur dans une boucle d'attente afin d'obtenir
   un retard correspondant au nombre de cycles d'horloge que vous souhaitez.

  4.2. Mesure du temps

   Pour des dures de la prcision d'une seconde, il est certainement plus
   simple d'utiliser la fonction time(). Pour obtenir plus de prcision,
   gettimeofday() a une prcision d'environ une microseconde (mais
   rappelez-vous de l'ordonnancement dj voqu prcdemment). Pour les
   Pentium, le fragment de code rdtsc ci-dessus est prcis au cycle d'horloge
   prs.

   Si vous souhaitez que votre processus reoive un signal aprs un certain
   laps de temps, utilisez setitimer() ou alarm(). Voyez les pages de manuel
   de ces fonctions pour plus de dtails.

5. D'autres langages de programmation

   Les descriptions ci-dessus se concentrent principalement sur le langage C.
   Cependant, ces exemples devraient tre directement applicables au C++ et 
   l'objective C. En assembleur, il vous suffit de faire appel  ioperm() ou
   iopl(), comme en C, et d'utiliser ensuite directement les ports d'entre /
   sortie.

   Avec d'autres langages,  moins de pouvoir insrer du code assembleur, du
   code C ou utiliser les appels systmes, il est probablement plus simple
   d'crire un programme C trs simple offrant des fonctions prenant en
   charge les temporisations et les accs aux ports d'entres / sorties
   souhaits, de le compiler et de le lier avec le reste de votre
   application. Ou vous pouvez utiliser /dev/port comme dcrit plus haut.

6. Quelques ports utiles

   Voici quelques informations utiles pour la programmation des ports les
   plus utiliss. Ces ports peuvent tre directement interfacs avec une
   lectronique logique de type TTL (ou CMOS).

   Si vous avez l'intention d'utiliser ces ports ou des ports classiques en
   vue d'applications pour lesquels ils ont t conus (par exemple pour
   contrler une imprimante ou un modem ordinaire), vous auriez intrt 
   utiliser les pilotes dj existants (qui sont gnralement inclus dans le
   noyau) plutt que de programmer directement les ports comme dcrit dans ce
   document. Cette section s'adresse principalement aux personnes dsireuses
   de connecter aux ports d'entre / sortie standards de leur PC des crans 
   cristaux liquides, des moteurs pas--pas ou d'autres montages
   lectroniques faits maison.

   En revanche, si votre but est de piloter un priphrique grand public, tel
   qu'un scanner, disponible sur le march depuis quelques temps dj,
   essayez plutt de savoir si un pilote a dj t dvelopp. Le
   Hardware-HOWTO
   [http://www.traduc.org/docs/HOWTO/lecture/Hardware-HOWTO.html] est un bon
   point de dpart pour mener cette investigation.

   http://www.hut.fi/Misc/Electronics/ [http://www.hut.fi/Misc/Electronics/]
   est galement une excellente source d'information sur la connexion de
   priphriques  un ordinateur et sur l'lectronique en gnrale.

  6.1. Le port parallle

   L'adresse de base du port parallle (appele BASE ci-dessous) est 0x3bc
   pour /dev/lp0, 0x378 pour /dev/lp1 et 0x278 pour /dev/lp2. Si vous
   souhaitez piloter un matriel fonctionnant comme une imprimante classique,
   voyez le Printing-HOWTO
   [http://www.traduc.org/docs/HOWTO/lecture/Printing-HOWTO.html].

   En plus du mode standard d'criture-seule dcrit ci-dessous, il existe un
   mode  tendu  bidirectionnel sur la plupart des ports parallles. Pour
   des informations  ce sujet ainsi que sur les rcents modes ECP / EPP (et
   le standard IEEE 1284 en gnral), voici deux adresses :
   http://www.fapo.com/ [http://www.fapo.com/] et http://www.beyondlogic.org/
   [http://www.beyondlogic.org/]. Rappelez-vous cependant que puisque vous ne
   pouvez pas utiliser les requtes d'interruptions (IRQ) ou l'accs DMA dans
   un programme en mode utilisateur, vous serez certainement oblig d'crire
   un pilote pour le noyau afin d'exploiter les modes ECP / EPP. Il me semble
   que quelqu'un est dj en train de dvelopper un tel pilote, mais je n'en
   sais pas plus  ce sujet.

   Le port BASE+0 (port de donnes) contrle les signaux de donnes du port
   (respectivement D0  D7 pour les bits 0  7; tats : 0 = bas (0V), 1 =
   haut (5V)). Une criture sur ce port positionne l'tat des broches
   correspondantes. La lecture retourne la dernire valeur crite en mode
   standard ou tendu, sinon les donnes prsentes sur les broches connectes
    un autre priphrique en mode de lecture tendue.

   Le port BASE+1 (port d'tat) est en lecture seule et retourne l'tat des
   signaux d'entre suivants :

     o Bits 0 et 1 sont rservs.

     o Bit 2 tat d'IRQ (ne correspond pas  une broche, je ne sais pas
       comment il fonctionne)

     o Bit 3 ERROR (1 = tat haut)

     o Bit 4 SLCT (1 = tat haut)

     o Bit 5 PE (1 = tat haut)

     o Bit 6 ACK (1 = tat haut)

     o Bit 7 -BUSY (0 = tat haut)

   Le port BASE+2 (port de contrle) est en criture seule (une lecture sur
   celui-ci retourne la dernire valeur crite) et contrle les signaux
   d'tat suivants :

     o Bit 0 -STROBE (0 = tat haut)

     o Bit 1 -AUTO_FD_XT (0 = tat haut)

     o Bit 2 INIT (1 = tat haut)

     o Bit 3 -SLCT_IN (0 = tat haut)

     o Bit 4 active la requte d'interruption (IRQ) du port parallle (qui
       survient lors d'une transition bas-vers-haut de la broche ACK)
       lorsqu'il est positionn  1.

     o Bit 5 contrle la direction du mode tendu (0 = criture, 1 = lecture)
       et est en criture seule (une lecture sur ce bit ne retournera pas de
       valeur significative).

     o Bits 6 et 7 sont rservs.

   Brochage (d'un connecteur femelle sub-D 25 sur le port) (e = entre, s =
   sortie) :

  1(e/s) -STROBE,   2(e/s) D0,          3(e/s) D1,          4(e/s) D2,
  5(e/s) D3,        6(e/s) D4,          7(e/s) D5,          8(e/s) D6,
  9(e/s) D7,       10(e) ACK,          11(e) -BUSY,        12(e) PE,
 13(e) SLCT,       14(s) -AUTO_FD_XT,  15(e) ERROR,        16(s) INIT,
 17(s) -SLCT_IN,   18-25 masse

   Les spcifications donnes par IBM indiquent que les broches 1, 14, 16 et
   17 (les sorties de contrle) sont de type collecteur ouvert ramen  5,0 V
   au moyen d'une rsistance de 4,7 kohm (intensit de fuite de 20 mA, de
   source 0,55 mA, tat haut de sortie 5,0 V moins tension de pull-up). Le
   reste des broches ont une intensit de fuite de 24 mA, de source de 15 mA
   et leur voltage  l'tat haut est au minimum de 2,4 V. L'tat bas pour
   toutes les broches est au maximum de 0,5 V. Les ports parallles d'autres
   types que celui d'IBM peuvent s'carter de ce standard. Pour plus
   d'informations  ce sujet, voyez l'adresse :
   http://www.hut.fi/Misc/Electronics/circuits/lptpower.html
   [http://www.hut.fi/Misc/Electronics/circuits/lptpower.html].

   [1][Avertissement] Avertissement
                      Faites trs attention aux masses ! J'ai dj grill 
                      plusieurs reprises des ports parallles en les
                      connectant alors que l'ordinateur tait sous tension.
                      Utiliser un port parallle non intgr  la carte mre
                      peut s'avrer une bonne solution pour viter de trop
                      grands dsagrments. Vous pouvez obtenir un second port
                      parallle pour votre machine au moyen d'une carte
                      multi-entres / sorties  petit prix. Il vous suffit de
                      dsactiver les ports dont vous n'avez pas besoin, puis
                      de configurer le port parallle de la carte sur une
                      adresse libre. Vous n'avez pas besoin de vous
                      proccuper de l'IRQ du port parallle si vous n'y
                      faites pas appel.

  6.2. Le port de manette de jeu

   Le port de manette de jeu est accessible aux adresses 0x200-0x207. Si vous
   souhaitez contrler une manette de jeu ordinaire, vous serez probablement
   mieux servi en utilisant les pilotes distribus avec le noyau.

   Brochage (pour un connecteur sub-D 15 femelle) :

     o 1,8,9,15 : +5 V (Alimentation)

     o 4,5,12 : masse

     o 2,7,10,14 : entres numriques, respectivement BA1, BA2, BB1 et BB2

     o 3,6,11,13 : entres  analogiques , respectivement AX, AY, BX et BY

   Les broches fournissant une tension de +5 V semblent souvent tre
   connectes directement sur l'alimentation de la carte mre, ce qui peut
   leur permettre d'obtenir pas mal de puissance, en fonction de la carte
   mre, du bloc d'alimentation et du port de manette de jeu.

   Les entres numriques sont utilises pour les boutons des deux manettes
   de jeu (manette A et manette B, avec deux boutons chacune) que vous pouvez
   connecter au port. Ces entres devraient tre de niveau TTL classique,
   ainsi vous pouvez lire directement leurs valeurs sur le port d'tat (voir
   plus bas). Une vritable manette de jeu retourne un tat bas (0 V) lorsque
   le bouton est press et un tat haut dans l'autre cas (une tension de 5 V
   des broches de d'alimentation au travers d'une rsistance de 1 kohm).

   Les pseudo entres analogiques mesurent en ralit la rsistance. Le port
   de manette de jeu comporte un quadruple monostable (une puce de type
   NE558) connect aux quatre entres. Pour chaque entre, il y a une
   rsistance de 2,2 kohm entre la broche d'entre et la sortie du
   monostable, et un condensateur de 0,01 F entre la sortie du monostable et
   la masse. Une vritable de manette de jeu a un potentiomtre pour chaque
   axe (X et Y), connect entre le +5 V et la broche d'entre approprie (AX
   ou AY pour la manette A, ou BX ou BY pour la manette B).

   Lorsqu'il est activ, le monostable initialise ses lignes de sortie  un
   tat haut (5 V) et attend que chaque condensateur de temporisation
   atteigne une tension de 3,3 V avant de mettre la sortie correspondante 
   un tat bas. La dure de l'tat haut de la sortie du temporisateur est
   proportionnelle  la rsistance du potentiomtre de la manette de jeu (en
   clair, la position du manche de la manette de jeu de l'axe appropri)
   comme expliqu ci-dessous :

     R = (t - 24.2) / 0.011

   o R est la rsistance en ohm, du potentiomtre et t la dure de l'tat
   haut de la sortie, en microsecondes.

   Pour effectuer une lecture sur les entres analogiques, vous devez tout
   d'abord activer le monostable (au moyen d'une criture sur le port, voir
   plus bas), puis scruter l'tat des quatre axes au moyen de lectures
   rptes jusqu' la transition  un tat bas, permettant ainsi de mesurer
   la dure de l'tat haut. Cette scrutation est trs gourmande en temps
   machine. De plus, sur un systme multitche non temps-rel tel que Linux
   (en mode utilisateur normal) le rsultat n'est pas trs prcis, du fait de
   l'impossibilit de scruter le port constamment ( moins d'utiliser un
   pilote noyau et de dsactiver la gestion des interruptions pendant la
   scrutation, mais sachez que vous consommerez encore plus de temps
   machine). Si vous savez  l'avance que le signal va mettre un certain
   temps (plusieurs dizaines de millisecondes) avant de basculer, vous pouvez
   faire appel  usleep() avant de procder  la scrutation afin de laisser
   un peu de temps machine aux autres processus.

   Le seul port d'entres / sorties auquel vous avez besoin d'accder est le
   port 0x201 (les autres ports se comportent de faon identique ou ne
   ragissent pas). Toute criture sur ce port, peu importe la valeur
   envoye, active le temporisateur. Une lecture retourne l'tat des signaux
   d'entre :

     o Bit 0 : AX (tat de la sortie du temporisateur, 1 = tat haut)

     o Bit 1 : AY (tat de la sortie du temporisateur, 1 = tat haut)

     o Bit 2 : BX (tat de la sortie du temporisateur, 1 = tat haut)

     o Bit 3 : BY (tat de la sortie du temporisateur, 1 = tat haut)

     o Bit 4 : BA1 (entre numrique, 1 = tat haut)

     o Bit 5 : BA2 (entre numrique, 1 = tat haut)

     o Bit 6 : BB1 (entre numrique, 1 = tat haut)

     o Bit 7 : BB2 (entre numrique, 1 = tat haut)

  6.3. Le port srie

   Si le priphrique avec lequel vous communiquez est  peu prs compatible
   avec le standard RS-232, vous devriez tre en mesure d'utiliser pour cela
   le port srie. Le pilote srie du noyau Linux devrait suffire pour la
   plupart des applications (vous ne devriez pas avoir  programmer le port
   directement, de plus vous devriez probablement crire un pilote pour le
   noyau si vous souhaitiez le faire), il est en effet trs polyvalent et
   l'usage de dbits non-standards ne devrait pas poser de problmes
   particuliers.

   Voyez la page de manuel de termios(3), le code source du pilote
   (linux/drivers/char/serial.c) ainsi que la page
   http://www.easysw.com/~mike/serial/ [http://www.easysw.com/~mike/serial/]
   pour plus d'informations sur la programmation des ports sries des
   systmes Unix.

7. Conseils

   Si vous souhaitez obtenir une bonne acquisition analogique, vous pouvez
   connecter un CAN ou un CNA au port parallle (conseil : pour
   l'alimentation, utilisez soit le port de manette de jeu, soit un
   connecteur d'alimentation de disque que vous relierez  l'extrieur de
   votre botier,  moins que vous n'utilisiez un priphrique peu
   consommateur d'nergie, et que vous puissiez utiliser le port parallle
   lui-mme pour l'alimenter, sinon utilisez tout simplement une source
   d'alimentation externe). Vous pouvez galement investir dans l'achat d'une
   carte d'acquisition numrique / analogique ou analogique / numrique (la
   plupart des cartes d'acquisition les plus anciennes et les plus lentes
   s'appuient sur l'usage de ports de communication externes). Sinon, si vous
   pouvez vous satisfaire de seulement un ou deux canaux d'acquisition, de
   rsultats peu prcis et sans doute d'un mauvais niveau du zro,
   l'utilisation d'une carte son bas de gamme supporte par le noyau Linux
   devrait rpondre  votre attente (et vous permettre de faire des
   acquisitions rapides).

   Avec des priphriques analogiques de prcision, une mauvaise mise  la
   masse peut gnrer de mauvaises mesures lors de lectures ou d'critures
   analogiques. Si vous rencontrez ce type de dsagrment, vous pouvez isoler
   lectriquement le priphrique de votre ordinateur au moyen
   d'optocoupleurs (sur toutes les lignes entre le priphrique et
   l'ordinateur). Vous pouvez essayer de tirer l'alimentation lectrique des
   optocoupleurs de votre ordinateur (les signaux de broches non utilises du
   port peuvent peut-tre vous fournir une puissance suffisante) afin
   d'obtenir une meilleure isolation.

   Si vous tes  la recherche d'un logiciel de conception de circuits
   imprims sous Linux, sachez qu'il existe une application libre pour X11
   appele Pcb(TM) qui devrait vous rendre de grands services, tout du moins
   si vous n'avez pas de projets trop compliqus. Ce logiciel est inclus dans
   la plupart des distributions Linux et est disponible  l'adresse
   suivante : [2]ftp://sunsite.unc.edu/pub/Linux/apps/circuits/ (nom de
   l'archive pcb-*).

8. Problmes et solutions

   Q1.

           J'obtiens une erreur de segmentation lorsque j'essaye d'accder
           aux ports.

   R1.

           Soit votre programme n'a pas les privilges de super utilisateur,
           soit l'appel  ioperm() n'a pas russi pour une raison ou une
           autre. Vrifiez la valeur de retour de ioperm(). Vrifiez
           galement que vous accdez bien aux ports que vous avez activs
           pralablement avec ioperm() (voir Q3). Si vous faites appel aux
           macros de temporisation (inb_p(), outb_p(), et ctera), pensez
           aussi  utiliser ioperm() pour obtenir l'accs au port 0x80.

   Q2.

           Je n'arrive  trouver nulle part la dfinition de in*(), out*(),
           de plus gcc se plaint de rfrences non-dfinies.

   R2.

           Vous n'avez pas lanc la compilation avec les drapeaux
           d'optimisation (-O), en consquence gcc n'a pas pu trouver les
           macros dfinies dans asm/io.h. Ou alors, vous n'avez pas inclus du
           tout la ligne #include <asm/io.h> dans votre code.

   Q3.

           out*() ne fait rien ou ne retourne que des valeurs bizarres.

   R3.

           Vrifiez l'ordre des paramtres, ceux-ci devraient tre comme ce
           qui suit : outb(valeur, , port) et non pas outportb(port, ,
           valeur) comme en MS-DOS.

   Q4.

           Je souhaite contrler un priphrique standard, tel qu'un
           priphrique RS-232, une imprimante parallle, une manette de
           jeu, ...

   R4.

           Vous auriez plutt intrt  utiliser les pilotes dj existants
           (dans le noyau Linux ou un serveur X ou ailleurs) pour ce faire.
           Ces pilotes sont gnralement assez polyvalents. Ainsi ils
           arrivent mme en gnral  faire fonctionner les priphriques
           sortant lgrement des standards. Voyez la note d'information
           ci-dessus sur les ports standards pour de la documentation  leur
           sujet.

9. Code d'exemple

   Voici un exemple de programme simple permettant l'accs aux ports
   d'entres / sorties :

 /*
 * exemple.c : un exemple trs simple d'accs aux ports d'E/S
 *
 * Ce programme ne fait rien d'utile, juste une criture sur le port,
 * une pause, puis une lecture sur le mme port.
 *  compiler avec  gcc -O2 -o exemple exemple.c et 
 * excuter en tant que root avec  ./exemple .
 */

 #include <stdio.h>
 #include <unistd.h>
 #include <asm/io.h>

 #define BASEPORT 0x378 /* lp1 */

 int main()
 {
 /* Obtention de l'accs aux ports */
 if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}

 /* Initialisation de tous les signaux de donnes (D0-D7)  l'tat bas (0) */
 outb(0, BASEPORT);

 /* Dormons pendant un moment (100 ms) */
 usleep(100000);

 /* Lecture sur le port d'tat (BASE+1) et affichage du rsultat */
 printf("status : %d\n", inb(BASEPORT + 1));

 /* Nous n'avons plus besoin de l'accs aux ports */
 if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

 exit(0);
 }

 /* fin d'exemple.c */

10. Remerciements

   Beaucoup trop de personnes ont contribu  l'criture de ce document pour
   que je puisse en faire la liste ici, mais merci  tous. Je n'ai pas pu
   rpondre  toutes les contributions reues, je m'en excuse, mais encore
   merci pour votre aide.

11. Adaptation franaise

  11.1. Traduction

   La traduction franaise de ce document a t ralise par Jean-Franois
   Prvost <prevost CHEZ 2cse TIRET group POINT com>.

  11.2. Relecture

   La relecture de ce document a t ralise par Guillaume Lelarge <gleu
   CHEZ wanadoo POINT fr> et Jean-Philippe Gurard <fevrier CHEZ tigreraye
   POINT org>.

