wiki:SujetTP2-2017

TP2 : Premier pilote

Objectif

L'objectif de la séance est de commander les LEDS et le bouton poussoir (BP) en passant par un pilote installé dans le noyau. Lors 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.

Nous allons donc créer un pilote pour le périphérique LED+BP.

Ce pilote sera accessible dans par le pseudo-fichier /dev/ledbpXY
XY correspond aux initiales de votre binôme pour éviter les conflits avec vos camarades
Par exemple, pour Almada et Fomentin, il faudrait créer /dev/ledbpAF

Dans le texte de TP, nous n'avons pas fait apparaître les lettres XY, vous devez les ajoutez vous-même.

Par 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.

Le comportement proposé ici du pilote est le suivant :

Pour les LEDS
on envoie un tableau de caractères. La case 'i' définit l'état de la LED 'i' ('0' LED éteinte sinon LED allumée).
Pour les boutons
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'.
- Quand le bouton est relâché, le pilote met le caractère '0'.
- Quand le bouton est enfoncé, le pilote met une valeur différente de '0'.

C'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.

#include <stdio.h>

#define NBLED 2
#define NBBP 1
char led[NBLED];
char bp[NBBP];
 
int main()
{
   int i;
   int fd = open("/dev/ledbpXY", O_RDWR);
   if (fd < 0) {
      fprintf(stderr, "Erreur d'ouverture du pilote LED et Boutons\n");
      exit(1);
   }
   for( i = 0; i < NBLED; i ++) {
      led[i] = '0';
   }
   do { 
      led[O] = (led[O] == '0') ? '1' : '0';
      write( fd, led, NBLED);
      sleep( 1);
      read( fd, bp, NBBP);
   } while (bp[0] == '1');
   return 0;
}

Étape 1 : création et test d'un module noyau

La première étape consiste à créer un module noyau, l'insérer puis l'effacer du noyau.

Le module minimal se compose d'une fonction d'initialisation et d'une fonction de cleanup, dans le fichier module.c suivant:

#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Charlie, 2015");
MODULE_DESCRIPTION("Module, aussitot insere, aussitot efface");

static int __init mon_module_init(void)
{
   printk(KERN_DEBUG "Hello World <votre nom> !\n");
   return 0;
}

static void __exit mon_module_cleanup(void)
{
   printk(KERN_DEBUG "Goodbye World!\n");
}

module_init(mon_module_init);
module_exit(mon_module_cleanup);

Ce fichier est cross compilé et copié sur la Raspberry Pi cible avec le fichier Makefile suivant:

CARD_NUMB       = 2X
ROUTER          = 132.227.102.36
LOGIN           = nom1-nom2
LAB             = lab2

MODULE          = module

CROSSDIR        = /users/enseig/franck/peri
KERNELDIR       = /dsk/l1/misc/linux-rpi-3.18.y
CROSS_COMPILE   = $(CROSSDIR)/arm-bcm2708hardfp-linux-gnueabi/bin/bcm2708hardfp-
        
obj-m           += $(MODULE).o
default:;       make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
clean:;         make -C $(KERNELDIR) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean
upload:;        scp -P50$(CARD_NUMB) $(MODULE).ko pi@$(ROUTER):$(LOGIN)/$(LAB)

Codes source du noyau de la raspberry:

  • Ce Makefile a besoin des sources compilées du noyau. Comme elles sont volumineuses, elles sont copiées dans le répertoire /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 Compilation croisée d'un module linux pour Raspberry Pi.
  • 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 :
    • en téléchargeant l'archive linux-rpi-3.18.y.tbz2 (192Mb) dans le répertoire /dsk/l1/misc
    • 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)

Sur votre compte enseignement, vous devez:

  • Créer ces fichiers et bien sûr, les comprendre.
  • changer la valeur des variables CARD_NUMB, LOGINet 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.
  • Compiler le module avec la commande make.
  • Copier sur la RaspberryPi avec scp avec la commande make upload.

Sur la carte RaspberryPi, vous devez:

  • Dans le répertoire $(LOGIN)/$(LAB)'(par exemple lacas/lab2`) où vous avez copié votre module
$ sudo insmod ./module.ko
$ lsmod
$ dmesg 
$ sudo rmmod module
$ lsmod
$ dmesg
  • Les commandes lsmodet dmesg permettent de voir que les actions du module.
  • Résumez dans le CR ce que que vous avez fait et ce que vous observez.

