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