高通android启动代码流程分析(SBL->ABL)

一、前言

在面对UEFI阶段代码移植以及开机故障问题,需要对开机启动流程有一定的了解

二、芯片的冷启动

Cold boot flow: 冷启动

可以看出,在设备上电后,先跑的是 APPS PBL,接着运行XBL SEC、XBL Loader,通过Loader引出XBL CORE APPSBL,最后进入HLOS

备注:

下面补充点arm架构的知识点,以便可以看懂上图

2.1 异常级别

异常级别 运行的软件
EL0 Application
EL1 Linux kernel- os
EL2 Hypervisor(可理解为上面跑多个虚拟OS)
EL3 Secure Monitor(ARM Trusted Firmware)
  1. ELx(x<4),x越大等级越高,执行特权越高
  2. 执行在EL0,称为非特权执行
  3. EL2没有Secure state,只有Non-secure state
  4. EL3 只有Secure state,支持EL0/EL1的Secure 和Non-secure之间的切换
  5. EL0 & EL1 必须要实现,EL2/EL3则是可选实现
  6. 当接收到一个异常时,异常级别只能调高或保持;
  7. 当从异常返回时,异常级别只能调低或保持
  8. 在接收到异常将要切换或保持的异常级别称为目标异常级别
  9. 每个异常级别本身有一个默认固定的目标异常级别,还可以通过寄存器设置目标异常级别,目标异常级别不能为EL0
  10. 当PE运行在一个异常级别时,可以访问如下两种资源:1)当前异常级别和安全状态组合下的资源;2)低异常级别可访问的资源(要符合安全状态)

2.2 secure state

状态 特点
Non-secure EL0/EL1/EL2, 只能访问Non-secure 物理地址空间
Secure EL0/EL1/EL3, 可以访问Non-secure 物理地址空间 & Secure 物理地址空间,可起到物理屏障安全隔离作用

这几个涉及的模块大概功能

  1. Application primary boot loader (APPS PBL)

PBL 启动时,CPU只开启了第一个核心 CPU Core 0,运行固件在ROM中,这部分是高通写死在芯片中的固件,外部开发人员是无法修改这部份的。

主要功能为:

(1)系统安全环境的初始化,以确保后续的XBL中的APPS 能够正常运行。

(2)根据boot gpio的配置选择从什么设备启动操作系统(如 Nand,USB等)。

(3)通过检测GPIO判断是否进入Emergency Download mode,用户可以通过QFIL来下载完整的系统镜像。

(4)通过L2 TCM来加载XBL1 ELF,OCIMEM 和 RPM CodeRAM 代码。

  1. Extensible boot loader (XBL)

从XBL开始,跑的就是我们编译下载进eMMC/UFS的系统镜像了,在XBL中主要是初始化相关的硬件环境,及代码安全环境。

(1)初始化 Buses、DDR、Clocks、CDT,启动QSEE,QHEE,RPM_FW, XBL core images。

(2)使能memory dump through USB and Sahara(系统死机时memory dump),看门狗,RAM dump to SD support等功能。

(3)初始化 USB驱动,USB充电功能,温升检测,PMIC驱动初始化,和 DDR training模块。

  1. XBL core (UEFI or LK,ABL)

XBL core,就是之前的bootloader,主要功能就是初始化display驱动,提供fastboot功能,引导进入HLOS kernel操作系统。 注意,在ABL中,同样也只有CPU Core0在工作,其他的CPU核以是在进入HLOS Kernel后才开始初始化启用的。

三、代码目录架构

以前的lk相关代码移到了boot_images/QcomPkg路径下,编译方式和之前也不同了

1
2
3
4
5
6
#编译指令
cd boot_images/QcomPkg/SocPkg/DivarPkg
python …/…/buildex.py --variant LAA -r RELEASE -t DivarPkg,QcomToolsPkg

#clean指令
python …/…/buildex.py --variant LAA -r RELEASE -t DivarPkg,QcomToolsPkg --build_flags=cleanall

代码位于:${BP_ROOT}/BOOT.XF.1.4/boot_images/

四、PBL->SBL

PBL是上电后芯片执行的第一行代码,位于ROM中,PBL代码未开源,无法查看,之后通过PBL启动SBL。SBL1负责初始化部分硬件以进行后续引导,初始化DDR内存,加载TrustZone,加载RPM固件,加载ABL

从SBL开始就进入了XBL中,而我们所说的SBL1也就是XBL REG#1,代码为

BOOT.XF.4.1/boot_images/QcomPkg/SocPkg/Library/XBLLoaderLib/sbl1_Aarch64.s

1
2
3
4
5
6
sbl1_entry_init_stack:
// -------------------------------
// add more assembly init code here for entering sbl1_main_ctl//
// restore PBL parameter and enter sbl1_main_ctl
// -------------------------------
MOVw0,w7BLsbl1_main_ctl //跳转到C环境继续

4.1 sbl1_main_ctl函数

