Schreiben eines Linux-Kernelmoduls: GPIO mit IRQ-Unterstützung

Habr, hallo!

Dieser Artikel befasst sich mit der Entwicklung des GPIO-Moduls (General-Purpose Input / Output) des Linux-Kernels. Wie im vorherigen Artikel implementieren wir die Grundstruktur des GPIO-Treibers mit Interrupt-Unterstützung (IRQ: Interrupt Request).



Die Eingabedaten ähneln dem vorherigen Artikel: Der GPIO-Block, der für den neuen Prozessor entwickelt wurde, ist mit dem FPGA „verkabelt“ und läuft unter Linux Version 3.18.19.

Um einen GPIO-Treiber zu entwickeln, müssen Sie die folgenden Schritte ausführen:

  1. Verstehen Sie das Prinzip der Interaktion zwischen dem GPIO-Treiber und der Benutzeroberfläche.
  2. Fügen Sie das Kernelmodul zur Assembly hinzu und beschreiben Sie die Hardware im Gerätebaum.
  3. Implementieren Sie das Grundgerüst des Treibers sowie seine Eingangs- und Abrufpunkte.
  4. Implementieren Sie den funktionalen Teil des GPIO-Treibers.
  5. Fügen Sie der Treiberimplementierung IRQ-Unterstützung hinzu.

Beispiele für GPIO-Treiber finden Sie hier .

Erster Schritt


Machen wir uns zunächst mit dem Prinzip der Interaktion zwischen dem GPIO-Treiber über die Benutzerkonsole vertraut.

Erstellen Sie mithilfe eines kleinen Bash-Skripts die Steuerelemente für jedes GPIO in / sysfs. Schreiben Sie dazu das folgende Skript in die Befehlszeile:

for i in {248..255}; do echo $i > /sys/class/gpio/export; done 

Als nächstes wollen wir sehen, welche Funktionen / sysfs für die Konfiguration jedes GPIO bereitstellen:

 root@zed-slave:/sys/class/gpio# ls -l gpio248/ total 0 -rw-r--r-- 1 root root 4096 Jan 7 20:50 active_low -rw-r--r-- 1 root root 4096 Jan 7 20:50 direction -rw-r--r-- 1 root root 4096 Jan 7 20:50 edge drwxr-xr-x 2 root root 0 Jan 7 20:50 power lrwxrwxrwx 1 root root 0 Jan 7 20:50 subsystem -> ../../../../class/gpio -rw-r--r-- 1 root root 4096 Jan 7 20:10 uevent -rw-r--r-- 1 root root 4096 Jan 7 20:50 value 

Derzeit interessieren uns folgende Bereiche:

  • Richtung - Legt die Richtung der Linie fest. Kann die Werte "in" oder "out" annehmen;
  • Wert - Ermöglicht das Einstellen eines hohen oder niedrigen Signals auf der Leitung (wenn die Richtung auf "out" eingestellt ist), andernfalls (wenn die Richtung auf "in" eingestellt ist) können Sie den Status der Leitung lesen.
  • edge - Mit dieser Option können Sie das Ereignis konfigurieren, bei dem der Interrupt auftritt. Es kann folgende Werte annehmen: "keine", "steigend", "fallend" oder "beides".

Nach einer kurzen Einführung in die Treiberoberfläche über sysfs können Sie überlegen, wie der Treiber Benutzerbefehle verarbeitet. Der Linux-Kernel hat eine gpio_chip-Struktur, die die Funktionalität des gpio-Controllers beschreibt. Die folgenden Felder sind darin vorhanden:

  • direction_input : Setzt die Zeile auf den Eingang. Wird bei folgendem Eintrag aufgerufen: echo "in"> / sys / class / gpio / gpio248 / direction;
  • direction_output : Setzt die zu beendende Zeile. Wird bei folgendem Eintrag aufgerufen: echo "out"> / sys / class / gpio / gpio248 / direction;
  • get : liest den in der Zeile eingestellten Wert. Wird bei folgendem Eintrag aufgerufen: cat / sys / class / gpio / gpio248 / value;
  • set : Legt den Wert in der Zeile fest. Wird bei folgendem Eintrag aufgerufen: echo 1/0> / sys / class / gpio / gpio248 / value;

