Khorina · 9月25日

arm-trusted-firmware分析(上)

1.概要

这个公众号就是从写ATF开始起家的,哈哈哈。最近看到了一篇不错的文章,推荐给大家。

ARM Trusted Firmware实现了一个可信引导过程,并给操作系统提供运行环境(SMC调用服务)。本文分析基于ARM Trusted Firmware - version 1.3,代码托管与GitHub。

引导过程分为多步,每一步为一个独立的程序。上一步负责验证下一步的程序(可能是多个,BL2会加载认证其他所有的bootloader),并把控制权交给下一级程序。每一级程序需要为下一级程序提供运行环境。最终引导一个通用的bootloader,并实现SMC调用服务。

本文主要分析AArch64相关部分代码

2.安全相关

1)auth_common

此模块主要用于声明一些公共的数据结构,这些数据结构主要用与描述认证的方式auth_method_desc_s和认证的参数auth_param_desc_t

认证参数auth_param_desc_t是由两个基本结构组成:类型auth_param_type_desc_t和数据auth_param_data_desc_t

并定义了两个宏用于快速实现一个类型和数据

#define AUTH_PARAM_TYPE_DESC(_type, _cookie) \
 { \
  .type = _type, \
  .cookie = (void *)_cookie \
 }

#define AUTH_PARAM_DATA_DESC(_ptr, _len) \
 { \
  .ptr = (void *)_ptr, \
  .len = (unsigned int)_len \
 }

2)crypto_mod

此模块主要实现一个框架,并非具体实现。主要用于校验哈希和签名。

此框架主要基于一个结构体,结构体定义如下:

typedef struct crypto_lib_desc_s {
 const char *name;/* 名称, */

 void (*init)(void);/* 初始化方法 */

 /* 校验签名的方法 */
 int (*verify_signature)(
                void *data_ptr, unsigned int data_len,   /* 要签名的数据 */
    void *sig_ptr, unsigned int sig_len,     /* 签名 */
    void *sig_alg, unsigned int sig_alg_len, /* 签名算法 */
    void *pk_ptr, unsigned int pk_len);      /* 公钥 */

 /* 校验哈希的方法 */
 int (*verify_hash)(
               void *data_ptr, unsigned int data_len,       /* 要计算哈希的数据 */
      void *digest_info_ptr, unsigned int digest_info_len);/* 哈希值 */
} crypto_lib_desc_t;

通过REGISTER_CRYPTO_LIB宏实现一个名为crypto_lib_desc类型为crypto_lib_desc_t结构体。宏实现如下:

#define REGISTER_CRYPTO_LIB(_name, _init, _verify_signature, _verify_hash) \
 const crypto_lib_desc_t crypto_lib_desc = { \
  .name = _name, \
  .init = _init, \
  .verify_signature = _verify_signature, \
  .verify_hash = _verify_hash \
 }

此模块通过操作crypto_lib_desc变量,实现模块初始化、校验签名、校验哈希。函数声明如下:

/* 模块初始化 */
void crypto_mod_init(void);

/* 校验签名 */
int crypto_mod_verify_signature(void *data_ptr, unsigned int data_len,
    void *sig_ptr, unsigned int sig_len,
    void *sig_alg, unsigned int sig_alg_len,
    void *pk_ptr, unsigned int pk_len);
/* 校验哈希值 */
int crypto_mod_verify_hash(void *data_ptr, unsigned int data_len,
      void *digest_info_ptr, unsigned int digest_info_len);

3)img_parser_mod

此模块实现一个框架,并非具体实现。主要用于校验镜像完整性,以及从镜像中提取内容。

镜像被分为以下几种类型

typedef enum img_type_enum {
 IMG_RAW,   /* Binary image */
 IMG_PLAT,   /* Platform specific format */
 IMG_CERT,   /* X509v3 certificate */
 IMG_MAX_TYPES,
} img_type_t;

镜像解析器被包装成一个结构体,定义如下

typedef struct img_parser_lib_desc_s {
 img_type_t img_type;   /* 镜像解析器操作的镜像的类型 */
 const char *name;      /* 镜像解析器的名字 */

 void (*init)(void);    /* 初始化镜像解析器 */

    /* 校验镜像完整性 */
 int (*check_integrity)(void *img, unsigned int img_len);

    /* 从镜像中提取内容 */
 int (*get_auth_param)(
            const auth_param_type_desc_t *type_desc,  /* 要提取的内容的描述信息 */
   void *img, unsigned int img_len,          /* 镜像内容 */
   void **param, unsigned int *param_len);   /* 输出:提取的内容 */
} img_parser_lib_desc_t;

通过宏REGISTER_IMG_PARSER_LIB实现一个解析器,定义如下:

#define REGISTER_IMG_PARSER_LIB(_type, _name, _init, _check_int, _get_param) \
 static const img_parser_lib_desc_t __img_parser_lib_desc_##_type \
 __section(".img_parser_lib_descs") __used = { \
  .img_type = _type, \
  .name = _name, \
  .init = _init, \
  .check_integrity = _check_int, \
  .get_auth_param = _get_param \
 }

多个解析器被放在同一个段.img_parser_lib_descs。配合链接脚本此段开始符号为__PARSER_LIB_DESCS_START__,结束符号为__PARSER_LIB_DESCS_END__。两个符号可以看作解析器数组的起始符号和结束符号。

img_parser_init通过遍历解析器数组,调用解析器的初始化函数init,对解析器进行初始化。并根据img_type构建索引(通过镜像类型找到解析器下标)。并防止有多个相同类型的镜像解析器存在。

img_parser_check_integrity校验镜像完整性

img_parser_get_auth_param从镜像中提取需要的内容

4)auth_mod

auth_mod实现了一个校验镜像的模型,此模型通过结构体auth_img_desc_t描述。

typedef struct auth_img_desc_s {
 /* 镜像的ID,标志是哪一个镜像 */
 unsigned int img_id;

 /* 镜像类型(Binary、证书等) */
 img_type_t img_type;

 /* 父镜像,保存了认证当前镜像的公钥、哈希等 */
 const struct auth_img_desc_s *parent;

 /* 认证当前镜像的方法 */
 auth_method_desc_t img_auth_methods[AUTH_METHOD_NUM];

 /* 用于校验子镜像的公钥、哈希等 */
 auth_param_desc_t authenticated_data[COT_MAX_VERIFIED_PARAMS];
} auth_img_desc_t;

并定义一个宏REGISTER_COT,用于注册auth_img_desc_t数组

#define REGISTER_COT(_cot) \
 const auth_img_desc_t *const cot_desc_ptr = \
   (const auth_img_desc_t *const)&_cot[0]; \
 unsigned int auth_img_flags[sizeof(_cot)/sizeof(_cot[0])]

