[英]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.