:QEMU + TF-A(ATF) OP-TEE 初始化详解)
系列文章目录手撕ARM64启动栈一QEMU TF-A → OP-TEE → U-Boot → Linux 全链路总览手撕ARM64启动栈二QEMU TF-A(ATF) BL1 执行上下文——GDB 实测复位到 BL2手撕ARM64启动栈三QEMU TF-A(ATF) BL1 核心职责——bl1_main()逐函数拆解与 BL2 加载手撕ARM64启动栈四QEMU TF-A(ATF) BL2 详解手撕ARM64启动栈五QEMU TF-A(ATF) BL32/OP-TEE 镜像头解析——三模式判定与入口改写手撕ARM64启动栈六QEMU TF-A(ATF) BL31 运行时服务框架与 opteed 注册手撕ARM64启动栈七QEMU TF-A(ATF) BL31 世界切换手撕ARM64启动栈八QEMU TF-A(ATF) OP-TEE 初始化详解文章目录系列文章目录1. 引言2. 初始化总览3. 入口刚进 OP-TEE 时还缺什么4. 建 Secure 地址空间5. 四阶段初始化early → late → runtime → final5.1 early装上 S-EL0 → S-EL1 异常入口5.2 late改写外部 DT告诉 Linux 哪些内存不能碰5.3 runtime准备 TA 运行时5.4 final收尾并准备返回 Normal World6. 交还 BL31登记 SMC 入口表7. 小结1. 引言上一篇末尾BL31 通过el3_exit()执行ERETCPU 从 EL3 进入 Secure World 的 S-EL1PC指向 OP-TEE 的_start。本篇关注OP-TEE 初始化的主干它如何把自己从一个刚被 BL31 拉起的 Secure 镜像初始化成一个能被 Normal World 通过 SMC 调用的 TEE 服务端。涉及关键词OP-TEE、TrustZone、Secure World、S-EL1、S-EL0、SMC、entry_a64.S、core_init_mmu_map、NSEC_SHM、reserved-memory、thread_excp_vect、el0_svc、thread_vector_table。2. 初始化总览OP-TEE 初始化主线可以压成五件事BL31 ERET 到 OP-TEE → OP-TEE 建自己的 Secure 地址空间 → 划清 Secure 私有内存和 Non-secure 共享内存 → 改写 Normal World DT让 Linux 知道 OP-TEE 的内存和 SMC 入口 → 准备 S-EL0 TA 的 syscall/返回路径和 TEE runtime → 把 SMC 入口表交给 BL31自己休眠待命3. 入口刚进 OP-TEE 时还缺什么入口文件optee_os/core/arch/arm/kernel/entry_a64.S。BL31ERET落到_start时CPU 已经在Secure World / S-EL1但 OP-TEE 自己的运行时环境还没建好S-EL1 MMU 和正式 VA 映射还没启用线程池不存在VBAR_EL1也还不是运行时向量。_start会先把VBAR_EL1设为reset_vect_table临时表到early 阶段才换成thread_excp_vect。_start设置临时VBAR_EL1的代码可以参考这张图_start的启动动作可以压成这几步保存 BL31 传进来的 handoff 参数重点是外部 DTB 地址。安装临时reset_vect_table。这张表只兜底不是真正的optee 运行时异常向量入口。清.bss、切临时栈。调console_init()、boot_save_args()、boot_mem_init()。建页表、开 MMU进入 C 侧early → late → runtime → final四阶段。这里关键的外部 DTB后面 Linux 看到的reserved-memory和/firmware/optee都是 OP-TEE 在初始化期间改写这份 DTB 得来的。4. 建 Secure 地址空间core_init_mmu_map()建 OP-TEE 自己的 VA/PA 映射日志里三类内存值得关注区域含义TEE_RAM_RX/TEE_RAM_RWOP-TEE core 的代码/数据Secure World 私有SEC_RAM_OVERALLOP-TEE 可用的 Secure RAM 总范围NSEC_SHM静态 Non-secure 共享内存两边通过它交换消息和参数关键日志D/TC:0 add_phys_mem:798 VCORE_UNPG_RX_PA type TEE_RAM_RX 0x0e100000 size 0x00105000 D/TC:0 add_phys_mem:798 VCORE_UNPG_RW_PA type TEE_RAM_RW 0x0e205000 size 0x00049000 D/TC:0 add_phys_mem:798 VCORE_UNPG_RW_PA type SEC_RAM_OVERALL 0x0e205000 size 0x00049000 D/TC:0 add_phys_mem:798 TEE_SHMEM_START type NSEC_SHM 0x42000000 size 0x00200000 D/TC:0 init_mem_map:1546 Mapping core at 0xb2fd3000 offs 0xa4ed3000 D/TC:0 dump_mmap_table:968 type NSEC_SHM va 0xad800000..0xad9fffff pa 0x42000000..0x421fffff size 0x00200000 D/TC:0 dump_mmap_table:968 type SEC_RAM_OVERALL va 0xada05000..0xae7fffff pa 0x0e205000..0x0effffff size 0x00dfb000 D/TC:0 dump_mmap_table:968 type TEE_RAM_RX va 0xb2fd3000..0xb30d7fff pa 0x0e100000..0x0e204fff size 0x00105000 D/TC:0 dump_mmap_table:968 type TEE_RAM_RW va 0xb30d8000..0xb31d2fff pa 0x0e205000..0x0e2fffff size 0x000fb000在 MMU 初始化阶段用 GDB 查看mem_map可以看到如下内存布局5. 四阶段初始化early → late → runtime → finalMMU 开启后OP-TEE 按固定顺序跑完四阶段 C 初始化。5.1 early装上 S-EL0 → S-EL1 异常入口入口boot_init_primary_early()。关键动作thread_init_canaries() thread_init_per_cpu() thread_init_vbar(get_excp_vect())thread_init_vbar()把VBAR_EL1从临时reset_vect_table换成运行时向量thread_excp_vect。这和 TA svc call直接相关跑在S-EL0的TA 调TEE_*API 时执行svcCPU 从 S-EL0 陷入 S-EL1入口就是thread_excp_vect里的el0_svc路径。所以 early 阶段结束时S-EL0 系统调用通道已经准备好。本阶段的日志标志init_primary()建好 malloc 池后打一条IMSG_RAW(\n)在日志里是一行**I/TC:**——early 阶段的分界线前面是第 4 节的映射表输出后面进 late 阶段D/TC:0 core_mmu_xlat_table_alloc:700 xlat tables used 7 I/TC: ← early 阶段的分界 D/TC:0 carve_out_core_mem:2816 0xe100000 .. 0xe300000 D/TC:0 boot_mem_release_unused:294 Allocated 41280 bytes at va 0xb3121000 pa 0xe24e000 D/TC:0 boot_mem_release_unused:298 Tempalloc 33864 bytes at va 0xb31cabb8 D/TC:0 boot_mem_release_unused:320 Carving out 0xe100000..0xe25813f D/TC:0 boot_mem_release_unused:329 Releasing 650944 bytes from va 0xb312c000这里可以思考下为什么不一开始就使用“thread_excp_vect”临时向量存在的意义是什么5.2 late改写外部 DT告诉 Linux 哪些内存不能碰入口boot_init_primary_late()。这是本篇最重要的一段。OP-TEE 会解析 Normal World 的外部 DT然后把安全边界写回这份 DT写reserved-memory标出optee_core让 Linux 避开 OP-TEE 私有 Secure RAM。写reserved-memory标出optee_shm让 Linux 把静态共享内存交给optee驱动管理而不是当普通页分配。写/firmware/opteecompatible linaro,optee-tzmethod smc让 Linux 能匹配optee驱动并使用 SMC conduit。记录外部 DT 里的 Non-secure DDR 范围后续动态共享内存注册时用来判断物理页是否合法。在discover_nsec_memory()改写前和configure_console_from_dt()改写后两处下断点各 dump 一次 DT 转成文本比较能看到 OP-TEE 实际插入的内容 reserved-memory { ranges; #size-cells 0x02; #address-cells 0x02; optee_coree100000 { no-map; reg 0x00 0xe100000 0x00 0xf00000; }; optee_shm42000000 { no-map; reg 0x00 0x42000000 0x00 0x200000; }; }; firmware { optee { interrupts 0x00 0xbb 0x01; method smc; compatible linaro,optee-tz; }; };optee_core的reg是0xe100000起0xf0000015MBoptee_shm的reg精确对应第 4 节的NSEC_SHM0x42000000起 2MB。对应日志I/TC: Non-secure external DT found D/TC:0 discover_nsec_memory:158 Non-secure memory found in extern DT D/TC:0 core_mmu_set_discovered_nsec_ddr:540 0: pa 0x40000000..0x41ffffff sz 0x2000000 D/TC:0 core_mmu_set_discovered_nsec_ddr:540 1: pa 0x42200000..0x820fffff sz 0x3ff00000 I/TC: Embedded DTB found两段 Non-secure DDR 中间缺掉的0x42000000..0x421fffff就是前面NSEC_SHM的 2MB 静态共享内存。late 阶段还会建线程池thread_init_threads(CFG_NUM_THREADS) thread_init_boot_thread()从这之后日志前缀由D/TC:0变成D/TC:0 0多出来的0是线程 ID表示 0 号 boot 线程已激活。5.3 runtime准备 TA 运行时入口boot_init_primary_runtime()。runtime 阶段有两个重点。第一thread_init_primary()调init_user_kcode()把 S-EL1/S-EL0 切换需要的一小段跳板映射到用户态可见地址。它和 early 阶段的el0_svc配套前者解决“怎么回到/恢复用户态”后者解决“用户态怎么陷入内核态”。这部分将在后续章节展开更多细节可持续关注后续篇章第二init_tee_runtime()跑 initcall注册 TA store、系统调用分发表、加密服务等运行时组件I/TC: OP-TEE version: c02b129 ... I/TC: Primary CPU initializing D/TC:0 0 boot_init_primary_runtime:1053 Executing at offset 0xa4ed3000 with virtual load address 0xb2fd3000 D/TC:0 0 mobj_mapped_shm_init:543 Shared memory address range: afe00000, b1e00000 D/TC:0 0 check_ta_store:460 TA store: early TA D/TC:0 0 check_ta_store:460 TA store: Secure Storage TA D/TC:0 0 check_ta_store:460 TA store: REE5.4 final收尾并准备返回 Normal World入口boot_init_primary_final()。final 阶段跑 driver initcall/finalcall关键日志D/TC:0 0 do_init_calls:19 finalcall level 8 release_external_dt() I/TC: Primary CPU switching to normal world bootrelease_external_dt()表示外部 DT 改写完成。Primary CPU switching to normal world boot是主核初始化结束的标志。6. 交还 BL31登记 SMC 入口表四阶段初始化完成后OP-TEE 要把自己变成“被动服务端”线程池空闲入口表交给 BL31之后等 Normal World 的 SMC。关键代码仍在entry_a64.Sbl thread_clr_boot_thread adr x1, thread_vector_table mov x0, #TEESMC_OPTEED_RETURN_ENTRY_DONE smc #0含义thread_clr_boot_thread()清掉 0 号 boot 线程线程池回到空闲状态。x1 thread_vector_table把 OP-TEE 的 SMC 入口向量表交给 BL31/opteed。x0 TEESMC_OPTEED_RETURN_ENTRY_DONE告诉 BL31 初始化完成。smc #0陷入 EL3BL31 保存入口表然后继续启动 U-Boot/Linux。这条 SMC 之后OP-TEE 的状态是OP-TEE core 的代码和数据留在 Secure RAM。SMC 入口表已经挂到 BL31。S-EL0 syscall/返回路径已经准备好。线程池空闲。外部 DT 已被改写后续会交给 Linux。7. 小结OP-TEE 初始化真正和 TrustZone 安全边界相关的是这几件事TEE_RAM_RX/RWSecure World 私有代码/数据内存。NSEC_SHMSecure/Normal World 固定共享缓冲区。外部 DTreserved-memory防止 Linux 误用 OP-TEE 私有内存和静态共享内存。外部 DT/firmware/optee让 Linux 知道通过 SMC 调 OP-TEE。thread_excp_vect/el0_svc未来 TA 从 S-EL0 进入 S-EL1 的系统调用入口。init_user_kcode()S-EL1/S-EL0 切换所需的用户态可见跳板。thread_vector_tableBL31 后续分发 SMC 到 OP-TEE 的入口表。一句话概括OP-TEE 初始化的结果是把一个刚被 BL31 拉起的 Secure 镜像变成内存边界清楚、Linux 能识别内存保留和 SMC 调用方式、TA 用户态通道和 SMC 入口都已就绪的 TEE 服务端。思考OP-TEE 改写 DT只是告诉 Linux 哪些物理内存不要纳入普通内存管理比如TEE_RAM_*和静态NSEC_SHM。DT 本身不是访问控制机制也不能阻止 Normal World 代码强行访问某个物理地址。那么真正限制 Linux 访问 OP-TEE 私有 Secure RAM 的是谁如果在 Linux 里绕过内核内存管理强行映射并访问TEE_RAM_*对应的物理地址会发生什么