Драйвер elcore50

Описание

Драйвер elcore50 создаёт файл устройства /dev/elcore, а также по одному файлу устройства на каждое DSP-ядро: /dev/elcore0, /dev/elcore1 и т. д.

Взаимодействие с устройствами осуществляется через стандартные системные вызовы: open(), ioctl() и close().

Интерфейс драйвера elcore50 предоставляет следующую функциональность:

  1. Отображение областей памяти пользовательского процесса в адресное пространство DSP.

  2. Создание нового задания.

  3. Постановка заданий в локальную и глобальную очереди на выполнение на DSP.

  4. Уведомление о готовности заданий.

  5. Отслеживание текущего состояния задания.

  6. Обеспечение согласованности данных между CPU и DSP для заданной области памяти.

  7. Поддержка huge pages.

  8. Поддержка экспорта и импорта dma-buf.

  9. Отладка заданий.

Драйвер elcore50 отвечает за:

  1. Управление энергопотреблением DSP.

  2. Запуск заданий DSP.

  3. Отображение областей памяти между адресными пространствами пользовательского процесса и DSP.

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

  5. Идентификацию конкретного устройства DSP. У каждого устройства должен быть свой уникальный идентификатор, соответствующий физическому расположению устройства на чипе.

  6. Выделение XYRAM в DSP.

  7. ELcore-50 имеет возможность распределять внутреннюю память на две области: одна область работает как L2-кэш, вторая — как обычная память. Эти области могут быть разных размеров. Драйвер осуществляет корректную настройку распределения между L2-кэшем и обычной внутренней памятью.

  8. Управление сбросами DSP.

  9. Обработка системных вызовов DSP.

  10. Профилирование заданий DSP.

Параметры модуля ядра

Модуль ядра имеет следующие параметры:

  • caches — битовая маска, указывающая, какие из кэшей L1 или L2 должны быть включены или выключены. Значение 1 — кэш включен, значение 0 — кэш выключен.

    • Бит 0 управляет включением кэша L1.

    • Бит 1 управляет включением кэша L2.

    При этом, если задание затребовало в качестве локальной памяти более 256 КБайт, кэш L2 выключается принудительно, так как физически L2-кэш и XYRAM — это одно устройство с общей памятью.

    По умолчанию все кэши включены.

  • dbg_registers_simple — флаг, определяющий доступ отладчика к управляющим регистрам DSP-ядра. Допустимые значения:

    • 0 - доступ возможен к любым программно-доступным регистрам DSP-ядра.

    • 1 - доступ возможен только к регистровому и векторному файлам DSP-ядра, а также регистру PC.

    По умолчанию доступ возможен к любым программно-доступным регистрам DSP-ядра.

  • timer_period — период таймера для профилирования в микросекундах. См. Профилирование заданий DSP.

    По умолчанию таймер настроен на период 10 мс.

Параметры задаются при загрузке модуля ядра elcore50 в ОС Linux. Например, для того, чтобы выключить кэши L1 и L2 на уровне драйвера, необходимо выполнить:

# Выгрузить модуль ядра elcore50
modprobe -r elcore50

# Загрузить модуль ядра elcore50 с параметром caches=0
modprobe elcore50 caches=0

Подробнее о способах передачи параметров см. официальную документацию Linux Kernel.

Стандартные системные вызовы

Драйвер elcore50 реализует стандартные для файловых дескрипторов системные вызовы со следующими особенностями:

  • poll()

    • не реализован для файлового дескриптора устройства /dev/elcore*.

    • при указании файлового дескриптора задания ожидает завершения задания.

  • ioctl() c функциями, описанными в следующем разделе.

Описание IOCTL для устройства /dev/elcore

ioctl() данной группы работают с объектами, которые могут использоваться любым DSP-ядром.

Перед выполнением ioctl() данной группы необходимо открыть файл устройства /dev/elcore с помощью системного вызова open().

ELCORE50_IOC_CREATE_BUFFER

Экспорт dma-buf.

enum elcore50_buf_type {
  ELCORE50_CACHED_BUFFER_FROM_UPTR,
  ELCORE50_NONCACHED_BUFFER
};

struct elcore50_buf {
  /* out */
  int dmabuf_fd;

  /* unused */
  int mapper_fd

  /* in */
  enum elcore50_buf_type type;
  __u64 p;
  __u64 size;
};

Описание полей структуры elcore50_buf:

dmabuf_fd

Файловый дескриптор экспортируемого dma-buf.

type

Тип экспортуремого буфера. Допустимые значения:

  • ELCORE50_CACHED_BUFFER_FROM_UPTR — экспорт dma-buf на основе виртуального адреса из пользовательского пространства.

  • ELCORE50_NONCACHED_BUFFER — выделение некэшируемого буфера и экспорт dma-buf.

p

Виртуальный адрес буфера.

size

Размер буфера.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job из раздела «in». Для буферов типа ELCORE50_NONCACHED_BUFFER поле p игнорируется.

В случае успеха вызов ioctl() возвращает нулевое значение, поле dmabuf_fd содержит файловый дескриптор экспортируемого dma-buf.

ELCORE50_IOC_CREATE_MAPPER

Подготовка буфера к запуску задания. Драйвер инкапсулирует данные dmabuf’а во внутреннюю структуру, сбрасывает кэш буфера и экспортирует файловый дескриптор.

struct elcore50_buf {
  /* in */
  int dmabuf_fd;

