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-репозитории, который ляжет в основу следующей статьи и позволит получить исчерпывающее представление о подсистеме.


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

Комментарии (2)


  1. xztau
    24.08.2024 07:33

    А какова цена ОС для использования на 100-150 устройств?


    1. a-n-d
      24.08.2024 07:33

      Боюсь, что среди авторов блога представлен в основном инженерный состав компании. Вопрос Ваш коллегам, занимающимся поставками, мы конечно передадим, но не припомню, чтобы эти вопросы регулярно обсуждались публично. Насколько я могу судить, имеется как некоторый прайс-лист, так и кучерявая система скидок и предложений по поддержке отечественных производителей и образовательных учреждений. Увы, не компетентен в этих вопросах.