Пишем модуль ядра Linux: GPIO с поддержкой IRQ

Хабр, привет!

Данная статья посвящена разработке GPIO (General-Purpose Input/Output) модуля ядра Linux. Как и в предыдущей статье мы реализуем базовую структуру GPIO драйвера с поддержкой прерываний (IRQ: Interrupt Request).



Входные данные аналогичны предыдущей статье: разработанный GPIO блок для нового процессора «зашитый» на ПЛИС и запущенный Linux версии 3.18.19.

Для того чтобы разработать GPIO драйвер, нам потребуется выполнить следующие шаги:

  1. Понять принцип взаимодействия GPIO драйвера с user space интерфейсом;
  2. Добавить модуль ядра в сборку и описать аппаратную часть в device tree;
  3. Реализовать базовый скелет драйвера, а также его точки входа и извлечения;
  4. Реализовать функциональную часть GPIO драйвера;
  5. Добавить к реализации драйвера поддержку IRQ.

Примеры GPIO драйверов можно посмотреть тут.

Шаг первый

Для начала познакомимся с принципом взаимодействия GPIO драйвера через консоль пользователя.

С помощью небольшого bash скрипта, создадим в /sysfs элементы управления каждого GPIO. Для этого в командой строке нужно написать следующий скрипт:

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

Далее посмотрим какие возможности предоставляет /sysfs для конфигурации каждого 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 

В данный момент нас интересуют следующие поля:

  • direction — задает направление линии. Может принимать значения «in» или «out»;
  • value — позволяет выставить высокий или низкий сигнал на линии (если direction установлен в «out»), в противном случае (direction установлен в «in») позволяет прочитать состояние линии;
  • edge — позволяет настроить событие по которому происходит прерывание. Может принимать следующие значения: «none», «rising», «falling» или «both».

После беглого знакомства с интерфейсом взаимодействия драйвера через sysfs, можно рассмотреть как драйвер обрабатывает команды пользователя. В ядре Linux есть структура gpio_chip которая описывает функционал gpio контроллера. В ней присутствуют следующие поля:

  • direction_input: настраивает линию на вход. Вызывается при следующей записи: echo «in» > /sys/class/gpio/gpio248/direction;
  • direction_output: настраивает линию на выход. Вызывается при следующей записи: echo «out» > /sys/class/gpio/gpio248/direction;
  • get: считывает установленное на линии значение. Вызывается при следующей записи: cat /sys/class/gpio/gpio248/value;
  • set: устанавливает значение на линии. Вызывается при следующей записи: echo 1/0 > /sys/class/gpio/gpio248/value;

Для описания конфигурации IRQ в Linux существует структура irq_chip, которая содержит следующие поля:

  • irq_set_type: настраивает тип события по которому будет происходить прерывание. Вызывается при следующей записи: echo > «rising»/«falling»/«both» > /sys/class/gpio/gpio248/edge;
  • irq_mask: запрещает прерывания. Вызывается при следующей записи: echo «none» > /sys/class/gpio/gpio248/edge;
  • irq_unmask: разрешает прерывание по событию, которое было установлено в irq_set_type. Вызывается сразу после выполнения irq_set_type.

Шаг второй

Теперь можно добавить драйвер в сборку и описать аппаратную часть, выполнив уже стандартные действия. Первым делом создадим исходный файл:

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

После добавим конфигурацию драйвера в drivers/gpio/Kconfig:

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

Добавим в сборку драйвер в drivers/gpio/Makefile:

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

И, наконец, добавим в devicetree (*.dts) описание GPIO блока:

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

Более подробную информацию про devicetree можно прочитать тут.

Шаг третий

Перейдем к самой интересной для нас части!

Разработку драйвера начнем с подключения необходимых заголовочных файлов и описания полного скелета драйвера без поддержки IRQ. Далее последовательно будем наполнять каждую функцию кодом и сопровождать необходимыми пояснениями.

Скелет драйвера 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"); 

Как видно из реализации, скелет драйвера выглядит достаточно просто и содержит не так много необходимых функций и структур.

Для того чтобы описать будущий драйвер нам потребуются следующие элементы:

  • platform_driver skel_gpio_driver — описывает точку входа skel_gpio_probe при загрузке драйвера и skel_gpio_remove при его извлечении из ядра;
  • struct of_device_id skel_gpio_of_match — содержит таблицу, которая описывает аппаратную часть GPIO блока;
  • struct skel_gpio_chip — содержит необходимые поля для управления драйвером GPIO блоком.

Далее, чтобы загрузить/извлечь драйвер в/из Linux, необходимо реализовать указанные в структуре skel_gpio_driver методы .probe и .remove.

Реализация 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; } 

Реализация 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; } 

