Начиная с версии ядра 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