auth_mod_verify_img通过img_id访问cot_desc_ptr数组,找到对应的镜像描述符auth_method_desc_t,即可知道当前镜像的认证方式,访问父节点找到签名的公钥或哈希,即可认证当前镜像是否合法。在认证完当前镜像后,从镜像中解析出公钥哈希等放入当前的镜像描述符中,便于对下一级镜像校验

int auth_mod_verify_img(unsigned int img_id,
   void *img_ptr,
   unsigned int img_len)
{
 const auth_img_desc_t *img_desc = NULL;
 const auth_method_desc_t *auth_method = NULL;
 void *param_ptr;
 unsigned int param_len;
 int rc, i;

 /* 根据img_id获取镜像描述符 */
 img_desc = &cot_desc_ptr[img_id];

 /* 校验镜像完整性 */
 rc = img_parser_check_integrity(img_desc->img_type, img_ptr, img_len);
 return_if_error(rc);

 /* 根据镜像描述符的仍正方式对镜像进行认证 */
 for (i = 0 ; i < AUTH_METHOD_NUM ; i++) {
  auth_method = &img_desc->img_auth_methods[i];
  switch (auth_method->type) {
  case AUTH_METHOD_NONE:/* 不需要认证 */
   rc = 0;
   break;
  case AUTH_METHOD_HASH:/* 哈希认证 */
   rc = auth_hash(&auth_method->param.hash,
     img_desc, img_ptr, img_len);
   break;
  case AUTH_METHOD_SIG:/* 签名认证 */
   rc = auth_signature(&auth_method->param.sig,
     img_desc, img_ptr, img_len);
   break;
  case AUTH_METHOD_NV_CTR:/* Non-Volatile counter认证? */
   rc = auth_nvctr(&auth_method->param.nv_ctr,
     img_desc, img_ptr, img_len);
   break;
  default:
   /* 未知认证类型,报错 */
   rc = 1;
   break;
  }
  return_if_error(rc);
 }

 /* 从镜像中解析出公钥哈希等,以便对下一级镜像进行认证 */
 for (i = 0 ; i < COT_MAX_VERIFIED_PARAMS ; i++) {
  if (img_desc->authenticated_data[i].type_desc == NULL) {
   continue;
  }

  /* 通过镜像解析器从镜像中提取内容 */
  rc = img_parser_get_auth_param(img_desc->img_type,
    img_desc->authenticated_data[i].type_desc,
    img_ptr, img_len, &param_ptr, &param_len);
  return_if_error(rc);

  /* 异常检查
     防止从镜像中解析出的数据字节数大于镜像描述符中的字节数
     出现内存访问溢出 */
  if (param_len > img_desc->authenticated_data[i].data.len) {
   return 1;
  }

  /* 把解析出的内容拷贝到镜像描述符中,便于解析下一级BL */
  memcpy((void *)img_desc->authenticated_data[i].data.ptr,
    (void *)param_ptr, param_len);
 }

 /* 标记镜像以认证过 */
 auth_img_flags[img_desc->img_id] |= IMG_FLAG_AUTHENTICATED;

 return 0;
}

3.设备抽象

为了便于移植ARM Trusted Firmware提供了一种设备抽象机制,这里主要用来抽象镜像读取操作,以便加载镜像到系统内存中。

1)主要数据结构

io_dev_info_t

io_dev_info_t对应一个设备描述符

/* 设备操作相关的函数句柄 */
typedef struct io_dev_funcs {
 io_type_t (*type)(void);
 int (*open)(io_dev_info_t *dev_info, const uintptr_t spec,
   io_entity_t *entity);
 int (*seek)(io_entity_t *entity, int mode, ssize_t offset);
 int (*size)(io_entity_t *entity, size_t *length);
 int (*read)(io_entity_t *entity, uintptr_t buffer, size_t length,
   size_t *length_read);
 int (*write)(io_entity_t *entity, const uintptr_t buffer,
   size_t length, size_t *length_written);
 int (*close)(io_entity_t *entity);
 int (*dev_init)(io_dev_info_t *dev_info, const uintptr_t init_params);
 int (*dev_close)(io_dev_info_t *dev_info);
} io_dev_funcs_t;

typedef struct io_dev_info {
 const struct io_dev_funcs *funcs;/* 设备操作相关的函数句柄 */
 uintptr_t info;/* 设备信息(比如设备地址) */
} io_dev_info_t;

io_entity_t

io_entity_t对应一个文件描述符

typedef struct io_entity {
 struct io_dev_info *dev_handle;/* 设备信息 */
 uintptr_t info;/* 文件有关信息 */
} io_entity_t;

io_dev_connector_t

io_dev_connector_t对应一个驱动

typedef struct io_dev_connector {
 /* dev_open opens a connection to a particular device driver */
 int (*dev_open)(const uintptr_t dev_spec, io_dev_info_t **dev_info);
} io_dev_connector_t;

关联

系统通过dev_countdevices,来维护一个注册的设备列表

/* 注册一个设备此变量加1 */
static unsigned int dev_count;
/* 指向设备的指针数组,注册的设备会被添加进来 */
static const io_dev_info_t *devices[MAX_IO_DEVICES];

系统通过entity_poolentity_mapentity_count来维护已经打开的文件

/* 实际的文件句柄 */
static io_entity_t entity_pool[MAX_IO_HANDLES];

/* 指针数组用于标记entity_pool中的文件句柄是否空闲 */
static io_entity_t *entity_map[MAX_IO_HANDLES];

/* 记录当前打开的文件句柄书目 */
static unsigned int entity_count;

2)接口

打开设备

int io_dev_open(const struct io_dev_connector *dev_con,/* 驱动描述符 */
  const uintptr_t dev_spec,/* 用于指定打开那个设备,与具体驱动有关 */
  uintptr_t *dev_handle);/* 返回值:设备句柄(io_dev_info_t) */

设备初始化

/*
 * dev_handle设备句柄
 * init_params设备初始化的参数(特定设备相关)
 */
int io_dev_init(uintptr_t dev_handle, const uintptr_t init_params);

关闭设备

int io_dev_close(uintptr_t dev_handle);

文件相关操作

/*
 * 打开文件
 * dev_handle设备句柄
 * spec文件信息(特定设备相关)
 * handle文件句柄
 */
int io_open(uintptr_t dev_handle, const uintptr_t spec, uintptr_t *handle);

/*
 * 移动文件指针
 * handle文件句柄
 * mode移动文件指针的相对初始地址
 * offset移动文件指针的相对地址
 */
int io_seek(uintptr_t handle, io_seek_mode_t mode, ssize_t offset);

/*
 * 获取文件大小
 * handle文件句柄
 * length返回值,文件大小
 */
int io_size(uintptr_t handle, size_t *length);

/*
 * 读文件
 * handle文件句柄
 * buffer是读出缓存的地址
 * length是读出缓存的大小
 * length_read返回值,实际读出的大小
 */
