Changes between Initial Version and Version 1 of SujetTP2-2017


Ignore:
Timestamp:
Feb 1, 2017, 3:48:51 PM (8 years ago)
Author:
franck
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SujetTP2-2017

    v1 v1  
     1= TP2 : Premier pilote =
     2
     3== Objectif ==
     4
     5L'objectif de la séance est de commander les LEDS et le bouton poussoir (BP) en passant par un pilote installé dans le noyau.
     6Lors de la dernière séance pour commander les LEDS et accéder au BP, vous avez dû mapper dans l'espace virtuel du processus utilisateur la zone de mémoire permettant l'accès aux GPIO. Pour cela, il vous fallait avoir les droits du root. Désormais, les LED et BP seront accessibles en mode utilisateur normal.
     7
     8{{{#!div
     9{{{#!td
     10**Nous allons donc créer un pilote pour le périphérique LED+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}}}
     15}}}
     16
     17Par 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.
     18
     19{{{#!div
     20{{{#!td
     21**Le comportement proposé ici du pilote est le suivant :**
     22
     23  Pour les LEDS::
     24    on envoie un tableau de caractères. La case `'i'` définit l'état de la LED `'i'` (`'0'` LED éteinte sinon
     25    LED allumée).
     26  Pour les boutons::
     27    on propose un tableau de caractères. Le pilote lit l'état des boutons et met dans la case `'i'` l'état
     28    du bouton `'i'`. \\
     29    **-** Quand le bouton est relâché, le pilote met le caractère `'0'`. \\
     30    **-** Quand le bouton est enfoncé, le pilote met une valeur différente de `'0'`.
     31}}}
     32}}}
     33
     34C'est une proposition, vous pouvez faire comme bon vous semble. Ce programme (non testé) est censé faire clignoter la led `'0'` jusqu'à ce qu'on appuie sur le bouton.
     35{{{#!c
     36#include <stdio.h>
     37
     38#define NBLED 2
     39#define NBBP 1
     40char led[NBLED];
     41char bp[NBBP];
     42 
     43int main()
     44{
     45   int i;
     46   int fd = open("/dev/ledbpXY", O_RDWR);
     47   if (fd < 0) {
     48      fprintf(stderr, "Erreur d'ouverture du pilote LED et Boutons\n");
     49      exit(1);
     50   }
     51   for( i = 0; i < NBLED; i ++) {
     52      led[i] = '0';
     53   }
     54   do {
     55      led[O] = (led[O] == '0') ? '1' : '0';
     56      write( fd, led, NBLED);
     57      sleep( 1);
     58      read( fd, bp, NBBP);
     59   } while (bp[0] == '1');
     60   return 0;
     61}
     62}}}
     63
     64== Étape 1 : création et test d'un module noyau ==
     65
     66La première étape consiste à créer un module noyau, l'insérer puis l'effacer du noyau.
     67
     68Le module minimal se compose d'une fonction d'initialisation et d'une fonction de cleanup, dans le fichier `module.c`suivant:
     69
     70{{{#!c
     71#include <linux/module.h>
     72#include <linux/init.h>
     73
     74MODULE_LICENSE("GPL");
     75MODULE_AUTHOR("Charlie, 2015");
     76MODULE_DESCRIPTION("Module, aussitot insere, aussitot efface");
     77
     78static int __init mon_module_init(void)
     79{
     80   printk(KERN_DEBUG "Hello World !\n");
     81   return 0;
     82}
     83
     84static void __exit mon_module_cleanup(void)
     85{
     86   printk(KERN_DEBUG "Goodbye World!\n");
     87}
     88
     89module_init(mon_module_init);
     90module_exit(mon_module_cleanup);
     91}}}
     92
     93Ce fichier est cross compilé et copié sur la Raspberry Pi cible avec le fichier `Makefile` suivant:
     94{{{#!make
     95CARD_NUMB       = 2X
     96ROUTER          = 132.227.102.36
     97LOGIN           = nom1-nom2
     98LAB             = lab2
     99
     100MODULE          = module
     101
     102CROSSDIR        = /users/enseig/franck/peri
     103KERNELDIR       = /dsk/l1/misc/linux-rpi-3.18.y
     104CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
     105       
     106obj-m           += $(MODULE).o
     107default:;       make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
     108clean:;         make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
     109upload:;        scp -P50$(CARD_NUMB) $(MODULE).ko pi@$(ROUTER):$(LOGIN)/$(LAB)
     110}}}
     111
     112
     113 ||'''''Note 1:'''\\ Ce Makefile a besoin des sources compilées du noyau. Comme elles sont volumineuses, elles sont copiées dans le répertoires `/dsk/l1/misc/linux-rpi-3.18.y`. 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]''.||
     114
     115
     116 ||'''''Note 2:'''\\ Pour ne plus à avoir à taper le mot de passe, vous devez suivre la procédure décrite [[wiki:SujetTP1-2016#mdp]]||
     117
     118Sur votre compte enseignement, vous devez:
     119* **Créer** ces fichiers et bien sûr, les comprendre.
     120* **changer la valeur des variables** `CARD_NUMB`, `LOGIN`et `LAB` afin de les adapter respectivement au numéro de la carte choisie, 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.
     121* **Compiler** le module avec la commande `make`.
     122* **Copier** sur la !RaspberryPi avec scp avec la commande `make upload`.
     123
     124Sur la carte !RaspberryPi, vous devez:
     125* Dans le répertoire `$(LOGIN)/$(LAB)'(par exemple `franck/lab2`) où vous avez copié votre module
     126
     127{{{#!sh
     128$ sudo insmod ./module.ko
     129$ lsmod
     130$ dmesg
     131$ sudo rmmod module
     132$ lsmod
     133$ dmesg
     134}}}
     135* Les commandes `lsmod`et `dmesg` permettent de voir que les actions du module.
     136
     137
     138== Étape 2 : ajout des paramètres au module ==
     139
     140Votre driver devra être paramétré pour lui indiquer le numéro de ports utilisés pour les LEDS et les boutons.
     141Dans 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.
     142
     143Vous devez ajouter dans module.c (faite d'équivalent pour les boutons):
     144{{{#!c
     145static int LED;
     146module_param(LED, int, 0);
     147MODULE_PARM_DESC(LED, "Nombre de led");
     148
     149static int __init mon_module_init(void)
     150{
     151    printk(KERN_DEBUG "Hello World !\n");
     152    printk(KERN_DEBUG "LED=%d !\n", LED);
     153    return 0;
     154}
     155}}}
     156Le paramètre est défini au moment de l'insertion.
     157{{{#!sh
     158$ sudo insmod ./module.ko LED=2
     159}}}
     160
     161== Étape 3 : création d'un driver qui ne fait rien mais dans le noyau ==
     162
     163Votre driver va être intégré dans un module. Vous allez donc créer un module nommé `ledbp` 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.
     164
     165* Vous ajoutez dans le fichier `.c` du module:
     166 
     167{{{#!c
     168
     169#include <linux/fs.h>
     170
     171struct file_operations fops_ledbp =
     172{
     173    .open       = open_ledbp,
     174    .read       = read_ledbp,
     175    .write      = write_ledbp,
     176    .release    = release_ledbp
     177};
     178static int
     179open_ledbp(struct inode *inode, struct file *file) {
     180    printk(KERN_DEBUG "open()\n");
     181    return 0;
     182}
     183
     184static ssize_t
     185read_ledbp(struct file *file, char *buf, size_t count, loff_t *ppos) {
     186    printk(KERN_DEBUG "read()\n");
     187    return count;
     188}
     189
     190static ssize_t
     191write_ledbp(struct file *file, const char *buf, size_t count, loff_t *ppos) {
     192    printk(KERN_DEBUG "write()\n");
     193    return count;
     194}
     195
     196static int
     197release_ledbp(struct inode *inode, struct file *file) {
     198    printk(KERN_DEBUG "close()\n");
     199    return 0;
     200}
     201}}}
     202* 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.\\\\
     203  * Au début du fichier c du module, vous déclarez une nouvelle variable statique.
     204   {{{#!c
     205   static int major;
     206   }}}
     207  * et dans la fonction d'initalisation du module, vous ajouter l'enregistrement du driver,
     208   {{{#!c
     209   register_chrdev(0, "ledbp" &fops_ledbp); // 0 est le numéro majeur qu'on laisse choisir par linux
     210   }}}
     211  * et vous allez décharger le driver dans ce module en ajoutant dans la fonction exit du module:
     212   {{{#!c
     213   unregister_chrdev(major, "ledbp");
     214   }}}
     215
     216* Vous devez compiler, déplacer le module (upload) et le charger (insmod).
     217* Vous allez chercher dans le fichier `/proc/devices` le numéro `major` choisi par linux.
     218* vous allez maintenant créer le noeud dans le répertoire `/dev`et le rendre accessible par tous.
     219  Le numéro mineur est 0 car il n'y a qu'une seule instance.
     220
     221{{{
     222sudo mknod /dev/ledbp c major 0
     223sudo chmod a+rw /dev/ledbp
     224}}}
     225
     226Le test de votre driver peut se faire par les commandes suivantes (avant de faire un vrai programme):
     227
     228{{{#!sh
     229$ echo "rien" > /dev/ledbp
     230$ dd bs=1 count=1 < /dev/ledbp
     231$ dmesg
     232}}}
     233
     234== Étape 4 : accès aux GPIO depuis les fonctions du pilote ==
     235
     236Pour vous aider, voici un code qui fait clignoter la LED du `GPIO04` dans le module noyau.
     237* 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.
     238* Vous noterez également que l'adresse physique de base des GPIO (ici 0x20200000) est mappé dans l'espace virtuel du noyau à l'adresse '''io_addresse'''.
     239* Vous trouvez aussi le moyen de faire faire périodiquement une fonction au système grâce à une file d'attente temporelle.
     240Inspirez-vous de code pour votre pilote.
     241
     242{{{#!c
     243#include <linux/module.h>
     244#include <linux/init.h>
     245#include <asm/io.h>
     246#include <mach/platform.h>
     247
     248static const int LED0 = 2;
     249
     250//------------------------------------------------------------------------------
     251//                                GPIO ACCES
     252//------------------------------------------------------------------------------
     253
     254struct gpio_s
     255{
     256    uint32_t gpfsel[7];
     257    uint32_t gpset[3];
     258    uint32_t gpclr[3];
     259    uint32_t gplev[3];
     260    uint32_t gpeds[3];
     261    uint32_t gpren[3];
     262    uint32_t gpfen[3];
     263    uint32_t gphen[3];
     264    uint32_t gplen[3];
     265    uint32_t gparen[3];
     266    uint32_t gpafen[3];
     267    uint32_t gppud[1];
     268    uint32_t gppudclk[3];
     269    uint32_t test[1];
     270}
     271*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);;
     272
     273enum {FUN_INPUT, FUN_OUTPUT};
     274
     275static void gpio_fsel(int pin, int fun)
     276{
     277    uint32_t reg = pin / 10;
     278    uint32_t bit = (pin % 10) * 3;
     279    uint32_t mask = 0b111 << bit;
     280    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
     281}
     282
     283static void gpio_write(int pin, bool val)
     284{
     285    if (val)
     286        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
     287    else
     288        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
     289}
     290
     291//------------------------------------------------------------------------------
     292//                             TIMER PROGRAMMING
     293//------------------------------------------------------------------------------
     294
     295static struct timer_list led_blink_timer;
     296static int led_blink_period = 1000;
     297
     298static void led_blink_handler(unsigned long unused)
     299{
     300    static bool on = false;
     301    on = !on;
     302    gpio_write(LED0, on);
     303    mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     304}
     305
     306//------------------------------------------------------------------------------
     307//                              MODULE INIT & EXIT
     308//------------------------------------------------------------------------------
     309
     310MODULE_LICENSE("GPL");
     311MODULE_AUTHOR("Franck from http://sysprogs.com/VisualKernel/tutorials/raspberry/leddriver/)");
     312MODULE_DESCRIPTION("leds on-off");
     313 
     314static int __init LedBlinkModule_init(void)
     315{
     316    int result;
     317
     318    gpio_fsel(LED0, FUN_OUTPUT);
     319    gpio_write(LED0, 1);
     320    setup_timer(&led_blink_timer, led_blink_handler, 0);
     321    result = mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
     322    BUG_ON(result < 0);
     323    return 0;
     324}
     325
     326static void __exit LedBlinkModule_exit(void)
     327{
     328    gpio_fsel(LED0, FUN_INPUT);
     329    del_timer(&led_blink_timer);
     330}
     331
     332module_init(LedBlinkModule_init);
     333module_exit(LedBlinkModule_exit);
     334
     335}}}