  /* out */
  int mapper_fd;

  /* unused */
  enum elcore50_buf_type type;
  __u64 p;
  __u64 size;
};

Описание полей структуры elcore50_buf:

dmabuf_fd

Файловый дескриптор dma-buf, выделенный через ELCORE50_IOC_CREATE_BUFFER или аналогичный ioctl() других драйверов.

mapper_fd

Файловый дескриптор внутренней структуры буфера в драйвере.

Перед вызовом ioctl() необходимо заполнить поле dmabuf_fd.

В случае успеха вызов ioctl() возвращает нулевое значение, поле mapper_fd содержит файловый дескриптор внутренней структуры буфера в драйвере.

ELCORE50_IOC_SYNC_BUFFER

Синхронизация данных буфера.

enum elcore50_buf_sync_dir {
  ELCORE50_BUF_SYNC_DIR_TO_CPU,
  ELCORE50_BUF_SYNC_DIR_TO_DEVICE,
  ELCORE50_BUF_SYNC_BIDIRECTIONAL
};

struct elcore50_buf_sync {
  /* in */
  int mapper_fd;
  size_t offset;
  size_t size;
  enum elcore50_buf_sync_dir dir;
};

Описание полей структуры elcore50_buf_sync:

mapper_fd

Файловый дескриптор внутренней структуры буфера в драйвере, полученный после вызова ioctl() ELCORE50_IOC_CREATE_MAPPER.

offset

Смещение относильно начала буфера, начиная с которого выполняется синхронизация.

size

Размер синхронизируемой области памяти в байтах.

dir

Направление синхронизации. Допустимые значения:

  • ELCORE50_BUF_SYNC_DIR_TO_CPU — инвалидация кэш-памяти CPU.

  • ELCORE50_BUF_SYNC_DIR_TO_DEVICE — сброс кэш-памяти CPU.

  • ELCORE50_BUF_SYNC_BIDIRECTIONAL — сброс и инвалидация кэш-памяти CPU.

Перед вызовом ioctl() необходимо заполнить все поля структуры elcore50_buf_sync.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_CREATE_JOB

Создание нового задания.

#define ELCORE50_MAX_ELF_SECTIONS 64

enum elcore50_job_elf_section_type {
  ELCORE50_ELF_SECTION_CODE = 0,
  ELCORE50_ELF_SECTION_DATA = 1,
  ELCORE50_ELF_SECTION_DATA_CONST = 2
};

struct elcore50_job_elf_section {
  enum elcore50_job_elf_section_type type;
  int mapper_fd;
  int size;
  int virtual_address;
};

struct elcore50_job {
  /*in*/
  uint32_t num_elf_sections;
  uint32_t stack_virtual_address;
  struct elcore50_job_elf_section elf_sections[ELCORE50_MAX_ELF_SECTIONS];
  int hugepages;

  /*out*/
  int job_fd;
};


int ioctl(int fd, ELCORE50_IOC_CREATE_JOB, struct elcore50_job *job);

Описание полей структуры elcore50_job_elf_section:

type

Тип секции. Код, данные или константные данные.

mapper_fd

Файловый дескриптор секции, полученный с помощью ioctl() ELCORE50_IOC_CREATE_MAPPER.

size

Размер секции в байтах.

virtual_address

Адрес, по которому должна располагаться секция в адресном пространстве DSP.

Описание полей структуры elcore50_job:

num_elf_sections

Количество элементов массива elf_sections.

elf_sections

Массив, содержащий информацию о всех секциях ELF-файла.

stack_virtual_address

Адрес дна стека из ELF-файла. Адрес должен принадлежать одной из секций elf_sections.

hugepages

Использование huge page при настройке виртуального адресного пространства задачи. Допустимые значения:

  • 0 — huge page не используются. Виртуальное адресное пространство настраивается с использованием 4К страниц.

  • 1 — huge page используются. Виртуальное адресное пространство настраивается с использованием 4К, 2М и 1G страниц.

job_fd

Возвращемый дескриптор файла для данного задания.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job из раздела «in».

Системный вызов:

  • сохраняет ссылки на mapper’ы образа ELF-файла, созданные с помощью ioctl ELCORE50_IOC_CREATE_MAPPER.

  • создает карту виртуальной памяти.

  • создает фиктивные MMU-таблицы для внутренних диапазонов виртуальных адресов. Поскольку на этапе создания задания неизвестно на каком DSP-ядре будет оно выполняться, выкалываются диапазоны виртуальных адресов для всех DSP-ядер.

  • создает MMU-таблицы для ELF-секций. Не все буферы будут использовать 2M/1G страницы. Для использования 2M/1G страниц необходимо, чтобы буфер имел непрерывный физический блок >2M/1G с 2M/1G виртуальным выравниванием.

    Примечание

    Драйвер не гарантирует использование 2M/1G страниц.

  • создаёт файловый дескриптор экземпляра задания.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_ENQUEUE_JOB

Постановка экземпляра задания в глобальную очередь.

#define ELCORE50_MAX_JOB_AGRS 32

enum elcore50_job_arg_type {
  ELCORE50_TYPE_GLOBAL_MEMORY = 0,
  ELCORE50_TYPE_NC_GLOBAL_MEMORY = 1,
  ELCORE50_TYPE_LOCAL_MEMORY = 2,
  ELCORE50_TYPE_BASIC = 3,
  ELCORE50_TYPE_DMA_MEMORY = 4
};

