Écriture d'un module du noyau Linux: GPIO avec prise en charge IRQ

Habr, bonjour!

Cet article concerne le développement du module GPIO (General-Purpose Input / Output) du noyau Linux. Comme dans l' article précédent , nous implémentons la structure de base du pilote GPIO avec prise en charge des interruptions (IRQ: Interrupt Request).



Les données d'entrée sont similaires à l'article précédent: le bloc GPIO développé pour le nouveau processeur «câblé» au FPGA et exécutant la version 3.18.19 de Linux.

Afin de développer un pilote GPIO, nous devons effectuer les étapes suivantes:

  1. Comprendre le principe d'interaction entre le pilote GPIO et l'interface de l'espace utilisateur;
  2. Ajoutez le module du noyau à l'assembly et décrivez le matériel dans l'arborescence des périphériques;
  3. Implémenter le squelette de base du pilote, ainsi que ses points d'entrée et de récupération;
  4. Implémenter la partie fonctionnelle du pilote GPIO;
  5. Ajoutez la prise en charge IRQ à l'implémentation du pilote.

Des exemples de pilotes GPIO peuvent être trouvés ici .

Première étape


Tout d'abord, familiarisons-nous avec le principe d'interaction entre le pilote GPIO via la console utilisateur.

À l'aide d'un petit script bash, créez les contrôles pour chaque GPIO dans / sysfs. Pour ce faire, écrivez le script suivant dans la ligne de commande:

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

Voyons ensuite quelles fonctionnalités / sysfs fournit pour configurer chaque GPIO:

 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 

Actuellement, nous nous intéressons aux domaines suivants:

  • direction - définit la direction de la ligne. Peut prendre les valeurs «in» ou «out»;
  • valeur - vous permet de définir un signal haut ou bas sur la ligne (si la direction est définie sur "out"), sinon (la direction est définie sur "in") vous permet de lire l'état de la ligne;
  • edge - vous permet de configurer l'événement par lequel l'interruption se produit. Il peut prendre les valeurs suivantes: "aucune", "montante", "descendante" ou "les deux".

Après une rapide connaissance de l'interface du pilote via sysfs, vous pouvez considérer comment le pilote traite les commandes utilisateur. Le noyau Linux a une structure gpio_chip qui décrit les fonctionnalités du contrôleur gpio. Les champs suivants y sont présents:

  • direction_input : définit la ligne sur l'entrée. Appelé sur l'entrée suivante: echo "in"> / sys / class / gpio / gpio248 / direction;
  • direction_output : définit la ligne de sortie. Appelé sur l'entrée suivante: echo "out"> / sys / class / gpio / gpio248 / direction;
  • get : lit la valeur définie sur la ligne. Appelé sur l'entrée suivante: cat / sys / class / gpio / gpio248 / value;
  • set : définit la valeur sur la ligne. Appelé sur l'entrée suivante: echo 1/0> / sys / class / gpio / gpio248 / value;

Pour décrire la configuration IRQ sous Linux, il existe une structure irq_chip qui contient les champs suivants:

  • irq_set_type : définit le type d'événement par lequel l'interruption se produira. Appelé sur l'entrée suivante: echo> "montée" / "chute" / "les deux"> / sys / class / gpio / gpio248 / edge;
  • irq_mask : interdit les interruptions. Appelé sur l'entrée suivante: echo "none"> / sys / class / gpio / gpio248 / edge;
  • irq_unmask : active l'interruption d'un événement défini sur irq_set_type. Appelé immédiatement après l'exécution de irq_set_type.

Deuxième étape


Vous pouvez maintenant ajouter le pilote à l'assemblage et décrire le matériel, en suivant les étapes standard. Créez d'abord le fichier source:

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

Après avoir ajouté la configuration du pilote à drivers / gpio / Kconfig :

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

Ajoutez le pilote à l'assembly dans drivers / gpio / Makefile :

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

Et enfin, ajoutez une description du bloc GPIO à devicetree (* .dts):

 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>; } ; 

Vous pouvez en savoir plus sur devicetree ici .

Étape trois


Passons à la partie la plus intéressante pour nous!

Nous commençons le développement du pilote en connectant les fichiers d'en-tête nécessaires et en décrivant le squelette complet du pilote sans prise en charge IRQ. Ensuite, nous remplirons séquentiellement chaque fonction d'un code et accompagnerons les explications nécessaires.