主要工作:一些重要外设的初始化,如RAM的初始化,为下一步程序的运行做准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
voidsbl1_main_ctl(boot_pbl_shared_data_type *pbl_shared){
DALResultbsy_wait_init;

/* Configure Domain access control register */
mmu_set_dacr(DACR_ALL_DOMAIN_CLIENTS); //配置域访问控制器

/* Retrieve info passed from PBL*/
sbl1_retrieve_shared_info_from_pbl(pbl_shared); //将pbl传过来的信息保存到pbl_shared

/* Initialize shared functions structure - provides other images with function pointers in Loader */
boot_shared_functions_register(); //初始化共享函数结构

/* Initialize SBL memory map */
sbl1_populate_initial_mem_map(&bl_shared_data); //初始化SBL内存映射

/* Calculate the SBL start time for use during boot logger initialization. */
sbl_start_time =CALCULATE_TIMESTAMP(HWIO_IN(TIMETICK_QTIMER_CLK));
sbl_start_time_mpm =CALCULATE_MPM_TIMESTAMP(HWIO_IN(TIMETICK_CLK));

/* Initialize busywait module Note: required before logger init due to uart driver dependency on busywait */
BL_VERIFY((bsy_wait_init=boot_busywait_init()) ==DAL_SUCCESS, (uint16)bsy_wait_init|BL_ERROR_GROUP_BUSYWAIT);

/* Enable qdss workaround*/
BL_VERIFY(boot_clock_debug_init() ==TRUE,FALSE|BL_ERROR_GROUP_CLK );

/* Enter debug mode if debug cookie is set */
sbl1_debug_mode_enter(); //sbl1 debug mode,其实就是一个while,知道jtag获取到地址。判断cookie是否匹配,以确定是否进入jtag调试模式

/* Initialize the stack protection canary */
boot_init_stack_chk_canary();//堆栈保护,随机初始化多出数值,避免堆栈溢出攻击

/* Initialize boot shared imem */
boot_shared_imem_init(&bl_shared_data);
//进行SBL间数据共享初始化,数据类型bl_shared_data_type,先进行溢出判断,检查魔数是否匹配,否则写0xFF,并更新版本和魔数值

/* Initialize the ChipInfo driver */
ChipInfo_Init();

/* Initialize the QSEE interface */
sbl1_init_sbl_qsee_interface(&bl_shared_data, &sbl_verified_info);
//初始化QSEE(Secure Excution Environment)安全执行环境接口,清零interface,初始化完成后并为QSEE接口分配魔数和version号以及复位请求,调用boot_ddr_enter_self_refresh进行DDR自我更新和调用boot_ddr_exit_self_refresh退出自我更新。并使用PBL提供的验证信息更新QSEE的接口。更新bl_shared_data中的qsee中的boot_image_entry入口点。QSEE一般是为APP提供安全运行环境,它将整体系统分为安全运行环境和常规运行环境,像指纹识别、人脸识别均需要运行在安全环境下,而像普通APP则运行在常规环境下,QSEE一般和Trust Zone共同完成安全运行环境和常规运行环境的搭建
/* Initialize dal heap using internal memory */
boot_DALSYS_HeapInit(boot_internal_heap,BOOT_INTERNAL_HEAP_SIZE,FALSE);

/*Initialize DAL, needs to be called before modules that uses DAL */
boot_DALSYS_InitMod(NULL);

/* Initialize boot logger and start the log timer.
This must be done after sbl1_retrieve_shared_info_from_pbl
and boot_secboot_ftbl_init. */
sbl1_boot_logger_init(&boot_log_data,pbl_shared);
//配置GPIO使能串口,调用sbl1_boot_logger_init进行串口及定时器和相关log初始化,便于boot_log_message打印log信息。串口硬件初始化:sbl1_boot_logger_init-> boot_log_init-> boot_log_init_uart-> boot_uart_init> uart_initialize-> uart_open(&uart_debug_handle, UART_DEBUG_PORT, &c),最后一个函数位于QomPkg/Library/UartQupv3Lib/UartXBL.c, 函数内调用UART_LOG_0(INFO,"+uart_open")打印串口开启log,有待探究,uart_open调用register_init函数配置寄存器,函数内调用REG_OUT;配置相关寄存器。

boot_log_set_meta_info(boot_log_data.meta_info_start);

/* Set hash algorithm */
BL_VERIFY(boot_set_hash_algo(SBL_HASH_SHA256) ==BL_ERR_NONE,BL_ERR_UNSUPPORTED_HASH_ALGO|BL_ERROR_GROUP_BOOT);

/* Call sbl1_hw_init to config pmic device so we can use PS_HOLD to reset */
sbl1_hw_init();
//硬件初始化,调用boot_Tsens_Init初始化温度传感器;调用boot_qusb_ldr_utils_hs_phy_nondrive_mode_set将高速QUSB2置位为非驱动模式;IIC时钟初始化,因为EEPROM使用IIC协议,初始化IIC时钟来确保后面的EEPROM能够得到正常的初始化。首先根据宏定义FEATURE_BOOT_FAST_DEBUG判断是否进入debug mode->boot_debug_mode_enter();

#if defined (FEATURE_DEVICEPROGRAMMER_IMAGE) || defined (FEATURE_DDI_IMAGE)
/* Enter device programmer does not return */
device_programmer_init(&bl_shared_data,pbl_shared);
#else/* Store the sbl1 hash to shared imem */
boot_store_tpm_hash_block(&bl_shared_data, &sbl_verified_info);

/*-----------------------------------------------------------------------
Process the target-dependent SBL1 procedures
-----------------------------------------------------------------------*/
boot_config_process_bl(&bl_shared_data,SBL1_IMG,sbl1_config_table);
#endif

} /* sbl1_main_ctl() */

sbl1_main_ctl中使用了sbl1_boot_logger_init进行了logger的初始化,这其中就会调用boot_log_init_uart,将串口进行初始化

在这个阶段如果需要加log,有下面几种方式:

  1. boot_log_dump(boot_log_ram_to_uart)将ram中的所有log从串口输出
  2. boot_log_message,此函数是加log比较方便的方式,此函数会将需要打印的log同时写入ram以及输出到串口

上面分析的函数在开机串口log中的体现如下:

https://hexoimg.oss-cn-shanghai.aliyuncs.com/blog/24/11/image_5933e408163d69a1b9b530b8069e3248.png

4.2 boot_config_process_bl函数

此函数为核心函数,形参包括sbl共享数据结构,SBL1镜像文件,和sbl1配置表。函数循环调用boot_config_process_entry对配置表中 各项进行配置,配置列表中包含PMIC、APDP、QSEE (Qualcomm secure execution environment)、QHEE( Qualcomm secure execution environment)、RPM、STI、ABL、APPSBL等子系统。

在sbl1_config_table结构体数组中,每个数组按照对应顺序对应PMIC、RPM等子系统,在子系统中的load_…_pre_procs函数和load…post_procs,两个函数中分别对应相应系统加载前要做的初始化和配置操作以及加载后操作。

这部分的代码有点难以理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
boot_config_process_bl->boot_config_process_entry->boot_do_procedures->