Um die IRQ-Konfiguration unter Linux zu beschreiben, gibt es eine irq_chip-Struktur, die die folgenden Felder enthält:

  • irq_set_type : Legt den Ereignistyp fest, bei dem der Interrupt auftritt. Wird bei folgendem Eintrag aufgerufen: echo> "steigend" / "fallend" / "beide"> / sys / class / gpio / gpio248 / edge;
  • irq_mask : Verbietet Interrupts. Wird bei folgendem Eintrag aufgerufen: echo "none"> / sys / class / gpio / gpio248 / edge;
  • irq_unmask : Aktiviert den Interrupt für ein Ereignis, das auf irq_set_type festgelegt wurde. Wird sofort nach Ausführung von irq_set_type aufgerufen.

Zweiter Schritt


Jetzt können Sie den Treiber zur Baugruppe hinzufügen und die Hardware gemäß den Standardschritten beschreiben. Erstellen Sie zunächst die Quelldatei:

 cd drivers/gpio/ vim gpio-skel.c :wq 

Nachdem wir die Treiberkonfiguration zu drivers / gpio / Kconfig hinzugefügt haben :

 config GPIO_SKEL tristate "SKEL GPIO" help Say yes here to support SKEL GPIO. 

Fügen Sie den Treiber der Assembly in drivers / gpio / Makefile hinzu :

 obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o 

Fügen Sie abschließend eine Beschreibung des GPIO-Blocks zu devicetree (* .dts) hinzu:

 gpio: gpio@f8f01d00 { compatible = "skel-gpio"; rcm,ngpio = <8>; rcm,interrupt-type = <IRQ_TYPE_EDGE_RISING>; clocks = <&clkc 42>; gpio-controller ; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; reg = <0x43c00000 0x100>; } ; 

Hier können Sie mehr über Gerätebaum lesen.

Schritt drei


Kommen wir zum für uns interessantesten Teil!

Wir beginnen die Entwicklung des Treibers, indem wir die erforderlichen Header-Dateien verbinden und das gesamte Skelett des Treibers ohne IRQ-Unterstützung beschreiben. Als nächstes füllen wir jede Funktion nacheinander mit einem Code und begleiten die notwendigen Erklärungen.

