赞
踩
背景:
这周本来应该发“Linux进程地址空间管理”的,但是由于准备驾考等原因耽误了。插入一篇几个月前写的文章“ATF实现原理”。这篇文章主要讲解ATF的开机流程和安全空间与非安全空间的切换原理。文章是基于MTK平台写的,也以此纪念下在MTK工作的日子——那是我职业生涯中最美好的日子。
目录
1-2、Secure world 与 Non Secure world 2
2-1、MTK平台Android开机流程以及ATF的内存布局 7
2-3、Non-Secure world与 Secure world之间的切换 16
2-3-2、Non-Secure world到 Secure world的切换 17
2-3-3、从 Secure world返回到Non-Secure world 20
SPSR与CPSR寄存器中每个bit的含义相同,在发生异常的时候CPSR会保存在SPSR中。CPSR/SPSR是最重要的寄存器之一,它保存了指令运行状态,异常类型、全局中断开关、 Exception LEVE 等信息。 其中SPSR的M[3:0]保存了程序所运行的Exception LEVE。M[3:0]具体定义如下面截图所示。
M[3:2]用于设置EL0~EL3,M[1]保留,M[0]表明使用SP_EL0 还是使用SP_ELX,这里的X指当前所处的Exception LEVE编号。
在Android系统中应用程序运行在Non-secure EL0,linux kernel运行在Non-secure EL1 ,TEE比如trustonic运行在Secure EL0和Secure EL1。Android和TEE的切换都必须经过EL3的monitor,在mtk平台上ATF就是一个运行在EL3上面的monitor。
Non Secure world是如何切换到Secure world的呢?
首先Non Secure world运行的程序需要切换到EL3,在EL3中monitor会通过设置寄存器SCR( Secure Configuration Register)的NS bit来切换到Secure world。
Non-secure world与Secure world是如何隔离开来的?
查看"arm 架构参考手册"有下面这段描述:处于Secure state的时候PE可以访问 Secure物理地址空间也可以访问Non Secure物理地址空间;处于Non-secure state的时候PE只可以访问Non Secure物理地址空间,不可以访问Secure物理地址空间。我们知道PE是通过地址来访问所有的硬件资源的。只要我们把一些硬件资源的地址区间映射到Non-secure world,另一些硬件资源(比如spi、部分dram)的地址区间映射到Secure world,就可以实现Non-secure world与Secure world的隔离了。
怎么实现将地址映射到Non-secure world或者Secure world?
操作系统(android 和TEE)使用的地址都是虚拟地址,虚拟地址都是需要页表转换之后才能访问到实际的物理地址。转换的过程是由MMU来完成,TTBR是页表基地址。页表中包含多个enrty,每一个entry都是按照一定规则来填写的。每一个entry中包含下一级(页表或则目标page)的物理地址和描述符(table descriptor/Block descriptor/ Page descriptor)。虚拟地址转换为物理地址的过程就是MMU解析页表找到目标物理页的过程。
关于页表的详细信息可以到arm官网下载arm架构参考手册"DDI0487A_i_armv8_arm.pdf",这里只看与secure 相关的部分。下面是一个Page descriptor各个位域的含义,可以看到其中有一个bit是NS,在内存访问的时候MMU会检查这个bit。如果NS为0,而且访问请求来至Non-secure world,这个访问请求会被忽略,MMU会触发一个Permission fault。
如果触发异常,程序会跳转到异常向量表去执行。Arrch64的异常类型和异常在异常向量表中的偏移见下表。整个异常向量表的大小为0x800字节,每一个entry都是0x80对齐。异常总共有4种:synchronous、IRQ、FIQ、SError。根据异常发生的exception level又分为4类:1)发生异常的exception level栈指针使用SP_EL0;2)发生异常的exception level栈指针使用SP_ELx;3)低level触发异常进入高level,而且低level使用的是64位架构;4)低level触发异常进入高level,而且低level使用的是32位架构。以linux kernel为例,kernel中收到中断将进入第2类异常入口;32位应用程序系统调用进入第3类异常入口;64位应用程序系统调用进入第4类异常入口。
与32位架构不同,64位架构的异常向量表地址不固定,在启动的时候由软件将异常向量表的地址设置到专用寄存器VBAR(Vector Base Address Register)中。除了上面的异常之外还有一种特殊的异常"复位",复位是一种最高优先级的异常,复位异常向量表存放在寄存器RVBAR_ELx 中。从异常向量表中可知不同Exception level之间是可以切换的。为此arm定义了几条用于不同EL跳转的指令。其中HVC、SMC、SVC、可以分别从低level进入EL2、EL3、EL1。这几条指令的执行将会触发同步异常(synchronous)根据具体场景进入到异常向量表种对应的entry。
前面的几条指令都是由低level进入高level,如果要返回原来的level该怎么办?比如应用程序运行在EL0,发起系统调用进入到EL1,执行完成之后如何返回EL0?
指令ERET就是为这种场景准备的,在发生异常的时候,异常的下一条指令地址将被保存在ELR寄存器中,异常返回的时候ERET将自动把ELR中的地址保存到PC中继续异常之前的程序执行。
前一章讲了arm体系架构对Secure的支持。这一章将从软件的角度来看arm Non-secure state和Secure state切换的具体实现。前面1-2节了解到Non-secure与Secure之间的切换需要经过EL3(secure monitor),本章要讲的ATF(Arm Trust Firmware)就是运行在EL3上的一个monitor。
开机过程中preloader把atf加载到sram中,ATF在sram中的内存布局如下:
从"开机流程"中可以看到,在进入ATF之前系统处于preloader阶段。ATF的运行需要依赖preloader提供的参数。其中有三个重要的参数:⑴ LK入口地址;⑵ TEE 入口地址;
⑶ ATF入口地址。下面就来分析这三个地址如何获取的。
- vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/mt6797/src/core/main.c
- void main(u32 *arg)
- {
- ............
- if (0 != bldr_load_images(&jump_addr)) {
- print("%s Second Bootloader Load Failed\n", MOD);
- goto error;
- }
- ............
- trustzone_post_init();
- ............
- bldr_jump64(jump_addr, jump_arg, sizeof(boot_arg_t));
- ............
- }
这是preloader的main函数,我们只关注它做的三件事:加载lk、atf、tee等到内存;设置atf参数;跳转到atf。下面分别讲解这三个函数的具体实现。
- static int bldr_load_images(u32 *jump_addr)
- {
- ............
- addr = CFG_UBOOT_MEMADDR;
- ret = bldr_load_part_lk(bootdev, &addr, &size);
- *jump_addr = addr;
- ............
- addr = CFG_ATF_ROM_MEMADDR;
- ret = bldr_load_tee_part("tee1", bootdev, &addr, 0, &size);
- ............
- }
函数bldr_load_images加载后续各个image,这里我们只关注lk、atf、tee。函数bldr_load_part_lk加载lk到地址CFG_UBOOT_MEMADDR。这里找到了前面提到的三个重要参数的第一个"LK入口地址"。函数bldr_load_tee_part加载atf到CFG_ATF_ROM_MEMADDR为起始的内存区域,然后加载tee到指定地址,这里的指定地址包含在tee头中。
- int bldr_load_tee_part(char *name, blkdev_t *bdev, u32 *addr, u32 offset, u32 *size)
- {
- ............
- ret = part_load(bdev, part, addr, offset, size); //加载ATF
-
- u32 tee_addr = 0;
- u32 next_offset = sizeof(part_hdr_t) + *size;
-
- ret = part_load(bdev, part, &tee_addr, next_offset, size); //加载TEE到指定地址
- ............
- tee_set_entry(tee_addr); //设置tee加载地址为tee入口地址
- ............
- }
-
- void tee_set_entry(u32 addr)
- {
- tee_entry_addr = addr;
- ............
- }
前面函数tee_set_entry设置了tee入口地址,下面将进一步将tee入口地址设置到atf的参数中。
- void trustzone_post_init(void)
- {
- atf_arg_t_ptr teearg = (atf_arg_t_ptr)trustzone_get_atf_boot_param_addr();
- teearg->atf_magic = ATF_BOOTCFG_MAGIC;
- teearg->tee_entry = tee_entry_addr;
- teearg->tee_boot_arg_addr = TEE_BOOT_ARG_ADDR;
- ............
Atf参数字段teearg->tee_entry保存了tee入口地址,后面atf中将用到。这也是前面提到的三个重要参数中的第二个"TEE 入口地址"。参数准备好了接下来该跳转到ATF中运行了。
- void trustzone_jump(u32 addr, u32 arg1, u32 arg2)
- {
- ............
- jumparch64(addr, arg1, arg2, trustzone_get_atf_boot_param_addr());
- }
前面Main函数中bldr_jump64会调用到函数trustzone_jump,接着进一步调用jumparch64实现从preloader到ATF的跳转。这里需要注意的是函数jumparch64传递的参数。尤其是第一个LK 入口地址,第三个ATF参数地址。函数jumparch64是汇编代码实现,参数传递方式是r0 ~ r3分别保存第一个到第四个参数。
- jumparch64:
- MOV r4, r1 /* r4 argument */
- MOV r5, r2 /* r5 argument */
- MOV r6, r0 /* keep LK jump addr */
-
- MOV r7, r3 /* r3 = TEE boot entry, relocate to r7 */
-
- /* setup the reset vector base address after warm reset to Aarch64 */
- LDR r0, =bl31_base_addr
- LDR r0,[r0]
-
- LDR r1, =rst_vector_base_addr
- LDR r1,[r1]
- str r0,[r1]
- ............
- /* do warm reset:reset request */
- MRC p15,0,r0,c12,c0,2
- orr r0, r0, #2
- MCR p15,0,r0,c12,c0,2
- DSB
- ISB
- MOV r0, #0xC0000000
- .globl WFI_LOOP
- WFI_LOOP:
- WFI
- B WFI_LOOP
函数jumparch64开始将r1、r2、r0、r3 分别保存在了r4、r5、r6、r7。这里需要记住的是LK的入口地址保存在了r6中。接下来将bl31_base_addr设置了到了寄存器rst_vector_base_addr中。rst_vector_base_addr就是"异常"那一节中讲到的RVBAR。我们知道RVBAR中保存的是复位向量表的基地址。这里将bl31_base_addr设置到了RVBAR中,下面看看bl31_base_addr是怎么来的:
u32 bl31_base_addr = BL31_BASE;
#define BL31_BASE (CFG_ATF_ROM_MEMADDR + BL31_VECTOR_SIZE + ATF_HEADER_SIG_SIZE)
bl31_base_addr = ATF加载地址 + 异常向量表大小 + ATF 参数头,这个值计算后是:0x00101000。
这就是前面提到的三个重要参数中的第三个"ATF入口地址"。
设置了复位向量表之后就发起复位请求,复位之后程序就从Preloader进入到了ATF。
前一节中preloader通过复位进入ATF。复位之后程序首先跳转到复位向量表。从前面"MTK平台Android开机流程"一节可知,复位向量表就放在bl31_entrypoint.o的开头。
vendor/mediatek/proprietary/trustzone/atf/v1.0/bl31/aarch64/bl31_entrypoint.S
.globl bl31_entrypoint .globl bl31_on_entrypoint func bl31_entrypoint ldr x8, =BOOT_ARGUMENT_LOCATION str w4, [x8] ldr x8, =BOOT_ARGUMENT_SIZE str w5, [x8] ldr x8, =BL33_START_ADDRESS str w6, [x8] ldr x8, =TEE_BOOT_INFO_ADDR str w7, [x8] ............ adr x1, runtime_exceptions msr vbar_el3, x1 isb ............ bl bl31_early_platform_setup ............ bl bl31_main b el3_exit
在复位处理函数中做了这么几件重要的事情;保存preloader传递过来的参数,比较重要的是LK的入口地址保存到了BL33_START_ADDRESS中;设置异常限量表的基地址到VBAR中;早期平台初始化,这里面完成了Non-secure world与Secure world上下文的初始化;进入ATF的main函数中进行环境和服务的初始化;el3_exit退出ATF进入LK。具体细节后面一一详解。
- void bl31_early_platform_setup(bl31_params_t *from_bl2, void *plat_params_from_bl2)
- {
- ............
- memcpy((void *)>eearg, (void *)(uintptr_t)TEE_BOOT_INFO_ADDR, sizeof(atf_arg_t));
- atf_arg_t_ptr teearg = >eearg;
- ............
- SET_PARAM_HEAD(&bl32_image_ep_info,
- PARAM_EP,
- VERSION_1,
- 0);
- SET_SECURITY_STATE(bl32_image_ep_info.h.attr, SECURE);
- bl32_image_ep_info.pc = teearg->tee_entry; //设置TEE的入口地址
- bl32_image_ep_info.spsr = plat_get_spsr_for_bl32_entry();
-
- SET_PARAM_HEAD(&bl33_image_ep_info,
- PARAM_EP,
- VERSION_1,
- 0);
- bl33_image_ep_info.pc = plat_get_ns_image_entrypoint(); //设置LK的入口地址
- bl33_image_ep_info.spsr = plat_get_spsr_for_bl33_entry();
- ............
这个函数做的比较重要的两件事就是:设置TEE的入口地址到bl32_image_ep_info.pc;设置LK的入口地址到bl33_image_ep_info.pc。
- unsigned long plat_get_ns_image_entrypoint(void)
- {
- return BL33_START_ADDRESS;
- }
BL33_START_ADDRESS;就是preloader传递进来的LK入口地址。
- void bl31_main(void)
- {
- ............
-
- runtime_svc_init();
- ............
- bl31_prepare_next_image_entry();
- ............
- }
bl31_main中我们比较关注的是两件事:runtime service的初始化;进入下一个image(LK)前的准备。下面先来看runtime service的初始化。
ATF中实现了多个runtime service,每一个runtime service都是用结构体rt_svc_desc来描述。服务通过start_oen、end_oen、call_type编码后来唯一识别。ATF中总共支持128个runtime service,其中call_type可以区分请求的服务是高64个还是低64个,start_oen和end_oen指定了编码宽度。Init字段保存了服务的初始化函数。Handle保存了服务处理函数。
- typedef struct rt_svc_desc {
- uint8_t start_oen;
- uint8_t end_oen;
- uint8_t call_type;
- const char *name;
- rt_svc_init_t init;
- rt_svc_handle_t handle;
- } rt_svc_desc_t;
ATF编译之后这些服务都放在rt_svc_descs这个section中。
- void runtime_svc_init(void)
- {
- ............
- rt_svc_descs_num = RT_SVC_DESCS_END - RT_SVC_DESCS_START;
- rt_svc_descs_num /= sizeof(rt_svc_desc_t);
- ............
- rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
- for (index = 0; index < rt_svc_descs_num; index++) {
- ............
- if (rt_svc_descs[index].init) {
- rc = rt_svc_descs[index].init();
- if (rc) {
- ERROR("Error initializing runtime service %s\n",
- rt_svc_descs[index].name);
- continue;
- }
- }
-
- start_idx = get_unique_oen(rt_svc_descs[index].start_oen,
- rt_svc_descs[index].call_type);
- end_idx = get_unique_oen(rt_svc_descs[index].end_oen,
- rt_svc_descs[index].call_type);
-
- for (; start_idx <= end_idx; start_idx++)
- rt_svc_descs_indices[start_idx] = index;
- ............
- }
函数runtime_svc_init 的主要工作就是初始化数组rt_svc_descs_indices。将runtime service在rt_svc_descs中的索引写入数组rt_svc_descs_indices中对应位置。
完成runtime service的初始化之后就为跳转到下一个image(LK)做准备。
- void bl31_prepare_next_image_entry(void)
- {
- ............
- image_type = bl31_get_next_image_type();
- next_image_info = bl31_plat_get_next_image_ep_info(image_type);
- ............
- cm_init_context(read_mpidr_el1(), next_image_info);
- cm_prepare_el3_exit(image_type);
- }
前面函数首先获取image_type,接下来我们程序将跳转到LK中,LK运行在Non-secure环境中,所以这里的image_type等于NON_SECURE,接下来设置Non-secure上下文,最后在cm_prepare_el3_exit中将栈指针SP指向上下文内存。
- entry_point_info_t *bl31_plat_get_next_image_ep_info(uint32_t type)
- {
- if (type == NON_SECURE)
- return &bl33_image_ep_info;
- else
- return &bl32_image_ep_info;
- ............
- }
由前面分析可知函数bl31_plat_get_next_image_ep_info 将返回bl33_image_ep_info,并作为参数传递到cm_init_context中进一步初始化Non-secure上下文。
在继续讲解之前先认识一个结构体cpu_context_t,这就是前面提到的执行上下文,他包含了系统通用寄存器,和其他系统专用寄存器。部分寄存器内容如下描述。
- void cm_init_context(uint64_t mpidr, const entry_point_info_t *ep)
- {
- ctx = cm_get_context_by_mpidr(mpidr, security_state);
- ............
- if (security_state != SECURE)
- scr_el3 |= SCR_NS_BIT; //设置为Non-secure
- ............
- /* Populate EL3 state so that we've the right context before doing ERET */
- state = get_el3state_ctx(ctx);
- write_ctx_reg(state, CTX_SCR_EL3, scr_el3);
- write_ctx_reg(state, CTX_ELR_EL3, ep->pc);
- write_ctx_reg(state, CTX_SPSR_EL3, ep->spsr);
- ............
cm_init_context中初始化Non-secure上下文,最重要的两个地方是配置SCR寄存器为Non-secure;和设置下一步跳转的程序入口地址ep->pc设置到ctx-> el3state_ctxde 的CTX_ELR_EL3中。在前面函数bl31_early_platform_setup中有讲到ep->pc中保存的是LK的入口地址。上下文设置完成接下来就需要将这些值设置到栈中,以便最终加载到对应寄存器。
- void cm_prepare_el3_exit(uint32_t security_state)
- {
- cpu_context_t *ctx = cm_get_context(security_state);
- ............
- cm_set_next_context(ctx);
- }
-
- static inline void cm_set_next_context(void *context)
- {
- ............
-
- __asm__ volatile("msr spsel, #1\n"
- "mov sp, %0\n"
- "msr spsel, #0\n"
- : : "r" (context));
- }
上面两个函数做的事就是将上下文设置到栈中,让sp指向ctx。走到这里ATF的启动就完成了,下一个image的运行上下文也准备好了,最后要做的是就是跳转到下一个image中去执行。
- el3_exit: ; .type el3_exit, %function
-
- mov x17, sp
- msr spsel, #1
- str x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
-
- ldr x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
- ldp x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3] //将spsr和elr分别保存到x16和x17中
- msr scr_el3, x18
- msr spsr_el3, x16
- msr elr_el3, x17 //elr 也就是前面保存的LK入口地址加载到elr中。
-
- b restore_gp_registers_eret
-
- func restore_gp_registers_eret
- ............
- ldp x30, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
- msr sp_el0, x17
- ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
- eret
函数el3_exit中先将LK的入口地址保存到elr中然后调用异常返回指令eret返回。前面讲过eret这条指令的调用将会导致elr加载到pc中,程序就会到elr指向的地址继续执行。这就完成ATF到LK的跳转。
下面以tbase为例来讲解Non-Secure world与 Secure world之间的切换过程。文件tbase_fastcall中定义了名为tbase_fastcall的runtime service。
vendor/mediatek/proprietary/trustzone/atf/v1.0/services/spd/tbase/tbase_fastcall.c
- DECLARE_RT_SVC(
- tbase_fastcall,
- OEN_TOS_START,
- OEN_TOS_END,
- SMC_TYPE_FAST,
- tbase_fastcall_setup,
- tbase_fastcall_handler
- );
宏DECLARE_RT_SVC根据传入的参数设置结构体rt_svc_desc的各个成员,并且保证连接到rt_svc_descs这个section。tbase_fastcall_setup是这个service的初始化函数,它在函数runtime_svc_init中调用,主要工作是设置Secure上下文,如下:
- void runtime_svc_init(void)
- {
- ............
- if (rt_svc_descs[index].init) {
- rc = rt_svc_descs[index].init();
- ............
- }
-
- int32_t tbase_fastcall_setup(void)
- {
- entry_point_info_t *image_info;
- image_info = bl31_plat_get_next_image_ep_info(SECURE);
- ............
- tbase_init_eret(image_info->pc,TBASE_AARCH32);
- }
这里函数bl31_plat_get_next_image_ep_info将返回 bl32_image_ep_info。image_info->pc作为参数传递到tbase_init_eret中,前面函数bl31_early_platform_setup中有讲到image_info->pc保存的是TEE的入口地址。其中函数tbase_init_eret设置了Secure上下文。
- static void tbase_init_eret( uint64_t entrypoint, uint32_t rw ) {
- ............
- scr &= ~SCR_NS_BIT;
- ............
- ctx = cm_get_context(SECURE);
- ............
- state = get_el3state_ctx(ctx);
- write_ctx_reg(state, CTX_SPSR_EL3, spsr);
- write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
- write_ctx_reg(state, CTX_SCR_EL3, scr);
- ............
- }
这个函数中两个重要的地方是配置SCR寄存器为Secure;和设置下一步跳转的程序入口地址entrypoint,设置到ctx-> el3state_ctxde 的CTX_ELR_EL3中。这里的entrypoint就是前面传入的TEE入口地址。
Linux kernel运行在Non-Secure EL1,如果要进入TEE,首先需要调用汇编指令smc进入EL3,由monitor(ATF)来完成Non-Secure world到 Secure world的切换。在mtk平台上函数mt_secure_call是进入EL3的入口函数,它调用smc指令并通过x0~x3传入四个参数。其中x0中是多个位域的一个编码,根据它可以找到哪个service以及service中的哪一项服务。
- static noinline int mt_secure_call(u64 function_id, u64 arg0, u64 arg1, u64 arg2)
- {
- register u64 reg0 __asm__("x0") = function_id;
- register u64 reg1 __asm__("x1") = arg0;
- register u64 reg2 __asm__("x2") = arg1;
- register u64 reg3 __asm__("x3") = arg2;
- int ret = 0;
-
- asm volatile ("smc #0\n" : "+r" (reg0) :
- "r"(reg1), "r"(reg2), "r"(reg3));
-
- ret = (int)reg0;
- return ret;
- }
前面运行指令smc触发一个同步异常,进入EL3异常向量表对应同步异常入口。
- smc_handler64:
- ............
-
- mov x6, sp
- ............
- ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
- ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
- orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
-
- adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
-
- /* Load descriptor index from array of indices */
- adr x14, rt_svc_descs_indices
- ldrb w15, [x14, x16]
- ............
- lsl w10, w15, #RT_SVC_SIZE_LOG2
- ldr x15, [x11, w10, uxtw]
- ............
- mrs x16, spsr_el3
- mrs x17, elr_el3
- mrs x18, scr_el3
- stp x16, x17, [x6, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
- str x18, [x6, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
- ............
- blr x15
函数smc_handler64主要做了三件事:
⑴ 根据function_id找到对应的runtime service,查找方法:
Index = (function_id >> 24 & 0x3f) | ((function_id >> 31) << 6);
Handler = __RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE + rt_svc_descs_indices[Index] << 5
__RT_SVC_DESCS_START__为rt_svc_descs的起始地址,RT_SVC_DESC_HANDLE为服务处理函数handle在结构体rt_svc_desc中的偏移,左移5,是因为结构体rt_svc_desc大小为32字节。
⑵ 保存Non-Secure world中的spsr_el3 、elr_el3、scr_el3到栈中。
⑶ 跳转到runtime service的处理函数handle中执行
前面提到了runtime service,tbase_fastcall,这个service的服务处理函数是tbase_fastcall_handler其具体实现如下:
- static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
- uint64_t x1,
- uint64_t x2,
- uint64_t x3,
- uint64_t x4,
- void *cookie,
- void *handle,
- uint64_t flags)
- {
- uint64_t mpidr = read_mpidr();
- uint32_t linear_id = platform_get_core_pos(mpidr);
- tbase_context *tbase_ctx = &secure_context[linear_id];
- int caller_security_state = flags&1;
-
- if (caller_security_state==SECURE) {
- switch(maskSWdRegister(smc_fid)) {
- case TBASE_SMC_FASTCALL_RETURN: {
- DBG_PRINTF( "tbase_fastcall_handler TBASE_SMC_FASTCALL_RETURN\n\r");
- tbase_synchronous_sp_exit(tbase_ctx, 0, 1);
- }
- ............
- else
- {
- gp_regs_t *ns_gpregs = get_gpregs_ctx((cpu_context_t *)handle);
- write_ctx_reg(ns_gpregs, CTX_GPREG_X0, smc_fid ); // These are not saved yet
- write_ctx_reg(ns_gpregs, CTX_GPREG_X1, x1 );
- write_ctx_reg(ns_gpregs, CTX_GPREG_X2, x2 );
- write_ctx_reg(ns_gpregs, CTX_GPREG_X3, x3 );
- cm_el1_sysregs_context_save(NON_SECURE);
- ............
- tbase_synchronous_sp_entry(tbase_ctx);
- cm_el1_sysregs_context_restore(NON_SECURE);
- cm_set_next_eret_context(NON_SECURE);
- return 0;
- }
Linux kernel运行在Non-Secure中,所以这里程序会走下一个分支。这里主要做了三件重要的事:⑴保存异常前的寄存器值到Non-secure上下文中⑵调用函数tbase_synchronous_sp_entry进入TEE环境 ⑶准备Non-secure上下文,异常返回,返回到linux kernel中。下面分别讲解。
- uint64_t tbase_synchronous_sp_entry(tbase_context *tbase_ctx)
- {
- ............
- cm_set_next_eret_context(SECURE);
-
- rc = tbase_enter_sp(&tbase_ctx->c_rt_ctx);
- ............
- }
-
- void cm_set_next_eret_context(uint32_t security_state)
- {
- cpu_context_t *ctx;
-
- ctx = cm_get_context(security_state);
- assert(ctx);
-
- cm_set_next_context(ctx);
- }
函数cm_get_context设置secure上下文到栈中,然后调用函数cm_set_next_context进入TEE程序。
- func tbase_enter_sp
- mov x3, sp
- str x3, [x0, #0] //保存sp到tbase_ctx->c_rt_ctx中
- sub sp, sp, #TSPD_C_RT_CTX_SIZE
-
- /* Save callee-saved registers on to the stack */
- stp x19, x20, [sp, #TSPD_C_RT_CTX_X19]
- stp x21, x22, [sp, #TSPD_C_RT_CTX_X21]
- stp x23, x24, [sp, #TSPD_C_RT_CTX_X23]
- stp x25, x26, [sp, #TSPD_C_RT_CTX_X25]
- stp x27, x28, [sp, #TSPD_C_RT_CTX_X27]
- stp x29, x30, [sp, #TSPD_C_RT_CTX_X29] //保存lr(x30)到栈中
-
- b el3_exit
func表明tbase_enter_sp是一个函数,调用这个函数会将函数返回地址保存到lr(x30)中,这一点很重要,后面还会用到。然后跳转到el3_exit 做异常返回,这个代码块前面讲过。在函数tbase_fastcall_setup中有将secure上下文中的elr设置为了TEE入口地址所以这里异常返回,将会返回到TEE中。
程序进入TEE中完成指定的任务之后最终还会返回到linux kernel中。在返回到linux kernel之前会先返回到ATF中。下面接着再看tbase_fastcall_handler这个函数。这个函数有两个分支,前面讲了后一个分支。TEE是在Secure world运行的,在TEE返回的时候程序就会走到前一个分支,下面看程序具体如何运行:
- static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
- uint64_t x1,
- uint64_t x2,
- uint64_t x3,
- uint64_t x4,
- void *cookie,
- void *handle,
- uint64_t flags)
- {
- uint64_t mpidr = read_mpidr();
- uint32_t linear_id = platform_get_core_pos(mpidr);
- tbase_context *tbase_ctx = &secure_context[linear_id];
- int caller_security_state = flags&1;
-
- if (caller_security_state==SECURE) {
- switch(maskSWdRegister(smc_fid)) {
- case TBASE_SMC_FASTCALL_RETURN: {
- DBG_PRINTF( "tbase_fastcall_handler TBASE_SMC_FASTCALL_RETURN\n\r");
- tbase_synchronous_sp_exit(tbase_ctx, 0, 1);
- }
- ............
- else
- {
- ............
- }
函数先会调用tbase_synchronous_sp_exit来做实际的返回操作。
- func tbase_exit_sp
- mov sp, x0 //前面x0保存了栈指针
-
- ldp x19, x20, [x0, #(TSPD_C_RT_CTX_X19 - TSPD_C_RT_CTX_SIZE)]
- ldp x21, x22, [x0, #(TSPD_C_RT_CTX_X21 - TSPD_C_RT_CTX_SIZE)]
- ldp x23, x24, [x0, #(TSPD_C_RT_CTX_X23 - TSPD_C_RT_CTX_SIZE)]
- ldp x25, x26, [x0, #(TSPD_C_RT_CTX_X25 - TSPD_C_RT_CTX_SIZE)]
- ldp x27, x28, [x0, #(TSPD_C_RT_CTX_X27 - TSPD_C_RT_CTX_SIZE)]
- ldp x29, x30, [x0, #(TSPD_C_RT_CTX_X29 - TSPD_C_RT_CTX_SIZE)] //将lr值从栈中加载到lr(x30)中
- mov x0, x1
- ret
汇编指令ret做程序返回,这条指令会导致lr自动加载到pc中,然后程序跳转到lr指定的地址运行。这个与eret指令类似,不过那里保存返回地址的是elr。这里的函数返回又会返回到函数tbase_fastcall_handler中执行,如下:
- static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
- uint64_t x1,
- uint64_t x2,
- uint64_t x3,
- uint64_t x4,
- void *cookie,
- void *handle,
- uint64_t flags)
- {
- ............
- if (caller_security_state==SECURE) {
- ............
- else
- {
- ............
- tbase_synchronous_sp_entry(tbase_ctx); //这里进入TEE
- cm_el1_sysregs_context_restore(NON_SECURE); // TEE返回后会返回到这里执行
- cm_set_next_eret_context(NON_SECURE);
- return 0;
- }
cm_el1_sysregs_context_restore(NON_SECURE);将SECURE环境的寄存器保存到NON_SECURE的上下文中,这其中就包含TEE环境的返回值。然后通过函数cm_set_next_eret_context(NON_SECURE);设置NON_SECURE的上下文到栈中,最后程序返回异常处理函数smc_handler64通过el3_exit返回NON_SECURE world中的linux kernel
- void cm_set_next_eret_context(uint32_t security_state)
- {
- cpu_context_t *ctx;
-
- ctx = cm_get_context(security_state);
- assert(ctx);
- cm_set_next_context(ctx);
- }
函数cm_set_next_eret_context执行完成之后,函数tbase_fastcall_handler就返回了,返回到异常处理函数smc_handler64中,具体如下:
- smc_handler64:
- ............
- mov x6, sp
- ............
- blr x15 //根据function id进入对应的service handle
- el3_exit: ; .type el3_exit, %function //service handle函数返回后继续完成退出的工作
- ............
- msr scr_el3, x18
- msr spsr_el3, x16
- msr elr_el3, x17
- b restore_gp_registers_eret
-
- func restore_gp_registers_eret
- ............
- msr sp_el0, x17
- ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
- eret //实现异常返回
函数tbase_fastcall_handler中Non-secure分支会调用函数cm_el1_sysregs_context_save(NON_SECURE); 保存异常前的寄存器值到Non-secure上下文中,这其中就包含elr,由于是从linux kernel中调用smc下来的,所以这里elr保存的就是linux kernel中的地址,所以这里异常返回就会返回到linux中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。