#define BOOT_FUNCTION_LEN 48
void boot_do_procedures
(
bl_shared_data_type *bl_shared_data,
boot_function_table_type *procs
)
{
boot_function_table_type *cur_proc;
uint32 func_start_time=0;
char func_name[BOOT_FUNCTION_LEN];

BL_VERIFY( bl_shared_data != NULL, BL_ERR_NULL_PTR_PASSED|BL_ERROR_GROUP_BOOT );

if (procs != NULL)
{
for ( cur_proc = procs; (boot_procedure_func_type)cur_proc->func != NULL; cur_proc++ )
{
func_start_time = boot_log_get_time();
((boot_procedure_func_type)cur_proc->func)( bl_shared_data );
qsnprintf(func_name, BOOT_FUNCTION_LEN ,"%s", cur_proc->func_name);
boot_log_delta_time(func_start_time,func_name);//这边会打印log
}
}
}
//我们需要看这个sbl_config_table,他是在sbl_config.c中配置的
boot_configuration_table_entry sbl1_config_table[] =
{
/* host_img_id host_img_type target_img_id target_img_type target_img_sec_type load auth exec jump exec_func jump_func pre_procs post_procs load_cancel target_img_partition_id target_img_str boot_ssa_enabled enable_xpu xpu_proc_id sbl_qsee_interface_index seg_elf_entry_point whitelist_ptr */
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_APDP_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, apdp_pre_procs, apdp_post_procs, apdp_load_cancel, apdp_partition_id, APDP_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, apdp_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_OEM_MISC_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, NULL, oem_misc_load_cancel, multi_image_partition_id, OEM_MISC_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, oem_misc_img_whitelist},
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_QTI_MISC_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, NULL, qti_misc_load_cancel, multi_image_qti_partition_id, QTI_MISC_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, qti_misc_img_whitelist},
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_RPM_FW_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, rpm_pre_procs, NULL, rpm_load_cancel, rpm_partition_id, RPM_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, rpm_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_QSEE_DEVCFG_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, NULL, qsee_devcfg_load_cancel, qsee_devcfg_image_partition_id, QSEE_DEVCFG_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, devcfg_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_QSEE_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, qsee_post_procs, NULL, qsee_partition_id, QSEE_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, qsee_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_SEC_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, NULL, sec_load_cancel, secdata_partition_id, SEC_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, sec_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_QHEE_SW_TYPE, TRUE, TRUE, FALSE, FALSE, NULL, NULL, NULL, NULL, NULL, qhee_partition_id, QHEE_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, qhee_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_WDT_SW_TYPE, TRUE, TRUE, FALSE, TRUE, NULL, sti_jump_func, NULL, NULL, sti_load_cancel, sti_partition_id, STI_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, 0x0, sti_img_whitelist },
{SBL1_IMG, CONFIG_IMG_QC, GEN_IMG, CONFIG_IMG_ELF, SECBOOT_APPSBL_SW_TYPE, TRUE, TRUE, FALSE, TRUE, NULL, qsee_jump_func, NULL, appsbl_post_procs, appsbl_load_cancel, appsbl_partition_id, APPSBL_BOOT_LOG_STR, FALSE, FALSE, 0x0, 0x0, SCL_XBL_CORE_CODE_BASE, xbl_core_img_whitelist},
{NONE_IMG, }
};

//boot_do_procedures函数会按照这个表循环去执行相应的func,以第一个为例
//第一个循环的func为apdp_pre_procs
boot_function_table_type apdp_pre_procs[] =
{
/* Initialize the flash device */
{boot_flash_init, "boot_flash_init"}, //func: boot_flash_init, name: boot_flash_init

/* Initialize XBL config Lib */
{sbl1_xblconfig_init, "sbl1_xblconfig_init"},

/*Initialize feature configuration from xbl config image*/
{sbl1_feature_config_init,"sbl1_feature_config_init"},

/* Initialize the default CDT before reading CDT from flash */
{boot_config_data_table_default_init, "boot_config_data_table_default_init"},

/* Set default DDR params */
{sbl1_ddr_set_default_params, "sbl1_ddr_set_default_params"},

/* Copy the configure data table from flash */
{boot_config_data_table_init, "boot_config_data_table_init"},

/* Store platform id */
{sbl1_hw_platform_pre_ddr, "sbl1_hw_platform_pre_ddr"},

/* Initialize PMIC and railway driver */
{sbl1_hw_pre_ddr_init, "sbl1_hw_pre_ddr_init"},

/* Check if forced dload timeout reset cookie is set */
{boot_dload_handle_forced_dload_timeout, "boot_dload_handle_forced_dload_timeout"},

/* Configure ddr parameters based on eeprom CDT table data. */
{sbl1_ddr_set_params, "sbl1_ddr_set_params"},

/* Initialize DDR */
{sbl1_ddr_init, "sbl1_ddr_init"},

/* Train DDR if applicable */
{sbl1_do_ddr_training, "sbl1_do_ddr_training"},

/*----------------------------------------------------------------------
Run deviceprogrammer if compiling the deviceprogrammer_ddr image.
In XBL builds the function below is stubbed out (does nothing)
----------------------------------------------------------------------*/
{sbl1_hand_control_to_devprog_ddr_or_ddi, "sbl1_hand_control_to_devprog_ddr_or_ddi"},

#ifndef FEATURE_DEVICEPROGRAMMER_IMAGE

/* Initialize SBL1 DDR ZI region, relocate boot log to DDR */
{sbl1_post_ddr_init, "sbl1_post_ddr_init"},

{sbl1_hw_init_secondary, "sbl1_hw_init_secondary"},

#endif /*FEATURE_DEVICEPROGRAMMER_IMAGE*/

/* Last entry in the table. */
NULL
};

//这也正好对应着串口lo中后续的log
D - 11 - boot_flash_init

对应的串口如下:

在串口中对应的打印信息Image load Start,APDP Image Loaded,Delta;Image_Load,Start……就是对应函数boot_config_process_entry函数该阶段的image加载、验证与执行都是类似RPM、QSEE、这种子系统环境的搭建,之后在ABL部分会正式进行kernel的加载进而启动HLOS

五、SBL

其实第四节已经将SBL的流程梳理了,本章节独立出来是希望能更加细节的研究一下这部分的代码

5.1 sbl1_xblconfig_init

1
sbl1_xblconfig_init->xblconfig_init

5.2 sbl1_hw_pre_ddr_init

5.2.1 boot_pm_device_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static int abs(int x) { return x >= 0 ? x : -x; }

