Використання 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, що є недоліком, так як немає можливості включити даний параметр через конфігураційного меню або 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, а другий, цілком можливо, вже включений.
Матеріали рекомендовані для додаткового читання:
  1. High-level IRQ flow handlers
  2. Linux Kernel IRQ Domain
  3. Edge-Triggered Vs Level Triggered interrupts
» Вихідні коди, Makefile і README
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.