baron · 1 天前

ATF的代码学习篇-一篇就够了

文章目录
1、ATF里都有什么?
2、ATF的编译
3、ATF的启动
4、进入ATF的和退出ATF方式
(1)、进入ATF的方式
(2)、退出ATF的方式
5、ATF中向量表的介绍
6、ATF中栈的设置
7、ATF中寄存器的保存和恢复
8、ATF的rt_svc介绍(runtime service)
(1)、SMC Calling convention文档
(2)、DECLARE_RT_SVC的使用
(3)、DECLARE_RT_SVC的定义
(4)、在同步异常中smc_handler64,跳转到响应的rt_svc
(5)、smc在驱动中的调用
5、smc流程下的代码分析

1、ATF里都有什么?
最初的功能很简单:

cpu_context的保存和恢复,即: 双系统的切换

电源管理、PSCI等
但是随着技术的发展,功能也越来越多,越来越复杂,以下列举了当前的部分功能:

安全世界的初始化,例如异常向量表、一些控制寄存器和中断寄

CPU reset和power down的时序。包括Arm DynamIQ cpu的支持。

标准的system IP的驱动,例如Generic Interrupt Controller (GIC), Cache Coherent Interconnect (CCI), Cache Coherent Network (CCN), Network Interconnect (NIC) and TrustZone Controller (TZC).

一种通用的SCMI驱动程序, 适用于电源控制接口,例如ARM SYSTEM Control Processor(SCP)

smc处理,using an EL3 runtime services framework

PSCI库的支持,用于CPU/Cluster/system的电源管理,这个库集成到了aarch64 el3的runtime中,也适用于aarch32 el3

secure monitor代码,用于world切换、中断routing

SPDs for the OP-TEE Secure OS, NVIDIA Trusted Little Kernel and Trusty Secure OS

SecureBoot实现

预集成TBB与Arm CryptoCell产品,利用其硬件Root的信任和加密加速服务。
如需更详细,请参考《ATF里面都有什么?》 一文

2、ATF的编译
不同平台之间的设计肯定都是不一样的,但大多数类似如下,请注意 RESET_TO_BL31=1,表示该ATF从BL31启动。

make -C $DIRPATH RESET_TO_BL31=1 PLAT=xxx clean
make -C $DIRPATH RESET_TO_BL31=1 PLAT=xxx HIGHADDR_DEVICE=1 all
3、ATF的启动
废话不多说,直接上图,请自行理解:BL1 BL2 BL31 BL32 BL33的概念、EL3 S-EL1 NS-EL1的概念。image
4、进入ATF的和退出ATF方式
透过事务看本质, 进入和退出ATF,就是就是EL等级切换的过程,那么EL等级都是怎么切换的呢?通过下面一张图就可以说明这一切:
image
(事实上除了以上同步异常指令,如果是触发异步异常,也会trapped到ATF)

(1)、进入ATF的方式
进入ATF的方式触发异常:同步异常SMC、异步异常(irq,fiq)

➨ 如果是同步异常,那么一定是在linux或tee中发生了smc调用,此时进入跳转ATF中异常向量表中的同步异常程序smc_handler64或smc_handler32
.
在该程序中,解析smc id,来选择跳转到具体哪一个rt-svc(runtime service)

➨ 如果是异步异常,那么一定是触发了irq或fiq或serror中断等,此时进入跳转ATF中异常向量表中的异步异常程序,进而跳转到响应的中断处理函数.
.
在ATF中仅实现irq_aarch64、fiq_aarch64、irq_aarch32、fiq_aarch32 四个异常中断处理函数

vector_entry irq_aarch64
check_and_unmask_ea
handle_interrupt_exception irq_aarch64
end_vector_entry irq_aarch64
vector_entry fiq_aarch64
check_and_unmask_ea
handle_interrupt_exception fiq_aarch64
end_vector_entry fiq_aarch64
vector_entry irq_aarch32
check_and_unmask_ea
handle_interrupt_exception irq_aarch32
end_vector_entry irq_aarch32
vector_entry fiq_aarch32
check_and_unmask_ea
handle_interrupt_exception fiq_aarch32
end_vector_entry fiq_aarch32
在中断函数中,先调用plat_ic_get_pending_interrupt_type获取interrupt_type,其实就是通过读取寄存器read_icc_hppir0_el1() ,判断中断是从那里来的,然后返回下面三种interrupt_type:

