Khorina · 9月25日 · 广东

arm-trusted-firmware分析(下)

接上篇:arm-trusted-firmware分析(上)

12.TEE

arm-trusted-firware并没有实现一个完整的TEE,只给出了一个简单的与TEE类似的程序(BL32)。AARCH64 TEE应该是BL31(monitor)与BL32(Trusted OS)配合工作的。一般TOS通过monitor的一个SMC服务与Non-Secure通信。

1)Monitor SMC服务

此SMC服务需要启动TOS,并且为TOS与Non-Secure提供通信服务。

TOS启动

应为arm-trusted-firware并没有实际的TOS,这里以tsp(位于bl32\tsp,对应的SMC服务位于services\spd\tspd)为例作部分介绍。

其中有一个宏TSP_INIT_ASYNC
当宏TSP_INIT_ASYNC定义时

  1. BL31(monitor)在异常退出时执行BL32(TOS)
  2. BL32(TOS)初始化完成后通过SMC调用通知BL31(monitor)
  3. BL31(monitor)在SMC服务中启动BL33(APPBL)
  4. 当宏TSP_INIT_ASYNC未定义时
  5. BL31(monitor)在SMC服务初始化完成后通过异常退出执行BL32(TOS)
  6. BL32(TOS)初始化完成后通过SMC调用返回初始化的位置
  7. BL31(monitor)执行完才退出异常执行BL33(APPBL)

此处涉及代码比较多

BL31主函数

void bl31_main(void)
{
 //……

 /* 初始化SMC服务 
  * 为TOS服务的SMC服务会根据TSP_INIT_ASYNC调用部分函数
  *   void bl31_set_next_image_type(uint32_t security_state)
  *   void bl31_register_bl32_init(int32_t (*func)(void))
     */
 INFO("BL31: Initializing runtime services\n");
 runtime_svc_init();

 /*
  * 当宏TSP_INIT_ASYNC未定义时
  * 给TOS服务的SMC宏将初始化此函数句柄
  * 此函数句柄将会通过异常退出执行TOS(BL32)
  */
 if (bl32_init) {
  INFO("BL31: Initializing BL32\n");
  (*bl32_init)();
 }

  /* 初始化下一级BL的执行环境 */
 bl31_prepare_next_image_entry();

 //……
}

以上代码是一个简化的主函数。在SMC服务初始化时,为TOS服务的SMC服务将根据TSP_INIT_ASYNC调用bl31_set_next_image_type、bl31_register_bl32_initbl31_set_next_image_type相关部分,TSP_INIT_ASYNC未定义时此不分发挥作用

/* 默认BL31执行完成后异常退出要执行的镜像的security state */
static uint32_t next_image_type = NON_SECURE;

/* 设置BL31执行完成异常退出要执行镜像的security state
 * security_state = NON_SECURE 执行BL33 APPBL
 * security_state = SECURE 执行BL32 TOS
 */
void bl31_set_next_image_type(uint32_t security_state)
{
 assert(sec_state_is_valid(security_state));
 next_image_type = security_state;
}

/* 在bl31_prepare_next_image_entry函数中被调用,用于初始化下一级BL */
uint32_t bl31_get_next_image_type(void)
{
 return next_image_type;
}

bl31_register_bl32_init相关部分,TSP_INIT_ASYNC定义时此不分发挥作用

/* 函数指针,用于初始化TOS */
static int32_t (*bl32_init)(void);
/* 设置bl32_init*/
void bl31_register_bl32_init(int32_t (*func)(void))
{
 bl32_init = func;
}

TSPD

TSPD并不是一个完整的TOS,只用于测试。不过可以用于启动分析。

服务初始化

int32_t tspd_setup(void)
{
 entry_point_info_t *tsp_ep_info;
 uint32_t linear_id;

 linear_id = plat_my_core_pos();

 /* 获取TOS执行环境并做部分验证工作 */
 tsp_ep_info = bl31_plat_get_next_image_ep_info(SECURE);
 if (!tsp_ep_info) {
  WARN("No TSP provided by BL2 boot loader, Booting device"
   " without TSP initialization. SMC`s destined for TSP"
   " will return SMC_UNK\n");
  return 1;
 }
 if (!tsp_ep_info->pc)
  return 1;

 /*
  * 执行部分初始化工作
  */
 tspd_init_tsp_ep_state(tsp_ep_info,
    TSP_AARCH64,
    tsp_ep_info->pc,
    &tspd_sp_context[linear_id]);

#if TSP_INIT_ASYNC
  /* 设置下一级BL的security state
     在异常退出时执行TOS */
 bl31_set_next_image_type(SECURE);
#else
 /* 在bl32_init中初始话BL32*/
 bl31_register_bl32_init(&tspd_init);
#endif
 return 0;
}

异常处理

uint64_t tspd_smc_handler(uint32_t smc_fid,
    uint64_t x1,
    uint64_t x2,
    uint64_t x3,
    uint64_t x4,
    void *cookie,
    void *handle,
    uint64_t flags)
{
 cpu_context_t *ns_cpu_context;
 uint32_t linear_id = plat_my_core_pos(), ns;
 tsp_context_t *tsp_ctx = &tspd_sp_context[linear_id];
 uint64_t rc;
#if TSP_INIT_ASYNC
 entry_point_info_t *next_image_info;
#endif

 /* Determine which security state this SMC originated from */
 ns = is_caller_non_secure(flags);

 switch (smc_fid) {
 //……
 /* TSP_ENTRY_DONE在BL32初始化完才,通过SMC返回 */
 case TSP_ENTRY_DONE:
  if (ns)/* 此异常只能来自Secure */
   SMC_RET1(handle, SMC_UNK);
  
        /* 此异常只能发生一次 */
  assert(tsp_vectors == NULL);
  tsp_vectors = (tsp_vectors_t *) x1;/* 记录服务函数 */

  if (tsp_vectors) {
           /* 记录状态 */
   set_tsp_pstate(tsp_ctx->state, TSP_PSTATE_ON);

   /* TSP初始化完才,注册电源管理句柄 */
   psci_register_spd_pm_hook(&tspd_pm);

           /* S-EL1注册中断处理函数 */
   flags = 0;
   set_interrupt_rm_flag(flags, NON_SECURE);
   rc = register_interrupt_type_handler(INTR_TYPE_S_EL1,
      tspd_sel1_interrupt_handler,
      flags);
   if (rc)
    panic();

#if TSP_NS_INTR_ASYNC_PREEMPT
   /* Non-Secure注册中断处理函数 */
   flags = 0;
   set_interrupt_rm_flag(flags, SECURE);

   rc = register_interrupt_type_handler(INTR_TYPE_NS,
      tspd_ns_interrupt_handler,
      flags);
   if (rc)
    panic();

   /* 禁止中断 */
   disable_intr_rm_local(INTR_TYPE_NS, SECURE);
#endif
  }


#if TSP_INIT_ASYNC
  /* 启动BL33(APPBL) */
  assert(cm_get_context(SECURE) == &tsp_ctx->cpu_ctx);
  cm_el1_sysregs_context_save(SECURE);
  next_image_info = bl31_plat_get_next_image_ep_info(NON_SECURE);
  assert(next_image_info);
  assert(NON_SECURE ==
    GET_SECURITY_STATE(next_image_info->h.attr));
  cm_init_my_context(next_image_info);
  cm_prepare_el3_exit(NON_SECURE);
  SMC_RET0(cm_get_context(NON_SECURE));
#else
  /* 返回tspd_synchronous_sp_entry处继续BL31的main方法 */
  tspd_synchronous_sp_exit(tsp_ctx, x1);
#endif
 
 //……
 default:
  break;
 }

 SMC_RET1(handle, SMC_UNK);
}

