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

от автора

image

Современнные Системы-на-Кристалле (SoC) содержат в себе десятки различных контроллеров, вариативность которых меняется в зависимости от поколения или ревизии чипов того или иного производителя. Особо выделяются контроллеры системного тактирования (Clock) и сброса (Reset), объем функциональности которых охватывает все оставшиеся контроллеры более узкого назначения.

В этой статье мы расскажем о новой разработанной подсистеме управления такими блоками в контексте операционной системы реального времени «Нейтрино». Затронем небольшую предысторию её создания, общую архитектуру с примерами кода и пример использования.


Вступление

Одной из ключевых особенностей ЗОСРВ «Нейтрино» является микроядерная архитектура, представляющая из себя инфраструктуру изолированных сервисов (менеджеры ресурсов, драйвера и пользовательские программы). Коммуникации между ними строятся вокруг концепций общей памяти и передачи сообщений микроядром.

В процессе разработки и портирования различных драйверов интерфейсов, зачастую возникает проблема синхронизации и разделения доступа к физическим ресурсам (в случаях когда ряд драйверов или пользовательских программ напрямую обращаются к регистрам одного и того же контроллера), что приносит некоторые неудобства и затягивает процесс разработки, не говоря уже и об отсутствии продуманного HAL (Hardware Abstraction Layer) для общих блоков SoC.

Регулярно решая такие задачи необобщенными способами при разработке BSP и драйверов различных интерфейсов, было решено спроектировать и разработать подсистему, предоставляющую интерфейс к системным управляемым блокам СнК (SoC).

Архитектура подсистемы

Подсистема получила название «Подсистема управления общими элементами платформ» или platform-control.

Состав:

  • Библиотека libplatform-control, содержащая в себе прикладные интерфейсы подсистемы (о них будет расскзано позже).
  • Менеджер ресурсов platform-control, обрабатывающий запросы клиентов.
  • Внутренний драйверный интерфейс динамически подгружаемых драйверов (частично также будет рассмотрен).
  • Расширяемый набор загружаемых драйверов для различных платформ (SoC), имеющих в имени префикс devp-*. В их задачи входит непосредственное управление аппаратными блоками платформы (Clock, Reset, Power-Domain и т.д.).

Взаимозависимость компонентов в архитектуре имеет вид:

image

Клиентские приложения в данном случае — преимущественно более высокоуровневые драйвера, которым требуется сервис доступа к блокам SoC.

Спойлер

В микроядерных ОС любой код, даже драйверный, интерпретируется со стороны ядра как прикладной. Их можно спокойно перезапускать, дебажить и издеваться разными способами без существенного влияния на работоспособность системы в целом.

Модель работы подсистемы

При запуске менеджер ресурсов platform-control загружает драйвера devp-*.so и ищет их точки входа. Она определяется в драйверах с помощью структуры plat_ctrl_funcs_t. Далее, для коммуникации клиента с менеджером используется клиентское API, функции которого формируют и отправляют команды используя devctl(). platform-control обрабатывает принятые команды и вызывает функции драйверного API, реализации которых определены в драйвере через стркутуру plat_ctrl_funcs_t.

Последовательность вызова функций в общей модели взаимодействия имеет вид:

image

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

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

Типы данных для различных блоков

Каждая из структур представляет унифицированное описание состояния, которое требуется настроить для того или иного управляемого блока. Пользователь (со стороны клиентского приложения) напрямую не взаимодействует с полями этих структур, т.к. для удобства самого пользователя эти действия инкапсулированы внутри функций пользовательского 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 с драйвером devp-example.so

    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/


Комментарии

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

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