Начиная с версии ядра 4.6-r1 нам стал доступен новый интерфейс для взаимодействия с подсистемой ядра gpio. Теперь существует три официальных способа работы с gpio и получения от них прерываний. Нет смысла углубляться в потребности для данной подсистемы, для малой части это суровые будни, для другой части веселое хобби, и для всех вместе в ядре была предоставлена новая возможность взаимодействия.
Заметка носит популярный характер, так как основных преимуществ, которые шли в комплекте с нововведением, а именно упрощение работы с gpio в контексте ядра касаться не будем.
Новый интерфейс uapi gpio
https://github.com/torvalds/linux/blob/master/include/uapi/linux/gpio.h
Во-первых, теперь 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 порядковый номер линии в банке.
# modprobe gpio-mockup gpio_mockup_ranges=0,8,8,16 Данной командой мы создали два gpiochip по 8 линий каждый, c диапазонами [0-8), [8,16). Про gpio_mockup_named_lines мы поговорим чуть позже.
Сравнение sysfs и uapi
С помощью нового драйвера рассмотрим отличия между двумя системами с точки зрения пользователя.
Информация о gpiochip's
sysfs
# cat /sys/class/gpio/gpiochip*/base
0
8
# cat /sys/class/gpio/gpiochip*/ngpio
8
8
# cat /sys/class/gpio/gpiochip*/label
gpio-mockup-A
gpio-mockup-B uapi
struct gpiochip_info chip_info;
ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info); # ./lsgpio | grep GPIO\ chip
GPIO chip: gpiochip1, "gpio-mockup-B", 8 GPIO lines
GPIO chip: gpiochip0, "gpio-mockup-A", 8 GPIO lines Задание линии как входа и чтение значения
sysfs
# echo in > /sys/class/gpio/gpio0/direction
# cat /sys/class/gpio/gpio0/value uapi
struct gpiohandle_request req;
req.lineoffsets[0] = 0;
req.flags = GPIOHANDLE_REQUEST_INPUT;
req.lines = 1;
struct gpiohandle_data data;
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); Задание линии как выхода
sysfs
# echo high > /sys/class/gpio/gpio0/direction uapi
struct gpiohandle_request req;
req.lineoffsets[0] = 0;
req.flags = GPIOHANDLE_REQUEST_OUTPUT;
req.default_values[0] = 1;
req.lines = 1;
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); Edge handling
sysfs
# echo both > /sys/class/gpio/gpio0/edge uapi
struct gpioevent_request ereq;
ereq.lineoffset = 0;
ereq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &ereq); Polling on events
В документации ядра для sysfs указано использовать EPOLLPRI и EPOLLERR (или exceptfds для select), это в принципе характерно для любого вызова sysfs_notify необязательно именно для подсистемы gpio.
Для uapi достаточно EPOLLIN.
struct epoll_event event;
event.data.fd = ereq.fd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, ereq.fd, &event);Читаем мы событие с временной меткой и типом GPIOEVENT_EVENT_RISING_EDGE или GPIOEVENT_EVENT_FALLING_EDGE.
struct gpioevent_data event;
read(pin->fd, &event, sizeof(event)); EPOLLET для uapi работает согласно документации на epoll.
labels
Маленькая ремарка для sysfs
Имя контакта gpioN к которому так все привыкли, вообще говоря, каноническим не является, а используется если контакту не было присвоено имя, например в Device Tree.
// drivers/gpio/gpiolib-sysfs.c
// int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
offset = gpio_chip_hwgpio(desc);
if (chip->names && chip->names[offset])
ioname = chip->names[offset];
dev = device_create_with_groups(&gpio_class, &gdev->dev,
MKDEV(0, 0), data, gpio_groups,
ioname ? ioname : "gpio%u",
desc_to_gpio(desc)); Попробуем gpio-mockup с опцией gpio_mockup_named_lines:
# modprobe gpio-mockup gpio_mockup_ranges=0,8,8,16 gpio_mockup_named_lines=1
# echo 0 > /sys/class/gpio/export
# ls /sys/class/gpio/gpio-mockup-A-0
active_low device direction edge power subsystem uevent value Как мы видим имя контакта приобрело вид gpio_chip_label-gpio_offset, но это справедливо только для драйвера gpio-mockup.
// drivers/gpio/gpio-mockup.c
// static int gpio_mockup_name_lines(struct device *dev, struct gpio_mockup_chip *chip)
for (i = 0; i < gc->ngpio; i++)
names[i] = devm_kasprintf(dev, GFP_KERNEL, "%s-%d", gc->label, i); Способа "угадать" заранее, существует ли имя для контакта, не используя uapi не представляется возможным, а поиск экспортированной "именованной" линии затруднен, если имя заранее не известно (опять же если известно, то нам для однозначной идентификации необходимо имя и смещение на известном gpiochip).
uapi
Интерфейс uapi позволяет нам без инициализации видеть имена линий:
#./lsgpio | grep gpio-mockup-A
GPIO chip: gpiochip0, "gpio-mockup-A", 8 GPIO lines
line 0: "gpio-mockup-A-0" unused [output]
...
line 7: "gpio-mockup-A-7" unused [output] С соответствующим файлом device tree (пример взят из документации ядра):
gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R",
"LED G", "LED B", "Col A", "Col B", "Col C", "Col D",
"Row A", "Row B", "Row C", "Row D", "NMI button",
"poweroff", "reset"; Мы бы видели имя в struct gpioline_info для каждого контакта, к сожалению, мало кто именует контакты, даже для распостраненных SBC.
Возможности uapi недоступные для sysfs
Теперь перечислим преимущества недоступные старому интерфейсу.
Основным преимуществом я считаю временную метку, которая присваивается событию в верхней половине обработчика прерывания. Что незаменимо для приложений, которым важным является точность измерения времени между событиями.
le->timestamp = ktime_get_real_ns(); Если позволяет драйвер устройства, линия дополнительно может быть сконфигурирована как открытый коллектор (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
- Экономит нам syscall (не забываем про необходимость lseek после чтения gpio/value).
- Инициализация массива входов или выходов.
- Чтение или запись массива входов или выходов.
- Open drain и Open source
- Пользовательские метки
- "Real time nanosecond timestamp" передаваемая в событии
Критика 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
https://github.com/torvalds/linux/tree/master/tools/gpio
- gpio-event-mon — утилита для отслеживания событий gpio линий
- gpio-hammer — включает/выключает линию n раз с фиксированной частотой
- lsgpio — пример листинга gpiochip и линий
libgpiod
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/
От автора драйверов gpio-mockup и irq-sim товарища Bartosz'a Gołaszewski.
Позиционируется автором как библиотека, для облегчения работы с gpio посредством нового интерфейса uapi, так же содержит набор полезных утилит.
- gpiodetect — листинг gpiochip с именем, ярлыком и количеством линий
- gpioinfo — листинг gpiochip с именем, смещением, ярлыком и статусом линий
- gpioget — читаем состояние линии
- gpioset — задаем состояние линии, и если необходимо держим линию занятой до истечения заданного времени, сигнала или пользовательского ввода
- gpiofind — находит линию по имени и выводит устройство gpiochipN и смещение линии
- gpiomon — то же самое, что и gpio-event-mon
Материалы
- GPIO Interfaces (legacy)
- Specifying GPIO information for devices
- A fresh look at the kernel's device model
- A fresh look at the kernel's device model, Linus W. comment
- simulated interrupts
- GPIO bulk changes for kernel v4.6