int io_read(uintptr_t handle, uintptr_t buffer, size_t length,
  size_t *length_read);

/*
 * 写文件
 * handle文件句柄
 * buffer要写入文件的缓存的起始地址
 * length要写如文件的缓存的大小
 * length_written返回值,实际写入的大小
 */
int io_write(uintptr_t handle, const uintptr_t buffer, size_t length,
  size_t *length_written);

/* 关闭文件,handle文件句柄 */
int io_close(uintptr_t handle);

4.EL3初始化

EL3初始化通过宏el3_entrypoint_common实现,此宏位于include/common/aarch64/el3_common_macros.S

此宏有6个参数分别控制初始化的内容

.macro el3_entrypoint_common                    \
        _set_endian, _warm_boot_mailbox, _secondary_cold_boot,    \
        _init_memory, _init_c_runtime, _exception_vectors

1)大小端设置

.if \_set_endian
    /* sctlr_el3.ee = 0 EL3设置为小端 */
    mrs    x0, sctlr_el3
    bic    x0, x0, #SCTLR_EE_BIT
    msr    sctlr_el3, x0
    isb
.endif

2)热启动处理

此部分,用在热启动时跳过初始化代码

.if \_warm_boot_mailbox
    /* 冷启动返回0,
       热启动返回PLAT_ARM_TRUSTED_MAILBOX_BASE处的值 */
    bl    plat_get_my_entrypoint
    cbz    x0, do_cold_boot
    /* 热启动跳转到PLAT_ARM_TRUSTED_MAILBOX_BASE内存处指定的地址继续执行 */
    br    x0

/* 冷启动继续执行 */
do_cold_boot:
.endif

3)次处理器处理

为了便于处理,在初始化时会让其他处理器暂停(执行while循环)

.if \_secondary_cold_boot
    bl    plat_is_my_cpu_primary        /* 判断当前CPU是不是主CPU */
    cbnz    w0, do_primary_cold_boot

    /* 非主CPU暂停,
       循环等待PLAT_ARM_TRUSTED_MAILBOX_BASE出出现地址,跳转 */
    bl    plat_secondary_cold_boot_setup
    /* 此处不会执行 */
    bl    el3_panic

/* 主处理器继续 */
do_primary_cold_boot:
.endif

4)内存初始化

此部分与具体硬件相关,需要具体平台实现void platform_mem_init(void)

.if \_init_memory
    bl    platform_mem_init
.endif

5)C运行环境初始化

    .if \_init_c_runtime
#ifdef IMAGE_BL31
        /* 初始化cache */
        adr    x0, __RW_START__
        adr    x1, __RW_END__
        sub    x1, x1, x0
        bl    inv_dcache_range
#endif /* IMAGE_BL31 */

        /* bss内存初始化为0 */
        ldr    x0, =__BSS_START__
        ldr    x1, =__BSS_SIZE__
        bl    zeromem16

#if USE_COHERENT_MEM
        /* 多个内核共享的内存,初始化为0 */
        ldr    x0, =__COHERENT_RAM_START__
        ldr    x1, =__COHERENT_RAM_UNALIGNED_SIZE__
        bl    zeromem16
#endif

#ifdef IMAGE_BL1
        /* data段初始化 */
        ldr    x0, =__DATA_RAM_START__
        ldr    x1, =__DATA_ROM_START__
        ldr    x2, =__DATA_SIZE__
        bl    memcpy16
#endif
    .endif

6)栈初始化

使用SP_EL0作为C语言的堆栈指针,此处有单内核堆栈初始化与多内核堆栈初始化,这里只关注多内核堆栈初始化。这样每个内核会获取到一个不同的堆栈指针。

msr    spsel, #0 /* 切换当前SP指针为SP_EL0 */
bl    plat_set_my_stack

func plat_set_my_stack
    mov    x9, x30 /* 保护LR下面要调用函数 */
    bl     plat_get_my_stack
    mov    sp, x0/* 修改SP */
    ret    x9/* 返回 */
endfunc plat_set_my_stack

func plat_get_my_stack
    mrs    x0, mpidr_el1 /* 读取CPU标示 */
    b    platform_get_stack
endfunc plat_get_my_stack

func plat_get_my_stack
    mov    x10, x30 /* 保护LR下面要调用函数 */
    get_my_mp_stack platform_normal_stacks, PLATFORM_STACK_SIZE
    ret    x10/* 返回 */
endfunc plat_get_my_stack

/* 
 * _name为栈顶地址
 * _size一个内核的栈的大小
 * 栈地址 = 栈顶地址 + (内核编号 + 1)* 单个内核栈大小
 */
.macro get_my_mp_stack _name, _size
    bl  plat_my_core_pos /* 获取处理器编号 */
    ldr x2, =(\_name + \_size)/* 起始地址,栈向低地址增长 */
    mov x1, #\_size /* 一个内核栈的大小 */
    madd x0, x0, x1, x2 /* x0=x0*x1+x2 */
.endm

7)异常向量初始化

    /* 使能指令缓存、对齐检查 初始化异常向量 */
    el3_arch_init_common \_exception_vectors
    
.macro el3_arch_init_common _exception_vectors
    /* 使能指令缓存、对齐检查 */
    mov    x1, #(SCTLR_I_BIT | SCTLR_A_BIT | SCTLR_SA_BIT)
    mrs    x0, sctlr_el3
    orr    x0, x0, x1
    msr    sctlr_el3, x0
    isb

#ifdef IMAGE_BL31

    bl    init_cpu_data_ptr
#endif /* IMAGE_BL31 */

    /* 异常向量设置 */
    adr    x0, \_exception_vectors
    msr    vbar_el3, x0
    isb
    
    /* 不允许从非安全存储器获取安全状态指令
       外部中止和SError路由到EL3*/
    mov    x0, #(SCR_RES1_BITS | SCR_EA_BIT | SCR_SIF_BIT)
    msr    scr_el3, x0

    /* 复位mdcr_el3寄存器 */
    msr    mdcr_el3, xzr

    /* 使能外部中止和SError异常 */
    msr    daifclr, #DAIF_ABT_BIT

    /* ---------------------------------------------------------------------
     * The initial state of the Architectural feature trap register
     * (CPTR_EL3) is unknown and it must be set to a known state. All
     * feature traps are disabled. Some bits in this register are marked as
     * reserved and should not be modified.
     *
     * CPTR_EL3.TCPAC: This causes a direct access to the CPACR_EL1 from EL1
     *  or the CPTR_EL2 from EL2 to trap to EL3 unless it is trapped at EL2.
     *
     * CPTR_EL3.TTA: This causes access to the Trace functionality to trap
     *  to EL3 when executed from EL0, EL1, EL2, or EL3. If system register
     *  access to trace functionality is not supported, this bit is RES0.
     *
     * CPTR_EL3.TFP: This causes instructions that access the registers
     *  associated with Floating Point and Advanced SIMD execution to trap
     *  to EL3 when executed from any exception level, unless trapped to EL1
     *  or EL2.
     * ---------------------------------------------------------------------
     */
    mrs    x0, cptr_el3
    bic    w0, w0, #TCPAC_BIT
    bic    w0, w0, #TTA_BIT
    bic    w0, w0, #TFP_BIT
    msr    cptr_el3, x0
