Драйвер elcore50
Описание
Драйвер elcore50 создаёт файл устройства /dev/elcore
, а также по одному
файлу устройства на каждое DSP-ядро: /dev/elcore0
, /dev/elcore1
и т. д.
Взаимодействие с устройствами осуществляется через
стандартные системные вызовы: open()
, ioctl()
и close()
.
Интерфейс драйвера elcore50 предоставляет следующую функциональность:
Отображение областей памяти пользовательского процесса в адресное пространство DSP.
Создание нового задания.
Постановка заданий в локальную и глобальную очереди на выполнение на DSP.
Уведомление о готовности заданий.
Отслеживание текущего состояния задания.
Обеспечение согласованности данных между CPU и DSP для заданной области памяти.
Поддержка huge pages.
Поддержка экспорта и импорта dma-buf.
Отладка заданий.
Драйвер elcore50 отвечает за:
Запуск заданий DSP.
Отображение областей памяти между адресными пространствами пользовательского процесса и DSP.
Освобождение ресурсов, в том числе отмену выполняющихся заданий, при аварийном завершении процесса, относящихся к завершённому процессу. В этом случае все результаты работы задания игнорируются.
Идентификацию конкретного устройства DSP. У каждого устройства должен быть свой уникальный идентификатор, соответствующий физическому расположению устройства на чипе.
Выделение XYRAM в DSP.
ELcore-50 имеет возможность распределять внутреннюю память на две области: одна область работает как L2-кэш, вторая — как обычная память. Эти области могут быть разных размеров. Драйвер осуществляет корректную настройку распределения между L2-кэшем и обычной внутренней памятью.
Параметры модуля ядра
Модуль ядра имеет следующие параметры:
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;
uint32_t launcher_stop_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()
, записать в это поле ноль.launcher_stop_address
Адрес инструкции, следующей за STOP в коде библиотеки elcore-runtime.
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()
драйвер:
отключает все префетчи;
сбрасывает кэши DSP;
выталкивает из конвейера DSP все инструкции;
считывает/записывает данные;
сбрасывает кэши CPU;
восстанавливает префетчи.
В случае успеха вызов 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. При появлении в очередях новых заданий, планировщик:
Просматривает глобальную очередь заданий. При наличии задания, извлекает из очереди, назначает на выполнение соответствующее DSP-ядро и переходит к п. 4.
Просматривает локальную очередь заданий. При наличии задания, извлекает из очереди и переходит к п. 4.
Если на момент осмотра задания отсутствуют как в глобальной, так и локальной очередях, то есть задания были захвачены планировщиками других DSP-ядер, текущий планировщик переходит в ожидание.
Помещает задание в список запущенных и запускает на соответствующем 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 реализована возможность печати дампа DSP-регистров в кольцевой
буфер ядра Linux для всех заданий, завершившихся с ошибкой. Управление печатью
осуществляется через файл в debugfs
/sys/kernel/debug/elcore50/reg-dump-enable
.
Включение/выключение печати выполняется записью единицы/нуля в файл. По умолчанию
печать дампов отключена.
Управление печатью состояния заданий
В драйвере elcore50 реализована возможность печати дампа состояния заданий в кольцевой
буфер ядра Linux. Управление печатью осуществляется через запись в файл
/sys/kernel/debug/elcore50/job-status-dump-enable
, расположенного в debugfs
,
единицы для включения печати дампа и нуля для его отключения.
По умолчанию печать дампов отключена.
Управление сбросами DSP
В драйвере elcore50 реализовано 2 вида сброса:
Сброс Quelcore: снимается при загрузке модуля ядра, устанавливается при выгрузке модуля ядра.
Сброс 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_GETTIMEOFDAY
—gettimeofday(2)
.SC_WRITE
—write(2)
.SC_READ
—read(2)
.SC_OPEN
—open(2)
.SC_CLOSE
—close(2)
.SC_FSTAT
—fstat(2)
.SC_LSEEK
—lseek(2)
.SC_ISATTY
—isatty(2)
.SC_CHDIR
—chdir(2)
.SC_STAT
—stat(2)
.SC_TIMES
—times(2)
.SC_LINK
—link(2)
.SC_UNLINK
—unlink(2)
.SC_PROFIL
—profil(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.
Обработка системных вызовов DSP в elcore50
Профилирование заданий DSP
Профилирование заданий DSP реализована на базе gprof (подробнее см. The GNU Profiler).
Драйвер elcore50 выполняет сэмплирование выполняемого задания DSP. Период сэмплирования
определяется параметром timer_period
, описанным в разделе Параметры модуля ядра.
Включение/завершение сэмплирования осуществляется в обработчике системного вызова SC_PROFIL
,
аргументами которого являются буфер сэмплов samples
, его размер size
, минимальный
виртуальный адрес, по которому может находиться код offset
и масштаб scale
.
Во время сэмплирования драйвер elcore50 считывает текущее значение регистра DSP PC
.
Исходя из полученного значение рассчитывается индекс элемента массива samples
, к которому затем
прибавляется единица. Индекс рассчитывает по следующей формуле:
Профилирование драйвера
Профилирование драйвера 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.