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/05_01
Bis zum letzten Mal wissen Sie, wie Sie Systemaufrufe (Öffnen, Schließen, Lesen, Schreiben) implementieren, die grundlegende Vorgänge mit dem Gerätetreiber ausführen. Dieses Mal werden wir den bisherigen Inhalt verwenden, um den GPIO-Gerätetreiber für Raspai zu implementieren.
Da der Zweck nicht darin besteht, ein solides GPIO-Gerät herzustellen, werden wir der Einfachheit halber nur auf GPIO4 abzielen und immer den Ausgabemodus verwenden. High / Low wird durch Schreiben ausgegeben. Read gibt den aktuellen Ausgangspegel als Zeichenfolge "1" / "0" zurück.
Letztes Mal erklärte ich die Erinnerung an Raspeye. In Raspberry Pi 2 lautet die physikalische Adresse des von ARM (CPU) aus gesehenen Peripherieregisters 0x3F000000. Dies scheint 0x20000000 für den ursprünglichen Raspberry Pi gewesen zu sein. Bei Betrachtung der Speicherkarte des BCM2835-Datenblatts sagt I / O Peripherals 0x20000000. Es ist gewesen. Wie beim letzten Mal eingeführt, können Sie diese Adresse mit "bcm_host_get_peripheral_address" abrufen. Der Einfachheit halber werden wir es diesmal jedoch auf 0x3F000000 korrigieren. Wenn Razzpie 4 oder 5 in Zukunft herauskommt, kann sich diese Adresse ändern.
Wie oben erwähnt, wissen wir, dass die physikalische Adresse des Peripherieregisters bei 0x3F000000 beginnt. Unter BCM2835-Datenblatt erfahren Sie, welches Register speziell für die GPIO-Steuerung ausgewählt werden muss. Es ist aufgeführt. Derzeit gibt es eine Einschränkung beim Lesen dieses Datenblattes. Dieses Datenblatt enthält eine Beschreibung jedes Registers sowie eine Adresse für jedes Register. Diese Adresse ist jedoch eine Busadresse, die mit 0x7E000000 beginnt. Was wir brauchen, ist die physikalische Adresse, wie sie von der CPU gesehen wird. Wir wissen bereits, dass die Startadresse 0x3F000000 lautet. Sehen Sie sich daher nur den Offset an. Zum Beispiel ist GPFSEL0 (GPIO Function Select 0) im Datenblatt 0x7E200000, aber die von der CPU aus gesehene physikalische Adresse ist 0x3F200000 (im Fall von Raspberry Pi 2).
Übrigens sind die diesmal verwendeten Register wie folgt.
Ich habe bisher Kernelmodule erstellt, aber tatsächlich ist der Registerzugriff vom Benutzerbereich aus möglich. Dieses Mal habe ich nur das GPIO-Steuerregister gedrückt, damit ich es vorerst mit einem Programm im Benutzerbereich implementieren kann. Wenn dies funktioniert, werden wir später mit der Implementierung des Gerätetreibers auf der Seite des Kernelmoduls beginnen. Ein vorheriger Versuch im Benutzerbereich hat den Vorteil, dass das Debuggen einfach ist.
Um von einem User Space-Programm aus auf die physikalische Adresse zuzugreifen, öffnen Sie zuerst / dev / mem
. Verwenden Sie dann diesen Dateideskriptor (fd) für mmap. Ich denke, es ist in Ordnung, nur die Größe zu verwenden, aber es scheint, dass es viele Fälle gibt, in denen die Seitengröße (4 KByte) gesichert ist. Außerdem wird beim Öffnen "O_SYNC" angegeben. Dies macht den Cache ungültig und ermöglicht einen sofortigen Registerzugriff. (Erstens denke ich, dass der Cache nicht verwendet wird, weil es sich um ein Register handelt, sondern nur für den Fall)
Der Code sieht folgendermaßen aus: Sie können auf das Register (0x3F000000) zugreifen, indem Sie die von mmap erfasste virtuelle Adresse lesen und schreiben. Die Erklärung des in jedem Register einzustellenden Wertes entfällt. Bitte überprüfen Sie das Datenblatt. Grundsätzlich werden nur Werte für GPIO4 festgelegt und eingegeben und ausgegeben.
userGpio.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
/*Physikalische Adresse des Peripherieregisters(Aus den Spezifikationen von BCM2835) */
#define REG_ADDR_BASE (0x3F000000) /* bcm_host_get_peripheral_address()Ist besser*/
#define REG_ADDR_GPIO_BASE (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_LENGTH 4096
#define REG_ADDR_GPIO_GPFSEL_0 0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0 0x0034
#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printf("%08X\n", REG(addr));
int main()
{
int address; /*Virtuelle Adresse zum GPIO-Register(Benutzerraum) */
int fd;
/*Öffnen Sie die Gerätedatei für den Speicherzugriff*/
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
perror("open");
return -1;
}
/* ARM(CPU)Physische Adresse von → Zuordnung zur virtuellen Adresse*/
address = (int)mmap(NULL, REG_ADDR_GPIO_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, REG_ADDR_GPIO_BASE);
if (address == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
/*Stellen Sie GPIO4 auf Ausgabe*/
REG(address + REG_ADDR_GPIO_GPFSEL_0) = 1 << 12;
/*Hohe Leistung von GPIO4*/
REG(address + REG_ADDR_GPIO_OUTPUT_SET_0) = 1 << 4;
DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);
/*Niedrige Leistung von GPIO4*/
REG(address + REG_ADDR_GPIO_OUTPUT_CLR_0) = 1 << 4;
DUMP_REG(address + REG_ADDR_GPIO_LEVEL_0);
/*Geben Sie die verwendeten Ressourcen frei*/
munmap((void*)address, REG_ADDR_GPIO_LENGTH);
close(fd);
return 0;
}
Verbinden Sie den GPIO4-Pin (den Pin rechts von der SCL) über die LED und den Widerstand mit 3,3 V. Erstellen und ausführen Sie mit dem folgenden Befehl.
gcc userGpio.c
sudo ./a.out
Wie Sie dem Code entnehmen können, leuchtet die LED auf, da GPIO4 am Ende niedrig ausgegeben wird. Wenn Sie diese Zeile auskommentieren, wird GPIO4 High ausgegeben und die LED erlischt.
Erstellen Sie schließlich einen echten Gerätetreiber. Wie eingangs erwähnt, werden wir diesmal der Einfachheit halber eine sehr einfache Spezifikation verwenden.
Verwenden Sie im Kernel-Space "ioremap_nocache", um physische Adressen in virtuelle Adressen (nicht zwischengespeichert) zu übersetzen. In Bezug auf diese Adresskonvertierung wollte ich beim Laden oder Öffnen des Kernelmoduls einen großen Bereich (z. B. 4 KByte) sofort konvertieren, aber der Kernelraum hat nur einen virtuellen Adressraum als Ganzes. Also fragte ich mich, ob nur ein Modul so viel belegen könnte, und entschied mich, bei jedem Zugriff auf das Register 4 Bytes zu konvertieren. Natürlich ist es in Bezug auf die Geschwindigkeit nachteilig. Was machst du normalerweise? Wenn du irgendwelche Kenntnisse hast, lass es mich wissen. (Im Fall des Kernelraums habe ich das Gefühl, dass virtuelle Adressen nicht zugewiesen, sondern nur von physischen Adressen übersetzt werden, sodass möglicherweise kein Adressraum verschwendet wird.)
Nach der Adresskonvertierung können Sie wie bei einem User Space-Programm auf die Register zugreifen. Der Code sieht folgendermaßen aus: Die Verarbeitung in "mydevice_open", "mydevice_read", "mydevice_write" ist wichtig. Ansonsten sind init und exit die gleichen wie beim letzten Mal. Ich dachte, ich würde es weglassen, aber da es ein separater Artikel ist, werde ich alles posten.
In Bezug auf den Datenaustausch mit dem Benutzerbereich habe ich letztes Mal "copy_to_user" und "copy_from_user" verwendet. Dieses Mal wird nur 1 Byte Daten ausgetauscht, also habe ich versucht, put_user
und get_user
zu verwenden.
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 <linux/slab.h>
#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*Physikalische Adresse des Peripherieregisters(Aus den Spezifikationen von BCM2835) */
#define REG_ADDR_BASE (0x3F000000) /* bcm_host_get_peripheral_address()Ist besser*/
#define REG_ADDR_GPIO_BASE (REG_ADDR_BASE + 0x00200000)
#define REG_ADDR_GPIO_GPFSEL_0 0x0000
#define REG_ADDR_GPIO_OUTPUT_SET_0 0x001C
#define REG_ADDR_GPIO_OUTPUT_CLR_0 0x0028
#define REG_ADDR_GPIO_LEVEL_0 0x0034
#define REG(addr) (*((volatile unsigned int*)(addr)))
#define DUMP_REG(addr) printk("%08X\n", REG(addr));
/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Gerätename in Geräten usw. angezeigt.*/
static const unsigned int MINOR_BASE = 0; /*Startnummer und Anzahl der in diesem Gerätetreiber verwendeten Nebennummern(=Anzahl der Geräte) */
static const unsigned int MINOR_NUM = 1; /*Die kleine Zahl ist nur 0*/
static unsigned int mydevice_major; /*Hauptnummer dieses Gerätetreibers(Dynamisch entscheiden) */
static struct cdev mydevice_cdev; /*Zeichen Geräteobjekt*/
static struct class *mydevice_class = NULL; /*Gerätetreiberklassenobjekt*/
/*Funktion beim Öffnen aufgerufen*/
static int mydevice_open(struct inode *inode, struct file *file)
{
printk("mydevice_open");
/* ARM(CPU)Physische Adresse gesehen von → Virtuelle Adresse(Kernelraum)Zuordnung zu*/
int address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_GPFSEL_0, 4);
/*Stellen Sie GPIO4 auf Ausgabe*/
REG(address) = 1 << 12;
iounmap((void*)address);
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");
/* ARM(CPU)Physische Adresse gesehen von → Virtuelle Adresse(Kernelraum)Zuordnung zu*/
int address = address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_LEVEL_0, 4);
int val = (REG(address) & (1 << 4)) != 0; /*0, wenn GPIO4 0 ist,Auf 1 setzen*/
/*Gibt den GPIO-Ausgabewert als Zeichen an den Benutzer zurück*/
put_user(val + '0', &buf[0]);
iounmap((void*)address);
return count;
}
/*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");
int address;
char outValue;
/*Ruft den vom Benutzer festgelegten Ausgabewert auf GPIO ab*/
get_user(outValue, &buf[0]);
/* ARM(CPU)Physische Adresse gesehen von → Virtuelle Adresse(Kernelraum)Zuordnung zu*/
if(outValue == '1') {
/* '1'Dann SET*/
address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_SET_0, 4);
} else {
/* '0'Dann CLR*/
address = (int)ioremap_nocache(REG_ADDR_GPIO_BASE + REG_ADDR_GPIO_OUTPUT_CLR_0, 4);
}
REG(address) = 1 << 4;
iounmap((void*)address);
return count;
}
/*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", 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);
Bauen Sie es wie folgt in Ihren Kernel ein:
make
sudo insmod MyDeviceModule.ko
echo "0" > /dev/mydevice0
echo "1" > /dev/mydevice0
cat /dev/mydevice0
1111111111111111^C
Das Schreiben von "0" oder "1" mit Echo sollte den Ausgang von GPIO4 ändern und dazu führen, dass die LED flackert. Beim Lesen mit cat wird außerdem der Ausgangspegel zu diesem Zeitpunkt angezeigt. Es wird jedoch immer ein Wert zurückgegeben, sodass Sie ihn mit Strg-c stoppen müssen.
Es scheint, dass Sie die physische Speicherzuordnung mit dem folgenden Befehl überprüfen können. Wenn Sie sich das ansehen, können Sie sehen, dass es den gleichen Inhalt hat, den wir bisher gesehen haben. SDRAM befindet sich von der physischen Adresse 0. In der Speicherzuordnung unten ist es 00000000-3b3fffff. 0x3b3fffff = 994050047 = Ungefähr 1 GByte, also scheinen wir uns tatsächlich zu treffen. Die Registeradresse von Peri befindet sich ebenfalls unter 0x3fXXXXXX.
sudo cat /proc/iomem
00000000-3b3fffff : System RAM
00008000-00afffff : Kernel code
00c00000-00d3da63 : Kernel data
3f006000-3f006fff : dwc_otg
3f007000-3f007eff : /soc/dma@7e007000
3f00b840-3f00b84e : /soc/vchiq
3f00b880-3f00b8bf : /soc/mailbox@7e00b880
3f101000-3f102fff : /soc/cprman@7e101000
3f200000-3f2000b3 : /soc/gpio@7e200000
3f201000-3f201fff : /soc/serial@7e201000
3f201000-3f201fff : /soc/serial@7e201000
3f202000-3f2020ff : /soc/sdhost@7e202000
3f215000-3f215007 : /soc/aux@0x7e215000
3f980000-3f98ffff : dwc_otg
Recommended Posts