pm_err_flag_type
pm_device_init ( void )
{
static pm_err_flag_type err_flag = PM_ERR_FLAG_SUCCESS;
uint32 initial_num_spmi_transn = pm_get_num_spmi_transaction(0);

err_flag |= pm_device_setup();//SPMI bus初始化

pm_target_information_init();

if( (pm_is_target_pre_silicon() == TRUE) && (pm_is_pmic_present(PMIC_A) == FALSE) )
{
pm_log_message("Bootup: No PMIC on RUMI Target");
return err_flag = PM_ERR_FLAG_SUCCESS;
}

pm_comm_info_init();

err_flag |= pm_device_pre_init();

err_flag |= pm_pon_init(); //PON机制的初始化

err_flag |= pm_pbs_info_rom_init(); /* Read PBS INFO for the pmic rom devices */

err_flag |= pm_sbl_pre_config(); /* SBL Pre Configuration */

err_flag |= pm_sbl_config(); /* SBL Configuration */
if (err_flag == PM_ERR_FLAG_SUCCESS)
{
pm_ram_image_loaded_flag = TRUE;
}

err_flag |= pm_sbl_config_test(); /* SBL Configuration validation, only executes complete code if spare reg 0x88F bit 0 is set*/

err_flag |= pm_pbs_info_ram_init(); /* Read PBS INFO for the pmic ram devices */

err_flag |= pm_pbs_ram_version_validation_test(); /* PBS RAM Version validation, only executes complete code if spare reg 0x88F bit 0 is set*/

err_flag |= pm_device_post_init(); /* Initialize PMIC with the ones PDM can not perform */

//Write to Spare bit for pm_device_init_status
if(err_flag == PM_ERR_FLAG_SUCCESS)
{
err_flag = pm_comm_write_byte_mask(PMIC_A_SLAVEID_PRIM, PMIO_PON_PERPH_RB_SPARE_ADDR, PON_PERPH_RB_SPARE_DEVICE_INIT_MASK,PON_PERPH_RB_SPARE_DEVICE_INIT_MASK, 0);
}

pm_log_message("Device Init # SPMI Transn: %d", pm_get_num_spmi_transaction(initial_num_spmi_transn));
return err_flag; /* NON ZERO return means an ERROR */
}

PMIC相关的逻辑不扩展,可查看这一系列文章SBL PMIC 流程分析

https://wiki.n.miui.com/pages/viewpage.action?pageId=610308677

5.2.2 sbl1_hw_reset_platform_type和sbl1_hw_reset_platform_version

这部分涉及高通平台boardid的设计,详细可查看小米古明涛大神的文章

https://wiki.n.miui.com/pages/viewpage.action?pageId=135269079

https://wiki.n.miui.com/display/~gumingtao/2018/12/18/How+to+select+Dtb

六、UEFI背景介绍

要详细了解UEFI,还得从BIOS讲起。我们都知道,每一台普通的电脑都会有一个BIOS,用于加载电脑最基本的程式码,担负着初始化硬件,检测硬件功能以及引导操作系统的任务。UEFI就是与BIOS相对的概念,这种接口用于操作系统自动从预启动的操作环境,加载到一种操作系统上,从而达到开机程序化繁为简节省时间的目的。传统BIOS技术正在逐步被UEFI取而代之,在最近新出厂的电脑中,很多已经使用UEFI,使用UEFI模式安装操作系统是趋势所在。

主要是由于安腾处理器芯片组的创新,64位架构的处理器已经不再适用于传统BIOS的16运行模式,因此intel将系统固件和OS的接口完全重新定义成一个可扩展、标准化的固件接口规范。

优化项 UEFI BIOS 描述
开发效率 • 开源
• 标准接口
• 绝大部分是c代码 • 闭源
• 接口混乱
• 主要采用汇编 正是由于BIOS的闭源以及混乱的接口导致其停滞不前,并最终无法适用于新架构的芯片。UEFI的开源且指定了标准的接口规范,并且也同提供接口,将大部分代码替换成c进行编写,不再使用晦涩的汇编进行开发,这样大大降低了开发的难度,这也是其迅速发展的根本原因。
性能 异步+时钟中断 中断机制 UEFI舍弃了硬件外部中断的低效方式,只保留了时钟中断机制,然后通过异步+事件来实现对外部设备的操作,性能由此得到了极大的提升。
扩展和兼容性 模块化驱动设计 静态链接 UEFI采用了规范的模块化设计,在扩展功能的时候只需要动态链接其模块就可以实现,这样的扩展十分方便便捷。而传统的BIOS必须运行在16位的指令模式之下,寻址范围也十分有限,而UEFI支持64位程序,并兼容32位,这也是为什么老古董windows XP出现这么久了,稍微改改就可以安装在新的设备上。
安全性 安全 未考虑安全性问题 UEFI安装的驱动设备是需要经过签名验证才可以安装的,并且还采用了一定的加密机制进行验证,这样的安全机制一定程度上可以保障我们设备的安全。
驱动器容量 支持容量>2TB的驱动器 不支持容量>2TB的驱动器 • 传统的BIOS只支持容量不超过2TB的驱动器,因为常见的512Byte扇区的分区表的单个分区的第13-16字节是用来进行LAB寻址的,也就是以扇区为单位进行寻址。总共4个字节,也就是48 = 32位,也就是2^32个单位空间,以扇区为单位进行寻址,也就是每次512Byte,那么: 2^32512 = 2^41B = 2^31KB = 2^21MB = 2^11GB = 2TB. 所以传统的BIOS支持的最大容量的驱动器不超过2TB,以硬件厂商1000:1024的计算方式,实际容 量是小于2TB的。
• UEFI支持64位的地址空间,所以其寻址偏移刚好为一个机器长度——64位,即8Byte,还是按照LAB寻址方式进行计算:2^64*512 = 2^73B = 2^13EB = 8ZB但是,微软官方给的资料显示是18EB(按照硬件厂商1000:1024计算)(10^60)/(10^60) = 18EB
文件系统 拥有文件系统的支持 由于UEFI本身已经相当于一个微型的OS了,具备了文件系统的支持,能够直接读取FAT分区中的文件。
应用程序 可以开发出直接在UEFI下运行的应用程序 这类程序文件通常以efi结尾。既然,UEFI既可以直接识别FAT分区中的文件,又可以直接在其中运行应用程序,那么完全可以将win安装程序做成efi类型的应用程序,然后放在任意的FAT分区中,直接运行即可。这样一来,原来安装win操作系统这个过去看起来有些许小复杂的事情忽然变得异常简单,就像在win中打开一个qq一样简单。而这在BIOS上是无法做到,因为BIOS下启动OS之前,必须从硬盘上指定扇区读取系统的启动代码(包含在主引导记录中),然后从活动分区中引导OS的启动。对扇区的操作远远比不上对分区中的文件的操作来得直观和简单,所以在BIOS上安装win这类的OS,不得不使用一些工具对设备进行配置以达到启动要求。

6.1 Android的bootloader

