Changes between Version 1 and Version 2 of SujetTP2-2018


Ignore:
Timestamp:
Feb 16, 2018, 7:18:39 AM (7 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SujetTP2-2018

    v1 v2  
    11= TP2 : Premier pilote =
    22
     3
     4
    35== Objectif ==
     6
     7
    48
    59L'objectif de la séance est de commander les LEDS et le bouton poussoir (BP) en passant par un pilote installé dans le noyau.
     
    1216  '''XY''' correspond aux initiales de votre binôme pour éviter les conflits avec vos camarades\\
    1317  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 ajoutez vous-même.**
    15 }}}
    16 }}}
    17 
    18 
    19 **Je vais changer le texte pour demain. Celui de l'an passé ne me plait pas...**
     18  **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.**
     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 (non testé) 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[O] = (led[O] == '0') ? '1' : '0';
     62      write( fd, led, 1);
     63      sleep( 1);
     64      read( fd, bp, 1);
     65   } while (bp[0] == '1');
     66   return 0;
     67}
     68}}}
     69}}}
     70}}}
     71
     72
     73
     74== Étape 1 : création et test d'un module noyau ==
     75
     76
     77
     78=== Code du module
     79
     80* La première étape consiste à créer un module noyau, l'insérer puis l'effacer du noyau.
     81* Le module minimal se compose d'une fonction d'initialisation et d'une fonction de cleanup, dans le fichier `module.c` suivant:
     82
     83{{{#!c
     84#include <linux/module.h>
     85#include <linux/init.h>
     86
     87MODULE_LICENSE("GPL");
     88MODULE_AUTHOR("Charlie, 2015");
     89MODULE_DESCRIPTION("Module, aussitot insere, aussitot efface");
     90
     91static int __init mon_module_init(void)
     92{
     93   printk(KERN_DEBUG "Hello World <votre nom> !\n");
     94   return 0;
     95}
     96
     97static void __exit mon_module_cleanup(void)
     98{
     99   printk(KERN_DEBUG "Goodbye World!\n");
     100}
     101
     102module_init(mon_module_init);
     103module_exit(mon_module_cleanup);
     104}}}
     105
     106* **Questions**
     107  * Quelle fonction est exécutée lorsqu'on insère le module du noyau ?
     108  * Quelle fonction est exécutée lorsqu'on enlève le module du noyau ?
     109
     110=== Compilation du module
     111
     112* Ce programme est cross compilé, puis copié sur la Raspberry Pi cible avec le fichier `Makefile` ci-après.
     113* 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`.
     114* 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]''.
     115* 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 :
     116  * en téléchargeant l'archive [[htdocs:linux-rpi-3.18.y.tbz2]]  (192Mb) **dans le répertoire** `/dsk/l1/misc`
     117  * puis en la décompressant \\`tar xvjf /dsk/l1/misc/linux-rpi-3.18.y.tbz2 -C /dsk/l1/misc` (1.1Gb après décompression)
     118{{{#!make
     119CARD_NUMB       = 2X
     120ROUTER          = 132.227.102.36
     121LOGIN           = nom1-nom2
     122LAB             = lab2
     123
     124MODULE          = module
     125
     126CROSSDIR        = /users/enseig/franck/peri
     127KERNELDIR       = /dsk/l1/misc/linux-rpi-3.18.y
     128CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
     129       
     130obj-m           += $(MODULE).o
     131default:;       make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
     132clean:;         make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
     133upload:;        scp -P50$(CARD_NUMB) $(MODULE).ko pi@$(ROUTER):$(LOGIN)/$(LAB)
     134}}}
     135
     136=== **Travail à faire**
     137  * Sur votre compte enseignement, vous devez:
     138    * **Créer** ces fichiers, vous pouvez les commenter en cherchant dans les manuels (ou Google).
     139    * **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.
     140    * **Compiler** le module avec la commande `make`.
     141    * **Copier** sur la !RaspberryPi avec scp avec la commande `make upload`.\\\\
     142  * Sur la carte !RaspberryPi, vous devez:
     143    * Dans le répertoire `$(LOGIN)/$(LAB)'(par exemple `lacas/lab2`) où vous avez copié votre module
     144{{{#!sh
     145$ sudo insmod ./module.ko
     146$ lsmod
     147$ dmesg
     148$ sudo rmmod module
     149$ lsmod
     150$ dmesg
     151}}}
     152  * Les commandes `lsmod`et `dmesg` permettent de voir que les actions du module.
     153  * **Résumez dans le CR** ce que que vous avez fait et ce que vous observez.
     154
     155
     156
     157== Étape 2 : ajout des paramètres au module ==
     158
     159
     160
     161Votre driver devra être paramétré pour lui indiquer le numéro de ports utilisés pour les LEDS et les boutons.
     162Dans un premier temps vous allez vous contenter d'indiquer le nombre de LED et de bouton pour le module de test, mais il faudra être plus précis pour le vrai driver.
     163
     164**Vous devez ajouter** dans module.c (faite d'équivalent pour les boutons):
     165{{{#!c
     166static int LED;
     167module_param(LED, int, 0);
     168MODULE_PARM_DESC(LED, "Nombre de led");
     169
     170static int __init mon_module_init(void)
     171{
     172    printk(KERN_DEBUG "Hello World !\n");
     173    printk(KERN_DEBUG "LED=%d !\n", LED);
     174    return 0;
     175}
     176}}}
     177Le paramètre est défini au moment de l'insertion.
     178{{{#!sh
     179$ sudo insmod ./module.ko LED=2
     180}}}
     181
     182* **Questions** :
     183  * Comment **voir** que le paramètre a bien été lu ?
     184
     185
     186
     187== Étape 3 : création d'un driver qui ne fait rien mais qui le fait dans le noyau ==
     188
     189
     190=== Création du driver
     191
     192* 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.
     193* Vous ajoutez dans le fichier `.c` du module `ledbp`:
     194{{{#!c
     195
     196#include <linux/fs.h>
     197
     198static int
     199open_ledbp(struct inode *inode, struct file *file) {
     200    printk(KERN_DEBUG "open()\n");
     201    return 0;
     202}
     203
     204static ssize_t
     205read_ledbp(struct file *file, char *buf, size_t count, loff_t *ppos) {
     206    printk(KERN_DEBUG "read()\n");
     207    return count;
     208}
     209
     210static ssize_t
     211write_ledbp(struct file *file, const char *buf, size_t count, loff_t *ppos) {
     212    printk(KERN_DEBUG "write()\n");
     213    return count;
     214}
     215
     216static int
     217release_ledbp(struct inode *inode, struct file *file) {
     218    printk(KERN_DEBUG "close()\n");
     219    return 0;
     220}
     221
     222struct file_operations fops_ledbp =
     223{
     224    .open       = open_ledbp,
     225    .read       = read_ledbp,
     226    .write      = write_ledbp,
     227    .release    = release_ledbp
     228};
     229}}}
     230* 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.\\\\
     231  * Au **début du fichier c du module**, vous déclarez une nouvelle variable statique.
     232    {{{#!c
     233    static int major;
     234    }}}
     235  * et **dans la fonction d'initialisation du module**, vous ajoutez l'enregistrement du driver,
     236    {{{#!c
     237    major = register_chrdev(0, "ledbp", &fops_ledbp); // 0 est le numéro majeur qu'on laisse choisir par linux
     238    }}}
     239  * et **dans la fonction exit du module**, vous allez décharger le driver dans ce module en ajoutant :
     240    {{{#!c
     241    unregister_chrdev(major, "ledbp");
     242    }}}
     243
     244=== Compilation
     245
     246* Vous devez compiler, déplacer le module (upload du Makefile) et le charger (insmod) dans la !RaspberryPi.
     247* Vous allez chercher dans le fichier `/proc/devices` le numéro `major` choisi par linux.
     248* vous allez maintenant créer le noeud dans le répertoire `/dev` et le rendre accessible par tous.
     249  Le numéro mineur est 0 car il n'y a qu'une seule instance.
     250{{{
     251sudo mknod /dev/ledbp c major 0
     252sudo chmod a+rw /dev/ledbp
     253}}}
     254
     255=== Questions
     256* Dans votre CR, je vous suggère d'expliquer chaque étape.
     257* **Comment savoir** que le device a été créé ?
     258* Le test de votre driver peut se faire par les commandes suivantes (avant de faire un vrai programme): dites ce que vous observez:
     259  {{{#!sh
     260  $ echo "rien" > /dev/ledbp
     261  $ dd bs=1 count=1 < /dev/ledbp
     262  $ dmesg
     263  }}}
     264* Nous pouvons automatiser le chargement du driver et son effacement en créant deux scripts shell:\\\\
     265  * Dans un fichier `insdev`
     266  {{{#!bash
     267  #!/bin/sh
     268  module=$1
     269  shift
     270  /sbin/insmod ./$module.ko $* || exit 1
     271  rm -f /dev/$module
     272  major=$(awk "\$2==\"$module\" {print \$1;exit}" /proc/devices)
     273  mknod /dev/$module c $major 0
     274  chmod 666 /dev/$module
     275  echo "=> Device /dev/$module created with major=$major"
     276  }}}
     277  * Dans un fichier `rmdev`
     278  {{{#!bash
     279  #!/bin/sh
     280  module=$1
     281  /sbin/rmmod $module || exit 1
     282  rm -f /dev/$module
     283  echo "=> Device /dev/$module removed"
     284  }}}
     285* 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.
     286  {{{#!bash
     287  chmod u+x insdev rmdev
     288  }}}
     289* Pour les exécuter :
     290  {{{#!bash
     291  $ sudo ./insdev ledbp LED=2
     292    => Device /dev/ledbp created with major=237
     293  $ sudo ./rmdev ledbp LED=2
     294    => Device /dev/ledbp removed
     295  }}}
     296
     297== Étape 4 : accès aux GPIO depuis les fonctions du pilote ==
     298
     299Nous devons pouvoir accéder aux registres de configuration des GPIO.
     300* 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.
     301* 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()`.
     302
     303{{{#!c
     304#include <linux/module.h>
     305#include <linux/init.h>
     306#include <asm/io.h>
     307#include <mach/platform.h>
     308
     309static const int LED0 = 4;
     310
     311struct gpio_s
     312{
     313    uint32_t gpfsel[7];
     314    uint32_t gpset[3];
     315    uint32_t gpclr[3];
     316    uint32_t gplev[3];
     317    uint32_t gpeds[3];
     318    uint32_t gpren[3];
     319    uint32_t gpfen[3];
     320    uint32_t gphen[3];
     321    uint32_t gplen[3];
     322    uint32_t gparen[3];
     323    uint32_t gpafen[3];
     324    uint32_t gppud[1];
     325    uint32_t gppudclk[3];
     326    uint32_t test[1];
     327}
     328*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);
     329}}}
     330
     331Les deux fonctions `gpio_fsel()` et `gpio_write()` possibles sont données juste après. Vous pouvez voir comment exploiter la structure.
     332Nous vous laissons le soin de faire `gpio_read()`, puis d'invoquer ces fonctions dans les fonctions `open_ledbp()`, `read_ledbp()`, etc.
     333
     334{{{#!c
     335static void gpio_fsel(int pin, int fun)
     336{
     337    uint32_t reg = pin / 10;
     338    uint32_t bit = (pin % 10) * 3;
     339    uint32_t mask = 0b111 << bit;
     340    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     341}
     342
     343static void gpio_write(int pin, bool val)
     344{
     345    if (val)
     346        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     347    else
     348        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     349}
     350}}}
     351
     352== Étape 5 : Usage d'un timer dans le noyau pour faire clignoter (optionnel) ==
     353
     354Le code suivant fait clignoter la led GPIO04.
     355Vous pouvez adapter votre driver, pour demander un clignotement plutôt qu'un allumage.
     356En effet, il existe un moyen de faire faire périodiquement une fonction au système grâce à une file d'attente temporelle.
     357Inspirez-vous de code pour votre pilote.
     358
     359{{{#!c
     360#include <linux/module.h>
     361#include <linux/init.h>
     362#include <asm/io.h>
     363#include <mach/platform.h>
     364
     365static const int LED0 = 2;
     366
     367//------------------------------------------------------------------------------
     368//                                GPIO ACCES
     369//------------------------------------------------------------------------------
     370
     371struct gpio_s
     372{
     373    uint32_t gpfsel[7];
     374    uint32_t gpset[3];
     375    uint32_t gpclr[3];
     376    uint32_t gplev[3];
     377    uint32_t gpeds[3];
     378    uint32_t gpren[3];
     379    uint32_t gpfen[3];
     380    uint32_t gphen[3];
     381    uint32_t gplen[3];
     382    uint32_t gparen[3];
     383    uint32_t gpafen[3];
     384    uint32_t gppud[1];
     385    uint32_t gppudclk[3];
     386    uint32_t test[1];
     387}
     388*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);;
     389
     390enum {FUN_INPUT, FUN_OUTPUT};
     391
     392static void gpio_fsel(int pin, int fun)
     393{
     394    uint32_t reg = pin / 10;
     395    uint32_t bit = (pin % 10) * 3;
     396    uint32_t mask = 0b111 << bit;
     397    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     398}
     399
     400static void gpio_write(int pin, bool val)
     401{
     402    if (val)
     403        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     404    else
     405        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     406}
     407
     408//------------------------------------------------------------------------------
     409//                             TIMER PROGRAMMING
     410//------------------------------------------------------------------------------
     411
     412static struct timer_list led_blink_timer;
     413static int led_blink_period = 1000;
     414
     415static void led_blink_handler(unsigned long unused)
     416{
     417    static bool on = false;
     418    on = !on;
     419    gpio_write(LED0, on);
     420    mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     421}
     422
     423//------------------------------------------------------------------------------
     424//                              MODULE INIT & EXIT
     425//------------------------------------------------------------------------------
     426
     427MODULE_LICENSE("GPL");
     428MODULE_AUTHOR("Franck from http://sysprogs.com/VisualKernel/tutorials/raspberry/leddriver/)");
     429MODULE_DESCRIPTION("leds on-off");
     430 
     431static int __init LedBlinkModule_init(void)
     432{
     433    int result;
     434
     435    gpio_fsel(LED0, FUN_OUTPUT);
     436    gpio_write(LED0, 1);
     437    setup_timer(&led_blink_timer, led_blink_handler, 0);
     438    result = mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     439    BUG_ON(result < 0);
     440    printk(KERN_DEBUG "blink loaded\n");
     441
     442    return 0;
     443}
     444
     445static void __exit LedBlinkModule_exit(void)
     446{
     447    gpio_fsel(LED0, FUN_INPUT);
     448    del_timer(&led_blink_timer);
     449    printk(KERN_DEBUG "blink removed\n");
     450}
     451
     452module_init(LedBlinkModule_init);
     453module_exit(LedBlinkModule_exit);
     454
     455}}}