Подсистема управления GPIO для ЗОСРВ «Нейтрино»

от автора

При разработке драйверов GPIO для нашей операционной системы реального времени «Нейтрино» исторически имеется одна неприятность — отсутствие общего дизайна для таких драйверов. Причин для этого несколько: они считались и считаются тривиальными, разрабатывают их разные организации и разработчики. Из-за этого каждый инженер нередко писал реализацию «под себя»: кто-то в виде сервиса, кто-то даже в виде статической библиотеки. Такой подход, хоть и кажется удобным на первых этапах, со временем приводит к фрагментации кода, усложнению поддержки и невозможности систематизировать накопленный опыт. Чтобы избежать этих проблем в будущем мы решили перейти на унифицированную подсистему управления GPIO устройствами и выработать подход, который будет считаться best practices в нашей ОС.

Подсистема управления GPIO призвана стать универсальным слоем абстракции, ключевая задача которой — обеспечить простоту разработки драйверов и максимальную переносимость кода между различными платформами. В данной статье мы рассмотрим архитектуру и особенности реализации подсистемы управления GPIO, а также проанализируем, как она обеспечивает управление аппаратными ресурсами и взаимодействие с драйверами.

Общее представление архитектуры подсистемы GPIO

Подсистема GPIO состоит из следующих компонентов:

  • Библиотека libgpio — содержит в себе клиентские и драйверные интерфейсы подсистемы
  • Менеджер ресурсов — обрабатывает запросы клиентов и передает их непосредственно драйверу
  • Драйвер — взаимодействует с аппаратурой, управляя регистрами GPIO

В данной подсистеме менеджер ресурсов разрабатывается вместе с драйверными функциями, являясь частью самого драйвера. Для взаимодействия клиента с менеджером используется клиентское API, функции которого формируют команды и передают их через системный вызов devctl(). После чего менеджер GPIO обрабатывает принятые команды и вызывает функции драйверного API.

Их взаимное расположение в архитектуре будет иметь следующий вид:

Библиотека управления подсистемой GPIO

Для полного понимания работы подсистемы разберем непосредственно функциональность библиотеки, которая включает в себя реализацию пользовательского и драйверного интерфейсов, необходимые функции менеджера, макроопределения, типы данных и структуры.

Функции клиентского и драйверного API

Основные операции клиентских функций:

  • установка на вход/выход GPIO пина
  • установка состояния в 1/0 GPIO пина
  • чтение состояния GPIO пина

Данные функции позволяют клиентскому приложению взаимодействовать с требуемыми GPIO пинами. Объявления функций пользовательского интерфейса хранятся в заголовочном файле gpio.h:

#include <hw/gpio.h>  int gpio_close( int fd ); int gpio_open( void ); int gpio_set_input( int fd, uint8_t pin_num ); int gpio_set_output( int fd, uint8_t pin_num ); int gpio_write( int fd, uint8_t pin_num, uint8_t value ); uint8_t gpio_read( int fd, uint8_t pin_num );

Но как же именно работают данные функции? У функций драйверного API, про которые будет рассказано чуть дальше, во входных параметрах указывается определенный тип структуры. Эти структуры представляют из себя стандартизированный формат параметров управления состоянием пинов GPIO. Пользователь не взаимодействует напрямую с полями этих структур, но за их заполнение отвечают как раз эти клиентские функции библиотеки.

Для примера возьмем структуру управления пином GPIO (Установка состояния в 1 или 0):

typedef struct {     uint8_t     pin_num;     uint8_t     data; } gpio_write_t;

Структура состоит из двух полей, определяющих состояние пина GPIO:

  • pin_num — номер GPIO пина
  • data — состояние, которое мы хотим присвоить пину, указанному в pin_num

Вызов gpio_write( int fd, uint8_t pin_num, uint8_t value ) заполняет структуру и передает её менеджеру GPIO, используя devctl(). После чего менеджер вызовет функцию драйверного интерфейса с именем gpio_cmd_write(), у которой в качестве аргумента и будет наша структура gpio_write_t. В качестве синхронизации для многопоточных приложений используется мьютекс.

Код данной функции:

int gpio_write( int fd, uint8_t pin_num, uint8_t value ) {     gpio_write_t cfg;     int error = EINVAL;      cfg.pin_num = pin_num;     cfg.data    = value;      pthread_mutex_lock( &gpio_mutex );     error = devctl( fd, DCMD_GPIO_WRITE, &cfg, sizeof(gpio_write_t), NULL );     pthread_mutex_unlock( &gpio_mutex );      return ( error ); }

Драйверное API представляет из себя набор callback-функций. При их реализации функции должны быть объявлены в точности так, как указано ниже:

void *gpio_init(); void gpio_fini( void *hdl ); uint32_t gpio_get_value( void *hdl, int gpio_num ); uint32_t gpio_get_direction( void *hdl, int gpio_num ); int gpio_cmd_set_input( void *hdl, gpio_input_t *pin ); int gpio_cmd_set_output( void *hdl, gpio_output_t *pin ); int gpio_cmd_read( void *hdl, gpio_read_t *buf ); int gpio_cmd_write( void *hdl, gpio_write_t *buf );

Полная документация всех перечисленных функций будет опубликована вместе с будущим релизом ЗОСРВ «Нейтрино».

Функции менеджера ресурсов