同步执行

上上面遗产处理中,有一组特别的函数tspd_synchronous_sp_entry 、tspd_synchronous_sp_exittspd_synchronous_sp_entry用于退出异常执行下一级BLtspd_synchronous_sp_exit用于下一级BL执行结束后通过SMC返回,然后跳转到tspd_synchronous_sp_entry后继续执行。

具体代码如下

uint64_t tspd_synchronous_sp_entry(tsp_context_t *tsp_ctx)
{
 uint64_t rc;

 assert(tsp_ctx != NULL);
 assert(tsp_ctx->c_rt_ctx == 0);
 assert(cm_get_context(SECURE) == &tsp_ctx->cpu_ctx);
   
   /* 恢复SECURE下的系统寄存器 */
 cm_el1_sysregs_context_restore(SECURE);
   /* 设置遗产返回相关的寄存器,以便异常退出后执行对应的程序 */
 cm_set_next_eret_context(SECURE);
   /* 保存x19-x30到堆栈,并记录堆栈指针到tsp_ctx->c_rt_ctx */
 rc = tspd_enter_sp(&tsp_ctx->c_rt_ctx);
#if DEBUG
 tsp_ctx->c_rt_ctx = 0;
#endif

 return rc;
}

void tspd_synchronous_sp_exit(tsp_context_t *tsp_ctx, uint64_t ret)
{
 assert(tsp_ctx != NULL);
 assert(cm_get_context(SECURE) == &tsp_ctx->cpu_ctx);
    /* 保存SECURE下的系统寄存器 */
 cm_el1_sysregs_context_save(SECURE);

 assert(tsp_ctx->c_rt_ctx != 0);
   
   /* 通过tsp_ctx->c_rt_ctx恢复SP,并从堆栈中恢复x19-x30 */
 tspd_exit_sp(tsp_ctx->c_rt_ctx, ret);

 /* 此处不会执行 */
 assert(0);
}

tspd_enter_sp、tspd_exit_sp实现细节如下

    .global    tspd_enter_sp
func tspd_enter_sp
    /* 保存SP到X0指定的地址 */
    mov    x3, sp
    str    x3, [x0, #0]    
    
    /* 开辟栈空间保存x19-x30 */
    sub    sp, sp, #TSPD_C_RT_CTX_SIZE
    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]

    /* 遗产退出执行下一级BL */
    b    el3_exit
endfunc tspd_enter_sp

    .global tspd_exit_sp
func tspd_exit_sp
    /* 恢复堆栈指针 */
    mov    sp, x0

    /* 恢复x19-x30 */
    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)]

    /* 返回x1 */
    mov    x0, x1
    ret/* 通过x30返回,将返回到CALL tspd_enter_sp的下一条指令 */
endfunc tspd_exit_sp

TOS与Non-Secure数据交换

TOS与Non-Secure通过Monitor进行数据交换,异常处理代码如下。

smc_handler32:
    /* Check whether aarch32 issued an SMC64 */
    tbnz    x0, #FUNCID_CC_SHIFT, smc_prohibited

    /*
     * Since we're are coming from aarch32, x8-x18 need to be saved as per
     * SMC32 calling convention. If a lower EL in aarch64 is making an
     * SMC32 call then it must have saved x8-x17 already therein.
     */
    stp    x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
    stp    x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
    stp    x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
    stp    x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
    stp    x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]

    /* x4-x7, x18, sp_el0 are saved below */
smc_handler64:
    /*
     * Populate the parameters for the SMC handler.
     * We already have x0-x4 in place. x5 will point to a cookie (not used
     * now). x6 will point to the context structure (SP_EL3) and x7 will
     * contain flags we need to pass to the handler Hence save x5-x7.
     *
     * Note: x4 only needs to be preserved for AArch32 callers but we do it
     *       for AArch64 callers as well for convenience
     */
    stp    x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
    stp    x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]

    /* Save rest of the gpregs and sp_el0*/
    save_x18_to_x29_sp_el0

    mov    x5, xzr
    mov    x6, sp

    /* Get the unique owning entity number */
    /* X0用于确定调用的功能
     * b31    调用类型 1->Fast Call 0->Yielding Call
     * b30    标示32bit还是64bit调用
     * b29:24 服务类型
     *           0x0  ARM架构相关
     *           0x1  CPU服务
     *           0x2  SiP服务
     *           0x3  OEM服务
     *           0x4  标准安全服务
     *           0x5  标准Hypervisor服务
     *           0x6  厂商定制的Hypervisor服务
     *     0x07-0x2f  预留
     *     0x30-0x31  Trusted Application Calls
     *     0x32-0x3f  Trusted OS Calls
     * b23:16 在Fast Call时这些比特位必须为0,其他值保留未用
     *        ARMv7传统的Trusted OS在Fast Call时必须为1
     * b15:0  函数号
     */
    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

    /* __RT_SVC_DESCS_START__为rt_svc_descs段的起始地址,此段存放服务描述符
     * RT_SVC_DESC_HANDLE为服务句柄在服务描述符结构体中的偏移量
     * 以下代码保存第一个复位句柄的地址到x11中
     */
    adr    x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)

    /* Load descriptor index from array of indices */
    /* 因为描述符在内存中布局是乱的通过rt_svc_descs_indices数组来查找描述符在内存中的位置
     * rt_svc_descs_indices为rt_svc_descs的索引
     */
    adr    x14, rt_svc_descs_indices
    ldrb    w15, [x14, x16]

    /*
     * Restore the saved C runtime stack value which will become the new
     * SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
     * structure prior to the last ERET from EL3.
     */
    ldr    x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]

    /*
     * Any index greater than 127 is invalid. Check bit 7 for
     * a valid index
     */
    tbnz    w15, 7, smc_unknown/* 索引的值不应该大于127 */

    /* Switch to SP_EL0 */
    msr    spsel, #0

    /*
     * Get the descriptor using the index
     * x11 = (base + off), x15 = index
     *
     * handler = (base + off) + (index << log2(size))
     */
    lsl    w10, w15, #RT_SVC_SIZE_LOG2/* 计算偏移量 */
    ldr    x15, [x11, w10, uxtw]/* 加载出对应服务的句柄 */

    /*
     * Save the SPSR_EL3, ELR_EL3, & SCR_EL3 in case there is a world
     * switch during SMC handling.
     * TODO: Revisit if all system registers can be saved later.
     */
    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]

    /* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
    bfi    x7, x18, #0, #1/* x7用于传递给服务函数security state */

    mov    sp, x12

    /*
     * Call the Secure Monitor Call handler and then drop directly into
     * el3_exit() which will program any remaining architectural state
     * prior to issuing the ERET to the desired lower EL.
     */
