Драйвер 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 мс.
use_kretprobes— флаг для включения профилирования с помощью Kretprobes. См. Профилирование на базе Kretprobes.По умолчанию не задан.
kretprobes_max_active— количество параллельно обрабатываемых экземпляров вызовов одной и той же функции. См. Профилирование на базе Kretprobes.По умолчанию до 16 параллельных вызовов для каждой функции.
Параметры задаются при загрузке модуля ядра 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-файла, созданные с помощью
ioctlELCORE50_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. Содержит ошибку, если:задание не смогло выполниться успешно, в этом случае в кольцевой буфер ядра Linux будет выведено сообщение вида:
Job <jobname> failed with DQSTR: <dqstrvalue>где
jobname- имя вызываемой функции,dqstrvalue- значение регистра признаков запросов на внешние прерывания (DQSTR).При отладке ошибок может быть полезным вывод дампа регистров DSP.
после завершения задания имеется активность в VDMA-каналах.
задание не смогло выполниться успешно. В противном случае
ELCORE50_JOB_STATUS_SUCCESS.
Перед вызовом ioctl() необходимо заполнить поля структуры
elcore50_job_instance_status из раздела «in».
Описание IOCTL для устройств /dev/elcoreX
Перед выполнением ioctl данной группы необходимо открыть файл устройства /dev/elcoreX, где
X - номер DSP-ядра (/dev/elcore0, dev/elcore1 и т.д.) с помощью системного вызова
open().
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 и kretprobes.
Профилирование на базе 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.
Профилирование на базе Kretprobes
Kretprobes - это механизм ядра Linux, который позволяет отслеживать возвраты из функций.
Каждый раз, когда функция трассируется с помощью kretprobe, ядро выделяет ресурсы для
отслеживания её возврата. Количество одновременно трассируемых функции определяется
параметром kretprobes_max_active, который по умолчанию равен 16. Если количество активных
kretprobes достигает этого предела, новые трассировки не будут активированы, пока не
освободятся ресурсы (например, после возврата из ранее отслеживаемых функций).
Список активных трассировок функции доступен в debugfs по следующему пути:
/sys/kernel/debug/kprobes/list.
Метрики функции экспортируются в debugfs по следующему пути:
/sys/kernel/debug/elcore50/kretprobes.
Именование иерархии путей директории назначается слеующим образом:
kretprobes
|-- <job1> -- Наименование DSP задачи
| |-- avg_time -- Среднее время выполнения
| |-- call_times -- Количество вызовов
| |-- exec_time -- Время работы DSP когда эта задача выполнялась последний раз
| |-- max_time -- Максимальное время выполнения
| |-- min_time -- Минимальное время выполнения
| `-- total_time -- Суммарное время выполнения
|-- ...
|-- <jobN>
|-- <function1> -- Наименование функции, вызванной внутри драйвера
| |-- avg_time
| |-- call_times
| |-- max_time
| |-- min_time
| `-- total_time
|-- ...
|-- <functionM>
`-- reset -- Файл для сброса метрик. Сброс осуществляется записью '1'
Атрибуты времени представленны в наносекундах. Атрибут call_times – разы.
Включение Kretprobes для драйвера elcore50 осуществляется через параметр модуля ядра
use_kretprobes.
Просмотр метрик доступен с помощью утилиты kretprobes-parser.
Управление энергопотреблением DSP
С целью снижения энергопотребления включение частоты DSP происходит после открытия файла устройства
/dev/elcoreX, а выключение — после закрытия устройства. Включение/выключение частоты DSP
осуществляется с использованием clock framework.
Драйвер elcore50 реализует возможность динамического изменения частоты DSP через sysfs с помощью devfreq.