Semaine Athens Linux Device Drivers


Introduction

Le sous-système input du noyau permet de gérer tous les périphériques capturant les actions de l'utilisateur : notamment le clavier, la souris, mais également les manettes de jeu, les boutons divers, etc.

Il se décompose principalement en trois parties :

Pilote de périphérique input

Structure input_dev

Chaque pilote de périphérique qui veut s'enregistrer auprès du sous-système input est décrit par la structure struct input_dev définie dans include/linux/input.h.

Allocation

Cette structure, complexe, doit être allouée et initialisée à l'aide de la fonction :

struct input_dev *devm_input_allocate_device(struct device *dev);

Comme pour les autres fonctions devm_*, la structure sera automatiquement désallouée lorsque le périphérique dev disparaîtra. L'ancienne fonction non automatique est toujours disponible :

struct input_dev *input_allocate_device(void);

Dans ce cas, la structure doit être désallouée explicitement en appelant la fonction :

void input_free_device(struct input_dev *dev);

Contenu

Nous allons maintenant décrire les principaux champs de la structure struct input_dev.

const char *name : nom du périphérique

struct input_id id : identifiant du périphérique (bustype indique le bus sur lequel est connecté le périphérique (voir liste dans include/uapi/linux/input.h), vendor et product indiquent l'identifiant du fabricant et du produit)

unsigned long evbit[BITS_TO_LONGS(EV_CNT)] : champ de bits indiquant les types d'événements susceptibles d'être générés par le périphérique. La liste des événements et leur signification est disponible dans Documentation/input/event-codes.rst. Retenons EV_KEY (touches), EV_REL (valeurs sur un axe avec changements relatifs, i.e. par rapport à la position précédente, exemple souri), EV_ABS (valeurs absolues sur un axe, exemple joystick) et EV_SW (switch on/off).

unsigned long keybit[BITS_TO_LONGS(KEY_CNT)] : pour les périphériques ayant indiqué EV_KEY dans le champ evbit, ce champ de bits indique les touches présentes (voir les symboles KEY_ et BTN_ dans include/linux/input.h).

unsigned long relbit[BITS_TO_LONGS(REL_CNT)] : pour les périphériques ayant indiqué EV_REL dans le champ evbit, ce champ de bits indique les axes présents (voir les symboles REL_ dans include/linux/input.h).

unsigned long absbit[BITS_TO_LONGS(ABS_CNT)] : pour les périphériques ayant indiqué EV_ABS dans le champ evbit, ce champ de bits indique les axes présents (voir les symboles ABS_ dans include/linux/input.h).

struct input_absinfo *absinfo : pour les périphériques ayant indiqué EV_ABS dans le champ evbit, cette structure décrit les propriétés de chacun des axes (valeur minimale, maximale, marge de bruit et zone neutre). Ce champ peut être manipulé à l'aide de la fonction :

void input_set_abs_params(struct input_dev *dev, unsigned int axis,
                          int min, int max, int fuzz, int flat);

int (*open)(struct input_dev *dev) : pointeur vers une fonction qui sera appelée la première fois qu'une application ouvrira le périphérique. Cette fonction n'est pas nécessaire mais elle permet de différer certaines actions (mise en place du polling, activation des interruptions en provenance du périphérique, etc.).

void (*close)(struct input_dev *dev) : pointeur vers une fonction qui sera appelée lorsque plus aucune application n'utilise le périphérique. De même que pour open, cette fonction n'est pas obligatoire mais elle permet par exemple de désactiver les interruptions en provenance du périphérique quand il n'est pas utilisé.

Enregistrement

Une fois la structure struct input_dev correctement renseignée, il faut appeler la fonction :

int input_register_device(struct input_dev *dev);

pour enregistrer le pilote auprès du sous-système input.

L'opération inverse est réalisée à l'aide de la fonction :

void input_unregister_device(struct input_dev *dev);

Génération d'événements

Lorsqu'un événement survient, il faut le signaler à l'aide des fonctions suivantes en fonction du type d'événement.

Pour une touche (EV_KEY) :

void input_report_key(struct input_dev *dev, unsigned int code, int value);

Pour une valeur relative (EV_REL) :

void input_report_rel(struct input_dev *dev, unsigned int code, int value);

Pour une valeur absolue (EV_ABS) :

void input_report_abs(struct input_dev *dev, unsigned int code, int value);

À chaque fois, code indique la touche ou l'axe concerné (KEY_, BTN_, REL_, ABS_) et value la valeur (booléen dans le cas d'une touche ou valeur relative ou absolue selon le cas).