struct elcore50_job_arg {
  enum elcore50_job_arg_type type;
  union {
    struct {
      int mapper_fd;
    } global_memory;
    struct {
      __u32 size;
    } local_memory;
    struct {
      __u32 size;
      __u64 p;
    } basic;
    struct {
      int mapper_fd;
    } dma_memory;
  };
};

struct elcore50_job_instance {
  /*in*/
  int job_fd;
  uint32_t argc;
  struct elcore50_job_arg args[ELCORE50_MAX_JOB_ARGS];
  uint32_t entry_point_virtual_address;
  uint32_t launcher_virtual_address;
  char name[255];
  int debug_enable;

  /*out*/
  int job_instance_fd;
};

int ioctl(int fd, ELCORE50_IOC_ENQUEUE_JOB, struct elcore50_job_instance *job_instance);

Описание полей структуры elcore50_job_arg:

type

Тип аргумента, передаваемого заданию.

job_arg

Значение аргумента, в зависимости от типа:

global_memory

Указатель и размер глобальной памяти. В дальнейшем возможно расширение: например, могут передаваться права доступа. Глобальной памятью является обычная системная память. Выделение и подготовка областей памяти осуществляется с помощью ioctl() ELCORE50_IOC_CREATE_BUFFER, ELCORE50_IOC_CREATE_MAPPER.

local_memory

Размер необходимой локальной памяти. Локальная память — это память XYRAM. Её нельзя отобразить в пользовательский процесс. Тем не менее, при запуске задания можно указать в качестве аргумента указатель на внутреннюю память определённого объёма, причём объём динамически задаёт пользовательский процесс. Эта память никак не инициализируется и может использоваться заданием для оверлейной обработки данных. Если в списке аргументов присутствует несколько аргументов типа ELCORE50_TYPE_LOCAL_MEMORY, то они будут располагаться во внутренней памяти друг за другом и если общий объём затребованной локальной памяти больше реального объёма, системный вызов вернёт ошибку.

basic

Размер аргумента и указатель на его значение. Например, если задание принимает в качестве аргумента cl_int, то size = sizeof(cl_int), а указатель будет представлять из себя указатель на целое число. Аргументы этого типа интерпретируются как передаваемые по значению и копируются на стек или в регистры в соответствии с документом «Компилятор С/С++ Clang для DSP Elcore50 (32-х битный режим). Соглашение о вызовах». Кроме того, эти аргументы должны быть скопированы драйвером во время системного вызова и должны храниться в элементе очереди заданий, чтобы изменение в пользовательском процессе соответствующей памяти (если задание долго находилось в очереди) не повлекло изменений значений аргументов.

dma_memory

Указатель и размер глобальной памяти. Аналогичен типу global_memory за исключением того, что выделяемый адрес имеет размер 48 бит, вследствие чего обращение по этому адресу возможно через VDMA и невозможно через DSP.

Описание полей структуры elcore50_job_instance:

job_fd

Файловый дескриптор задания, полученный с помощью ioctl() ELCORE50_IOC_CREATE_JOB.

argc

Количество аргументов в задании.

args

Аргументы задания.

entry_point_virtual_address

Адрес функции, которую необходимо выполнить. Задаётся в адресном пространстве DSP.

launcher_virtual_address

Адрес функции elcorecl_job_launcher(). Задаётся в адресном пространстве DSP. Если необходимо запустить задание без функции elcorecl_job_launcher(), записать в это поле ноль.

name

Название экземпляра задания.

debug_enable Флаг отладки. Допустимые значения:

  • 0 — Режим отладки выключен.

  • 1 — Режим отладки включен. Выполнение экземпляра задания будет остановлено на

    первой инструкции для дальнейшей отладки в соответствии с Отладка заданий DSP.

job_instance_fd

Возвращемый дескриптор файла для данного экземпляра задания.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job из раздела «in».

Системный вызов:

  • получает доступ к памяти пользовательского процесса (get_user_pages()) для аргументов задания, имеющих тип ELCORE50_TYPE_GLOBAL_MEMORY, ELCORE50_TYPE_NC_GLOBAL_MEMORY и ELCORE50_TYPE_DMA_MEMORY.;

  • формирует стек в соответствии с документом «Компилятор С/С++ Clang для DSP Elcore50 (32-х битный режим). Соглашение о вызовах»:

    • аргументы типа ELCORE50_TYPE_BASIC интерпретируются как передаваемые по значению;

    • аргументы типа ELCORE50_TYPE_GLOBAL_MEMORY и ELCORE50_TYPE_NC_GLOBAL_MEMORY интерпретируются как указатели соответственно в кэшируемом и некэшируемом виртуальном адресном пространстве DSP, направленные на соответствующую переданную пользователем область памяти;

    • аргументы типа ELCORE50_TYPE_DMA_MEMORY аналогичны аргументам типа ELCORE50_TYPE_GLOBAL_MEMORY за исключением того, что виртуальный адрес имеет размер 48 бит и предназначен для работы с VDMA;

    • аргументы типа ELCORE50_TYPE_LOCAL_MEMORY интерпретируются как указатели в виртуальном адресном пространстве DSP, направленные на соответствующую внутреннюю память.

  • рассчитывает размер L2-кэша, исходя из размера затребованной аргументами типа ELCORE50_TYPE_LOCAL_MEMORY локальной памяти. Используется максимально возможный размер L2-кэша для данной задачи.

  • создаёт файловый дескриптор экземпляра задания.

