ARM64架构启动流程
Linux和android在烧写kernel前需要烧写以下文件:
Bootparam_sa0*: 需要传递给linux kernel 的param
究竟是如何完成boot param 的传递呢:
都知道linux kernel C语言的入口函数是start_kernel()
在start_kernel()函数里面会有setup_arch(&command_line) ,
之后调用setup_processor(), setup_machine_fdt(__atags_pointer), setup_machine_tags(machine_arch_type).
这些调用流程如果有Jtag 的话,可以清晰的看到调用流程,同时需要注意的是linux kernel 编译的时候会进行GNU 编译优化, 如果一个函数是static 类型,而且只被调用过一次,那么这个函数就会被优化掉,不会产生栈帧,类似有内联函数的处理。
接着说setup_machine_tags()
这个函数中会有如下代码:
if(__atags_pointer)
tags = phys_to_virt(__atags_poniter);
这里解释一下,__atags_pointer 是在head-common.S 中进行的赋值操作。
也就是说在start_kernel 打上断点的话就能看到__atags_pointer 的值,在OMAP4430开发板上就是0x80000100,这是物理地址。
因此,需要使用phy_to_virt() 函数把物理地址转换成虚拟地址。转换后一般就是0xc0000100,这样在这个地址上就能看到boot param 了!
Bl2*:BL2 image将会为后续image的加载执行相关的初始化操作。主要是内存,MMU,串口以及EL3软件运行环境的设置,并且加载bl3x的image到RAM中
1:通过查看bl2.ld.S文件就可以发现,bl2 image的入口函数是bl2_entrypoint。该函数定义在bl2/aarch64/bl2_entrypoint.S文件中。
bl2_entrypoint:该函数最终会出发smc操作,从bl1中将CPU的控制权转交给bl31
2:bl2_main
该函数主要实现将bl3x的image加载RAM中,并通过smc调用执行bl1中指定的smc handle将CPU的全向交给bl31。
3: bl2_load_images
该函数用来加载bl3x的image到RAM中,返回一个具有image入口信息的变量。smc handle根据该变量跳转到bl31进行执行
4: REGISTER_BL_IMAGE_DESCS(bl2_mem_params_descs)
该宏的执行将会初始化组成bl2加载bl3x image的列表使用到的重要全局变量, 其中bl2_mem_params_descs变量的定义如下:
static bl_mem_params_node_t bl2_mem_params_descs[] = {
#ifdef SCP_BL2_BASE
/* Fill SCP_BL2 related information if it exists */
{
.image_id = SCP_BL2_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_IMAGE_BINARY,
VERSION_2, entry_point_info_t, SECURE | NON_EXECUTABLE),
SET_STATIC_PARAM_HEAD(image_info, PARAM_IMAGE_BINARY,
VERSION_2, image_info_t, 0),
.image_info.image_base = SCP_BL2_BASE,
.image_info.image_max_size = PLAT_CSS_MAX_SCP_BL2_SIZE,
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#endif /* SCP_BL2_BASE */
#ifdef EL3_PAYLOAD_BASE
/* Fill EL3 payload related information (BL31 is EL3 payload)*/
{
.image_id = BL31_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t,
SECURE | EXECUTABLE | EP_FIRST_EXE),
.ep_info.pc = EL3_PAYLOAD_BASE,
.ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS),
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t,
IMAGE_ATTRIB_PLAT_SETUP | IMAGE_ATTRIB_SKIP_LOADING),
.next_handoff_image_id = INVALID_IMAGE_ID,
},
#else /* EL3_PAYLOAD_BASE */
/* Fill BL31 related information */
{
.image_id = BL31_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t,
SECURE | EXECUTABLE | EP_FIRST_EXE),
.ep_info.pc = BL31_BASE,
.ep_info.spsr = SPSR_64(MODE_EL3, MODE_SP_ELX,
DISABLE_ALL_EXCEPTIONS),
#if DEBUG
.ep_info.args.arg1 = ARM_BL31_PLAT_PARAM_VAL,
#endif
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, IMAGE_ATTRIB_PLAT_SETUP),
.image_info.image_base = BL31_BASE,
.image_info.image_max_size = BL31_LIMIT - BL31_BASE,
# ifdef BL32_BASE
.next_handoff_image_id = BL32_IMAGE_ID,
# else
.next_handoff_image_id = BL33_IMAGE_ID,
# endif
},
# ifdef BL32_BASE
/* Fill BL32 related information */
{
.image_id = BL32_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t, SECURE | EXECUTABLE),
.ep_info.pc = BL32_BASE,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, 0),
.image_info.image_base = BL32_BASE,
.image_info.image_max_size = BL32_LIMIT - BL32_BASE,
.next_handoff_image_id = BL33_IMAGE_ID,
},
# endif /* BL32_BASE */
/* Fill BL33 related information */
{
.image_id = BL33_IMAGE_ID,
SET_STATIC_PARAM_HEAD(ep_info, PARAM_EP,
VERSION_2, entry_point_info_t, NON_SECURE | EXECUTABLE),
# ifdef PRELOADED_BL33_BASE
.ep_info.pc = PRELOADED_BL33_BASE,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, IMAGE_ATTRIB_SKIP_LOADING),
# else
.ep_info.pc = PLAT_ARM_NS_IMAGE_OFFSET,
SET_STATIC_PARAM_HEAD(image_info, PARAM_EP,
VERSION_2, image_info_t, 0),
.image_info.image_base = PLAT_ARM_NS_IMAGE_OFFSET,
.image_info.image_max_size = ARM_DRAM1_SIZE,
# endif /* PRELOADED_BL33_BASE */
.next_handoff_image_id = INVALID_IMAGE_ID,
}
#endif /* EL3_PAYLOAD_BASE */
};
在该变量中规定了SCP_BL2, EL3_payload, bl32, bl33 image的相关信息,例如:
image的入口地址信息:ep_info
image在RAM中的基地址:image_base
image的基本信息:image_info
image的ID值:image_id
3:cert_header:加载认证程序
bl31*:arm trusted firmware
在bl2中通过调用smc指令后会跳转到bl31中进行执行,bl31最终主要的作用是建立EL3 runtime software,在该阶段会建立各种类型的smc调用注册并完成对应的cortex状态切换。该阶段主要执行在monitor中。
1:bl31_entrypoint
通过bl31.ld.S文件可知, bl31的入口函数是:bl31_entrypoint函数
执行完bl31_entrypoint函数后,将跳转到bl33中执行,即执行bootloader
2:bl31_main
该函数主要完成必要初始化操作,配置EL3中的各种smc操作,以便在后续顺利响应在CA和TA中产生的smc操作
3: runtime_svc_init
该函数主要用来建立smc索引表并执行EL3中提供的service的初始化操作
4:DECLARE_RT_SVC
该宏用来在编译的时候将EL3中的service编译进rt_svc_descs段中,该宏定义如下:
#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 }
start_oen:该service的起始内部number
end.oen: 该service的末尾number
call_type: 调用的smc的类型
name: 该service的名字
init: 该service在执行之前需要被执行的初始化操作
handle: 当触发了call type的调用时调用的handle该请求的函数
5: 以OP-TEE为例从bl31跳转到OP-TEE
实现从bl31到OP-TEE的跳转是通过执行opteed_setup函数来实现的,该函数在执行runtime_svc_int中对各service做service->init()函数来实现,而OPTEE这个service就是通过DECALARE_RT_SVC被注册到tr_svc_descs段中,代码存在service/spd/opteed/opteed_main.c文件中,内容如下:
Tee*:
驱动与secure world之间的数据交互是通过共享内存来完成的,在OP-TEE启动的时候会将作为共享内存的物理内存块reserve出来,具体的可以查看OP-TEE启动代码中的core_init_mmu_map函数。在OP-TEE驱动初始化阶段会将reserve出来作为共享内存的物理内存配置成驱动的内存池,并且通知OP-TEE OS执行相同的动作。配置完成之后,secure world就能从共享内存中获取到来自于REE端的数据。
1:配置驱动与OP-TEE之间的共享内存
在驱动做probe操作时,会调用到optee_config_shm_memremap函数来完成OP-TEE驱动和OP-TEE之间共享内存的配置。该函数定义在linux/drivers/tee/optee/core.c文件中。
在secure world中reserve出来的内存块作为驱动与sercure world之间的共享内存使用。在驱动端将会建立一个内存池,以便驱动在需要的使用通过调用驱动的alloc函数来完成共享内存的分配。而共享内存池的建立是通过调用tee_shm_pool_alloc_res_mem来实现的。
2:分配和设置OP-TEE驱动作为被libteec和tee_supplicant使用的设备信息结构体
在OP-TEE驱动做probe操作时会分配和设置两个tee_device结构体变量,分别用来表示被libteec和tee_supplicant使用的设备。分别通过调用tee_device_alloc(&optee_desc, NULL, pool, optee)和tee_device_alloc(&optee_supp_desc, NULL, pool, optee)来实现,主要是设置驱动作为被libteec和tee_supplicant使用的设备的具体操作和表示该设备对应的名称等信息,
当libteec调用文件操作函数执行打开,关闭等操作/dev/tee0设备文件的时,其最终将调用到optee_desc中具体的函数来实现对应操作。
当tee_supplicant调用文件操作函数执行打开,关闭等操作/dev/teepriv0设备文件的时,其最终将调用到optee_supp_desc中具体的函数来实现对应操作。
上述配置操作都是通过调用tee_device_all函数来实现的
3:设备注册
完成版本检查,共享内存池配置,不同设备的配置之后就需要将这些配置好的设备注册到系统中去。对于被liteec和tee_supplicant使用的设备分别通过调用tee_device_register(optee->teedev)和tee_device_register(optee->supp_teedev)来实现。其中optee->teedev和optee->supp_teedev就是在上一章中被配置好的分别被libteec和tee_supplicant使用的设备结构体。调用tee_device_register函数来实现将设备注册到系统的目的,
4:两个队列的建立
在OP-TEE驱动提供两个设备,分别被libteec使用的/dev/tee0和被tee_supplicant使用的/dev/teepriv0。为确保normal world与secure world之间数据交互便利和能在normal world端进行异步处理。OP-TEE驱动在挂载的时候会建立两个类似于消息队列的队列,用于保存normal world的请求数据以及secure world请求。optee_wait_queue_init用于初始化/dev/tee0设备使用的队列,optee_supp_init用于初始化/dev/teepriv0设备使用的队列
5:通知op-tee是能以下共享内存的cache
当一切做完之后,最终就剩下通知OP-TEE使能共享内存的cache了,在驱动挂载过程中调用optee_enable_shm_cache函数来实现
6:fast smc与std smc
在OP-TEE驱动的挂载过程中会使用fast smc的方式从OP-TEE中获取到相关数据,例如从secure world中获取reserve的共享内存信息时就是通过调用如下函数来实现的:
invoke_fn(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
在支持smc操作的32位系统中该函数等价于
arm_smccc_smc(OPTEE_SMC_GET_SHM_CONFIG, 0, 0, 0, 0, 0, 0, 0, &res.smccc);
而OPTEE_SMC_ENABLE_SHM_CACHE的定义如下:
#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)
完全展开之后OPTEE_SMC_GET_SHM_CONFIG 宏的值的各个bit中的数值如下如所示:
在执行smc操作时,cortex会解读第一个参数中的相关位来决定进入到monitor模式后的相关操作,在ARM官方定义了第一个参数(a0)的定义如下
当bit31为1时,则表示进入monitor模式后会执行fast smc的中断处理函数,而在不带ATF的ARCH32中,monitor的中断向量表如下:
FUNC thread_vector_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b vector_std_smc_entry
b vector_fast_smc_entry
b vector_cpu_on_entry
b vector_cpu_off_entry
b vector_cpu_resume_entry
b vector_cpu_suspend_entry
b vector_fiq_entry
b vector_system_off_entry
b vector_system_reset_entry
UNWIND( .fnend)
END_FUNC thread_vector_table
由此可以见,驱动在挂载的时候请求共享内存配置数据的请求将会被vector_fast_smc_entry处理。而当请求来自于CA的请求时,驱动会将第一个参数的bi31设置成0,也就是CA的请求会被vector_std_smc_entry进行处理
总结:
OP-TEE的驱动的挂载过程来看,OP-TEE驱动会分别针对libteec和tee_supplicant建立不同的设备/dev/tee0和/dev/teepriv0。并且为两个设备中的des执行各自独有的operation,并建立类似消息队列来存放normal world与secure world之间的请求,这样libteec和tee_supplicant使用OP-TEE驱动的时候就能做到相对的独立。secure world与OP-TEE驱动之间使用共享内存进行数据交互。用于作为共享内存的物理内存块在OP-TEE启动的时做MMU初始化时需要被reserve出来,在OP-TEE挂载过程中需要将该内存块映射到系统内存中。
U-boot:目标功能是从flash中读出内核,放到内存中,启动内核
1:第一阶段
硬件设备初始化;
为加载 Bootloader 的第二阶段代码准备 RAM 空间;
复制 Bootloader 的第二阶段代码到 RAM 空间中;
设置好栈;
跳转到第二阶段代码的 C 入口点(start_armboot);
备注:在第一阶段进行的硬件初始化一般包括:关闭 WATCHDOG、关中断、设置 CPU的速度和时钟频率、 RAM 初始化等。这些并不都是必须的,比如
S3C2410/S3C2440的开发板所使用的 U-Boot 中,就将 CPU的速度和时钟频率放在第二阶段进行设置。
2:第二阶段
初始化本阶段要使用到的硬件设备;
检测系统内存映射( Memory map );
将内核映像和根文件系统映像从 Flash上读到 RAM 空间中;
为内核设置启动参数;
调用内核;
备注:为了方便开发,初始化一个串口以便程序员与 Bootloader 进行交互。
common/cmd_nand.c文件提供了操作NAND Flash的各种命令,这些命令调用drivers/nand/nand_base.c中的擦除、读写函数来实现;而这些函数是针对NAND Flash的共性做的封装,与平台/开发板相关的代码用宏或外部函数代替;平台相关则在cpu/xxx,开发板相关则在board/xxx。
以增加yaffs文件系统映像功能为例,先在common下的cmd_nand.c增加命令,比如nand
write.yaffs,这个命令调用/drivers/nand/nand_util.c中的函数,而这些函数依赖于drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中的相关函数
所谓检测内存映射,就是确定板上使用了多少内存、他们的地址空间是什么。由于嵌入式开发中的 Bootloader 多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。
Flash上的内核映像有可能是经过压缩的,在读到 RAM 之后,还需要进行解压。当然,对于有自解压功能的内核,不需要 Bootloader 来解压。
将根文件系统映像复制到RAM中并不是必须的,这取决于是什么类型的根文件系统,以及内核访问它的方法。
将内核放在适当的位置后,在跳入执行内核之前,需要根据内核启动的需求,配置相应的启动参数。