Changes between Version 1 and Version 2 of IOC20_T02


Ignore:
Timestamp:
Feb 3, 2020, 1:20:12 PM (5 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC20_T02

    v1 v2  
    1 = aa
     1= Pilote de périphérique simple
     2
     3== Objectif
     4
     5L'objectif de la séance est de commander les LEDS et le bouton poussoir (BP) et en passant par un pilote installé dans le noyau sur la !RaspberryPi 1.
     6Vous savez déjà contrôler les LEDS et accéder au BP depuis l'espace utilisateur. Pour cela, vous avez dû mapper dans l'espace virtuel du processus utilisateur la zone de l'espace d'adressage physique permettant l'accès aux GPIO. Mais, il vous fallait avoir les droits du root. En passant par un pilote, les LED et le BP seront accessibles par un utilisateur standard.
     7
     8{{{#!div
     9{{{#!td
     10**Nous allons donc créer un pilote pour le périphérique LED et BP.**\\
     11  Ce pilote sera accessible dans par le pseudo-fichier `/dev/ledbpXY`\\
     12  '''XY''' correspond aux initiales de votre binôme pour éviter les conflits avec vos camarades\\
     13  Par exemple, pour '''A'''lmada et '''F'''omentin, il faudrait créer /dev/ledbp'''AF'''\\\\
     14  **Dans le texte de TP, nous n'avons pas fait apparaître les lettres XY, vous devez les ajouter vous-même en les remplaçant par vos initiales.**
     15}}}
     16}}}
     17
     18Par exemple, vous pourrez écrire un programme test (cf fichier `test.c` ci-dessous) qui accède aux LED et BP en s'exécutant entièrement en mode utilisateur.
     19
     20{{{#!div
     21{{{#!td
     22**Le comportement proposé ici du pilote est le suivant :**\\
     23
     24  * **Pour les LEDS**
     25    * on envoie un tableau de caractères.
     26    * La case `'i'` définit l'état de la LED `'i'` (`'0'` LED éteinte sinon LED allumée).
     27    * Puisqu'il y a deux LEDs, le pilote acceptera un tableau à 2 caractères.
     28  * **Pour les boutons**
     29    * on propose un tableau de caractères. Le pilote lit l'état des boutons et met dans la case `'i'` l'état du bouton `'i'`. 
     30    * Quand le bouton est relâché, le pilote met le caractère `'0'`.
     31    * Quand le bouton est enfoncé, le pilote met une valeur différente de `'0'`.
     32
     33C'est une proposition, vous pouvez faire comme bon vous semble.
     34On peut imaginer d'autres manières, mais celle-ci me semble plus simple.
     35Ce programme (non testé) est censé faire clignoter la led `'0'` jusqu'à ce qu'on appuie sur le bouton.
     36
     37{{{#!c
     38#include <stdio.h>
     39
     40#define NBLED 2
     41#define NBBP 1
     42char led[NBLED];
     43char bp[NBBP];
     44 
     45int main()
     46{
     47   int i;
     48   int fd = open("/dev/ledbpXY", O_RDWR);
     49   if (fd < 0) {
     50      fprintf(stderr, "Erreur d'ouverture du pilote LED et Boutons\n");
     51      exit(1);
     52   }
     53   for( i = 0; i < NBLED; i ++) {
     54      led[i] = '0';
     55   }
     56   do {
     57      led[0] = (led[0] == '0') ? '1' : '0';
     58      write( fd, led, NBLED);
     59      sleep( 1);
     60      read( fd, bp, 1);
     61   } while (bp[0] == '1');
     62   return 0;
     63}
     64}}}
     65}}}
     66}}}
     67
     68**Références**
     69* Vous trouverez pas mal d'informations sur internet :
     70  - [http://doc.ubuntu-fr.org/tutoriel/tout_savoir_sur_les_modules_linux]
     71  - [http://pficheux.free.fr/articles/lmf/drivers/]
     72  - [https://broux.developpez.com/articles/c/driver-c-linux/]
     73* si vous voulez allez plus loin, il y a le livre :
     74  - [http://www.xml.com/ldd/chapter/book/ Linux Device Drivers, 2nd Edition]
     75
     76
     77== Étape 1 : création et test d'un module noyau ==
     78
     79
     80
     81=== Code du module
     82
     83* La première étape consiste à créer un module noyau, l'insérer puis l'effacer du noyau.
     84* Le module minimal se compose d'une fonction d'initialisation et d'une fonction de cleanup, dans le fichier `module.c` suivant:
     85
     86{{{#!c
     87#include <linux/module.h>
     88#include <linux/init.h>
     89
     90MODULE_LICENSE("GPL");
     91MODULE_AUTHOR("Charlie, 2015");
     92MODULE_DESCRIPTION("Module, aussitot insere, aussitot efface");
     93
     94static int __init mon_module_init(void)
     95{
     96   printk(KERN_DEBUG "Hello World <votre nom> !\n");
     97   return 0;
     98}
     99
     100static void __exit mon_module_cleanup(void)
     101{
     102   printk(KERN_DEBUG "Goodbye World!\n");
     103}
     104
     105module_init(mon_module_init);
     106module_exit(mon_module_cleanup);
     107}}}
     108
     109* **Questions**
     110  * Quelle fonction est exécutée lorsqu'on insère le module du noyau ?
     111  * Quelle fonction est exécutée lorsqu'on enlève le module du noyau ?
     112
     113{{{#!protected
     114Les réponses sont respectivement mon_module_init et mon_module_cleanup
     115}}}
     116
     117=== Compilation du module
     118
     119* Ce programme est cross compilé, puis copié sur la Raspberry Pi cible avec le fichier `Makefile` ci-après.
     120* Ce Makefile a besoin des sources compilées du noyau présent sur la !RaspberryPi. Comme elles sont   volumineuses, elles sont copiées dans le répertoire `/dsk/l1/misc/linux-rpi-3.18.y`.
     121* Si vous voulez le faire chez vous, il faut que vous preniez les sources de votre distribution. Vous pouvez suivre le tutoriel très clair [http://www.chicoree.fr/w/Compilation_crois%C3%A9e_d%27un_module_Linux_pour_Rasberry_Pi Compilation croisée d'un module linux pour Raspberry Pi]''.
     122* Sur votre PC, vous allez commencer par regarder si le répertoire `/dsk/l1/misc/linux-rpi-3.18.y` existe avec \\`ls -d /dsk/l1/misc/linux-rpi-3.18.y`. \\**s'il existe**, c'est bon, il n'y a rien à faire.\\**s'il n'existe pas** vous allez le créer :
     123  * en téléchargeant l'archive [[htdocs:linux-rpi-3.18.y.tbz2]]  (192Mb) **dans le répertoire** `/dsk/l1/misc`
     124  * puis en la décompressant \\`tar xjf /dsk/l1/misc/linux-rpi-3.18.y.tbz2 -C /dsk/l1/misc` (1.1Gb après décompression)
     125{{{#!make
     126CARD_NUMB       = 2X
     127ROUTER          = peri
     128LOGIN           = nom1-nom2
     129LAB             = lab2
     130
     131MODULE          = module
     132
     133CROSSDIR        = /users/enseig/franck/peri
     134KERNELDIR       = /dsk/l1/misc/linux-rpi-3.18.y
     135CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
     136       
     137obj-m           += $(MODULE).o
     138default:;       make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
     139clean:;         make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
     140upload:;        scp -P50$(CARD_NUMB) $(MODULE).ko pi@$(ROUTER):$(LOGIN)/$(LAB)
     141}}}
     142
     143=== **Travail à faire**
     144  * Sur votre compte enseignement, vous devez:
     145    * **Créer** ces fichiers, vous pouvez les commenter en cherchant dans les manuels (ou Google).
     146    * **changer la valeur des variables** `CARD_NUMB`, `LOGIN`et `LAB` afin de les adapter respectivement à votre numéro carte, au nom du répertoire créé par vous sur la !RaspberryPI et au nom du sous-répertoire créé par vous pour ce TP. Les répertoires et sous-répertoires doivent exister et vous devez donc commencer par vous logger sur votre carte !RaspberryPI avec `ssh` pour les créer.
     147    * **Compiler** le module avec la commande `make`.
     148    * **Copier** sur la !RaspberryPi avec scp avec la commande `make upload`.\\\\
     149  * Sur la carte !RaspberryPi, vous devez:
     150    * Dans le répertoire `$(LOGIN)/$(LAB)'(par exemple `lacas/lab2`) où vous avez copié votre module
     151{{{#!sh
     152$ sudo insmod ./module.ko
     153$ lsmod
     154$ dmesg
     155$ sudo rmmod module
     156$ lsmod
     157$ dmesg
     158}}}
     159  * Les commandes `lsmod`et `dmesg` permettent de voir que les actions du module.
     160  * **Résumez dans le CR** ce que vous avez fait et ce que vous observez.
     161
     162
     163
     164== Étape 2 : ajout des paramètres au module ==
     165
     166
     167
     168* Votre driver devra être paramétré pour lui indiquer le numéro de ports utilisés pour les LEDS et les boutons.
     169  Dans un premier temps vous allez vous contenter d'indiquer le numero du bouton pour le module de test.
     170* **Vous devez ajouter** dans module.c
     171{{{#!c
     172static int btn;
     173module_param(btn, int, 0);
     174MODULE_PARM_DESC(btn, "numéro du port du bouton");
     175
     176static int __init mon_module_init(void)
     177{
     178    printk(KERN_DEBUG "Hello World !\n");
     179    printk(KERN_DEBUG "btn=%d !\n", btn);
     180    return 0;
     181}
     182}}}
     183* Le paramètre est défini au moment de l'insertion.
     184{{{#!sh
     185$ sudo insmod ./module.ko btn=18
     186}}}
     187* Pour les numéros de GPIO de LEDs, comme il peut y en avoir plusieurs, vous pouvez utiliser `module_param_array`.
     188{{{#!c
     189#define NBMAX_LED 32
     190static int leds[NBMAX_LED];
     191static int nbled;
     192module_param_array(leds, int, &nbled, 0);
     193MODULE_PARM_DESC(LEDS, "tableau des numéros de port LED");
     194
     195static int __init mon_module_init(void)
     196{
     197    int i;
     198    printk(KERN_DEBUG "Hello World !\n");
     199    for (i=0; i < nbled; i++)
     200       printk(KERN_DEBUG "LED %d = %d\n", i, leds[i]);
     201    return 0;
     202}
     203}}}
     204* Le paramètre est défini au moment de l'insertion.
     205{{{#!sh
     206$ sudo insmod ./module.ko leds=4,17
     207}}}
     208* **Questions** :
     209  * Comment **voir** que le paramètre a bien été lu ?
     210
     211
     212== Étape 3 : création d'un driver qui ne fait rien, mais qui le fait dans le noyau ==
     213
     214
     215=== Création du driver
     216
     217* Votre driver va être intégré dans un module. Vous allez donc créer un module **nommé `ledbp`** (et non plus `module`) paramétré avec les numéros de ports pour les LEDS et le bouton. Vous utiliserez un nouveau répertoire. Vous modifierez le Makefile en conséquence.
     218* Vous ajoutez dans le fichier `.c` du module `ledbp`:
     219{{{#!c
     220
     221#include <linux/fs.h>
     222
     223static int
     224open_ledbp(struct inode *inode, struct file *file) {
     225    printk(KERN_DEBUG "open()\n");
     226    return 0;
     227}
     228
     229static ssize_t
     230read_ledbp(struct file *file, char *buf, size_t count, loff_t *ppos) {
     231    printk(KERN_DEBUG "read()\n");
     232    return count;
     233}
     234
     235static ssize_t
     236write_ledbp(struct file *file, const char *buf, size_t count, loff_t *ppos) {
     237    printk(KERN_DEBUG "write()\n");
     238    return count;
     239}
     240
     241static int
     242release_ledbp(struct inode *inode, struct file *file) {
     243    printk(KERN_DEBUG "close()\n");
     244    return 0;
     245}
     246
     247struct file_operations fops_ledbp =
     248{
     249    .open       = open_ledbp,
     250    .read       = read_ledbp,
     251    .write      = write_ledbp,
     252    .release    = release_ledbp
     253};
     254}}}
     255* Vous allez **enregistrer** ce driver dans ce module en ajoutant la fonction d'enregistrement dans la fonction init du module. Vous devez aussi prendre en compte les paramètres. C'est à vous de décider comment.\\\\
     256  * Au **début du fichier c du module**, vous déclarez une nouvelle variable statique.
     257    {{{#!c
     258    static int major;
     259    }}}
     260  * et **dans la fonction d'initialisation du module**, vous ajoutez l'enregistrement du driver,
     261    {{{#!c
     262    major = register_chrdev(0, "ledbp", &fops_ledbp); // 0 est le numéro majeur qu'on laisse choisir par linux
     263    }}}
     264  * et **dans la fonction exit du module**, vous allez décharger le driver dans ce module en ajoutant :
     265    {{{#!c
     266    unregister_chrdev(major, "ledbp");
     267    }}}
     268
     269=== Compilation
     270
     271* Vous devez compiler, déplacer le module (upload du Makefile) et le charger (insmod) dans la !RaspberryPi.
     272* Vous allez chercher dans le fichier `/proc/devices` le numéro `major` choisi par linux.
     273* vous allez maintenant créer le noeud dans le répertoire `/dev` et le rendre accessible par tous.
     274  Le numéro mineur est 0 car il n'y a qu'une seule instance.
     275{{{
     276sudo mknod /dev/ledbp c major 0
     277sudo chmod a+rw /dev/ledbp
     278}}}
     279
     280=== Questions
     281* Dans votre CR, je vous suggère d'expliquer chaque étape.
     282* **Comment savoir** que le device a été créé ?
     283* Le test de votre driver peut se faire par les commandes suivantes (avant de faire un vrai programme): dites ce que vous observez:
     284  {{{#!sh
     285  $ echo "rien" > /dev/ledbp
     286  $ dd bs=1 count=1 < /dev/ledbp
     287  $ dmesg
     288  }}}
     289* Nous pouvons automatiser le chargement du driver et son effacement en créant deux scripts shell:\\\\
     290  * Dans un fichier `insdev`
     291  {{{#!bash
     292  #!/bin/sh
     293  module=$1
     294  shift
     295  /sbin/insmod ./$module.ko $* || exit 1
     296  rm -f /dev/$module
     297  major=$(awk "\$2==\"$module\" {print \$1;exit}" /proc/devices)
     298  mknod /dev/$module c $major 0
     299  chmod 666 /dev/$module
     300  echo "=> Device /dev/$module created with major=$major"
     301  }}}
     302  * Dans un fichier `rmdev`
     303  {{{#!bash
     304  #!/bin/sh
     305  module=$1
     306  /sbin/rmmod $module || exit 1
     307  rm -f /dev/$module
     308  echo "=> Device /dev/$module removed"
     309  }}}
     310* Ces deux scripts doivent être copiés dans votre répertoire de la !RaspberryPi. Ils doivent être exécutables et exécutés avec sudo.
     311  {{{#!bash
     312  chmod u+x insdev rmdev
     313  }}}
     314* Pour les exécuter :
     315  {{{#!bash
     316  $ sudo ./insdev ledbp LED=2
     317    => Device /dev/ledbp created with major=237
     318  $ sudo ./rmdev ledbp LED=2
     319    => Device /dev/ledbp removed
     320  }}}
     321
     322
     323
     324== Étape 4 : accès aux GPIO depuis les fonctions du pilote ==
     325
     326
     327
     328=== Création du driver qui accède aux GPIO
     329
     330* Nous devons pouvoir accéder aux registres de configuration des GPIO.
     331  * Pour l'accès aux GPIOs, vous voyez que l'on peut simplifier les calculs d'adresses en utilisant une structure représentant l'organisation des registres.
     332  * Vous noterez également que l'adresse physique de base des GPIO (GPIO_BASE 0x20200000) est mappée dans l'espace virtuel du noyau à l'adresse '''io_addresse''' et récupérer avec la macro du noyau `__io_address()`.
     333{{{#!c
     334#include <linux/module.h>
     335#include <linux/init.h>
     336#include <asm/io.h>
     337#include <mach/platform.h>
     338
     339static const int LED0 = 4;
     340
     341struct gpio_s
     342{
     343    uint32_t gpfsel[7];
     344    uint32_t gpset[3];
     345    uint32_t gpclr[3];
     346    uint32_t gplev[3];
     347    uint32_t gpeds[3];
     348    uint32_t gpren[3];
     349    uint32_t gpfen[3];
     350    uint32_t gphen[3];
     351    uint32_t gplen[3];
     352    uint32_t gparen[3];
     353    uint32_t gpafen[3];
     354    uint32_t gppud[1];
     355    uint32_t gppudclk[3];
     356    uint32_t test[1];
     357}
     358volatile *gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);
     359}}}
     360* Les deux fonctions `gpio_fsel()` et `gpio_write()` possibles sont données juste après. Vous pouvez voir comment exploiter la structure.
     361* Vous devez écrire `gpio_read()`, puis invoquer ces fonctions dans les fonctions `open_ledbp()`, `read_ledbp()` et write_ledbp`.
     362{{{#!c
     363static void gpio_fsel(int pin, int fun)
     364{
     365    uint32_t reg = pin / 10;
     366    uint32_t bit = (pin % 10) * 3;
     367    uint32_t mask = 0b111 << bit;
     368    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     369}
     370
     371static void gpio_write(int pin, bool val)
     372{
     373    if (val)
     374        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     375    else
     376        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     377}
     378}}}
     379
     380=== Travail à faire
     381
     382* Ecrivez le driver complet.
     383* Un script de chargement.
     384* un programme de validation utilisant le driver.