Escrevendo um módulo do kernel Linux: GPIO com suporte a IRQ

Habr, olá!

Este artigo é sobre o desenvolvimento do módulo GPIO (General-Purpose Input / Output) do kernel do Linux. Como no artigo anterior , implementamos a estrutura básica do driver GPIO com suporte a interrupções (IRQ: solicitação de interrupção).



Os dados de entrada são semelhantes ao artigo anterior: o bloco GPIO desenvolvido para o novo processador "conectado" ao FPGA e executando a versão 3.18.19 do Linux.

Para desenvolver um driver GPIO, precisamos executar as seguintes etapas:

  1. Entenda o princípio de interação entre o driver GPIO e a interface do espaço do usuário;
  2. Adicione o módulo do kernel ao assembly e descreva o hardware na árvore de dispositivos;
  3. Implementar o esqueleto básico do motorista, bem como seus pontos de entrada e recuperação;
  4. Implemente a parte funcional do driver GPIO;
  5. Adicione suporte de IRQ à implementação do driver.

Exemplos de drivers GPIO podem ser encontrados aqui .

Primeiro passo


Primeiro, vamos nos familiarizar com o princípio de interação entre o driver GPIO por meio do console do usuário.

Usando um pequeno script bash, crie os controles para cada GPIO em / sysfs. Para fazer isso, escreva o seguinte script na linha de comando:

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

A seguir, vamos ver o que o features / sysfs fornece para configurar cada 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 

Atualmente, estamos interessados ​​nos seguintes campos:

  • direction - define a direção da linha. Pode levar os valores "dentro" ou "fora";
  • valor - permite definir um sinal alto ou baixo na linha (se a direção estiver definida como "out"); caso contrário (a direção estiver definida como "in"), você poderá ler o status da linha;
  • edge - permite configurar o evento pelo qual a interrupção ocorre. Pode assumir os seguintes valores: "nenhum", "subindo", "caindo" ou "ambos".

Após um rápido conhecimento da interface do driver através do sysfs, você pode considerar como o driver processa os comandos do usuário. O kernel do Linux possui uma estrutura gpio_chip que descreve a funcionalidade do controlador gpio. Os seguintes campos estão presentes nele:

  • direction_input : define a linha para a entrada. Chamado na seguinte entrada: echo "em"> / sys / class / gpio / gpio248 / direction;
  • direction_output : define a linha para sair. Chamado na seguinte entrada: echo "out"> / sys / class / gpio / gpio248 / direction;
  • get : lê o valor definido na linha. Chamado na seguinte entrada: cat / sys / class / gpio / gpio248 / value;
  • set : define o valor na linha. Chamado na seguinte entrada: echo 1/0> / sys / class / gpio / gpio248 / value;

Para descrever a configuração do IRQ no Linux, existe uma estrutura irq_chip que contém os seguintes campos:

  • irq_set_type : define o tipo de evento pelo qual a interrupção ocorrerá. Chamado na seguinte entrada: eco> "crescente" / "decrescente" / "ambos"> / sys / class / gpio / gpio248 / edge;
  • irq_mask : proíbe interrupções. Chamado na seguinte entrada: echo "none"> / sys / class / gpio / gpio248 / edge;
  • irq_unmask : Ativa a interrupção em um evento que foi definido como irq_set_type. Chamado imediatamente após a execução do irq_set_type.

Segundo passo


Agora você pode adicionar o driver à montagem e descrever o hardware, seguindo as etapas padrão. Primeiro, crie o arquivo de origem:

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

Depois de adicionar a configuração do driver aos drivers / gpio / Kconfig :

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

Adicione o driver ao assembly em drivers / gpio / Makefile :

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

E, finalmente, adicione uma descrição do bloco GPIO ao 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>; } ; 

Você pode ler mais sobre o devicetree aqui .

Passo três


Vamos para a parte mais interessante para nós!

Iniciamos o desenvolvimento do driver conectando os arquivos de cabeçalho necessários e descrevendo o esqueleto completo do driver sem suporte a IRQ. Em seguida, preencheremos seqüencialmente cada função com um código e acompanharemos as explicações necessárias.

Esqueleto do driver 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"); 


Como você pode ver na implementação, o esqueleto do motorista parece bastante simples e não contém muitas funções e estruturas necessárias.

Para descrever o driver futuro, precisamos dos seguintes elementos:

  • platform_driver skel_gpio_driver - descreve o ponto de entrada skel_gpio_probe ao carregar o driver e skel_gpio_remove quando extraído do kernel;
  • struct of_device_id skel_gpio_of_match - contém uma tabela que descreve o hardware do bloco GPIO;
  • struct skel_gpio_chip - contém os campos necessários para controlar o driver de bloco GPIO.

Além disso, para carregar / extrair o driver para / do Linux, é necessário implementar os métodos .probe e .remove especificados na estrutura skel_gpio_driver .

Implementação 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; } 


Implementação 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; } 