Из-за схожести построения GPIO на различных платформах было решено отдельно выделить в библиотеку функции ввода/вывода уровня POSIX для менеджера ресурсов, а именно:

  • _gpio_read() — чтение устройства с помощью утилиты cat
  • _gpio_write() — запись в устройство с помощью утилиты echo
  • _gpio_devctl() — обработка команд, отправленных через системный вызов devctl()

Их прототипы представлены ниже:

int _gpio_read( resmgr_context_t *ctp, io_read_t* msg, RESMGR_OCB_T *pocb_c ); int _gpio_write( resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *pocb_c ); int _gpio_devctl( resmgr_context_t *ctp, io_devctl_t *msg, gpio_ocb_t *ocb );

Пользователь и разработчик не взаимодействуют напрямую с данными функциями. Разработчику необходимо всего лишь указать эти функции при создании менеджера.
Основное назначение этих функций это создание унифицированного взаимодействия с различными девайсами. В итоге появляется общий стиль ввода/вывода информации о пинах GPIO для всех платформ.

Ниже представлен пример вывода информации о пинах GPIO:

cat /dev/gpio gpio0   0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31   0i 1i 0i 1i 1i 0i 1i 1i 0i 1i 1i 1i 1i 1i 1i 0i 0i 1i 0i 0i 1i 0i 1i 0i 1i 1i 0i 1i 0i 0i 0i 0i  gpio1   0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31   1i 1i 0o 0i 0i 0i 0i 0i 0i 0i 0i 0i 1i 1i 1i 1i 1i 1i 1i 1i 1i 0i 0i 1i 0i 1i 1i 1i 1i 1i 1i 1i

Cуфикс i — означает, что вывод в режиме вход. Суфикс o — означает, что вывод в режиме выход.

Для ввода присутствует два варианта записи установки пинов:

echo "<gpio_number> <gpio_pin_number> <value|i|o>" > /dev/gpio  echo "P<pin_number> <value|i|o>" > /dev/gpio

где gpio_number — номер контроллера GPIO; gpio_pin_number — номер пина контроллера GPIO; pin_number — номер среди всех контроллеров GPIO.

Если размер контроллера GPIO окажется меньше 32, то старшие биты просто будут всегда в состоянии 0i.

Взаимодействие клиентского приложения с подсистемой

В роли клиентских приложений могут выступать как и обычные программы, которым просто нужно обратиться к состоянием пинов, так и драйвера других интерфейсов.
При инициализации подсистемы менеджер ресурсов создает устройство /dev/gpio, через которое осуществляется все взаимодействие между клиентскими приложениями и подсистемой с использованием API-функций.

Для подключения к клиенту библиотеки подсистемы libgpio необходимо подключить заголовочный файл gpio.h и слинковать бинарный архив библиотеки. Далее нужно зарегистрировать файловый дескриптор менеджера, открыв файл /dev/gpio с помощью функции gpio_open(). И после чего мы сможем использовать все доступные клиентские функции библиотеки.

Пример установки и последующего чтения состояния GPIO пина из клиентского приложения:

#include <hw/gpio.h>  ...  /* Регистрируем файловый дескриптор /dev/gpio */ int fd = gpio_open();  ...  /* Установка состояния GPIO пина */ if ( gpio_write( fd, pin, write_data ) != EOK ) {     /* Сигнализирование о некорректной работе вызова */      ... }  ...  /* Чтение состояния GPIO пина */ if ( ( read_data = gpio_read( fd, pin ) ) != EINVAL ) {     /* Сигнализирование о некорректной работе вызова */      ... }  ...  /* Закрываем файловый дескриптор */ gpio_close( fd );

Реализация драйверных функций

Естественно, первым этапом при разработке драйвера будет являться его инициализация. Для этого заводится структура управляющего дескриптора, которая может содержать различные данные в зависимости от необходимых потребностей устройства. В нашем случае для примера это будет просто массив из адресов GPIO контроллеров устройства. После чего этот дескриптор сможет передаваться во все остальные функции драйвера.

Пример псевдокода некоторых функций драйвера:

#include <hw/gpio.h>  typedef struct {     uintptr_t   gpio_base[GPIO_NUMBERS]; } sample_t;  void *gpio_init() {     sample_t *gpio_hdl = NULL;      /* Вызов функций маппирования, сброс регистров и т.д. */      ...      return ( gpio_hdl );  }  int  gpio_cmd_set_input( void *hdl, gpio_input_t *pin ) {     /* Установка пина GPIO в состояние входа в соответствии с параметрами структуры gpio_input_t */      ...      return 0;  }  int gpio_cmd_read( void *hdl, gpio_read_t *buf ) {     /* Чтение состояния пина GPIO в соответствии с параметрами структуры gpio_read_t и сохранение результата в её поле*/      ...      return 0; }

В итоге схема работы типичного GPIO драйвера будет выглядеть следующим образом:

Заключение

Использование унифицированной подсистемы упрощает разработку и отладку, и сохраняет гибкость для дальнейшего расширения. Полных публикаций примеров драйверов в открытом доступе пока нет, т.к. в актуальную версию ЗОСРВ «Нейтрино» 2024 подсистема еще не включена. Но в дальнейшем мы планируем выложить исходники драйверов в наш GIT-репозиторий, а также опубликовать уже готовые подробные dev-гайды и документацию на API.

Подписывайтесь на наш канал, чтобы быть в курсе свежих новостей


ссылка на оригинал статьи https://habr.com/ru/articles/898380/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *