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/03_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_02
La dernière fois, j'ai essayé la méthode consistant à définir le numéro majeur de l'appareil de manière statique et à l'enregistrer dans le noyau. Cependant, cette méthode ne semble pas recommandée pour le moment. Cette fois, nous allons vous montrer comment définir cela de manière dynamique. Essayez également de créer automatiquement un fichier de périphérique en utilisant le mécanisme d'udev.
--Il semble que vous deviez montrer la licence du module noyau. Sans cela, vous recevrez un avertissement au moment de la construction. À la suite du livre, définissez les licences suivantes.
- MODULE_LICENSE("Dual BSD/GPL");
CFILES = myDeviceDriver.c
obj-m := MyDeviceModule.o
MyDeviceModule-objs := $(CFILES:.c=.o)
ccflags-y += -std=gnu99 -Wall -Wno-declaration-after-statement
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
Les fonctions du gestionnaire d'appels système telles que ouvrir / fermer / lire / écrire sont les mêmes que la dernière fois. L'important est le processus d'enregistrement dans la fonction mydevice_init
.
cdev_init
. Plus précisément, enregistrez la table du gestionnaire d'appels système (ouverture / fermeture / lecture / écriture).cdev_add
pour enregistrer l'objet cdev initialisé en 3. dans le noyau.myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <asm/current.h>
#include <asm/uaccess.h>
MODULE_LICENSE("Dual BSD/GPL");
/* /proc/Nom de l'appareil affiché sur les appareils, etc.*/
#define DRIVER_NAME "MyDevice"
/*Numéro de départ et nombre de nombres mineurs utilisés dans ce pilote de périphérique(=Nombre d'appareils) */
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM = 2; /*Le nombre mineur est 0~ 1 */
/*Numéro majeur de ce pilote de périphérique(Décider dynamiquement) */
static unsigned int mydevice_major;
/*Objet périphérique de caractère*/
static struct cdev mydevice_cdev;
/*Fonction appelée à l'ouverture*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
return 0;
}
/*Fonction appelée à la fermeture*/
static int mydevice_close(struct inode *inode, struct file *file)
{
printk("mydevice_close");
return 0;
}
/*Fonction appelée lors de la lecture*/
static ssize_t mydevice_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_read");
buf[0] = 'A';
return 1;
}
/*Fonction appelée au moment de l'écriture*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_write");
return 1;
}
/*Table des gestionnaires pour divers appels système*/
struct file_operations s_mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
};
/*Route(insmod)Fonctions parfois appelées*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Sécurisez un numéro majeur gratuit*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/* 2.Obtenu dev( =Numéro majeur+Numéro mineur)Obtenez le numéro de mesure et conservez-le*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Inutile? */
/* 3.Initialisation de la structure cdev et enregistrement de la table du gestionnaire d'appels système*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.Ce pilote de périphérique(cdev)Vers le noyau*/
cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", cdev_err);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 5.Ce pilote de périphérique(cdev)Depuis le noyau*/
cdev_del(&mydevice_cdev);
/* 6.Supprimez l'enregistrement de numéro majeur utilisé par ce pilote de périphérique*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
La construction et le chargement sont les mêmes que la dernière fois. Bien qu'il s'agisse d'une création de fichier de périphérique, puisque le nombre majeur est déterminé dynamiquement, il est difficile de vérifier chacun d'eux, je vais donc le résumer avec la commande suivante. À propos, dans l'environnement Raspeye, le nombre majeur est 242.
make
sudo insmod MyDeviceModule.ko
sudo mknod --mode=666 /dev/mydevice0 c `grep MyDevice /proc/devices | awk '{print $1;}'` 0
sudo mknod --mode=666 /dev/mydevice1 c `grep MyDevice /proc/devices | awk '{print $1;}'` 1
sudo mknod --mode=666 /dev/mydevice2 c `grep MyDevice /proc/devices | awk '{print $1;}'` 2
J'ai créé / dev / mydevice0, / dev / mydevice1 et / dev / mydevice2 expérimentalement. Lorsque cdev_add
est terminé, le nombre de périphériques est spécifié comme 2. Je peux donc créer / dev / mydevice2, mais quand j'essaye de l'ouvrir ou d'y accéder, j'obtiens une erreur comme cat: / dev / mydevice2: No such device or addres
.
Comme pour le cas statique, supprimez-le ci-dessous.
sudo rmmod MyDeviceModule
sudo rm /dev/mydevice0
sudo rm /dev/mydevice1
sudo rm /dev/mydevice2
Par exemple, vous pouvez changer le processus entre le numéro mineur 0 (/ dev / mydevice0) et le numéro mineur 1 (/ dev / mydevice1). Dans la fonction de gestionnaire de lecture / écriture, vous pouvez utiliser le nombre mineur pour diviser le traitement par cas de commutateur, mais cela peut également être réalisé en séparant la table de gestionnaire à enregistrer. Plus précisément, dans le code ci-dessus, préparez s_mydevice_fops
pour le nombre de mineurs dont vous voulez diviser le traitement (par exemple, s_mydevice_fops0
et s_mydevice_fops1
). En transformant struct cdev mydevice_cdev;
en un tableau et en définissant différemment le contenu de traitement de 3, 4 et 5 (cdev_init ()
, cdev_add ()
, cdev_del ()
, respectivement), Je peux le faire.
Avec la méthode ci-dessus, vous pouvez maintenant charger le pilote de périphérique auquel est attribué dynamiquement le numéro majeur. Cependant, vous devez créer le fichier de périphérique manuellement, ce qui est fastidieux. Linux a un mécanisme appelé udev. Si vous enregistrez une classe dans / sys / class / lors du chargement du pilote, un démon appelé udevd la détectera et créera automatiquement un fichier de périphérique. En termes d'implémentation, un processus supplémentaire d '"enregistrement d'une classe dans / sys / class / lors du chargement d'un pilote" est requis.
En fait, udev semble être utilisé pour la fonction plug & play. Si vous placez les modules de pilote de périphérique (.ko) dans un emplacement spécial (/ lib / modules /), il semble que le pilote de périphérique correspondant sera chargé automatiquement lorsque le périphérique est détecté. Par conséquent, insmod n'est pas nécessaire.
Seuls mydevice_init
et mydevice_exit
ont changé, donc je vais juste les extraire. Des processus d'enregistrement et de suppression de classe ont été ajoutés respectivement.
myDeviceDriver.c(Partiellement omis)
/*Objet de classe de pilote de périphérique*/
static struct class *mydevice_class = NULL;
/*Route(insmod)Fonctions parfois appelées*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Sécurisez un numéro majeur gratuit*/
alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
if (alloc_ret != 0) {
printk(KERN_ERR "alloc_chrdev_region = %d\n", alloc_ret);
return -1;
}
/* 2.Obtenu dev( =Numéro majeur+Numéro mineur)Obtenez le numéro de mesure et conservez-le*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Inutile? */
/* 3.Initialisation de la structure cdev et enregistrement de la table du gestionnaire d'appels système*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.Ce pilote de périphérique(cdev)Vers le noyau*/
cdev_err = cdev_add(&mydevice_cdev, dev, MINOR_NUM);
if (cdev_err != 0) {
printk(KERN_ERR "cdev_add = %d\n", alloc_ret);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 5.Enregistrer la classe de cet appareil(/sys/class/mydevice/faire) */
mydevice_class = class_create(THIS_MODULE, "mydevice");
if (IS_ERR(mydevice_class)) {
printk(KERN_ERR "class_create\n");
cdev_del(&mydevice_cdev);
unregister_chrdev_region(dev, MINOR_NUM);
return -1;
}
/* 6. /sys/class/mydevice/mydevice*faire*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_create(mydevice_class, NULL, MKDEV(mydevice_major, minor), NULL, "mydevice%d", minor);
}
return 0;
}
/*Décharger(rmmod)Fonctions parfois appelées*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 7. /sys/class/mydevice/mydevice*Supprimer*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
}
/* 8.Supprimer l'enregistrement de cours pour cet appareil(/sys/class/mydevice/Supprimer) */
class_destroy(mydevice_class);
/* 9.Ce pilote de périphérique(cdev)Depuis le noyau*/
cdev_del(&mydevice_cdev);
/* 10.Supprimez l'enregistrement de numéro majeur utilisé par ce pilote de périphérique*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Avec le code ci-dessus, / sys / class / mydevice / mydevice0 / dev
sera créé au moment de insmod. Le mécanisme udev crée automatiquement le fichier de périphérique / dev / mydevice0
basé sur les informations contenues dans / sys / class / mydevice / mydevice0 / dev
.
Cependant, par défaut, vous n'aurez pas de privilèges d'accès pour les utilisateurs généraux. Ajoutez un fichier de règles pour modifier les autorisations d'accès. Le fichier de règles se trouve dans / etc / udev / rules.d /
. Le fichier de règles lui-même commence par un nombre et semble être n'importe quel fichier avec une extension .rules. Pour Raspeye, il existe depuis le début un fichier de règles appelé / etc / udev / rules.d / 99-com.rules
, vous pouvez donc l'ajouter ici. Je voulais le diviser, alors j'ai fait ce qui suit. Avec Raspeye, je ne pouvais pas créer de nouveau fichier dans / etc / udev / rules.d /
même si j'avais ajouté sudo
, donc je suis entré en mode super utilisateur une fois avec sudo -i
et j'ai ensuite effectué l'opération.
sudo -i
echo 'KERNEL=="mydevice[0-9]*", GROUP="root", MODE="0666"' >> /etc/udev/rules.d/81-my.rules
exit
Essayez de construire et insmod. Cette fois, les fichiers de périphérique (/ dev / mydevice0 et / dev / mydevice1) sont automatiquement créés simplement par insmoding.
make
sudo insmod MyDeviceModule.ko
ls -a /dev/my*
/dev/mydevice0 /dev/mydevice1
De plus, un fichier contenant des informations sur le périphérique est créé dans le répertoire / sys / class / mydevice / mydevice0 /
et en dessous. Si udev ne fonctionne pas, vérifiez également ici.
cat /sys/class/mydevice/mydevice0/dev
242:0
cat /sys/class/mydevice/mydevice0/uevent
MAJOR=242
MINOR=0
DEVNAME=mydevice0
Le contenu de cet article est en ligne avec le contenu de "Linux Device Driver Programming (Yutaka Hirata)".
Le livre utilise class_device_create
pour créer la classe. Cependant, class_device_create
est maintenant obsolète. Cet article utilise device_create
à la place.
Recommended Posts