Étape 2 : ajout des paramètres au module

Votre driver devra être paramétré pour lui indiquer le numéro de ports utilisés pour les LEDS et les boutons. 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.

Vous devez ajouter dans module.c (faite d'équivalent pour les boutons):

static int LED;
module_param(LED, int, 0);
MODULE_PARM_DESC(LED, "Nombre de led");

static int __init mon_module_init(void)
{
    printk(KERN_DEBUG "Hello World !\n");
    printk(KERN_DEBUG "LED=%d !\n", LED);
    return 0;
}

Le paramètre est défini au moment de l'insertion.

$ sudo insmod ./module.ko LED=2
  • Comment voir que le paramètre a été lu ?

Étape 3 : création d'un driver qui ne fait rien mais dans le noyau

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.

  • Vous ajoutez dans le fichier .c du module:

#include <linux/fs.h>

static int 
open_ledbp(struct inode *inode, struct file *file) {
    printk(KERN_DEBUG "open()\n");
    return 0;
}

static ssize_t 
read_ledbp(struct file *file, char *buf, size_t count, loff_t *ppos) {
    printk(KERN_DEBUG "read()\n");
    return count;
}

static ssize_t 
write_ledbp(struct file *file, const char *buf, size_t count, loff_t *ppos) {
    printk(KERN_DEBUG "write()\n");
    return count;
}

static int 
release_ledbp(struct inode *inode, struct file *file) {
    printk(KERN_DEBUG "close()\n");
    return 0;
}

struct file_operations fops_ledbp =
{
    .open       = open_ledbp,
    .read       = read_ledbp,
    .write      = write_ledbp,
    .release    = release_ledbp 
};

  • 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.

    • Au début du fichier c du module, vous déclarez une nouvelle variable statique.
      static int major;
      
    • et dans la fonction d'initialisation du module, vous ajoutez l'enregistrement du driver,
      major = register_chrdev(0, "ledbp", &fops_ledbp); // 0 est le numéro majeur qu'on laisse choisir par linux
      
    • et vous allez décharger le driver dans ce module en ajoutant dans la fonction exit du module:
      unregister_chrdev(major, "ledbp");
      
  • Vous devez compiler, déplacer le module (upload) et le charger (insmod).
  • Vous allez chercher dans le fichier /proc/devices le numéro major choisi par linux.
  • vous allez maintenant créer le noeud dans le répertoire /dev et le rendre accessible par tous. Le numéro mineur est 0 car il n'y a qu'une seule instance.
sudo mknod /dev/ledbp c major 0
sudo chmod a+rw /dev/ledbp
  • Comment savoir que le device a été créé ?

Le test de votre driver peut se faire par les commandes suivantes (avant de faire un vrai programme):

$ echo "rien" > /dev/ledbp
$ dd bs=1 count=1 < /dev/ledbp
$ dmesg

Nous pouvons automatiser le chargement du driver et son effacement dans des scripts shell:

  • Dans un fichier insdev
    #!/bin/sh
    module=$1
    shift
    /sbin/insmod ./$module.ko $* || exit 1
    rm -f /dev/$module
    major=$(awk "\$2==\"$module\" {print \$1;exit}" /proc/devices)
    mknod /dev/$module c $major 0
    chmod 666 /dev/$module
    echo "=> Device /dev/$module created with major=$major"
    
  • Dans un fichier rmdev
    #!/bin/sh
    module=$1
    /sbin/rmmod $module || exit 1
    rm -f /dev/$module
    echo "=> Device /dev/$module removed"
    
  • 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.
    chmod u+x insdev rmdev
    
  • Pour les exécuter :
    $ sudo ./insdev ledbp LED=2
      => Device /dev/ledbp created with major=237
    $ sudo ./rmdev ledbp LED=2
      => Device /dev/ledbp removed
    

Étape 4 : accès aux GPIO depuis les fonctions du pilote

Nous devons pouvoir accéder aux registres de configuration des GPIO.

  • 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.
  • 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().
#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
#include <mach/platform.h>

static const int LED0 = 4;

struct gpio_s
{
    uint32_t gpfsel[7];
    uint32_t gpset[3];
    uint32_t gpclr[3];
    uint32_t gplev[3];
    uint32_t gpeds[3];
    uint32_t gpren[3];
    uint32_t gpfen[3];
    uint32_t gphen[3];
    uint32_t gplen[3];
    uint32_t gparen[3];
    uint32_t gpafen[3];
    uint32_t gppud[1];
    uint32_t gppudclk[3];
    uint32_t test[1];
}
*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);