#if DEBUG
    cbz    x15, rt_svc_fw_critical_error/* x15等于0说明有错误存在 */
#endif
    blr    x15 /* 调用对应的服务 */

    b    el3_exit/* 异常恢复 */

异常处理函数如下
异常处理保护了X4-X7、X18-X30、SP_EL1
X8-X17没有进行保护需要调用SMC服务的程序自行保存
X0作为SMC的功能传递
X1-X4作为SMC服务的参数传递
cookie固定为0
handle把SP_EL3传递给函数,便于修改返回值(现场保护通过SP_EL3完才)

typedef uintptr_t (*rt_svc_handle_t)(uint32_t smc_fid,
      u_register_t x1,
      u_register_t x2,
      u_register_t x3,
      u_register_t x4,
      void *cookie,
      void *handle,
      u_register_t flags);

2)Monitor中断

为了灵活配置中断,Monitor使中断处理函数可以配置

数据结构
/* 中断类型定义 */
#define INTR_TYPE_S_EL1   0
#define INTR_TYPE_EL3   1
#define INTR_TYPE_NS   2
#define MAX_INTR_TYPES   3 
#define INTR_TYPE_INVAL   MAX_INTR_TYPES

/* 中断函数句柄 */
typedef uint64_t (*interrupt_type_handler_t)(uint32_t id,
          uint32_t flags,
          void *handle,
          void *cookie);

typedef struct intr_type_desc {
   /* 中断处理函数 */
 interrupt_type_handler_t handler;
   /* 中断的路由模式:
     Bit0在Secure时中断要不要路由到EL3
     Bit1在Non-Secure时中断要不要路由到EL3*/
 uint32_t flags; 
   /* 记录两种安全状态下(SCR_EL3.FIQ SCR_EL3.IRQ)的掩码(置位用) */
 uint32_t scr_el3[2];
} intr_type_desc_t;

/* 根据中断分类,保存的函数句柄 */
static intr_type_desc_t intr_type_descs[MAX_INTR_TYPES];
主要方法

获取EL3路由模式控制位

uint32_t get_scr_el3_from_routing_model(uint32_t security_state)
{
 uint32_t scr_el3;
 assert(sec_state_is_valid(security_state));/* 确定是有效的Security state */
   
   /* 获取每一种中断的路由控制位 */
 scr_el3 = intr_type_descs[INTR_TYPE_NS].scr_el3[security_state];
 scr_el3 |= intr_type_descs[INTR_TYPE_S_EL1].scr_el3[security_state];
 scr_el3 |= intr_type_descs[INTR_TYPE_EL3].scr_el3[security_state];
 return scr_el3;
}

根据中断类型设置路由控制位

/* type为中断类型
 * flags为路由模式
 *   Bit0在Secure时中断要不要路由到EL3
 *  Bit1在Non-Secure时中断要不要路由到EL3
 */
int32_t set_routing_model(uint32_t type, uint32_t flags)
{
 int32_t rc;

 rc = validate_interrupt_type(type);/* 校验中断类型 */
 if (rc)
  return rc;

 rc = validate_routing_model(type, flags);/* 校验路由模式 */
 if (rc)
  return rc;
 
 intr_type_descs[type].flags = flags;/* 更新路由模式 */
 set_scr_el3_from_rm(type, flags, SECURE);/* 更新scr_el3[SECURE] */
 set_scr_el3_from_rm(type, flags, NON_SECURE);/* 更新scr_el3[NON_SECURE] */

 return 0;
}

设置中断处理函数

int32_t register_interrupt_type_handler(uint32_t type,/* 中断类型 */
     interrupt_type_handler_t handler,/* 中断处理句柄 */
     uint32_t flags)/* 路由模式标志 
         *  Bit0在Secure时中断要不要路由到EL3
          * Bit1在Non-Secure时中断要不要路由到EL3*/
{
 int32_t rc;

 /* 确认中断处理句柄不是空指针 */
 if (!handler)
  return -EINVAL;

 /* 校验路由模式有效 */
 if (flags & INTR_TYPE_FLAGS_MASK)
  return -EINVAL;

 /* 中断处理句柄只能注册一次,此处检查中断处理句柄有无注册 */
 if (intr_type_descs[type].handler)
  return -EALREADY;
 
   /* 根据中断类型设置路由模式 */
 rc = set_routing_model(type, flags);
 if (rc)
  return rc;

 /* 保存中断处理句柄 */
 intr_type_descs[type].handler = handler;

 return 0;
}

获取中断处理句柄

interrupt_type_handler_t get_interrupt_type_handler(uint32_t type)
{
   /* 校验是否是合法的中断类型 */
 if (validate_interrupt_type(type))
  return NULL;
 /* 根据中断类型获取对应的中断处理句柄 */
 return intr_type_descs[type].handler;
}

使能对应安全模式对应中断类型的中断

int enable_intr_rm_local(uint32_t type, uint32_t security_state)
{
 uint32_t bit_pos, flag;
 /* 判断有无中断处理句柄,操作没有中断句柄的中断是非法的 */
 assert(intr_type_descs[type].handler);
 
   /* 获取对应安全模式对应中断类型的中断的路由模式 */
 flag = get_interrupt_rm_flag(intr_type_descs[type].flags,
    security_state);
 /* 获取对应中断的bit_pos,即FIQ IRQ */
 bit_pos = plat_interrupt_type_to_line(type, security_state);
   /* 写SCR_EL3的路由控制位 SCR_EL3 |= flag << bit_pos */
 cm_write_scr_el3_bit(security_state, bit_pos, flag);

 return 0;
}

失能对应安全模式对应中断类型的中断

