当前位置:   article > 正文

ATF实现原理_arm atf

arm atf

背景:

    这周本来应该发“Linux进程地址空间管理”的,但是由于准备驾考等原因耽误了。插入一篇几个月前写的文章“ATF实现原理”。这篇文章主要讲解ATF的开机流程和安全空间与非安全空间的切换原理。文章是基于MTK平台写的,也以此纪念下在MTK工作的日子——那是我职业生涯中最美好的日子。

目录

一、    ARM体系架构基础    2

1-1、AArch64 Exception LEVE    2

1-2、Secure world 与 Non Secure world    2

1-3、异常    5

二、    ATF实现原理    7

2-1、MTK平台Android开机流程以及ATF的内存布局    7

2-2、ATF的启动流程    8

2-2-1、从Preloader到ATF    8

2-2-2、ATF的开机初始化    10

2-2-3、从ATF到LK    13

2-3、Non-Secure world与 Secure world之间的切换    16

2-3-1、t-base开机初始化    16

2-3-2、Non-Secure world到 Secure world的切换    17

2-3-3、从 Secure world返回到Non-Secure world    20

一、    ARM体系架构基础

1-1、AArch64 Exception LEVE

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编号。

 



1-2、Secure world 与 Non Secure world

在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。

 

1-3、异常

如果触发异常,程序会跳转到异常向量表去执行。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中继续异常之前的程序执行。

 

二、    ATF实现原理

前一章讲了arm体系架构对Secure的支持。这一章将从软件的角度来看arm Non-secure state和Secure state切换的具体实现。前面1-2节了解到Non-secure与Secure之间的切换需要经过EL3(secure monitor),本章要讲的ATF(Arm Trust Firmware)就是运行在EL3上的一个monitor。

2-1、MTK平台Android开机流程以及ATF的内存布局

 

开机过程中preloader把atf加载到sram中,ATF在sram中的内存布局如下:

2-2、ATF的启动流程

2-2-1、从Preloader到ATF

从"开机流程"中可以看到,在进入ATF之前系统处于preloader阶段。ATF的运行需要依赖preloader提供的参数。其中有三个重要的参数:⑴ LK入口地址;⑵ TEE 入口地址;

⑶ ATF入口地址。下面就来分析这三个地址如何获取的。

  1. vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/mt6797/src/core/main.c
  2. void main(u32 *arg)
  3. {
  4. ............
  5. if (0 != bldr_load_images(&jump_addr)) {
  6. print("%s Second Bootloader Load Failed\n", MOD);
  7. goto error;
  8. }
  9. ............
  10. trustzone_post_init();
  11. ............
  12. bldr_jump64(jump_addr, jump_arg, sizeof(boot_arg_t));
  13. ............
  14. }

这是preloader的main函数,我们只关注它做的三件事:加载lk、atf、tee等到内存;设置atf参数;跳转到atf。下面分别讲解这三个函数的具体实现。

  1. static int bldr_load_images(u32 *jump_addr)
  2. {
  3. ............
  4. addr = CFG_UBOOT_MEMADDR;
  5. ret = bldr_load_part_lk(bootdev, &addr, &size);
  6. *jump_addr = addr;
  7. ............
  8. addr = CFG_ATF_ROM_MEMADDR;
  9. ret = bldr_load_tee_part("tee1", bootdev, &addr, 0, &size);
  10. ............
  11. }

函数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头中。

  1. int bldr_load_tee_part(char *name, blkdev_t *bdev, u32 *addr, u32 offset, u32 *size)
  2. {
  3. ............
  4. ret = part_load(bdev, part, addr, offset, size); //加载ATF
  5. u32 tee_addr = 0;
  6. u32 next_offset = sizeof(part_hdr_t) + *size;
  7. ret = part_load(bdev, part, &tee_addr, next_offset, size); //加载TEE到指定地址
  8. ............
  9. tee_set_entry(tee_addr); //设置tee加载地址为tee入口地址
  10. ............
  11. }
  12. void tee_set_entry(u32 addr)
  13. {
  14. tee_entry_addr = addr;
  15. ............
  16. }