.endm

5.CPU数据

每个处理器有私有的数据

1)数据结构

其中保存了上下文信息、电源管理信息等

typedef struct cpu_data {
#ifndef AARCH32
 void *cpu_context[2];/* cpu的上下文,根据security state分为两部分 */
#endif
 uintptr_t cpu_ops_ptr;/* 指针,执行的结构存放cpu操作的函数以及cpu的MIDR(cpu编号) */
#if CRASH_REPORTING/* 崩溃信息 */
 u_register_t crash_buf[CPU_DATA_CRASH_BUF_SIZE >> 3];
#endif
#if ENABLE_RUNTIME_INSTRUMENTATION/* PMF服务相关 */
 uint64_t cpu_data_pmf_ts[CPU_DATA_PMF_TS_COUNT];
#endif
 struct psci_cpu_data psci_svc_cpu_data;/* cpu电源管理状态 */
#if PLAT_PCPU_DATA_SIZE/* 平台特定数据 */
 uint8_t platform_cpu_data[PLAT_PCPU_DATA_SIZE];
#endif
} __aligned(CACHE_WRITEBACK_GRANULE) cpu_data_t;

2)初始化

每个cpu有一个cpu_data_t的数据,定义在lib/el3_runtime/aarch64/cpu_data_array.c

cpu_data_t percpu_data[PLATFORM_CORE_COUNT];

为了便于cpu访问到自己的数据通过TPIDR_EL3指向自己的cpu_data_t,初始化过程如下

func init_cpu_data_ptr
    mov    x10, x30 /* 保存lr便于下面调用函数 */
    bl    plat_my_core_pos /* 获取cpu的编号 */
    bl    _cpu_data_by_index/* 通过cpu编号获取cpu_data_t结构 */
    msr    tpidr_el3, x0
    ret    x10
endfunc init_cpu_data_ptr

func _cpu_data_by_index
    adr    x1, percpu_data
    add    x0, x1, x0, LSL #CPU_DATA_LOG2SIZE 
    ret
endfunc _cpu_data_by_index

3)访问CPU数据

通过读取TPIDR_EL3获取cpu_data_t

static inline struct cpu_data *_cpu_data(void){ return (cpu_data_t *)read_tpidr_el3();}

6.中断现场保存恢复

1)单处理器相关

此部分代码位于lib/el3_runtime/aarch64/context.S中,声明在include/lib/el3_runtime/aarch64/context.h

include/lib/el3_runtime/aarch64/context.h声明了现场保护结构体(寄存器保存在内存中的结构),以及各个寄存器在现场保护结构体中的偏移量(在汇编程序中有效),并且声明了一些C语言访问现场抱回结构的方法

DEFINE_REG_STRUCT用于定义一组寄存器,name寄存器组的名字,num_regs为寄存器的个数

#define DEFINE_REG_STRUCT(name, num_regs) \
 typedef struct name {   \
  uint64_t _regs[num_regs]; \
 }  __aligned(16) name##_t

现场保护结构定义如下

/* 声明一些寄存器组 */
DEFINE_REG_STRUCT(gp_regs, CTX_GPREG_ALL);
DEFINE_REG_STRUCT(el1_sys_regs, CTX_SYSREG_ALL);
#if CTX_INCLUDE_FPREGS
DEFINE_REG_STRUCT(fp_regs, CTX_FPREG_ALL);
#endif
DEFINE_REG_STRUCT(el3_state, CTX_EL3STATE_ALL);

/* 现场保护结构体 */
typedef struct cpu_context {
 gp_regs_t gpregs_ctx;/* 通用寄存器组 */
 el3_state_t el3state_ctx;/* 状态寄存器组 */
 el1_sys_regs_t sysregs_ctx;/* 系统寄存器组 */
#if CTX_INCLUDE_FPREGS
 fp_regs_t fpregs_ctx;/* 浮点寄存器组 */
#endif
} cpu_context_t;

为了方便访问现场保护结构体中的一组寄存器,特别定义如下宏

#define get_el3state_ctx(h) (&((cpu_context_t *) h)->el3state_ctx)
#if CTX_INCLUDE_FPREGS
#define get_fpregs_ctx(h) (&((cpu_context_t *) h)->fpregs_ctx)
#endif
#define get_sysregs_ctx(h) (&((cpu_context_t *) h)->sysregs_ctx)
#define get_gpregs_ctx(h) (&((cpu_context_t *) h)->gpregs_ctx)

为了便于访问寄存器组的寄存器,特别定义如下宏用于读写寄存器

#define read_ctx_reg(ctx, offset) ((ctx)->_regs[offset >> DWORD_SHIFT])
#define write_ctx_reg(ctx, offset, val) (((ctx)->_regs[offset >> DWORD_SHIFT]) = val)

context.S文件中通过汇编实现了8个函数,用于现场保护还原。下面是这8个函数的C语言声明

/* 保存系统寄存器到regs中 */
void el1_sysregs_context_save(el1_sys_regs_t* regs);

/* 从regs中恢复系统寄存器 */
void el1_sysregs_context_restore(el1_sys_regs_t *regs);

/* 保存浮点寄存器到regs中 */
void fpregs_context_save(fp_regs_t *regs);

/* 从regs中恢复浮点寄存器 */
void fpregs_context_restore(fp_regs_t *regs);

/* 保存X0-X29、SP_EL0到堆栈
   X30需要在CALL此函数前保存,CALL指令会修改X30 */
void save_gp_registers(void);

/* 从堆栈中恢复X0-X30、SP_EL0并退出异常 */
void restore_gp_registers_eret(void);

/* 从堆栈中恢复X4-X30、SP_EL0并退出异常
   此函数在特定情况下有效(SMC调用通过X0-X3返回调用结果) 
   但此函数实际应用较少,程序会通过访问现场保护的指针修改X0-X4 */
void restore_gp_registers_callee_eret(void)

2)多处理器相关

中断现场保护通过SP_EL3完成,但每个处理器需要分别初始化自己的SP_EL3。这部分在异常退出之前(引导下一级代码之前)通过设置SP_EL3完成,这样每个处理器发生中断时就有了独立的上下文(context),具体通过cm_set_next_context函数实现

