Changes between Initial Version and Version 1 of IOC21_T02


Ignore:
Timestamp:
Mar 4, 2021, 7:59:36 PM (4 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • IOC21_T02

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