Arm TF-A无法访问Linux内核虚拟/物理地址的问题求助
Arm TF-A访问Linux内存触发RCU停滞问题分析与解决
我认为Arm TF-A(EL3)权限高于Linux内核(EL1),安全世界应该能访问普通世界所有内存地址。为验证这一点,在Arm TF-A中新增了SMC调用,在Hikey960开发板上编写内核模块测试:通过alloc_pages获取物理地址,page_address转虚拟地址,再通过smc #0传递两个地址给TF-A。但程序运行异常,TF-A访问传递的内存地址时卡住,系统触发RCU抢占停滞。
内核模块代码
static int __init smc_testing_init(void) { uintptr_t phys_addr, virt_addr; pr_info("Mmodule loaded\n"); phys_addr = (uintptr_t) alloc_pages(GFP_KERNEL, 2); virt_addr = (uintptr_t) page_address((struct page *) phys_addr); memset((void *) virt_addr, 'A', PAGE_SIZE << 2); printk("phys_addr: 0x%llx, virt_addr: 0x%llx\n", (uint64_t) phys_addr, (uint64_t) virt_addr); asm volatile( "ldr x0, =0xC8000003\n" "mov x1, %0\n" "mov x2, %1\n" "smc #0\n" : : "r" (phys_addr), "r" (virt_addr) : "x0", "x1", "x2" ); __free_pages((struct page *) phys_addr, 2); return 0; } static void __exit smc_testing_exit(void) { pr_info("Module unloaded\n"); }
Arm TF-A处理代码
if (0xC8000003 == smc_fid) { uintptr_t virt_addr = x3; NOTICE("phys_addr: 0x%lx, virt_addr: 0x%lx\n", phys_addr, virt_addr); NOTICE("virt_addr[0] = 0x%x\n", *((uint32_t *)virt_addr)); NOTICE("phys_addr[0] = 0x%x\n", *((uint32_t *)phys_addr)); SMC_RET0(handle); }
输出日志
# insmod ./smc_testing.ko [ 75.259590] smc_testing: loading out-of-tree module taints kernel. [ 75.266682] Mmodule loaded [ 75.269404] phys_addr: 0xfffffc0002dc5e00, virt_addr: 0xffff0000b7178000 NOTICE: phys_addr: 0xfffffc0002dc5e00, virt_addr: 0xffff0000b7178000 [ 96.276605] rcu: INFO: rcu_preempt detected stalls on CPUs/tasks: [ 96.282741] rcu: 4-...0: (1 ticks this GP) idle=72ec/1/0x4000000000000000 softirq=204/204 fqs=2592 [ 96.291835] (detected by 0, t=5253 jiffies, g=-643, q=16 ncpus=8) [ 96.298044] Task dump for CPU 4: [ 96.301287] task:insmod state:R running task stack:0 pid:201 ppid:185 flags:0x00000006 [ 96.311254] Call trace: [ 96.313713] __switch_to+0xe4/0x160 [ 96.317241] printk_rb_static+0x30/0x58
问题原因
- 参数传递错误:内核模块中把虚拟地址存在
x2寄存器传递给TF-A,但TF-A代码里错误地从x3读取虚拟地址,导致拿到的是未初始化的垃圾值,访问该无效地址直接触发MMU异常,CPU卡住。 - 虚拟地址空间不兼容:EL3(安全世界)和EL1(普通世界)使用完全独立的页表,Linux的虚拟地址在TF-A的地址空间中是无效的,即使参数传递正确,直接访问EL1虚拟地址也会导致异常。
- 物理地址访问限制:即使TF-A权限高,若该物理地址未在TF-A的页表中完成映射,或者被硬件(如SMMU、GIC)标记为不可访问,直接解引用物理地址也会触发异常,进而导致系统停滞。
- RCU触发条件:SMC调用长时间未返回,普通世界的CPU无法完成RCU回调,最终触发RCU抢占停滞告警。
修复步骤
- 修正参数传递逻辑:TF-A代码中改用
x2读取虚拟地址,x1读取物理地址,修正后的代码如下:
if (0xC8000003 == smc_fid) { uintptr_t phys_addr = x1; uintptr_t virt_addr = x2; NOTICE("phys_addr: 0x%lx, virt_addr: 0x%lx\n", phys_addr, virt_addr); // 注意:不要访问virt_addr,改用物理地址处理 NOTICE("phys_addr[0] = 0x%x\n", *((uint32_t *)phys_addr)); SMC_RET0(handle); }
- 避免访问EL1虚拟地址:直接放弃访问Linux的虚拟地址,仅使用物理地址进行操作。
- 确保物理地址在TF-A中可访问:
- 检查TF-A的内存映射配置,确保该物理地址所在区域被正确映射到EL3地址空间,权限设置为可读写。
- 若需要临时映射,可以使用TF-A提供的
mmap_add_region接口动态添加映射,访问完成后再移除。
- 缩短SMC处理时间:确保TF-A中的SMC处理逻辑尽可能简洁,避免长时间占用EL3,防止触发RCU或其他调度异常。
内容的提问来源于stack exchange,提问作者b3a1e
相关产品推荐
相关产品推荐

