- Заметка о новом интерфейсе linux kernel — gpio uapi
- Новый интерфейс uapi gpio
- gpio-mockup
- Инициализация gpio-mockup
- Сравнение sysfs и uapi
- Информация о gpiochip’s
- sysfs
- Задание линии как входа и чтение значения
- sysfs
- Задание линии как выхода
- sysfs
- Edge handling
- sysfs
- Polling on events
- labels
- Маленькая ремарка для sysfs
- Возможности uapi недоступные для sysfs
- Заключение по сравнению
- Преимущества uapi
- Критика uapi
- Критика sysfs gpio
- Утилиты
- Linux kernel gpio tools
- libgpiod
- Пишем модуль ядра Linux: GPIO с поддержкой IRQ
- Шаг первый
- Шаг второй
- Шаг третий
- Шаг четвертый
- Шаг пятый
Заметка о новом интерфейсе linux kernel — gpio uapi
Начиная с версии ядра 4.6-r1 нам стал доступен новый интерфейс для взаимодействия с подсистемой ядра gpio. Теперь существует три официальных способа работы с gpio и получения от них прерываний. Нет смысла углубляться в потребности для данной подсистемы, для малой части это суровые будни, для другой части веселое хобби, и для всех вместе в ядре была предоставлена новая возможность взаимодействия.
Заметка носит популярный характер, так как основных преимуществ, которые шли в комплекте с нововведением, а именно упрощение работы с gpio в контексте ядра касаться не будем.
Новый интерфейс uapi gpio
Во-первых, теперь gpiochip это действительно устройство и его можно видеть в devfs в виде gpiochipN, где N номер чипа присвоенный в порядке инициализации. Во-вторых, вся настройка теперь осуществляется через ioctl. И в-третьих чтение и запись, как это ни удивительно, осуществляются через вызовы read/write, правда с помощью специальной структуры struct gpiohandle_data.
gpio-mockup
Начиная с версии ядра v4.9-rc1 (использовать его фактически получится только в версиях старше v4.12-rc1) стал доступно устройство виртуальных gpio, с поддержкой управления состояниям посредством debugfs.
Рассмотрим на примере разницу между sysfs и uapi в userspace.
Инициализация gpio-mockup
- gpio_mockup_ranges — пары чисел для инициализации gpiochips, в виде «base,end», где base стартовый номер, end — конец диапазона.
- gpio_mockup_named_lines — boolean параметр, в случае если задан присваивает каждой линии метку в виде gpio-mockup-A..Z-N, где N порядковый номер линии в банке.
Данной командой мы создали два gpiochip по 8 линий каждый, c диапазонами [0-8), [8,16). Про gpio_mockup_named_lines мы поговорим чуть позже.
Сравнение sysfs и uapi
С помощью нового драйвера рассмотрим отличия между двумя системами с точки зрения пользователя.
Информация о gpiochip’s
sysfs
Задание линии как входа и чтение значения
sysfs
Задание линии как выхода
sysfs
Edge handling
sysfs
Polling on events
В документации ядра для sysfs указано использовать EPOLLPRI и EPOLLERR (или exceptfds для select), это в принципе характерно для любого вызова sysfs_notify необязательно именно для подсистемы gpio.
Для uapi достаточно EPOLLIN.
Читаем мы событие с временной меткой и типом GPIOEVENT_EVENT_RISING_EDGE или GPIOEVENT_EVENT_FALLING_EDGE.
EPOLLET для uapi работает согласно документации на epoll.
labels
Маленькая ремарка для sysfs
Имя контакта gpioN к которому так все привыкли, вообще говоря, каноническим не является, а используется если контакту не было присвоено имя, например в Device Tree.
Попробуем gpio-mockup с опцией gpio_mockup_named_lines:
Как мы видим имя контакта приобрело вид gpio_chip_label—gpio_offset, но это справедливо только для драйвера gpio-mockup.
Способа «угадать» заранее, существует ли имя для контакта, не используя uapi не представляется возможным, а поиск экспортированной «именованной» линии затруднен, если имя заранее не известно (опять же если известно, то нам для однозначной идентификации необходимо имя и смещение на известном gpiochip).
Интерфейс uapi позволяет нам без инициализации видеть имена линий:
С соответствующим файлом device tree (пример взят из документации ядра):
Мы бы видели имя в struct gpioline_info для каждого контакта, к сожалению, мало кто именует контакты, даже для распостраненных SBC.
Возможности uapi недоступные для sysfs
Теперь перечислим преимущества недоступные старому интерфейсу.
Основным преимуществом я считаю временную метку, которая присваивается событию в верхней половине обработчика прерывания. Что незаменимо для приложений, которым важным является точность измерения времени между событиями.
Если позволяет драйвер устройства, линия дополнительно может быть сконфигурирована как открытый коллектор (GPIOLINE_FLAG_OPEN_DRAIN) или открытый эммитер (GPIOLINE_FLAG_OPEN_SOURCE), данное нововведение как раз может быть легко перенесено в sysfs, но этого не будет так как Линус Ваерли против.
Так же новый api позволяет присваивать каждому контакту пользовательские ярлыки при инициализации в struct gpiohandle_request поле consumer_label.
И в заключение позволяет «читать» и «писать» сразу группу состояний для контактов.
Заключение по сравнению
Субъективно, uapi выглядит более громоздким, чем sysfs, но не стоит забывать, что сравнивали мы управление через стандартные утилиты GNU cat и echo и C код, если сравнивать C код для каждого из интерфейсов, получится приблизительно тоже самое по сложности и объему.
Важным моментом является, что в случае использование sysfs линия остается инициализированной пока пользователь не попросит обратного или до перезагрузки. uapi освобождает линию сразу после закрытия файлового дескриптора.
Преимущества uapi
Критика uapi
Официальной или неофициальной критики нет, или я её не нашёл. Поэтому обойдемся парой собственных мыслей.
- Непонятно почему обошли стороной push-pull, debounce, и pull-up, pull-down.
- В struct gpioevent_data неплохо было бы добавить параметр value с текущим значением линии
Критика sysfs gpio
Закончим официальной критикой интерфейса sysfs. Линусом Ваерли (сопровождающий в ядре подсистемы gpio и pinctrl) в комментариях к патчам были выдвинуты следующие тезисы:
- Невозможно включить выключить сразу несколько линий одним вызовом
- Для работы sysfs должен быть включен соответствующий ключ в конфигурации ядра
- В случае краха приложения gpio линии остаются в «инициализированном» состоянии
- Затруднён поиск необходимой линии
- «Sysfs is horribly broken» ©
В общем, и, если честно, я считаю, что sysfs вполне нормален, для тех задач, которые возлагаются на gpio в userspace. В нём есть нечто романтичное, когда люди не знакомые даже с основами электротехники могли зажигать свет с помощью echo. С помощью нового интерфейса такой прямой связи не чувствуется, так как теперь требуются дополнительные утилиты для взаимодействия.
But GPIOs are often used together as a group. As a simple example (and the only), consider a pair of GPIOs used as an I2C bus; one line handles data, the other the clock.
По поводу первого тезиса ничего сказать не могу, никогда не сталкивался с такой необходимостью, в конце концов можно сразу инициализировать контакты как входы или выходы в device tree. Знаю, что данная функциональность пригодилась бы тем кому нужен bit-banging в user-space, но здесь существует одно но, на чистом linux, bit-banging возможен разве что для очень низкочастотных вещей, а так нужeн как минимум PREEMPT_RT patch.
Второй тезис также странен не могу себе представить такой экономии места, что бы было необходимо отключить sysfs.
Третий еще более странен, может просто не надо «краха» приложения?
По поводу четвертого могу сказать, что ничего принципиально почти не поменялось. Если указанный в платформенном драйвере или в device tree label для gpiochip соответствует действительности, то «поиск» несложен, а если они названы «черти-как», то интерфейс здесь уже никак не поможет.
В общем и целом, вразумительного ответа я найти не смог. Я не против нового интерфейса, я даже за него, но такое старательное закапывания старого интерфейса мне лично непонятно и неприятно.
Утилиты
Для uapi их пока далеко не так много, как для sysfs.
Linux kernel gpio tools
- gpio-event-mon — утилита для отслеживания событий gpio линий
- gpio-hammer — включает/выключает линию n раз с фиксированной частотой
- lsgpio — пример листинга gpiochip и линий
libgpiod
От автора драйверов gpio-mockup и irq-sim товарища Bartosz’a Gołaszewski.
Позиционируется автором как библиотека, для облегчения работы с gpio посредством нового интерфейса uapi, так же содержит набор полезных утилит.
- gpiodetect — листинг gpiochip с именем, ярлыком и количеством линий
- gpioinfo — листинг gpiochip с именем, смещением, ярлыком и статусом линий
- gpioget — читаем состояние линии
- gpioset — задаем состояние линии, и если необходимо держим линию занятой до истечения заданного времени, сигнала или пользовательского ввода
- gpiofind — находит линию по имени и выводит устройство gpiochipN и смещение линии
- gpiomon — то же самое, что и gpio-event-mon
Источник
Пишем модуль ядра Linux: GPIO с поддержкой IRQ
Данная статья посвящена разработке GPIO (General-Purpose Input/Output) модуля ядра Linux. Как и в предыдущей статье мы реализуем базовую структуру GPIO драйвера с поддержкой прерываний (IRQ: Interrupt Request).
Входные данные аналогичны предыдущей статье: разработанный GPIO блок для нового процессора «зашитый» на ПЛИС и запущенный Linux версии 3.18.19.
Для того чтобы разработать GPIO драйвер, нам потребуется выполнить следующие шаги:
- Понять принцип взаимодействия GPIO драйвера с user space интерфейсом;
- Добавить модуль ядра в сборку и описать аппаратную часть в device tree;
- Реализовать базовый скелет драйвера, а также его точки входа и извлечения;
- Реализовать функциональную часть GPIO драйвера;
- Добавить к реализации драйвера поддержку IRQ.
Примеры GPIO драйверов можно посмотреть тут.
Шаг первый
Для начала познакомимся с принципом взаимодействия GPIO драйвера через консоль пользователя.
С помощью небольшого bash скрипта, создадим в /sysfs элементы управления каждого GPIO. Для этого в командой строке нужно написать следующий скрипт:
Далее посмотрим какие возможности предоставляет /sysfs для конфигурации каждого GPIO:
В данный момент нас интересуют следующие поля:
- 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.
Шаг второй
Теперь можно добавить драйвер в сборку и описать аппаратную часть, выполнив уже стандартные действия. Первым делом создадим исходный файл:
После добавим конфигурацию драйвера в drivers/gpio/Kconfig:
Добавим в сборку драйвер в drivers/gpio/Makefile:
И, наконец, добавим в devicetree (*.dts) описание GPIO блока:
Более подробную информацию про devicetree можно прочитать тут.
Шаг третий
Перейдем к самой интересной для нас части!
Разработку драйвера начнем с подключения необходимых заголовочных файлов и описания полного скелета драйвера без поддержки IRQ. Далее последовательно будем наполнять каждую функцию кодом и сопровождать необходимыми пояснениями.
Как видно из реализации, скелет драйвера выглядит достаточно просто и содержит не так много необходимых функций и структур.
Для того чтобы описать будущий драйвер нам потребуются следующие элементы:
- 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_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. Чтобы узнать данные номера в конце драйвера добавлен вывод:
Конечно, Linux предоставляет возможность задать номера вручную, но давайте посмотрим на комментарий в исходном коде:
Шаг четвертый
Рассмотрим функциональную часть драйвера, а именно реализуем следующие методы:
.direction_output, .direction_input, .get и .set. Далее будет показан аппаратно-зависимый код, который в большинстве случаев будет отличаться.
Метод skel_gpio_direction_input выполняет следующие действия:
- Вызывается при следующей команде echo «in» > /sys/class/gpio/gpioN/direction;
- Считывает регистр SKEL_GPIO_PAD_DIR, который отвечает за установку направления пина GPIO;
- Устанавливает необходимую маску;
- Записывает полученное значение обратно в SKEL_GPIO_PAD_DIR.
Метод 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 выполняет следующие действия:
- Вызывается при следующей команде echo 1/0 > /sys/class/gpio/gpioN/value;
- Считывает регистр SKEL_GPIO_WR_DATA, который показывает значение текущего сигнала на линии;
- Устанавливает или сбрасывает необходимый бит по offset;
- Записывает полученное значение обратно в SKEL_GPIO_WR_DATA.
Метод skel_gpio_get считывает значение сигнала на линии, прочитав регистр SKEL_GPIO_RD_DATA.
После того как мы описали все необходимые методы и структуры, можно собрать все вместе и взглянуть на итоговый вариант.
Реализованный драйвер содержит необходимый функционал для управления GPIO, но в данный момент в драйвере отсутствует поддержка работы с прерываниями, поэтому можно перейти к следующему шагу.
Шаг пятый
Добавление IRQ в GPIO драйвер можно разделить на три шага:
- Описание поддерживаемых методов в структурах данных ядра;
- Включение поддержки IRQ в момент загрузки драйвера в систему;
- Реализация поддерживаемых методов.
Первоначально опишем необходимый набор операций:
Следовательно драйвер может разрешать(skel_gpio_irq_unmask)/запрещать(skel_gpio_irq_mask) прерывания и указать тип события по которому оно будет генерироваться (skel_gpio_irq_set_type).
Далее опишем единственный метод, который отвечает за сопоставление виртуального irq number с аппаратным.
Затем укажем ядру, что загружаемый драйвер поддерживает работу с IRQ. Для этого необходимо добавить в probe функцию следующий код:
В выше приведенном коде происходит:
- Выделение и инициализация области под irq_domain;
- Считывание номера прерывания с devicetree;
- mapping между виртуальным и аппаратным прерыванием;
- Регистрация обработчика прерывания и установка данных на передачу в обработчик;
Приступим к реализации некоторых выше описанных методов.
skel_gpio_to_irq — создает mapping между аппаратным и виртуальным прерыванием. Если же данный mapping уже был создан, то возвращает номер созданного виртуального прерывания.
skel_irq_handler — обработчик прерывания, который:
- Считывает регистр статуса прерывания;
- Считывает регистр маски прерывания;
- Выделяет прерывания ожидающие обработки;
- Для каждого GPIO в котором возникло прерывание вызывает generic_handle_irq.
Вот и все, в данной статье мы узнали как происходит взаимодействие GPIO драйвера с виртуальной файловой системой sysfs, реализовали базовую структуру GPIO драйвера, а также рассмотрели методы которые требуются для поддержки IRQ.
В статье не приведена реализация методов skel_gpio_irq_unmask, skel_gpio_irq_mask и skel_gpio_irq_set_type по двум причинам. Во-первых, данные методы просты в реализации. Во-вторых, аппаратно-зависимы. Они отвечают за разрешение или запрет прерываний по определенным событиям, которые поддерживает GPIO контроллер.
Пожалуйста, если вы нашли ошибки/неточности, или вам есть что добавить — напишите в ЛС или в комментарии.
Источник