A função skel_gpio_remove simplesmente remove o driver GPIO registrado do kernel, portanto, considere os pontos principais em skel_gpio_probe:

  • devm_kzalloc - aloca memória para a estrutura skel_gpio_chip;
  • platform_get_resource - lê o endereço do início do cartão de registro GPIO no devicetree;
  • devm_ioremap_resource - executa o mapeamento de um endereço físico para um endereço virtual;
  • of_property_read_u32 - lê o número de GPIOs disponíveis no devicetree;
  • skel_gc-> gchip. * - preenche os campos de estrutura necessários;
  • gpiochip_add - adiciona um controlador GPIO ao driver;

Até o momento, não foi descrito por que números mágicos como 248 ... 255. A entrada skel_gc-> gchip.base = -1; solicita que o kernel aloque dinamicamente os números usados ​​pelo GPIO. Para descobrir esses números no final do driver, a saída é adicionada:

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

Obviamente, o Linux fornece a capacidade de definir números manualmente, mas vamos dar uma olhada no comentário no código- fonte:

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


Quarto passo


Considere a parte funcional do driver, a saber, implementamos os seguintes métodos:
.direction_output , .direction_input , .get e .set . Em seguida, será exibido um código dependente de hardware, que na maioria dos casos será diferente.

Implementação 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; } 


O método skel_gpio_direction_input executa as seguintes ações:

  • Chamado com o seguinte comando echo "in"> / sys / class / gpio / gpioN / direction;
  • Lê o registro SKEL_GPIO_PAD_DIR, responsável por definir a direção do pino GPIO;
  • Define a máscara necessária;
  • Grava o valor recebido de volta em SKEL_GPIO_PAD_DIR.

Implementação 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; } 


O método skel_gpio_direction_output executa ações semelhantes ao skel_gpio_direction_inut, exceto pelo fato de ser chamado com os seguintes comandos:

  • eco "out"> / sys / class / gpio / gpioN / direction;
  • eco "high"> / sys / class / gpio / gpioN / direction, configurando o valor para 1;
  • eco "low"> / sys / class / gpio / gpioN / direction, configurando o valor para 0.

valor - um valor que determina o nível do sinal na linha de saída.

Implementação 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); } 


O método skel_gpio_set executa as seguintes ações:

  • Chamado com o seguinte comando echo 1/0> / sys / class / gpio / gpioN / value;
  • Lê o registro SKEL_GPIO_WR_DATA, que mostra o valor do sinal atual na linha;
  • Define ou redefine o bit necessário por deslocamento;
  • Grava o valor recebido de volta em SKEL_GPIO_WR_DATA.

Implementação 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)); } 


O método skel_gpio_get lê o valor do sinal na linha lendo o registro SKEL_GPIO_RD_DATA.

Depois de descrevermos todos os métodos e estruturas necessários, você pode juntar tudo e dar uma olhada na versão final.

Implementação de driver 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"); 


O driver implementado contém a funcionalidade necessária para controlar o GPIO, mas no momento o driver não suporta manipulação de interrupção, para que você possa ir para a próxima etapa.

Quinto passo


A adição de IRQ ao driver GPIO pode ser dividida em três etapas:

  • Descrição dos métodos suportados nas estruturas de dados do kernel;
  • Ativando o suporte a IRQ no momento em que o driver é carregado no sistema;
  • Implementação de métodos suportados.

Inicialmente, descrevemos o conjunto de operações necessário:

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

Portanto, o driver pode ativar (skel_gpio_irq_unmask) / desativar (skel_gpio_irq_mask) interrupções e indicar o tipo de evento pelo qual será gerado (skel_gpio_irq_set_type).
A seguir, descrevemos o único método responsável por mapear um número de IRQ virtual para um número de hardware.

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

Em seguida, informaremos ao kernel que o driver carregado suporta o trabalho com IRQ. Para fazer isso, adicione o seguinte código à função de análise:

Adicionando suporte a 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); 


No código acima acontece:

  • Alocação e inicialização da área sob irq_domain;
  • Leia o número da interrupção com o dispositivo;
  • mapeamento entre interrupção virtual e de hardware;
  • Registrando um manipulador de interrupção e configurando dados para transmissão ao manipulador;

Prosseguimos na implementação de alguns dos métodos descritos acima.
skel_gpio_to_irq - cria um mapeamento entre hardware e interrupções virtuais. Se esse mapeamento já tiver sido criado, ele retornará o número da interrupção virtual criada.

Implementação 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 é um manipulador de interrupção que:

  • Lê um registro de status de interrupção;
  • Lê o registro da máscara de interrupção;
  • Lança interrupções aguardando processamento;
  • Para cada GPIO no qual ocorre uma interrupção, chama generic_handle_irq.

Implementação do manipulador de interrupções
 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); } 


Isso é tudo: neste artigo, aprendemos como o driver GPIO interage com o sistema de arquivos virtual sysfs, implementou a estrutura básica do driver GPIO e também examinamos os métodos necessários para dar suporte ao IRQ.

O artigo não descreve a implementação dos métodos skel_gpio_irq_unmask, skel_gpio_irq_mask e skel_gpio_irq_set_type por dois motivos. Em primeiro lugar, esses métodos são fáceis de implementar. Em segundo lugar, depende do hardware. Eles são responsáveis ​​por permitir ou desativar interrupções para determinados eventos que o controlador GPIO suporta.

Por favor, se você encontrar erros / imprecisões ou se tiver algo a acrescentar, escreva no PM ou nos comentários.

Obrigado pela atenção!

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


All Articles