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/09_01 https://github.com/take-iwiw/DeviceDriverLesson/tree/master/09_02
In der 4. Folge dieser Serie habe ich einen GPIO-Gerätetreiber für Raspeye implementiert. Zu diesem Zeitpunkt wurde die Steuerung durch direktes Schlagen des Registers durchgeführt. Die Registeradresse und der eingestellte Wert wurden beim Betrachten des Datenblattes von BCM2835 eingestellt. Dies sind "chipabhängige" Informationen. Wenn ich einen Gerätetreiber erstelle, der ein externes Gerät wie einen Sensor oder einen Motor steuert, möchte ich nicht das Datenblatt jedes Chips sehen. Es gibt eine Funktion für die GPIO-Steuerung. Verwenden wir sie also.
In diesem Zusammenhang möchte ich zunächst versuchen, die in anderen Kernelmodulen definierten Funktionen aufzurufen.
Wie bereits in 4 .: Lese- / Schreibimplementierung und Speicherstory erwähnt, verfügt der Kernel über insgesamt einen Speicherplatz. Teilen Dies schließt auch Kernelmodule ein. Daher können Sie die Funktionen anderer Kernelmodule von dem Kernelmodul aus aufrufen, das Sie implementieren, oder Sie können die Funktionen aufrufen, die statisch in den Kernel selbst integriert sind.
Um eine Funktion zu erstellen, die von anderen Modulen aufgerufen werden kann, definieren Sie einfach die Funktion und exportieren Sie sie dann mit "EXPORT_SYMBOL". EXPORT_SYMBOL
registriert die Funktion in der Symboltabelle des Kernels, damit sie von anderen Kernelmodulen aufgerufen werden kann.
Erstellen Sie ein Modul, das nur die Eingabefunktionen zum Laden (insmod) und Entladen (rmmod) und die Funktion (mydevicea_func ()
) definiert. Nennen wir das MyDeviceDriverA. Ursprünglich sollte die Funktionsdeklaration im Header beschrieben werden, sie ist jedoch problematisch und wird daher weggelassen.
make
um MyDeviceDriverA.ko zu machen.
myDeviceDriverA.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceA" /* /proc/Gerätename in Geräten usw. angezeigt.*/
void mydevicea_func(void)
{
printk("This is a message in mydevicea_func\n");
}
/*Registrieren Sie sich in der Symboltabelle des Kernels. Machen Sie es von anderen Kernelmodulen aus aufrufbar*/
EXPORT_SYMBOL(mydevicea_func);
/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydevicea_init(void)
{
printk("[A]: mydevicea_init\n");
mydevicea_func();
return 0;
}
/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevicea_exit(void)
{
printk("[A]: mydevicea_exit\n");
}
module_init(mydevicea_init);
module_exit(mydevicea_exit);
Erstellen Sie das Kernelmodul B, das die zuvor vorbereitete Funktion aufruft. Sie können es wie eine normale C-Sprache nennen. Da der Funktionsdeklarationsheader diesmal weggelassen wird, wird er auf der Aufruferseite mit extern deklariert. Ich bin nicht sehr brav. Versuchen Sie zum Zeitpunkt des Ladens (insmod) dieses Moduls B, die vorherige Funktion (mydevicea_func ()
) aufzurufen.
make
um MyDeviceDriverB.ko zu machen. Der aktuelle Status von "mydevicea_func ()" ist nicht hier, aber es gibt kein Problem beim Erstellen eines Kernelmoduls. Dies liegt daran, dass make for kernel modules nur Objektdateien kompiliert und erstellt, keine Links.
myDeviceDriverB.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDeviceB" /* /proc/Gerätename in Geräten usw. angezeigt.*/
/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydeviceb_init(void)
{
printk("[B]: mydeviceb_init\n");
extern void mydevicea_func(void);
mydevicea_func();
return 0;
}
/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydeviceb_exit(void)
{
printk("[B]: mydeviceb_exit\n");
}
module_init(mydeviceb_init);
module_exit(mydeviceb_exit);
Laden Sie zuerst MyDeviceDriverA.ko.
sudo insmod MyDeviceModuleA.ko
dmesg
[16909.979207] [A]: mydevicea_init
[16909.979223] This is a message in mydevicea_func
Wenn Sie sich nach dem Laden das Protokoll mit dmesg ansehen, sehen Sie, dass der Init-Prozess von Modul A und die Funktion in init aufgerufen wurden. Ich denke nicht, dass dies ein Problem ist.
Laden Sie dann MyDeviceDriverB.ko.
sudo insmod MyDeviceModuleB.ko
dmesg
[17087.119434] [B]: mydeviceb_init
[17087.119449] This is a message in mydevicea_func
Dann können Sie sehen, dass Modul B auf diese Weise auch die in Modul A definierte Funktion aufrufen kann.
Die Abhängigkeit besteht darin, dass Modul B Modul A verwendet. Daher muss Modul A vor dem Laden von Modul B geladen werden. Andernfalls tritt beim Laden von Modul B ein Fehler auf. Ebenso muss Modul B entladen werden, bevor Modul A entladen werden kann. Wenn Sie zuerst versuchen, Modul A zu entladen, wird eine Fehlermeldung angezeigt.
Wenn Sie den erforderlichen Inhalt ordnungsgemäß implementieren und das erstellte Kernelmodul (.ko) an der entsprechenden Stelle platzieren, können Sie "modprobe" anstelle von "insmod" verwenden, um auch die abhängigen Module automatisch zu laden. Es scheint, dass sie es tun werden.
Wie eingangs erwähnt, werden Sie, solange Sie Gerätetreiber für externe Geräte und Onboard-Geräte erstellen, keine Registereinstellungen vornehmen, indem Sie sich das Chip-Datenblatt ansehen. Wenn Sie Ingenieur eines SoC-Herstellers sind, die Funktionen erweitern möchten oder freiwillig Chip-abhängige Geräte entwickeln möchten, benötigen Sie diese. (Es kann also nicht völlig irrelevant sein. Es kann Fehler geben.)
Bei der Steuerung von GPIO über den Gerätetreiber rufen wir die von diesen Personen erstellten Funktionen auf. Derzeit wird es nicht von allen in einem nicht zusammenhängenden Format implementiert, sondern so, dass es eine Schnittstelle wie die in "linux / gpio.h" hat. Infolgedessen können Benutzer (obwohl Devadora-Entwickler) die Funktionen in "linux / gpio.h" verwenden, um GPIO zu steuern. Sie können denselben Code auf einem anderen Chip verwenden, wenn Sie die Funktionen hier verwenden. (In der Dokumentation heißt es etwa "GPIO hat einen breiten Funktionsumfang, folgen Sie also so weit wie möglich" linux / gpio.h "." Daher kann es je nach verwendetem Chip unterschiedlich sein. Es gibt.)
Für welchen Chip der GPIO-Steuerungsprozess verwendet wird, wird durch die Einstellungen zum Zeitpunkt der Kernel-Erstellung bestimmt. Im Fall von Raspetorte sollten Sie das Verfahren für bcm2835 verwenden. Die GPIO-Verarbeitung für BCM2835 erfolgte in "pinctrl-bcm2835.c". Ich habe es nicht genau verfolgt, aber ich denke, wenn Sie die Funktion in linux / gpio.h
aufrufen, werden Sie endlich jeden Prozess von pinctrl-bcm2835.c
erreichen. (Zusätzlich zur Grundsteuerung von GPIO gibt es auch eine MUX-Steuerung als Funktions-Pin, so dass dies ziemlich kompliziert zu sein scheint.)
__ Wie auch immer, Sie können GPIO mit den Funktionen in linux / gpio.h
steuern __
Diese Funktion ist für die grundlegende GPIO-Steuerung erforderlich.
int gpio_direction_output(unsigned gpio, int value)
int gpio_direction_input(unsigned gpio)
void gpio_set_value(unsigned gpio, int value)
int gpio_to_irq(unsigned gpio)
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
Da das Implementieren von Lesen / Schreiben schwierig ist, erstellen Sie einen Gerätetreiber (Kernelmodul) mit den folgenden einfachen Spezifikationen. Als Voraussetzung wird angenommen, dass die LED an GPIO4 von Raspeye und die Taste an GPIO17 angeschlossen ist. Die LED ist über einen Widerstand mit 3,3 V verbunden. Die Taste ist mit GND verbunden und die GPIO17-Seite ist hochgezogen. Wenn es problematisch ist, können Sie den Ausgang von GPIO4 mit einem Tester sehen, oder der Eingang von GPIO17 kann direkt an 3,3 V / GND angeschlossen werden.
Spezifikationen des zu erstellenden Kernelmoduls
Der Code sieht folgendermaßen aus: Der Interrupt-Handler ist "mydevice_gpio_intr ()". Dies wird beim Laden mit request_irq ()
registriert.
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/gpio.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
/***Informationen zu diesem Gerät***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice" /* /proc/Gerätename in Geräten usw. angezeigt.*/
#define GPIO_PIN_LED 4
#define GPIO_PIN_BTN 17
static irqreturn_t mydevice_gpio_intr(int irq, void *dev_id)
{
printk("mydevice_gpio_intr\n");
int btn;
btn = gpio_get_value(GPIO_PIN_BTN);
printk("button = %d\n", btn);
return IRQ_HANDLED;
}
/*Straße(insmod)Funktionen, die manchmal aufgerufen werden*/
static int mydevice_init(void)
{
printk("mydevice_init\n");
/*GPIO4 für LED ausgeben. Der Anfangswert ist 1(High) */
gpio_direction_output(GPIO_PIN_LED, 1);
/*0 bis GPIO4 für LED(Low)Ausgabe*/
gpio_set_value(GPIO_PIN_LED, 0);
/*Geben Sie GPIO17 für die Schaltfläche ein*/
gpio_direction_input(GPIO_PIN_BTN);
/*Rufen Sie die GPIO17-Interrupt-Nummer für die Schaltfläche ab*/
int irq = gpio_to_irq(GPIO_PIN_BTN);
printk("gpio_to_irq = %d\n", irq);
/*Registrieren Sie den GPIO17-Interrupt-Handler für die Schaltfläche*/
if (request_irq(irq, (void*)mydevice_gpio_intr, IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "mydevice_gpio_intr", (void*)mydevice_gpio_intr) < 0) {
printk(KERN_ERR "request_irq\n");
return -1;
}
return 0;
}
/*Entladen(rmmod)Funktionen, die manchmal aufgerufen werden*/
static void mydevice_exit(void)
{
printk("mydevice_exit\n");
int irq = gpio_to_irq(GPIO_PIN_BTN);
free_irq(irq, (void*)mydevice_gpio_intr);
}
module_init(mydevice_init);
module_exit(mydevice_exit);
Erstellen und laden Sie wie folgt.
make
sudo insmod MyDeviceModule.ko
Die LED sollte aufleuchten. Versuchen Sie danach mehrmals, die Taste zu drücken oder GPIO17 an 3,3 V / GND anzuschließen.
dmesg
[19652.388837] mydevice_init
[19652.388873] gpio_to_irq = 183
[19654.100437] mydevice_gpio_intr
[19654.100457] button = 0
[19656.061705] mydevice_gpio_intr
[19656.061727] button = 1
Wenn Sie sich das Protokoll mit dmesg ansehen, können Sie sehen, dass der registrierte Interrupt-Handler aufgerufen und der GPIO-Eingabewert darin gedruckt wird. Übrigens können Sie den Interrupt-Status in / proc / interrupts
überprüfen.
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
183: 7 0 0 0 pinctrl-bcm2835 17 Edge mydevice_gpio_intr
Es scheint keine allgemeine Möglichkeit zu geben, es über den Code festzulegen (https://raspberrypi.stackexchange.com/questions/44924/how-to-set-pull-up-down-resistors-in-a-kernel-module). Es scheint im Gerätebaum eingestellt zu sein. Wenn man sich brcm, bcm2835-gpio.txt ansieht, scheint es, dass es mit brcm, pull
in der dts-Datei festgelegt wird.
Recommended Posts