整理 OPTEE OS 被 ATF 加载的过程.

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.bintee-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.bintee-pageable_v2.bin, 并分别生成软链接 bl32.bin, bl32_extra1.binbl32_extra2.bin.

在 ATF 的 BL2 阶段, 将相应的 bin 文件加载到内存中, 即将 TOS 代码加载到内存中. 到 BL31 阶段获取到相应的 TOS 入口地址, BL32 阶段直接执行 TOS 代码.

References

optee学习篇(1) 环境&调试
3. ATF(ARM Trusted firmware)启动—bl2
聊聊SOC启动(四) ATF BL31启动流程