UEFI的出道是在PC领域,UEFI+gpt以其自身优点干翻了传统bios+mbr,UEFI也成为了未来bootloader的发展方向,计算机软件界有一个名言,计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决(抽象),UEFI也不例外,之所以bios不行一个重要的点就是因为他的兼容性,UEFI为固件和操作系统提供了统一的接口,打通了PC固件之间的鸿沟;安卓bootloader从最初使用uboot,到lk(目前大多数依然是lk),一直到现在选择使用uefi,也足以证明UEFI的优势; 原来的安卓启动是采用boot loader这种引导启动方式。在有的文章中,会将bootloader进行比较,有的又会说现在的安卓bootloader是使用UEFI,而且在查找有关Android启动流程或者说开机流程的文章里,大部分还是在loader层以bootloader作为启动流程中的一部分。这里,算是给自己统一下说法,早期的bootloader是一种引导程序的名称,后来采用了LK、UEFI之后,bootloader就是代指某种的引导程序的统称,换句话说,UEFI、LK都是安卓bootloader采用的一种引导方式。所以,在学习安卓开机流程的时候,如果不关注loader层,完全可以忽略这里的bootloader采用的是哪种方式。这里说的有点绕,简单来说bootloader可以理解为一种过时的引导程序(古早且已经被时代淘汰了),也可以理解为引导程序的统称(比如,UEFI是一种bootloader、LK是一种bootloader,甚至可以说BIOS是一种bootloader),现在我们讲bootloader就是在第二种理解。

6.2 UEFI概述

UEFI: 统一可扩展固件接口(Unified Extensible Firmware Interface,UEFI),是一种详细描述全新类型接口的标准,是适用于电脑的标准固件接口,旨在代替BIOS(基本输入/输出系统)。此标准由UEFI联盟中的140多个技术公司共同创建,如intel、IBM、Microsoft、AMI等等。UEFI旨在提高软件互操作性和解决BIOS的局限性。它用来定义操作系统和系统固件之间的软件界面,作为BIOS的替代方案,其主要目的是为了提供一组在 OS 加载之前(启动前)在所有平台上一致的、正确指定的启动服务,被看做是有近20多年历史的 BIOS 的继任者,并且UEFI的security有所加强,在手机这个微型PC,或者说在android系统启动之中替代了bootloader和litter kernel,是新时代系统启动引导程序。

总的来说,UEFI是一个规范,被定义为一个软件接口,用于连接OS和遵守这个规范的平台固件platform firmware。

七、UEFI流程分析

7.1 XBL Loader Architecture

7.2 xbl代码运行流程

从图中可以看出,UEFI代码运行流程为: SEC(安全验证)--->PEI(EFI前期初始化)--->DXE(驱动执行环境)--->BDS(启动设备选择)--->UEFI Loader(操作系统加载前期)--->RT(Run Time)

7.2.1 SEC(安全验证)

SEC的汇编代码入口位于:BOOT.XF.4.1/boot_images/QcomPkg/XBLCore/ModuleEntryPoint.masm的_ModuleEntryPoint

7.2.1.1 ModuleEntryPoint.masm 代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <AsmMacroIoLibV8.h>
#include <Base.h>
#include <Library/PcdLib.h>
#include <AutoGen.h>

AREA |.text|,ALIGN=8,CODE,READONLY

IMPORT CEntryPoint// 导入CEntryPoint()函数
EXPORT _ModuleEntryPoint// 输出 _ModuleEntryPoint段
IMPORT ArmWriteCpacr// 导入InitStackCanary()函数 初始化栈
IMPORT ArmEnableInstructionCache//导入ArmDisableInterrupts()函数 禁用arm 中断
IMPORT ArmInvalidateInstructionCache// 导入ArmDisableCachesAndMmu()函数 禁用cache, mmu
IMPORT ArmInvalidateTlb
IMPORT ArmEnableDataCache
IMPORT InitStackCanary

IMPORT ArmDisableInterrupts
IMPORT ArmDisableCachesAndMmu
IMPORT ArmWriteCptr
IMPORT ArmDisableAlignmentCheck
IMPORT ArmWriteHcr
IMPORT ArmWriteVBar
IMPORT ArmInvalidateDataCache

// TODO - JDG
EXPORT _StackBase
EXPORT _StackSize
EXPORT CNTFRQ

_StackBase
dcq FixedPcdGet64(PcdPrePiStackBase)

_StackSize
dcq FixedPcdGet64(PcdPrePiStackSize)

CNTFRQ
dcq FixedPcdGet32(PcdArmArchTimerFreqInHz)

_ModuleEntryPoint
mov x0, #0
/* First ensure all interrupts are disabled */
bl ArmDisableInterrupts //关闭所有的中断

/* Ensure that the MMU and caches are off */
bl ArmDisableCachesAndMmu //关闭MMU和caches

/* Invalidate Instruction Cache and TLB */
bl ArmInvalidateInstructionCache //关闭TLB

bl ArmInvalidateTlb

/* Get current EL in x0 */
//获得当前运行的安全环境:EL1、EL2、EL3
EL1_OR_EL2_OR_EL3(x0)
// CurrentEL : 0xC = EL3; 8 = EL2; 4 = EL1
// This only selects between EL1 and EL2 and EL3, else we die.
// Provide the Macro with a safe temp xreg to use.
//mrs x0, CurrentEL
cmp x0, #0xC// 比较 x0寄存器是否为 0xc,如果是跳转到 标签3
beq %F3
cmp x0, #0x8// 比较 x0寄存器是否为 0x8,如果是跳转到 标签2
beq %F2
cmp x0, #0x4// 比较 x0寄存器是否为 0x4
bne . // We should never get here
// EL1 code starts here

1 beq _Start
2 beq _Start// 如果当前是 EL2,直接跳转到_Start

/* Do not trap any access to Floating Point and Advanced SIMD in EL3. */
/* Note this works only in EL3, x0 has current EL mode */
3 mov x0, #0
bl ArmWriteCptr// 如果当前是 EL3,直接跳转到ArmWriteCptr

//初始化ELX 安全环境
_SetupELx
mov x0, #0x30 /* RES1 */// x0 = 0x30
orr x0, x0, #(1 << 0) /* Non-secure bit */// 使能第0位为1
orr x0, x0, #(1 << 8) /* HVC enable */// 使能第8位为1
orr x0, x0, #(1 << 10) /* 64-bit EL2 */// 使能第10位为1
msr scr_el3, x0

// ...
// ...
// ...