Une fois les différents événements rapportés, il faut ensuite impérativement appeler la fonction :

static inline void input_sync(struct input_dev *dev);

D'autres fonctions sont disponibles ainsi qu'une fonction plus générique : void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value).

Anatomie d'un pilote

Le fichier Documentation/input/input-programming.rst contient un exemple documenté d'un pilote de périphérique input.

Périphériques polled

Il existe une sous-classe qui permet de faciliter l'écriture d'un pilote pour un périphérique input qui nécessite d'être interrogé (polled) régulièrement, i.e. qui n'est pas capable de générer une interruption lorsqu'un événement survient.

Un tel périphérique est représenté par la structure struct input_polled_dev définie dans include/linux/input-polldev.h.

struct input_polled_dev {
   void *private;

   void (*open)(struct input_polled_dev *dev);
   void (*close)(struct input_polled_dev *dev);
   void (*poll)(struct input_polled_dev *dev);

   unsigned int poll_interval; /* msec */
   unsigned int poll_interval_max; /* msec */
   unsigned int poll_interval_min; /* msec */

   struct input_dev *input;
   /* ... */
}

Allocation

Cette structure doit être allouée et initialisée à l'aide d'une des deux fonctions suivantes :

struct input_polled_dev *devm_input_allocate_polled_device(struct device *dev);
struct input_polled_dev *input_allocate_polled_device(void);

Elle peut être désallouée à l'aide de la fonction :

void input_free_polled_device(struct input_polled_dev *dev);

Contenu de la structure struct input_polled_dev

Les fonctions open et close fonctionnent de la même manière que celles de la structure input_dev.

La fonction poll est importante. C'est cette fonction qui sera appelée régulièrement par le sous-système input et qui est en charge d'interroger le périphérique et de générer les événements correspondants si nécessaire.

poll_interval indique la fréquence (par défaut 500 ms) à laquelle la fonction poll est appelée.

Les champs de la structure struct input_dev *input (et notamment name, evbit, keybit, relbit et/ou absbit) doivent aussi être remplis avant l'enregistrement.

Enregistrement

Une fois la structure correctement remplie, le périphérique peut être enregistré auprès du sous-système input à l'aide de la fonction :

int input_register_polled_device(struct input_polled_dev *dev);

Le périphérique peut être désenregistré à l'aide de la fonction :

void input_unregister_polled_device(struct input_polled_dev *dev);

Interface utilisateur

Au niveau utilisateur, les périphériques peuvent être accédés via plusieurs interfaces en fonction de leur type (une interface est spécialisée pour les claviers, une autre pour les souris, etc.). Une interface générique evdev permet de récupérer simplement tous les événements.

Les périphériques sont représentés par un fichier spécial /dev/input/eventX.

Chaque lecture dans un de ces fichiers renvoie une structure struct input_event décrivant un événement :

struct input_event {
   struct timeval time;
   unsigned short type;
   unsigned short code;
   unsigned int value;
};

Ces fichiers spéciaux supportent les lectures bloquantes (read bloque en attendant un événement) et non bloquante (read retourne tout de suite si aucun événement ne s'est produit). Il est également possible d'utiliser select.

Un petit utilitaire, evtest (installé sur les maquettes de TP), permet de visualiser facilement tous les événements.

Travail à faire

Vous allez faire en sorte que le pilote de l'accéléromètre s'enregistre comme un périphérique input disposant de trois axes (X, Y et Z).

Une première variante de votre pilote utilisera les interruptions et s'enregistrera comme un périphérique input "classique". La seconde variante utilisera input_polled_dev sans interruptions.


Retour au sommaire du cours


© Copyright 2017 Guillaume Duc. Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International (à l'exception des exemples de code tirés du noyau Linux et qui sont distribués sous leurs licences d'origine).

Licence
Creative Commons