Все промежуточные данные, а именно: образ стека и значения аргументов, передаваемые через регистры; pinned страницы памяти пользовательского процесса (см. описание get_user_pages()); созданный файловый дескриптор экземпляра задания; — сохраняются как элемент очереди заданий и этот элемент ставится в конец очереди.

После этого системный вызов возвращает управление пользовательскому процессу.

Когда очередь доходит до некоторого экземпляра задания, драйвер:

  • создает MMU-таблицы для аргументов. Не все буферы будут использовать 2M/1G страницы. Для использования 2M/1G страниц необходимо, чтобы буфер имел непрерывный физический блок >2M/1G с 2M/1G виртуальным выравниванием.

    Примечание

    Драйвер не гарантирует использование 2M/1G страниц.

  • настраивает регистры MMU.

  • устанавливает указатель стека на вершину стека в данном образе;

  • настраивает размер L2-кэша;

  • копирует секцию PRAM в память PRAM;

  • копирует секцию XYRAM в память XYRAM;

  • если значение launcher_virtual_address не равно нулю, то в регистр PC записывает это значение, а регистр R8 устанавливает в значение, равное entry_point_virtual_address и запускает DSP-ядро. Если значение launcher_virtual_address равно нулю, то в регистр PC записывается значение, равное entry_point_virtual_address.

  • если значение debug_enable равно нулю, то запускает DSP-ядро. Если значение debug_enable не равно нулю, то переводит задание в состояние ELCORE50_JOB_STATUS_INTERRUPTED и не запускает DSP-ядро до выполнения ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE, ELCORE50_IOC_DBG_STEP или завершения отладки путем закрытия отладочного файлового дескриптора, полученного путем выполнения ELCORE50_IOC_DBG_JOB_ATTACH.

Пользовательский процесс может вызвать функцию poll() c параметром elcore50_job_instance.job_instance_fd при необходимости дождаться завершения выполнения задания.

Задание во время выполнения не запрашивает у драйвера дополнительной памяти и работает только со стеком, данными переданными через аргументы типа ELCORE50_TYPE_GLOBAL_MEMORY и ELCORE50_TYPE_LOCAL_MEMORY, а также с секциями ELF-файла.

ELCORE50_IOC_GET_JOB_STATUS

Получение текущего состояния задания.

enum elcore50_job_instance_state {
  ELCORE50_JOB_STATUS_ENQUEUED = 0,
  ELCORE50_JOB_STATUS_RUN = 1,
  ELCORE50_JOB_STATUS_INTERRUPTED = 2,
  ELCORE50_JOB_STATUS_SYSCALL = 3,
  ELCORE50_JOB_STATUS_DONE = 4
};

enum elcore50_job_instance_error {
  ELCORE50_JOB_STATUS_SUCCESS = 0,
  ELCORE50_JOB_STATUS_ERROR = 1
};

struct elcore50_job_instance_status
{
  /*in*/
  int job_instance_fd;

  /*out*/
  enum elcore50_job_instance_state state;
  enum elcore50_job_instance_error error;
};

int ioctl(int fd, ELCORE50_IOC_GET_JOB_STATUS, struct elcore50_job_instance_status
  *job_status);

Описание полей структуры elcore50_job_instance_status:

job_instance_fd

Файловый дескриптор, возвращённый функцией ELCORE50_IOC_ENQUEUE_JOB.

state

Состояние задачи. Допустимые значения:

  • ELCORE50_JOB_STATUS_ENQUEUED — задание помещено в очередь готовых к выполнению задач.

  • ELCORE50_JOB_STATUS_RUN — задание находится на этапе выполнения.

  • ELCORE50_JOB_STATUS_INTERRUPTED — задание остановлено отладчиком.

  • ELCORE50_JOB_STATUS_SYSCALL — задание приостановлено. Выполняется системный вызов.

  • ELCORE50_JOB_STATUS_DONE — задание завершено.

error

Актуально в состоянии ELCORE50_JOB_STATUS_DONE. Содержит ошибку, если задание не смогло выполниться успешно. В противном случае ELCORE50_JOB_STATUS_SUCCESS.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job_instance_status из раздела «in».

Описание IOCTL для устройств /dev/elcoreX

Перед выполнением ioctl данной группы необходимо открыть файл устройства /dev/elcoreX, где X - номер DSP-ядра (/dev/elcore0, dev/elcore1 и т.д.) с помощью системного вызова open().

ELCIOC_GET_CAPS

Получение информации о драйвере.

struct elcore_caps {
  /*out*/
  char drvname[32];
  __u32 hw_id;
};

int ioctl(int fd, ELCIOC_GET_CAPS, struct elcore_caps *caps);

Описание полей структуры elcore_caps:

drvname

Название драйвера.

hw_id

Ревизия ядра из регистра IDR.

В случае успеха вызов ioctl() возвращает нулевое значение, при этом значение поля elcore_caps.drvname равно значению elcore50, а значение поля elcore_caps.hw_id — ревизии и подревизии DSP.

ELCORE50_IOC_CREATE_JOB

Аналогичен IOC_CREATE_JOB для файла устройства /dev/elcore.

Данная реализация оставлена для обратной совместимости со старым ПО.

ELCORE50_IOC_ENQUEUE_JOB

Постановка экземпляра задания в локальную очередь ядра.