int disable_intr_rm_local(uint32_t type, uint32_t security_state)
{
 uint32_t bit_pos, flag;
 /* 判断有无中断处理句柄,操作没有中断句柄的中断是非法的 */
 assert(intr_type_descs[type].handler);
 
   /* 获取对应安全模式对应中断类型的中断的路由模式 */
   /* INTR_DEFAULT_RM = 0
    *     Bit0 = 0 Secure中断不路由到EL3
    *     Bit1 = 1 Non-Secure中断不路由到EL3
    */
 flag = get_interrupt_rm_flag(INTR_DEFAULT_RM, security_state);
   /* 获取对应中断的bit_pos,即FIQ IRQ */
 bit_pos = plat_interrupt_type_to_line(type, security_state);
   /* 写SCR_EL3的路由控制位 SCR_EL3 |= flag << bit_pos */
 cm_write_scr_el3_bit(security_state, bit_pos, flag);

 return 0;
}
中断处理
   /* 此宏用于FIQ IRQ处理 */
    .macro    handle_interrupt_exception label
    /* Enable the SError interrupt */
    /* 使能SError中断 */
    msr    daifclr, #DAIF_ABT_BIT

    /* 保存x0-x30 sp_el1 */
    str    x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
    bl    save_gp_registers/* 因为此处为函数调用会修改lr(x30),所以lr需要提前保存 */

    /* Save the EL3 system registers needed to return from this exception */
    mrs    x0, spsr_el3 /* 获取进入异常前的程序状态寄存器 */
    mrs    x1, elr_el3  /* 获取程序被中断的异常地址 */
    /* 保存进入异常的程序状态和异常地址 */
    stp    x0, x1, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]

    /* Switch to the runtime stack i.e. SP_EL0 */
    ldr    x2, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
    mov    x20, sp   /* 保护中断现场保护指针,用于修改返回值 对应中断处理函数的handle参数 */
    msr    spsel, #0
    mov    sp, x2 /* 切换到EL0的SP指针 */

    /*
     * Find out whether this is a valid interrupt type.
     * If the interrupt controller reports a spurious interrupt then return
     * to where we came from.
     */
    bl    plat_ic_get_pending_interrupt_type /* 获取中断类型 */
    cmp    x0, #INTR_TYPE_INVAL
    b.eq    interrupt_exit_\label /* 非法类型直接退出 */

    /*
     * Get the registered handler for this interrupt type.
     * A NULL return value could be 'cause of the following conditions:
     *
     * a. An interrupt of a type was routed correctly but a handler for its
     *    type was not registered.
     *
     * b. An interrupt of a type was not routed correctly so a handler for
     *    its type was not registered.
     *
     * c. An interrupt of a type was routed correctly to EL3, but was
     *    deasserted before its pending state could be read. Another
     *    interrupt of a different type pended at the same time and its
     *    type was reported as pending instead. However, a handler for this
     *    type was not registered.
     *
     * a. and b. can only happen due to a programming error. The
     * occurrence of c. could be beyond the control of Trusted Firmware.
     * It makes sense to return from this exception instead of reporting an
     * error.
     */
    bl    get_interrupt_type_handler /* 获取中断处理函数 */
    cbz    x0, interrupt_exit_\label  /* 函数指针为0,直接退出 */
    mov    x21, x0

    mov    x0, #INTR_ID_UNAVAILABLE /* 参数id */

    /* Set the current security state in the 'flags' parameter */
    mrs    x2, scr_el3
    ubfx    x1, x2, #0, #1    /* 获取Security state,对应中断处理函数的flags参数 */

    /* Restore the reference to the 'handle' i.e. SP_EL3 */
    mov    x2, x20 /* x2指向SP_EL3 用于修改返回值*/

    /* x3 will point to a cookie (not used now) */
    mov    x3, xzr

    /* Call the interrupt type handler */
    blr    x21

interrupt_exit_\label:
    /* Return from exception, possibly in a different security state */
    b    el3_exit

    .endm

13.BL1分析

1)功能概要

BL1主要负责对BL2进行认证加载并执行,并且提供SMC中断服务,便于执行在EL1-Secure的BL2能给把BL31加载到EL3。

2)主过程

BL1的入口代码位于bl1/aarch64/bl1_entrypoint.S文件中,代码注释如下。

    .globl    bl1_entrypoint
func bl1_entrypoint /* BL1的程序入口 */
     /* EL3通用的初始化宏 */
    el3_entrypoint_common                    \
        _set_endian=1        /* 大小端初始化 */\
        _warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS    /* 热启动分支执行 */\
        _secondary_cold_boot=!COLD_BOOT_SINGLE_CPU /* 次处理器进入低功耗模式 */\
        _init_memory=1    /* 内存初始化 */\
        _init_c_runtime=1    /* 初始化堆栈(SP)等 */\
        _exception_vectors=bl1_exceptions /* 设置异常向量 */

    /*--------------------------------------------------
     * bl1_early_platform_setup
     * 初始化看梦狗、串口(用于输出调试信息)
     * 更新BL1内存布局到bl1_tzram_layout
     *--------------------------------------------------
     */
    bl    bl1_early_platform_setup

    /*--------------------------------------------------
     * bl1_plat_arch_setup
     * 根据内存布局设置页表并使能MMU
     *--------------------------------------------------
     */
    bl    bl1_plat_arch_setup

    /* --------------------------------------------------
     * 转到主程序
     * --------------------------------------------------
     */
    bl    bl1_main

    /* --------------------------------------------------
     * 退出异常,执行下一级BL
     * --------------------------------------------------
     */
    b    el3_exit
endfunc bl1_entrypoint

bl1_main是bl1的主程序,位于bl1/bl1_main.c

void bl1_main(void)
{
 unsigned int image_id;

 /* 输出一些提示信息 */
 NOTICE(FIRMWARE_WELCOME_STR);
 NOTICE("BL1: %s\n", version_string);
 NOTICE("BL1: %s\n", build_message);

 INFO("BL1: RAM %p - %p\n", (void *)BL1_RAM_BASE,
     (void *)BL1_RAM_LIMIT);

 print_errata_status();

 /*
  * 异常检测,查看汇编中初始化是否正常
  */
#if DEBUG
 u_register_t val;
#ifdef AARCH32
 val = read_sctlr();
#else
 val = read_sctlr_el3();
#endif
 assert(val & SCTLR_M_BIT);
 assert(val & SCTLR_C_BIT);
 assert(val & SCTLR_I_BIT);
 val = (read_ctr_el0() >> CTR_CWG_SHIFT) & CTR_CWG_MASK;
 if (val != 0)
  assert(CACHE_WRITEBACK_GRANULE == SIZE_FROM_LOG2_WORDS(val));
 else
  assert(CACHE_WRITEBACK_GRANULE <= MAX_CACHE_LINE_SIZE);
#endif

 /* 设置下一级EL可以运行在64bit */
 bl1_arch_setup();

#if TRUSTED_BOARD_BOOT
 /* 初始化认证模块 */
 auth_mod_init();
#endif /* TRUSTED_BOARD_BOOT */

 /* 具体硬件相关设置(IO外设等) */
 bl1_platform_setup();

 /* 获取下一级要执行的镜像id */
 image_id = bl1_plat_get_next_image_id();

 if (image_id == BL2_IMAGE_ID)
  bl1_load_bl2();/* 加载BL2到内存 */
 else
  NOTICE("BL1-FWU: *******FWU Process Started*******\n");

 /* 执行环境配置,在程序结束后可以执行下一个镜像 */
 bl1_prepare_next_image(image_id);
}

3)中断服务

BL1异常服务程序位于bl1/aarch64/bl1_exceptions.S

