Pull to refresh

Использование gpio-generic и irq_chip_generic для драйвера gpio

Reading time 6 min
Views 4K

Данная статья является логичным продолжением предыдущей и её прочтение рекомендуется после ознакомления с предшествующим материалом. Текущая заметка необходима для понимания последующего материала, дополнительного понимания подсистемы 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, а второй, вполне возможно, уже включен.


Материалы рекомендованные к дополнительному чтению:


  1. High-level IRQ flow handlers
  2. Linux Kernel IRQ Domain
  3. Edge Triggered Vs Level Triggered interrupts

» Исходные коды, Makefile и README

Tags:
Hubs:
+11
Comments 0
Comments Leave a comment

Articles