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:
- Entenda o princípio de interação entre o driver GPIO e a interface do espaço do usuário;
- Adicione o módulo do kernel ao assembly e descreva o hardware na árvore de dispositivos;
- Implementar o esqueleto básico do motorista, bem como seus pontos de entrada e recuperação;
- Implemente a parte funcional do driver GPIO;
- 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 #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 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 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 #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!