Squelette de pilote GPIO
 /* 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"); 


Comme vous pouvez le voir dans l'implémentation, le squelette du pilote semble assez simple et ne contient pas beaucoup de fonctions et de structures nécessaires.

Pour décrire le futur pilote, nous avons besoin des éléments suivants:

  • platform_driver skel_gpio_driver - décrit le point d'entrée skel_gpio_probe lors du chargement du pilote et skel_gpio_remove lorsqu'il est extrait du noyau;
  • struct of_device_id skel_gpio_of_match - contient un tableau qui décrit le matériel du bloc GPIO;
  • struct skel_gpio_chip - contient les champs nécessaires pour contrôler le pilote de bloc GPIO.

De plus, pour charger / extraire le pilote vers / depuis Linux, il est nécessaire d'implémenter les méthodes .probe et .remove spécifiées dans la structure skel_gpio_driver .

Implémentation de 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; } 


Implémentation de 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; } 


La fonction skel_gpio_remove supprime simplement le pilote GPIO enregistré du noyau, alors considérez les principaux points de skel_gpio_probe:

  • devm_kzalloc - alloue de la mémoire pour la structure skel_gpio_chip;
  • platform_get_resource - lit l'adresse du début de la carte d'enregistrement GPIO à partir de devicetree;
  • devm_ioremap_resource - effectue le mappage d'une adresse physique à une adresse virtuelle;
  • of_property_read_u32 - lit le nombre de GPIO disponibles dans devicetree;
  • skel_gc-> gchip. * - remplit les champs de structure requis;
  • gpiochip_add - ajoute un contrôleur GPIO au pilote;

Jusqu'à présent, il n'a pas été décrit pourquoi des nombres magiques tels que 248 ... 255. L'entrée skel_gc-> gchip.base = -1; demande au noyau d'allouer dynamiquement les nombres utilisés par GPIO. Pour connaître ces numéros à la fin du pilote, une sortie est ajoutée:

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

Bien sûr, Linux offre la possibilité de définir des nombres manuellement, mais regardons le commentaire dans le code source:

 @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. 


Quatrième étape


Considérez la partie fonctionnelle du pilote, à savoir, nous implémentons les méthodes suivantes:
.direction_output , .direction_input , .get et .set . Ensuite, un code dépendant du matériel sera affiché, qui dans la plupart des cas sera différent.

Implémentation de 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; } 


La méthode skel_gpio_direction_input effectue les actions suivantes:

  • Appelé avec la commande suivante echo "in"> / sys / class / gpio / gpioN / direction;
  • Lit le registre SKEL_GPIO_PAD_DIR, qui est chargé de définir la direction de la broche GPIO;
  • Définit le masque requis;
  • Ecrit la valeur reçue dans SKEL_GPIO_PAD_DIR.

Implémentation de 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; } 


La méthode skel_gpio_direction_output effectue des actions similaires à skel_gpio_direction_inut, sauf qu'elle est appelée avec les commandes suivantes:

  • echo "out"> / sys / class / gpio / gpioN / direction;
  • echo «high»> / sys / class / gpio / gpioN / direction, définissant la valeur sur 1;
  • echo «low»> / sys / class / gpio / gpioN / direction, définissant la valeur sur 0.

valeur - une valeur qui détermine le niveau du signal sur la ligne de sortie.

Implémentation de 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); } 


La méthode skel_gpio_set effectue les actions suivantes:

  • Appelé avec la commande suivante echo 1/0> / sys / class / gpio / gpioN / value;
  • Lit le registre SKEL_GPIO_WR_DATA, qui montre la valeur du signal actuel sur la ligne;
  • Définit ou réinitialise le bit requis par décalage;
  • Ecrit la valeur reçue dans SKEL_GPIO_WR_DATA.

Implémentation de 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)); } 


La méthode skel_gpio_get lit la valeur du signal sur la ligne en lisant le registre SKEL_GPIO_RD_DATA.

Après avoir décrit toutes les méthodes et structures nécessaires, vous pouvez tout assembler et jeter un œil à la version finale.

Implémentation du pilote GPIO
 /* 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"); 


Le pilote implémenté contient les fonctionnalités nécessaires pour contrôler le GPIO, mais pour le moment le pilote ne prend pas en charge la gestion des interruptions, vous pouvez donc passer à l'étape suivante.

Cinquième étape


L'ajout d'IRQ au pilote GPIO peut être divisé en trois étapes:

  • Description des méthodes prises en charge dans les structures de données du noyau;
  • Activation de la prise en charge d'IRQ au moment du chargement du pilote dans le système;
  • Implémentation des méthodes supportées.

Dans un premier temps, nous décrivons l'ensemble des opérations nécessaires:

 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, }; 

Par conséquent, le pilote peut activer (skel_gpio_irq_unmask) / désactiver (skel_gpio_irq_mask) les interruptions et indiquer le type d'événement par lequel il sera généré (skel_gpio_irq_set_type).
Ensuite, nous décrivons la seule méthode responsable du mappage d'un numéro d'IRQ virtuel à un numéro matériel.

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

Ensuite, nous dirons au noyau que le pilote chargé prend en charge le travail avec IRQ. Pour ce faire, ajoutez le code suivant à la fonction de sonde:

Ajout de la prise en charge IRQ
 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); 


Dans le code ci-dessus se produit:

  • Attribution et initialisation de la zone sous irq_domain;
  • Lire le numéro d'interruption avec devicetree;
  • mappage entre l'interruption virtuelle et matérielle;
  • Enregistrer un gestionnaire d'interruption et définir les données à transmettre au gestionnaire;

Nous procédons à la mise en œuvre de certaines des méthodes décrites ci-dessus.
skel_gpio_to_irq - Crée un mappage entre le matériel et les interruptions virtuelles. Si ce mappage a déjà été créé, il renvoie le numéro de l'interruption virtuelle créée.

Implémentation de 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 est un gestionnaire d'interruption qui:

  • Lit un registre d'état d'interruption;
  • Lit le registre de masque d'interruption;
  • Lance des interruptions en attente de traitement;
  • Pour chaque GPIO dans lequel une interruption se produit, appelle generic_handle_irq.

Implémentation du gestionnaire d'interruption
 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); } 


C'est tout, dans cet article, nous avons appris comment le pilote GPIO interagit avec le système de fichiers virtuel sysfs, implémenté la structure de base du pilote GPIO et également examiné les méthodes requises pour prendre en charge IRQ.

L'article ne décrit pas l'implémentation des méthodes skel_gpio_irq_unmask, skel_gpio_irq_mask et skel_gpio_irq_set_type pour deux raisons. Tout d'abord, ces méthodes sont faciles à mettre en œuvre. Deuxièmement, dépend du matériel. Ils sont chargés d'autoriser ou de désactiver les interruptions pour certains événements pris en charge par le contrôleur GPIO.

S'il vous plaît, si vous trouvez des erreurs / inexactitudes, ou si vous avez quelque chose à ajouter, écrivez dans le MP ou dans les commentaires.

Merci de votre attention!

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


All Articles