[英]Using GCC inline assembly with instructions that take immediate values
我正在为 ARM Cortex-M3 处理器开发自定义操作系统。 要与我的 kernel 交互,用户线程必须生成一个 SuperVisor Call (SVC) 指令(以前称为 SWI,用于软件中断)。 ARM ARM中这条指令的定义是:
这意味着该指令需要立即参数,而不是寄存器值。
这让我很难以可读的方式构建我的界面。 它需要如下代码:
asm volatile( "svc #0");
当我更喜欢类似的东西
svc(SVC_YIELD);
但是,我无法构造这个 function,因为 SVC 指令需要立即参数,而当值通过寄存器传入时我无法提供。
作为背景,svc指令在kernel中解码如下
#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
这个案例陈述很快就失控了,但我看不出解决这个问题的办法。 欢迎提出任何建议。
我最初考虑过
__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;
}
}
但那有一股难闻的代码味道。 这当然可以做得更好。
最后一次尝试是在 RAM 中生成指令(代码的 rest 从只读闪存运行)然后运行它:
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
);
}
但这也感觉不对。 此外,它不起作用 - 我在这里做错了什么; 也许我的指令没有正确对齐,或者我没有设置处理器以允许在此位置从 RAM 运行代码。
我必须处理最后一个选项。 但是,感觉我应该能够做类似的事情:
__attribute__((naked)) svc(char code)
{
asm volatile ("scv %1"
: /* No outputs */
: "i" (code) // Imaginary directive specifying an immediate argument
// as opposed to conventional "r"
);
}
但是我在文档中没有找到任何这样的选项,而且我无法解释如何实现这样的功能,所以它可能不存在。 我应该怎么做?
您希望使用约束来强制将操作数分配为8位立即数。 对于ARM,这是约束I
。 所以你要
#define SVC(code) asm volatile ("svc %0" : : "I" (code) )
有关所有constaints的摘要,请参阅GCC文档 - 您需要查看特定于处理器的注释以查看特定平台的约束。 在某些情况下,您可能需要查看gcc源代码中架构的.md
(机器描述)文件以获取完整信息。
还有一些不错的特定ARM-GCC文档在这里 。 在“输入和输出操作数”标题下面几页,它提供了所有ARM约束的表格
使用宏怎么样:
#define SVC(i) asm volatile("svc #"#i)
正如Chris Dodd在关于宏的评论中所指出的那样,它并不是很有效,但是这样做:
#define STRINGIFY0(v) #v
#define STRINGIFY(v) STRINGIFY0(v)
#define SVC(i) asm volatile("svc #" STRINGIFY(i))
但是请注意,如果您将枚举值传递给它,它只会是#defined。
因此,克里斯上面的答案是最好的,因为它使用立即值,这是所需要的,至少是拇指指令。
我的解决方案(“动态生成指令编码”):
#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))();
}
它的工作原理和它比switch-case变体要好得多,后者需要大约2kb的ROM才能生成256个svc。 这个func不必放在RAM部分,FLASH就可以了。 如果svc_num应该是运行时变量,则可以使用它。
正如在这个问题中所讨论的那样, SVC
的操作数是固定的,即预处理器应该知道它,它与立即数据处理操作数不同。
gcc手册上写着
'I'-作为数据处理指令中的立即操作数有效的整数。 也就是说,0到255范围内的整数旋转2的倍数。
因此,这里使用宏的答案是首选,并且Chris Dodd的答案不能保证可行,具体取决于gcc版本和优化级别。 请参阅另一个问题的讨论。
我最近在 Cortex-M 上为我自己的玩具操作系统编写了一个处理程序。 如果任务使用 PSP 指针,则工作。
思路:获取被中断进程的栈指针,获取进程的栈PC,就会有SVC之后指令的指令地址,查找指令中的立即数。 它并不像听起来那么难。
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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.