int ioctl(int fd, ELCORE50_IOC_ENQUEUE_JOB, struct elcore50_job_instance *job_instance);

Описание ioctl аналогично ELCORE50_IOC_ENQUEUE_JOB для файла устройства /dev/elcore.

ELCORE50_IOC_GET_JOB_STATUS

Аналогичен ELCORE50_IOC_GET_JOB_STATUS для файла устройства /dev/elcore.

Данная реализация оставлена для обратной совместимости со старым ПО.

ELCORE50_IOC_GET_JOB_COUNT

Получение числа доступных экземпляров заданий, находящихся либо в локальной очереди, либо на исполнении.

int ioctl(int fd, ELCORE50_IOC_GET_JOB_COUNT, __u32 *count);

Предупреждение

Число экземпляров заданий count возвращается без учета экземпляров заданий, находящихся в глобальной очереди, для которых еще не назначено DSP-ядро.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_GET_JOB_LIST

Получения списка доступных экземпляров заданий, находящихся либо в локальной очереди, либо на исполнении.

struct elcore50_job_instance_info {
  long id;
  int pid;
  char name[255];
};

struct elcore50_job_instance_list {
  /* in */
  __u32 job_instance_count;
  struct elcore50_job_instance_list *list;

  /* out */
  __u32 job_instance_ret;
};

int ioctl(int fd, ELCORE50_IOC_GET_JOB_LIST, struct elcore50_job_instance_list *list);

Описание полей структуры elcore50_job_instance_info:

id

Уникальный идентификатор экземпляра задания.

pid

Идентификатор процесса, создавшего экземпляр задания.

name

Название экземпляра задания.

Описание полей структуры elcore50_job_instance_list:

job_instance_count

Число запрашиваемых экземпляров задания, полученное с помощью ioctl() ELCORE50_IOC_GET_JOB_COUNT.

info

Описание экземпляров задания.

job_instance_ret

Число описанных экземпляров заданий в поле elcore50_job_instance_list.info.

Поля elcore50_job_instance_list.job_instance_count и elcore50_job_instance_list.job_instance_ret могут отличаться в случае, если между вызовами ELCORE50_IOC_GET_JOB_COUNT и ELCORE50_IOC_GET_JOB_LIST добавлялись или удалялись некоторые экземпляры заданий из очереди.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job_instance_list из раздела «in»: определить elcore50_job_instance_list.job_instance_count с помощью ioctl() ELCORE50_IOC_GET_JOB_COUNT и выделить необходимую память под elcore50_job_instance_list.info. В случае успеха вызов ioctl() возвращает нулевое значение.

Предупреждение

Список экземпляров заданий заполняется без учета экземпляров заданий, находящихся в глобальной очереди, для которых еще не назначено DSP-ядро.

ELCORE50_IOC_DBG_JOB_ATTACH

Подключение к экземпляру задания для отладки.

struct elcore50_job_instance_dbg {
  /* in */
  long job_instance_id;

  /* out */
  int job_instance_dbg_fd;
};

int ioctl(int fd, ELCORE50_IOC_DBG_JOB_ATTACH, struct elcore50_job_instance_dbg *inst_dbg);

Описание полей структуры elcore50_job_instance_dbg:

job_instance_id

Уникальный идентификатор экземпляра задания, полученный с помощью ioctl() ELCORE50_IOC_GET_JOB_LIST.

job_instance_dbg_fd

Файловый дескриптор отладки.

При вызове ioctl() выполнение задания прерывается и переводится в состояние отладки ELCORE50_JOB_STATUS_INTERRUPTED. Выполнение задания может быть продолжено после выполнения ioctl() ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE, ELCORE50_IOC_DBG_STEP или завершения отладки путем закрытия файлового дескриптора :c:member:job_instance_dbg_fd.

Если экземпляр задания был поставлен в очередь с флагом :c:member:debug_enable, вызов ioctl() будет завершен с ошибкой.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_job_instance_info из раздела «in». В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_GET_CORE_IDX

Получение информации о ядре.

struct elcore50_device_info {
  /* out */
  int nclusters;
  int cluster_id;
  int cluster_cap;
  int core_in_cluster_id;
};

int ioctl(int fd, ELCORE50_IOC_GET_CORE_IDX, struct elcore50_device_info
          *dev_info);

Описание полей структуры elcore50_device_info:

nclusters

Общее количество кластеров.

cluster_id

Порядковый номер кластера.

cluster_cap

Количество ядер в одном кластере.

core_in_cluster_id

Порядковый номер ядра в кластере.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_CREATE_BUFFER

Аналогичен IOC_CREATE_BUFFER для файла устройства /dev/elcore.

Данная реализация оставлена для обратной совместимости со старым ПО.

ELCORE50_IOC_CREATE_MAPPER

Аналогичен IOC_CREATE_MAPPER для файла устройства /dev/elcore.

Данная реализация оставлена для обратной совместимости со старым ПО.

ELCORE50_IOC_SYNC_BUFFER

Аналогичен IOC_SYNC_BUFFER для файла устройства /dev/elcore.

Данная реализация оставлена для обратной совместимости со старым ПО.

Описание отладочных IOCTL

Нижеописанные ioctl применяются к отладочным файловым дескрипторам заданий, полученных с помощью ELCORE50_IOC_DBG_JOB_ATTACH или ELCORE50_IOC_ENQUEUE_JOB с установленным флагом debug_enable структуры elcore50_job_instance.