/*
 * BL1的异常向量表
 * plat_report_exception用与指示异常与具体架构有关
 * no_ret 是一个bl的宏,用于指示被调用的函数没有返回值
 * plat_panic_handler是一个死循环,在程序出错时停止程序的运行
 * check_vecror_size用于检查代码有没有越界(一个异常向量只有128Byte)
 * BL1只处理低异常等级的SMC调用,其他异常全部非法
 */
    .globl    bl1_exceptions

vector_base bl1_exceptions

    /* -----------------------------------------------------
     * Current EL with SP0 : 0x0 - 0x200
     * -----------------------------------------------------
     */
vector_entry SynchronousExceptionSP0
    mov    x0, #SYNC_EXCEPTION_SP_EL0
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SynchronousExceptionSP0

vector_entry IrqSP0
    mov    x0, #IRQ_SP_EL0
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size IrqSP0

vector_entry FiqSP0
    mov    x0, #FIQ_SP_EL0
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size FiqSP0

vector_entry SErrorSP0
    mov    x0, #SERROR_SP_EL0
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SErrorSP0

    /* -----------------------------------------------------
     * Current EL with SPx: 0x200 - 0x400
     * -----------------------------------------------------
     */
vector_entry SynchronousExceptionSPx
    mov    x0, #SYNC_EXCEPTION_SP_ELX
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SynchronousExceptionSPx

vector_entry IrqSPx
    mov    x0, #IRQ_SP_ELX
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size IrqSPx

vector_entry FiqSPx
    mov    x0, #FIQ_SP_ELX
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size FiqSPx

vector_entry SErrorSPx
    mov    x0, #SERROR_SP_ELX
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SErrorSPx

    /* -----------------------------------------------------
     * Lower EL using AArch64 : 0x400 - 0x600
     * -----------------------------------------------------
     */
vector_entry SynchronousExceptionA64
    /* 使能SError中断 */
    msr    daifclr, #DAIF_ABT_BIT

    /* 保存x30下面需要使用x30作临时寄存器分析异常的原因 */
    str    x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]

    /* Expect only SMC exceptions */
    mrs    x30, esr_el3 /* 获取异常的原因 */
    ubfx    x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH
    cmp    x30, #EC_AARCH64_SMC
    b.ne    unexpected_sync_exception /* 报告非法异常(只处理AArch64 SMC调用) */

    b    smc_handler64 /* 处理低异常等级SMC调用 */
    check_vector_size SynchronousExceptionA64

vector_entry IrqA64
    mov    x0, #IRQ_AARCH64
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size IrqA64

vector_entry FiqA64
    mov    x0, #FIQ_AARCH64
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size FiqA64

vector_entry SErrorA64
    mov    x0, #SERROR_AARCH64
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SErrorA64

    /* -----------------------------------------------------
     * Lower EL using AArch32 : 0x600 - 0x800
     * -----------------------------------------------------
     */
vector_entry SynchronousExceptionA32
    mov    x0, #SYNC_EXCEPTION_AARCH32
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SynchronousExceptionA32

vector_entry IrqA32
    mov    x0, #IRQ_AARCH32
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size IrqA32

vector_entry FiqA32
    mov    x0, #FIQ_AARCH32
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size FiqA32

vector_entry SErrorA32
    mov    x0, #SERROR_AARCH32
    bl    plat_report_exception
    no_ret    plat_panic_handler
    check_vector_size SErrorA32


func smc_handler64

    /* RUN_IMAGE SMC调用,用于运行低异常等级的镜像,并执行在EL3,
     * 即不返回调用的位置,所以需要特别处理 */
    mov    x30, #BL1_SMC_RUN_IMAGE
    cmp    x30, x0
    b.ne    smc_handler/* 其他SMC调用 */

    /* 确定在secure world执行RUN_IMAGE SMC调用 */
    mrs    x30, scr_el3
    tst    x30, #SCR_NS_BIT
    b.ne    unexpected_sync_exception/* 报告异常 */

    /* 加载出异常退出时保存的SP,切换到SP_EL0 */
    ldr    x30, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
    msr    spsel, #0
    mov    sp, x30

    /*
     * bl1_print_next_bl_ep_info用于打印要执行的镜像文件的地址参数
     * 这些参数保存在entry_point_info_t结构体中
     * x1为指针指向entry_point_info_t
     */
    mov    x20, x1
    mov    x0, x20
    bl    bl1_print_next_bl_ep_info

    ldp    x0, x1, [x20, #ENTRY_POINT_INFO_PC_OFFSET]/* 获取pc、spsr */
    msr    elr_el3, x0/* 修改返回地址,用来在异常退出时执行指定的镜像文件 */
    msr    spsr_el3, x1/* 修改返回的cpsr,可以修改异常等级 */
    ubfx    x0, x1, #MODE_EL_SHIFT, #2
    cmp    x0, #MODE_EL3/* 执行的镜像必须为EL3,为后面的代码放开权限 */
    b.ne    unexpected_sync_exception

    bl    disable_mmu_icache_el3
    tlbi    alle3

#if SPIN_ON_BL1_EXIT
    bl    print_debug_loop_message
debug_loop:
    b    debug_loop
#endif

    mov    x0, x20
    bl    bl1_plat_prepare_exit
    /* 从x1指定的结构体中获取参数,并退出异常 */
    ldp    x6, x7, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x30)]
    ldp    x4, x5, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x20)]
    ldp    x2, x3, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x10)]
    ldp    x0, x1, [x20, #(ENTRY_POINT_INFO_ARGS_OFFSET + 0x0)]
    eret
endfunc smc_handler64

unexpected_sync_exception:
    mov    x0, #SYNC_EXCEPTION_AARCH64
    bl    plat_report_exception
    no_ret    plat_panic_handler

smc_handler:
    bl    save_gp_registers/* 保存x0-x29,LR(x30)在之前已经保存过了 */
    mov    x5, xzr/* x5作为cookie参数传递给异常函数现在未用 */
    mov    x6, sp /* x6为作为handle传递给异常处理函数,x6为堆栈的指针,便于遗产函数设置返回值 */

    /* 加载出上一次异常退出的SP指针 */
    ldr    x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]

    /* ---------------------------------------------
     * Switch back to SP_EL0 for the C runtime stack.
     * ---------------------------------------------
     */
    msr    spsel, #0
    mov    sp, x12

    /* -----------------------------------------------------
     * 保存spsr_el3、elr_el3、scr_el3寄存器
     * 在el3_exit时可以退出到进入遗产的位置
     * -----------------------------------------------------
     */
    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]

    /* 传递给异常处理函数的参数flag,标记之前是否在Secure Word */
    bfi    x7, x18, #0, #1

    /* -----------------------------------------------------
     * 跳转到证实的异常处理函数
     * -----------------------------------------------------
     */
    bl    bl1_smc_handler

    /* -----------------------------------------------------
     * 遗产调用返回,返回到调用位置
     * -----------------------------------------------------
     */
    b    el3_exit

