Использование gpio-generic и irq_chip_generic для драйвера gpio
Данная статья является логичным продолжением предыдущей и её прочтение рекомендуется после ознакомления с предшествующим материалом. Текущая заметка необходима для понимания последующего материала, дополнительного понимания подсистемы gpio в целом и способствует разработке собственных gpio драйверов. Положения данной статьи уже применимы не только к нашему виртуальному драйверу gpio, но и к любому mmio gpio драйверу в целом. Речь пойдет об упомянутой в предыдущей части «оптимизации».
Цель
Прежде чем приступать к более интересным и полезным вещам, необходимо навести порядок. Как и упоминалось ранее количество кода можно сократить. Для этого воспользуемся двумя подсистемами (драйверами) gpio-generic (gpio-mmio начиная с 4.7) и irq_chip_generic.
Согласно принципам ядра linux лучше пожертвовать производительностью ради понятности и отсутствию повторяющегося типового кода. А использованию существующих реализаций в ядре linux, несомненно служит данной цели.
Реализация
pci_ids
Небольшое отступление.
Первоначально при переходе на bgpio был проведен небольшой успешный эксперимент для виртуального драйвера с поддержкой устройств по 8, 16 и 32 входа. Итоговый код можно посмотреть здесь. Данного результата удалось достичь благодаря небольшой, всего 7 строк, модификации ivshmem, чтобы добавить передаваемые параметры sub-vendor-id и sub-device-id для pci устройства (патч входящий в ветку предназначен для версии qemu 2.5.1.1).
Использование gpio-mmio
В ядре данный драйвер зависит от CONFIG_GPIO_GENERIC.
Начнем с простого, драйвера, который создавался для различных MMIO gpio, и в последствии части которого стали достаточно активно использоваться некоторыми драйверами. Он был представлен Антоном Воронцовым в 2010 году (https://lkml.org/lkml/2010/8/25/303). В своем патче он анонсировал для драйвера следующие возможности:
- Поддержку 8/16/32/64 битных регистров
- Поддержку контроллеров gpio с регистрами задания/очистки
- Поддержку контроллеров gpio только с регистром данных
- Поддержку big-endian
В принципе данный драйвер избавляет нас от необходимости самим реализовывать такие функции как, задание состояния и выбор направления контакта.
Для использование достаточно всего лишь передать размер и регистр данных, все остальное опционально и зависит от конкретной реализации контроллера gpio, функция инициализации принимает в качестве параметров:
- Регистр состояния
- Регистр задания состояния
- Регистр очистки состояния
- Регистр переключения контакта в состояние выхода
- Регистр переключения контакта в состояние входа
То есть возможны следующие ситуации:
- Регистр dat только для чтения состояния, регистр set для задания и регистр clr для очистки состояния
- Регистр dat только для чтения, регистр set для задания и очистки
- Регистр dat для всего
Тоже самое справедливо и для направлений. Либо регистр задания как выхода, либо как входа. Или отсутствие самой возможности переключить контакт в состояние выхода или входа.
Симулировать можно любое из выше перечисленных проведений. Для наших целей вполне достаточно поведения номер 3 и задания состояния как выхода, с состоянием для контакта по умолчанию как вход.
bgpio_init
#if IS_ENABLED(CONFIG_GPIO_GENERIC)
int bgpio_init(struct gpio_chip *gc,
struct device *dev,
unsigned long sz,
void __iomem *dat,
void __iomem *set,
void __iomem *clr,
void __iomem *dirout,
void __iomem *dirin,
unsigned long flags);
#define BGPIOF_BIG_ENDIAN BIT(0)
#define BGPIOF_UNREADABLE_REG_SET BIT(1) /* reg_set is unreadable */
#define BGPIOF_UNREADABLE_REG_DIR BIT(2) /* reg_dir is unreadable */
#define BGPIOF_BIG_ENDIAN_BYTE_ORDER BIT(3)
#define BGPIOF_READ_OUTPUT_REG_SET BIT(4) /* reg_set stores output value */
#define BGPIOF_NO_OUTPUT BIT(5) /* only input */
#endif
Необходимо обратить внимание, что в качестве размера bgpio_init принимает не количество входов, а параметр кратный 8, то есть ngpio/8.
err = bgpio_init(&vg->chip, dev, BITS_TO_BYTES(VIRTUAL_GPIO_NR_GPIOS),
data, NULL, NULL, dir, NULL, 0);
Использование irq_chip_generic
В ядре данный драйвер зависит от GENERIC_IRQ_CHIP, что является недостатком, так как нет возможности включить данный параметр через menuconfig или oldconfig.
Теперь рассмотрим немного более сложную часть. irq_chip_generic был представлен в версии ядра v3.0-rc1 и выполняет функции схожие с gpio-mmio, то есть предоставляет стандартную имплементацию для многих случаев irq_chip.
Стандартные функции чтения/записи регистра являются 32 битными, это была одна из причин по которой я решил отказаться от 8/16 битных версий драйвера, тем не менее есть возможность предоставить подсистеме irq_chip_generic свои функции для чтения/записи или указать стандартные (например ioread8, iowrite8).
Совместное использование irq_chip_generic и функций gpiochip_irqchip_add, gpiochip_set_chained_irqchip
Память под irq_chip_generic и инициализация происходит с помощью irq_alloc_generic_chip. Функция, помимо тривиальных параметров, требует еще и irq_base, который, вообще говоря, нам неизвестен до вызова gpiochip_irqchip_add, что в свою очередь требует struct irq_chip. Но irq_alloc_generic_chip отвечает только за выделение памяти и инициализацию некоторых параметров, так что мы можем передать 0 в качестве параметра irq_base и присвоить реальное значение после вызова функции gpiochip_irqchip_add.
gc = irq_alloc_generic_chip(VIRTUAL_GPIO_DEV_NAME, 1, 0, vg->data_base_addr, handle_edge_irq);
Для использования достаточно указать стандартные функции маскирования/демаскирования, подтверждения прерывания и регистры. Функция задания типа прерывания у нас остается неизменной, как и наш обработчик прерывания.
ct->chip.irq_ack = irq_gc_ack_set_bit;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->chip.irq_set_type = virtual_gpio_irq_type;
Смещение регистров для подтверждения и маскирования/демаскирования:
ct->regs.ack = VIRTUAL_GPIO_INT_EOI;
ct->regs.mask = VIRTUAL_GPIO_INT_EN;
Регистрами типа прерывания мы по-прежнему заведуем сами, в функции virtual_gpio_irq_type.
Соответственно в инициализацию gpiochip_irqchip_add и gpiochip_set_chained_irqchip мы передаем экземпляр irq_chip выделенный в irq_chip_generic→chip_types[0] (irq_chip_generic может иметь несколько типов irq_chip ассоциированных с одним и тем же подмножеством irq).
После чего используем полученный irq_base и завершим настройку irq_chip_generic.
irq_setup_generic_chip(gc, 0, 0, 0, 0);
gc->irq_cnt = VIRTUAL_GPIO_NR_GPIOS;
gc->irq_base = vg->chip.irq_base;
u32 msk = IRQ_MSK(32);
u32 i;
for (i = gc->irq_base; msk; msk >>= 1, i++) {
if (!(msk & 0x01))
continue;
struct irq_data *d = irq_get_irq_data(i);
d->mask = 1 << (i - gc->irq_base);
irq_set_chip_data(i, gc);
}
Мы намеренно передаем 0 в качестве параметра msk, чтобы избежать повторной инициализации номеров irq.
irq_chip_generic используется для вполне себе серьёзных контроллеров irq в основном платформенных, поэтому маска для регистров irq вычисляется заранее, чтобы ускорить обработку прерываний и не тратить время на вычисление маски в процессе работы. Поскольку мы передаем 0 в качестве параметра в функцию irq_setup_generic_chip, инициализируем маски самостоятельно.
Остается еще одна проблема, связанная с irq_set_chip_data (которая ничто иное как irq_data.chip_data = (void*)struct irq_chip_generic;
). Дело в том, что gpiolib тоже опирается на void* chip_data и полагает, что там должен находится указатель на struct gpio_chip (http://lxr.free-electrons.com/source/drivers/gpio/gpiolib.c#L1138). Проблема решается предоставлением собственных реализаций функций irq_request_resources и irq_release_resources вместо стандартных gpiochip_irq_reqres и gpiochip_irq_relres.
Исходный код для данного примера.
Альтернативный подход
Строго говоря перечисленные выше манипуляции не очевидны, и может возникнуть некоторая сложность с их пониманием. Попробуем отказаться от gpiochip_irqchip_add.
Перво-наперво запросим irq_base сами:
irq_base = irq_alloc_descs(-1, 0, vg->chip.ngpio, 0);
Как мы помним из предыдущей статьи наш случай линейный (функция irq_domain_add_simple делает почти то же самое, но является устаревшей).
vg->irq_domain = irq_domain_add_linear(0, vg->chip.ngpio, &irq_domain_simple_ops, vg);
irq_domain_associate_many(vg->irq_domain, irq_base, 0, vg->chip.ngpio);
Следующий шаг остается практически неизменным и заключается в связывании gpio_chip с irq_chip:
vg->chip.irqdomain = vg->irq_domain;
gpiochip_set_chained_irqchip(&vg->chip, &ct->chip, pdev->irq, NULL);
Единственный момент — чтобы был доступен файл «edge» в соответствующей директории управления контактом (gpiolib-sysfs), необходимо указать функцию трансляции to_irq:
vg->chip.to_irq = virtual_gpio_to_irq;
Заключение
Благодаря использованию уже существующих подсистем код немного сократился. В понимании драйвер так же стал проще, так как использование данных подсистем достаточно распространено, и сразу видно, что это стандартный драйвер без подводных камней. К тому же с такой структурой будет гораздо проще воспользоваться механизмами Device Tree.
Условным недостатком следует считать зависимость от CONFIG_GPIO_GENERIC и GENERIC_IRQ_CHIP, но первый параметр легко включается в конфигурации ярда linux, а второй, вполне возможно, уже включен.
Материалы рекомендованные к дополнительному чтению:
- High-level IRQ flow handlers
- Linux Kernel IRQ Domain
- Edge Triggered Vs Level Triggered interrupts
» Исходные коды, Makefile и README