How Does Arm Trusted Firmware Load OPTEE OS?
整理 OPTEE OS 被 ATF 加载的过程.
- Overview
- BL2 (
trusted-firmware-a/bl2/bl2_main.c
) - Scripts in OPTEE OS (
optee_os/scripts/gen_tee_bin.py
) - Conclusion
- References
Overview
ATF 将系统启动从最底层进行了完整的统一划分. ATF Cold Boot 共分为 5 个步骤. 执行顺序如下 (ARM Trusted Firmware Design):
- Boot Loader stage 1 (BL1) AP Trusted ROM
- Boot Loader stage 2 (BL2) Trusted Boot Firmware
- Boot Loader stage 3-1 (BL31) EL3 Runtime Firmware
- Boot Loader stage 3-2 (BL32) Secure-EL1 Payload (optional)
- Boot Loader stage 3-3 (BL33) Non-trusted Firmware
每个阶段对应的二进制程序如下:
BL1 --- ./trusted-firmware-a/build/qemu/debug/bl1/bl1.elf
BL2 --- ./trusted-firmware-a/build/qemu/debug/bl2/bl2.elf
BL31 --- ./trusted-firmware-a/build/qemu/debug/bl31/bl31.elf
BL32 (TOS) --- ./optee_os/out/arm/core/tee.elf
BL33 (UEFI) --- ./edk2/Build/
BL2 (trusted-firmware-a/bl2/bl2_main.c
)
根据启动顺序, 猜测 TOS 的镜像应该是在 BL31 阶段中加载的. 但事实并非如此.
实际上, 是在 BL2 中调用了 bl2_load_images
函数对 BL2 和 BL3X 对应的镜像加载到内存中:
/*******************************************************************************
* This function loads SCP_BL2/BL3x images and returns the ep_info for
* the next executable image.
******************************************************************************/
struct entry_point_info *bl2_load_images(void)
{
bl_params_t *bl2_to_next_bl_params;
bl_load_info_t *bl2_load_info;
const bl_load_info_node_t *bl2_node_info;
int plat_setup_done = 0;
int err;
/*
* Get information about the images to load.
*/
bl2_load_info = plat_get_bl_image_load_info();
assert(bl2_load_info != NULL);
assert(bl2_load_info->head != NULL);
assert(bl2_load_info->h.type == PARAM_BL_LOAD_INFO);
assert(bl2_load_info->h.version >= VERSION_2);
bl2_node_info = bl2_load_info->head;
while (bl2_node_info != NULL)
{
/*
* Perform platform setup before loading the image,
* if indicated in the image attributes AND if NOT
* already done before.
*/
if ((bl2_node_info->image_info->h.attr &
IMAGE_ATTRIB_PLAT_SETUP) != 0U)
{
if (plat_setup_done != 0)
{
WARN("BL2: Platform setup already done!!\n");
}
else
{
INFO("BL2: Doing platform setup\n");
bl2_platform_setup();
plat_setup_done = 1;
}
}
err = bl2_plat_handle_pre_image_load(bl2_node_info->image_id); // do nothing
if (err != 0)
{
ERROR("BL2: Failure in pre image load handling (%i)\n", err);
plat_error_handler(err);
}
if ((bl2_node_info->image_info->h.attr &
IMAGE_ATTRIB_SKIP_LOADING) == 0U)
{
INFO("BL2: Loading image id %d\n", bl2_node_info->image_id);
err = load_auth_image(bl2_node_info->image_id,
bl2_node_info->image_info);
if (err != 0)
{
ERROR("BL2: Failed to load image id %d (%i)\n",
bl2_node_info->image_id, err);
plat_error_handler(err);
}
}
else
{
INFO("BL2: Skip loading image id %d\n", bl2_node_info->image_id);
}
/* Allow platform to handle image information. */
err = bl2_plat_handle_post_image_load(bl2_node_info->image_id);
if (err != 0)
{
ERROR("BL2: Failure in post image load handling (%i)\n", err);
plat_error_handler(err);
}
/* Go to next image */
bl2_node_info = bl2_node_info->next_load_info;
}
/*
* Get information to pass to the next image.
*/
bl2_to_next_bl_params = plat_get_next_bl_params();
assert(bl2_to_next_bl_params != NULL);
assert(bl2_to_next_bl_params->head != NULL);
assert(bl2_to_next_bl_params->h.type == PARAM_BL_PARAMS);
assert(bl2_to_next_bl_params->h.version >= VERSION_2);
assert(bl2_to_next_bl_params->head->ep_info != NULL);
/* Populate arg0 for the next BL image if not already provided */
if (bl2_to_next_bl_params->head->ep_info->args.arg0 == (u_register_t)0)
bl2_to_next_bl_params->head->ep_info->args.arg0 =
(u_register_t)bl2_to_next_bl_params;
/* Flush the parameters to be passed to next image */
plat_flush_next_bl_params();
return bl2_to_next_bl_params->head->ep_info;
}
/*******************************************************************************
* Internal function to load an image at a specific address given
* an image ID and extents of free memory.
*
* If the load is successful then the image information is updated.
*
* Returns 0 on success, a negative error code otherwise.
******************************************************************************/
static int load_image(unsigned int image_id, image_info_t *image_data)
{
uintptr_t dev_handle;
uintptr_t image_handle;
uintptr_t image_spec;
uintptr_t image_base;
size_t image_size;
size_t bytes_read;
int io_result;
assert(image_data != NULL);
assert(image_data->h.version >= VERSION_2);
image_base = image_data->image_base;
/* Obtain a reference to the image by querying the platform layer */
io_result = plat_get_image_source(image_id, &dev_handle, &image_spec);
if (io_result != 0) {
WARN("Failed to obtain reference to image id=%u (%i)\n",
image_id, io_result);
return io_result;
}
/* Attempt to access the image */
io_result = io_open(dev_handle, image_spec, &image_handle);
if (io_result != 0) {
WARN("Failed to access image id=%u (%i)\n",
image_id, io_result);
return io_result;
}
INFO("Loading image id=%u at address 0x%lx\n", image_id, image_base);
/* Find the size of the image */
io_result = io_size(image_handle, &image_size);
if ((io_result != 0) || (image_size == 0U)) {
WARN("Failed to determine the size of the image id=%u (%i)\n",
image_id, io_result);
goto exit;
}
/* Check that the image size to load is within limit */
if (image_size > image_data->image_max_size) {
WARN("Image id=%u size out of bounds\n", image_id);
io_result = -EFBIG;
goto exit;
}
/*
* image_data->image_max_size is a uint32_t so image_size will always
* fit in image_data->image_size.
*/
image_data->image_size = (uint32_t)image_size;
/* We have enough space so load the image now */
/* TODO: Consider whether to try to recover/retry a partially successful read */
io_result = io_read(image_handle, image_base, image_size, &bytes_read);
if ((io_result != 0) || (bytes_read < image_size)) {
WARN("Failed to load image id=%u (%i)\n", image_id, io_result);
goto exit;
}
INFO("Image id=%u loaded: 0x%lx - 0x%lx\n", image_id, image_base,
(uintptr_t)(image_base + image_size));
exit:
(void)io_close(image_handle);
/* Ignore improbable/unrecoverable error in 'close' */
/* TODO: Consider maintaining open device connection from this bootloader stage */
(void)io_dev_close(dev_handle);
/* Ignore improbable/unrecoverable error in 'dev_close' */
return io_result;
}
具体加载的镜像在各个子模块中对应的软链接:
$ ls -l out/bin/*
lrwxrwxrwx bea1e bea1e 82 B Sun Aug 20 17:41:31 2023 out/bin/bl1.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../trusted-firmware-a/build/qemu/debug/bl1.bin
lrwxrwxrwx bea1e bea1e 82 B Sun Aug 20 17:41:31 2023 out/bin/bl2.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../trusted-firmware-a/build/qemu/debug/bl2.bin
lrwxrwxrwx bea1e bea1e 83 B Sun Aug 20 17:41:31 2023 out/bin/bl31.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../trusted-firmware-a/build/qemu/debug/bl31.bin
lrwxrwxrwx bea1e bea1e 78 B Sun Aug 20 17:41:31 2023 out/bin/bl32.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../optee_os/out/arm/core/tee-header_v2.bin
lrwxrwxrwx bea1e bea1e 77 B Sun Aug 20 17:41:31 2023 out/bin/bl32_extra1.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../optee_os/out/arm/core/tee-pager_v2.bin
lrwxrwxrwx bea1e bea1e 80 B Sun Aug 20 17:41:31 2023 out/bin/bl32_extra2.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../optee_os/out/arm/core/tee-pageable_v2.bin
lrwxrwxrwx bea1e bea1e 101 B Sun Aug 20 17:41:31 2023 out/bin/bl33.bin ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../edk2/Build/ArmVirtQemuKernel-AARCH64/DEBUG_GCC5/FV/QEMU_EFI.fd
lrwxrwxrwx bea1e bea1e 66 B Sun Aug 20 17:41:25 2023 out/bin/Image ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../linux/arch/arm64/boot/Image
lrwxrwxrwx bea1e bea1e 67 B Mon Aug 21 10:12:39 2023 out/bin/rootfs.cpio.gz ⇒ /home/bea1e/TEE/optee_qemu_v8/build/../out-br/images/rootfs.cpio.gz
Scripts in OPTEE OS (optee_os/scripts/gen_tee_bin.py
)
根据软链接信息, 可以发现 tee.elf
被划分为了 tee-header_v2.bin
, tee-pager_v2.bin
和 tee-pageable_v2.bin
.
根据 OPTEE OS 的 link 文件 (optee_os/core/arch/arm/kernel/link.mk
), 在编译生成 tee.elf
后会执行 gen_tee_bin.py
来生成相应的 bin 文件:
def output_header_v2(elffile, outf):
arch_id = get_arch_id(elffile)
init_load_addr = get_init_load_addr(elffile)
init_bin_size = get_symbol(elffile, '__init_size')['st_value']
pager_bin_size = len(get_pager_bin(elffile))
paged_area_size = len(get_pageable_bin(elffile))
embdata_bin_size = len(get_embdata_bin(elffile))
init_size = (pager_bin_size + min(init_bin_size, paged_area_size) +
embdata_bin_size)
paged_size = paged_area_size - min(init_bin_size, paged_area_size)
magic = 0x4554504f # 'OPTE'
version = 2
flags = 0
nb_images = 1 if paged_size == 0 else 2
outf.write(struct.pack('<IBBHI', magic, version, arch_id, flags,
nb_images))
outf.write(struct.pack('<IIII', init_load_addr[0], init_load_addr[1],
0, init_size))
if nb_images == 2:
outf.write(struct.pack('<IIII', 0xffffffff, 0xffffffff, 1, paged_size))
def output_pager_v2(elffile, outf):
init_bin_size = get_symbol(elffile, '__init_size')['st_value']
pager_bin = get_pager_bin(elffile)
pageable_bin = get_pageable_bin(elffile)
embdata_bin = get_embdata_bin(elffile)
outf.write(pager_bin)
outf.write(pageable_bin[:init_bin_size])
outf.write(embdata_bin)
def output_pageable_v2(elffile, outf):
init_bin_size = get_symbol(elffile, '__init_size')['st_value']
outf.write(get_pageable_bin(elffile)[init_bin_size:])
Conclusion
OPTEE 在编译时将 tee.elf
划分为 tee-header_v2.bin
, tee-pager_v2.bin
和 tee-pageable_v2.bin
, 并分别生成软链接 bl32.bin
, bl32_extra1.bin
和 bl32_extra2.bin
.
在 ATF 的 BL2 阶段, 将相应的 bin 文件加载到内存中, 即将 TOS 代码加载到内存中. 到 BL31 阶段获取到相应的 TOS 入口地址, BL32 阶段直接执行 TOS 代码.
References
optee学习篇(1) 环境&调试
3. ATF(ARM Trusted firmware)启动—bl2
聊聊SOC启动(四) ATF BL31启动流程