Функция skel_gpio_remove просто удаляет зарегистрированный GPIO драйвер из ядра, поэтому рассмотрим основные моменты в skel_gpio_probe:

  • devm_kzalloc — выделяет память под структуру skel_gpio_chip;
  • platform_get_resource — читает адрес начала регистровой карты GPIO из devicetree;
  • devm_ioremap_resource — выполняет mapping физического адреса на виртуальный;
  • of_property_read_u32 — читает количество доступных GPIO из devicetree;
  • skel_gc->gchip.* — заполняет необходимые для работы поля структуры;
  • gpiochip_add — добавляет GPIO контроллер в драйвер;

До сих пор не было описано почему же используется такие магические числа как 248… 255. Запись skel_gc->gchip.base = -1; просит ядро динамически выделить номера используемых GPIO. Чтобы узнать данные номера в конце драйвера добавлен вывод:

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

Конечно, Linux предоставляет возможность задать номера вручную, но давайте посмотрим на комментарий в исходном коде:

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

Шаг четвертый

Рассмотрим функциональную часть драйвера, а именно реализуем следующие методы:
.direction_output, .direction_input, .get и .set. Далее будет показан аппаратно-зависимый код, который в большинстве случаев будет отличаться.

Реализация 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; } 

Метод skel_gpio_direction_input выполняет следующие действия:

  • Вызывается при следующей команде echo «in» > /sys/class/gpio/gpioN/direction;
  • Считывает регистр SKEL_GPIO_PAD_DIR, который отвечает за установку направления пина GPIO;
  • Устанавливает необходимую маску;
  • Записывает полученное значение обратно в SKEL_GPIO_PAD_DIR.

Реализация 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; } 

Метод skel_gpio_direction_output выполняет действия аналогичные skel_gpio_direction_inut за исключением того, что вызывается при следующих командах:

  • echo «out» > /sys/class/gpio/gpioN/direction;
  • echo «high» > /sys/class/gpio/gpioN/direction, при этом устанавливает значение value в 1;
  • echo «low» > /sys/class/gpio/gpioN/direction, при этом устанавливает значение value в 0.

value — значение, определяющее уровень сигнала на выходной линии.

Реализация 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); } 

Метод skel_gpio_set выполняет следующие действия:

  • Вызывается при следующей команде echo 1/0 > /sys/class/gpio/gpioN/value;
  • Считывает регистр SKEL_GPIO_WR_DATA, который показывает значение текущего сигнала на линии;
  • Устанавливает или сбрасывает необходимый бит по offset;
  • Записывает полученное значение обратно в SKEL_GPIO_WR_DATA.

Реализация 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)); } 

Метод skel_gpio_get считывает значение сигнала на линии, прочитав регистр SKEL_GPIO_RD_DATA.

После того как мы описали все необходимые методы и структуры, можно собрать все вместе и взглянуть на итоговый вариант.

Реализация 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"); 

Реализованный драйвер содержит необходимый функционал для управления GPIO, но в данный момент в драйвере отсутствует поддержка работы с прерываниями, поэтому можно перейти к следующему шагу.

Шаг пятый

Добавление IRQ в GPIO драйвер можно разделить на три шага:

  • Описание поддерживаемых методов в структурах данных ядра;
  • Включение поддержки IRQ в момент загрузки драйвера в систему;
  • Реализация поддерживаемых методов.

Первоначально опишем необходимый набор операций:

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

Следовательно драйвер может разрешать(skel_gpio_irq_unmask)/запрещать(skel_gpio_irq_mask) прерывания и указать тип события по которому оно будет генерироваться (skel_gpio_irq_set_type).
Далее опишем единственный метод, который отвечает за сопоставление виртуального irq number с аппаратным.

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

Затем укажем ядру, что загружаемый драйвер поддерживает работу с IRQ. Для этого необходимо добавить в probe функцию следующий код:

Добавление поддержки 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); 

В выше приведенном коде происходит:

  • Выделение и инициализация области под irq_domain;
  • Считывание номера прерывания с devicetree;
  • mapping между виртуальным и аппаратным прерыванием;
  • Регистрация обработчика прерывания и установка данных на передачу в обработчик;

Приступим к реализации некоторых выше описанных методов.
skel_gpio_to_irq — создает mapping между аппаратным и виртуальным прерыванием. Если же данный mapping уже был создан, то возвращает номер созданного виртуального прерывания.

Реализация 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 — обработчик прерывания, который:

  • Считывает регистр статуса прерывания;
  • Считывает регистр маски прерывания;
  • Выделяет прерывания ожидающие обработки;
  • Для каждого GPIO в котором возникло прерывание вызывает generic_handle_irq.

Реализация обработчика прерывания

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

Вот и все, в данной статье мы узнали как происходит взаимодействие GPIO драйвера с виртуальной файловой системой sysfs, реализовали базовую структуру GPIO драйвера, а также рассмотрели методы которые требуются для поддержки IRQ.

В статье не приведена реализация методов skel_gpio_irq_unmask, skel_gpio_irq_mask и skel_gpio_irq_set_type по двум причинам. Во-первых, данные методы просты в реализации. Во-вторых, аппаратно-зависимы. Они отвечают за разрешение или запрет прерываний по определенным событиям, которые поддерживает GPIO контроллер.

Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.

Спасибо за ваше внимание!

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *