Der Linux-Kernel verfügt über Interrupt-Funktionen. Wenn Sie im Linux-Kernel einen Interrupt verwenden möchten, müssen Sie ihn beantragen, und die Funktion request_irq() wird verwendet, um einen Interrupt zu beantragen. Nachdem der Interrupt verwendet wurde, muss der entsprechende Interrupt über free_irq( freigegeben werden. )-Funktion; es gibt auch die Funktionen „enable_irq()“ und „disable_irq()“, mit denen bestimmte Interrupts aktiviert und deaktiviert werden.
Die Betriebsumgebung dieses Tutorials: Linux7.3-System, Dell G3-Computer.
request_irq-Funktion
Wenn Sie im Linux-Kernel einen Interrupt verwenden möchten, müssen Sie ihn beantragen Um einen Interrupt zu beantragen, kann die Funktion request_irq Schlaf verursachen, sodass die Funktion request_irq nicht im Interrupt-Kontext oder in anderen Codeabschnitten verwendet werden kann, die den Schlaf verhindern. Die Funktion „request_irq“ aktiviert (aktiviert) den Interrupt, sodass wir den Interrupt nicht manuell aktivieren müssen. Der Prototyp der Funktion „request_irq“ lautet wie folgt:
irq: Die Interrupt-Nummer, die für einen Interrupt angewendet werden soll.
handler: Interrupt-Verarbeitungsfunktion. Diese Interrupt-Verarbeitungsfunktion wird ausgeführt, wenn ein Interrupt auftritt.
Flags: Interrupt-Flags, Sie können alle Interrupt-Flags in der Datei include/linux/interrupt.h anzeigen
Name: Interrupt-Name, nach dem Setzen können Sie den entsprechenden Interrupt-Namen in /proc/interrupts sehen Datei .
dev: Wenn Flags auf IRQF_SHARED gesetzt sind, wird dev zur Unterscheidung verschiedener Interrupts verwendet. Im Allgemeinen wird dev auf die Gerätestruktur festgelegt und dev wird an den zweiten Parameter der Interrupt-Verarbeitungsfunktion irq_handler_t übergeben.
Rückgabewert: 0 bedeutet, dass die Interrupt-Anwendung erfolgreich ist, andere negative Werte bedeuten, dass die Interrupt-Anwendung fehlschlägt. Wenn -EBUSY zurückgegeben wird, bedeutet dies, dass der Interrupt beantragt wurde.
free_irq
Wenn Sie einen Interrupt verwenden, müssen Sie ihn über die Funktion „request_irq“ beantragen. Nach Abschluss der Verwendung müssen Sie den entsprechenden Interrupt über die Funktion „free_irq“ freigeben. Wenn der Interrupt nicht gemeinsam genutzt wird, entfernt free_irq den Interrupt-Handler und deaktiviert den Interrupt. Der Funktionsprototyp free_irq lautet wie folgt:
Die Bedeutung der Funktionsparameter und Rückgabewerte ist wie folgt:
irq: Der freizugebende Interrupt.
dev: Wenn der Interrupt auf Shared (IRQF_SHARED) eingestellt ist, wird dieser Parameter verwendet, um bestimmte Interrupts zu unterscheiden. Gemeinsame Interrupts werden nur deaktiviert, wenn der letzte Interrupt-Handler freigegeben wird.
Rückgabewert: Keiner.
Interrupt-Verarbeitungsfunktion
Wenn Sie die Funktion request_irq verwenden, um einen Interrupt zu beantragen, müssen Sie die Interrupt-Verarbeitungsfunktion wie folgt festlegen:
Interrupt aktivieren und Funktionen deaktivieren
Häufig verwendete Interrupt-Nutzungs- und Deaktivierungsfunktionen sind wie folgt:
enable_irq und disable_irq werden zum Aktivieren und Deaktivieren bestimmter Interrupts verwendet, irq ist die Interrupt-Nummer, die deaktiviert werden soll. Die Funktion „disable_irq“ kehrt erst zurück, wenn die aktuell ausgeführte Interrupt-Verarbeitungsfunktion abgeschlossen ist. Daher muss der Benutzer sicherstellen, dass keine neuen Interrupts generiert werden und dass alle Interrupt-Handler, die mit der Ausführung begonnen haben, beendet wurden. In diesem Fall können Sie eine andere Interrupt-Deaktivierungsfunktion verwenden:
disable_irq_nosync Die Funktion kehrt sofort nach dem Aufruf zurück und wartet nicht darauf, dass der aktuelle Interrupt-Handler die Ausführung abschließt. Die oben genannten drei Funktionen aktivieren oder deaktivieren alle einen bestimmten Interrupt. Manchmal müssen wir das gesamte Interrupt-System des aktuellen Prozessors ausschalten, was beim Erlernen von STM32 oft zum Ausschalten von globalen Interrupts führt Funktionen:
local_irq_enable wird verwendet, um das aktuelle Prozessor-Interrupt-System zu aktivieren, und local_irq_disable wird verwendet, um das aktuelle Prozessor-Interrupt-System zu deaktivieren. Wenn Task A „local_irq_disable“ aufruft, um den globalen Interrupt für 10 Sekunden auszuschalten, beginnt Task B mit der Ausführung, wenn er für 2 Sekunden ausgeschaltet ist. Task B ruft auch „local_irq_disable“ auf, um den globalen Interrupt für 3 Sekunden auszuschalten um den globalen Interrupt einzuschalten. Zu diesem Zeitpunkt vergingen 2 + 3 = 5 Sekunden, und dann wurde der globale Interrupt aktiviert. Zu diesem Zeitpunkt wurde der Wunsch von Task A, den globalen Interrupt für 10 Sekunden auszuschalten, zunichte gemacht, und dann wurde Task A „wütend“. Die Folgen waren schwerwiegend, möglicherweise wird das System durch Aufgabe A vollständig zum Absturz gebracht. Um dieses Problem zu lösen, kann die B-Task den globalen Interrupt nicht einfach und grob über die Funktion local_irq_enable aktivieren, sondern den Interrupt-Status auf den vorherigen Zustand zurücksetzen. Unter Berücksichtigung der Gefühle anderer Tasks müssen die folgenden zwei Funktionen verwendet werden zu diesem Zeitpunkt. :
In einigen Materialien werden die obere Hälfte und die untere Hälfte auch als obere Hälfte und untere Hälfte bezeichnet, was die gleiche Bedeutung hat. Die Interrupt-Service-Funktion, die wir bei der Beantragung eines Interrupts mit request_irq registriert haben, gehört zur oberen Hälfte der Interrupt-Verarbeitung. Solange der Interrupt ausgelöst wird, wird die Interrupt-Verarbeitungsfunktion ausgeführt. Wir alle wissen, dass die Interrupt-Verarbeitungsfunktion so schnell wie möglich ausgeführt werden muss und je kürzer, desto besser, aber die Realität ist oft grausam. Einige Interrupt-Verarbeitungsprozesse sind zeitaufwändig und wir müssen sie verarbeiten, um die Ausführungszeit zu verkürzen Unterbrechungsverarbeitungsfunktion. Beispielsweise benachrichtigt der kapazitive Touchscreen das SOC über einen Interrupt, dass ein Berührungsereignis auftritt. Das SOC reagiert auf den Interrupt, liest dann den Berührungskoordinatenwert über die IIC-Schnittstelle und meldet ihn an das System. Wir alle wissen jedoch, dass die maximale Geschwindigkeit von IIC nur 400 Kbit/s beträgt, sodass das Lesen von Daten über IIC während des Interrupts Zeit verschwendet. Wir können den Vorgang des Lesens von Berührungsdaten vorübergehend über IIC ausführen, und die Interrupt-Verarbeitungsfunktion reagiert nur auf den Interrupt und löscht dann das Interrupt-Flag-Bit. Zu diesem Zeitpunkt ist der Interrupt-Verarbeitungsprozess in zwei Teile unterteilt:
Obere Hälfte: Die obere Hälfte ist die Interrupt-Verarbeitungsfunktion. Die Verarbeitungsprozesse, die schneller sind und nicht lange dauern, können in der oberen Hälfte abgeschlossen werden.
Unterer Teil: Wenn der Interrupt-Verarbeitungsprozess zeitaufwändig ist, werden diese zeitaufwändigen Codes ausgegeben und zur Ausführung an den unteren Teil übergeben, sodass die Interrupt-Verarbeitungsfunktion schnell ein- und ausgeht.
Daher besteht der Hauptzweck der Aufteilung von Interrupts in obere und untere Hälften des Linux-Kernels darin, ein schnelles Ein- und Aussteigen aus der Interrupt-Verarbeitungsfunktion zu erreichen. Diese zeitkritischen und schnellen Ausführungsvorgänge können in der Interrupt-Verarbeitungsfunktion platziert werden Obere Hälfte. Alle übrigen Arbeiten können in der unteren Hälfte durchgeführt werden. Beispielsweise werden in der oberen Hälfte Daten in den Speicher kopiert und in der unteren Hälfte kann die spezifische Verarbeitung der Daten durchgeführt werden. Für die Frage, welche Codes zur oberen und welche zur unteren Hälfte gehören, gibt es keine klaren Vorschriften. Alle Urteile basieren auf der tatsächlichen Verwendung. Dies wird die Fähigkeiten der Fahrerautoren testen. Hier sind einige Anhaltspunkte, von denen Sie lernen können:
① Wenn der zu verarbeitende Inhalt nicht durch andere Unterbrechungen unterbrochen werden möchte, kann er in der oberen Hälfte platziert werden.
② Wenn die zu bearbeitende Aufgabe zeitkritisch ist, können Sie sie in die obere Hälfte eintragen.
③ Wenn die zu bearbeitenden Aufgaben einen Bezug zur Hardware haben, können sie in der oberen Hälfte platziert werden.
④ Für andere Aufgaben als die oben genannten drei Punkte sollte die Priorität in der unteren Hälfte liegen.
Mechanismus der unteren Hälfte:
Soft Interrupt
Am Anfang stellte der Linux-Kernel einen Mechanismus für die „untere Hälfte“ zur Implementierung der unteren Hälfte bereit, der als „BH“ bezeichnet wird. Später wurden Soft-Interrupts und Tasklets eingeführt, um den „BH“-Mechanismus zu ersetzen. Ab Version 2.5 des Linux-Kernels wurde BH aufgegeben. Der Linux-Kernel verwendet die Struktur softirq_action, um Soft-Interrupts darzustellen. Die Struktur softirq_action ist in der Datei include/linux/interrupt.h definiert und der Inhalt lautet wie folgt:
Im Kernel/ sind insgesamt 10 Soft-Interrupts definiert. softirq.c-Datei, wie unten gezeigt:
NR_SOFTIRQS ist ein Aufzählungstyp, der in der Datei include/linux/interrupt.h definiert ist und wie folgt definiert ist:
Es ist ersichtlich, dass 10 Soft-Interrupts vorhanden sind insgesamt, also ist NR_SOFTIRQS 10, also hat das Array softirq_vec 10 Elemente. Die Aktionsmitgliedsvariable in der Struktur softirq_action ist die Servicefunktion des Soft-Interrupts, sodass alle CPUs (für SMP-Systeme) darauf zugreifen und nur ausführen können Soft-Interrupts, die von selbst ausgelöst werden. Die von jeder CPU ausgeführten Soft-Interrupt-Dienstfunktionen sind jedoch tatsächlich dieselben und alle Aktionsfunktionen, die im Array softirq_vec definiert sind. Um Soft-Interrupts zu verwenden, müssen Sie zunächst die Funktion open_softirq verwenden, um die entsprechende Soft-Interrupt-Verarbeitungsfunktion zu registrieren. Der Prototyp der open_softirq-Funktion lautet wie folgt:
nr: Um den Soft-Interrupt zu aktivieren, wählen Sie einen aus Beispielcode 51.1.2.3.
Aktion: Die Verarbeitungsfunktion, die dem Soft-Interrupt entspricht.
Rückgabewert: Kein Rückgabewert.
Nach der Registrierung des Soft-Interrupts muss dieser über die Funktion raise_softirq ausgelöst werden. Der Prototyp der Funktion raise_softirq lautet wie folgt:
Soft-Interrupt muss während der Kompilierung statisch registriert werden! Der Linux-Kernel verwendet die Funktion softirq_init, um Softirqs zu initialisieren. Der Funktionsinhalt ist wie folgt:
tasklet
tasklet ist ein weiterer Mechanismus der unteren Hälfte, der mit Softirqs implementiert wird . Zwischen Soft-Interrupts und Tasklets wird die Verwendung von Tasklets empfohlen. Die Funktionsfunktion in Zeile 489 der Linux-Kernelstruktur
ist die vom Tasklet auszuführende Verarbeitungsfunktion. Der Inhalt der benutzerdefinierten Funktion entspricht der Interrupt-Verarbeitungsfunktion. Wenn Sie ein Tasklet verwenden möchten, müssen Sie zuerst ein Tasklet definieren und dann die Funktion tasklet_init verwenden, um das Tasklet zu initialisieren. Der Prototyp der Funktion taskled_init lautet wie folgt:
Die Bedeutung der Funktionsparameter und Rückgabewerte lauten wie folgt:
t: Das zu initialisierende Tasklet
func: Tasklet-Verarbeitungsfunktion.
Daten: Parameter, die an die Funktion func übergeben werden sollen
Rückgabewert: Kein Rückgabewert.
Sie können auch das Makro DECLARE_TASKLET verwenden, um die Definition und Initialisierung des Tasklets gleichzeitig abzuschließen. DECLARE_TASKLET ist in der Datei include/linux/interrupt.h definiert und wie folgt definiert:
wobei Name der Name des Tasklets ist zu definieren, was ein tasklet_struct-Typ ist. Wenn Variablen verwendet werden, ist func die Verarbeitungsfunktion des Tasklets und data der an die func-Funktion übergebene Parameter.
Im oberen Teil kann das Aufrufen der Funktion tasklet_schedule in der Interrupt-Verarbeitungsfunktion dazu führen, dass das Tasklet zum richtigen Zeitpunkt ausgeführt wird. Der Prototyp der Funktion tasklet_schedule lautet wie folgt:
Das Referenzverwendungsbeispiel für das Tasklet lautet wie folgt :
Arbeitswarteschlange
Die Arbeitswarteschlange ist eine weitere Ausführungsmethode der unteren Hälfte. Die Arbeitswarteschlange übergibt die Arbeit, die zur Ausführung verschoben werden soll, an einen Kernel-Thread Die Warteschlange ermöglicht das Schlafen oder Umplanen. Wenn also die Arbeit, die Sie verschieben möchten, in den Ruhezustand versetzt werden kann, können Sie die Arbeitswarteschlange auswählen, andernfalls können Sie nur Soft-Interrupts oder Tasklets auswählen.
Der Linux-Kernel verwendet die Struktur work_struct, um einen Job darzustellen. Der Inhalt ist wie folgt (die bedingte Kompilierung wird weggelassen):
Diese Jobs sind in Arbeitswarteschlangen organisiert, und die Arbeitswarteschlange wird durch die Struktur workqueue_struct dargestellt, der Inhalt ist wie folgt (bedingte Kompilierung wird weggelassen):
Der Linux-Kernel verwendet Arbeitsthreads, um jeden Job in der Arbeitswarteschlange zu verarbeiten. Der Inhalt der Arbeitsthreads ist wie folgt:
Wie aus dem Beispielcode 51.1.2.10 ersichtlich ist, verfügt jeder Worker über eine Arbeitswarteschlange, und der Worker-Thread verarbeitet die gesamte Arbeit in seiner eigenen Arbeitswarteschlange. Bei der eigentlichen Treiberentwicklung müssen wir nur die Arbeit (work_struct) definieren und müssen uns grundsätzlich nicht um die Arbeitswarteschlange und die Arbeitsthreads kümmern. Das Erstellen einer Arbeit ist sehr einfach. Definieren Sie einfach eine Strukturvariable „work_struct“ und initialisieren Sie die Arbeit dann mit dem Makro „INIT_WORK“. Das Makro „INIT_WORK“ ist wie folgt definiert:
Wenn Sie den Gerätebaum verwenden, müssen Sie die Interrupt-Attributinformationen im Gerätebaum festlegen. Der Linux-Kernel konfiguriert Interrupts, indem er die Interrupt-Attributinformationen im Gerätebaum liest. Für Interrupt-Controller finden Sie Informationen zur Gerätebaumbindung im Dokument Documentation/devicetree/bindings/arm/gic.txt. Öffnen Sie die Datei imx6ull.dtsi. Der intc-Knoten ist der Interrupt-Controller-Knoten von I.MX6ULL. Der Knoteninhalt lautet wie folgt:
Zeile 2, der kompatible Attributwert ist „arm, cortex-a7-gic“. Kernel-Quellcode Suchen Sie nach „arm,cortex-a7-gic“, um die GIC-Interrupt-Controller-Treiberdatei zu finden.
Zeile 3, #interrupt-cells ist dasselbe wie #address-cells, #size-cells. Gibt die Zellengröße des Geräts unter diesem Interrupt-Controller an. Für das Gerät wird das Interrupts-Attribut verwendet, um die Interrupt-Informationen zu beschreiben, d. h. wie viele Zellen es für eine gibt Nachricht. Jede Zelle ist ein 32-Bit-Ganzzahlwert, der von ARM verarbeitet wird. Die Bedeutung dieser drei Zellen ist wie folgt:
Die ersten Zellen: Interrupt-Typ, 0 bedeutet SPI-Interrupt, 1 bedeutet PPI-Interrupt .
Die zweiten Zellen: Bei SPI-Interrupts beträgt der Bereich der Interrupt-Nummern 0–987. Bei PPI-Interrupts liegt der Bereich der Interrupt-Nummern bei 0–15.
Die dritte Zelle: Flag, Bit[3:0] gibt den Interrupt-Triggertyp an. Wenn es 1 ist, zeigt es einen Trigger mit steigender Flanke an, wenn es 2 ist, zeigt es einen Trigger mit fallender Flanke an, wenn es 4 ist, zeigt es an Bei einem Wert von 8 handelt es sich um einen Auslöser mit hohem Pegel. Bei einem Wert von 8 handelt es sich um einen Auslöser mit hohem Pegel. Bit[15:8] ist die CPU-Maske des PPI-Interrupts.
In Zeile 4 ist der Interrupt-Controller-Knoten leer, was darauf hinweist, dass der aktuelle Knoten der Interrupt-Controller ist.
Für GPIO kann der GPIO-Knoten auch als Interrupt-Controller verwendet werden. Der Inhalt des GPIO5-Knotens in der Datei imx6ull.dtsi lautet beispielsweise wie folgt:
In Zeile 4 beschreibt Interrupts die Interrupt-Quelleninformationen gpio5, es gibt insgesamt zwei Informationen, alle Interrupt-Typen sind SPI und die Triggerebenen sind IRQ_TYPE_LEVEL_HIGH. Der Unterschied liegt in der Interrupt-Quelle, eine ist 74 und die andere ist 75. Öffnen Sie das Kapitel „Kapitel 3 Interrupts und DMA-Ereignisse“ im „IMX6ULL-Referenzhandbuch“ und suchen Sie nach Tabelle 3-1, wie in Abbildung 50.1.3.1 dargestellt:
Wie aus Abbildung 50.1.3.1 ersichtlich ist, verwendet GPIO5 insgesamt 2 Interrupt-Nummern, eine ist 74 und die andere ist 75. Davon entsprechen 74 den unteren 16 IOs von GPIO5_IO00~GPIO5_IO15 und 75 den höheren 16 IOs von GPIO5_IO16~GPIOI5_IO31. Zeile 8, Interrupt-Controller, gibt an, dass der GPIO5-Knoten auch ein Interrupt-Controller ist, der zur Steuerung aller E/A-Interrupts von GPIO5 verwendet wird.
Zeile 9, ändere #interrupt-cells in 2.
Öffnen Sie die Datei imx6ull-alientek-emmc.dts und finden Sie den folgenden Inhalt:
Die Bedeutung der Funktionsparameter und des Rückgabewerts ist wie folgt:
dev: Geräteknoten.
index: Das Interrupts-Attribut kann mehrere Interrupt-Informationen enthalten, die über den Index abgerufen werden sollen.
Rückgabewert: Interrupt-Nummer. Wenn Sie GPIO verwenden, können Sie die Funktion gpio_to_irq verwenden, um die Interrupt-Nummer zu erhalten, die GPIO entspricht. Der Funktionsprototyp lautet wie folgt:
#include <linux/types.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/ide.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <asm/mach/map.h> #include <linux/timer.h> #include <linux/jiffies.h> #define IMX6UIRQ_CNT 1 /* 设备号个数 */ #define IMX6UIRQ_NAME "irqDev" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0 按键值 */ #define INVAKEY 0XFF /* 无效的按键值 */ #define KEY_NUM 1 /* 按键数量 */ /* 可能会有好多按键,通过结构体数组来描述按键 */ /* 中断 IO 描述结构体 */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* 中断号 */ unsigned char value; /* 按键对应的键值 */ char name[10]; /* 名字 */ irqreturn_t (*handler)(int, void *); /* 中断服务函数 */ }; /* irq设备结构体 */ struct imx6uirq_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* 字符设备 */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 注设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备节点 */ atomic_t keyvalue; /* 有效的按键键值 */ atomic_t releasekey; /* 标记是否完成一次完成的按键*/ struct timer_list timer; /* 定义一个定时器*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */ unsigned char curkeynum; /* 当前的按键号 */ }; struct imx6uirq_dev irqDev; /* 定义LED结构体 */ /* @description : 中断服务函数,开启定时器,延时 10ms, * 定时器用于按键消抖。 * 两个参数是中断处理函数的必须写法 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; /* 采用定时器削抖,如果再定时器时间内还是这个值,说明是真的按下了,在定时器中断中处理 */ /* 这里设置为0是简易处理,因为只有一个按键 */ /* 有其他按键要再建一个中断处理函数,并把curkeynum改成相应的按键值 */ /* 注意不能所有按键用一个中断函数,第一是一起按的时候会出错 */ /* 第二,无法用curkeynum判断使用的是第几个按键 */ dev->curkeynum = 0; /* 传递给定时器的参数,注意要强转,在中断处理函数里面再转回来 */ dev->timer.data = (volatile long)dev_id; /* mod_timer会启动定时器,第二个参数是要修改的超时时间 */ mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); return IRQ_RETVAL(IRQ_HANDLED); } /* @description : 定时器服务函数,用于按键消抖,定时器到了以后 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。 * @param – arg : 设备结构变量 * @return : 无 */ void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; /* 因为只有一个按键,这里是0 */ num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */ if(value == 0){ /* 按下按键 */ atomic_set(&dev->keyvalue, keydesc->value); } else{ /* 按键松开 */ /* 这种情况是按下再松开的松开,使用keyValue加上releaseKey */ /* 没按下的话, releasekey一直为0*/ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ } } /* * @description : 按键 IO 初始化 * @param : 无 * @return : 无 */ static int keyio_init(void) { unsigned char i = 0; int ret = 0; /* 1.获取key节点 */ irqDev.nd = of_find_node_by_path("/key"); if (irqDev.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* 对每个按键都提取 GPIO */ for (i = 0; i < KEY_NUM; i++) { irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i); if (irqDev.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* 初始化 key 所使用的 IO,并且设置成中断模式 */ for (i = 0; i < KEY_NUM; i++) { /* 先对每一个IO命名 */ /* 先对命名清0 */ memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name)); /* 给IO命名 */ sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i); /* 请求GPIO */ gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name); /* 设置GPIO为输入 */ gpio_direction_input(irqDev.irqkeydesc[i].gpio); /* 获取中断号,以下为两个方法,都可以获取到 */ /* 从interrupts属性里面获取 */ /* 注意i是根据设备树里面设置了多少个就是多少个,都会获取到 */ /* 下面的方法是通用的获取中断号的函数 */ irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i); #if 0 /* 此方法是gpio获取中断号的方法 */ irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio); #endif printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].irqnum); } /* 2. 按键中断初始化 */ /* 设置中断处理函数和按键初始值 */ /* 因为只有一个key0.,所以这里也没用循环 */ irqDev.irqkeydesc[0].handler = key0_handler; irqDev.irqkeydesc[0].value = KEY0VALUE; /* 申请中断 */ for (i = 0; i < KEY_NUM; i++) { /* request_irq参数 * 中断号,中断函数,中断触发类型,中断名字,传递给中断处理函数的参数(第二个),这里传的结构体 * */ ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, irqDev.irqkeydesc[i].name, &irqDev); if(ret < 0){ printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum); return -EFAULT; } } /* 3. 创建定时器 */ init_timer(&irqDev.timer); irqDev.timer.function = timer_function; /* 注意下面不能让定时器运行,因为要按下按键之后再运行 */ /* 启动定时器通过mod_timer启动,通常在初始化阶段的定时器用的是add_timer */ return 0; } static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &irqDev; return 0; } static int imx6uirq_release(struct inode *inode, struct file *filp) { //struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; return 0; } /* * @description : 从设备读取数据 * @param – filp : 要打开的设备文件(文件描述符) * @param – buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param – offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; /* 按键值 */ unsigned char releasekey = 0; /* 标记是否一次完成 */ struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* 有按键按下 */ if (keyvalue & 0x80) { keyvalue &= ~0x80; /* 因为中断中或了一个0x80,这里面去掉0x80 */ ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0); /* 按下标志清零 */ } else { /* 没有按下 */ goto data_error; } return 0; data_error: return -EINVAL; } /* 字符设备操作集 */ static const struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .release = imx6uirq_release, .read = imx6uirq_read }; /* 模块入口函数 */ static int __init imx6uirq_init(void) { /* 定义一些所需变量 */ int ret = 0; /* 1. 注册字符设备驱动 */ irqDev.major = 0; if(irqDev.major) { irqDev.devid = MKDEV(irqDev.major, 0); ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME ); } else { alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME ); irqDev.major = MAJOR(irqDev.devid); irqDev.minor = MINOR(irqDev.devid); } if(ret < 0){ goto fail_devid; } printk("Make devid success! \r\n"); printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor); /* 2. 初始化cdev */ irqDev.cdev.owner = THIS_MODULE; cdev_init(&irqDev.cdev, &imx6uirq_fops); ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT); if (ret < 0){ goto fail_cdev; } else { printk("Cdev add sucess! \r\n"); } /* 3. 自动创建设备节点 */ irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME ); if(IS_ERR(irqDev.class)) { ret = PTR_ERR(irqDev.class); goto fail_class; } else { printk("Class create sucess! \r\n"); } irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME ); if(IS_ERR(irqDev.device)) { ret = PTR_ERR(irqDev.device); goto fail_device; } else { printk("Device create sucess! \r\n"); } /* 4.初始化按键 */ atomic_set(&irqDev.keyvalue, INVAKEY); atomic_set(&irqDev.releasekey, 0); keyio_init(); printk("irqDev init! \r\n"); return 0; /* 错误处理 */ fail_device: class_destroy(irqDev.class); fail_class: cdev_del(&irqDev.cdev); fail_cdev: unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT); fail_devid: return ret; } /* 模块出口函数 */ static void __exit imx6uirq_exit(void) { unsigned int i = 0; /* 删除定时器 */ del_timer_sync(&irqDev.timer); /* 释放中断 */ for (i = 0; i < KEY_NUM; i++) { free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev); } /* 1. 释放设备号 */ cdev_del(&irqDev.cdev); /* 2. 注销设备号 */ unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT); /* 3. 摧毁设备 */ device_destroy(irqDev.class, irqDev.devid); /* 4.摧毁类 */ class_destroy(irqDev.class); printk("irqDev exit! \r\n"); } /* 模块入口和出口注册 */ module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Shao Zheming");
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include "linux/ioctl.h" /* * argc: 应用程序参数个数 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯 * .chrdevbaseApp /dev/led 0 关灯 * .chrdevbaseApp /dev/led 1 开灯 * */ int main(int argc, char *argv[]) { if(argc != 2) { printf("Error Usage!\r\n"); return -1; } int fd, ret; char *filename; unsigned char data; filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failed! \r\n", filename); return -1; } while (1) { ret = read(fd, &data, sizeof(data)); if (ret < 0) { /* 数据读取错误或者无效 */ } else { /* 数据读取正确 */ if (data) /* 读取到数据 */ printf("key value = %#X\r\n", data); } } close(fd); return 0; }
Das obige ist der detaillierte Inhalt vonVerfügt der Linux-Kernel über eine Interrupt-Funktion?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!