ELCORE50_IOC_DBG_MEMORY_[READ|WRITE]

Чтение/запись памяти.

struct elcore50_dbg_mem {
 /* in */
  __u64 vaddr;
  size_t size;

 /* in-out */
  void *data;
};

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_MEMORY_READ, struct elcore50_dbg_mem *mem);
int ioctl(int dbg_fd, ELCORE50_IOC_DBG_MEMORY_WRITE, struct elcore50_dbg_mem *mem);

Описание полей структуры elcore50_dbg_mem:

vaddr

Виртуальной адрес читаемого/записываемого блока памяти (или смещение регистра).

size

Размер блока памяти.

data

Указатель на блок памяти в пользовательском пространстве, содержащего данные DSP.

Предупреждение

Для записи/чтения регистров DSP необходимо использовать ioctl() ELCORE50_IOC_DBG_REGISTER_[READ|WRITE].

Чтение и запись памяти возможны только для PRAM, XYRAM и отмапированных через VMMU областей.

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_dbg_mem из раздела «in», а также выделить память под поле data. При вызове ioctl() драйвер:

  1. отключает все префетчи;

  2. сбрасывает кэши DSP;

  3. выталкивает из конвейера DSP все инструкции;

  4. считывает/записывает данные;

  5. сбрасывает кэши CPU;

  6. восстанавливает префетчи.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_REGISTER_[READ|WRITE]

Чтение/запись регистров DSP.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_REGISTER_READ, struct elcore50_dbg_mem *mem);
int ioctl(int dbg_fd, ELCORE50_IOC_DBG_REGISTER_WRITE, struct elcore50_dbg_mem *mem);

Перед вызовом ioctl() необходимо заполнить поля структуры elcore50_dbg_mem из раздела «in», а также выделить память под поле data.

Если драйвер загружен с параметром ядра dbg_registers_simple (см. Параметры модуля ядра), равным единице, доступ возможен только к регистровому и векторному файлам DSP-ядра, а также регистру PC. В противном случае, доступ возможен к любым программно-доступным регистрам DSP-ядра.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_HW_BREAKPOINT_SET

Установка аппаратной точки останова.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_HW_BREAKPOINT_SET, __u32 *vaddr);

Для установки аппаратной точки останова передать виртуальный адрес точки останова в качестве параметра vaddr. Драйвер записывает адрес точки останова в один из свободных регистров dbSAR. В случае, если все регистры заняты, возвращается ошибка -EBUSY. В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_HW_BREAKPOINT_CLEAR

Сброс аппаратной точки останова.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_HW_BREAKPOINT_CLEAR, __u32 *vaddr);

Для сброса аппаратной точки останова, ранее установленной с помощью ioctl() ELCORE50_IOC_DBG_HW_BREAKPOINT_SET необходимо передать ее виртуальный адрес в качестве параметра vaddr. В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_JOB_INSTANCE_INTERRUPT

Принудительный останов исполнения.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_JOB_INSTANCE_INTERRUPT, NULL);

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_GET_STOP_REASON

Запрос причины останова.

enum elcore50_stop_reason {
  ELCORE50_STOP_REASON_HW_BREAKPOINT,
  ELCORE50_STOP_REASON_SW_BREAKPOINT,
  ELCORE50_STOP_REASON_EXTERNAL_REQUEST,
  ELCORE50_STOP_REASON_STEP,
  ELCORE50_STOP_REASON_DBG_INTERRUPT,
  ELCORE50_STOP_REASON_APP_EXCEPTION
};

struct elcore50_dbg_stop_reason {
  enum elcore50_stop_reason reason;
};

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_GET_STOP_REASON, elcore50_dbg_stop_reason *state);

Описание полей структуры elcore50_dbg_stop_reason:

reason

Причина остановка задания. Допустимые значения:

  • ELCORE50_STOP_REASON_HW_BREAKPOINT — задание остановлено по причине срабатывания аппаратной точки останова.

  • ELCORE50_STOP_REASON_SW_BREAKPOINT — задание остановлено по причине срабатывания программной точки останова.

  • ELCORE50_STOP_REASON_EXTERNAL_REQUEST — задание остановлено по внешнему запросу.

  • ELCORE50_STOP_REASON_STEP — задание остановлено по причине завершения пошагового выполнения после ioctl() ELCORE50_IOC_DBG_STEP.

  • ELCORE50_STOP_REASON_DBG_INTERRUPT — задание остановлено по причине вызова ioctl() ELCORE50_IOC_DBG_JOB_INSTANCE_INTERRUPT.

  • ELCORE50_STOP_REASON_APP_EXCEPTION — задание остановлено по причине завершения работы.

В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_STEP

Выполнение по шагам.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_STEP, __u32 *steps);

Пошаговое выполнение задания возможно для ранее остановленного задания. Для выполнения N инструкций задания необходимо передать N в качестве параметра steps. В случае успеха вызов ioctl() возвращает нулевое значение.

ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE

Продолжить выполнение задания.

int ioctl(int dbg_fd, ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE, NULL);

В случае успеха вызов ioctl() возвращает нулевое значение.

Локальная и глобальная очереди задач

В драйвере elcore50 реализовано два типа очередей: локальная и глобальная.

Локальная (или частная) очередь существует отдельно для каждого из DSP-ядер и содержит задания, которые будут выполняться на соответствующем DSP-ядре, когда до них дойдет очередь.

