Драйвер 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;
char name[255];
int debug_enable;
/*out*/
int job_instance_fd;
};
int ioctl(int fd, ELCORE50_IOC_ENQUEUE_JOB, struct elcore50_job_instance *job_instance);
Описание полей структуры elcore50_job_arg
:
type
Тип аргумента, передаваемого заданию.
job_arg
Значение аргумента, в зависимости от типа:
global_memory
Указатель и размер глобальной памяти. В дальнейшем возможно расширение: например, могут передаваться права доступа. Глобальной памятью является обычная системная память. Выделение и подготовка областей памяти осуществляется с помощью
ioctl()
ELCORE50_IOC_CREATE_BUFFER, ELCORE50_IOC_CREATE_MAPPER.local_memory
Размер необходимой локальной памяти. Локальная память — это память XYRAM. Её нельзя отобразить в пользовательский процесс. Тем не менее, при запуске задания можно указать в качестве аргумента указатель на внутреннюю память определённого объёма, причём объём динамически задаёт пользовательский процесс. Эта память никак не инициализируется и может использоваться заданием для оверлейной обработки данных. Если в списке аргументов присутствует несколько аргументов типа
ELCORE50_TYPE_LOCAL_MEMORY
, то они будут располагаться во внутренней памяти друг за другом и если общий объём затребованной локальной памяти больше реального объёма, системный вызов вернёт ошибку.basic
Размер аргумента и указатель на его значение. Например, если задание принимает в качестве аргумента
cl_int
, тоsize = sizeof(cl_int)
, а указатель будет представлять из себя указатель на целое число. Аргументы этого типа интерпретируются как передаваемые по значению и копируются на стек или в регистры в соответствии с документом «Компилятор С/С++ Clang для DSP Elcore50 (32-х битный режим). Соглашение о вызовах». Кроме того, эти аргументы должны быть скопированы драйвером во время системного вызова и должны храниться в элементе очереди заданий, чтобы изменение в пользовательском процессе соответствующей памяти (если задание долго находилось в очереди) не повлекло изменений значений аргументов.dma_memory
Указатель и размер глобальной памяти. Аналогичен типу
global_memory
за исключением того, что выделяемый адрес имеет размер 48 бит, вследствие чего обращение по этому адресу возможно через VDMA и невозможно через DSP.
Описание полей структуры elcore50_job_instance
:
job_fd
Файловый дескриптор задания, полученный с помощью
ioctl()
ELCORE50_IOC_CREATE_JOB.argc
Количество аргументов в задании.
args
Аргументы задания.
entry_point_virtual_address
Адрес функции, которую необходимо выполнить. Задаётся в адресном пространстве DSP.
launcher_virtual_address
Адрес функции
elcorecl_job_launcher()
. Задаётся в адресном пространстве DSP. Если необходимо запустить задание без функцииelcorecl_job_launcher()
, записать в это поле ноль.name
Название экземпляра задания.
debug_enable
Флаг отладки. Допустимые значения:0
— Режим отладки выключен.1
— Режим отладки включен. Выполнение экземпляра задания будет остановлено напервой инструкции для дальнейшей отладки в соответствии с Отладка заданий DSP.
job_instance_fd
Возвращемый дескриптор файла для данного экземпляра задания.
Перед вызовом ioctl()
необходимо заполнить поля структуры
elcore50_job
из раздела «in».
Системный вызов:
получает доступ к памяти пользовательского процесса (
get_user_pages()
) для аргументов задания, имеющих типELCORE50_TYPE_GLOBAL_MEMORY
, ELCORE50_TYPE_NC_GLOBAL_MEMORY и ELCORE50_TYPE_DMA_MEMORY.;формирует стек в соответствии с документом «Компилятор С/С++ Clang для DSP Elcore50 (32-х битный режим). Соглашение о вызовах»:
аргументы типа
ELCORE50_TYPE_BASIC
интерпретируются как передаваемые по значению;аргументы типа
ELCORE50_TYPE_GLOBAL_MEMORY
иELCORE50_TYPE_NC_GLOBAL_MEMORY
интерпретируются как указатели соответственно в кэшируемом и некэшируемом виртуальном адресном пространстве DSP, направленные на соответствующую переданную пользователем область памяти;аргументы типа
ELCORE50_TYPE_DMA_MEMORY
аналогичны аргументам типаELCORE50_TYPE_GLOBAL_MEMORY
за исключением того, что виртуальный адрес имеет размер 48 бит и предназначен для работы с VDMA;аргументы типа
ELCORE50_TYPE_LOCAL_MEMORY
интерпретируются как указатели в виртуальном адресном пространстве DSP, направленные на соответствующую внутреннюю память.
рассчитывает размер L2-кэша, исходя из размера затребованной аргументами типа
ELCORE50_TYPE_LOCAL_MEMORY
локальной памяти. Используется максимально возможный размер L2-кэша для данной задачи.создаёт файловый дескриптор экземпляра задания.
Все промежуточные данные, а именно: образ стека и значения аргументов, передаваемые
через регистры; pinned страницы памяти пользовательского процесса (см. описание
get_user_pages()
); созданный файловый дескриптор экземпляра задания; —
сохраняются как элемент очереди заданий и этот элемент ставится в конец очереди.
После этого системный вызов возвращает управление пользовательскому процессу.
Когда очередь доходит до некоторого экземпляра задания, драйвер:
создает MMU-таблицы для аргументов. Не все буферы будут использовать 2M/1G страницы. Для использования 2M/1G страниц необходимо, чтобы буфер имел непрерывный физический блок >2M/1G с 2M/1G виртуальным выравниванием.
Примечание
Драйвер не гарантирует использование 2M/1G страниц.
настраивает регистры MMU.
устанавливает указатель стека на вершину стека в данном образе;
настраивает размер L2-кэша;
копирует секцию PRAM в память PRAM;
копирует секцию XYRAM в память XYRAM;
если значение
launcher_virtual_address
не равно нулю, то в регистр PC записывает это значение, а регистр R8 устанавливает в значение, равноеentry_point_virtual_address
и запускает DSP-ядро. Если значениеlauncher_virtual_address
равно нулю, то в регистр PC записывается значение, равноеentry_point_virtual_address
.если значение
debug_enable
равно нулю, то запускает DSP-ядро. Если значениеdebug_enable
не равно нулю, то переводит задание в состояниеELCORE50_JOB_STATUS_INTERRUPTED
и не запускает DSP-ядро до выполнения ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE, ELCORE50_IOC_DBG_STEP или завершения отладки путем закрытия отладочного файлового дескриптора, полученного путем выполнения ELCORE50_IOC_DBG_JOB_ATTACH.
Пользовательский процесс может вызвать функцию poll()
c параметром
elcore50_job_instance.job_instance_fd
при необходимости дождаться завершения
выполнения задания.
Задание во время выполнения не запрашивает у драйвера дополнительной памяти и
работает только со стеком, данными переданными через аргументы типа
ELCORE50_TYPE_GLOBAL_MEMORY
и ELCORE50_TYPE_LOCAL_MEMORY
, а
также с секциями ELF-файла.
ELCORE50_IOC_GET_JOB_STATUS
Получение текущего состояния задания.
enum elcore50_job_instance_state {
ELCORE50_JOB_STATUS_ENQUEUED = 0,
ELCORE50_JOB_STATUS_RUN = 1,
ELCORE50_JOB_STATUS_INTERRUPTED = 2,
ELCORE50_JOB_STATUS_SYSCALL = 3,
ELCORE50_JOB_STATUS_DONE = 4
};
enum elcore50_job_instance_error {
ELCORE50_JOB_STATUS_SUCCESS = 0,
ELCORE50_JOB_STATUS_ERROR = 1
};
struct elcore50_job_instance_status
{
/*in*/
int job_instance_fd;
/*out*/
enum elcore50_job_instance_state state;
enum elcore50_job_instance_error error;
};
int ioctl(int fd, ELCORE50_IOC_GET_JOB_STATUS, struct elcore50_job_instance_status
*job_status);
Описание полей структуры elcore50_job_instance_status
:
job_instance_fd
Файловый дескриптор, возвращённый функцией
ELCORE50_IOC_ENQUEUE_JOB
.state
Состояние задачи. Допустимые значения:
ELCORE50_JOB_STATUS_ENQUEUED
— задание помещено в очередь готовых к выполнению задач.ELCORE50_JOB_STATUS_RUN
— задание находится на этапе выполнения.ELCORE50_JOB_STATUS_INTERRUPTED
— задание остановлено отладчиком.ELCORE50_JOB_STATUS_SYSCALL
— задание приостановлено. Выполняется системный вызов.ELCORE50_JOB_STATUS_DONE
— задание завершено.
error
Актуально в состоянии
ELCORE50_JOB_STATUS_DONE
. Содержит ошибку, если задание не смогло выполниться успешно. В противном случаеELCORE50_JOB_STATUS_SUCCESS
.
Перед вызовом ioctl()
необходимо заполнить поля структуры
elcore50_job_instance_status
из раздела «in».
Описание IOCTL для устройств /dev/elcoreX
Перед выполнением ioctl
данной группы необходимо открыть файл устройства /dev/elcoreX
, где
X - номер DSP-ядра (/dev/elcore0
, dev/elcore1
и т.д.) с помощью системного вызова
open()
.
ELCIOC_GET_CAPS
Получение информации о драйвере.
struct elcore_caps {
/*out*/
char drvname[32];
__u32 hw_id;
};
int ioctl(int fd, ELCIOC_GET_CAPS, struct elcore_caps *caps);
Описание полей структуры elcore_caps
:
drvname
Название драйвера.
hw_id
Ревизия ядра из регистра IDR.
В случае успеха вызов ioctl()
возвращает нулевое значение, при этом
значение поля elcore_caps.drvname
равно значению elcore50
, а
значение поля elcore_caps.hw_id
— ревизии и подревизии DSP.
ELCORE50_IOC_CREATE_JOB
Аналогичен IOC_CREATE_JOB для файла устройства /dev/elcore
.
Данная реализация оставлена для обратной совместимости со старым ПО.
ELCORE50_IOC_ENQUEUE_JOB
Постановка экземпляра задания в локальную очередь ядра.
int ioctl(int fd, ELCORE50_IOC_ENQUEUE_JOB, struct elcore50_job_instance *job_instance);
Описание ioctl
аналогично ELCORE50_IOC_ENQUEUE_JOB для файла
устройства /dev/elcore
.
ELCORE50_IOC_GET_JOB_STATUS
Аналогичен ELCORE50_IOC_GET_JOB_STATUS для файла устройства
/dev/elcore
.
Данная реализация оставлена для обратной совместимости со старым ПО.
ELCORE50_IOC_GET_JOB_COUNT
Получение числа доступных экземпляров заданий, находящихся либо в локальной очереди, либо на исполнении.
int ioctl(int fd, ELCORE50_IOC_GET_JOB_COUNT, __u32 *count);
Предупреждение
Число экземпляров заданий count
возвращается без учета экземпляров заданий, находящихся в
глобальной очереди, для которых еще не назначено DSP-ядро.
В случае успеха вызов ioctl()
возвращает нулевое значение.
ELCORE50_IOC_GET_JOB_LIST
Получения списка доступных экземпляров заданий, находящихся либо в локальной очереди, либо на исполнении.
struct elcore50_job_instance_info {
long id;
int pid;
char name[255];
};
struct elcore50_job_instance_list {
/* in */
__u32 job_instance_count;
struct elcore50_job_instance_list *list;
/* out */
__u32 job_instance_ret;
};
int ioctl(int fd, ELCORE50_IOC_GET_JOB_LIST, struct elcore50_job_instance_list *list);
Описание полей структуры elcore50_job_instance_info
:
id
Уникальный идентификатор экземпляра задания.
pid
Идентификатор процесса, создавшего экземпляр задания.
name
Название экземпляра задания.
Описание полей структуры elcore50_job_instance_list
:
job_instance_count
Число запрашиваемых экземпляров задания, полученное с помощью
ioctl()
ELCORE50_IOC_GET_JOB_COUNT.info
Описание экземпляров задания.
job_instance_ret
Число описанных экземпляров заданий в поле
elcore50_job_instance_list.info
.
Поля elcore50_job_instance_list.job_instance_count
и
elcore50_job_instance_list.job_instance_ret
могут отличаться в случае, если
между вызовами ELCORE50_IOC_GET_JOB_COUNT и ELCORE50_IOC_GET_JOB_LIST добавлялись или
удалялись некоторые экземпляры заданий из очереди.
Перед вызовом ioctl()
необходимо заполнить поля структуры
elcore50_job_instance_list
из раздела «in»: определить
elcore50_job_instance_list.job_instance_count
с помощью ioctl()
ELCORE50_IOC_GET_JOB_COUNT и выделить необходимую память под
elcore50_job_instance_list.info
. В случае успеха вызов ioctl()
возвращает
нулевое значение.
Предупреждение
Список экземпляров заданий заполняется без учета экземпляров заданий, находящихся в глобальной очереди, для которых еще не назначено DSP-ядро.
ELCORE50_IOC_DBG_JOB_ATTACH
Подключение к экземпляру задания для отладки.
struct elcore50_job_instance_dbg {
/* in */
long job_instance_id;
/* out */
int job_instance_dbg_fd;
};
int ioctl(int fd, ELCORE50_IOC_DBG_JOB_ATTACH, struct elcore50_job_instance_dbg *inst_dbg);
Описание полей структуры elcore50_job_instance_dbg
:
job_instance_id
Уникальный идентификатор экземпляра задания, полученный с помощью
ioctl()
ELCORE50_IOC_GET_JOB_LIST.job_instance_dbg_fd
Файловый дескриптор отладки.
При вызове ioctl()
выполнение задания прерывается и переводится в состояние отладки
ELCORE50_JOB_STATUS_INTERRUPTED
. Выполнение задания может быть продолжено после выполнения
ioctl()
ELCORE50_IOC_DBG_JOB_INSTANCE_CONTINUE, ELCORE50_IOC_DBG_STEP или
завершения отладки путем закрытия файлового дескриптора :c:member:job_instance_dbg_fd
.
Если экземпляр задания был поставлен в очередь с флагом :c:member:debug_enable, вызов
ioctl()
будет завершен с ошибкой.
Перед вызовом ioctl()
необходимо заполнить поля структуры
elcore50_job_instance_info
из раздела «in». В случае успеха вызов
ioctl()
возвращает нулевое значение.
ELCORE50_IOC_GET_CORE_IDX
Получение информации о ядре.
struct elcore50_device_info {
/* out */
int nclusters;
int cluster_id;
int cluster_cap;
int core_in_cluster_id;
};
int ioctl(int fd, ELCORE50_IOC_GET_CORE_IDX, struct elcore50_device_info
*dev_info);
Описание полей структуры elcore50_device_info
:
nclusters
Общее количество кластеров.
cluster_id
Порядковый номер кластера.
cluster_cap
Количество ядер в одном кластере.
core_in_cluster_id
Порядковый номер ядра в кластере.
В случае успеха вызов ioctl()
возвращает нулевое значение.
ELCORE50_IOC_CREATE_BUFFER
Аналогичен IOC_CREATE_BUFFER для файла устройства /dev/elcore
.
Данная реализация оставлена для обратной совместимости со старым ПО.
ELCORE50_IOC_CREATE_MAPPER
Аналогичен IOC_CREATE_MAPPER для файла устройства /dev/elcore
.
Данная реализация оставлена для обратной совместимости со старым ПО.
ELCORE50_IOC_SYNC_BUFFER
Аналогичен IOC_SYNC_BUFFER для файла устройства /dev/elcore
.
Данная реализация оставлена для обратной совместимости со старым ПО.
Описание отладочных IOCTL
Нижеописанные ioctl
применяются к отладочным файловым дескрипторам заданий,
полученных с помощью ELCORE50_IOC_DBG_JOB_ATTACH или
ELCORE50_IOC_ENQUEUE_JOB с
установленным флагом debug_enable
структуры elcore50_job_instance
.
ELCORE50_IOC_DBG_MEMORY_[READ|WRITE]
Чтение/запись памяти.
struct elcore50_dbg_mem {
/* in */
__u64 vaddr;
size_t size;
/* in-out */
void *data;
};
int ioctl(int dbg_fd, ELCORE50_IOC_DBG_MEMORY_READ, struct elcore50_dbg_mem *mem);
int ioctl(int dbg_fd, ELCORE50_IOC_DBG_MEMORY_WRITE, struct elcore50_dbg_mem *mem);
Описание полей структуры elcore50_dbg_mem
:
vaddr
Виртуальной адрес читаемого/записываемого блока памяти (или смещение регистра).
size
Размер блока памяти.
data
Указатель на блок памяти в пользовательском пространстве, содержащего данные DSP.
Предупреждение
Для записи/чтения регистров DSP необходимо использовать ioctl()
ELCORE50_IOC_DBG_REGISTER_[READ|WRITE].
Чтение и запись памяти возможны только для PRAM, XYRAM и отмапированных через VMMU областей.
Перед вызовом ioctl()
необходимо заполнить поля структуры
elcore50_dbg_mem
из раздела «in», а также выделить память под поле
data
. При вызове ioctl()
драйвер:
отключает все префетчи;
сбрасывает кэши 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
Профилирование заданий 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.