[Boot]硬件上电到Bootloader
platform:rk3399
OS:Android 7.1
参考:
1.Younix 《Android启动流程分析》
概述
本系列简要介绍Android开机流程,用于整体了解Android的启动流程。进一步为开机优化,系统裁剪,启动时相关功能开发,bug调试提供理论支持。
系统上电
系统电源上电顺序:
VDD_LOG&VDD_CENTER --->
PLL_AVDD_0V9& PMU_VDD_0V9 --->
PLL_AVDD_1V8& PMU_VDD_1V8 --->
PMUIO1_VDD_1V8 --->
VCC_CPU_L&VCC_CPU_B --->
VDD_GPU --->
VCC_DDR&VCC_DDRC
上电原则:相同模块 低压先上,高压后上,相同电压同时上
不同的平台和板子上电顺序可能有差别,但是基本的上电原则都是差不多的。了解上电原则及上电顺序有助于排查板子无法上电开机时到底是哪一步,哪个模块出现了问题。
Loader 加载
第一次启动或者需要重新烧录固件的时候,会进入烧录下载模式,需要将Loader及其他image下载到板子中。烧录完成后一般会重启进入第一次固件启动流程。
正常开机启动,此时不需要烧录,系统会依次加载emmc(或ufs)中的image,逐步初始化,最后使整个系统跑起来。
第一次启动(烧录)
在这里插入图片描述FDL1即BL1(Bootloader 1),也就是第一阶段的Bootloader,也有的厂商称之为SPI。不同的厂商有不同的名称,但是功能都是类似的。
正常启动
在这里插入图片描述正常启动不需要下载image,只需要将image按流程从emmc拷贝到flash并逐步完成初始化和启动即可。
Bootloader启动
Bootloader有非常多种,现在使用得比较广泛的是UBoot,而不同的厂商都会对Uboot进行不同程度的定制,增加一些特有的功能或实现。所以不同厂商Bootloader启动流程会有一些不同,但是大致的流程和功能是差不多的,这里以RK3399的7.1.2版本的SDK做一个简要说明。
正常启动
在这里插入图片描述一般ROM Code是固化在芯片内部的,BL1是有厂商提供的bin文件,并不会开放源代码,所以从BL2开始。
从系统启动打印的信息可以找到BL2的入口如下,关键是_start函数。
//log
Load uboot, ReadLba = 2000 //加载uboot (BL2)
Load OK, addr=0x200000, size=0x873d4 //uboot 地址和大小 mainload结束
...
INFO: Entry point address = 0x200000 //UBoot 入口地址
//u-boot 符号表
4019: 0000000000200000 0 NOTYPE GLOBAL DEFAULT 1 _start
arch/arm/cpu/armv8/start.S
.globl _start
_start: //uboot入口
nop
b reset
....
reset:
#ifdef CONFIG_ROCKCHIP
/*
* check loader tag
*/
ldr x0, =__loader_tag
ldr w1, [x0]
ldr x0, =LoaderTagCheck
ldr w2, [x0]
cmp w1, w2
b.eq checkok
ret /* return to maskrom or miniloader */
...
master_cpu:
/* On the master CPU */
#endif /* CONFIG_ARMV8_MULTIENTRY */
bl _main //跳转到_main执行
arch/arm/lib/crt0_64.S
ENTRY(_main)
...
bl board_init_f /*调用board_init_f,硬件准备*/
....
b board_init_r /*调用board_init_r,最终完成环境初始化*/
/*无返回*/
ENDPROC(_main)
- board_init_f
common/board.c
void board_innit_f(ulong boot_flags)
{
...
//依次调用初始化函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
...
}
列举初始化函数如下:
init_fnc_t *init_sequence[] = {
arch_cpu_init, /* basic arch cpu dependent setup */
mark_bootstage,
#ifdef CONFIG_OF_CONTROL
fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
timer_init, /* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INIT
board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
print_cpuinfo, /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
NULL,
};
- board_init_r
common/board.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
...
serial_initialize();
...
power_init_board();
...
interrupt_init();
....
board_late_init(); //RK自定义初始化内容
....
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}
/* NOTREACHED - no way out of command loop except booting */
}
执行RK自己定义的board_late_init();函数
board/rockchip/rk33xx/rk33xx.c
int board_late_init(void)
{
debug("board_late_init\n");
board_init_adjust_env();
load_disk_partitions();
#ifdef CONFIG_RK_PWM_REMOTE
RemotectlInit();
#endif
debug("rkimage_prepare_fdt\n");
rkimage_prepare_fdt();
#ifdef CONFIG_RK_KEY
debug("key_init\n");
key_init();
#endif
#ifdef CONFIG_RK_POWER
debug("fixed_init\n");
fixed_regulator_init();
debug("pmic_init\n");
pmic_init(0); /*pmic init*/
#if defined(CONFIG_POWER_PWM_REGULATOR)
debug("pwm_regulator_init\n");
pwm_regulator_init();
#endif
rkclk_set_apll_high();
rkclk_dump_pll();
debug("fg_init\n");
fg_init(0); /*fuel gauge init*/
debug("charger init\n");
plat_charger_init(); /*charger init*/
#endif /* CONFIG_RK_POWER */
#ifdef CONFIG_OPTEE_CLIENT
load_attestation_key();
#endif
debug("idb init\n");
//TODO:set those buffers in a better way, and use malloc?
rkidb_setup_space(gd->arch.rk_global_buf_addr);
/* after setup space, get id block data first */
rkidb_get_idblk_data();
/* Secure boot check after idb data get */
SecureBootCheck();
if (rkidb_get_bootloader_ver() == 0) {
printf("\n#Boot ver: %s\n", bootloader_ver);
}
char tmp_buf[32];
/* rk sn size 30bytes, zero buff */
memset(tmp_buf, 0, 32);
if (rkidb_get_sn(tmp_buf)) {
setenv("fbt_sn#", tmp_buf);
}
debug("fbt preboot\n");
board_fbt_preboot(); //进入preboot
return 0;
}
board/rockchip/common/rkboot/fastboot.c
/*
* Determine if we should enter fastboot mode based on board specific
* key press or parameter left in memory from previous boot.
*/
void board_fbt_preboot(void)
{
//判断启动模式
....
//判断是否为uboot-charge并处理相关逻辑
....
rockchip_show_logo(); //显示logo
//各种启动模式分支...
.....
}
初始化完成之后进入main_loop
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
modem_init();
cli_init();
//执行preboot设置的command
run_preboot_environment_command();
s = bootdelay_process();
//安全的执行给定boot command
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
//执行command list
autoboot_command(s);
cli_loop();
}
common/cmd_bootrk.c
int do_bootrk(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
//启动linux内核
puts("bootrk: do_bootm_linux...\n");
do_bootm_linux(0, 0, NULL, &images);
...
}
总结:
-
进行软硬件初始化
-
执行指定的功能(cmd)
-
加载并启动kernel
charger mode
在Uboot启动并执行command的过程中可以实现非常多的功能,启动关机充电是非常常见的功能,该功能就是在uboot中实现的。
主要文件目录
include/configs/rk33plat.h //配置文件,相关功能宏
tools/resource_tool/resources //关机图片资源
kernel/arch/arm64/boot/dts/rockchip //相关设备dts配置
board/rockchip/common/rkboot/fastboot.c //低电量判断;启动模式判断
common/cmd_charge.c //do_charge 关机充电循环
drivers/power/power_rockchip.c //充电相关设备注册框架,状态查询及更新
drivers/power/charge/bq25700_charger.c //充电状态
drivers/power/fuel_gauge/fg_cw201x.c //电量状态,电池状态
drivers/power/mfd/fusb302.c //typec usb 设备识别
初始化
board/rockchip/rk33xx/rk33xx.c
int board_late_init(void)
{
...
//初始化RK定义的pmic框架
pmic_init(0); /*pmic init*/
//注册电量计
fg_init(0); /*fuel gauge init*/
debug("charger init\n");
//注册充电IC及fusb302 typec管理IC
plat_charger_init(); /*charger init*/
....
}
在board_late_init中注册电量计cw2015,充电IC bq25700以及typec管理IC fusb302。这三个设备注册成功后,如果此时充电器处于插上状态,则已经开始正常进行充电了。实现逻辑与kernel中的充电类似。
充电逻辑
common/cmd_charge.c
int do_charge(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
//一直进行关机充电,除非条件不满足退出
while (1) {
//step 1: 检查充电状态
exit_type = check_charging();
//step 2: 判断是否亮屏
if (IS_BRIGHT(g_state.brightness))
//step 3: 检查是否有按键按下
key_state = power_key_pressed();
//step 4:显示关机动画
if (uboot_brightness)
update_image();
}
exit:
//正常启动
if (exit_type == EXIT_BOOT) {
printf("booting...\n");
return 1;
} else if (exit_type == EXIT_SHUTDOWN) {
shut_down(); //关机
}
}
上面只是RK提供的一套业务逻辑,我们可以根据自己的需求实现自己的关机充电业务逻辑。
recovery
除了正常的启动,我们还可以让系统进入recovery模式。
简介
Recovery 模式指的是一种可以对安卓内部的数据或系统进行修改的模式(类似于windows PE或DOS).在这个模式下我们可以刷入新的安卓系统,或者对已有的系统进行备份升级,恢复出厂设置等操作。
Bootloader根据某些判定条件决定是否进入recovery模式。Recovery模式会装载recovery分区,该分区包含了recovery.img。recovery.img包含了标准内核(和boot.img中的相同)以及recovery根文件系统。
组成及通信
Android recovery分为三个部分两个接口,recovery的工作需要整个软件平台的配合,从架构的角度看,有三个部分:
- Main System:用boot.img启动的linux系统,Android的正常工作模式
- recovery:用recovery.ing启动的linux系统,主要运行recovery程序
- Bootloader:除了加载,启动系统,还会通过读取flash的misc分区获得来自main system和recovery的消息,并以此决定作出何种操作。
两个通信接口:
- /cache/recovery: command、log、intent
- BCB(Bootloader Control Block): misc分区
示意图如下:
在这里插入图片描述Main System 如何进入 Recovery 模式:
当我们在 Main System 使用 update.zip 包进行升级时,系统会重启并进入recovery模式。在系统重启前,我们可以看到Main System定会向recovery域写入boot-recovery(粉红色线),用来告知bootloader重启后进入Rcovery模式。这一步是必须的,至于Main System是否会向recovery域写入值我们在源码中不能肯定这一点。即便如此,重启进入Recovery模式后,Bootloader会从/cache/recovery/command中读取值并放入到BCB的recovery域。而Main System在重启之前肯定会向/cache/recovery/command中写入Recovery将要进行的操作命令。
下图是Main System进入Recovery模式调用接口的流程图:
在这里插入图片描述
1.installPackage:RecoverySystem的接口,完成升级包路径转换,并调用bootCommand。
2.bootCommand:RecoverySystem的接口,将命令写入/cache/recovery/command,并调用pm.reboot.
3.Pm.reboot:PowerManager的接口,重启并进入Recovery模式。
进入recovery
在这里插入图片描述void board_fbt_preboot(void)
{
...
//获取reboot_type
frt = board_fbt_get_reboot_type();
...
//进入recovery相应操作
if (frt == FASTBOOT_REBOOT_RECOVERY) {
printf("\n%s: starting recovery img because of reboot flag\n", __func__);
#if 1
board_fbt_set_recovery_for_hrr_32();
#endif
board_fbt_run_recovery();
} else if (frt == FASTBOOT_REBOOT_RECOVERY_WIPE_DATA) {
printf("\n%s: starting recovery img to wipe data "
"because of reboot flag\n", __func__);
/* we've not initialized most of our state so don't
* save env in this case
*/
board_fbt_run_recovery_wipe_data();
}
...