GPIO-Treiberskelett
 /* gpio-skel.c: GPIO driver * * Name Surname <email> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { } /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { } /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_probe(struct platform_device *pdev) { } static int skel_gpio_remove(struct platform_device *pdev) { } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL"); 


Wie Sie der Implementierung entnehmen können, sieht das Skelett des Treibers recht einfach aus und enthält nicht viele erforderliche Funktionen und Strukturen.

Um den zukünftigen Treiber zu beschreiben, benötigen wir folgende Elemente:

  • platform_driver skel_gpio_driver - beschreibt den Einstiegspunkt skel_gpio_probe beim Laden des Treibers und skel_gpio_remove beim Extrahieren aus dem Kernel;
  • struct of_device_id skel_gpio_of_match - enthält eine Tabelle, die die Hardware des GPIO-Blocks beschreibt;
  • struct skel_gpio_chip - enthält die erforderlichen Felder zur Steuerung des GPIO-Blocktreibers.

Um den Treiber unter Linux zu laden / zu extrahieren, müssen die in der Struktur skel_gpio_driver angegebenen Methoden .probe und .remove implementiert werden.

Implementierung von Skel_gpio_probe
 static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) goto free; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } 


Implementierung von Skel_gpio_remove
 static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } 


Die Funktion skel_gpio_remove entfernt einfach den registrierten GPIO-Treiber aus dem Kernel. Beachten Sie daher die wichtigsten Punkte in skel_gpio_probe:

  • devm_kzalloc - reserviert Speicher für die Struktur skel_gpio_chip;
  • platform_get_resource - liest die Anfangsadresse der GPIO-Registrierungskarte aus dem Gerätebaum;
  • devm_ioremap_resource - führt die Zuordnung einer physischen Adresse zu einer virtuellen Adresse durch;
  • of_property_read_u32 - liest die Anzahl der verfügbaren GPIOs aus dem Gerätebaum;
  • skel_gc-> gchip. * - füllt die erforderlichen Strukturfelder aus;
  • gpiochip_add - fügt dem Treiber einen GPIO-Controller hinzu;

Bisher wurde nicht beschrieben, warum magische Zahlen wie 248 ... 255 verwendet werden. Der Eintrag skel_gc-> gchip.base = -1; fordert den Kernel auf, die vom GPIO verwendeten Nummern dynamisch zuzuweisen. Um diese Zahlen am Ende des Treibers herauszufinden, wird die Ausgabe hinzugefügt:

 dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); 

Natürlich bietet Linux die Möglichkeit, Zahlen manuell festzulegen, aber schauen wir uns den Kommentar im Quellcode an :

 @base: identifies the first GPIO number handled by this chip; * or, if negative during registration, requests dynamic ID allocation. * DEPRECATION: providing anything non-negative and nailing the base * offset of GPIO chips is deprecated. Please pass -1 as base to * let gpiolib select the chip base in all possible cases. We want to * get rid of the static GPIO number space in the long run. 


Vierter Schritt


Betrachten Sie den funktionalen Teil des Treibers, nämlich implementieren wir die folgenden Methoden:
.direction_output , .direction_input , .get und .set . Als nächstes wird ein hardwareabhängiger Code angezeigt, der in den meisten Fällen unterschiedlich ist.

Implementierung von Skel_gpio_direction_input
 /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } 


Die Methode skel_gpio_direction_input führt die folgenden Aktionen aus:

  • Wird mit dem folgenden Befehlsecho "in"> / sys / class / gpio / gpioN / direction aufgerufen.
  • Liest das Register SKEL_GPIO_PAD_DIR, das für die Einstellung der Richtung des GPIO-Pins verantwortlich ist.
  • Legt die gewünschte Maske fest.
  • Schreibt den empfangenen Wert zurück in SKEL_GPIO_PAD_DIR.

Implementierung von Skel_gpio_direction_output
 /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } 


Die Methode skel_gpio_direction_output führt Aktionen ähnlich wie skel_gpio_direction_inut aus, außer dass sie mit den folgenden Befehlen aufgerufen wird:

  • echo "out"> / sys / class / gpio / gpioN / direction;
  • Echo “high”> / sys / class / gpio / gpioN / direction, wobei der Wert auf 1 gesetzt wird;
  • Echo “low”> / sys / class / gpio / gpioN / direction und setze den Wert auf 0.

value - Ein Wert, der den Signalpegel auf der Ausgangsleitung bestimmt.

Implementierung von Skel_gpio_set
 static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } 


Die Methode skel_gpio_set führt die folgenden Aktionen aus:

  • Wird mit dem folgenden Befehl echo 1/0> / sys / class / gpio / gpioN / value aufgerufen.
  • Liest das Register SKEL_GPIO_WR_DATA, das den Wert des aktuellen Signals auf der Leitung anzeigt.
  • Setzt oder setzt das erforderliche Bit durch Versatz zurück;
  • Schreibt den empfangenen Wert zurück in SKEL_GPIO_WR_DATA.

Implementierung von Skel_gpio_get
 static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } 


Die Methode skel_gpio_get liest den Signalwert in der Zeile durch Lesen des Registers SKEL_GPIO_RD_DATA.

Nachdem wir alle notwendigen Methoden und Strukturen beschrieben haben, können Sie alles zusammenstellen und sich die endgültige Version ansehen.

Implementierung des GPIO-Treibers
 /* gpio-skel.c: GPIO driver * * Name Surname <email> * * This file is licensed under the terms of the GNU General Public License * version 2. This program is licensed "as is" without any warranty of any * kind, whether express or implied. */ #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { return container_of(chip, struct skel_gpio_chip, gchip); } /* * echo > "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } /* * echo > "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) return -ENXIO; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL"); 


Der implementierte Treiber enthält die erforderlichen Funktionen zur Steuerung des GPIO. Derzeit unterstützt der Treiber jedoch keine Interrupt-Behandlung, sodass Sie mit dem nächsten Schritt fortfahren können.

Fünfter Schritt


Das Hinzufügen von IRQ zum GPIO-Treiber kann in drei Schritte unterteilt werden:

  • Beschreibung der unterstützten Methoden in Kerneldatenstrukturen;
  • Aktivieren der IRQ-Unterstützung zum Zeitpunkt des Ladens des Treibers in das System;
  • Implementierung unterstützter Methoden.

Zunächst beschreiben wir die erforderlichen Operationen:

 static struct irq_chip skel_irq_chip = { .name = "skel-gpio", .irq_mask = skel_gpio_irq_mask, .irq_unmask = skel_gpio_irq_unmask, .irq_set_type = skel_gpio_irq_set_type, }; 

