简体   繁体   中英

Using GCC inline assembly with instructions that take immediate values

The problem

I'm working on a custom OS for an ARM Cortex-M3 processor. To interact with my kernel, user threads have to generate a SuperVisor Call (SVC) instruction (previously known as SWI, for SoftWare Interrupt). The definition of this instruction in the ARM ARM is:

在此处输入图像描述

Which means that the instruction requires an immediate argument, not a register value.

This is making it difficult for me to architect my interface in a readable fashion. It requires code like:

asm volatile( "svc #0");

when I'd much prefer something like

svc(SVC_YIELD);

However, I'm at a loss to construct this function, because the SVC instruciton requires an immediate argument and I can't provide that when the value is passed in through a register.

The kernel:

For background, the svc instruction is decoded in the kernel as follows

#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

This case statement is getting rapidly out of hand, but I see no way around this problem. Any suggestions are welcome.

What I've tried

SVC with a register argument

I initially considered

__attribute__((naked)) svc(char code)
{
    asm volatile ("scv r0"); 
}

but that, of course, does not work as SVC requires a register argument.

Brute force

The brute-force attempt to solve the problem looks like:

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;
  }
}

but that has a nasty code smell. Surely this can be done better.

Generating the instruction encoding on the fly

A final attempt was to generate the instruction in RAM (the rest of the code is running from read-only Flash) and then run it:

void svc(char code)
{
  asm volatile (
      "orr r0, 0xDF00  \n\t" // Bitwise-OR the code with the SVC encoding
      "push {r1, r0}   \n\t" // Store the instruction to RAM (on the stack)
      "mov r0, sp      \n\t" // Copy the stack pointer to an ordinary register
      "add r0, #1      \n\t" // Add 1 to the address to specify THUMB mode
      "bx r0           \n\t" // Branch to newly created instruction
      "pop {r1, r0}    \n\t" // Restore the stack
      "bx lr           \n\t" // Return to caller
      );
}

but this just doesn't feel right either. Also, it doesn't work - There's something I'm doing wrong here; perhaps my instruction isn't properly aligned or I haven't set up the processor to allow running code from RAM at this location.

What should I do?

I have to work on that last option. But still, it feels like I ought to be able to do something like:

__attribute__((naked)) svc(char code)
{
    asm volatile ("scv %1"
         : /* No outputs */
         : "i" (code)    // Imaginary directive specifying an immediate argument
                         // as opposed to conventional "r"
          ); 
}

but I'm not finding any such option in the documentation and I'm at a loss to explain how such a feature would be implemented, so it probably doesn't exist. How should I do this?

You want to use a constraint to force the operand to be allocated as an 8-bit immediate. For ARM, that is constraint I . So you want

#define SVC(code) asm volatile ("svc %0" : : "I" (code) )

See the GCC documentation for a summary of what all the constaints are -- you need to look at the processor-specific notes to see the constraints for specific platforms. In some cases, you may need to look at the .md (machine description) file for the architecture in the gcc source for full information.

There's also some good ARM-specific gcc docs here . A couple of pages down under the heading "Input and output operands" it provides a table of all the ARM constraints

使用宏怎么样:

#define SVC(i)  asm volatile("svc #"#i)

As noted by Chris Dodd in the comments on the macro, it doesn't quite work, but this does:

#define STRINGIFY0(v) #v
#define STRINGIFY(v) STRINGIFY0(v)
#define SVC(i)  asm volatile("svc #" STRINGIFY(i))

Note however that it won't work if you pass an enum value to it, only a #defined one.

Therefore, Chris' answer above is the best, as it uses an immediate value, which is what's required, for thumb instructions at least.

My solution ("Generating the instruction encoding on the fly"):

#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))();
}

It works and its much better than switch-case variant, which takes ~2kb ROM for 256 svc's. This func does not have to be placed in RAM section, FLASH is ok. You can use it if svc_num should be a runtime variable.

As discussed in this question, the operand of SVC is fixed, that is it should be known to the preprocessor, and it is different from immediate Data-processing operands.

The gcc manual reads

'I'- Integer that is valid as an immediate operand in a data processing instruction . That is, an integer in the range 0 to 255 rotated by a multiple of 2.

Therefore the answers here that use a macro are preferred, and the answer of Chris Dodd is not guaranteed to work, depending on the gcc version and optimization level. See the discussion of the other question.

I wrote one handler recently for my own toy OS on Cortex-M. Works if tasks use PSP pointer.

Idea: Get interrupted process's stack pointer, get process's stacked PC, it will have the instruction address of instruction after SVC, look up the immediate value in the instruction. It's not as hard as it sounds.

uint8_t __attribute__((naked)) get_svc_code(void){
__asm volatile("MSR R0, PSP"); //Get Process Stack Pointer (We're in SVC ISR, so currently MSP in use)
__asm volatile("ADD R0, #24"); //Pointer to stacked process's PC is in R0
__asm volatile("LDR R1, [R0]"); //Instruction Address after SVC is in R1
__asm volatile("SUB R1, R1, #2"); //Subtract 2 bytes from the address of the current instruction. Now R1 contains address of SVC instruction
__asm volatile("LDRB R0, [R1]"); //Load lower byte of 16-bit instruction into R0. It's immediate value.
//Value is in R0. Function can return
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM