Использование 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, а второй, вполне возможно, уже включен.


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


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

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

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 0

    Only users with full accounts can post comments. Log in, please.