我正在尝试通过用户空间(通过自定义驱动程序)在内存寄存器中执行一些写操作。我想写三个64位整数,并将变量“ value_1,value_2和value_3”初始化为uint64_t类型。
我必须使用gcc内联mov指令,并且正在针对嵌入式系统在定制版本的linux上开发ARM 64位体系结构。
Thi是我的代码:
asm ( "MOV %[reg1], %[val1]\t\n"
"MOV %[reg2], %[val2]\t\n"
"MOV %[reg3], %[val3]\t\n"
:[reg1] "=&r" (*register_1),[arg2] "=&r" (*register_2), [arg3] "=&r" (*register_3)
:[val1] "r"(value_1),[val2] "r" (value_2), [val3] "r" (value_3)
);
问题很奇怪... 如果我仅执行两个MOV,则代码有效。 如果执行全部三个MOV,则整个系统将崩溃,并且必须重新引导整个系统。
甚至是陌生人... 如果我在第二个和第三个MOV之间放置一个“ printf”甚至一个0纳秒的纳秒睡眠,代码就可以工作!
我环顾四周,试图找到解决方案,同时我也使用了内存的废话:
asm ( "MOV %[reg1], %[val1]\t\n"
"MOV %[reg2], %[val2]\t\n"
"MOV %[reg3], %[val3]\t\n"
:[reg1] "=&r" (*register_1),[arg2] "=&r" (*register_2), [arg3] "=&r" (*register_3)
:[val1] "r"(value_1),[val2] "r" (value_2), [val3] "r" (value_3)
:"memory"
);
...不起作用!
我还使用了第二个和第三个MOV之间或三个MOV末尾的内存屏障宏:
asm volatile("": : :"memory")
..不起作用!
此外,我尝试使用指针直接写入寄存器,并且具有相同的行为:第二次写入后,系统崩溃...
任何人都可以建议我一个解决方案。或者告诉我是否以错误的方式使用了gcc内联MOV或内存屏障?
---->更多详细信息<-----
这是我的主要爱好
int main()
{
int dev_fd;
volatile void * base_addr = NULL;
volatile uint64_t * reg1_addr = NULL;
volatile uint32_t * reg2_addr = NULL;
volatile uint32_t * reg3_addr = NULL;
dev_fd = open(MY_DEVICE, O_RDWR);
if (dev_fd < 0)
{
perror("Open call failed");
return -1;
}
base_addr = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, xsmll_dev_fd, 0);
if (base_addr == MAP_FAILED)
{
perror("mmap operation failed");
return -1;
}
printf("BASE ADDRESS VIRT: 0x%p\n", base_addr);
/* Preparing the registers */
reg1_addr = base_addr + REG1_OFF;
reg2_addr = base_addr + REG2_OFF;
reg3_addr = base_addr + REG3_OFF;
uint64_t val_1 = 0xEEEEEEEE;
uint64_t val_2 = 0x00030010;
uint64_t val_3 = 0x01;
asm ( "str %[val1], %[reg1]\t\n"
"str %[val2], %[reg2]\t\n"
"str %[val3], %[reg3]\t\n"
:[reg1] "=&m" (*reg1_addr),[reg2] "=&m" (*reg2_addr), [reg3] "=&m" (*reg3_addr)
:[val1] "r"(val_1),[val2] "r" (val_2), [val3] "r" (val_3)
);
printf("--- END ---\n");
close(dev_fd);
return 0;
}
这是关于asm语句(linaro..I交叉编译)的编译器输出:
400bfc: f90013a0 str x0, [x29,#32]
400c00: f94027a3 ldr x3, [x29,#72]
400c04: f94023a4 ldr x4, [x29,#64]
400c08: f9402ba5 ldr x5, [x29,#80]
400c0c: f9401ba0 ldr x0, [x29,#48]
400c10: f94017a1 ldr x1, [x29,#40]
400c14: f94013a2 ldr x2, [x29,#32]
400c18: f9000060 str x0, [x3]
400c1c: f9000081 str x1, [x4]
400c20: f90000a2 str x2, [x5]
谢谢!
答案 0 :(得分:2)
我尝试使用* reg1_addr = val_1;和我有同样的问题。
然后这不是问题所在。避免使用asm
只是获得等效机器代码的一种更干净的方法,而不必使用嵌入式asm。问题可能出在您选择寄存器和值或内核驱动程序上。
还是您需要在写入第一个映射位置之前将值存储在CPU寄存器中,以避免在商店之间的堆栈中加载任何内容?这是我能想到的,您需要内联汇编代码的唯一原因,在此汇编程序生成的存储可能不相同。
原始问题的答案:
"=&r"
输出约束表示 CPU 寄存器。因此,您的inline-asm指令将按此顺序运行,组装成类似
mov x0, x5
mov x1, x6
mov x2, x7
然后,编译器生成的代码将以未指定的顺序将值存储回内存。该顺序取决于它选择如何为周围的C生成代码。这可能就是为什么更改周围的代码会改变行为的原因。
一种解决方案可能是使用"=&m"
指令约束str
,因此您的asm 确实存储到内存中。 str %[val1], %[reg1]
是因为STR指令将寻址模式作为第二个操作数,即使它是目的地。
为什么不能像普通人一样使用volatile uint64_t* = register_1;
来使编译器发出不允许重新排序或优化的存储指令? MMIO就是volatile
的目的。
Linux是否没有用于执行MMIO加载/存储的宏或函数?
如果您对内联汇编有疑问,调试的第一步应该是查看编译器在填入asm模板时所发出的实际asm及其周围的代码。
然后通过代码单步执行指令(使用GDB stepi
,也许处于layout reg
模式)。