前面函数tee_set_entry设置了tee入口地址,下面将进一步将tee入口地址设置到atf的参数中。

  1. void trustzone_post_init(void)
  2. {
  3. atf_arg_t_ptr teearg = (atf_arg_t_ptr)trustzone_get_atf_boot_param_addr();
  4. teearg->atf_magic = ATF_BOOTCFG_MAGIC;
  5. teearg->tee_entry = tee_entry_addr;
  6. teearg->tee_boot_arg_addr = TEE_BOOT_ARG_ADDR;
  7. ............

Atf参数字段teearg->tee_entry保存了tee入口地址,后面atf中将用到。这也是前面提到的三个重要参数中的第二个"TEE 入口地址"。参数准备好了接下来该跳转到ATF中运行了。

  1. void trustzone_jump(u32 addr, u32 arg1, u32 arg2)
  2. {
  3. ............
  4. jumparch64(addr, arg1, arg2, trustzone_get_atf_boot_param_addr());
  5. }

前面Main函数中bldr_jump64会调用到函数trustzone_jump,接着进一步调用jumparch64实现从preloader到ATF的跳转。这里需要注意的是函数jumparch64传递的参数。尤其是第一个LK 入口地址,第三个ATF参数地址。函数jumparch64是汇编代码实现,参数传递方式是r0 ~ r3分别保存第一个到第四个参数。

  1. jumparch64:
  2. MOV r4, r1 /* r4 argument */
  3. MOV r5, r2 /* r5 argument */
  4. MOV r6, r0 /* keep LK jump addr */
  5. MOV r7, r3 /* r3 = TEE boot entry, relocate to r7 */
  6. /* setup the reset vector base address after warm reset to Aarch64 */
  7. LDR r0, =bl31_base_addr
  8. LDR r0,[r0]
  9. LDR r1, =rst_vector_base_addr
  10. LDR r1,[r1]
  11. str r0,[r1]
  12. ............
  13. /* do warm reset:reset request */
  14. MRC p15,0,r0,c12,c0,2
  15. orr r0, r0, #2
  16. MCR p15,0,r0,c12,c0,2
  17. DSB
  18. ISB
  19. MOV r0, #0xC0000000
  20. .globl WFI_LOOP
  21. WFI_LOOP:
  22. WFI
  23. 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。

 

2-2-2、ATF的开机初始化

前一节中preloader通过复位进入ATF。复位之后程序首先跳转到复位向量表。从前面"MTK平台Android开机流程"一节可知,复位向量表就放在bl31_entrypoint.o的开头。

vendor/mediatek/proprietary/trustzone/atf/v1.0/bl31/aarch64/bl31_entrypoint.S

  1. .globl bl31_entrypoint
  2. .globl bl31_on_entrypoint
  3. func bl31_entrypoint
  4. ldr x8, =BOOT_ARGUMENT_LOCATION
  5. str w4, [x8]
  6. ldr x8, =BOOT_ARGUMENT_SIZE
  7. str w5, [x8]
  8. ldr x8, =BL33_START_ADDRESS
  9. str w6, [x8]
  10. ldr x8, =TEE_BOOT_INFO_ADDR
  11. str w7, [x8]
  12. ............
  13. adr x1, runtime_exceptions
  14. msr vbar_el3, x1
  15. isb
  16. ............
  17. bl bl31_early_platform_setup
  18. ............
  19. bl bl31_main
  20. b el3_exit

在复位处理函数中做了这么几件重要的事情;保存preloader传递过来的参数,比较重要的是LK的入口地址保存到了BL33_START_ADDRESS中;设置异常限量表的基地址到VBAR中;早期平台初始化,这里面完成了Non-secure world与Secure world上下文的初始化;进入ATF的main函数中进行环境和服务的初始化;el3_exit退出ATF进入LK。具体细节后面一一详解。

  1. void bl31_early_platform_setup(bl31_params_t *from_bl2, void *plat_params_from_bl2)
  2. {
  3. ............
  4. memcpy((void *)>eearg, (void *)(uintptr_t)TEE_BOOT_INFO_ADDR, sizeof(atf_arg_t));
  5. atf_arg_t_ptr teearg = >eearg;
  6. ............
  7. SET_PARAM_HEAD(&bl32_image_ep_info,
  8. PARAM_EP,
  9. VERSION_1,
  10. 0);
  11. SET_SECURITY_STATE(bl32_image_ep_info.h.attr, SECURE);
  12. bl32_image_ep_info.pc = teearg->tee_entry; //设置TEE的入口地址
  13. bl32_image_ep_info.spsr = plat_get_spsr_for_bl32_entry();
  14. SET_PARAM_HEAD(&bl33_image_ep_info,
  15. PARAM_EP,
  16. VERSION_1,
  17. 0);
  18. bl33_image_ep_info.pc = plat_get_ns_image_entrypoint(); //设置LK的入口地址
  19. bl33_image_ep_info.spsr = plat_get_spsr_for_bl33_entry();
  20. ............

这个函数做的比较重要的两件事就是:设置TEE的入口地址到bl32_image_ep_info.pc;设置LK的入口地址到bl33_image_ep_info.pc。

  1. unsigned long plat_get_ns_image_entrypoint(void)
  2. {
  3. return BL33_START_ADDRESS;
  4. }

BL33_START_ADDRESS;就是preloader传递进来的LK入口地址。

  1. void bl31_main(void)
  2. {
  3. ............
  4. runtime_svc_init();
  5. ............
  6. bl31_prepare_next_image_entry();
  7. ............
  8. }

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保存了服务处理函数。

  1. typedef struct rt_svc_desc {
  2. uint8_t start_oen;
  3. uint8_t end_oen;
  4. uint8_t call_type;
  5. const char *name;
  6. rt_svc_init_t init;
  7. rt_svc_handle_t handle;
  8. } rt_svc_desc_t;

ATF编译之后这些服务都放在rt_svc_descs这个section中。

 

  1. void runtime_svc_init(void)
  2. {
  3. ............
  4. rt_svc_descs_num = RT_SVC_DESCS_END - RT_SVC_DESCS_START;
  5. rt_svc_descs_num /= sizeof(rt_svc_desc_t);
  6. ............
  7. rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
  8. for (index = 0; index < rt_svc_descs_num; index++) {
  9. ............
  10. if (rt_svc_descs[index].init) {
  11. rc = rt_svc_descs[index].init();
  12. if (rc) {
  13. ERROR("Error initializing runtime service %s\n",
  14. rt_svc_descs[index].name);
  15. continue;
  16. }
  17. }
  18. start_idx = get_unique_oen(rt_svc_descs[index].start_oen,
  19. rt_svc_descs[index].call_type);
  20. end_idx = get_unique_oen(rt_svc_descs[index].end_oen,
  21. rt_svc_descs[index].call_type);
  22. for (; start_idx <= end_idx; start_idx++)
  23. rt_svc_descs_indices[start_idx] = index;
  24. ............
  25. }

函数runtime_svc_init 的主要工作就是初始化数组rt_svc_descs_indices。将runtime service在rt_svc_descs中的索引写入数组rt_svc_descs_indices中对应位置。

 

2-2-3、从ATF到LK

完成runtime service的初始化之后就为跳转到下一个image(LK)做准备。

  1. void bl31_prepare_next_image_entry(void)
  2. {
  3. ............
  4. image_type = bl31_get_next_image_type();
  5. next_image_info = bl31_plat_get_next_image_ep_info(image_type);
  6. ............
  7. cm_init_context(read_mpidr_el1(), next_image_info);
  8. cm_prepare_el3_exit(image_type);
  9. }

前面函数首先获取image_type,接下来我们程序将跳转到LK中,LK运行在Non-secure环境中,所以这里的image_type等于NON_SECURE,接下来设置Non-secure上下文,最后在cm_prepare_el3_exit中将栈指针SP指向上下文内存。

  1. entry_point_info_t *bl31_plat_get_next_image_ep_info(uint32_t type)
  2. {
  3. if (type == NON_SECURE)
  4. return &bl33_image_ep_info;
  5. else
  6. return &bl32_image_ep_info;
  7. ............
  8. }

由前面分析可知函数bl31_plat_get_next_image_ep_info 将返回bl33_image_ep_info,并作为参数传递到cm_init_context中进一步初始化Non-secure上下文。

在继续讲解之前先认识一个结构体cpu_context_t,这就是前面提到的执行上下文,他包含了系统通用寄存器,和其他系统专用寄存器。部分寄存器内容如下描述。

 

  1. void cm_init_context(uint64_t mpidr, const entry_point_info_t *ep)
  2. {
  3. ctx = cm_get_context_by_mpidr(mpidr, security_state);
  4. ............
  5. if (security_state != SECURE)
  6. scr_el3 |= SCR_NS_BIT; //设置为Non-secure
  7. ............
  8. /* Populate EL3 state so that we've the right context before doing ERET */
  9. state = get_el3state_ctx(ctx);
  10. write_ctx_reg(state, CTX_SCR_EL3, scr_el3);
  11. write_ctx_reg(state, CTX_ELR_EL3, ep->pc);
  12. write_ctx_reg(state, CTX_SPSR_EL3, ep->spsr);
  13. ............

cm_init_context中初始化Non-secure上下文,最重要的两个地方是配置SCR寄存器为Non-secure;和设置下一步跳转的程序入口地址ep->pc设置到ctx-> el3state_ctxde 的CTX_ELR_EL3中。在前面函数bl31_early_platform_setup中有讲到ep->pc中保存的是LK的入口地址。上下文设置完成接下来就需要将这些值设置到栈中,以便最终加载到对应寄存器。

  1. void cm_prepare_el3_exit(uint32_t security_state)
  2. {
  3. cpu_context_t *ctx = cm_get_context(security_state);
  4. ............
  5. cm_set_next_context(ctx);
  6. }
  7. static inline void cm_set_next_context(void *context)
  8. {
  9. ............
  10. __asm__ volatile("msr spsel, #1\n"
  11. "mov sp, %0\n"
  12. "msr spsel, #0\n"
  13. : : "r" (context));
  14. }

上面两个函数做的事就是将上下文设置到栈中,让sp指向ctx。走到这里ATF的启动就完成了,下一个image的运行上下文也准备好了,最后要做的是就是跳转到下一个image中去执行。

  1. el3_exit: ; .type el3_exit, %function
  2. mov x17, sp
  3. msr spsel, #1
  4. str x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
  5. ldr x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
  6. ldp x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3] //将spsr和elr分别保存到x16和x17中
  7. msr scr_el3, x18
  8. msr spsr_el3, x16
  9. msr elr_el3, x17 //elr 也就是前面保存的LK入口地址加载到elr中。
  10. b restore_gp_registers_eret
  11. func restore_gp_registers_eret
  12. ............
  13. ldp x30, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
  14. msr sp_el0, x17
  15. ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
  16. eret

函数el3_exit中先将LK的入口地址保存到elr中然后调用异常返回指令eret返回。前面讲过eret这条指令的调用将会导致elr加载到pc中,程序就会到elr指向的地址继续执行。这就完成ATF到LK的跳转。

2-3、Non-Secure world与 Secure world之间的切换

2-3-1、t-base开机初始化

下面以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

  1. DECLARE_RT_SVC(
  2. tbase_fastcall,
  3. OEN_TOS_START,
  4. OEN_TOS_END,
  5. SMC_TYPE_FAST,
  6. tbase_fastcall_setup,
  7. tbase_fastcall_handler
  8. );

宏DECLARE_RT_SVC根据传入的参数设置结构体rt_svc_desc的各个成员,并且保证连接到rt_svc_descs这个section。tbase_fastcall_setup是这个service的初始化函数,它在函数runtime_svc_init中调用,主要工作是设置Secure上下文,如下:

  1. void runtime_svc_init(void)
  2. {
  3. ............
  4. if (rt_svc_descs[index].init) {
  5. rc = rt_svc_descs[index].init();
  6. ............
  7. }
  8. int32_t tbase_fastcall_setup(void)
  9. {
  10. entry_point_info_t *image_info;
  11. image_info = bl31_plat_get_next_image_ep_info(SECURE);
  12. ............
  13. tbase_init_eret(image_info->pc,TBASE_AARCH32);
  14. }

这里函数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上下文。

  1. static void tbase_init_eret( uint64_t entrypoint, uint32_t rw ) {
  2. ............
  3. scr &= ~SCR_NS_BIT;
  4. ............
  5. ctx = cm_get_context(SECURE);
  6. ............
  7. state = get_el3state_ctx(ctx);
  8. write_ctx_reg(state, CTX_SPSR_EL3, spsr);
  9. write_ctx_reg(state, CTX_ELR_EL3, entrypoint);
  10. write_ctx_reg(state, CTX_SCR_EL3, scr);
  11. ............
  12. }

这个函数中两个重要的地方是配置SCR寄存器为Secure;和设置下一步跳转的程序入口地址entrypoint,设置到ctx-> el3state_ctxde 的CTX_ELR_EL3中。这里的entrypoint就是前面传入的TEE入口地址。

2-3-2、Non-Secure world到 Secure world的切换

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中的哪一项服务。

  1. static noinline int mt_secure_call(u64 function_id, u64 arg0, u64 arg1, u64 arg2)
  2. {
  3. register u64 reg0 __asm__("x0") = function_id;
  4. register u64 reg1 __asm__("x1") = arg0;
  5. register u64 reg2 __asm__("x2") = arg1;
  6. register u64 reg3 __asm__("x3") = arg2;
  7. int ret = 0;
  8. asm volatile ("smc #0\n" : "+r" (reg0) :
  9. "r"(reg1), "r"(reg2), "r"(reg3));
  10. ret = (int)reg0;
  11. return ret;
  12. }

前面运行指令smc触发一个同步异常,进入EL3异常向量表对应同步异常入口。

  1. smc_handler64:
  2. ............
  3. mov x6, sp
  4. ............
  5. ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH
  6. ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH
  7. orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
  8. adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
  9. /* Load descriptor index from array of indices */
  10. adr x14, rt_svc_descs_indices
  11. ldrb w15, [x14, x16]
  12. ............
  13. lsl w10, w15, #RT_SVC_SIZE_LOG2
  14. ldr x15, [x11, w10, uxtw]
  15. ............
  16. mrs x16, spsr_el3
  17. mrs x17, elr_el3
  18. mrs x18, scr_el3
  19. stp x16, x17, [x6, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
  20. str x18, [x6, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
  21. ............
  22. 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其具体实现如下:

  1. static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
  2. uint64_t x1,
  3. uint64_t x2,
  4. uint64_t x3,
  5. uint64_t x4,
  6. void *cookie,
  7. void *handle,
  8. uint64_t flags)
  9. {
  10. uint64_t mpidr = read_mpidr();
  11. uint32_t linear_id = platform_get_core_pos(mpidr);
  12. tbase_context *tbase_ctx = &secure_context[linear_id];
  13. int caller_security_state = flags&1;
  14. if (caller_security_state==SECURE) {
  15. switch(maskSWdRegister(smc_fid)) {
  16. case TBASE_SMC_FASTCALL_RETURN: {
  17. DBG_PRINTF( "tbase_fastcall_handler TBASE_SMC_FASTCALL_RETURN\n\r");
  18. tbase_synchronous_sp_exit(tbase_ctx, 0, 1);
  19. }
  20. ............
  21. else
  22. {
  23. gp_regs_t *ns_gpregs = get_gpregs_ctx((cpu_context_t *)handle);
  24. write_ctx_reg(ns_gpregs, CTX_GPREG_X0, smc_fid ); // These are not saved yet
  25. write_ctx_reg(ns_gpregs, CTX_GPREG_X1, x1 );
  26. write_ctx_reg(ns_gpregs, CTX_GPREG_X2, x2 );
  27. write_ctx_reg(ns_gpregs, CTX_GPREG_X3, x3 );
  28. cm_el1_sysregs_context_save(NON_SECURE);
  29. ............
  30. tbase_synchronous_sp_entry(tbase_ctx);
  31. cm_el1_sysregs_context_restore(NON_SECURE);
  32. cm_set_next_eret_context(NON_SECURE);
  33. return 0;
  34. }

Linux kernel运行在Non-Secure中,所以这里程序会走下一个分支。这里主要做了三件重要的事:⑴保存异常前的寄存器值到Non-secure上下文中⑵调用函数tbase_synchronous_sp_entry进入TEE环境 ⑶准备Non-secure上下文,异常返回,返回到linux kernel中。下面分别讲解。

  1. uint64_t tbase_synchronous_sp_entry(tbase_context *tbase_ctx)
  2. {
  3. ............
  4. cm_set_next_eret_context(SECURE);
  5. rc = tbase_enter_sp(&tbase_ctx->c_rt_ctx);
  6. ............
  7. }
  8. void cm_set_next_eret_context(uint32_t security_state)
  9. {
  10. cpu_context_t *ctx;
  11. ctx = cm_get_context(security_state);
  12. assert(ctx);
  13. cm_set_next_context(ctx);
  14. }

函数cm_get_context设置secure上下文到栈中,然后调用函数cm_set_next_context进入TEE程序。

  1. func tbase_enter_sp
  2. mov x3, sp
  3. str x3, [x0, #0] //保存sp到tbase_ctx->c_rt_ctx中
  4. sub sp, sp, #TSPD_C_RT_CTX_SIZE
  5. /* Save callee-saved registers on to the stack */
  6. stp x19, x20, [sp, #TSPD_C_RT_CTX_X19]
  7. stp x21, x22, [sp, #TSPD_C_RT_CTX_X21]
  8. stp x23, x24, [sp, #TSPD_C_RT_CTX_X23]
  9. stp x25, x26, [sp, #TSPD_C_RT_CTX_X25]
  10. stp x27, x28, [sp, #TSPD_C_RT_CTX_X27]
  11. stp x29, x30, [sp, #TSPD_C_RT_CTX_X29] //保存lr(x30)到栈中
  12. b el3_exit

func表明tbase_enter_sp是一个函数,调用这个函数会将函数返回地址保存到lr(x30)中,这一点很重要,后面还会用到。然后跳转到el3_exit 做异常返回,这个代码块前面讲过。在函数tbase_fastcall_setup中有将secure上下文中的elr设置为了TEE入口地址所以这里异常返回,将会返回到TEE中。

2-3-3、从 Secure world返回到Non-Secure world

程序进入TEE中完成指定的任务之后最终还会返回到linux kernel中。在返回到linux kernel之前会先返回到ATF中。下面接着再看tbase_fastcall_handler这个函数。这个函数有两个分支,前面讲了后一个分支。TEE是在Secure world运行的,在TEE返回的时候程序就会走到前一个分支,下面看程序具体如何运行:

  1. static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
  2. uint64_t x1,
  3. uint64_t x2,
  4. uint64_t x3,
  5. uint64_t x4,
  6. void *cookie,
  7. void *handle,
  8. uint64_t flags)
  9. {
  10. uint64_t mpidr = read_mpidr();
  11. uint32_t linear_id = platform_get_core_pos(mpidr);
  12. tbase_context *tbase_ctx = &secure_context[linear_id];
  13. int caller_security_state = flags&1;
  14. if (caller_security_state==SECURE) {
  15. switch(maskSWdRegister(smc_fid)) {
  16. case TBASE_SMC_FASTCALL_RETURN: {
  17. DBG_PRINTF( "tbase_fastcall_handler TBASE_SMC_FASTCALL_RETURN\n\r");
  18. tbase_synchronous_sp_exit(tbase_ctx, 0, 1);
  19. }
  20. ............
  21. else
  22. {
  23. ............
  24. }

函数先会调用tbase_synchronous_sp_exit来做实际的返回操作。

  1. func tbase_exit_sp
  2. mov sp, x0 //前面x0保存了栈指针
  3. ldp x19, x20, [x0, #(TSPD_C_RT_CTX_X19 - TSPD_C_RT_CTX_SIZE)]
  4. ldp x21, x22, [x0, #(TSPD_C_RT_CTX_X21 - TSPD_C_RT_CTX_SIZE)]
  5. ldp x23, x24, [x0, #(TSPD_C_RT_CTX_X23 - TSPD_C_RT_CTX_SIZE)]
  6. ldp x25, x26, [x0, #(TSPD_C_RT_CTX_X25 - TSPD_C_RT_CTX_SIZE)]
  7. ldp x27, x28, [x0, #(TSPD_C_RT_CTX_X27 - TSPD_C_RT_CTX_SIZE)]
  8. ldp x29, x30, [x0, #(TSPD_C_RT_CTX_X29 - TSPD_C_RT_CTX_SIZE)] //将lr值从栈中加载到lr(x30)中
  9. mov x0, x1
  10. ret

汇编指令ret做程序返回,这条指令会导致lr自动加载到pc中,然后程序跳转到lr指定的地址运行。这个与eret指令类似,不过那里保存返回地址的是elr。这里的函数返回又会返回到函数tbase_fastcall_handler中执行,如下:

  1. static uint64_t tbase_fastcall_handler(uint32_t smc_fid,
  2. uint64_t x1,
  3. uint64_t x2,
  4. uint64_t x3,
  5. uint64_t x4,
  6. void *cookie,
  7. void *handle,
  8. uint64_t flags)
  9. {
  10. ............
  11. if (caller_security_state==SECURE) {
  12. ............
  13. else
  14. {
  15. ............
  16. tbase_synchronous_sp_entry(tbase_ctx); //这里进入TEE
  17. cm_el1_sysregs_context_restore(NON_SECURE); // TEE返回后会返回到这里执行
  18. cm_set_next_eret_context(NON_SECURE);
  19. return 0;
  20. }

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

  1. void cm_set_next_eret_context(uint32_t security_state)
  2. {
  3. cpu_context_t *ctx;
  4. ctx = cm_get_context(security_state);
  5. assert(ctx);
  6. cm_set_next_context(ctx);
  7. }

函数cm_set_next_eret_context执行完成之后,函数tbase_fastcall_handler就返回了,返回到异常处理函数smc_handler64中,具体如下:

  1. smc_handler64:
  2. ............
  3. mov x6, sp
  4. ............
  5. blr x15 //根据function id进入对应的service handle
  6. el3_exit: ; .type el3_exit, %function //service handle函数返回后继续完成退出的工作
  7. ............
  8. msr scr_el3, x18
  9. msr spsr_el3, x16
  10. msr elr_el3, x17
  11. b restore_gp_registers_eret
  12. func restore_gp_registers_eret
  13. ............
  14. msr sp_el0, x17
  15. ldp x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]
  16. eret //实现异常返回

函数tbase_fastcall_handler中Non-secure分支会调用函数cm_el1_sysregs_context_save(NON_SECURE); 保存异常前的寄存器值到Non-secure上下文中,这其中就包含elr,由于是从linux kernel中调用smc下来的,所以这里elr保存的就是linux kernel中的地址,所以这里异常返回就会返回到linux中。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/129966
推荐阅读
相关标签
  

闽ICP备14008679号