static inline void cm_set_next_context(void *context)
{
#if DEBUG
   /* 调试代码
      用于确定当前使用的是SP_EL0
      C语言运行环境使用的是SP_EL0 */
 uint64_t sp_mode;
 __asm__ volatile("mrs %0, SPSel\n"
    : "=r" (sp_mode));

 assert(sp_mode == MODE_SP_EL0);
#endif
 /* 切换到SP_EL3赋值后,修改为SP_EL0 */
 __asm__ volatile("msr spsel, #1\n"
    "mov sp, %0\n"
    "msr spsel, #0\n"
    : : "r" (context));
}

为了实现在Secure WordNon-Secure Word之间切换,每个处理器有两个上下文。

#define get_cpu_data(_m)  _cpu_data()->_m
#define set_cpu_data(_m, _v) _cpu_data()->_m = _v

/* 获取当前安全状态下的上下文 */
void *cm_get_context(uint32_t security_state)
{
 assert(security_state <= NON_SECURE);
 return get_cpu_data(cpu_context[security_state]);
}

/* 设置当前安全状态下的上下文 */
void cm_set_context(void *context, uint32_t security_state)
{
 assert(security_state <= NON_SECURE);
 set_cpu_data(cpu_context[security_state], context);
}

7.加载镜像

镜像加载通过load_auth_image函数实现,并实现对镜像的认证

int load_auth_image(meminfo_t *mem_layout,
      unsigned int image_id,
      uintptr_t image_base,
      image_info_t *image_data,
      entry_point_info_t *entry_point_info)
{
   /* 实际的加载方法 */
 return load_auth_image_internal(mem_layout, image_id, image_base,
     image_data, entry_point_info, 0);
}

具体实现通过load_auth_image_internal实现

static int load_auth_image_internal(meminfo_t *mem_layout,
        unsigned int image_id,
        uintptr_t image_base,
        image_info_t *image_data,
        entry_point_info_t *entry_point_info,
        int is_parent_image)
{
 int rc;

/* 可信加载要先加载父镜像,这样才能校验下一级镜像 */
#if TRUSTED_BOARD_BOOT
 unsigned int parent_id;

 /* 获取父镜像id */
 rc = auth_mod_get_parent_id(image_id, &parent_id);
 if (rc == 0) {
  /* 递归,现加载认真父镜像 */
  rc = load_auth_image_internal(mem_layout, parent_id, image_base,
         image_data, NULL, 1);
  if (rc != 0) {/* 出错直接退出 */
   return rc;
  }
 }
#endif /* TRUSTED_BOARD_BOOT */

 /* 镜像加载到内存,更新entry_point_info image_data */
 rc = load_image(mem_layout, image_id, image_base, image_data,
   entry_point_info);
 if (rc != 0) {
  return rc;
 }

#if TRUSTED_BOARD_BOOT
 /* 认证镜像 */
 rc = auth_mod_verify_img(image_id,
     (void *)image_data->image_base,
     image_data->image_size);
 if (rc != 0) {
  /* 镜像认证失败,清除加载的内容防止执行未认证的内容 */
  memset((void *)image_data->image_base, 0x00,
         image_data->image_size);
  flush_dcache_range(image_data->image_base,
       image_data->image_size);
  return -EAUTH;
 }
 /* 镜像加载认证成功,将镜像同步到主存储器,以便每个cpu都可以执行
  * 不需要同步父镜像,应为只有当前镜像需要执行
  */
 if (!is_parent_image) {
  flush_dcache_range(image_data->image_base,
       image_data->image_size);
 }
#endif /* TRUSTED_BOARD_BOOT */

 return 0;
}

8.镜像认证

1)镜像认证描述符

认证描述符为auth_img_desc_t,结构体。

typedef struct auth_img_desc_s {
 /* 镜像的ID,标志是哪一个镜像 */
 unsigned int img_id;

 /* 镜像类型(Binary、证书等) */
 img_type_t img_type;

 /* 父镜像,保存了认证当前镜像的公钥、哈希等 */
 const struct auth_img_desc_s *parent;

 /* 认证当前镜像的方法 */
 auth_method_desc_t img_auth_methods[AUTH_METHOD_NUM];

 /* 用于校验子镜像的公钥、哈希等 */
 auth_param_desc_t authenticated_data[COT_MAX_VERIFIED_PARAMS];
} auth_img_desc_t;

系统通过一个数组记录每个镜像的认证方式,并且通过镜像id获取对应镜像的认证方式

2)认证方法

auth_mod_verify_img根据镜像描述符描述的认证方式对镜像进行校验认证

int auth_mod_verify_img(unsigned int img_id,
   void *img_ptr,
   unsigned int img_len)
{
 const auth_img_desc_t *img_desc = NULL;
 const auth_method_desc_t *auth_method = NULL;
 void *param_ptr;
 unsigned int param_len;
 int rc, i;

 /* 根据img_id获取镜像描述符 */
 img_desc = &cot_desc_ptr[img_id];

 /* 校验镜像完整性 */
 rc = img_parser_check_integrity(img_desc->img_type, img_ptr, img_len);
 return_if_error(rc);

 /* 根据镜像描述符的仍正方式对镜像进行认证 */
 for (i = 0 ; i < AUTH_METHOD_NUM ; i++) {
  auth_method = &img_desc->img_auth_methods[i];
  switch (auth_method->type) {
  case AUTH_METHOD_NONE:/* 不需要认证 */
   rc = 0;
   break;
  case AUTH_METHOD_HASH:/* 哈希认证 */
   rc = auth_hash(&auth_method->param.hash,
     img_desc, img_ptr, img_len);
   break;
  case AUTH_METHOD_SIG:/* 签名认证 */
   rc = auth_signature(&auth_method->param.sig,
     img_desc, img_ptr, img_len);
   break;
  case AUTH_METHOD_NV_CTR:/* Non-Volatile counter认证? */
   rc = auth_nvctr(&auth_method->param.nv_ctr,
     img_desc, img_ptr, img_len);
   break;
  default:
   /* 未知认证类型,报错 */
   rc = 1;
   break;
  }
  return_if_error(rc);
 }

 /* 从镜像中解析出公钥哈希等,以便对下一级镜像进行认证 */
 for (i = 0 ; i < COT_MAX_VERIFIED_PARAMS ; i++) {
  if (img_desc->authenticated_data[i].type_desc == NULL) {
   continue;
  }

  /* 通过镜像解析器从镜像中提取内容 */
  rc = img_parser_get_auth_param(img_desc->img_type,
    img_desc->authenticated_data[i].type_desc,
    img_ptr, img_len, &param_ptr, &param_len);
  return_if_error(rc);

  /* 异常检查
     防止从镜像中解析出的数据字节数大于镜像描述符中的字节数
     出现内存访问溢出 */
  if (param_len > img_desc->authenticated_data[i].data.len) {
   return 1;
  }

  /* 把解析出的内容拷贝到镜像描述符中,便于解析下一级BL */
  memcpy((void *)img_desc->authenticated_data[i].data.ptr,
    (void *)param_ptr, param_len);
 }

 /* 标记镜像以认证过 */
 auth_img_flags[img_desc->img_id] |= IMG_FLAG_AUTHENTICATED;

 return 0;
}