//使能 Cache
_EnableCache
#ifdef PRE_SIL
LoadConstantToReg (FixedPcdGet32(PcdSkipEarlyCacheMaint), x0)
cmn x0, #0
b.ne _PrepareArguments
#endif
bl ArmInvalidateDataCache
bl ArmEnableInstructionCache
bl ArmEnableDataCache

//初始化栈
_PrepareArguments
/* Initialize Stack Canary */
bl InitStackCanary

//调用 CEntryPoint,传参 _StackBase、_StackSize
/* x0 = _StackBase and x1 = _StackSize */
ldr x0, _StackBase /* Stack base arg0 */
ldr x1, _StackSize /* Stack size arg1 */
bl CEntryPoint //进入C

dead
b dead /* We should never get here */

END

该汇编代码中,主要工作为:

  1. 关闭所有中断
  2. 关闭MMU和Caches
  3. 关闭TLB缓存表
  4. 获得当前运行的安全环境:EL1、EL2、EL3
  5. 初始化ELX 安全环境
  6. 使能 Cache
  7. 初始化栈
  8. 调用 CEntryPoint,传参 _StackBase、_StackSize

7.2.1.2 XBLCore\Sec.c 代码分析

前面汇编代码中主要目的是初始化C运行环境,初始化栈,以便可以调C代码运行。

SEC的C代码入口位于:BOOT.XF.4.1/boot_images/QcomPkg/XBLCore/Sec.c

主要就是main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
VOIDMain (INVOID  *StackBase,INUINTNStackSize){
EFI_STATUSStatus =EFI_NOT_READY;
UefiInfoBlkType*UefiInfoBlkPtr =NULL;
UINT64UefiFdBase = 0,SecHeapMemBase = 0;
UINTNSecHeapAslrVal = 0;
UINTNHobStackSize;
UINTNUARTLogBufferSize = 0;
UINTN *UARTLogBufferPtr;
MemRegionInfoDxeHeapMemInfo;
UINTNDxeHeapAslrVal = 0;

gReset =GetTimeInNanoSecond(GetPerformanceCounter());
//获得fdf文件所在的地址,fdf可以说是UEFI的配置文件
//在fdf文件中包含所有的inf文件所在路径,及相关的bmp图片资源路径,以及相关的cfg配置文件路径UefiFdBase =FixedPcdGet64(PcdEmbeddedFdBaseAddress);
SecHeapMemBase =UefiFdBase +SEC_HEAP_MEM_OFFSET;
HobStackSize =StackSize;

gStackBase =StackBase;

/* Start UART debug output */
UartInit(); //初始化串口

PrintUefiStartInfo(); //打印一些串口log
//这边加了log 来确定M7项目的UefiFdBase,StackBase,StackSize
//LIUQI: gStackBase = 0x000000005FF90000
//LIUQI: StackSize = 0x000000000003FD00
//LIUQI: UefiFdBase = 0x000000005FC00000
SerialPrint("LIUQI: gStackBase = 0x%016lx\\n", gStackBase);
SerialPrint("LIUQI: StackSize = 0x%016lx\\n", StackSize);
SerialPrint("LIUQI: UefiFdBase = 0x%016l\\nx", UefiFdBase);

/* Get nibble from random value to adjust SEC heap */
SecHeapAslrVal =AslrAdjustRNGVal(ASLR_HEAP_RNG_BITS);

InitHobList(SecHeapMemBase,
SEC_HEAP_MEM_SIZE - (SecHeapAslrVal*ASLR_HEAP_ALIGN),
UefiInfoBlkPtr);

// Need memory allocation working
InitializeCpuExceptionHandlers (NULL); //初始化CPU异常处理入口PrintTimerDelta(); //打印从开机到现在的时间差

/* Enable program flow prediction, if supported */// ArmEnableBranchPrediction ();/* Initialize Info Block */
UefiInfoBlkPtr =InitInfoBlock (UefiFdBase +UEFI_INFO_BLK_OFFSET);
UefiInfoBlkPtr->StackBase =StackBase;
UefiInfoBlkPtr->StackSize =StackSize;

//初始化 RAM 分区表InitRamPartitionTableLib ();

ValidateFdRegion(UefiFdBase);

/* Add the FVs to the hob list */
//初始化hoblist,有关hob可参考:<https://opqqrjk1dc.feishu.cn/wiki/wikcnabfL0g6PS10ctRWRfzX9bcBuildFvHob> (PcdGet64(PcdFlashFvMainBase),PcdGet64(PcdFlashFvMainSize));

/* Should be done after we have setup HOB for memory allocation */
//打印RAM 分区信息PrintRamPartitions ();

//初始化cacheStatus =EarlyCacheInit (UefiFdBase,UEFI_FD_SIZE);

/* Load and Parse platform cfg file, cache re-initialized per cfg file */
//加载并解析 uefiplat.cfg平台配置文件Status =LoadAndParsePlatformCfg();

//更新系统内存区相关信息
/* Add information from all other memory banks */
Status =UpdateSystemMemoryRegions();
/* Initialize cache for all memory regions */
Status =InitCacheWithMemoryRegions();

//初始化所有的共享库
/* All shared lib related initialization */
Status =InitSharedLibs();

//获得DXE Heap堆内存信息,uefiplat.cfg中定义的Status =GetMemRegionInfoByName("DXE Heap", &DxeHeapMemInfo);

/* Get nibble from random value to adjust DXE heap */
DxeHeapAslrVal =AslrAdjustRNGVal(ASLR_HEAP_RNG_BITS);

/* Re-initialize HOB to point to the DXE Heap in CFG */
ReInitHobList(DxeHeapMemInfo.MemBase,
DxeHeapMemInfo.MemSize - (DxeHeapAslrVal*ASLR_HEAP_ALIGN),
UefiInfoBlkPtr);

/* Update UART log buffer size if specififed in uefiplat else allocate 32KB of serial buffer in DXE Heap. */
//在uefiplat.cfg中定义,可以调整uart的buffer size,目前M7此项注释掉了
Status =GetConfigValue ("UARTLogBufferSize", (UINT32*) &UARTLogBufferSize);

// Allocate Runtime Pool
//初始化分页池缓存区UARTLogBufferPtr =AllocatePagesRuntimeServiceData(EFI_SIZE_TO_PAGES(UARTLogBufferSize));

/* Prevent Reinitializing of Serial Buffer */
SerialBufferReInitLock();

/* Now we have access to bigger pool, move pre-pi memory allocation pool to it */
ReInitPagePoolBuffer ();

InitSplitBoot (&UefiBootContinue, 0); // Doesn't return if succeeds

UefiBootContinue (0); // Just continue booting explicitly if returns or on failure
}

