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/06_01
Bis zum letzten Mal habe ich erklärt, wie grundlegende Systemaufrufe implementiert werden (Öffnen, Schließen, Lesen, Schreiben). Außerdem habe ich mit ihnen einen Gerätetreiber für GPIO von Raspeye erstellt. Dieses Mal werde ich zusätzlich einen Systemaufruf namens ioctl implementieren.
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
Der oben definierte Systemaufruf. Geben Sie den Dateideskriptor (fd) ein, der durch Öffnen im ersten Argument erhalten wurde. Das zweite Argument heißt Anfrage, aber es ist ein Befehl. Das dritte Argument (variable Länge) ist ein Parameter. Mit diesem ioctl können Sie auf der Treiberseite frei Schnittstellen hinzufügen.
Bisher habe ich cdev verwendet, um Gerätetreiber zu erstellen. Infolgedessen wird es vom Kernel als Gerätetreiber vom Zeichentyp erkannt. Daher wurde bei der Interaktion mit der Benutzeranwendung mit Lesen und Schreiben der Zeichentyp verwendet. Dies ist für einfache Interaktionen in Ordnung, reicht jedoch nicht aus, wenn Sie das Gerät tatsächlich steuern. Beispielsweise muss eine Nummer für die Einstellung der SPI-Kommunikationsgeschwindigkeit oder der I2C-Slave-Adresse angegeben werden. Dies kann jedoch nicht nur durch Lesen und Schreiben erfolgen. Fügen Sie in diesem Fall eine Schnittstelle auf der Gerätetreiberseite hinzu. Verwenden Sie dazu ioctl.
Wie oben erwähnt, definiert ioctl seine eigenen Befehle und Parameter für jeden Gerätetreiber. Daher lautet die bei der Verwendung von ioctl zu überprüfende Spezifikation (Header) nicht ioctl.h, sondern der von jedem Gerätetreiber erstellte Header (oder Quellcode). Dieses Mal werde ich versuchen, selbst einen Gerätetreiber zu erstellen, damit Sie ein Gefühl dafür bekommen.
Auch hier definiert ioctl seine eigenen Befehle und Parameter auf der Gerätetreiberseite. Befehle sind int-Typennummern, und Parameter sind normalerweise Strukturen. (Es sind keine Parameter erforderlich). Erstellen Sie einen Header mit diesen Definitionen. Es wird referenziert, wenn der Benutzer es verwendet, sodass es sich um eine separate Datei handelt.
myDeviceDriver.h
#ifndef MY_DEVICE_DRIVER_H_
#define MY_DEVICE_DRIVER_H_
#include <linux/ioctl.h>
/***Parameter für ioctl(3. Argument)Definition von***/
struct mydevice_values {
int val1;
int val2;
};
/***Befehl für ioctl(request,2. Argument)Definition von***/
/*Der Befehlstyp für IOCTL, der von diesem Gerätetreiber verwendet wird. Alles ist gut'M'Versuchen zu*/
#define MYDEVICE_IOC_TYPE 'M'
/*Ein Befehl zum Festlegen eines Werts für Devadora. Der Parameter ist mydevice_Wertetyp*/
#define MYDEVICE_SET_VALUES _IOW(MYDEVICE_IOC_TYPE, 1, struct mydevice_values)
/*Ein Befehl, um den Wert von Devadora abzurufen. Der Parameter ist mydevice_Wertetyp*/
#define MYDEVICE_GET_VALUES _IOR(MYDEVICE_IOC_TYPE, 2, struct mydevice_values)
#endif /* MY_DEVICE_DRIVER_H_ */
Dieses Mal werde ich vorerst einen Befehl zum Lesen und Schreiben von zwei Werten (val1 und val2) erstellen. Dies hat keine besondere Bedeutung. Die Befehls- (Anforderungs-) Namen sollten "MYDEVICE_SET_VALUES" und "MYDEVICE_GET_VALUES" sein. Dieser Befehl funktioniert logisch, auch wenn Sie die Nummer direkt eingeben. Sie können beispielsweise Folgendes tun.
#define MYDEVICE_SET_VALUES 3 //Die Zahlen haben keine besondere Bedeutung. Alles, was in diesem Treiber einzigartig ist, ist in Ordnung
#define MYDEVICE_GET_VALUES 4 //Die Zahlen haben keine besondere Bedeutung. Alles, was in diesem Treiber einzigartig ist, ist in Ordnung
Tatsächlich aus den Informationen wie "ob der Parameter Lesen oder Schreiben oder Lesen / Schreiben ist", "Typ", "eindeutige Nummer", "Parametergröße", "_IO", "_IOW", "_IOR", " Es scheint üblich zu sein, mit dem Makro _IOWR` zu generieren. Diesmal lautet der Name des Gerätetreibers "myDeviceDriver", daher habe ich den Typ auf "M" gesetzt.
Definieren Sie als Parametertyp auch eine Struktur struct mydevice_values
mit zwei Werten.
Der Code auf der Gerätetreiberseite benötigt nur eine Handlerfunktion, die beim Aufruf von ioctl verwendet werden kann, und eine Registrierung in der Tabelle struct file_operations
. Unten ist der Code nur aus den relevanten Teilen extrahiert.
myDeviceDriver.c
#include "myDeviceDriver.h"
/*Variablen, die Werte für ioctl-Tests enthalten*/
static struct mydevice_values stored_values;
/*Funktion zum Zeitpunkt von ioctl aufgerufen*/
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;
}
/*Handlertabelle für verschiedene Systemaufrufe*/
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
};
Befehle und Parameter werden in der Header-Datei definiert und sind daher enthalten. Außerdem habe ich diesmal einen Befehl zum Festlegen und Abrufen des Werts als Test ausgeführt, sodass ich eine statische Variable vorbereitet habe, um ihn zu speichern. Verwalten Sie es in der realen Implementierung ordnungsgemäß, indem Sie private_data in der Dateistruktur verwenden.
Dies ist der Prozess, in dem die Funktion mydevice_ioctl
von ioctl aufgerufen wird. Wie Sie sehen können, gibt es nur eine switch-case-Anweisung für den Befehl (request). Da sich der Zeiger auf den Parameter in arg befindet, wandeln Sie ihn entsprechend in Lesen und Schreiben um. Dies ist dasselbe wie Lesen und Schreiben. Registrieren Sie es schließlich in der Tabelle struct file_operations
. Obwohl hier nicht beschrieben, wird diese Tabelle beim Laden des Kernels mit "cdev_init, cdev_add" registriert.
Der Inhalt dieses Artikels entspricht dem Inhalt von "Linux Device Driver Programming (Yutaka Hirata)".
Registrieren Sie die Funktion in struct file_operations
, um den ioctl-Handler zu registrieren. In dem Buch habe ich mich mit dem .ioctl
Mitglied registriert. Es ist jedoch jetzt veraltet. Verwenden Sie stattdessen .unlocked_ioctl
und .compat_ioctl
. Es scheint zwei Gründe für die Unterstützung von 32-Bit-Anwendungen in einer 64-Bit-Umgebung zu geben. Details wurden unter hier erläutert.
In diesem Buch wurde auch die Unterstützung beider 32-Bit- / 64-Bit-Umgebungen, z. B. das Hinzufügen von Padding, vorgestellt. Möglicherweise wurde .compat_ioctl
hinzugefügt, um dies zu vermeiden. vielleicht.
Versuchen Sie, ioctl des implementierten Gerätetreibers mit dem folgenden Testprogramm aufzurufen. Stellen Sie den Wert ({1, 2}) mit MYDEVICE_SET_VALUES ein. Versuchen Sie danach, den Wert mit MYDEVICE_GET_VALUES abzurufen.
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;
}
Erstellen und laden Sie zunächst den Gerätetreiber. Erstellen Sie dann das Testprogramm und führen Sie es aus.
make
sudo insmod MyDeviceModule.ko
gcc test.c
./a.out
val1 = 1, val2 = 2
Dann können Sie sehen, dass der eingestellte Wert so gelesen werden kann.
Selbst wenn Sie ein Benutzer sind, der den Gerätetreiber nicht selbst schreibt, ist ioctl immer erforderlich, wenn Sie ein Gerät berühren. Es war immer chaotisch und ich war mir nicht sicher, aber wenn ich es selbst implementierte, konnte ich es gut verstehen.
Recommended Posts