BL1使用SP_EL0作为其C程序运行的堆栈,在进入异常时默认认为SPSEL=1,使用的是SP_EL3。这样,保存的线程在SP_EL3中。切换到SP_EL0执行程序,并通过SP_EL3来修改堆栈。

14.BL2分析

1)功能概要

BL2主要负责对其他所有BL进行认证和加载,并执行EL31。

2)主过程

BL2入口位于bl2/aarch64/bl2_entrypoint.S

    .globl    bl2_entrypoint



func bl2_entrypoint
    mov    x20, x1 /* x1保存了内存布局信息 */

    /* 设置异常处理函数 */
    adr    x0, early_exceptions
    msr    vbar_el1, x0
    isb

    /* ---------------------------------------------
     * 使能异常
     * ---------------------------------------------
     */
    msr    daifclr, #DAIF_ABT_BIT

    /* ---------------------------------------------
     * 使能指令缓存,使能堆栈数据访问对齐检查
     * ---------------------------------------------
     */
    mov    x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
    mrs    x0, sctlr_el1
    orr    x0, x0, x1
    msr    sctlr_el1, x0
    isb

    /* ---------------------------------------------
     * 失效内存
     * ---------------------------------------------
     */
    adr    x0, __RW_START__
    adr    x1, __RW_END__
    sub    x1, x1, x0
    bl    inv_dcache_range

    /* ---------------------------------------------
     * BSS内存初始化
     * ---------------------------------------------
     */
    ldr    x0, =__BSS_START__
    ldr    x1, =__BSS_SIZE__
    bl    zeromem16

#if USE_COHERENT_MEM
    ldr    x0, =__COHERENT_RAM_START__
    ldr    x1, =__COHERENT_RAM_UNALIGNED_SIZE__
    bl    zeromem16
#endif

    /* --------------------------------------------
     * 设置SP指针
     * --------------------------------------------
     */
    bl    plat_set_my_stack

    /* ---------------------------------------------
     * 串口初始化,更新内存布局信息,并初始化页表使能mmu
     * ---------------------------------------------
     */
    mov    x0, x20
    bl    bl2_early_platform_setup
    bl    bl2_plat_arch_setup

    /* ---------------------------------------------
     * 跳转到主函数(通过SMC执行下一级BL不会返回)
     * ---------------------------------------------
     */
    bl    bl2_main

    /* ---------------------------------------------
     * 下面的代码不会执行
     * ---------------------------------------------
     */
    no_ret    plat_panic_handler

endfunc bl2_entrypoint

bl2_main为bl2的主程序,位于bl2/bl2_main.c中

void bl2_main(void)
{
 entry_point_info_t *next_bl_ep_info;
 /* 输出提示信息 */
 NOTICE("BL2: %s\n", version_string);
 NOTICE("BL2: %s\n", build_message);

 /* 初始化,这里开启FP/SIMD的访问权限 */
 bl2_arch_setup();

#if TRUSTED_BOARD_BOOT
 /*
  * 初始化认证模块
  * 初始化加密库(crypto_mod),加密库可以用于校验签名和哈希
  * 初始化镜像解析模块(img_parser_mod),用于校验镜像完整性以及从镜像中提取内容
  */
 auth_mod_init();
#endif /* TRUSTED_BOARD_BOOT */

 /* 此处不不只是加载BL2,此处会加载校验所有的BL */
 next_bl_ep_info = bl2_load_images();

#ifdef AARCH32
 /*
  * For AArch32 state BL1 and BL2 share the MMU setup.
  * Given that BL2 does not map BL1 regions, MMU needs
  * to be disabled in order to go back to BL1.
  */
 disable_mmu_icache_secure();
#endif /* AARCH32 */

  /* 通过SMC系统调用运行下一级BL,下一级BL将跳转到EL3 */
 smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0);
}

3)中断服务

15.BL31分析

1)功能概要

BL31主要提供了SMC服务框架,并于SMC服务的添加扩展。并执行下一级BL

2)主过程

BL31入口位于bl31/aarch64/bl31_entrypoint.S

func bl31_entrypoint#if !RESET_TO_BL31     /* 由前端bootloader引导进入,这时cpu会通过X0/X1传递信息      * X0指向`bl31_params`结构体      * X1指向特定架构相关的结构体      */    mov    x20, x0    mov    x21, x1     /* 因为前端bootloader已经初始化了一部分类容,这里只要初始化当前内核的运行环境 */    el3_entrypoint_common                    \        _set_endian=0                    \        _warm_boot_mailbox=0                \        _secondary_cold_boot=0                \        _init_memory=0                    \        _init_c_runtime=1                \        _exception_vectors=runtime_exceptions     /* 恢复之前保存的参数 */    mov    x0, x20    mov    x1, x21#else     /* 由前端cpu复位进入,这时需要重新初始化,非主核cpu不需要初始化 */    el3_entrypoint_common                    \        _set_endian=1                    \        _warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS    \        _secondary_cold_boot=!COLD_BOOT_SINGLE_CPU    \        _init_memory=1                    \        _init_c_runtime=1                \        _exception_vectors=runtime_exceptions     /* 前端没有参数传递进来,给指针赋值0 */    mov    x0, 0    mov    x1, 0#endif /* RESET_TO_BL31 */    bl    bl31_early_platform_setup/* 初始化cpu内部初始化,初始化执行环境 */    bl    bl31_plat_arch_setup/* 初始化页表 */    /* ---------------------------------------------     * Jump to main function.     * ---------------------------------------------     */    bl    bl31_main     /* 把BL31的data段同步到内存 */    adr    x0, __DATA_START__    adr    x1, __DATA_END__    sub    x1, x1, x0    bl    clean_dcache_range    /* 把BL31的bss段同步到内存 */    adr    x0, __BSS_START__    adr    x1, __BSS_END__    sub    x1, x1, x0    bl    clean_dcache_range    /* 退出异常,并执行下一个镜像 */    b    el3_exitendfunc bl31_entrypoint

BL31主程序位于bl31/bl31_main.c

void bl31_main(void)
{
 NOTICE("BL31: %s\n", version_string);
 NOTICE("BL31: %s\n", build_message);

 /* 系统相关的初始化 */
 bl31_platform_setup();
 bl31_lib_init();

 /* 初始化系统服务,并构造索引rt_svc_descs_indices */
 INFO("BL31: Initializing runtime services\n");
 runtime_svc_init();

   /* 判对有无bl31,执行对应的初始化 */
 if (bl32_init) {
  INFO("BL31: Initializing BL32\n");
  (*bl32_init)();
 }

  /* 初始化下一级BL的执行环境,以便异常返回时执行下一级BL */
 bl31_prepare_next_image_entry();

  /* 从BL31退出前执行与特定平台相关的初始化工作 */
 bl31_plat_runtime_setup();
}

3)中断服务

bl31中断服务程序位于bl31/aarch64/runtime_exceptions.S