Les deux fonctions gpio_fsel() et gpio_write() possibles sont données juste après. Vous pouvez voir comment exploiter la structure. Nous vous laissons le soin de faire gpio_read(), puis d'invoquer ces fonctions dans les fonctions open_ledbp(), read_ledbp(), etc.

static void gpio_fsel(int pin, int fun)
{
    uint32_t reg = pin / 10;
    uint32_t bit = (pin % 10) * 3;
    uint32_t mask = 0b111 << bit;
    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
}

static void gpio_write(int pin, bool val)
{
    if (val)
        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
    else
        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
}

Étape 5 : Usage d'un timer dans le noyau pour faire clignoter (optionnel)

Le code suivant fait clignoter la led GPIO04. Vous pouvez adapter votre driver, pour demander un clignotement plutôt qu'un allumage. En effet, il existe un moyen de faire faire périodiquement une fonction au système grâce à une file d'attente temporelle. Inspirez-vous de code pour votre pilote.

#include <linux/module.h>
#include <linux/init.h>
#include <asm/io.h>
#include <mach/platform.h>

static const int LED0 = 2;

//------------------------------------------------------------------------------
//                                GPIO ACCES
//------------------------------------------------------------------------------

struct gpio_s
{
    uint32_t gpfsel[7];
    uint32_t gpset[3];
    uint32_t gpclr[3];
    uint32_t gplev[3];
    uint32_t gpeds[3];
    uint32_t gpren[3];
    uint32_t gpfen[3];
    uint32_t gphen[3];
    uint32_t gplen[3];
    uint32_t gparen[3];
    uint32_t gpafen[3];
    uint32_t gppud[1];
    uint32_t gppudclk[3];
    uint32_t test[1];
}
*gpio_regs = (struct gpio_s *)__io_address(GPIO_BASE);;

enum {FUN_INPUT, FUN_OUTPUT};

static void gpio_fsel(int pin, int fun)
{
    uint32_t reg = pin / 10;
    uint32_t bit = (pin % 10) * 3;
    uint32_t mask = 0b111 << bit;
    gpio_regs->gpfsel[reg] = (gpio_regs->gpfsel[reg] & ~mask) | ((fun << bit) & mask);
}

static void gpio_write(int pin, bool val)
{
    if (val)
        gpio_regs->gpset[pin / 32] = (1 << (pin % 32));
    else
        gpio_regs->gpclr[pin / 32] = (1 << (pin % 32));
}

//------------------------------------------------------------------------------
//                             TIMER PROGRAMMING 
//------------------------------------------------------------------------------

static struct timer_list led_blink_timer;
static int led_blink_period = 1000;

static void led_blink_handler(unsigned long unused)
{
    static bool on = false;
    on = !on;
    gpio_write(LED0, on);
    mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
}

//------------------------------------------------------------------------------
//                              MODULE INIT & EXIT 
//------------------------------------------------------------------------------

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Franck from http://sysprogs.com/VisualKernel/tutorials/raspberry/leddriver/)");
MODULE_DESCRIPTION("leds on-off");
 
static int __init LedBlinkModule_init(void)
{
    int result;

    gpio_fsel(LED0, FUN_OUTPUT); 
    gpio_write(LED0, 1);
    setup_timer(&led_blink_timer, led_blink_handler, 0);
    result = mod_timer(&led_blink_timer, jiffies + msecs_to_jiffies(led_blink_period));
    BUG_ON(result < 0);
    printk(KERN_DEBUG "blink loaded\n");

    return 0;
}

static void __exit LedBlinkModule_exit(void)
{
    gpio_fsel(LED0, FUN_INPUT); 
    del_timer(&led_blink_timer);
    printk(KERN_DEBUG "blink removed\n");
}

module_init(LedBlinkModule_init);
module_exit(LedBlinkModule_exit);

Last modified 8 years ago Last modified on Feb 3, 2017, 9:08:18 AM