sm_init 调用栈 1 2 3 4 5 6 7 8 #0 sm_init (cold_boot=cold_boot@entry=1 ) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/optee/sm.c:120 #1 0x00000000a00026aa in nuclei_final_init (cold_boot=<optimized out>) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/platform/nuclei/evalsoc/platform.c:66 #2 0x00000000a0000c18 in sbi_platform_final_init (cold_boot=1 , plat=0xa00138a8 <platform>) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/include/sbi/sbi_platform.h:401 #3 init_coldboot (hartid=0 , scratch=0xa002d000 ) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/sbi_init.c:295 #4 sbi_init (scratch=0xa002d000 ) at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/lib/sbi/sbi_init.c:425 #5 0x00000000a00004b6 in _start_warm () at /home/liguang/program/riscv-lab/nuclei-linux-sdk/opensbi/firmware/fw_base.S:443
重点关注 sm_init 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 -+ void sm_init(bool cold_boot) \ -|+ if cold_boot \ -+ sbi_ecall_register_extension(&ecall_optee) ; \ - .extid_start = SBI_EXT_OPTEE "0x4F505445" | - .handle = sbi_ecall_optee_handler, "注册 optee ext handler" | - sm_region_id = smm_init(); "添加 opensbi text 段 pmp entry, 并保存 id为 sm_region_id" | - os_region_id = osm_init(); "添加 0xfffffffff bottom entry" | - tee_region_id = teem_init(); "添加 FW_OPTEE_TZDRAM_BASE optee_os 的text 内存 pmp entry" | - shm_region_id = shm_init(); "添加 share memory (secure与non sec 的共享内存的) pmp entry" | - plicm_region_id = plicm_init(); "添加 plic 的 mmio的 pmp entry" | - timer_region_id = timerm_init(); "添加 timer 的 mmio的pmp entry" "没有判断 cold_boot 说明是所有核都会走" | -+ pmp_init() "将所有可用的 pmpaddr pmpcfg 置0" | -+ pmp_set_keystone(sm_region_id, PMP_NO_PERM); "设置 opensbi text 段不可读,这个是对 S-mode 有效,对 M-mode 不起作用" | -+ pmp_set_keystone(os_region_id, PMP_ALL_PERM); "设置优先级最低的 osm entry 0xfffffffff 保底读写执行权限,排在它前面的 entry 如果设置了不一样的权限, 以前面的 entry 设置的为准" | -+ pmp_set_keystone(tee_region_id, PMP_NO_PERM); "设置 optee text 无权限" | -|+ if cold_boot \ -+ opteed_init() \ - sbi_memset(psci_ns_context, 0 , sizeof(psci_ns_context)); "normal world context 初始化为 0" | - sbi_memset(opteed_sp_context, 0 , sizeof(opteed_sp_context)); "secure world context 初始化为 0" | -+ img_entry_point.sec_attr = SECURE; "保存状态, 后面会用. 该结构体最终会被放到 当前 cpu context 中" | -+ img_entry_point.pc = OPTEE_OS_LOAD_ADDR; "optee 的 text 段起始地址" | - img_entry_point.arg0 = current_hartid(); "cpu core id" | - img_entry_point.arg1 = 8 *1024 *1024 ; | - img_entry_point.arg2 = FW_JUMP_FDT_ADDR; "fdt 的地址,optee 和 opensbi 共用一个 fdt" | -+ cm_init_my_context(&img_entry_point); \ -+ cm_init_context_by_index(current_hartid(), &img_entry_point); \ -+ ctx = cm_get_context_by_index(cpu_idx, &img_entry_point->sec_attr); "根据 cpu id 和 sec_attr 属性找到 context 指针" \ - if secure? &opteed_sp_context[cpu_idx].cpu_ctx : &psci_ns_context[cpu_idx]; "secure 和 non sec 分别有一个context 数组, 按cpuid 保存对应的 当前cpu的context" | -+ cm_setup_context(ctx, ep); "填 context " \ - ctx->sec_attr = (ep->sec_attr == SECURE) ? SECURE : NON_SECURE; | - ctx->gp_regs.mepc = ep->pc; | - ctx->gp_regs.mstatus = (1 << MSTATUS_MPP_SHIFT); "S-mode" | - ctx->gp_regs.a0 = ep->arg0; | - ctx->gp_regs.a1 = ep->arg1; | - ctx->gp_regs.a2 = ep->arg2; | - ctx->gp_regs.... 直到 arg7 | -|+ if ep->sec_attr == SECURE \ - ctx->s_csrs.sie = 0 ; "初始 sie 为0, S-mode 中断关闭" | - saved_mie = csr_read(CSR_MIE); "保存中断使能状态" | - csr_write(CSR_MIE, 0 ); "关闭所有中断" | - rc = opteed_synchronous_sp_entry(optee_ctx); "进入 sec world optee_os 的准备工作, 及要进入 optee_os, 下面拆解" ----------------------------------------------------------------------------------------------------- "回到 M-mode的 opensbi" ----------------------------------------------------------------------------------------------------- | -+ ... sbi_ecall_optee_handler 处理流程 -->1 | - csr_write(CSR_MIE, saved_mie); "恢复中断状态" | - sbi_memset(&img_entry_point, 0 , sizeof(entry_point_info_t)); | - img_entry_point.sec_attr = NON_SECURE; "下一个 image 为 non sec 的 u-boot" | - cm_init_my_context(&img_entry_point); "初始化 non sec 的 context" | - cm_set_next_eret_context(NON_SECURE);
下面对 opteed_synchronous_sp_entry 进行拆解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 -+ uint64_t opteed_synchronous_sp_entry(optee_context_t *optee_ctx) \ -+ cm_sysregs_context_restore(SECURE) ; "从context 中恢复 s 开头的 csr, 这里主要是 sepc sie " | -+ cm_set_next_eret_context(SECURE) ; \ -+ ctx = cm_get_context(security_state) ; "拿 当前 cpu的 context 指针" | -+ cm_set_next_context(ctx) ; \ - next_cpu_context_ptr[current_hartid() ] = context; "设置 next_cpu_context_ptr 全局变量, 保存当前cpu的下一个 context" | -|+ if secure_state == SECURE ? \ -+ switch_plic_int_enable_mode(SECURE) ; "设置中断使能状态为 secure_state 方案" | -+ switch_vector_sec() ; \ - csr_write(CSR_MTVEC, &_trap_handler_sec) ; "设置 secure 方案的 trap handler" | - osm_pmp_set(PMP_NO_PERM) ; "bottom pmp entry 设置所有内存无权限" | - shm_pmp_set(PMP_ALL_PERM) ; "sec 与 non sec 的 share memory 设置 all 权限" | - plicm_pmp_set(PMP_ALL_PERM) ; | - timerm_pmp_set(PMP_ALL_PERM) ; | - teem_pmp_set(PMP_ALL_PERM) ; "设置 tee 的 text data bss 等的 all 权限" | -+ rc = opteed_enter_sp(&optee_ctx->c_rt_ctx) ; "进入汇编函数, 保存当前 opensbi的 cpu context, 恢复 context 中保存的上下文, 即为 optee_os 准备的初始的 cpu context" \ - REG_L s1, SBI_TRAP_REGS_OFFSET(mepc) (s0) | - csrw CSR_MEPC, s1 | - REG_L s1, SBI_TRAP_REGS_OFFSET(mstatus) (s0) | - csrw CSR_MSTATUS, s1 | - mret "进入到 optee_os, M-mode 切换为 S-mode"
–>1 optee_os 初始化完毕后, 会发 ecall 调用, 调用号 SBI_EXT_OPTEE “0x4F505445” 回到 opensbi 中, opensbi 由前面设置的 _trap_handler_sec
处理, 进而由 opensbi sbi_ecall_optee_handler
处理该 ecall 调用,其中 funcid 为 TEESMC_OPTEED_RETURN_ENTRY_DONE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -+ static int sbi_ecall_optee_handler(unsigned long extid, unsigned long funcid, const struct sbi_trap_regs *regs, unsigned long *out_val, struct sbi_trap_info *out_trap) \ -+ switch (funcid) \ -+ case TEESMC_OPTEED_RETURN_ENTRY_DONE \ -+ cm_gpregs_context_save(SECURE, regs) ; "保存 optee_os 的上下文到 context 中" \ - ctx = cm_get_context(security_state) ; | - sbi_memcpy(&ctx->gp_regs, trap_reg, sizeof(struct sbi_trap_regs)) ; "regs 已经由 _trap_handler_sec 保存下来了, 这里转移到 context 下" | -+ cm_sysregs_context_save(SECURE) ; "保存 s 开头的 csr 到 context 下, 这里不用参数, 因为 M-mode 基本上不会改动 S-mode 的寄存器, 直接读即可" | - optee_vector_table = (optee_vectors_t *) regs->a1; "regs->a1锚点 optee_os的 thread_vector_table 基址在 ecall 时会保存到 a1 寄存器中, 进而转移到这里" | - if optee_vector_table ? set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON) "optee_os的 power state, 这里on 表示正在运行" | -+ opteed_synchronous_sp_exit(optee_ctx, regs->a1) ; "回到 进入 optee_os 时的地方" \ -+ cm_sysregs_context_save(SECURE) ; \ -+ opteed_exit_sp(optee_ctx->c_rt_ctx, ret) ; \ - REG_L s0, (12 -13 ) * __SIZEOF_POINTER__(sp) | - ... "恢复 callee 即 s0-s11 寄存器" | - REG_L ra, (0 -13 ) * __SIZEOF_POINTER__(sp) "恢复 ra" | - ret "回到 进入 optee_os 时的地方 接着往下执行"
optee_os 启动流程 上面讲到在调用 opteed_synchronous_sp_entry->opteed_enter_sp mret 后进入了 optee_os 中 下面就讲下 optee_os 的启动流程.
optee_os/core/arch/riscv/kernel/entry.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 -+ _start \ - mv s1, a0 "保存hartid" | - la a0, reset_vect_table | - csrw stvec, a0 "保存 stvec 中断入口 为 reset_vect_table" | -+ clear_bss __bss_start, __bss_end | -+ set_sp s1 "为 hartid 设置 sp" | -+ jal thread_init_thread_core_local "初始化线程本地存储" | -+ jal dcache_cleaninv_range __text_start, cached_mem_end "dcache inv clean" | -+ jal console_init "初始化console" | -+ jal core_init_mmu_map(0 , boot_mmu_config) "初始化mmu 映射mmu 页表" | -+ jal enable_mmu(hartid) "开启mmu" | -+ jal boot_init_primary_early "主核初始化" \ -+ init_primary() \ -+ thread_init_core_local_stacks() "由于std smc需要支持线程(thread)功能,而每个线程都需要含有独立的线程栈, 用于保存该线程相关的上下文。该函数用于为每个cpu核初始化相关的线程栈, tmp_stack_va_end用于指定每个ARM核的栈空间" | -+ thread_set_exceptions(THREAD_EXCP_ALL) ; "由于在接下来的初始化流程中需要切换异常向量表的基地址, 因此在切换之前需要先mask掉相关的异常使能位" | - primary_save_cntfrq() "未实现" | - init_vfp_sec() "未实现,初始化浮点运算单元vfp" | - thread_get_core_local() ->curr_thread = 0; "curr_thread用于表示当前核使用的是哪个线程空间。" | -+ init_runtime() ; "初始化tee运行时所需的各种内存" | -+ thread_init_boot_thread() ; "将当前执行上下文转换为启动线程, 它主要包括从当前cpu的线程栈内存中分配一个栈空间,初始化线程页表和线程状态等" | -+ thread_init_primary() ; "该函数主要用于增强线程的运行安全,如设置线程栈的canary值,以用于监测栈溢出" | -+ thread_init_per_cpu() ; \ -+ thread_init_tvec() ; "为每个cpu 设置中断入口为 thread_trap_vect" | -+ init_sec_mon(nsec_entry) ; "未实现" | -+ jal boot_init_primary_late(fdt) "该函数用于建立optee的系统运行环境, 包括从devcicetree中解析相关的配置,初始化中断控制器,调用initcall回调函数等" \ -+ init_external_dt(fdt) ; "初始化devicetree,包括映射其物理内存,初始化dtb overlay等" | -+ tpm_map_log_area(get_external_dt()) ; "初始化tpm的log buffer,其主要操作是为log buffer建立虚拟地址页表" | -+ discover_nsec_memory() ; "optee与non secure world可通过共享内存通信,它一共支持静态映射和动态映射两种共享内存方式。 其中静态映射shm在optee启动时就为其建立了相应的内存页表, 而动态映射方式可以在运行过程中动态地为shm建立页表。该接口用于从devicetree中获取non secure内存,以支持动态共享内存机制" | -+ update_external_dt() ; "在devicetree中添加与optee相关的一些节点和属性,这些节点和属性将由hlos内核解析并用于初始化相应的模块。 如它会创建/firmware/optee节点,并在该节点中配置一些与optee相关的属性" | -+ configure_console_from_dt() "从devicetree中解析串口参数,并将optee的控制台切换为该串口" | - main_init_gic() ; "未实现" | - init_vfp_nsec() ; "未实现" | -+ init_tee_runtime() ; \ -+ core_mmu_init_ta_ram() ; "用户空间中使用的栈空间是从 tee_mm_sec_ddr 内存池中分配出来的, 该内存池属于MEM_AREA_TA_RAM内存区域,该区域是由OPTEE分配,用于运行TA镜像。" \ -+ tee_mm_init(&tee_mm_sec_ddr, ps, size, CORE_MMU_USER_CODE_SHIFT, TEE_MM_POOL_NO_FLAGS) ; | -+ call_initcalls() ; -->2 "调用已注册的initcall回调函数, 被各模块用于注册启动时调用的初始化接口, core/include/initcall.h" | -+ jal thread_clr_boot_thread " thread 结束阶段, 将thread->state 置为 THREAD_STATE_FREE" | -+ ecall SBI_EXT_OPTEE a6->TEESMC_OPTEED_RETURN_ENTRY_DONE a1-> thread_vector_table load offset -->3 " ecall 回opensbi, 将optee_os 的 vector address load offset(此处就是其物理地址,这里mmu 是直接映射的) 放到 a1 中, 给opensbi 用, 参照 sbi_ecall_optee_handler 的 regs->a1锚点 "
–>2 initcall 按顺序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 #define preinit_early(fn) __define_initcall(preinit, 1, fn) #define preinit(fn) __define_initcall(preinit, 2, fn) #define preinit_late(fn) __define_initcall(preinit, 3, fn) #define early_init(fn) __define_initcall(init, 1, fn) #define early_init_late(fn) __define_initcall(init, 2, fn) #define service_init(fn) __define_initcall(init, 3, fn) #define service_init_late(fn) __define_initcall(init, 4, fn) #define driver_init(fn) __define_initcall(init, 5, fn) #define driver_init_late(fn) __define_initcall(init, 6, fn) #define release_init_resource(fn) __define_initcall(init, 7, fn) #define boot_final(fn) __define_initcall(final, 1, fn)
–>3 thread_vector_table
1 2 3 4 5 6 7 8 9 10 11 12 13 14 FUNC thread_vector_table , : , .identity_map, , nobti .option push .option norvc j vector_std_smc_entry j vector_fast_smc_entry j vector_cpu_on_entry j vector_cpu_off_entry j vector_cpu_resume_entry j vector_cpu_suspend_entry j vector_fiq_entry j vector_system_off_entry j vector_system_reset_entry .option pop END_FUNC thread_vector_table
中断处理 原生的 riscv 下并没有安全世界/非安全世界的区分, 没有对中断进行区分. 而 arm trustzone 下可以根据 SCR.NS bit 位来标记进入安全世界还是非安全世界或者在进 monitor 时, 是来自于安全世界还是非安全世界. 且 arm 在硬件上做到了将非安全中断绑定到 irq 事件, 安全中断绑定到了 fiq 事件. irq 由 REE 处理, fiq 由 TEE 处理.
因此 riscv 想做 tee 方案,
需要由软件管理安全世界/非安全世界的运行状态
需要有软件管理非安全中断和安全中断 上述这两点增加了软件实现的复杂度
看下 nuclei 上怎么做到上述这两点的
安全状态标记
opensbi 中实现 ATF 类似的功能, 不同的是, 需要由软件维护安全状态.
opteeos 初始化阶段, opensbi 在平台初始化节点, 通过 mret 进入 optee_os, optee_os 完成初始化后返回 opensbi, opensbi 需要保存 optee 的 context, 即安全世界上下文
ree os 运行阶段, 当需要 optee 服务时, ree os 通过 ecall 发送 optee 请求, opensbi 收到请求, 保存当前 cpu 状态到非安全 context, 然后恢复安全世界上下文, 将请求给 optee_os 处理. optee_os 处理完后, 发送 ecall 到 opensbi, opensbi 保存安全世界上下文恢复非安全世界上下文, 保存 optee 返回值, 恢复到 ree os 执行. 维护安全状态中引入了多个数组指针, 保存当前 cpu 安全世界上下文/非安全世界上下文的指针. 以及 next context
从 next context 的 sec_attr 判断是来自 安全世界还是非安全世界
1 2 3 4 5 6 static int is_caller_non_secure (void ) { cpu_context_t *ctx; ctx = cm_get_next_context(); return (ctx->sec_attr == NON_SECURE); }
安全世界状态切换时, 需要更新维护 next context. 这点是比较容易理解的, 无论当前 cpu 是处在安全世界(TEE)还是非安全世界(TEE) 中, 进入这个模式前, 都需要 opensbi 切换世界状态的代码参与, 而这个代码 cm_set_next_context
维护了 sec_attr 的状态.
而 arm 类似判断来自于安全世界/非安全世界的 smc 调用时, 用的 SCR.NS 硬件状态.
维护中断状态 要实现类似 arm FIQ/IRQ 的能力, nuclei 做了如下方案
软件维护了一个表记录哪些中断是安全中断 (arm 使用 group 标记)
安全中断应该由 M-mode 响应还是由 S-mode 响应
进入 TEE, 设置安全中断由 S-mode 响应, 非安全中断由 M-mode 响应.
进入 REE, 设置安全中断由 M-mode 响应, 非安全中断由 S-mode 响应.
当处在 M-mode 时, 中断都由 M-mode 响应, 根据中断类型再分发给 TEE/REE
plic_secure_int 数组中每个元素是由 hartid 和中断号组成,各占16bit, plic_secure_int[0]是个特殊值记录安全中断总个数。
plic_secure_int[0]:表示有 plic_secure_int[0] & 0xFFFF 个安全中断
plic_secure_int[x] 表示 plic_secure_int[x] & 0xFFFF 这个安全中断绑定到了hartid为plic_secure_int[x] >> 16 的core上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 int plic_register_sec_interrupt (unsigned int intr) { hartid = current_hartid(); for (i = 0 ; i < cnt ; i++) if ((plic_secure_int[i + 1 ] & 0xFFFF ) == intr) return true ; plic_secure_int[cnt + 1 ] = (hartid << 16 ) | intr; plic_secure_int[0 ] = (0xFFFF << 16 ) | (cnt + 1 ); } int plic_is_sec_interrupt (int intr) { int i; for (i = 0 ; i < (plic_secure_int[0 ] & 0xFFFF ); i++) if (intr == (plic_secure_int[i+1 ] & 0xFFFF )) return true ; return false ; } void switch_plic_int_enable_mode (int next_state) { int en_mode, plic_int_num; int i, j; unsigned int * secint; plic_int_num = irqchip_plic_get_interrupt_num(); secint = plic_get_sec_interrupt_tab(); for (i = 1 ; i <= plic_int_num; i++) { en_mode = irqchip_plic_get_en_mode(i, NON_SECURE); if (en_mode == -1 || plic_is_sec_interrupt(i)) continue ; else { irqchip_plic_set_en_mode((u32*)&i, next_state == NON_SECURE, NON_SECURE); } } for (j = 0 ; j < (secint[0 ] & 0xFFFF ); j++) irqchip_plic_set_en_mode(&secint[j+1 ], !(next_state == NON_SECURE), SECURE); } int irqchip_plic_set_en_mode (unsigned int *pintid, unsigned int mode, int secure) { hartid = current_hartid(); if (secure == 0 ) { if ((*pintid >> 16 ) != 0xFFFF ) { if ((*pintid >> 16 ) != hartid) return SBI_EFAIL; } else { *pintid = (hartid << 16 ) | (*pintid & 0xFFFF ); } int_id = *pintid & 0xFFFF ; } else int_id = *pintid; plic = plic_hartid2data[hartid]; int_src_idx = int_id / 32 ; ... ie_val = plic_get_ie(plic, plic_hartid2context[hartid][!!mode], int_src_idx); ie_val |= 1 << (int_id % 32 ); plic_set_ie(plic, plic_hartid2context[hartid][!!mode], int_src_idx, ie_val); ie_val = plic_get_ie(plic, plic_hartid2context[hartid][!mode], int_src_idx); ie_val &= ~(1 << (int_id % 32 )); plic_set_ie(plic, plic_hartid2context[hartid][!mode], int_src_idx, ie_val); return 0 ; } void plic_set_ie (struct plic_data *plic, u32 cntxid, u32 word_index, u32 val) { plic_ie = (void *)plic->addr + PLIC_ENABLE_BASE + PLIC_ENABLE_STRIDE * cntxid; writel(val, plic_ie + word_index * 4 ); }
与 arm 差异
维护中断是否是安全中断/非安全中断
nuclei riscv 哪个中断号是安全的, 哪个是不安全的, 由软件维护
arm 由设置 gic group 寄存器确定了, 然后硬件维护中断状态, 根据运行模式将中断绑定到 fiq 或 irq 中断的处理:
nuclei riscv , optee 只处理安全中断, ree 只处理非安全中断, M-mode opensbi 处理剩下的, 因为在运行到 tee 时, 非安全中断的 S-mode ie 关闭了, 在运行到 ree 时, 安全中断在 S-mode ie 被关闭了
arm 上, tee ree 还是会处理 irq fiq, 需要陷入到 monitor 或 EL3 转给对方.