Changes between Initial Version and Version 1 of Peri19_T03


Ignore:
Timestamp:
Mar 1, 2019, 10:58:30 AM (6 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Peri19_T03

    v1 v1  
     1= TP3 : pilote de périphérique
     2
     3{{{
     4#!protected
     5== Objectif ==
     6
     7L'objectif de la séance est de commander, dans un premier temps, les LEDS et le bouton poussoir (BP) et, dans un second temps, l'afficheur LCD, en passant par un pilote installé dans le noyau sur la raspberry pi 1.
     8Vous savez déjà contrôler les LEDS et accéder au BP depuis l'espace utilisateur. 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. Pour cela, il vous fallait avoir les droits du root. Désormais, les LED et le BP seront accessibles en mode utilisateur normal.
     9
     10{{{#!div
     11{{{#!td
     12**Nous allons donc créer un pilote pour le périphérique LED+BP.**\\
     13  Ce pilote sera accessible dans par le pseudo-fichier `/dev/ledbpXY`\\
     14  '''XY''' correspond aux initiales de votre binôme pour éviter les conflits avec vos camarades\\
     15  Par exemple, pour '''A'''lmada et '''F'''omentin, il faudrait créer /dev/ledbp'''AF'''\\\\
     16  **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.**
     17}}}
     18}}}
     19
     20Par 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.
     21
     22{{{#!div
     23{{{#!td
     24**Le comportement proposé ici du pilote est le suivant :**\\
     25
     26  * **Pour les LEDS**
     27    * on envoie un tableau de caractères.
     28    * La case `'i'` définit l'état de la LED `'i'` (`'0'` LED éteinte sinon LED allumée).
     29    * Puisqu'il y a deux LEDs, le pilote acceptera un tableau à 2 caractères.
     30  * **Pour les boutons**
     31    * 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'`. 
     32    * Quand le bouton est relâché, le pilote met le caractère `'0'`.
     33    * Quand le bouton est enfoncé, le pilote met une valeur différente de `'0'`.
     34
     35C'est une proposition, vous pouvez faire comme bon vous semble.
     36On peut imaginer d'autres manières, mais celle-ci me semble plus simple.
     37Ce programme (non testé) est censé faire clignoter la led `'0'` jusqu'à ce qu'on appuie sur le bouton.
     38
     39{{{#!c
     40#include <stdio.h>
     41
     42#define NBLED 2
     43#define NBBP 1
     44char led[NBLED];
     45char bp[NBBP];
     46 
     47int main()
     48{
     49   int i;
     50   int fd = open("/dev/ledbpXY", O_RDWR);
     51   if (fd < 0) {
     52      fprintf(stderr, "Erreur d'ouverture du pilote LED et Boutons\n");
     53      exit(1);
     54   }
     55   for( i = 0; i < NBLED; i ++) {
     56      led[i] = '0';
     57   }
     58   do {
     59      led[O] = (led[O] == '0') ? '1' : '0';
     60      write( fd, led, 1);
     61      sleep( 1);
     62      read( fd, bp, 1);
     63   } while (bp[0] == '1');
     64   return 0;
     65}
     66}}}
     67}}}
     68}}}
     69
     70**Références**
     71* Vous trouvez pas mal d'informations sur google, mais si vous voulez allez plus loin il faut lire des livres tel que : [http://www.xml.com/ldd/chapter/book/ Linux Device Drivers, 2nd Edition]
     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{{{#!protected
     111Les réponses sont respectivement mon_module_init et mon_module_cleanup
     112}}}
     113
     114=== Compilation du module
     115
     116* Ce programme est cross compilé, puis copié sur la Raspberry Pi cible avec le fichier `Makefile` ci-après.
     117* 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`.
     118* 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]''.
     119* 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 :
     120  * en téléchargeant l'archive [[htdocs:linux-rpi-3.18.y.tbz2]]  (192Mb) **dans le répertoire** `/dsk/l1/misc`
     121  * 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)
     122{{{#!make
     123CARD_NUMB       = 2X
     124ROUTER          = 132.227.102.36
     125LOGIN           = nom1-nom2
     126LAB             = lab2
     127
     128MODULE          = module
     129
     130CROSSDIR        = /users/enseig/franck/peri
     131KERNELDIR       = /dsk/l1/misc/linux-rpi-3.18.y
     132CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
     133       
     134obj-m           += $(MODULE).o
     135default:;       make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
     136clean:;         make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
     137upload:;        scp -P50$(CARD_NUMB) $(MODULE).ko pi@$(ROUTER):$(LOGIN)/$(LAB)
     138}}}
     139
     140=== **Travail à faire**
     141  * Sur votre compte enseignement, vous devez:
     142    * **Créer** ces fichiers, vous pouvez les commenter en cherchant dans les manuels (ou Google).
     143    * **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.
     144    * **Compiler** le module avec la commande `make`.
     145    * **Copier** sur la !RaspberryPi avec scp avec la commande `make upload`.\\\\
     146  * Sur la carte !RaspberryPi, vous devez:
     147    * Dans le répertoire `$(LOGIN)/$(LAB)'(par exemple `lacas/lab2`) où vous avez copié votre module
     148{{{#!sh
     149$ sudo insmod ./module.ko
     150$ lsmod
     151$ dmesg
     152$ sudo rmmod module
     153$ lsmod
     154$ dmesg
     155}}}
     156  * Les commandes `lsmod`et `dmesg` permettent de voir que les actions du module.
     157  * **Résumez dans le CR** ce que vous avez fait et ce que vous observez.
     158
     159
     160
     161== Étape 2 : ajout des paramètres au module ==
     162
     163
     164
     165* Votre driver devra être paramétré pour lui indiquer le numéro de ports utilisés pour les LEDS et les boutons.
     166  Dans 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.
     167* **Vous devez ajouter** dans module.c (faite d'équivalent pour les boutons):
     168{{{#!c
     169static int LED;
     170module_param(LED, int, 0);
     171MODULE_PARM_DESC(LED, "Nombre de led");
     172
     173static int __init mon_module_init(void)
     174{
     175    printk(KERN_DEBUG "Hello World !\n");
     176    printk(KERN_DEBUG "LED=%d !\n", LED);
     177    return 0;
     178}
     179}}}
     180* Le paramètre est défini au moment de l'insertion.
     181{{{#!sh
     182$ sudo insmod ./module.ko LED=2
     183}}}
     184* **Questions** :
     185  * Comment **voir** que le paramètre a bien été lu ?
     186
     187
     188
     189== Étape 3 : création d'un driver qui ne fait rien, mais qui le fait dans le noyau ==
     190
     191
     192=== Création du driver
     193
     194* 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.
     195* Vous ajoutez dans le fichier `.c` du module `ledbp`:
     196{{{#!c
     197
     198#include <linux/fs.h>
     199
     200static int
     201open_ledbp(struct inode *inode, struct file *file) {
     202    printk(KERN_DEBUG "open()\n");
     203    return 0;
     204}
     205
     206static ssize_t
     207read_ledbp(struct file *file, char *buf, size_t count, loff_t *ppos) {
     208    printk(KERN_DEBUG "read()\n");
     209    return count;
     210}
     211
     212static ssize_t
     213write_ledbp(struct file *file, const char *buf, size_t count, loff_t *ppos) {
     214    printk(KERN_DEBUG "write()\n");
     215    return count;
     216}
     217
     218static int
     219release_ledbp(struct inode *inode, struct file *file) {
     220    printk(KERN_DEBUG "close()\n");
     221    return 0;
     222}
     223
     224struct file_operations fops_ledbp =
     225{
     226    .open       = open_ledbp,
     227    .read       = read_ledbp,
     228    .write      = write_ledbp,
     229    .release    = release_ledbp
     230};
     231}}}
     232* 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.\\\\
     233  * Au **début du fichier c du module**, vous déclarez une nouvelle variable statique.
     234    {{{#!c
     235    static int major;
     236    }}}
     237  * et **dans la fonction d'initialisation du module**, vous ajoutez l'enregistrement du driver,
     238    {{{#!c
     239    major = register_chrdev(0, "ledbp", &fops_ledbp); // 0 est le numéro majeur qu'on laisse choisir par linux
     240    }}}
     241  * et **dans la fonction exit du module**, vous allez décharger le driver dans ce module en ajoutant :
     242    {{{#!c
     243    unregister_chrdev(major, "ledbp");
     244    }}}
     245
     246=== Compilation
     247
     248* Vous devez compiler, déplacer le module (upload du Makefile) et le charger (insmod) dans la !RaspberryPi.
     249* Vous allez chercher dans le fichier `/proc/devices` le numéro `major` choisi par linux.
     250* vous allez maintenant créer le noeud dans le répertoire `/dev` et le rendre accessible par tous.
     251  Le numéro mineur est 0 car il n'y a qu'une seule instance.
     252{{{
     253sudo mknod /dev/ledbp c major 0
     254sudo chmod a+rw /dev/ledbp
     255}}}
     256
     257=== Questions
     258* Dans votre CR, je vous suggère d'expliquer chaque étape.
     259* **Comment savoir** que le device a été créé ?
     260* Le test de votre driver peut se faire par les commandes suivantes (avant de faire un vrai programme): dites ce que vous observez:
     261  {{{#!sh
     262  $ echo "rien" > /dev/ledbp
     263  $ dd bs=1 count=1 < /dev/ledbp
     264  $ dmesg
     265  }}}
     266* Nous pouvons automatiser le chargement du driver et son effacement en créant deux scripts shell:\\\\
     267  * Dans un fichier `insdev`
     268  {{{#!bash
     269  #!/bin/sh
     270  module=$1
     271  shift
     272  /sbin/insmod ./$module.ko $* || exit 1
     273  rm -f /dev/$module
     274  major=$(awk "\$2==\"$module\" {print \$1;exit}" /proc/devices)
     275  mknod /dev/$module c $major 0
     276  chmod 666 /dev/$module
     277  echo "=> Device /dev/$module created with major=$major"
     278  }}}
     279  * Dans un fichier `rmdev`
     280  {{{#!bash
     281  #!/bin/sh
     282  module=$1
     283  /sbin/rmmod $module || exit 1
     284  rm -f /dev/$module
     285  echo "=> Device /dev/$module removed"
     286  }}}
     287* 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.
     288  {{{#!bash
     289  chmod u+x insdev rmdev
     290  }}}
     291* Pour les exécuter :
     292  {{{#!bash
     293  $ sudo ./insdev ledbp LED=2
     294    => Device /dev/ledbp created with major=237
     295  $ sudo ./rmdev ledbp LED=2
     296    => Device /dev/ledbp removed
     297  }}}
     298
     299
     300
     301== Étape 4 : accès aux GPIO depuis les fonctions du pilote ==
     302
     303
     304
     305=== Création du driver qui accède aux GPIO
     306
     307* Nous devons pouvoir accéder aux registres de configuration des GPIO.
     308  * 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.
     309  * 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()`.
     310{{{#!c
     311#include <linux/module.h>
     312#include <linux/init.h>
     313#include <asm/io.h>
     314#include <mach/platform.h>
     315
     316static const int LED0 = 4;
     317
     318struct gpio_s
     319{
     320    uint32_t gpfsel[7];
     321    uint32_t gpset[3];
     322    uint32_t gpclr[3];
     323    uint32_t gplev[3];
     324    uint32_t gpeds[3];
     325    uint32_t gpren[3];
     326    uint32_t gpfen[3];
     327    uint32_t gphen[3];
     328    uint32_t gplen[3];
     329    uint32_t gparen[3];
     330    uint32_t gpafen[3];
     331    uint32_t gppud[1];
     332    uint32_t gppudclk[3];
     333    uint32_t test[1];
     334}
     335*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);
     336}}}
     337* Les deux fonctions `gpio_fsel()` et `gpio_write()` possibles sont données juste après. Vous pouvez voir comment exploiter la structure.
     338* Vous devez écrire `gpio_read()`, puis invoquer ces fonctions dans les fonctions `open_ledbp()`, `read_ledbp()` et write_ledbp`.
     339{{{#!c
     340static void gpio_fsel(int pin, int fun)
     341{
     342    uint32_t reg = pin / 10;
     343    uint32_t bit = (pin % 10) * 3;
     344    uint32_t mask = 0b111 << bit;
     345    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     346}
     347
     348static void gpio_write(int pin, bool val)
     349{
     350    if (val)
     351        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     352    else
     353        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     354}
     355}}}
     356
     357=== Travail à faire
     358
     359* Ecrivez le driver complet.
     360* Un script de chargement.
     361* un programme de validation utilisant le driver.
     362
     363
     364
     365== Étape 5 (optionnel) : Usage d'un timer dans le noyau pour faire clignoter la led ==
     366
     367* Le code suivant fait clignoter la led GPIO04.\\
     368  Vous pouvez adapter votre driver, pour demander un clignotement plutôt qu'un allumage.
     369  En effet, il existe un moyen de faire faire périodiquement une fonction au système grâce à une file d'attente temporelle.
     370* Le code ci-dessous n'est pas un pilote, il fait clignoter la led dès son insertion dans le noyau, mais vous pouvez vous en inspirer pour votre pilote.
     371* La gestion du timer passe par les fonction `setup_timer()` et `mod_timer()`, vous devez en chercher la documentation.
     372{{{#!c
     373#include <linux/module.h>
     374#include <linux/init.h>
     375#include <asm/io.h>
     376#include <mach/platform.h>
     377
     378static const int LED0 = 2;
     379
     380//------------------------------------------------------------------------------
     381//                                GPIO ACCES
     382//------------------------------------------------------------------------------
     383
     384struct gpio_s
     385{
     386    uint32_t gpfsel[7];
     387    uint32_t gpset[3];
     388    uint32_t gpclr[3];
     389    uint32_t gplev[3];
     390    uint32_t gpeds[3];
     391    uint32_t gpren[3];
     392    uint32_t gpfen[3];
     393    uint32_t gphen[3];
     394    uint32_t gplen[3];
     395    uint32_t gparen[3];
     396    uint32_t gpafen[3];
     397    uint32_t gppud[1];
     398    uint32_t gppudclk[3];
     399    uint32_t test[1];
     400}
     401*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);;
     402
     403enum {FUN_INPUT, FUN_OUTPUT};
     404
     405static void gpio_fsel(int pin, int fun)
     406{
     407    uint32_t reg = pin / 10;
     408    uint32_t bit = (pin % 10) * 3;
     409    uint32_t mask = 0b111 << bit;
     410    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     411}
     412
     413static void gpio_write(int pin, bool val)
     414{
     415    if (val)
     416        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     417    else
     418        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     419}
     420
     421//------------------------------------------------------------------------------
     422//                             TIMER PROGRAMMING
     423//------------------------------------------------------------------------------
     424
     425static struct timer_list led_blink_timer;
     426static int led_blink_period = 1000;
     427
     428static void led_blink_handler(unsigned long unused)
     429{
     430    static bool on = false;
     431    on = !on;
     432    gpio_write(LED0, on);
     433    mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     434}
     435
     436//------------------------------------------------------------------------------
     437//                              MODULE INIT & EXIT
     438//------------------------------------------------------------------------------
     439
     440MODULE_LICENSE("GPL");
     441MODULE_AUTHOR("Franck from http://sysprogs.com/VisualKernel/tutorials/raspberry/leddriver/)");
     442MODULE_DESCRIPTION("leds on-off");
     443 
     444static int __init LedBlinkModule_init(void)
     445{
     446    int result;
     447
     448    gpio_fsel(LED0, FUN_OUTPUT);
     449    gpio_write(LED0, 1);
     450    setup_timer(&led_blink_timer, led_blink_handler, 0);
     451    result = mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     452    BUG_ON(result < 0);
     453    printk(KERN_DEBUG "blink loaded\n");
     454
     455    return 0;
     456}
     457
     458static void __exit LedBlinkModule_exit(void)
     459{
     460    gpio_fsel(LED0, FUN_INPUT);
     461    del_timer(&led_blink_timer);
     462    printk(KERN_DEBUG "blink removed\n");
     463}
     464
     465module_init(LedBlinkModule_init);
     466module_exit(LedBlinkModule_exit);
     467
     468}}}
     469
     470}}}