lib源码位置:BOOT.XF.4.1/boot_images/QcomPkg/SocPkg/DivarPkg/LAA/Core.dsc

至于dsc、inf、fdf之间的关系可以查看文章:UEFI学习

7.2.1.2.1 LoadAndParsePlatformCfg

主要工作流程如下:

  1. 初始化相关全局变量
  2. 加载并解析 uefiplat.cfg 配置文件 #define UEFIPLATCFG_FILE “uefiplat.cfg”,文件内容保存在 CfgBuffer 中,解析器描述符保存在MemParserDesc 中。在cfg文件中包含了内存相关的信息及系统相关的配置,BOOT.XF.4.1/boot_images/QcomPkg/SocPkg/DivarPkg/LAA/uefiplat.cfg
  3. 解析 uefiplat.cfg 中的 [Config] 区域
  4. 解析 uefiplat.cfg 中的 [MemoryMap] 区域
  5. 解析 uefiplat.cfg 中的 [RegisterMap] 区域,内容保存在mMemRegions中
  6. 解析 uefiplat.cfg 中的 [ConfigParameters] 区域,内容保存在 ConfigTable 中,ConfigTableEntryCount表示其内容的数量
  7. 解析 uefiplat.cfg 中的 [ChipIDConfig] 区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
EFI_STATUSEFIAPILoadAndParsePlatformCfg (VOID ){
EFI_STATUSStatus;
INTNMemParserDesc = 0;
UINT8 *CfgBuffer =NULL;
UINTNFileSize = 0;

//1.初始化相关全局变量InitGlobals();
//2. 加载并解析 uefiplat.cfg 配置文件Status =LoadFileFromFV0 (UEFIPLATCFG_FILE, &CfgBuffer, &FileSize);

MemParserDesc =OpenParser (CfgBuffer,FileSize,NULL);

/* Reset Global for RAM load */
mMemRegions =NULL;
mNumMemRegions = 0;
//3. 解析uefiplat.cfg 中的 [Config] 区域EnumKeyValues (MemParserDesc, (UINT8*)"Config",ProcessConfigTokens)
MemParserDesc =ReopenParser (MemParserDesc);
//4. 解析uefiplat.cfg 中的 [MemoryMap] 区域EnumCommaDelimSectionItems (MemParserDesc, (UINT8*)"MemoryMap",ProcessMemoryMapTokens)
GetCfgMemMapBounds();

//UpdateSystemMemoryRegions();
MemParserDesc =ReopenParser (MemParserDesc);
//5. 解析uefiplat.cfg 中çš��?[RegisterMap] 区域,å†EnumCommaDelimSectionItems (MemParserDesc, (UINT8*)"RegisterMap",ProcessMemoryMapTokens)
MemParserDesc =ReopenParser (MemParserDesc);

/* Reset for RAM load */
ConfigTable =NULL;
ConfigTableEntryCount = 0;
// 6. 解析uefiplat.cfg 中的 [ConfigParameters] 区域// 内容保存在 ConfigTable 中,ConfigTableEntryCount表示其内容的数量EnumKeyValues (MemParserDesc, (UINT8*)"ConfigParameters",ParseConfigParameters)
MemParserDesc =ReopenParser (MemParserDesc);

//7. 解析uefiplat.cfg 中的 [ChipIDConfig] 区域EnumCommaDelimSectionItems (MemParserDesc, (UINT8*)"ChipIDConfig",ParseChipIDConfigParameters) < 0

CloseParser(MemParserDesc);

returnEFI_SUCCESS;

ErrorExit:
DEBUG ((EFI_D_ERROR, "Failed LoadAndParsePlatformCfg\\r\\n"));
CpuDeadLoop();
returnStatus;
}

7.2.1.2.2 UefiBootContinue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void UefiBootContinue(void* arg){
//EFI_STATUS Status = EFI_NOT_READY;
UINT32 UefiStartTime= 0;
UefiInfoBlkType* UefiInfoBlkPtr= NULL;
UINTN HobStackSize= 0;

//...

//创建Stack、CPU Hob信息
BuildStackHob((EFI_PHYSICAL_ADDRESS)gStackBase, HobStackSize);
BuildCpuHob(PcdGet8(PcdPrePiCpuMemorySize), PcdGet8(PcdPrePiCpuIoSize));

/* Build HOB to pass up prodmode info for security applications */
gProdmodeInfo= JTAGDebugDisableFusesBlown();
BuildGuidDataHob(&gQcomProdmodeInfoGuid, &gProdmodeInfo, sizeof(BOOLEAN));

//Display 早期初始化
DisplayEarlyInfo();

AddMemRegionHobs();

/* Start perf here, after timer init, start at current tick value */
//开启耗费的时间统计,用于计算性能
InitPerf();

/*Add performance hob for pei performance data*/
AddPerformanceHob(gReset);

//...

/* Any non-critical initialization */
TargetLateInit();

/* Build memory allocation HOB for FV2 type
Need to remove for decompressed image */
BuildMemHobForFv(EFI_HOB_TYPE_FV2);

SetUefiCrashCookie();

SetUefiTargetReset();

/* Load the DXE Core and transfer control to it */
//åŠ è½½ä¸”å°†CPU交
LoadDxeCoreFromFv(NULL, 0);

/* DXE Core should always load and never return */
ASSERT(FALSE);
CpuDeadLoop();
}

DisplayEarlyInfo() 显示模块早期初始化

在 DisplayEarlyInfo 中主要工作就是解析 UEFI version 版本号,然后根据版本号加载对应的镜像,接着打印系统启动的路径。

主要流程为:

  1. 获取UefiPlatCfg.c中的UEFI CORE字段信息,PlatConfigFileName=”uefiplatLA.cfg”
  2. 获取固件版本号,定义在boot_images\QcomPkg\Sdm660Pkg\LA\Sdm660Pkg_Core.dsc中

gEfiMdeModulePkgTokenSpaceGuid.PcdFirmwareVersionString|L”4.2”

  1. 根据固件版本号查找对应的image镜像 Append Image version string component
  2. 打印UEFI固件版本号
  3. 选UEFI启动类型,判断顺序为 UFS > EMMC > SPI

7.2.2 DXE(驱动执行环境)

从上一章分析后,代码将进入DXE继续执行,函数入口为 LoadDxeCoreFromFv()

