Использование встроенной сборки GCC с инструкциями, которые принимают немедленные значения
проблема
Я работаю над пользовательской ОС для процессора ARM Cortex-M3. Чтобы взаимодействовать с моим ядром, пользовательские потоки должны генерировать инструкцию вызова супервизора (SVC) (ранее известную как SWI, для прерывания программного обеспечения). Определение этой инструкции в ARM ARM:
это означает, что инструкция требует немедленно аргумент, а не значение регистра.
это мне трудно создать свой интерфейс в удобочитаемом виде. Для этого требуется код типа:
asm volatile( "svc #0");
когда я бы предпочел что-то вроде
svc(SVC_YIELD);
однако я не могу построить эту функцию, потому что инструкция SVC требует немедленного аргумента, и я не могу предоставить это, когда значение передается через регистр.
ядра:
для фона инструкция svc декодируется в ядре как следует
#define SVC_YIELD 0
// Other SVC codes
// Called by the SVC interrupt handler (not shown)
void handleSVC(char code)
{
switch (code) {
case SVC_YIELD:
svc_yield();
break;
// Other cases follow
это заявление по делу быстро выходит из-под контроля, но я не вижу способа обойти эту проблему. Любые предложения приветствуются.
что я пробовал
SVC с аргументом регистра
Я изначально считал
__attribute__((naked)) svc(char code)
{
asm volatile ("scv r0");
}
но это, конечно, не работает, поскольку SVC требует аргумента регистра.
грубую силу
попытка грубой силы решить проблему выглядит так:
void svc(char code)
switch (code) {
case 0:
asm volatile("svc #0");
break;
case 1:
asm volatile("svc #1");
break;
/* 253 cases omitted */
case 255:
asm volatile("svc #255");
break;
}
}
но это имеет неприятный запах кода. Конечно, это можно сделать лучше.
создание кодировки инструкции на лету
последней попыткой было сгенерировать инструкцию в ОЗУ (остальная часть кода выполняется только для чтения Flash), а затем запустить ее:
void svc(char code)
{
asm volatile (
"orr r0, 0xDF00 nt" // Bitwise-OR the code with the SVC encoding
"push {r1, r0} nt" // Store the instruction to RAM (on the stack)
"mov r0, sp nt" // Copy the stack pointer to an ordinary register
"add r0, #1 nt" // Add 1 to the address to specify THUMB mode
"bx r0 nt" // Branch to newly created instruction
"pop {r1, r0} nt" // Restore the stack
"bx lr nt" // Return to caller
);
}
но это тоже не кажется правильным. Кроме того, это не работает - есть что-то, что я делаю неправильно Здесь; возможно, моя инструкция неправильно выровнена или я не настройте процессор для запуска кода из ОЗУ в этом месте.
что делать?
Я должен работать над этим последним вариантом. Но все же, кажется, я должен быть в состоянии сделать что-то вроде:
__attribute__((naked)) svc(char code)
{
asm volatile ("scv %1"
: /* No outputs */
: "i" (code) // Imaginary directive specifying an immediate argument
// as opposed to conventional "r"
);
}
но я не нахожу такой опции в документации, и я не могу объяснить, как такая функция будет реализована, поэтому она, вероятно, не существует. Как мне это сделать?
5 ответов
вы хотите использовать ограничение, чтобы заставить операнд быть выделенным как 8-битный немедленный. Для ARM это ограничение I
. Так вы хотите
#define SVC(code) asm volatile ("svc %0" : : "I" (code) )
см. документацию GCC для резюме того, что все константы-вам нужно посмотреть на примечания к процессору, чтобы увидеть ограничения для определенных платформ. В некоторых случаях вам может потребоваться посмотреть на .md
(описание машины) файл для архитектуры в источнике gcc для полного информация.
есть также некоторые хорошие документы GCC для конкретной руки здесь. На пару страниц ниже под заголовком "входные и выходные операнды" он предоставляет таблицу всех ограничений ARM
Как отметил Крис Додд в комментариях к макросу, он не совсем работает, но это делает:
#define STRINGIFY0(v) #v
#define STRINGIFY(v) STRINGIFY0(v)
#define SVC(i) asm volatile("svc #" STRINGIFY(i))
обратите внимание, однако, что он не будет работать, если вы передадите ему значение перечисления, только #определенное.
поэтому ответ Криса выше является лучшим, так как он использует немедленное значение, которое требуется, по крайней мере, для инструкций большого пальца.
мое решение ("создание кодировки инструкций на лету"):
#define INSTR_CODE_SVC (0xDF00)
#define INSTR_CODE_BX_LR (0x4770)
void svc_call(uint32_t svc_num)
{
uint16_t instrs[2];
instrs[0] = (uint16_t)(INSTR_CODE_SVC | svc_num);
instrs[1] = (uint16_t)(INSTR_CODE_BX_LR);
// PC = instrs (or 1 -> thumb mode)
((void(*)(void))((uint32_t)instrs | 1))();
}
он работает и его гораздо лучше, чем вариант коммутатора, который занимает ~2kb ROM для 256 svc. Этот func не должен быть помещен в раздел ОЗУ, вспышка в порядке. Вы можете использовать его, если svc_num должен быть переменной времени выполнения.
Как говорится в этой вопрос, операнд SVC
фиксирован, то есть он должен быть известен препроцессору, и он отличается от непосредственных операндов обработки данных.
руководство gcc читает
'I' - целое число, действительное как непосредственный операнд в инструкция по обработке данных. То есть целое число в диапазоне от 0 до 255 вращается кратно 2.
поэтому ответы здесь использование макроса предпочтительнее, и ответ Криса Додда не гарантирует работу, в зависимости от версии gcc и уровня оптимизации. См. обсуждение другого вопроса.