Changes between Initial Version and Version 1 of IOC_T02


Ignore:
Timestamp:
Feb 10, 2022, 6:50:03 PM (2 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC_T02

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