代码导读
关于平台相关的代码和函数均以qemu实现解读。
BL31
在BL2中触发安全监控模式调用后会跳转到BL31中执行,同理复位的入口函数为bl31_entrypoint
。BL31最主要的两个功能:作为启动流程,初始化硬件和加载BL32、BL31等;启动完成后常驻内存,处理各种SMC异常和路由到EL3中断。启动大致的函数执行流程如下。
el3_entrypoint_common
同BL2,现将前面阶段传递过来的x0-x3参数保存起来。
/* ---------------------------------------------------------------
* Stash the previous bootloader arguments x0 - x3 for later use.
* ---------------------------------------------------------------
*/
mov x20, x0
mov x21, x1
mov x22, x2
mov x23, x3
接着同BL1一样,调用el3_entrypoint_common函数。这里根据RESET_TO_BL31是否定义分为两种情况:
- RESET_TO_BL31=0:复位后不是从BL31开始启动,即会按照TF-A的标准启动方式,从BL1,BL2,到BL31启动,由于系统的基本配置已经设置完成,只需要设置C运行环境,异常向量表,地址无关大小。
- RESET_TO_BL31=1:复位后直接从BL31开始启动,同BL1一样,需要初始化系统控制器,冷热启动处理,初始化内存,初始化C运行环境,异常向量表,地址无关大小。最后把x20-x23清零以便后续使用。
#if !RESET_TO_BL31
/* ---------------------------------------------------------------------
* For !RESET_TO_BL31 systems, only the primary CPU ever reaches
* bl31_entrypoint() during the cold boot flow, so the cold/warm boot
* and primary/secondary CPU logic should not be executed in this case.
*
* Also, assume that the previous bootloader has already initialised the
* SCTLR_EL3, including the endianness, and has initialised the memory.
* ---------------------------------------------------------------------
*/
el3_entrypoint_common \
_init_sctlr=0 \
_warm_boot_mailbox=0 \
_secondary_cold_boot=0 \
_init_memory=0 \
_init_c_runtime=1 \
_exception_vectors=runtime_exceptions \
_pie_fixup_size=BL31_LIMIT - BL31_BASE
#else
/* ---------------------------------------------------------------------
* For RESET_TO_BL31 systems which have a programmable reset address,
* bl31_entrypoint() is executed only on the cold boot path so we can
* skip the warm boot mailbox mechanism.
* ---------------------------------------------------------------------
*/
el3_entrypoint_common \
_init_sctlr=1 \
_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS \
_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU \
_init_memory=1 \
_init_c_runtime=1 \
_exception_vectors=runtime_exceptions \
_pie_fixup_size=BL31_LIMIT - BL31_BASE
#if !RESET_TO_BL31_WITH_PARAMS
/* ---------------------------------------------------------------------
* For RESET_TO_BL31 systems, BL31 is the first bootloader to run so
* there's no argument to relay from a previous bootloader. Zero the
* arguments passed to the platform layer to reflect that.
* ---------------------------------------------------------------------
*/
mov x20, 0
mov x21, 0
mov x22, 0
mov x23, 0
#endif /* RESET_TO_BL31_WITH_PARAMS */
#endif /* RESET_TO_BL31 */
bl31_setup
同BL2一样,bl31_setup是BL31建立初始化,包括两个函数:bl31_early_platform_setup2()
和bl31_plat_arch_setup()
。
void bl31_setup(u_register_t arg0, u_register_t arg1, u_register_t arg2,
u_register_t arg3)
{
/* Perform early platform-specific setup */
bl31_early_platform_setup2(arg0, arg1, arg2, arg3);
/* Perform late platform-specific setup */
bl31_plat_arch_setup();
}
-
bl31_early_platform_setup2
首先初始化打印串口。接着获取存储在BL2安全RAM中,通过arg0传递过来的参数即镜像描述参数指针。最后遍历镜像链表,将BL32(若有)和BL33的跳转信息存储在全局变量中,并判断BL33跳转PC是否为空。
void bl31_early_platform_setup2(u_register_t arg0, u_register_t arg1, u_register_t arg2, u_register_t arg3) { /* Initialize the console to provide early debug support */ qemu_console_init(); /* * Check params passed from BL2 */ bl_params_t *params_from_bl2 = (bl_params_t *)arg0; assert(params_from_bl2); assert(params_from_bl2->h.type == PARAM_BL_PARAMS); assert(params_from_bl2->h.version >= VERSION_2); bl_params_node_t *bl_params = params_from_bl2->head; /* * Copy BL33 and BL32 (if present), entry point information. * They are stored in Secure RAM, in BL2's address space. */ while (bl_params) { if (bl_params->image_id == BL32_IMAGE_ID) bl32_image_ep_info = *bl_params->ep_info; if (bl_params->image_id == BL33_IMAGE_ID) bl33_image_ep_info = *bl_params->ep_info; bl_params = bl_params->next_params_info; } if (!bl33_image_ep_info.pc) panic(); }
-
bl31_plat_arch_setup
同BL1一样,配置MMU,建立MMU页表,使能dcache。
void bl31_plat_arch_setup(void) { qemu_configure_mmu_el3(BL31_BASE, (BL31_END - BL31_BASE), BL_CODE_BASE, BL_CODE_END, BL_RO_DATA_BASE, BL_RO_DATA_END, BL_COHERENT_RAM_BASE, BL_COHERENT_RAM_END); }
bl31_main
bl31_main主要执行平台初始化,运行时服务初始化,启动BL32和BL33镜像。
void bl31_main(void)
{
NOTICE("BL31: %s\n", version_string);
NOTICE("BL31: %s\n", build_message);
/* Perform platform setup in BL31 */
bl31_platform_setup();
/* Initialise helper libraries */
bl31_lib_init();
#if EL3_EXCEPTION_HANDLING
INFO("BL31: Initialising Exception Handling Framework\n");
ehf_init();
#endif
/* Initialize the runtime services e.g. psci. */
INFO("BL31: Initializing runtime services\n");
runtime_svc_init();
/*
* All the cold boot actions on the primary cpu are done. We now need to
* decide which is the next image and how to execute it.
* If the SPD runtime service is present, it would want to pass control
* to BL32 first in S-EL1. In that case, SPD would have registered a
* function to initialize bl32 where it takes responsibility of entering
* S-EL1 and returning control back to bl31_main. Similarly, if RME is
* enabled and a function is registered to initialize RMM, control is
* transferred to RMM in R-EL2. After RMM initialization, control is
* returned back to bl31_main. Once this is done we can prepare entry
* into BL33 as normal.
*/
/*
* If SPD had registered an init hook, invoke it.
*/
if (bl32_init != NULL) {
INFO("BL31: Initializing BL32\n");
int32_t rc = (*bl32_init)();
if (rc == 0) {
WARN("BL31: BL32 initialization failed\n");
}
}
/*
* We are ready to enter the next EL. Prepare entry into the image
* corresponding to the desired security state after the next ERET.
*/
bl31_prepare_next_image_entry();
console_flush();
/*
* Perform any platform specific runtime setup prior to cold boot exit
* from BL31
*/
bl31_plat_runtime_setup();
}
-
bl31_platform_setup:BL31平台初始化。
void bl31_platform_setup(void) { plat_qemu_gic_init(); qemu_gpio_init(); }
-
plat_qemu_gic_init:初始化gic中断管理。
-
qemu_gpio_init:初始化qemu平台的pl061 gpio,设置基地址和回调函数。
-
-
bl31_lib_init:初始化帮助库,调用了cm_init上下文管理初始化,但是里面是空函数。
-
ehf_init:初始化异常处理框架。首先设置非安全路由到EL3的中断标志,然后设置bl31顶级中断处理函数。
void __init ehf_init(void) { unsigned int flags = 0; int ret __unused; /* Ensure EL3 interrupts are supported */ assert(plat_ic_has_interrupt_type(INTR_TYPE_EL3) != 0); /* * Make sure that priority water mark has enough bits to represent the * whole priority array. */ assert(exception_data.num_priorities <= (sizeof(ehf_pri_bits_t) * 8U)); assert(exception_data.ehf_priorities != NULL); /* * Bit 7 of GIC priority must be 0 for secure interrupts. This means * platforms must use at least 1 of the remaining 7 bits. */ assert((exception_data.pri_bits >= 1U) || (exception_data.pri_bits < 8U)); /* Route EL3 interrupts when in Non-secure. */ set_interrupt_rm_flag(flags, NON_SECURE); /* * Route EL3 interrupts when in secure, only when SPMC is not present * in S-EL2. */ #if !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1)) set_interrupt_rm_flag(flags, SECURE); #endif /* !(defined(SPD_spmd) && (SPMD_SPM_AT_SEL2 == 1)) */ /* Register handler for EL3 interrupts */ ret = register_interrupt_type_handler(INTR_TYPE_EL3, ehf_el3_interrupt_handler, flags); assert(ret == 0); }
-
runtime_svc_init():运行时服务初始化,如前所述,BL31启动完成后会常驻内存,处理来自低异常等级的SMC异常,这些异常处理流程就是运行时服务,不同的服务C在调用之前需要先注册到BL31中,以optee服务,看下注册流程,在
opteed_main.c
通过DECLARE_RT_SVC定义了两个服务的SMC Call:一个用于快速SMC调用,其不需要执行完整的调度过程就能返回;另一个用于yielding SMC调用,其是阻塞的,需要完整的调度过程才能返回,处理速度较第一种慢。这样OPTEE可以根据不同的场景选择合适的SMC类型来提供服务。/* * 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) \ }
DECLARE_RT_SVC宏定义被编译到镜像文件中的
rt_svc_descs
段中,这个段在链接脚本bl_common.ld.h
中,如下。#define RT_SVC_DESCS \ . = ALIGN(STRUCT_ALIGN); \ __RT_SVC_DESCS_START__ = .; \ KEEP(*(rt_svc_descs)) \ __RT_SVC_DESCS_END__ = .;
DECLARE_RT_SVC宏定义的参数说明如下:
- start_oen:服务的起始内部编号
- end_oen:服务的末尾编号
- call_type:调用的SMC的类型,包括上面的两种类型
SMC_TYPE_FAST
和SMC_TYPE_YIELD
。 - name:服务的名字
- init:服务在执行之前需要被执行的初始化操作函数
- handle:当触发SMC的调用时的请求处理函数
/* 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 );
回到
bl31_main
启动流程,runtime_svc_init
完成对上面定义的各种服务初始化。通过遍历rt_svc_descs
段,首先检验服务的有效性,然后调用对应服务的初始化函数,如optee服务即上面的opteed_setup
函数,最后对于每个服务计算描述符索引,存储在rt_svc_descs_indices
数组中,用于在处理服务时通过该索引获取到对应的服务。void __init runtime_svc_init(void) { int rc = 0; uint8_t index, start_idx, end_idx; rt_svc_desc_t *rt_svc_descs; /* Assert the number of descriptors detected are less than maximum indices */ assert((RT_SVC_DESCS_END >= RT_SVC_DESCS_START) && (RT_SVC_DECS_NUM < MAX_RT_SVCS)); /* If no runtime services are implemented then simply bail out */ if (RT_SVC_DECS_NUM == 0U) return; /* Initialise internal variables to invalid state */ (void)memset(rt_svc_descs_indices, -1, sizeof(rt_svc_descs_indices)); rt_svc_descs = (rt_svc_desc_t *) RT_SVC_DESCS_START; for (index = 0U; index < RT_SVC_DECS_NUM; index++) { rt_svc_desc_t *service = &rt_svc_descs[index]; /* * An invalid descriptor is an error condition since it is * difficult to predict the system behaviour in the absence * of this service. */ rc = validate_rt_svc_desc(service); if (rc != 0) { ERROR("Invalid runtime service descriptor %p\n", (void *) service); panic(); } /* * The runtime service may have separate rt_svc_desc_t * for its fast smc and yielding smc. Since the service itself * need to be initialized only once, only one of them will have * an initialisation routine defined. Call the initialisation * routine for this runtime service, if it is defined. */ if (service->init != NULL) { rc = service->init(); if (rc != 0) { ERROR("Error initializing runtime service %s\n", service->name); continue; } } /* * Fill the indices corresponding to the start and end * owning entity numbers with the index of the * descriptor which will handle the SMCs for this owning * entity range. */ start_idx = (uint8_t)get_unique_oen(service->start_oen, service->call_type); end_idx = (uint8_t)get_unique_oen(service->end_oen, service->call_type); assert(start_idx <= end_idx); assert(end_idx < MAX_RT_SVCS); for (; start_idx <= end_idx; start_idx++) rt_svc_descs_indices[start_idx] = index; } }
看一下optee快速SMC服务的初始化,
service->init()
其会调用opteed_setup
,该函数是启动optee的入口函数。static int32_t opteed_setup(void) { entry_point_info_t *optee_ep_info; uint32_t linear_id; uint64_t opteed_pageable_part; uint64_t opteed_mem_limit; uint64_t dt_addr; linear_id = plat_my_core_pos(); /* * Get information about the Secure Payload (BL32) image. Its * absence is a critical failure. TODO: Add support to * conditionally include the SPD service */ optee_ep_info = bl31_plat_get_next_image_ep_info(SECURE); if (!optee_ep_info) { WARN("No OPTEE provided by BL2 boot loader, Booting device" " without OPTEE initialization. SMC`s destined for OPTEE" " will return SMC_UNK\n"); return 1; } /* * If there's no valid entry point for SP, we return a non-zero value * signalling failure initializing the service. We bail out without * registering any handlers */ if (!optee_ep_info->pc) return 1; opteed_rw = optee_ep_info->args.arg0; opteed_pageable_part = optee_ep_info->args.arg1; opteed_mem_limit = optee_ep_info->args.arg2; dt_addr = optee_ep_info->args.arg3; opteed_init_optee_ep_state(optee_ep_info, opteed_rw, optee_ep_info->pc, opteed_pageable_part, opteed_mem_limit, dt_addr, &opteed_sp_context[linear_id]); /* * All OPTEED initialization done. Now register our init function with * BL31 for deferred invocation */ bl31_register_bl32_init(&opteed_init); return 0; }
- plat_my_core_pos:获取当前core的ID
- bl31_plat_get_next_image_ep_info:获取BL32(OP-TEE)镜像的描述信息,如果返回NULL,说明BL2没有启动BL32,那么对于OPTEE 的SMC将会报错误
- !optee_ep_info->pc:判断BL32 镜像的PC地址是否有效
- opteed_init_optee_ep_state:初始化optee上下文和入口信息
- bl31_register_bl32_init:注册opteed_init初始化函数给bl32_init变量,以备bl31调用
-
(*bl32_init)():初始化BL32,即调用上面的opteed_init,完成optee的设置。
static int32_t opteed_init(void) { uint32_t linear_id = plat_my_core_pos(); optee_context_t *optee_ctx = &opteed_sp_context[linear_id]; entry_point_info_t *optee_entry_point; uint64_t rc; /* * Get information about the OPTEE (BL32) image. Its * absence is a critical failure. */ optee_entry_point = bl31_plat_get_next_image_ep_info(SECURE); assert(optee_entry_point); cm_init_my_context(optee_entry_point); /* * Arrange for an entry into OPTEE. It will be returned via * OPTEE_ENTRY_DONE case */ rc = opteed_synchronous_sp_entry(optee_ctx); assert(rc != 0); return rc; }
-
plat_my_core_pos:获取当前core的ID
-
optee_ctx:获取执行的上下文
-
bl31_plat_get_next_image_ep_info:获取BL32(OP-TEE)镜像的描述信息
-
cm_init_my_context:初始化CPU的上下文
-
opteed_synchronous_sp_entry:同步进入跳转到optee执行。首先应用Seucre-EL1系统寄存器,然后调用opteed_enter_sp进入到optee。
uint64_t opteed_synchronous_sp_entry(optee_context_t *optee_ctx) { uint64_t rc; assert(optee_ctx != NULL); assert(optee_ctx->c_rt_ctx == 0); /* Apply the Secure EL1 system register context and switch to it */ assert(cm_get_context(SECURE) == &optee_ctx->cpu_ctx); cm_el1_sysregs_context_restore(SECURE); cm_set_next_eret_context(SECURE); rc = opteed_enter_sp(&optee_ctx->c_rt_ctx); return rc; }
由于BL31除了启动BL32,还要启动BL33,因此在跳转到BL32之前,需要保存当前上下文,用以返回到断点处继续执行。首先暂存EL3 callee-saved寄存器,然后调用el3_exit进入OPTEE执行。
func opteed_enter_sp /* Make space for the registers that we're going to save */ mov x3, sp str x3, [x0, #0] sub sp, sp, #OPTEED_C_RT_CTX_SIZE /* Save callee-saved registers on to the stack */ stp x19, x20, [sp, #OPTEED_C_RT_CTX_X19] stp x21, x22, [sp, #OPTEED_C_RT_CTX_X21] stp x23, x24, [sp, #OPTEED_C_RT_CTX_X23] stp x25, x26, [sp, #OPTEED_C_RT_CTX_X25] stp x27, x28, [sp, #OPTEED_C_RT_CTX_X27] stp x29, x30, [sp, #OPTEED_C_RT_CTX_X29] /* --------------------------------------------- * Everything is setup now. el3_exit() will * use the secure context to restore to the * general purpose and EL3 system registers to * ERET into OPTEE. * --------------------------------------------- */ b el3_exit endfunc opteed_enter_sp
当optee初始化完成后,由于其在Secure EL1下运行,其需要通过SMC方式重新进入到BL31,即通过
TEESMC_OPTEED_RETURN_ENTRY_DONE
进入到SMC处理流程,如下。opteed_smc_handler负责处理来自非安全状态的SMC调用的函数,它负责与安全负载进行通信,委派工作并将结果返回给非安全状态。这里会执行TEESMC_OPTEED_RETURN_ENTRY_DONE
的分支,表明该SMC是从BL32执行完成的返回,然后调用opteed_synchronous_sp_exit
恢复之前进入bl32之前保存的上下文,返回进行执行。static uintptr_t opteed_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) { cpu_context_t *ns_cpu_context; uint32_t linear_id = plat_my_core_pos(); optee_context_t *optee_ctx = &opteed_sp_context[linear_id]; uint64_t rc; /*.....*/ /* * Returning from OPTEE */ switch (smc_fid) { /* * OPTEE has finished initialising itself after a cold boot */ case TEESMC_OPTEED_RETURN_ENTRY_DONE: /* * Stash the OPTEE entry points information. This is done * only once on the primary cpu */ assert(optee_vector_table == NULL); optee_vector_table = (optee_vectors_t *) x1; if (optee_vector_table) { set_optee_pstate(optee_ctx->state, OPTEE_PSTATE_ON); /* * OPTEE has been successfully initialized. * Register power management hooks with PSCI */ psci_register_spd_pm_hook(&opteed_pm); /* * Register an interrupt handler for S-EL1 interrupts * when generated during code executing in the * non-secure state. */ flags = 0; set_interrupt_rm_flag(flags, NON_SECURE); rc = register_interrupt_type_handler(INTR_TYPE_S_EL1, opteed_sel1_interrupt_handler, flags); if (rc) panic(); } /* * OPTEE reports completion. The OPTEED must have initiated * the original request through a synchronous entry into * OPTEE. Jump back to the original C runtime context. */ opteed_synchronous_sp_exit(optee_ctx, x1); break; /* * These function IDs is used only by OP-TEE to indicate it has * finished: * 1. turning itself on in response to an earlier psci * cpu_on request * 2. resuming itself after an earlier psci cpu_suspend * request. */ case TEESMC_OPTEED_RETURN_ON_DONE: case TEESMC_OPTEED_RETURN_RESUME_DONE: /* .... */ default: panic(); } }
首先恢复以前保存在x0的栈,然后恢复栈上的callee-saved寄存器,最后将x1作为x0,返回到断点处继续执行,即返回到
bl32_init
处。/* --------------------------------------------- * This function is called 'x0' pointing to a C * runtime context saved in opteed_enter_sp(). It * restores the saved registers and jumps to * that runtime with 'x0' as the new sp. This * destroys the C runtime context that had been * built on the stack below the saved context by * the caller. Later the second parameter 'x1' * is passed as return value to the caller * --------------------------------------------- */ .global opteed_exit_sp func opteed_exit_sp /* Restore the previous stack */ mov sp, x0 /* Restore callee-saved registers on to the stack */ ldp x19, x20, [x0, #(OPTEED_C_RT_CTX_X19 - OPTEED_C_RT_CTX_SIZE)] ldp x21, x22, [x0, #(OPTEED_C_RT_CTX_X21 - OPTEED_C_RT_CTX_SIZE)] ldp x23, x24, [x0, #(OPTEED_C_RT_CTX_X23 - OPTEED_C_RT_CTX_SIZE)] ldp x25, x26, [x0, #(OPTEED_C_RT_CTX_X25 - OPTEED_C_RT_CTX_SIZE)] ldp x27, x28, [x0, #(OPTEED_C_RT_CTX_X27 - OPTEED_C_RT_CTX_SIZE)] ldp x29, x30, [x0, #(OPTEED_C_RT_CTX_X29 - OPTEED_C_RT_CTX_SIZE)] /* --------------------------------------------- * This should take us back to the instruction * after the call to the last opteed_enter_sp(). * Place the second parameter to x0 so that the * caller will see it as a return value from the * original entry call * --------------------------------------------- */ mov x0, x1 ret endfunc opteed_exit_sp
-
-
bl31_prepare_next_image_entry:获取下一阶段需要被加载的镜像文件,配置运行环境。
void __init bl31_prepare_next_image_entry(void) { entry_point_info_t *next_image_info; uint32_t image_type; /* Determine which image to execute next */ image_type = bl31_get_next_image_type(); /* Program EL3 registers to enable entry into the next EL */ next_image_info = bl31_plat_get_next_image_ep_info(image_type); assert(next_image_info != NULL); assert(image_type == GET_SECURITY_STATE(next_image_info->h.attr)); INFO("BL31: Preparing for EL3 exit to %s world\n", (image_type == SECURE) ? "secure" : "normal"); print_entry_point_info(next_image_info); cm_init_my_context(next_image_info); /* * If we are entering the Non-secure world, use * 'cm_prepare_el3_exit_ns' to exit. */ if (image_type == NON_SECURE) { cm_prepare_el3_exit_ns(); } else { cm_prepare_el3_exit(image_type); } }
- bl31_get_next_image_type:bl31进入下一阶段执行的镜像类型,即下一阶段BL33是非安全
NON_SECURE
的 - bl31_plat_get_next_image_ep_info:获取镜像跳转信息,即
bl33_image_ep_info
, - cm_init_my_context:初始化CPU上下文
- cm_prepare_el3_exit_ns:准备进入到非安全世界
- bl31_get_next_image_type:bl31进入下一阶段执行的镜像类型,即下一阶段BL33是非安全
-
console_flush:刷新串口终端数据
-
bl31_plat_runtime_setup:在退出bl31之前执行平台runtime建立,qemu是调用的弱函数,配置切换了console的状态,即
console_switch_state(CONSOLE_FLAG_RUNTIME)
。
el3_eixt
在bl31_main
函数执行完成后,回到bl31_entrypoint
继续执行,首先清零data和bss段,然后执行el3_eixt函数进入到bl33阶段。至此TF-A启动流程全部执行完毕,进入到uboot或者直接运行kernel。
/* --------------------------------------------------------------------
* Clean the .data & .bss sections to main memory. This ensures
* that any global data which was initialised by the primary CPU
* is visible to secondary CPUs before they enable their data
* caches and participate in coherency.
* --------------------------------------------------------------------
*/
adrp x0, __DATA_START__
add x0, x0, :lo12:__DATA_START__
adrp x1, __DATA_END__
add x1, x1, :lo12:__DATA_END__
sub x1, x1, x0
bl clean_dcache_range
adrp x0, __BSS_START__
add x0, x0, :lo12:__BSS_START__
adrp x1, __BSS_END__
add x1, x1, :lo12:__BSS_END__
sub x1, x1, x0
bl clean_dcache_range
b el3_exit