define INTR_TYPE_S_EL1 U(0)

define INTR_TYPE_EL3 U(1)

define INTR_TYPE_NS U(2)

有了type,再get_interrupt_type_handler获取handler程序,进而跳转到相应的handler程序。

ATF中中断的注册(这三种类型的handler程序的注册),以INTR_TYPE_S_EL1为例:
在开机bl32_main调用opteed_setup()时,将opteed_sel1_interrupt_handler()函数注册成了INTR_TYPE_S_EL1类型中断,同时也会将REE(Linux)使用的SCR_EL3.FIQ配置成1,
也意味着当CPU运行在TEE时,来了一个secure group1的中断,此中断在REE中被标记FIQ后将被target到EL3,进入EL3(ATF)的中断处理函数,也就是刚才注册的opteed_sel1_interrupt_handler()函数,在该函数中,会将cpu切换到TEE中,去处理这个中断;

(2)、退出ATF的方式
进入EL3(ATF)的方式触发异常:ERET指令、或是主动修改PSTATE寄存器

在ATF中执行smc_handler或中断handler结束后,会调用el3_exit,el3_exit会调用ERET指令,恢复Secure或non-secure的PC指针和PSTATE,回到secure EL1或non-secure EL1.

下图是一个smc进入ATF,处理完任务后再返回EL1的过程:
image
5、ATF中向量表的介绍
请参考《Linux Kernel/optee/ATF等操作系统的异常向量表的速查》 一文

6、ATF中栈的设置
请参考《思想解读:TF-A(ATF)中栈指针和栈内存的设计思想解读》 一文

7、ATF中寄存器的保存和恢复
请参考《TF-A代码阅读: 双系统切换时是如何保存寄存器的(cpu_context介绍)》 一文

8、ATF的rt_svc介绍(runtime service)
image
(1)、SMC Calling convention文档
image

我们重点看下这张表,对应smc id的定义

bit31决定是fast call,还是std call(yield对应的就是std call)
bit30表示是以32位传参,还是以64位传参, 注意我们看了optee在linux的driver,都是以32位方式
bit29:24 决定服务的类型
bit23:16 reserved
bit15:0 每种call类型下,表示range
bit31、bit30、bit23:16、bit15:0 都是很好理解,我们来讲一下bit29:24
在ATF中定义rt_svc(runtime service)时,需按照该文档的描述来定义

例如在opteed_main.c中,定义了一个service,它的call类型是OEN_TOS_START–OEN_TOS_END,对应的恰好是bit29:24 = 50–63

DECLARE_RT_SVC(
opteed_fast,
OEN_TOS_START,
OEN_TOS_END,
SMC_TYPE_FAST,
opteed_setup,
opteed_smc_handler
);
那么我们在linux kernel中,调用smc时的smc id的bit29:24需要等于50,那么此次的smc调用才会调用到这个runtime service的handler程序
例如在arm_arch_svc_setup.c中,定义了一个service,它的call类型是OEN_ARM_START–OEN_ARM_END,对应的恰好是bit29:24 = 0–0

/ Register Standard Service Calls as runtime service /
DECLARE_RT_SVC(
arm_arch_svc,
OEN_ARM_START,
OEN_ARM_END,
SMC_TYPE_FAST,
NULL,
arm_arch_svc_smc_handler
);
那么我们在linux kernel中,调用smc时的smc id的bit29:24需要等于0,那么此次的smc调用才会调用到这个runtime service的handler程序
例如mtk代码中,mtk_sip_svc.c中,定义了一个service,它的call类型是OEN_SIP_START–OEN_SIP_END,对应的恰好是bit29:24 = 2–2

/ Define a runtime service descriptor for fast SMC calls /
DECLARE_RT_SVC(
mediatek_sip_svc,
OEN_SIP_START,
OEN_SIP_END,
SMC_TYPE_FAST,
NULL,
sip_smc_handler
);
那么我们在linux kernel中,调用smc时的smc id的bit29:24需要等于2,那么此次的smc调用才会调用到这个runtime service的handler程序
(2)、DECLARE_RT_SVC的使用
DECLARE_RT_SVC是一个宏,用于定义一组service,例如在opteed_main.c中的使用