9.执行镜像

1)执行环境

执行环境,用于初始化上下文。此结构一般在加载镜像时初始化,通过系统定义的或链接器导出的符号初始化。

/* 结构体的一个标记,用于校验和错误检测 */
typedef struct param_header {
 uint8_t type;  /* 结构体类型 */
 uint8_t version;    /* 版本信息 */
 uint16_t size;      /* 结构体大小 */
 uint32_t attr;      /* 附加属性,第0比特标记是否为secure */
} param_header_t;

/* 用于向镜像传递参数 */
typedef struct aapcs32_params {
 u_register_t arg0;
 u_register_t arg1;
 u_register_t arg2;
 u_register_t arg3;
} aapcs32_params_t;

/* 用于向镜像传递参数 */
typedef struct aapcs64_params {
 u_register_t arg0;
 u_register_t arg1;
 u_register_t arg2;
 u_register_t arg3;
 u_register_t arg4;
 u_register_t arg5;
 u_register_t arg6;
 u_register_t arg7;
} aapcs64_params_t;

/* 执行环境结构体 */
typedef struct entry_point_info {
 param_header_t h;
 uintptr_t pc;/* 镜像加载在内存中的位置 */
 uint32_t spsr;/* 程序状态字(决定异常等级) */
#ifdef AARCH32
 aapcs32_params_t args;/* 用于向镜像传递参数 */
#else
 aapcs64_params_t args;/* 用于向镜像传递参数 */
#endif
} entry_point_info_t;

2)通过异常退出执行下一级BL

这种方式下,上一级BL可以为下一级BL提供服务。主要通过两个函数实现

/* 初始化当前处理器的上下文 */
void cm_init_my_context(const entry_point_info_t *ep)
{
 cpu_context_t *ctx;
    /* 根据secure的状态获取上下文 */
 ctx = cm_get_context(GET_SECURITY_STATE(ep->h.attr));
    /* 根据执行环境初始化上下文 */
 cm_init_context_common(ctx, ep);
}

/* 为异常退出作准备 */
void cm_prepare_el3_exit(uint32_t security_state)
{
 uint32_t sctlr_elx, scr_el3, cptr_el2;
    
    /* 根据secure的状态获取上下文 */
 cpu_context_t *ctx = cm_get_context(security_state);

 assert(ctx);
 /* 设置一些控制寄存器,为异常退出作准备 */
 if (security_state == NON_SECURE) {
  scr_el3 = read_ctx_reg(get_el3state_ctx(ctx), CTX_SCR_EL3);
  if (scr_el3 & SCR_HCE_BIT) {
   /* Use SCTLR_EL1.EE value to initialise sctlr_el2 */
   sctlr_elx = read_ctx_reg(get_sysregs_ctx(ctx),
       CTX_SCTLR_EL1);
   sctlr_elx &= ~SCTLR_EE_BIT;
   sctlr_elx |= SCTLR_EL2_RES1;
   write_sctlr_el2(sctlr_elx);
  } else if (read_id_aa64pfr0_el1()
                   & (ID_AA64PFR0_ELX_MASK << ID_AA64PFR0_EL2_SHIFT)) {
   write_hcr_el2((scr_el3 & SCR_RW_BIT) ? HCR_RW_BIT : 0);
   cptr_el2 = read_cptr_el2();
   cptr_el2 &= ~(TCPAC_BIT | TTA_BIT | TFP_BIT);
   write_cptr_el2(cptr_el2);
   write_cnthctl_el2(EL1PCEN_BIT | EL1PCTEN_BIT);
   write_cntvoff_el2(0);
   write_vpidr_el2(read_midr_el1());
   write_vmpidr_el2(read_mpidr_el1());
   write_vttbr_el2(0);
   write_mdcr_el2((read_pmcr_el0() & PMCR_EL0_N_BITS)>> PMCR_EL0_N_SHIFT);
   write_hstr_el2(0);
   write_cnthp_ctl_el2(0);
  }
 }
 
    /* 从上下文恢复系统寄存器 */
 el1_sysregs_context_restore(get_sysregs_ctx(ctx));

   /* 切换使用SP_EL3,并指向上下文 */
 cm_set_next_context(ctx);
}

异常退出通过,汇编函数实现

