Article HowTo pour développer des pilotes de périphériques Linux embarqués en tant que modules de noyau. Tout le contenu de cet article peut être exécuté sur Raspberry Pi.
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/06_01
Jusqu'à la dernière fois, j'ai expliqué comment implémenter les appels système de base (ouvrir, fermer, lire, écrire). De plus, en les utilisant, j'ai en fait créé un pilote de périphérique pour GPIO de Raspeye. Cette fois, je vais également implémenter un appel système appelé ioctl.
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
L'appel système défini ci-dessus. Entrez le descripteur de fichier (fd) obtenu par open dans le premier argument. Le deuxième argument s'appelle une requête, mais c'est une commande. Le troisième argument (longueur variable) est un paramètre. En utilisant cet ioctl, vous pouvez ajouter librement des interfaces côté pilote.
Jusqu'à présent, j'ai utilisé cdev pour créer des pilotes de périphériques. En conséquence, il est reconnu par le noyau comme un pilote de périphérique de type caractère. Par conséquent, lors de l'interaction avec l'application utilisateur en lecture et en écriture, le type de caractère a été utilisé. C'est bien pour les interactions simples, mais ce n'est pas suffisant lorsque vous contrôlez réellement l'appareil. Par exemple, il est nécessaire de spécifier un nombre pour le réglage de la vitesse de communication SPI ou le réglage de l'adresse d'esclave I2C, mais cela ne peut pas être fait uniquement par lecture et écriture. Dans ce cas, ajoutez une interface côté pilote de périphérique. Utilisez ioctl pour cela.
Comme mentionné ci-dessus, ioctl définit ses propres commandes et paramètres pour chaque pilote de périphérique. Par conséquent, la spécification (en-tête) à vérifier lors de l'utilisation d'ioctl n'est pas ioctl.h, mais l'en-tête (ou le code source) préparé par chaque pilote de périphérique. Cette fois, je vais essayer de créer un pilote de périphérique moi-même, donc je pense que vous pouvez vous en faire une idée.
Là encore, ioctl définit ses propres commandes et paramètres côté pilote de périphérique. Les commandes sont des nombres de type int et les paramètres sont généralement des structures. (Aucun paramètre n'est requis). Créez un en-tête avec ces définitions. Puisqu'il sera référencé lorsque l'utilisateur l'utilisera, ce sera un fichier séparé.
myDeviceDriver.h
#ifndef MY_DEVICE_DRIVER_H_
#define MY_DEVICE_DRIVER_H_
#include <linux/ioctl.h>
/***Paramètres pour ioctl(3e argument)Définition de***/
struct mydevice_values {
int val1;
int val2;
};
/***Commande pour ioctl(request,2ème argument)Définition de***/
/*Type de commande pour IOCTL utilisé par ce pilote de périphérique. Tout va bien'M'Essayez de*/
#define MYDEVICE_IOC_TYPE 'M'
/*Une commande pour définir une valeur pour Devadora. Le paramètre est mydevice_type de valeurs*/
#define MYDEVICE_SET_VALUES _IOW(MYDEVICE_IOC_TYPE, 1, struct mydevice_values)
/*Une commande pour obtenir la valeur de Devadora. Le paramètre est mydevice_type de valeurs*/
#define MYDEVICE_GET_VALUES _IOR(MYDEVICE_IOC_TYPE, 2, struct mydevice_values)
#endif /* MY_DEVICE_DRIVER_H_ */
Pour le moment, cette fois, je vais créer une commande pour lire et écrire deux valeurs (val1 et val2). Cela n'a pas de signification particulière. Les noms de commande (demande) doivent être «MYDEVICE_SET_VALUES» et «MYDEVICE_GET_VALUES». Cette commande fonctionne en logique même si vous tapez le nombre directement. Par exemple, vous pouvez effectuer les opérations suivantes.
#define MYDEVICE_SET_VALUES 3 //Les nombres n'ont pas de signification particulière. Tout ce qui est unique dans ce pilote est OK
#define MYDEVICE_GET_VALUES 4 //Les nombres n'ont pas de signification particulière. Tout ce qui est unique dans ce pilote est OK
En fait, à partir d'informations telles que "si le paramètre est en lecture ou en écriture ou en lecture / écriture", "type", "numéro unique", "taille du paramètre", _IO
, _IOW
, _IOR
, Il semble qu'il soit courant de générer en utilisant la macro _IOWR
. Cette fois, le nom du pilote de périphérique est "myDeviceDriver", j'ai donc défini le type sur "M".
En tant que type de paramètre, définissez également une structure struct mydevice_values
qui a deux valeurs.
Le code côté pilote de périphérique n'a besoin que d'une fonction de gestionnaire à utiliser lorsque ioctl est appelé et d'un enregistrement dans la table struct file_operations
. Vous trouverez ci-dessous le code extrait uniquement des parties pertinentes.
myDeviceDriver.c
#include "myDeviceDriver.h"
/*Variables contenant des valeurs pour les tests ioctl*/
static struct mydevice_values stored_values;
/*Fonction appelée lors de ioctl*/
static long mydevice_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
printk("mydevice_ioctl\n");
switch (cmd) {
case MYDEVICE_SET_VALUES:
printk("MYDEVICE_SET_VALUES\n");
if (copy_from_user(&stored_values, (void __user *)arg, sizeof(stored_values))) {
return -EFAULT;
}
break;
case MYDEVICE_GET_VALUES:
printk("MYDEVICE_GET_VALUES\n");
if (copy_to_user((void __user *)arg, &stored_values, sizeof(stored_values))) {
return -EFAULT;
}
break;
default:
printk(KERN_WARNING "unsupported command %d\n", cmd);
return -EFAULT;
}
return 0;
}
/*Table des gestionnaires pour divers appels système*/
static struct file_operations mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
.unlocked_ioctl = mydevice_ioctl,
.compat_ioctl = mydevice_ioctl, // for 32-bit App
};
Les commandes et les paramètres sont définis dans le fichier d'en-tête, ils sont donc inclus. De plus, cette fois, j'ai fait une commande pour définir et OBTENIR la valeur comme un essai, j'ai donc préparé une variable statique pour la contenir. Dans l'implémentation réelle, veuillez le gérer correctement en utilisant private_data dans la structure de fichiers.
C'est le processus dans lequel la fonction mydevice_ioctl
est appelée par ioctl. Comme vous pouvez le voir, il n'y a qu'une instruction switch-case pour la commande (demande). Puisque le pointeur vers le paramètre est en arg, transtypez-le de manière appropriée en lecture et en écriture. C'est la même chose que lire et écrire. Enfin, inscrivez-vous dans la table struct file_operations
. Bien que non décrite ici, cette table est enregistrée avec cdev_init, cdev_add
lorsque le noyau est chargé.
Le contenu de cet article est en ligne avec le contenu de "Linux Device Driver Programming (Yutaka Hirata)".
Enregistrez la fonction dans struct file_operations
pour enregistrer le gestionnaire ioctl. Dans le livre, je me suis inscrit en utilisant le membre .ioctl
. Cependant, il est désormais obsolète. À la place, utilisez .unlocked_ioctl
et .compat_ioctl
. Il semble y avoir deux raisons de prendre en charge les applications 32 bits dans un environnement 64 bits. Les détails ont été expliqués à ici.
Dans le livre, comment prendre en charge les deux environnements 32 bits / 64 bits, tels que l'ajout de remplissage, a également été présenté. Peut-être que .compat_ioctl
a été ajouté pour éviter cela. peut être.
Essayez d'appeler ioctl du pilote de périphérique implémenté avec le programme de test suivant. Définissez la valeur ({1, 2}) avec MYDEVICE_SET_VALUES. Après cela, essayez d'obtenir la valeur avec MYDEVICE_GET_VALUES.
test.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include "myDeviceDriver.h"
int main()
{
int fd;
struct mydevice_values values_set;
struct mydevice_values values_get;
values_set.val1 = 1;
values_set.val2 = 2;
if ((fd = open("/dev/mydevice0", O_RDWR)) < 0) perror("open");
if (ioctl(fd, MYDEVICE_SET_VALUES, &values_set) < 0) perror("ioctl_set");
if (ioctl(fd, MYDEVICE_GET_VALUES, &values_get) < 0) perror("ioctl_get");
printf("val1 = %d, val2 = %d\n", values_get.val1, values_get.val2);
if (close(fd) != 0) perror("close");
return 0;
}
Tout d'abord, créez et chargez le pilote de périphérique. Ensuite, créez et exécutez le programme de test.
make
sudo insmod MyDeviceModule.ko
gcc test.c
./a.out
val1 = 1, val2 = 2
Ensuite, vous pouvez voir que la valeur définie peut être lue comme ceci.
Même si vous êtes un utilisateur qui n'écrit pas vous-même le pilote de périphérique, ioctl est toujours requis lorsque vous touchez un périphérique. C'était toujours compliqué et je n'étais pas sûr, mais en l'implémentant moi-même, je pouvais bien le comprendre.
Recommended Posts