深入,并且广泛
-沉默犀牛
此篇博客原博客来自freebuf,原作者SetRet。原文链接:https://www.freebuf.com/news/135084.html
写在前面的话
写这篇文章之前,我只好假定你所知道的跟我一样浅薄(针对本文这一方面),所以如果你看到一些实在是太小儿科的内容,请你多加担待,这确实就是我目前的水平,谢谢。
这里就开始啦!
上一篇博客分析了bootstrap2都做了些什么,内容并不多,我们大概总结一下:
先后进行了:初始化了 SPMI(system power management interface) 系统电源管理结构的控制器,检测音量上下按键的状态并记录,emmc,读分区表,检测pwr_key按键时间、震动,初始化加密引擎,然后就遍历了.apps段,运行在这个字段中的init函数,接下来就进入到了aboot_init函数了。(很多平台在这个阶段只有aboot这个app)
大致描述aboot_init
aboot_init中的代码比较多,分为四个部分来介绍:
1.init部分
2.检测启动方式
3.fastboot部分模式启动
4.非fastboot模式启动
init部分
1.获取分页大小,并保存到全局变量 page_size 和 page_mask 中
/* Setup page size information for nv storage */
if (target_is_emmc_boot())
{
page_size = mmc_page_size();
page_mask = page_size - 1;
mmc_blocksize = mmc_get_device_blocksize();
mmc_blocksize_mask = mmc_blocksize - 1;
}
else
{
page_size = flash_page_size();
page_mask = page_size - 1;
}
ASSERT((MEMBASE + MEMSIZE) > MEMBASE);
2.从 emmc 中的 aboot 分区或 deviceinfo 分区获取 device
read_device_info(&device);
这里的 device 是一个全局变量,其结构如下:
typedef struct device_info device_info;
#define DEVICE_MAGIC "ANDROID-BOOT!"
#define DEVICE_MAGIC_SIZE 13
#define MAX_PANEL_ID_LEN 64
#define MAX_VERSION_LEN 64
struct device_info
{
unsigned char magic[DEVICE_MAGIC_SIZE];
bool is_unlocked;
bool is_tampered;
bool is_verified;
bool charger_screen_enabled;
char display_panel[MAX_PANEL_ID_LEN];
char bootloader_version[MAX_VERSION_LEN];
char radio_version[MAX_VERSION_LEN];
};
static device_info device = {DEVICE_MAGIC, 0, 0, 0, 0, {0}, {0},{0}};
其中保存的信息在后期经常会用到,比如 device_info.is_unlocked 就是 bootloader 是否解锁的标志位。
3.从 emmc 中的 config 分区或 frq 分区获取 is_allow_unlock 标志位
read_allow_oem_unlock(&device);
一般都为允许,使用加密手段来限制解锁
4.初始化开始屏幕信息和全局的屏幕信息缓存 display_panel_buf,大小为 128
#if DISPLAY_SPLASH_SCREEN
#if NO_ALARM_DISPLAY
if (!check_alarm_boot()) {
#endif
dprintf(SPEW, "Display Init: Start\n");
#if DISPLAY_HDMI_PRIMARY
if (!strlen(device.display_panel))
strlcpy(device.display_panel, DISPLAY_PANEL_HDMI,
sizeof(device.display_panel));
#endif
#if ENABLE_WBC
/* Wait if the display shutdown is in progress */
while(pm_app_display_shutdown_in_prgs());
if (!pm_appsbl_display_init_done())
target_display_init(device.display_panel);
else
display_image_on_screen();
#else
target_display_init(device.display_panel);
#endif
dprintf(SPEW, "Display Init: Done\n");
#if NO_ALARM_DISPLAY
}
#endif
#endif
5.获取序列号并保存在全局变量 sn_buf 中
target_serialno((unsigned char *) sn_buf);
dprintf(SPEW,"serial number: %s\n",sn_buf);
序列号就存储在 target_sdc_init 中初始化的 dev 变量中,还记得target_sdc_init这个函数吗?
在bootstrap2 -> target_init -> target_sdc_init //初始化emmc
2.检测启动方式
这部分的代码只是通过检测关机的方式,按键的状态来设定确定进入哪种模式的启动方式,但是不同的机型,对应的按键组合并不相同
/*
* Check power off reason if user force reset,
* if yes phone will do normal boot.
*/
if (is_user_force_reset())
goto normal_boot;
/* Check if we should do something other than booting up */
if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))
{
dprintf(ALWAYS,"dload mode key sequence detected\n");
reboot_device(EMERGENCY_DLOAD);
dprintf(CRITICAL,"Failed to reboot into dload mode\n");
boot_into_fastboot = true;
}
if (!boot_into_fastboot)
{
if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
boot_into_recovery = 1;
if (!boot_into_recovery &&
(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))
boot_into_fastboot = true;
}
#if NO_KEYPAD_DRIVER
if (fastboot_trigger())
boot_into_fastboot = true;
#endif
#if USE_PON_REBOOT_REG
reboot_mode = check_hard_reboot_mode();
#else
reboot_mode = check_reboot_mode();
#endif
if (reboot_mode == RECOVERY_MODE)
{
boot_into_recovery = 1;
}
else if(reboot_mode == FASTBOOT_MODE)
{
boot_into_fastboot = true;
}
else if(reboot_mode == ALARM_BOOT)
{
boot_reason_alarm = true;
}
#if VERIFIED_BOOT
else if (VB_M <= target_get_vb_version())
{
if (reboot_mode == DM_VERITY_ENFORCING)
{
device.verity_mode = 1;
write_device_info(&device);
}
#if ENABLE_VB_ATTEST
else if (reboot_mode == DM_VERITY_EIO)
#else
else if (reboot_mode == DM_VERITY_LOGGING)
#endif
{
device.verity_mode = 0;
write_device_info(&device);
}
else if (reboot_mode == DM_VERITY_KEYSCLEAR)
{
if(send_delete_keys_to_tz())
ASSERT(0);
}
}
#endif
常用的就只有 普通模式/recovery 模式/fastboot 模式 3 种,也是需要重点分析的 3 种
非 fastboot 模式启动就是 recovery 模式 或者 普通模式启动,这两者所使用的是同一套加载流程,所以可以归类为同一类。
3.fastboot部分模式启动
fastboot 模式启动fastboot 模式是 android 定义的一套通信协议,可以指定参数写入 emmc 分区的方法,通俗的说就是刷机的接口
fastboot:
aboot_fastboot_register_commands(); //fastboot指令注册
partition_dump();
/*设置 usb 监听的线程,并且启动了解析指令的线程*/
fastboot_init(target_get_scratch_address(), target_get_max_flash_size());
fastboot 本身就是 lk 的一部分,负责对外的一个接口而已,并不是一个单独的系统,是依赖于 lk 存在的。fastboot 可以分为以下几个方面:
- fastboot 指令注册
- fastboot 监听启动
- fastboot 指令解析
按照这个分类来一步步分析整个 fastboot 的框架
fastboot 指令注册
void aboot_fastboot_register_commands(void)
{
int i;
struct fastboot_cmd_desc cmd_list[] = {
/* By default the enabled list is empty. */
{"", NULL},
/* move commands enclosed within the below ifndef to here
* if they need to be enabled in user build.
*/
#ifndef DISABLE_FASTBOOT_CMDS
/* Register the following commands only for non-user builds */
{"flash:", cmd_flash},
{"erase:", cmd_erase},
{"boot", cmd_boot},
{"continue", cmd_continue},
{"reboot", cmd_reboot},
{"reboot-bootloader", cmd_reboot_bootloader},
{"oem unlock", cmd_oem_unlock},
{"oem unlock-go", cmd_oem_unlock_go},
{"oem lock", cmd_oem_lock},
{"oem verified", cmd_oem_verified},
{"oem device-info", cmd_oem_devinfo},
{"preflash", cmd_preflash},
{"oem enable-charger-screen", cmd_oem_enable_charger_screen},
{"oem disable-charger-screen", cmd_oem_disable_charger_screen},
{"oem select-display-panel", cmd_oem_select_display_panel},
#if UNITTEST_FW_SUPPORT
{"oem run-tests", cmd_oem_runtests},
#endif
#endif
};
int fastboot_cmds_count = sizeof(cmd_list)/sizeof(cmd_list[0]);
for (i = 1; i < fastboot_cmds_count; i++)
fastboot_register(cmd_list[i].name,cmd_list[i].cb);
/*********************************************************************************************************/
/* publish variables and their values */
fastboot_publish("product", TARGET(BOARD));
fastboot_publish("kernel", "lk");
fastboot_publish("serialno", sn_buf);
/*
* partition info is supported only for emmc partitions
* Calling this for NAND prints some error messages which
* is harmless but misleading. Avoid calling this for NAND
* devices.
*/
if (target_is_emmc_boot())
publish_getvar_partition_info(part_info, ARRAY_SIZE(part_info));
/* Max download size supported */
snprintf(max_download_size, MAX_RSP_SIZE, "\t0x%x",
target_get_max_flash_size());
fastboot_publish("max-download-size", (const char *) max_download_size);
/* Is the charger screen check enabled */
snprintf(charger_screen_enabled, MAX_RSP_SIZE, "%d",
device.charger_screen_enabled);
fastboot_publish("charger-screen-enabled",
(const char *) charger_screen_enabled);
snprintf(panel_display_mode, MAX_RSP_SIZE, "%s",
device.display_panel);
fastboot_publish("display-panel",
(const char *) panel_display_mode);
fastboot_publish("version-bootloader", (const char *) device.bootloader_version);
fastboot_publish("version-baseband", (const char *) device.radio_version);
}
这个函数可以分为两部分:
1.fastboot 指令注册,fastboot 的指令使用了一个 struct fastboot_cmd_desc 类型的局部数组来保存,这个结构包含了 fastboot 的 指令字符串 和 处理函数。有了这个数组后,就可以使用 fastboot_register 函数将指令注册到全局链表 cmd_list 中,这样fastboot 所有的指令都可以通过遍历链表而得到。
2.fastboot 数据注册,fastboot 模式还保存了一些主要的设备和厂商信息,这些信息都统一由 fastboot_publish 来注册,注册的信息和指令一样,存储在一个全局链表 varlist 中,这个结构比较简单,只有名称和对应的数据,和指令一样的道理,通过遍历 varlist 就可以找到全部的数据。
通过上面的两个步骤后, fastboot 的所有 指令 和 数据 就注册完成了,接下来的需要启动 fastboot 对 USB 设备的监听,以接送 fastboot 命令
fastboot 监听启动
fastboot_init 的代码可以分为以下 3 个流程:
-
usb_if(usb controller interface) 初始化
-
usb_if 绑定
-
fastboot 线程启动
usb_if(usb controller interface) 初始化
usb_if 是一个全局变量,这个阶段就是为这个结构体赋予一些需要的值,方便后面使用
- 初始化了两个全局变量
download_base
和download_size
, 这块空间是 fastboot 刷入系统时的缓冲区 - 获取序列号并赋值给
surf_udc_device
- 初始化
usb_if
中的各控制函数,
usb_if 绑定
usb_if 初始化完成后,需要设置通信的渠道,和响应命令的方法
创建了两个事件, usb_online
和 txn_done
:
usb_online
是响应 usb 上线的事件,然后等待处理命令
txn_done
则是在请求 usb 操作时等待返回的信号,由 req_complete 函数发送此信号
fastboot 线程启动
这一部分比较简单,主要的功能就是启动一个线程等待 fastboot 的指令传入,并且启动 udc
- 新注册了以下两条指令
getvar
: 和download
:, 以及一条数据version
- 创建并启动
fastboot
线程,线程的功能就是等待usb_online
事件,然后解析fastboot
指令 - 开启
udc
fastboot 指令解析
申请并清零缓冲区,然后使用 usb_read 接口获取 usb 数据。
遍历 cmdlist, 比对 指令 调用对应指令的处理函数