.globl    runtime_exceptions

    /* ---------------------------------------------------------------------
     * This macro handles Synchronous exceptions.
     * Only SMC exceptions are supported.
     * ---------------------------------------------------------------------
     */
     /* 同步异常处理只处理smc相关的调用 */
    .macro    handle_sync_exception
    /* Enable the SError interrupt */
    msr    daifclr, #DAIF_ABT_BIT

    /* 保存x30下面要分析异常来源 */
    str    x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]

#if ENABLE_RUNTIME_INSTRUMENTATION
    /*
     * Read the timestamp value and store it in per-cpu data. The value
     * will be extracted from per-cpu data by the C level SMC handler and
     * saved to the PMF timestamp region.
     */
    mrs    x30, cntpct_el0/* 时钟 */
    str    x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X29]
    mrs    x29, tpidr_el3 /* tpidr_el3 cpu标示 */
    str    x30, [x29, #CPU_DATA_PMF_TS0_OFFSET]
    ldr    x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X29]
#endif
    /* 获取exceptions code */
    mrs    x30, esr_el3
    ubfx    x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH

    /* Handle SMC exceptions separately from other synchronous exceptions */
    cmp    x30, #EC_AARCH32_SMC
    b.eq    smc_handler32 /* 32bit SMC调用 */

    cmp    x30, #EC_AARCH64_SMC
    b.eq    smc_handler64 /* 64bit SMC调用 */

    /* Other kinds of synchronous exceptions are not handled */
    no_ret    report_unhandled_exception
    .endm


    /* ---------------------------------------------------------------------
     * This macro handles FIQ or IRQ interrupts i.e. EL3, S-EL1 and NS
     * interrupts.
     * ---------------------------------------------------------------------
     */
    .macro    handle_interrupt_exception label
    /* Enable the SError interrupt */
    msr    daifclr, #DAIF_ABT_BIT

    /* 保存x0-x30 sp_el1 */
    str    x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
    bl    save_gp_registers/* 因为此处为函数调用会修改lr(x30),所以lr需要提前保存 */

    /* Save the EL3 system registers needed to return from this exception */
    mrs    x0, spsr_el3 /* 获取进入异常前的程序状态寄存器 */
    mrs    x1, elr_el3  /* 获取程序被中断的异常地址 */
    /* 保存进入异常的程序状态和异常地址 */
    stp    x0, x1, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]

    /* Switch to the runtime stack i.e. SP_EL0 */
    ldr    x2, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
    mov    x20, sp
    msr    spsel, #0
    mov    sp, x2 /* 切换到EL0的SP指针 */

    /*
     * Find out whether this is a valid interrupt type.
     * If the interrupt controller reports a spurious interrupt then return
     * to where we came from.
     */
    bl    plat_ic_get_pending_interrupt_type
    cmp    x0, #INTR_TYPE_INVAL
    b.eq    interrupt_exit_\label

    /*
     * Get the registered handler for this interrupt type.
     * A NULL return value could be 'cause of the following conditions:
     *
     * a. An interrupt of a type was routed correctly but a handler for its
     *    type was not registered.
     *
     * b. An interrupt of a type was not routed correctly so a handler for
     *    its type was not registered.
     *
     * c. An interrupt of a type was routed correctly to EL3, but was
     *    deasserted before its pending state could be read. Another
     *    interrupt of a different type pended at the same time and its
     *    type was reported as pending instead. However, a handler for this
     *    type was not registered.
     *
     * a. and b. can only happen due to a programming error. The
     * occurrence of c. could be beyond the control of Trusted Firmware.
     * It makes sense to return from this exception instead of reporting an
     * error.
     */
    bl    get_interrupt_type_handler
    cbz    x0, interrupt_exit_\label
    mov    x21, x0

    mov    x0, #INTR_ID_UNAVAILABLE

    /* Set the current security state in the 'flags' parameter */
    mrs    x2, scr_el3
    ubfx    x1, x2, #0, #1

    /* Restore the reference to the 'handle' i.e. SP_EL3 */
    mov    x2, x20

    /* x3 will point to a cookie (not used now) */
    mov    x3, xzr

    /* Call the interrupt type handler */
    blr    x21

interrupt_exit_\label:
    /* Return from exception, possibly in a different security state */
    b    el3_exit

    .endm


    .macro save_x18_to_x29_sp_el0
    stp    x18, x19, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X18]
    stp    x20, x21, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X20]
    stp    x22, x23, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X22]
    stp    x24, x25, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X24]
    stp    x26, x27, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X26]
    stp    x28, x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X28]
    mrs    x18, sp_el0
    str    x18, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_SP_EL0]
    .endm


vector_base runtime_exceptions

    /* ---------------------------------------------------------------------
     * Current EL with SP_EL0 : 0x0 - 0x200
     * ---------------------------------------------------------------------
     */
vector_entry sync_exception_sp_el0
    /* We don't expect any synchronous exceptions from EL3 */
    no_ret    report_unhandled_exception
    check_vector_size sync_exception_sp_el0

vector_entry irq_sp_el0
    /*
     * EL3 code is non-reentrant. Any asynchronous exception is a serious
     * error. Loop infinitely.
     */
    no_ret    report_unhandled_interrupt
    check_vector_size irq_sp_el0


vector_entry fiq_sp_el0
    no_ret    report_unhandled_interrupt
    check_vector_size fiq_sp_el0


vector_entry serror_sp_el0
    no_ret    report_unhandled_exception
    check_vector_size serror_sp_el0

    /* ---------------------------------------------------------------------
     * Current EL with SP_ELx: 0x200 - 0x400
     * ---------------------------------------------------------------------
     */
vector_entry sync_exception_sp_elx
    /*
     * This exception will trigger if anything went wrong during a previous
     * exception entry or exit or while handling an earlier unexpected
     * synchronous exception. There is a high probability that SP_EL3 is
     * corrupted.
     */
    no_ret    report_unhandled_exception
    check_vector_size sync_exception_sp_elx

vector_entry irq_sp_elx
    no_ret    report_unhandled_interrupt
    check_vector_size irq_sp_elx

vector_entry fiq_sp_elx
    no_ret    report_unhandled_interrupt
    check_vector_size fiq_sp_elx

vector_entry serror_sp_elx
    no_ret    report_unhandled_exception
    check_vector_size serror_sp_elx

    /* ---------------------------------------------------------------------
     * Lower EL using AArch64 : 0x400 - 0x600
     * ---------------------------------------------------------------------
     */
vector_entry sync_exception_aarch64
    /*
     * This exception vector will be the entry point for SMCs and traps
     * that are unhandled at lower ELs most commonly. SP_EL3 should point
     * to a valid cpu context where the general purpose and system register
     * state can be saved.
     */
    handle_sync_exception
    check_vector_size sync_exception_aarch64

vector_entry irq_aarch64
    handle_interrupt_exception irq_aarch64
    check_vector_size irq_aarch64

vector_entry fiq_aarch64
    handle_interrupt_exception fiq_aarch64
    check_vector_size fiq_aarch64

