You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

  1. 参数传递错误:内核模块中把虚拟地址存在x2寄存器传递给TF-A,但TF-A代码里错误地从x3读取虚拟地址,导致拿到的是未初始化的垃圾值,访问该无效地址直接触发MMU异常,CPU卡住。
  2. 虚拟地址空间不兼容:EL3(安全世界)和EL1(普通世界)使用完全独立的页表,Linux的虚拟地址在TF-A的地址空间中是无效的,即使参数传递正确,直接访问EL1虚拟地址也会导致异常。
  3. 物理地址访问限制:即使TF-A权限高,若该物理地址未在TF-A的页表中完成映射,或者被硬件(如SMMU、GIC)标记为不可访问,直接解引用物理地址也会触发异常,进而导致系统停滞。
  4. RCU触发条件:SMC调用长时间未返回,普通世界的CPU无法完成RCU回调,最终触发RCU抢占停滞告警。

修复步骤

  1. 修正参数传递逻辑: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);
}
  1. 避免访问EL1虚拟地址:直接放弃访问Linux的虚拟地址,仅使用物理地址进行操作。
  2. 确保物理地址在TF-A中可访问:
    • 检查TF-A的内存映射配置,确保该物理地址所在区域被正确映射到EL3地址空间,权限设置为可读写。
    • 若需要临时映射,可以使用TF-A提供的mmap_add_region接口动态添加映射,访问完成后再移除。
  3. 缩短SMC处理时间:确保TF-A中的SMC处理逻辑尽可能简洁,避免长时间占用EL3,防止触发RCU或其他调度异常。

内容的提问来源于stack exchange,提问作者b3a1e

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:05:21