Постановка задания в локальную очередь осуществляется путем выполнения ioctl() ELCORE50_IOC_ENQUEUE_JOB применительно к устройству /dev/elcoreX. Подробнее см. описание IOCTL.

Глобальная (или общая) очередь содержит задания, для которых в момент постановки в очередь неизвестно DSP-ядро, где это задание будет выполняться. Назначение DSP-ядра на задание осуществляется планировщиком.

Постановка задания в глобальную очередь осуществляется путем выполнения ioctl() ELCORE50_IOC_ENQUEUE_JOB применительно к устройству /dev/elcore. Подробнее см. описание IOCTL.

Планировщик — поток ядра Linux, который создается отдельно для каждого из существующих DSP-ядер при загрузке драйвера elcore50. При появлении в очередях новых заданий, планировщик:

  1. Просматривает глобальную очередь заданий. При наличии задания, извлекает из очереди, назначает на выполнение соответствующее DSP-ядро и переходит к п. 4.

  2. Просматривает локальную очередь заданий. При наличии задания, извлекает из очереди и переходит к п. 4.

  3. Если на момент осмотра задания отсутствуют как в глобальной, так и локальной очередях, то есть задания были захвачены планировщиками других DSP-ядер, текущий планировщик переходит в ожидание.

  4. Помещает задание в список запущенных и запускает на соответствующем DSP-ядре.

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

Управление таймаутом заданий

В драйвере elcore50 реализовано отображение значения таймаута заданий в файл /sys/kernel/debug/elcore50/irq-timeout-msec через debugfs. Установка выполняется записью значения таймаута (в мс) в файл. По умолчанию таймаут настроен на значение 0 мс. Это означает, что все последующие задания будут запущены без таймаута.

Мониторинг производительности

Драйвер elcore50 публикует метрики производительности в директорию /proc/elcore50 виртуальной файловой системы procfs:

/proc/elcore50/info

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

/proc/elcore50/usage

Файл содержит информацию о загрузке (в процентах) для каждого DSP. Файл доступен только на чтение.

Значение загрузки DSP рассчитывается как отношение общего времени работы DSP за период таймера к величине периода. Значение периода таймера установлено равным 500 мс.

Управление печатью состояния заданий

В драйвере elcore50 реализована возможность печати дампа состояния заданий в кольцевой буфер ядра Linux. Управление печатью осуществляется через запись в файл /sys/kernel/debug/elcore50/job-status-dump-enable, расположенного в debugfs, единицы для включения печати дампа и нуля для его отключения. По умолчанию печать дампов отключена.

Управление сбросами DSP

В драйвере elcore50 реализовано 2 вида сброса:

  1. Сброс Quelcore: снимается при загрузке модуля ядра, устанавливается при выгрузке модуля ядра.

  2. Сброс DSP: после завершения задания.

Обработка системных вызовов DSP

Поддержка системных вызовов DSP осуществляется путем реализации их в пользовательском процессе на CPU, в качестве которого выступает библиотека libelcorecl.

#define SC_GETTIMEOFDAY       1
#define SC_WRITE              2
#define SC_READ               3
#define SC_OPEN               4
#define SC_CLOSE              5
#define SC_FSTAT              6
#define SC_LSEEK              7
#define SC_ISATTY             8
#define SC_CHDIR              9
#define SC_STAT               10
#define SC_TIMES              11
#define SC_LINK               12
#define SC_UNLINK             13

enum elcore50_message_type {
  ELCORE50_MESSAGE_EMPTY = 0,
  ELCORE50_MESSAGE_SYSCALL_REPLY = 1,
  ELCORE50_MESSAGE_SYSCALL = 2,
};

struct elcore50_message {
  enum elcore50_message_type type;
  int num;
  __u64 arg0;
  __u64 arg1;
  __u64 arg2;
  __s64 retval;
};

Описание полей структуры elcore50_message:

type

Тип сообщения. Возможные значения:

  • ELCORE50_MESSAGE_EMPTY — пустое сообщение. Выставляется драйвером elcore50.

  • ELCORE50_MESSAGE_SYSCALL_REPLY — подтверждение. Выставляется пользовательским процессом после окончания обработки системного вызова.

  • ELCORE50_MESSAGE_SYSCALL — системный вызов. Используется драйвером elcore50.

num

Номер системного вызова. Возможные значения:

  • SC_GETTIMEOFDAYgettimeofday(2).

  • SC_WRITEwrite(2).

  • SC_READread(2).

  • SC_OPENopen(2).

  • SC_CLOSEclose(2).

  • SC_FSTATfstat(2).

  • SC_LSEEKlseek(2).

  • SC_ISATTYisatty(2).

  • SC_CHDIRchdir(2).

  • SC_STATstat(2).

  • SC_TIMEStimes(2).

  • SC_LINKlink(2).

  • SC_UNLINKunlink(2).

  • SC_PROFILprofil(2).

  • SC_GET_ENV — вспомогательный системный вызов для получения списка переменных окружений CPU-процесса, разделенных символами \0. Системный вызов имеет сигнатуру int _syscall_get_env_array(char *, uint32_t *).

  • SC_GET_KERNELNAME — вспомогательный системный вызов для получения имени вызываемой функции для данного экземпляра задания. Системный вызов имеет сигнатуру int _syscall_get_kernel_name(char *).

arg0

Первый аргумент системного вызова.

