Современнные Системы-на-Кристалле (SoC) содержат в себе десятки различных контроллеров, вариативность которых меняется в зависимости от поколения или ревизии чипов того или иного производителя. Особо выделяются контроллеры системного тактирования (Clock) и сброса (Reset), объем функциональности которых охватывает все оставшиеся контроллеры более узкого назначения.
В этой статье мы расскажем о новой разработанной подсистеме управления такими блоками в контексте операционной системы реального времени «Нейтрино». Затронем небольшую предысторию её создания, общую архитектуру с примерами кода и пример использования.
Вступление
Одной из ключевых особенностей ЗОСРВ «Нейтрино» является микроядерная архитектура, представляющая из себя инфраструктуру изолированных сервисов (менеджеры ресурсов, драйвера и пользовательские программы). Коммуникации между ними строятся вокруг концепций общей памяти и передачи сообщений микроядром.
В процессе разработки и портирования различных драйверов интерфейсов, зачастую возникает проблема синхронизации и разделения доступа к физическим ресурсам (в случаях когда ряд драйверов или пользовательских программ напрямую обращаются к регистрам одного и того же контроллера), что приносит некоторые неудобства и затягивает процесс разработки, не говоря уже и об отсутствии продуманного HAL (Hardware Abstraction Layer) для общих блоков SoC.
Регулярно решая такие задачи необобщенными способами при разработке BSP и драйверов различных интерфейсов, было решено спроектировать и разработать подсистему, предоставляющую интерфейс к системным управляемым блокам СнК (SoC).
Архитектура подсистемы
Подсистема получила название «Подсистема управления общими элементами платформ» или platform-control
.
Состав:
- Библиотека
libplatform-control
, содержащая в себе прикладные интерфейсы подсистемы (о них будет расскзано позже). - Менеджер ресурсов
platform-control
, обрабатывающий запросы клиентов. - Внутренний драйверный интерфейс динамически подгружаемых драйверов (частично также будет рассмотрен).
- Расширяемый набор загружаемых драйверов для различных платформ (SoC), имеющих в имени префикс
devp-*
. В их задачи входит непосредственное управление аппаратными блоками платформы (Clock, Reset, Power-Domain и т.д.).
Взаимозависимость компонентов в архитектуре имеет вид:
Клиентские приложения в данном случае — преимущественно более высокоуровневые драйвера, которым требуется сервис доступа к блокам SoC.
В микроядерных ОС любой код, даже драйверный, интерпретируется со стороны ядра как прикладной. Их можно спокойно перезапускать, дебажить и издеваться разными способами без существенного влияния на работоспособность системы в целом.
Модель работы подсистемы
При запуске менеджер ресурсов platform-control
загружает драйвера devp-*.so
и ищет их точки входа. Она определяется в драйверах с помощью структуры plat_ctrl_funcs_t
. Далее, для коммуникации клиента с менеджером используется клиентское API, функции которого формируют и отправляют команды используя devctl(). platform-control
обрабатывает принятые команды и вызывает функции драйверного API, реализации которых определены в драйвере через стркутуру plat_ctrl_funcs_t
.
Последовательность вызова функций в общей модели взаимодействия имеет вид:
Библиотека управления подсистемой
Функциональность библиотеки включает реализацию пользовательского и драйверного интерфейсов, необходимые макроопределения, типы данных и структуры. Давайте немного разберемся в них.
Типы данных для различных блоков
Каждая из структур представляет унифицированное описание состояния, которое требуется настроить для того или иного управляемого блока. Пользователь (со стороны клиентского приложения) напрямую не взаимодействует с полями этих структур, т.к. для удобства самого пользователя эти действия инкапсулированы внутри функций пользовательского API (их мы рассмортим в следующем подразделе), но они важны для понимания тонкостей работы всей подсистемы.
Для примера рассмотрим структуру для управления подблоком Reset. Более предметное описание других структур будет расмотрено в следующей статье, которую планируем посвятить разработке эталонного платформоспецифичного драйвера «от и до».
/* Reset Controller configuration */ typedef struct { uint32_t id; uint32_t state; #define PLAT_CTRL_RST_STATE_DISABLED (0 << 0) #define PLAT_CTRL_RST_STATE_ENABLED (1 << 0) } plat_ctrl_reset_cfg_t;
Структура состоит из двух полей определяющих состояние управляемого подблока:
id
— псевдо-номер линии сброса внутри управляемого подблока (определяется в заголовочном файле драйвераdevp-*
)state
— состояние, которое требуется присвоить линии, согласно псевдо-номеру, указанному полемid
.
В свою очередь поле state
описывается двумя состояниями, пресущими всем аппаратным линиям сброса современных СнК (SoC):
PLAT_CTRL_RST_STATE_ENABLED
— макроопределение состояния, соответсвующее включенной линии сброса.PLAT_CTRL_RST_STATE_DISABLED
— макроопределение состояния, соответсвующее выключенной линии сброса.
После заполнения данной структуры, формируется сообщение, которое отправляется для обработки менеджеру ресурсов platform-control
, используя вызов API int plat_ctrl_reset_assert(int fd, int id);
, основанный на общесистемной функции devctl().
Функции пользовательского API
Для основных типовых действий есть вызовы API, основные действия:
- включение/выключение линий сброса (Reset)
- включение/выключение линий питания (Power Domain)
- включение/выключение линий тактирования (Clock)
- настройка/получение частоты линий тактирования (Clock/PLL)
- настройка источника тактирования (Clock)
Данный набор функций предоставляет клиентскому приложению взаимодействовать с требуемыми аппаратными блоками SoC. Объявления функций пользовательского интерфейса хранятся в заголовочном файле platform-control.h
:
int plat_ctrl_close(int fd); int plat_ctrl_open(void); int plat_ctrl_getdrvinfo(int fd, plat_ctrl_drvinfo_t *drvinfo); int plat_ctrl_pd_attach(int fd, int id); int plat_ctrl_pd_deattach(int fd, int id); int plat_ctrl_reset_assert(int fd, int id); int plat_ctrl_reset_deassert(int fd, int id); int plat_ctrl_clk_enable(int fd, int id); int plat_ctrl_clk_disable(int fd, int id); int plat_ctrl_clk_set_rate(int fd, int id, uint32_t rate); int plat_ctrl_pll_set_rate(int fd, int id, uint32_t rate); int plat_ctrl_clk_set_parent(int fd, int id, uint32_t parent); uint32_t plat_ctrl_clk_get_rate(int fd, int id); uint32_t plat_ctrl_pll_get_rate(int fd, int id);
На примере уже известной нам структуры с описанием состояния блока Reset, рассмотрим изнутри логику работы реализующей функции plat_ctrl_reset_assert()
. Данная функция заполняет структуру plat_ctrl_reset_cfg_t
и передает её менеджеру platform-control
, используя devctl(). Это приведёт к вызову на стороне сервиса функцию драйверного интерфейса с именем set_reset
, которая предоставляется драйвером devp-*
. Аргументом этого callback-а является ранее заполненная структура plat_ctrl_reset_cfg_t
с конфигурацией.
Псевдокод данной функции:
int plat_ctrl_reset_assert( int fd, int id ) { plat_ctrl_reset_cfg_t cfg = {id, PLAT_CTRL_RST_STATE_ENABLED}; int error = EINVAL; ... pthread_mutex_lock(&platform_mutex); error = devctl(fd, DCMD_PLAT_CTRL_SET_RESET, &cfg, sizeof(plat_ctrl_reset_cfg_t), NULL); pthread_mutex_unlock(&platform_mutex); ... return (error); }
В качестве синхронизации для многопоточных приложений используется мьютекс. Спин-блокировки в этом месте опасны, поскольку длительность отработки драйвером devctl() не детерминирована.
Обзор драйверного API
Низкоуровневая реализация интерфейса взаимодействия с контроллерами в составе SoC основана на концепции драйверных callback-функций. Эти функции принимают в качестве параметра структуры описания состояния управляемого устройства и на их основе устанавливают новое состояние аппаратного блока.
Интерфейс драйвера имеет вид:
typedef struct { size_t size; void* (*init)(void *hdl, char *options); void* (*fini)(void *hdl); int (*drvinfo)(void *hdl, plat_ctrl_drvinfo_t *info); int (*set_pd)(void *hdl, plat_ctrl_pd_cfg_t *cfg); int (*set_reset)(void *hdl, plat_ctrl_reset_cfg_t *cfg); int (*set_clk)(void *hdl, plat_ctrl_clk_cfg_t *cfg); int (*get_clk)(void *hdl, plat_ctrl_clk_cfg_t *cfg); } plat_ctrl_funcs_t;
В следующей главе рассмотрим примеры использования функций драйверного и пользовательского интерфейсов.
Взаимодействие пользователя с подсистемой
Разобравшись с компонентами подсистемы и их взаимодействием друг с другом, погрузимся глубже и рассмотрим выдержки кода драйвера и общие примеры использования подсистемы со стороны клиентского приложения.
Драйвер
Драйвер отвечает за низкоуровневую настройку контроллеров, путем записи/чтения регистров на основе принятой конфигурации. Для сборки необходим заголовочный файл библиотеки platform-control.h
для доступа к структурам подсистемы.
Обязательным этапом является определение структуры plat_ctrl_funcs_t
с именем plat_ctrl_drv_entry
, именно её менеджер будет «искать» как точку входа при запуске драйвера. Поля струтуры указывают на функции драйверного API, содержащие реализацию того или иного функционала.
Обобщенный пример кода платформоспецифичного драйвера:
#include <hw/platform-control.h> /* Точка входа при запуске менеджера ресурсов */ plat_ctrl_funcs_t plat_ctrl_drv_entry = { sizeof(plat_ctrl_funcs_t), soc_init, /* init() */ soc_fini, /* fini() */ NULL, /* drvinfo() */ NULL, /* set_pd() */ soc_set_reset, /* set_reset() */ NULL, /* set_clk() */ NULL /* get_clk() */ }; void * soc_init(void *hdl, char *options) { /* Выделение необходимых ресурсов */ ... return (hdl); } void * soc_fini(void *hdl) { /* Освобождение выделенных в `init` ресурсов */ ... free(hdl); ... } int soc_set_reset(void *hdl, plat_ctrl_reset_cfg_t *cfg) { /* Низкоуровневая настройка регистров Reset контроллера согласно параметрам из структкры `plat_ctrl_reset_cfg_t`*/ ... return (EOK); }
В результате сборки, на выходе получаем динамическую библиотеку devp-example.so
.
Клиентское приложение
При запуске подсистемы, менеджер platform-control
создает устройство /dev/platform
, все взаимодействовия с подсистемой из клиентского приложения происходит через эту точку, используя функции (plat_ctrl_*
) пользовательского API.
platform-control -d example &
В первую очередь подключаем необходимый заголовочный файл библиотеки platform-control.h
и регистрируем файловый дескриптор подсистемы, открыв файл /dev/platform
с помощью функции plat_ctrl_open()
(в конце не забываем его закрыть используя plat_ctrl_close()
). Получив файловый дескриптор, можно эффективно использовать необходимый функционал подсистемы.
Пример сброса состояния произвольного контроллера из процесса клиентского приложения:
#include <hw/platform-control.h> ... /* Регистрируем файловый дескриптор `/dev/platform` */ int fd = plat_ctrl_open(); ... /* Включаем сигнал сброса необходимой линии согласно параметру `id` */ if(plat_ctrl_reset_assert(fd, id) != EOK) { ... /* Сигнализирование о некорректной работе вызова */ ... } delay(20); /* Выключаем сигнал сброса необходимой линии согласно параметру `id` */ if(plat_ctrl_reset_deassert(fd, id) != EOK) { ... /* Сигнализирование о некорректной работе вызова */ ... } ... /* Закрываем файловый дескриптор */ plat_ctrl_close(fd);
Заключение
Данная статья является ознакомительной и направлена на предварительное ознакомление пользователей с нововведениями в следующем номерном релизе ЗОСРВ «Нейтрино». Уже сейчас билды этих компонентов доступны в нашем публичном GIT-репозитории в составе BSP для OrangePi PC в рамках нашей академической программы. Таким образом данная статья послужит отличным дополнением к уже опубликованным материалам и поможет разобраться в архитектуре подсистемы.
В следующей статье по данной подсистеме будет рассмотрен законченный пример по разработке devp-*
драйвера и его использованию в клиентских приложениях. В скором времени будет опубликован пример реального кода в нашем GIT-репозитории, который ляжет в основу следующей статьи и позволит получить исчерпывающее представление о подсистеме.
Подписывайтесь на наш канал, чтобы быть в курсе свежих новостей
ссылка на оригинал статьи https://habr.com/ru/articles/837776/
Добавить комментарий