Daher kann der Treiber Unterbrechungen aktivieren (skel_gpio_irq_unmask) / deaktivieren (skel_gpio_irq_mask) und den Ereignistyp angeben, von dem er generiert wird (skel_gpio_irq_set_type).
Als nächstes beschreiben wir die einzige Methode, die für die Zuordnung einer virtuellen IRQ-Nummer zu einer Hardware-Nummer verantwortlich ist.

 static struct irq_domain_ops skel_gpio_irq_domain_ops = { .map = skel_gpio_irq_domain_map, }; 

Dann werden wir dem Kernel mitteilen, dass der geladene Treiber die Arbeit mit IRQ unterstützt. Fügen Sie dazu der Sondenfunktion den folgenden Code hinzu:

IRQ-Unterstützung hinzufügen
 skel_gc->gchip.to_irq = skel_gpio_to_irq; skel_gc->domain = irq_domain_add_linear(pdev->dev.of_node, rcm_gc->gchip.ngpio, &skel_gpio_irq_domain_ops, skel_gc); if (!skel_gc->domain) return -ENODEV; skel_gc->irq = platform_get_irq(pdev, 0); if (skel_gc->irq < 0) goto free; for (i = 0; i < skel_gc->gchip.ngpio; i++) { int irq = rcm_gpio_to_irq(&skel_gc->gchip, i); irq_set_chip_and_handler(irq, &skel_irq_chip, handle_simple_irq); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else irq_set_noprobe(irq); #endif } irq_set_chained_handler(skel_gc->irq, skel_irq_handler); irq_set_handler_data(skel_gc->irq, skel_gc); 


Im obigen Code passiert:

  • Zuweisung und Initialisierung des Bereichs unter irq_domain;
  • Interruptnummer mit Gerätebaum lesen;
  • Zuordnung zwischen virtuellem und Hardware-Interrupt;
  • Registrieren eines Interrupt-Handlers und Einstellen von Daten für die Übertragung an den Handler;

Wir werden einige der oben beschriebenen Methoden implementieren.
skel_gpio_to_irq - Erstellt eine Zuordnung zwischen Hardware und virtuellen Interrupts. Wenn diese Zuordnung bereits erstellt wurde, wird die Nummer des erstellten virtuellen Interrupts zurückgegeben.

Implementierung von Skel_gpio_to_irq
 static int skel_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) { struct skel_gpio_chip *rcm = to_skel_gpio(chip); return irq_create_mapping(rcm->domain, gpio); } 


skel_irq_handler ist ein Interrupt-Handler, der:

  • Liest ein Interrupt-Statusregister;
  • Liest das Interrupt-Maskenregister;
  • Löst Interrupts aus, die auf die Verarbeitung warten.
  • Ruft für jedes GPIO, in dem eine Unterbrechung auftritt, generic_handle_irq auf.

Implementierung des Interrupt-Handlers
 static void skel_irq_handler(unsigned int irq, struct irq_desc *desc) { struct skel_gpio_chip *skel_gc = irq_get_handler_data(irq); struct irq_chip *chip = irq_desc_get_chip(desc); void __iomem *base; u32 status, mask, gpio, pending; chained_irq_enter(chip, desc); base = skel_gc->regs; status = gpio_read(base, SKEL_GPIO_STATUS); mask = gpio_read(base, SKEL_GPIO_IRQ_MASK); pending = status & mask; while (pending) { gpio = __ffs(pending); pending &= ~BIT(gpio); generic_handle_irq( irq_find_mapping(skel_gc->domain, gpio)); } chained_irq_exit(chip, desc); } 


In diesem Artikel haben wir erfahren, wie der GPIO-Treiber mit dem virtuellen sysfs-Dateisystem interagiert, die Grundstruktur des GPIO-Treibers implementiert und die Methoden untersucht, die zur Unterstützung von IRQ erforderlich sind.

Der Artikel beschreibt die Implementierung der Methoden skel_gpio_irq_unmask, skel_gpio_irq_mask und skel_gpio_irq_set_type aus zwei Gründen nicht. Erstens sind diese Methoden einfach zu implementieren. Zweitens hardwareabhängig. Sie sind dafür verantwortlich, Interrupts für bestimmte vom GPIO-Controller unterstützte Ereignisse zuzulassen oder zu deaktivieren.

Wenn Sie Fehler / Ungenauigkeiten finden oder etwas hinzufügen möchten, schreiben Sie bitte in die PM oder in die Kommentare.

Vielen Dank für Ihre Aufmerksamkeit!

Source: https://habr.com/ru/post/de419273/


All Articles