arg1

Второй аргумент системного вызова.

arg2

Третий аргумент системного вызова.

retval

Возвращаемое значение системного вызова.

При вызове на DSP системного вызова драйвер посылает событие в пользовательское приложение на CPU через poll() и выставлением статуса задания в ELCORE50_JOB_STATUS_SYSCALL. Пользовательское приложение читает сообщение elcore50_message из файла задачи, используя системный вызов read() применительно к файловому дескриптору задачи, полученного после вызова ELCORE50_IOC_ENQUEUE_JOB. В зависимости от значения num, пользовательское приложение выполняет соответствующий системный вызов, изменяет поле retval, тип сообщения на ELCORE50_MESSAGE_SYSCALL_REPLY и записывает сообщение elcore50_message в файл задачи, используя системный вызов write(). Ниже приведена диаграмма последовательности, которая описывает механизм взаимодействия драйвера elcore50 и пользовательского приложения при возникновении прерывания от системного вызова DSP.

box
    participant "DSP" as DSP
end box

box "elcore50 driver"
    participant "elcore50_irq" as IrqHandler
    participant "elcore50_job_run" as JobRunner
    participant "syscall_handler" as SysHandler
    participant "elcore50_job_read" as job_read
    participant "elcore50_job_write" as job_write
end box

box
    participant "User application" as UserApp
end box

activate DSP
DSP -> IrqHandler: Исключение
deactivate DSP
activate IrqHandler
IrqHandler -> JobRunner: Отправка события
deactivate IrqHandler
activate JobRunner
JobRunner -> SysHandler: Отправка структуры задания
deactivate JobRunner
activate SysHandler
SysHandler -> SysHandler: Заполнение сообщения elcore50_message
SysHandler -> UserApp: Отправка события
deactivate SysHandler
activate UserApp
UserApp -> job_read: Чтение сообщения elcore50_message
activate job_read
UserApp <-- job_read: Отправка сообщения
deactivate job_read
UserApp -> UserApp : Обработка системного вызова,\nЗапись кода возврата,\nУстановка типа сообщения ELCORE50_MESSAGE_SYSCALL_REPLY
UserApp -> job_write: Запись сообщения elcore50_message
activate job_write
job_write -> SysHandler: Отправка события
activate SysHandler
UserApp <-- job_write: Отправка подтвержения записи
deactivate job_write
UserApp -> UserApp: Ожидание следующего события
deactivate UserApp
SysHandler -> DSP: Запуск DSP
activate DSP
deactivate SysHandler

Обработка системных вызовов DSP в elcore50

Профилирование заданий DSP

Профилирование заданий DSP реализована на базе gprof (подробнее см. The GNU Profiler).

Драйвер elcore50 выполняет сэмплирование выполняемого задания DSP. Период сэмплирования определяется параметром timer_period, описанным в разделе Параметры модуля ядра.

Включение/завершение сэмплирования осуществляется в обработчике системного вызова SC_PROFIL, аргументами которого являются буфер сэмплов samples, его размер size, минимальный виртуальный адрес, по которому может находиться код offset и масштаб scale.

Во время сэмплирования драйвер elcore50 считывает текущее значение регистра DSP PC. Исходя из полученного значение рассчитывается индекс элемента массива samples, к которому затем прибавляется единица. Индекс рассчитывает по следующей формуле:

\[idx = (((PC - offset) / 2) * scale) / 65536\]

Профилирование драйвера

Профилирование драйвера elcore50 основано на механизме ядра ftrace и выполняется с использованием утилит elcore-profile.py и ftrace-parser. Описание утилит приведено в документе Профилирование.

Результатом профилирования драйвера являются:

  • Общее время выполнения приложения, время выполнения DSP;

  • Доля драйвера elcore50 в общем времени выполнения приложения;

  • Накладные расходы, связанные с обработкой прерываний;

  • Накладные расходы, связанные с обработкой системных вызовов DSP;

  • Накладные расходы на создание и освобождение буферов;

  • Накладные расходы на создание MMU-таблиц для буферов;

  • Граф вызовов функций ядра, отсортированный по убыванию времени выполнения, включающий события.

Драйвер реализует следующие события (tracepoints):

elcore50_buf_create и elcore50_buf_release

Данные события соответствуют функциям создания буфера с помощью ioctl() ELCORE50_IOC_CREATE_BUFFER, а также освобождения через close(fd), где fd соответствует файловому дескриптору, возвращаемого ioctl() ELCORE50_IOC_CREATE_BUFFER. При срабатывании выводится type и size структуры elcore50_buf.

elcore50_syscall

Данное событие срабатывает при обработке драйвером системных вызовов DSP. При срабатывании выводится имя системного вызова, а также размеры аргументов.

elcore50_mmu_map

Данное событие срабатывает при настройке MMU-таблиц для буферов и ELF-секций. При срабатывании выводится назначенный виртуальный адрес DSP, а также размер буфера.

elcore50_uptime

Данное событие срабатывает при завершении задания.

При срабатывании выводится время выполнения задания в тактах DSP.

Управление энергопотреблением DSP

С целью снижения энергопотребления включение частоты DSP происходит после открытия файла устройства /dev/elcoreX, а выключение — после закрытия устройства. Включение/выключение частоты DSP осуществляется с использованием clock framework.

Драйвер elcore50 реализует возможность динамического изменения частоты DSP через sysfs с помощью devfreq.