先查找 DXE_CORE的文件地址,接着调用 LoadDxeCoreFromFfsFile 加载 EntryPoint函数

之后CPU跳转到了ENTRY_POINT函数 DxeMain()中

代码路径:BOOT.XF.1.4/boot_images/MdeModulePkg/Core/Dxe/DxeMain.inf

这段代码比较复杂,基本看不懂

主要工作流程如下:

  1. 初始化CPU异常处理
  2. 初始化内存等,初始化UEFI代码基础环境
  3. 初始化DXE调度器
1
2
//函数最后会跳转bds
gBds->Entry (gBds);

7.2.3 BDS (启动设备选择)

代码路径:BOOT.XF.4.1/boot_images/QcomPkg/Drivers/BdsDxe/BdsDxe.inf

其主要工作为:

  1. 注册按键事件,按下按键后会回调到HotkeyEvent() 函数,最终调用到HotkeyCallback()函数中,解析其中的key scancode
  2. 平台BDS初始化

在其中会打印显示版本号,平台版本信息等等

调用LaunchDefaultBDSApps ()加载默认APP

调用SetupPlatformSecurity()初始化secureboot安全环境

挂载efisp分区

调用ReadAnyKey() 循环检测音量下键是否按下,从而更新对应的启动项

  1. 初始化所有 DriverOptionList 上的 驱动协议。
  2. 根据选择的启动方式,启动对应的的系统
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// BOOT.XF.4.1/boot_images/QcomPkg/Drivers/BdsDxe/BdsEntry.c
VOID EFIAPI BdsEntry(IN EFI_BDS_ARCH_PROTOCOL *This)
{
// 1. 注册按键事件,按下按键后会回调到HotkeyEvent() 函数,最终调用到HotkeyCallback()函数中,解析其中的key scancode
// Initialize hotkey service
InitializeHotkeyService ();
// 2. 平台BDS初始化,在其中会打印显示版本号,平台版本信息等等,调用LaunchDefaultBDSApps ()加载默认APP,调用SetupPlatformSecurity()初始化secureboot安全环境,挂载efisp分区,调用ReadAnyKey() 循环检测音量下键是否按下,从而更新对应的启动项
PlatformBdsInit ();

// 3. 初始化所有 DriverOptionList 上的 驱动协议。
// Set up the device list based on EFI 1.1 variables
// process Driver#### and Load the driver's in the driver option list
BdsLibLoadDrivers (&DriverOptionList);

// Check if we have the boot next option
mBootNext = BdsLibGetVariableAndSize ( L"BootNext",&gEfiGlobalVariableGuid,&BootNextSize );

// Setup some platform policy here
PlatformBdsPolicyBehavior (&DriverOptionList, &BootOptionList, BdsProcessCapsules, BdsMemoryTest);
// 4. 根据选择的启动方式,启动对应的的系统, 启动sheLl app
// BDS select the boot device to load OS
BdsBootDeviceSelect ();

// Only assert here since this is the right behavior, we should never
// return back to DxeCore.
ASSERT (FALSE);
return ;
}

https://hexoimg.oss-cn-shanghai.aliyuncs.com/blog/24/11/image_e44b724b1375db7cf39aec94e057cbad.png

7.2.4 RT(Run Time)

代码位于BOOT.XF.4.1/boot_images/MdeModulePkg/Core/RuntimeDxe/Runtime

看不懂,和我们关系也不大

7.3 如何创建 UEFI DXE_DRIVER 驱动及UEFI_APPLICATION 应用程序

本章可查看:第一个UEFI程序

此篇文章以hello world的Dxe driver以及Application为例,在串口输出打印log

7.4 UEFI XBL QcomChargerApp充电流程代码分析

系统开机过程中,初始化 BDS 时,会调用LaunchDefaultBDSApps()函数,在该函数中,会实现对上述两个默认app 的调用。

  1. 解析 uefiplat.cfg 中的 DefaultChargerApp,字符串保存在 DefaultApp 数组中。
  2. 加载 QcomCharger App 应用程序,传参gMainFvGuid 和 “DefaultChargerApp”
  3. 解析 uefiplat.cfg 中的 DefaultBDSBootApp,字符串保存在 DefaultApp 数组中
  4. 加载 DefaultBDSBootApp 应用程序,加载后不再返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
EFIAPILaunchDefaultBDSApps (VOID){

EFI_STATUSStatus =EFI_SUCCESS;
CHAR8DefaultApp[DEF_APP_STR_LEN];
UINTNSize =DEF_APP_STR_LEN;
CHAR8FileinFV[64] = {0};
//1.解析 uefiplat.cfg中的DefaultChargerApp,字符串保存在DefaultApp数组中Status =GetConfigString ("DefaultChargerApp",DefaultApp, &Size);
AsciiStrCpy (FileinFV,FILE_IN_FV_PREPEND);
AsciiStrCat (FileinFV,DefaultApp);

if (Status ==EFI_SUCCESS)
{
//2. 加载QcomChargerApp 应用程序,传参gMainFvGuid 和 “DefaultChargerApp”Status =LoadImageFromFV (FileinFV,NULL );
if (EFI_ERROR(Status))
DEBUG((EFI_D_ERROR, "Failed to launch default charger app, status: %r\\n",Status));
}

Size =DEF_APP_STR_LEN;
// 3. 解析 uefiplat.cfg中的DefaultBDSBootApp,字符串保存在DefaultApp数组中。Status =GetConfigString ("DefaultBDSBootApp",DefaultApp, &Size);
if (Status ==EFI_SUCCESS)
{
DisplayPOSTTime ();
//4. 加载DefaultBDSBootApp 应用程序,加载后不再返回LaunchAppFromGuidedFv(&gEfiAblFvNameGuid,DefaultApp,NULL);
//If we return from above function, considered a failure
ConfirmShutdownOnFailure();
}
else
{
DEBUG ((EFI_D_INFO, "[QcomBds] No default boot app specified\\n"));
}

returnStatus;
}

后续部分请查看文章QCOM UEFI QcomChargerApp充电流程代码分析

八、UEFI之ABL

BOOT.XF.4.1/boot_images/QcomPkg/QcomToolsPkg/MenuApp/Uefi_Menu.cfg中可以定制相关的option

在LaunchDefaultBDSApps函数中load uefiplat.cfg中定义的DefaultChargerApp和DefaultBDSBootApp

而LinuxLoader是属于UEFI Application,这个代码是在ap侧的abl中