/ Define an OPTEED runtime service descriptor for fast SMC calls /
DECLARE_RT_SVC(
opteed_fast,
OEN_TOS_START,
OEN_TOS_END,
SMC_TYPE_FAST,
opteed_setup,
opteed_smc_handler
);
/ Define an OPTEED runtime service descriptor for yielding SMC calls /
DECLARE_RT_SVC(
opteed_std,
OEN_TOS_START,
OEN_TOS_END,
SMC_TYPE_YIELD,
NULL,
opteed_smc_handler
);
其中OEN_TOS_START和OEN_TOS_END、SMC_TYPE_FAST和SMC_TYPE_YIELD都是按照SMC Calling convention文档来定义的

define OEN_ARM_START U(0)

define OEN_ARM_END U(0)

define OEN_CPU_START U(1)

define OEN_CPU_END U(1)

define OEN_SIP_START U(2)

define OEN_SIP_END U(2)

define OEN_OEM_START U(3)

define OEN_OEM_END U(3)

define OEN_STD_START U(4) / Standard Service Calls /

define OEN_STD_END U(4)

define OEN_STD_HYP_START U(5) / Standard Hypervisor Service calls /

define OEN_STD_HYP_END U(5)

define OEN_VEN_HYP_START U(6) / Vendor Hypervisor Service calls /

define OEN_VEN_HYP_END U(6)

define OEN_TAP_START U(48) / Trusted Applications /

define OEN_TAP_END U(49)

define OEN_TOS_START U(50) / Trusted OS /

define OEN_TOS_END U(63)

define OEN_LIMIT U(64)

define SMC_TYPE_FAST ULL(1)

define SMC_TYPE_YIELD ULL(0)

SMC_TYPE_FAST和SMC_TYPE_YIELD也是根据SMC Calling convention文档定义
(3)、DECLARE_RT_SVC的定义
在runtime_svc.h中,其实就是在section(“rt_svc_descs”)段中定义了一个全局变量.

/*
Convenience macros to declare a service descriptor
*/

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)                \
}

section “rt_svc_descs”在RT_SVC_DESCS宏中

define RT_SVC_DESCS \

. = ALIGN(STRUCT_ALIGN); \
RT_SVC_DESCS_START = .; \
KEEP(*(rt_svc_descs)) \
RT_SVC_DESCS_END = .;
而在rodata_common的宏中,定义了RT_SVC_DESCS

define RODATA_COMMON \

RT_SVC_DESCS \
FCONF_POPULATOR \
PMF_SVC_DESCS \
PARSER_LIB_DESCS \
CPU_OPS \
GOT \
BASE_XLAT_TABLE_RO
在bl31.ld.S中,将RODATA_COMMON放入了rodata段

.rodata . : {

__RODATA_START__ = .;
*(SORT_BY_ALIGNMENT(.rodata*))

RODATA_COMMON

/* Place pubsub sections for events */
. = ALIGN(8);

include <lib/el3_runtime/pubsub_events.h>

. = ALIGN(PAGE_SIZE);
__RODATA_END__ = .;

} >RAM
(4)、在同步异常中smc_handler64,跳转到响应的rt_svc
附上完整代码和注释

smc_handler64:
/ NOTE: The code below must preserve x0-x4 /
/*
Save general purpose and ARMv8.3-PAuth registers (if enabled).
If Secure Cycle Counter is not disabled in MDCR_EL3 when
ARMv8.5-PMU is implemented, save PMCR_EL0 and disable Cycle Counter.
*/
bl save_gp_pmcr_pauth_regs

if ENABLE_PAUTH

/ Load and program APIAKey firmware key /
bl pauth_load_bl31_apiakey

endif

/*

  • 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.
    */

