Использование встроенной сборки GCC с инструкциями, которые принимают немедленные значения

проблема

Я работаю над пользовательской ОС для процессора ARM Cortex-M3. Чтобы взаимодействовать с моим ядром, пользовательские потоки должны генерировать инструкцию вызова супервизора (SVC) (ранее известную как SWI, для прерывания программного обеспечения). Определение этой инструкции в ARM ARM:

enter image description here

это означает, что инструкция требует немедленно аргумент, а не значение регистра.

это мне трудно создать свой интерфейс в удобочитаемом виде. Для этого требуется код типа:

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 SVC(i)  asm volatile("svc #"#i)

Как отметил Крис Додд в комментариях к макросу, он не совсем работает, но это делает:

#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 и уровня оптимизации. См. обсуждение другого вопроса.