Драйвер elcore50

Описание

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

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

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

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

  3. Постановка задания в очередь на выполнение на DSP.

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

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

  6. Выталкивание данных из кэша во внешнюю память для заданной области памяти.

  7. Инвалидация кэша для заданной области памяти.

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

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

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

Драйвер 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 реализует стандартные для файловых дескрипторов системные вызовы со следующими особенностями:

  • poll()

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

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

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

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

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

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

#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;
  struct elcore50_job_elf_section elf_sections[ELCORE50_MAX_ELF_SECTIONS];
  int stack_fd;
  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_fd

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

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

    Примечание

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

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

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 debug_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 — Происходит подключение к экземпляру заданию для последующей отладки с экспортом файлового дескриптора debug_fd.

job_instance_fd

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

debug_fd

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

Перед вызовом 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 не равно нулю, то в регистр r8 записывается это значение, а program counter устанавливает в значение, равное entry_point_virtual_address и запускает DSP-ядро. Если значение launcher_virtual_address равно нулю, то в program counter записывается значение, равное entry_point_virtual_address.

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

Пользовательский процесс может вызвать функцию 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».

ELCORE50_IOC_GET_JOB_COUNT

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

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

В случае успеха вызов 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() возвращает нулевое значение.

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

Экспорт 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() возвращает нулевое значение.

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

Управление сбросами 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.