mov x5, xzr
mov x6, sp
/*

  • 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]
/ Switch to SP_EL0 /
msr spsel, #MODE_SP_EL0
/*

  • 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
mov sp, x12
/ Get the unique owning entity number /
ubfx x16, x0, #FUNCID_OEN_SHIFT, #FUNCID_OEN_WIDTH ---------------- 获取FUNCID_OEN_SHIFT,对应第一节中的OEN_TOS_START
ubfx x15, x0, #FUNCID_TYPE_SHIFT, #FUNCID_TYPE_WIDTH ---------------- 获取FUNCID_TYPE_SHIFT,对应第一节中的SMC_TYPE_FAST(fast还是yield,yield其实就是standard)
orr x16, x16, x15, lsl #FUNCID_OEN_WIDTH
/ Load descriptor index from array of indices /
adrp x14, rt_svc_descs_indices ----在runtime_svc_init()中会将所有的section rt_svc_descs段放入rt_svc_descs_indices数组,这里获取该数组地址
add x14, x14, :lo12:rt_svc_descs_indices
ldrb w15, [x14, x16] ---找到rt_svc在rt_svc_descs_indices数组中的index
/ Any index greater than 127 is invalid. Check bit 7. /
tbnz w15, 7, smc_unknown
/*

  • Get the descriptor using the index
  • x11 = (base + off), w15 = index -------------------------重要的注释
    *
  • handler = (base + off) + (index << log2(size)) ------ 这句注释特别重要,整段汇编看不懂没关系,这句注释看懂就行
    */

adr x11, (__RT_SVC_DESCS_START__ + RT_SVC_DESC_HANDLE)
lsl w10, w15, #RT_SVC_SIZE_LOG2
ldr x15, [x11, w10, uxtw] ------------------------------这句话对应的就是上述注释:handler = (base + off) + (index << log2(size))
/*

  • 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

endif

blr x15 -------------------------------------跳转到handler
b el3_exit
(5)、smc在驱动中的调用
在optee_smc.h中,我们可以查看linux kernel中给driver定义的smc的类型有:

首先是两个宏,一个用于定义fast call,一个用于定义std call

define OPTEE_SMC_STD_CALL_VAL(func_num) \

ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, ARM_SMCCC_SMC_32, \

    ARM_SMCCC_OWNER_TRUSTED_OS, (func_num))

define OPTEE_SMC_FAST_CALL_VAL(func_num) \

ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, \

    ARM_SMCCC_OWNER_TRUSTED_OS, (func_num))

std call只有两个cmd,一个用于正向调用,一个用于rpc调用

define OPTEE_SMC_FUNCID_RETURN_FROM_RPC 3

define OPTEE_SMC_CALL_RETURN_FROM_RPC \

OPTEE_SMC_STD_CALL_VAL(OPTEE_SMC_FUNCID_RETURN_FROM_RPC)

define OPTEE_SMC_FUNCID_CALL_WITH_ARG OPTEE_MSG_FUNCID_CALL_WITH_ARG

define OPTEE_SMC_CALL_WITH_ARG \

OPTEE_SMC_STD_CALL_VAL(OPTEE_SMC_FUNCID_CALL_WITH_ARG)
fast call有5个分别用于: get_os_uuid、get_shm_config、exchange_capabilities、disable_shm_cache、enable_shm_cache

define OPTEE_SMC_FUNCID_GET_OS_UUID OPTEE_MSG_FUNCID_GET_OS_UUID

define OPTEE_SMC_CALL_GET_OS_UUID \

OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_OS_UUID)

define OPTEE_SMC_FUNCID_GET_SHM_CONFIG 7

define OPTEE_SMC_GET_SHM_CONFIG \

OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_GET_SHM_CONFIG)

define OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES 9

define OPTEE_SMC_EXCHANGE_CAPABILITIES \

OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_EXCHANGE_CAPABILITIES)

define OPTEE_SMC_FUNCID_DISABLE_SHM_CACHE 10

define OPTEE_SMC_DISABLE_SHM_CACHE \

OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_DISABLE_SHM_CACHE)

define OPTEE_SMC_FUNCID_ENABLE_SHM_CACHE 11

define OPTEE_SMC_ENABLE_SHM_CACHE \

OPTEE_SMC_FAST_CALL_VAL(OPTEE_SMC_FUNCID_ENABLE_SHM_CACHE)
5、smc流程下的代码分析
image
添加威♥:sami01_2023,回复ARM中文,领取ARM中文手册

推荐阅读
关注数
9491
文章数
256
vx: coding_the_world
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息