func el3_exit
    /* 保存SP_EL0到上下文中 */
    mov    x17, sp
    msr    spsel, #1
    str    x17, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]

    /* 
     * 从上下文恢复SCR_EL3、SPSR_EL3、ELR_EL3
     * SCR_EL3  : security state有关
     * SPSR_EL3 : 异常等级相关
     * ELR_EL3  : 异常返回地址
     */
    ldr    x18, [sp, #CTX_EL3STATE_OFFSET + CTX_SCR_EL3]
    ldp    x16, x17, [sp, #CTX_EL3STATE_OFFSET + CTX_SPSR_EL3]
    msr    scr_el3, x18
    msr    spsr_el3, x16
    msr    elr_el3, x17

    /* 从上下文恢复通用寄存器 */
    b    restore_gp_registers_eret
endfunc el3_exit

3)通过SMC执行下一级BL

这是BL1给BL2提供的中断服务,用于加载程序到EL3

func smc_handler64

    /* 确定是BL1_SMC_RUN_IMAGE调用 */
    mov    x30, #BL1_SMC_RUN_IMAGE
    cmp    x30, x0
    b.ne    smc_handler/* 其他SMC调用 */

    /* 确定在secure world执行SMC调用 */
    mrs    x30, scr_el3
    tst    x30, #SCR_NS_BIT
    b.ne    unexpected_sync_exception/* Non-Secure调用BL1_SMC_RUN_IMAGE */
    
    /* 从上下文恢复C运行环境堆栈 */
    ldr    x30, [sp, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
    msr    spsel, #0
    mov    sp, x30

    /* X1执行执行环境 */
    mov    x20, x1
    mov    x0, x20
    bl    bl1_print_next_bl_ep_info/* 打印执行环境 */

    ldp    x0, x1, [x20, #ENTRY_POINT_INFO_PC_OFFSET]
    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 /* 关闭指令cache */
    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

此服务在BL2中使用

/*
 * smc用于触发一条SMC调用,参数对应X0-X7
 * X0标记服务
 * X1执行环境结构体指针
 */
smc(BL1_SMC_RUN_IMAGE, (unsigned long)next_bl_ep_info, 0, 0, 0, 0, 0, 0);

10.SMC服务框架

SMC调用通过W0标示当前调用的类型。W0根据如下方式标记调用类型

W0用于确定调用的功能
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  函数号

1)SMC数据结构

SMC服务通过结构体描述

typedef struct rt_svc_desc {
   /* start_oen-end_oen 对应W0 b29:24中的一个片段 */
 uint8_t start_oen;     /* 当前服务可以处理的SMC调用编号的起始编号 */
 uint8_t end_oen;       /* 当前服务可以处理的SMC调用编号的结束编号 */
 uint8_t call_type;     /* 调用的类型,对应W0 b30 */
 const char *name;      /* 名字 */
 rt_svc_init_t init;    /* 服务初始化函数 */
 rt_svc_handle_t handle;/* 服务句柄 */
} rt_svc_desc_t;

通过DECLARE_RT_SVC的宏声明一个SMC服务,服务被放在rt_svc_descs段中,并在链接脚本中指定rt_svc_descs的起始结束符号(__RT_SVC_DESCS_START__/__RT_SVC_DESCS_END__)

#define DECLARE_RT_SVC(_name, _start, _end, _type, _setup, _smch) \
 static const rt_svc_desc_t __svc_desc_ ## _name \
  __section("rt_svc_descs") __used = { \
   .start_oen = _start, \
   .end_oen = _end, \
   .call_type = _type, \
   .name = #_name, \
   .init = _setup, \
   .handle = _smch }

通过如上结构,构建了一个可扩展的SMC服务框架

2)SMC服务初始化构建索引

每个服务需要初始化(rt_svc_desc_t.init),而且链接后所有的服务形成一个数组被放在__RT_SVC_DESCS_START__和__RT_SVC_DESCS_END__符号之间,需要建立一个索引通过服务号找到对应的rt_svc_desc_t

void runtime_svc_init(void)
{
 int rc = 0, index, start_idx, end_idx;

 /* 异常检查 */
 assert((RT_SVC_DESCS_END >= RT_SVC_DESCS_START) &&
   (RT_SVC_DECS_NUM < MAX_RT_SVCS));

 /* 没有系统服务退出 */
 if (RT_SVC_DECS_NUM == 0)
  return;

 /* 初始化索引*/
 memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices));

    /* 遍历所有的SMC服务 */
    rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START;
 for (index = 0; index < RT_SVC_DECS_NUM; index++) {
  rt_svc_desc_t *service = &rt_svc_descs[index];

       /* 检查SMC服务是否合法 */
  rc = validate_rt_svc_desc(service);
  if (rc) {
   ERROR("Invalid runtime service descriptor %p\n",
    (void *) service);
   panic();
  }

  /*
   * 调用初始话函数
   */
  if (service->init) {
   rc = service->init();
   if (rc) {
    ERROR("Error initializing runtime service %s\n",
      service->name);
    continue;
   }
  }

  /*
   * 构建索引
   * 通过调用类型和服务类型查找服务在rt_svc_descs中的下标
   */
  start_idx = get_unique_oen(rt_svc_descs[index].start_oen,
    service->call_type);
  assert(start_idx < MAX_RT_SVCS);
  end_idx = get_unique_oen(rt_svc_descs[index].end_oen,
    service->call_type);
  assert(end_idx < MAX_RT_SVCS);
  for (; start_idx <= end_idx; start_idx++)
   rt_svc_descs_indices[start_idx] = index;
 }
}

3)SMC中断服务

通过初始化构建的索引找到对应的服务,执行rt_svc_desc_t.handle

 /* 同步异常处理只处理smc相关的调用 */
    .macro    handle_sync_exception
    /* 使能SError中断 */
    msr    daifclr, #DAIF_ABT_BIT

    /* 保存x30,下面要使用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_data */
    str    x30, [x29, #CPU_DATA_PMF_TS0_OFFSET]
    ldr    x29, [sp, #CTX_GPREGS_OFFSET + CTX_GPREG_X29]
#endif
    /* 获取异常代码 */
    mrs    x30, esr_el3
    ubfx    x30, x30, #ESR_EC_SHIFT, #ESR_EC_LENGTH

    cmp    x30, #EC_AARCH32_SMC
    b.eq    smc_handler32 /* 32bit SMC调用 */

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

    no_ret    report_unhandled_exception/* 不处理其他的同步异常 */
    .endm
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)

    /* 因为描述符在内存中布局是乱的通过rt_svc_descs_indices数组来查找描述符在内存中的位置
     * rt_svc_descs_indices为rt_svc_descs的索引
     * 获取数组下标 -> W15
     */
    adr    x14, rt_svc_descs_indices
    ldrb    w15, [x14, x16]

    /* 从上下文中提取C运行环境的栈指针 */
    ldr    x12, [x6, #CTX_EL3STATE_OFFSET + CTX_RUNTIME_SP]
    
    /* 索引的值不应该大于127 */
    tbnz    w15, 7, smc_unknown

    /* 切换到C的指针 */
    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/* 异常恢复 */

11.PSCI

1)初始化

在BL1阶段除主处理器外,其他处理器被暂停

/* bl1入口第一行 */
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=bl1_exceptions
        
/* el3_entrypoint_common宏中的片段 */
.if \_secondary_cold_boot
        bl    plat_is_my_cpu_primary /* 判断是否为主处理器 */
        cmp    r0, #0
        bne    do_primary_cold_boot

        /* 次处理器处理 */
        bl    plat_secondary_cold_boot_setup/* 此函数与具体平台实现有关 */
        /* 此代码不会执行,因为plat_secondary_cold_boot_setup不会返回 */
        no_ret    plat_panic_handler
    /* 主处理器继续执行 */
    do_primary_cold_boot:
.endif /* _secondary_cold_boot */

在BL31阶段,在SMC的一个服务中(Standard Service)中实现PSCI服务。

/* 服务初始化函数 */
static int32_t std_svc_setup(void)
{
 uintptr_t svc_arg;
 
    /* cpu被唤醒的入口函数 */
 svc_arg = get_arm_std_svc_args(PSCI_FID_MASK);
 assert(svc_arg);

 /*
  *PSCI作为标准服务实现,初始化PSCI服务
  */
 return psci_setup((const psci_lib_args_t *)svc_arg);
}

/* PSCI服务初始化的参数 */
uintptr_t get_arm_std_svc_args(unsigned int svc_mask)
{
 /* 构造PSCI服务初始化的参数,cpu被唤醒入口为bl31_warm_entrypoint */
 DEFINE_STATIC_PSCI_LIB_ARGS_V1(psci_args, bl31_warm_entrypoint);

 /* PSCI is the only ARM Standard Service implemented */
 assert(svc_mask == PSCI_FID_MASK);

 return (uintptr_t)&psci_args;
}

/* PSCI服务初始化 */
int psci_setup(const psci_lib_args_t *lib_args)
{
 const unsigned char *topology_tree;
 
   /* 校验参数是否有效 */
 assert(VERIFY_PSCI_LIB_ARGS_V1(lib_args));

 /* 架构相关初始化 */
 psci_arch_setup();

 /* 获取芯片拓扑图(第一个字节标示有几个簇,后面的字节标示各个簇有几个cup) */
 topology_tree = plat_get_power_domain_tree_desc();

 /* Populate the power domain arrays using the platform topology map */
 populate_power_domain_tree(topology_tree);

 /* Update the CPU limits for each node in psci_non_cpu_pd_nodes */
 psci_update_pwrlvl_limits();

 /* Populate the mpidr field of cpu node for this CPU */
 psci_cpu_pd_nodes[plat_my_core_pos()].mpidr =
  read_mpidr() & MPIDR_AFFINITY_MASK;

 psci_init_req_local_pwr_states();

 /*
  * Set the requested and target state of this CPU and all the higher
  * power domain levels for this CPU to run.
  */
 psci_set_pwr_domains_to_run(PLAT_MAX_PWR_LVL);
 
    /* 获取平台支持的电源管理操作句柄psci_plat_pm_ops
     * 并设置芯片被唤醒的入口lib_args->mailbox_ep,即bl31_warm_entrypoint
     */
 plat_setup_psci_ops((uintptr_t)lib_args->mailbox_ep, &psci_plat_pm_ops);
 assert(psci_plat_pm_ops);

 /*
  * Flush `psci_plat_pm_ops` as it will be accessed by secondary CPUs
  * during warm boot before data cache is enabled.
  */
 flush_dcache_range((uintptr_t)&psci_plat_pm_ops,
     sizeof(psci_plat_pm_ops));

 /* 标记可用的电源管理操作 */
 psci_caps = PSCI_GENERIC_CAP;

 if (psci_plat_pm_ops->pwr_domain_off)
  psci_caps |=  define_psci_cap(PSCI_CPU_OFF);
 if (psci_plat_pm_ops->pwr_domain_on &&
   psci_plat_pm_ops->pwr_domain_on_finish)
  psci_caps |=  define_psci_cap(PSCI_CPU_ON_AARCH64);
 if (psci_plat_pm_ops->pwr_domain_suspend &&
   psci_plat_pm_ops->pwr_domain_suspend_finish) {
  psci_caps |=  define_psci_cap(PSCI_CPU_SUSPEND_AARCH64);
  if (psci_plat_pm_ops->get_sys_suspend_power_state)
   psci_caps |=  define_psci_cap(PSCI_SYSTEM_SUSPEND_AARCH64);
 }
 if (psci_plat_pm_ops->system_off)
  psci_caps |=  define_psci_cap(PSCI_SYSTEM_OFF);
 if (psci_plat_pm_ops->system_reset)
  psci_caps |=  define_psci_cap(PSCI_SYSTEM_RESET);
 if (psci_plat_pm_ops->get_node_hw_state)
  psci_caps |= define_psci_cap(PSCI_NODE_HW_STATE_AARCH64);

#if ENABLE_PSCI_STAT
 psci_caps |=  define_psci_cap(PSCI_STAT_RESIDENCY_AARCH64);
 psci_caps |=  define_psci_cap(PSCI_STAT_COUNT_AARCH64);
#endif
 return 0;
}

2)服务

