HowTo-Artikel zum Entwickeln eingebetteter Linux-Gerätetreiber als Kernelmodule. Der gesamte Inhalt dieses Artikels kann auf Raspberry Pi ausgeführt werden.
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/03_02
Beim letzten Mal habe ich versucht, die Hauptnummer des Geräts statisch festzulegen und im Kernel zu registrieren. Diese Methode scheint derzeit jedoch nicht empfohlen zu werden. Dieses Mal zeigen wir Ihnen, wie Sie dies dynamisch einstellen. Versuchen Sie außerdem, mithilfe des Mechanismus von udev automatisch eine Gerätedatei zu erstellen.
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
Systemaufruf-Handler-Funktionen wie Öffnen / Schließen / Lesen / Schreiben sind dieselben wie beim letzten Mal. Wichtig ist der Registrierungsprozess innerhalb der Funktion mydevice_init
.
alloc_chrdev_region
. Legen Sie zu diesem Zeitpunkt auch die Informationen zu der von diesem Gerätetreiber verwendeten Nebennummer fest. Diesmal habe ich versucht, die kleinen Zahlen 0 und 1 zu verwenden. Angenommen, zwei Geräte können verbunden werden.cdev_init
. Registrieren Sie insbesondere die Tabelle des Systemaufruf-Handlers (Öffnen / Schließen / Lesen / Schreiben).cdev_add
, um das in 3. initialisierte cdev-Objekt im Kernel zu registrieren.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/Gerätename in Geräten usw. angezeigt.*/
#define DRIVER_NAME "MyDevice"
/*Startnummer und Anzahl der in diesem Gerätetreiber verwendeten Nebennummern(=Anzahl der Geräte) */
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM = 2; /*Die kleine Zahl ist 0~ 1 */
/*Hauptnummer dieses Gerätetreibers(Dynamisch entscheiden) */
static unsigned int mydevice_major;
/*Zeichen Geräteobjekt*/
static struct cdev mydevice_cdev;
/*Funktion beim Öffnen aufgerufen*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
return 0;
}
/*Funktion wird beim Schließen aufgerufen*/
static int mydevice_close(struct inode *inode, struct file *file)
{
printk("mydevice_close");
return 0;
}
/*Funktion beim Lesen aufgerufen*/
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;
}
/*Funktion zum Zeitpunkt des Schreibens aufgerufen*/
static ssize_t mydevice_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
printk("mydevice_write");
return 1;
}
/*Handlertabelle für verschiedene Systemaufrufe*/
struct file_operations s_mydevice_fops = {
.open = mydevice_open,
.release = mydevice_close,
.read = mydevice_read,
.write = mydevice_write,
};
/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Sichern Sie sich eine kostenlose Hauptnummer*/
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.Erhaltene dev( =Hauptnummer+Kleine Zahl)Holen Sie sich und behalten Sie die Hauptnummer von*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Nicht notwendig? */
/* 3.Initialisierung der cdev-Struktur und Registrierung der Systemaufruf-Handler-Tabelle*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.Dieser Gerätetreiber(cdev)Zum Kernel*/
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;
}
/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 5.Dieser Gerätetreiber(cdev)Aus dem Kernel*/
cdev_del(&mydevice_cdev);
/* 6.Entfernen Sie die von diesem Gerätetreiber verwendete Hauptnummernregistrierung*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Erstellen und Laden sind die gleichen wie beim letzten Mal. Obwohl es sich um eine Gerätedateierstellung handelt, ist es schwierig, jede zu überprüfen, da die Hauptnummer dynamisch bestimmt wird. Daher werde ich sie mit dem folgenden Befehl zusammenfassen. In der Raspeye-Umgebung ist die Hauptzahl übrigens 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
Ich habe / dev / mydevice0, / dev / mydevice1 und / dev / mydevice2 experimentell gemacht. Wenn cdev_add
fertig ist, wird die Anzahl der Geräte als 2 angegeben. Ich kann also / dev / mydevice2 erstellen, aber wenn ich versuche, es zu öffnen oder darauf zuzugreifen, erhalte ich eine Fehlermeldung wie "cat: / dev / mydevice2: Kein solches Gerät oder keine solche Adresse".
Löschen Sie es wie im statischen Fall unten.
sudo rmmod MyDeviceModule
sudo rm /dev/mydevice0
sudo rm /dev/mydevice1
sudo rm /dev/mydevice2
Beispielsweise möchten Sie möglicherweise den Prozess zwischen der Nebennummer 0 (/ dev / mydevice0) und der Nebennummer 1 (/ dev / mydevice1) ändern. In der Lese- / Schreib-Handler-Funktion können Sie die Verarbeitung durch die Nebenzahl nach Switch-Case teilen, dies kann jedoch auch durch Trennen der zu registrierenden Handler-Tabelle realisiert werden. Bereiten Sie im obigen Code "s_mydevice_fops" speziell für die Anzahl der Minderjährigen vor, deren Verarbeitung Sie teilen möchten (z. B. "s_mydevice_fops0" und "s_mydevice_fops1"). Indem Sie "struct cdev mydevice_cdev;" in ein Array verwandeln und den Verarbeitungsinhalt von 3, 4 und 5 unterschiedlich einstellen ("cdev_init ()", "cdev_add ()", "cdev_del ()"), Ich kann damit umgehen.
Mit der obigen Methode können Sie jetzt den Gerätetreiber laden, dem dynamisch die Hauptnummer zugewiesen wurde. Sie müssen die Gerätedatei jedoch manuell erstellen, was mühsam ist. Linux hat einen Mechanismus namens udev. Wenn Sie beim Laden des Treibers eine Klasse in / sys / class / registrieren, erkennt ein Daemon namens udevd diese und erstellt automatisch eine Gerätedatei. In Bezug auf die Implementierung ist ein zusätzlicher Prozess zum "Registrieren einer Klasse in / sys / class / beim Laden eines Treibers" erforderlich.
Eigentlich scheint udev für die Plug & Play-Funktion verwendet zu werden. Wenn Sie das Gerätetreibermodul (.ko) an einem bestimmten Speicherort (/ lib / modules /) ablegen, wird der entsprechende Gerätetreiber anscheinend automatisch geladen, wenn das Gerät erkannt wird. Insmod ist daher nicht erforderlich.
Nur mydevice_init
und mydevice_exit
haben sich geändert, daher werde ich sie einfach extrahieren. Klassenregistrierungs- und Löschprozesse wurden hinzugefügt.
myDeviceDriver.c(Teilweise weggelassen)
/*Gerätetreiberklassenobjekt*/
static struct class *mydevice_class = NULL;
/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
int alloc_ret = 0;
int cdev_err = 0;
dev_t dev;
/* 1.Sichern Sie sich eine kostenlose Hauptnummer*/
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.Erhaltene dev( =Hauptnummer+Kleine Zahl)Holen Sie sich und behalten Sie die Hauptnummer von*/
mydevice_major = MAJOR(dev);
dev = MKDEV(mydevice_major, MINOR_BASE); /*Nicht notwendig? */
/* 3.Initialisierung der cdev-Struktur und Registrierung der Systemaufruf-Handler-Tabelle*/
cdev_init(&mydevice_cdev, &s_mydevice_fops);
mydevice_cdev.owner = THIS_MODULE;
/* 4.Dieser Gerätetreiber(cdev)Zum Kernel*/
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.Registrieren Sie die Klasse dieses Geräts(/sys/class/mydevice/machen) */
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*machen*/
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;
}
/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
dev_t dev = MKDEV(mydevice_major, MINOR_BASE);
/* 7. /sys/class/mydevice/mydevice*Löschen*/
for (int minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
device_destroy(mydevice_class, MKDEV(mydevice_major, minor));
}
/* 8.Entfernen Sie die Klassenregistrierung für dieses Gerät(/sys/class/mydevice/Löschen) */
class_destroy(mydevice_class);
/* 9.Dieser Gerätetreiber(cdev)Aus dem Kernel*/
cdev_del(&mydevice_cdev);
/* 10.Entfernen Sie die von diesem Gerätetreiber verwendete Hauptnummernregistrierung*/
unregister_chrdev_region(dev, MINOR_NUM);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Mit dem obigen Code wird zum Zeitpunkt von insmod / sys / class / mydevice / mydevice0 / dev
erstellt. Der udev-Mechanismus erstellt automatisch die Gerätedatei "/ dev / mydevice0" basierend auf den Informationen in "/ sys / class / mydevice / mydevice0 / dev".
Standardmäßig haben Sie jedoch keine Zugriffsrechte für allgemeine Benutzer. Fügen Sie eine Regeldatei hinzu, um die Zugriffsberechtigungen zu ändern. Die Regeldatei befindet sich in / etc / udev / rules.d /
. Die Regeldatei selbst beginnt mit einer Nummer und scheint eine beliebige Datei mit der Erweiterung .rules zu sein. Für Raspeye gibt es von Anfang an eine Regeldatei mit dem Namen "/ etc / udev / rules.d / 99-com.rules", sodass Sie sie dort hinzufügen können. Ich wollte es teilen, also tat ich Folgendes. Mit Raspeye konnte ich keine neue Datei in "/ etc / udev / rules.d /" erstellen, selbst wenn ich "sudo" hinzugefügt habe. Deshalb bin ich einmal mit "sudo -i" in den Superuser-Modus gegangen und habe dann den Vorgang ausgeführt.
sudo -i
echo 'KERNEL=="mydevice[0-9]*", GROUP="root", MODE="0666"' >> /etc/udev/rules.d/81-my.rules
exit
Versuchen Sie zu bauen und insmod. Dieses Mal werden Gerätedateien (/ dev / mydevice0 und / dev / mydevice1) automatisch nur durch Insmoding erstellt.
make
sudo insmod MyDeviceModule.ko
ls -a /dev/my*
/dev/mydevice0 /dev/mydevice1
Außerdem wird eine Datei mit Geräteinformationen im Verzeichnis / sys / class / mydevice / mydevice0 /
und darunter erstellt. Wenn udev nicht funktioniert, überprüfen Sie dies auch hier.
cat /sys/class/mydevice/mydevice0/dev
242:0
cat /sys/class/mydevice/mydevice0/uevent
MAJOR=242
MINOR=0
DEVNAME=mydevice0
Der Inhalt dieses Artikels entspricht dem Inhalt von "Linux Device Driver Programming (Yutaka Hirata)".
Das Buch verwendet "class_device_create", um die Klasse zu erstellen. Class_device_create
ist jedoch jetzt veraltet. Dieser Artikel verwendet stattdessen "device_create".
Recommended Posts