vector_entry serror_aarch64
    /*
     * SError exceptions from lower ELs are not currently supported.
     * Report their occurrence.
     */
    no_ret    report_unhandled_exception
    check_vector_size serror_aarch64

    /* ---------------------------------------------------------------------
     * Lower EL using AArch32 : 0x600 - 0x800
     * ---------------------------------------------------------------------
     */
vector_entry sync_exception_aarch32
    /*
     * This exception vector will be the entry point for SMCs and traps
     * that are unhandled at lower ELs most commonly. SP_EL3 should point
     * to a valid cpu context where the general purpose and system register
     * state can be saved.
     */
    handle_sync_exception
    check_vector_size sync_exception_aarch32

vector_entry irq_aarch32
    handle_interrupt_exception irq_aarch32
    check_vector_size irq_aarch32

vector_entry fiq_aarch32
    handle_interrupt_exception fiq_aarch32
    check_vector_size fiq_aarch32

vector_entry serror_aarch32
    /*
     * SError exceptions from lower ELs are not currently supported.
     * Report their occurrence.
     */
    no_ret    report_unhandled_exception
    check_vector_size serror_aarch32


    /* ---------------------------------------------------------------------
     * The following code handles secure monitor calls.
     * Depending upon the execution state from where the SMC has been
     * invoked, it frees some general purpose registers to perform the
     * remaining tasks. They involve finding the runtime service handler
     * that is the target of the SMC & switching to runtime stacks (SP_EL0)
     * before calling the handler.
     *
     * Note that x30 has been explicitly saved and can be used here
     * ---------------------------------------------------------------------
     */
func smc_handler
smc_handler32:
    /* Check whether aarch32 issued an SMC64 */
    tbnz    x0, #FUNCID_CC_SHIFT, smc_prohibited

    /*
     * Since we're are coming from aarch32, x8-x18 need to be saved as per
     * SMC32 calling convention. If a lower EL in aarch64 is making an
     * SMC32 call then it must have saved x8-x17 already therein.
     */
    stp    x8, x9, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X8]
    stp    x10, x11, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X10]
    stp    x12, x13, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X12]
    stp    x14, x15, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X14]
    stp    x16, x17, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X16]

    /* x4-x7, x18, sp_el0 are saved below */

smc_handler64:
    /*
     * Populate the parameters for the SMC handler.
     * We already have x0-x4 in place. x5 will point to a cookie (not used
     * now). x6 will point to the context structure (SP_EL3) and x7 will
     * contain flags we need to pass to the handler Hence save x5-x7.
     *
     * Note: x4 only needs to be preserved for AArch32 callers but we do it
     *       for AArch64 callers as well for convenience
     */
    stp    x4, x5, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X4]
    stp    x6, x7, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X6]

    /* Save rest of the gpregs and sp_el0*/
    save_x18_to_x29_sp_el0

    mov    x5, xzr
    mov    x6, sp

    /* Get the unique owning entity number */
    /* X0用于确定调用的功能
     * b31    调用类型 1->Fast Call 0->Yielding Call
     * b30    标示32bit还是64bit调用
     * b29:24 服务类型
     *           0x0  ARM架构相关
     *           0x1  CPU服务
     *           0x2  SiP服务
     *           0x3  OEM服务
     *           0x4  标准安全服务
     *           0x5  标准Hypervisor服务
     *           0x6  厂商定制的Hypervisor服务
     *     0x07-0x2f  预留
     *     0x30-0x31  Trusted Application Calls
     *     0x32-0x3f  Trusted OS Calls
     * b23:16 在Fast Call时这些比特位必须为0,其他值保留未用
     *        ARMv7传统的Trusted OS在Fast Call时必须为1
     * b15:0  函数号
     */
    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

    /* __RT_SVC_DESCS_START__为rt_svc_descs段的起始地址,此段存放服务描述符
     * RT_SVC_DESC_HANDLE为服务句柄在服务描述符结构体中的偏移量
     * 以下代码保存第一个复位句柄的地址到x11中
     */
    adr    x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)

    /* Load descriptor index from array of indices */
    /* 因为描述符在内存中布局是乱的通过rt_svc_descs_indices数组来查找描述符在内存中的位置
     * rt_svc_descs_indices为rt_svc_descs的索引
     */
    adr    x14, rt_svc_descs_indices
    ldrb    w15, [x14, x16]

    /*
     * Restore the saved C runtime stack value which will become the new
     * SP_EL0 i.e. EL3 runtime stack. It was saved in the 'cpu_context'
     * structure prior to the last ERET from EL3.
     */
    ldr    x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]

    /*
     * Any index greater than 127 is invalid. Check bit 7 for
     * a valid index
     */
    tbnz    w15, 7, smc_unknown/* 索引的值不应该大于127 */

    /* Switch to SP_EL0 */
    msr    spsel, #0

    /*
     * Get the descriptor using the index
     * x11 = (base + off), x15 = index
     *
     * handler = (base + off) + (index << log2(size))
     */
    lsl    w10, w15, #RT_SVC_SIZE_LOG2/* 计算偏移量 */
    ldr    x15, [x11, w10, uxtw]/* 加载出对应服务的句柄 */

    /*
     * Save the SPSR_EL3, ELR_EL3, & SCR_EL3 in case there is a world
     * switch during SMC handling.
     * TODO: Revisit if all system registers can be saved later.
     */
    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]

    /* Copy SCR_EL3.NS bit to the flag to indicate caller's security */
    bfi    x7, x18, #0, #1/* x7用于传递给服务函数security state */

    mov    sp, x12

    /*
     * Call the Secure Monitor Call handler and then drop directly into
     * el3_exit() which will program any remaining architectural state
     * prior to issuing the ERET to the desired lower EL.
     */
#if DEBUG
    cbz    x15, rt_svc_fw_critical_error/* x15等于0说明有错误存在 */
#endif
    blr    x15 /* 调用对应的服务 */

    b    el3_exit/* 异常恢复 */

smc_unknown:
    /*
     * Here we restore x4-x18 regardless of where we came from. AArch32
     * callers will find the registers contents unchanged, but AArch64
     * callers will find the registers modified (with stale earlier NS
     * content). Either way, we aren't leaking any secure information
     * through them.
     */
    mov    w0, #SMC_UNK
    b    restore_gp_registers_callee_eret

smc_prohibited:
    ldr    x30, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_LR]
    mov    w0, #SMC_UNK
    eret

rt_svc_fw_critical_error:
    /* Switch to SP_ELx */
    msr    spsel, #1
    no_ret    report_unhandled_exception
endfunc smc_handler

END

作者:wxjstz
文章来源:TrustZone

推荐阅读

更多物联网安全,PSA等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA技术交流群,请备注研究方向。
推荐阅读
关注数
4570
内容数
192
Arm发布的PSA旨在为物联网安全提供一套全面的安全指导方针,使从芯片制造商到设备开发商等价值链中的每位成员都能成功实现安全运行。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息