PSCI提供电源管理相关的服务

u_register_t psci_smc_handler(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)
{
   /* 只为Non-Secure服务 */
 if (is_caller_secure(flags))
  return SMC_UNK;

 /* 检测调用的服务是否存在 */
 if (!(psci_caps & define_psci_cap(smc_fid)))
  return SMC_UNK;

 if (((smc_fid >> FUNCID_CC_SHIFT) & FUNCID_CC_MASK) == SMC_32) {
  /* 32比特PSCI服务 */

  x1 = (uint32_t)x1;
  x2 = (uint32_t)x2;
  x3 = (uint32_t)x3;

  switch (smc_fid) {
  case PSCI_VERSION:
   return psci_version();

  case PSCI_CPU_OFF:
   return psci_cpu_off();

  case PSCI_CPU_SUSPEND_AARCH32:
   return psci_cpu_suspend(x1, x2, x3);

  case PSCI_CPU_ON_AARCH32:
   return psci_cpu_on(x1, x2, x3);

  case PSCI_AFFINITY_INFO_AARCH32:
   return psci_affinity_info(x1, x2);

  case PSCI_MIG_AARCH32:
   return psci_migrate(x1);

  case PSCI_MIG_INFO_TYPE:
   return psci_migrate_info_type();

  case PSCI_MIG_INFO_UP_CPU_AARCH32:
   return psci_migrate_info_up_cpu();

  case PSCI_NODE_HW_STATE_AARCH32:
   return psci_node_hw_state(x1, x2);

  case PSCI_SYSTEM_SUSPEND_AARCH32:
   return psci_system_suspend(x1, x2);

  case PSCI_SYSTEM_OFF:
   psci_system_off();
   /* We should never return from psci_system_off() */

  case PSCI_SYSTEM_RESET:
   psci_system_reset();
   /* We should never return from psci_system_reset() */

  case PSCI_FEATURES:
   return psci_features(x1);

#if ENABLE_PSCI_STAT
  case PSCI_STAT_RESIDENCY_AARCH32:
   return psci_stat_residency(x1, x2);

  case PSCI_STAT_COUNT_AARCH32:
   return psci_stat_count(x1, x2);
#endif

  default:
   break;
  }
 } else {
  /* 64位的PSCI服务 */

  switch (smc_fid) {
  case PSCI_CPU_SUSPEND_AARCH64:
   return psci_cpu_suspend(x1, x2, x3);

  case PSCI_CPU_ON_AARCH64:
   return psci_cpu_on(x1, x2, x3);

  case PSCI_AFFINITY_INFO_AARCH64:
   return psci_affinity_info(x1, x2);

  case PSCI_MIG_AARCH64:
   return psci_migrate(x1);

  case PSCI_MIG_INFO_UP_CPU_AARCH64:
   return psci_migrate_info_up_cpu();

  case PSCI_NODE_HW_STATE_AARCH64:
   return psci_node_hw_state(x1, x2);

  case PSCI_SYSTEM_SUSPEND_AARCH64:
   return psci_system_suspend(x1, x2);

#if ENABLE_PSCI_STAT
  case PSCI_STAT_RESIDENCY_AARCH64:
   return psci_stat_residency(x1, x2);

  case PSCI_STAT_COUNT_AARCH64:
   return psci_stat_count(x1, x2);
#endif

  default:
   break;
  }
 }
 /* 未定义的服务 */
 WARN("Unimplemented PSCI Call: 0x%x \n", smc_fid);
 return SMC_UNK;
}


END

作者:wxjstz
文章来源:TrustZone

推荐阅读

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