随着android智能手机的普及,硬件性能的逐渐增强,其承载的功能也逐渐增多,Bootloader作为智能手机系统的组成部分之一,作用也越来越重要;
bootloader是操作系统运行前,运行的一段或几段小程序,目的就是为了初始化硬件,建立内存空间映射图,为OS的运行准备好正确的环境;当然,bootloader还承载着下载软件版本的功能;
高通8952平台,是一款8核 A53平台的soc,采用双4核设计,集成GPU,modem,GPS,WLAN/BT/FM等,其子系统框架如图一:
图一 8952 系统架构
各子系统启动加载地址如图二:
调用堆如图三:
2.2 启动流程
从以上可以看出,8952平台,有多子系统,研究bootloader,我们需要重点关注APPS和RPM,早期的高通平台启动加载采用RPM来引导,现在的高通平台改为了APPS的core 0;其启动大概顺序为:APPS PBL--->sbl1-->tz-->hyp-->rpm,lk-->kernel,详细的启动流程如下图五:
详细流程:
1. 系统上电,服务cpu;
2. Core 0 加载执行cpu内置的PBL,PBL执行如下工作:
a. 从emmc加载和认证sbl1 的segment#1到L2 (TCM);
b. 从emmc加载和认证sbl1 的segment#2到rpm的code RAM,然后跳转到sbl1执行sbl1代码;
3. sbl1#1 初始化DDR,并且执行如下工作:
a. 从emmc加载和认证QSEE/TZ 镜像到DDR;
b. 从emmc加载和认证QHEE镜像到DDR;
c. 从emmc加载和认证rpm镜像到rpm code ram;
d. 从emmc加载和认证lk镜像到DDR;
4. sbl1跳转到QSEE/TZ,然后TZ 建立一个安全运行环境,配置xpu等:
a. sbl1运行在AArch32 模式,QSEE/TZ运行在AArch64模式,因此sbl1在跳转到TZ前,需要设置boot re-mapper(设置resetaddress为AArch64 QSEE entry),写RMR_EL3寄存器以设置AArch64 mode,再触发core 温重启,然后,tz就处于了AArch64运行模式了;
5. QSEE/TZ 跳转到QHEE,QHEE负责VMM ,SMMU,xPu配置;
6. QHEE,触发rpm 复位并启动;
7. QHEE 跳转到lk执行初始化系统,为内核执行做准备;
a. lk运行在AArch32模式;
b. QHEE通过解析LK的ELF 文件头,得到其运行于32位模式的配置,然后转换到32为模式,并启动LK;
8. LK加载和认证kernel,然后通过一个SCMcall跳转到64位的模式运行内核;
9. HLOS kernel 通过PIL加载MBA到DDR;
10. HLOS kernel 复位modem DSP;
11. Modem PBL 然后继续执行启动进程;
12. HLOS kernel 通过PIL加载AMSS modemimage 到DDR;
13. Modem PBL 认证 MBA 并且跳转到MBA执行;
14. HLOS kernel 通过PIL 加载 Prontoimage 到DDR;
15. HLOS kernel 复位Pronto 并使之运行;
16. HLOS kernel 通过PIL加载LPASS image到DDR;
17. HLOS kernel 复位LPASS并使之运行;
高通平台目前采用GPT分区架构,GPT分区架构取代的原有的MBR 分区架构,以支持更大的分区,可以最大支持到9.7ZB的分区,下图六是GPT 分区结构:
图六
从图六可以看到,GPT保留了对MBR的兼容,第一个LBA0就MBR,接着LBA1 是 primary GPT Header,然后LBA2到LBA34共32个sector,每个sector为512个字节,里面包括4个partition entry,每个partiton entry 为128个字节,32 sectors总共包含了128个partition;为了预防分区表被破坏后,无法正常加载分区,在存储器的尾部最后34 sectors保存有分区表的备份,当Primary分区表被修改后,会导致其CRC32 checksum无效,此时会去用备份的分区表覆盖它;下图七和图八分别为Partition header 和Partition entry 数据结构;
图七
图八
cpu上电后,跑的第一段程序是APP PBL,此段代码固化到了cpu内部,不开源,本文不研究讨论,pbl负责从emmc加载sbl1到L2,sbl1然后在负责初始化DDR,并加载认证tz,hyp,rpm,lk,sbl1的代码调用流程如下:
sbl1_mc.c |
sbl1.s: SBL1_ENTYRY |
Boot_config_process_bl |
SBL1-->QSEE |
SBL1àQHEE |
SBL1àRPM |
SBL1àLK |
boot_save_reset_register_log |
boot_flash_init |
boot_config_data_table_init |
sbl1_hw_platform_pre_ddr |
Sbl1_ddr_set_params |
sbl1_cleanse_security_regions |
sbl1_ddr_init |
Sbl1_post_ddr_init |
boot_dload_check |
boot_secure_watchdog_init |
sbl1_load_sec_partition |
boot_cache_set_memory_barrier |
boot_smem_debug_init |
boot_smsm_store_pon_status |
boot_populate_ram_partition_table |
sbl1_hw_platform_smem |
boot_share_flash_data |
Sbl1_tlmn_init |
Sbl1_efs_handle_cookies |
图九
CDT 全称Config_data_table,里面提供了platform和设备相关的配置数据,比如platformID ,和DDR 硬件参数等;目前支持三种方式,将该信息导入设备,分别为:
A. 烧写CDT data到EEPROM;
B. 将CDT data 转换为boot_cdt_array.c文件,然后,编译到sbl1;
C. 烧写CDT data 到emmc的BOOT1 partition;
目前,我们默认采用B方案,如果有使能USES_CDT_ON_EEPROMfeature 则会去查找EEPROM里面是否有,有则用其更新,如果没有使能USES_CDT_ON_EEPROM feature,则会查找emmc ,同样有则用其更新,下图十为CDT的基本数据结构;
图十
从上图上,我们可以看到其结构分为三大部分,分别为,header ,metadata, Config data;
其中,CDB0为platform info, CDB1为DDR parameters;
下面介绍CDT加载流程:
A. SBL1 检测emmc的CDT分区是否有CDTdata,如果有,则加载,否则执行B;
B. SBL1 加载自身集成的默认的CDTtable (config_data_table);
C. SBL1 将platform 信息,通过SMEM传递给LK;
SBL1-SMEM_HW_SW_BUILD_ID
LK- SMEM_BOARD_INFO_LOCATION
D. LK 获取platform信息,加载DTheader ,搜索匹配的DT entry;
E. LK 将正确的 DT entry地址传递给kernel;
CDT参数数据位于一个xml文件,比如jedec_lpddr3_single_channel.xml,我们选取其中的cdb0来看:
0x03, 0x08, 0x01, 0x00, 0x00, 0x00, end
以上是8952的对应xml文件,我们再来看CDB0的数据结构,
CDB0: platform info 如下结构体表现:
typedef PACKED struct
{
uint8nVersion;
uint8 nPlatform; //这个是平台id,用于高通不同平台类型,我们不能去修改。
uint8 nHWVersionMajor; //硬件版本号暂时没有使用,默认为0
uint8nHWVersionMinor;
uint8 nSubtype; // 默认为0没有用。我们可以用它来做项目分
uint8nNumKVPS;
PlatformInfoKVPSCDTTypeaKVPS[];
} PlatformInfoCDTType;
最后我们再来看8952的DT header定义:
其中,board-id中的8,0 分别对应,Platform type id 和platform subtype,正好跟CDT xml文件里面定义的CDB0值对应上;
LK全称little Kernel,其主要功能为:
A. 硬件初始化,包括建立vector table,MMU,cache,初始化peripherals,storage,USB,crypto等等;
B. 加载boot.img;
C. 支持烧写和进入recovery
设备树,是一个用来描述硬件属性的数据结构,其表现形式为dts文件,编译后,为dtb文件;
5.2.1 dts文件有如下特点:
A. device tree 是一种简单的由节点和属性组成的树形数据结构;
B. 属性为键值对,并且每个节点都有可能同时包括属性和子节点;
C. Dts文件的格式风格接近C语言;
5.2.2 Dtb文件为dts文件经过dtc(device tree compliler)编译生成的二进制文件,其被追加在boot.img的后面,如下图:
图十一
5.2.3 dt table和dt_entry结构
structdt_table {
uint32_t magic;
uint32_t version;
uint32_t num_entries;
};
structdt_entry {
uint32_t platform_id; → Platform ID/ChipsetID
uint32_t variant_id; → Hardware variants(MTP, CDP, etc.)
uint32_t board_hw_subtype; →Distinguishesbetween subtypes like pmic variants, fusion/standalone etc.
uint32_t soc_rev;→ SOC revision
uint32_t pmic_rev[4];→ PMIC revision
uint32_t offset;
uint32_t size;
};
5.3.1代码开始点为arch/arm/crt0.S: _start
1. Set up CPU
2. Call __cpu_early_init() ifnecessary (platform-specific initialization sequence)
3. platform/
4. Relocate if necessary
5. Set up stack
6. Call kmain()
5.3.2 kmain作为C入口调用流程图如下:
图十二
其中,kmain初始化过程:
n 线程初始化;//initthreading system
n arch_early_init();//指令集架构初始化
Disable cache
setIRQ vector base
InitMMU,turn on MMU
Enablecache
n platform_early_init();//芯片平台相关的初始化;
Init board-Platform ,target,baseband detect
(
高通平台的区分:
qcom,msm-id =
qcom,board-id =
qcom,pmic-id =
LkscanDTS for best match entry:
1.Platform ID =contain MSM ID and foundry ID
MSM ID:Bits0-15
Foundry ID:Bit16-23 ----exactmatch ,if not found choose Foudry id (0x0)
2.Platform subtype -Subtype for theboard-Platform
3.Platformtype,hardware id-CDP/MTP board type
4.PMIC model
PMIC major:Bit23-16
PMICminor:Bit15-8
PMIC model:Bit7-0
PS:Bestmatch IDS =< detect Soc rev (read from SMEM)
1.Soc version
2.PMIC0major+minor
3.PMIC1major+minor
4.PMIC2major+minor
5.PMIC3major+minor
)
init clocks
init interrupts
init timer
initscm
n target_early_init();//项目平台的初始化;
uart_dm_init-Onlyif WITH_DEBUG_UART
n initheap,timer等等;
n bootstrap2//新建线程,用于第二阶段初始化;
bootstraps2过程:
n arch_init(); //NULL
n platform_init();//NULL
n target_init();
l initspmi//system power manager interface
l init keypad//检测按键值
l set driver strength and pull configs for SDC pins(we havetransitions to SDHCI)
l mmc_init() ;init the sd host control/mmc card;identify the mmccard;set the clock ,etc
l partition_read_table();//Read the partition table from the eMMC card
l shutdown_detect();
l clock_ce_enable();
l qseecom_init();
l qseecom_tz_init();
l load_sec_app();//加载keymaster app
l rpmb_init();//emmc的一个安全模块区域,用来保存重要信息
l rpm_smd_init();
n yl_init_params();//inityulongparam
l yl_param_pull//读取ylparams到ram
l yl_platform_init//Project0初始化,写入root等字符到宇龙参数分区,更新yulong参数分区版本号,配置GPIO等
l yl_check_board_info();//硬件版本识别,先读param的版本,再去读电阻组合,如果两者不一样,则以电阻组合为准,写入param
PS:A8定义了,Tx开头的不去检测硬件版本号,Px开头则检测
l yl_correct_comm_runmode()// 设置通信的运行模式
l yl_load_download_flags();//加载下载标记
l yl_load_kernel_params();//加载cmdline
n target_set_warm_reset();//set warm reset on rooted devices
n app/init.c -apps_init();//搜索APP_START
l init apps that are defined using APP_START and APP_END macros,可搜索APP_START
example:aboot_init() ,shell_init(),clocktests,stringtests,tests,arecalled;
l Run the app in a separate thread if it has .entry section,//shell_entry,搜索APP_START
详细的流程见附件:
5.4.1 fastboot协议是一种通过USB连接与bootloaders通讯的机制。它被设计的非常容易实现,能够用于多种设备和运行Linux、Windows或者OSX的主机;
5.4.2 基本需求
* 两个端点,一个输入端,一个输出端。
* 对于全速(full-speed)USB,最大包尺寸必须是64个字节;
对于高速(hign-speed)USB,最大包尺寸必须是512个字节。
* 协议完全是主机驱动(SSW注:相对于设备客户端而言),并且同步的。
5.4.3 传输和组帧(Transportand Framing)
步骤1、主机发送命令(Command)。
一个命令是一个ASCII字符串,并且只能包含在不大于64个字节的单个包内。
步骤2、客户端(SSW注:设备)用一个单个的不大于64个字节的包响应。
响应包开头四个字节是“OKAY”、“FAIL”、“DATA”或者“INFO”。
响应包剩余的字节可以包含ASCII格式的说明性信息。
a、INFO ->剩余的60个字节包含说明信息(提供进度或者诊断信息)。
这些说明信息应该被显示,然后重复步骤2。
b、FAIL ->指示请求的命令失败。
剩余的60个字节可以提供一个文本形式的失败原因呈现给用户。交互停止。
c、OKAY ->指示请求的命令成功完成。跳转到步骤5。
d、DATA ->请求的命令已经为数据阶段做好准备。
一个数据响应包是12个字节长,组织形式为DATA00000000,
其中8位十六进制的数字表示所传输数据的总大小。
步骤3、数据阶段。
根据命令的不同,主机或者客户端将发送指定大小的数据。
比指定长度短的包总是可接受的,零长度的包将被忽略。
这个阶段会一直持续,直到客户端已经发送或接收了上面数据响应包中指定大小的字节数为止。
步骤4、客户端用一个单个的不大于64个字节的包响应。
响应包开头四个字节是“OKEY”、“FAIL”或者“INFO”,类似于步骤2。
a、INFO ->显示剩余的60个字节,然后返回到步骤4。
b、FAIL ->显示剩余的60个字节(如果有的话)作为失败原因,命令失败,停止交互。
c、OKAY ->成功。跳转到步骤5。
步骤5、命令执行成功。
结束交互。
5.4.4 示例
Host: "download:00001234" 请求发送0x1234大小的字节数据
Client: "DATA00001234" 准备好接收数据
Host: < 0x1234 bytes >发送数据
Client: "OKAY" 数据接收成功完成
Host: "flash:bootloader" 请求刷新数据到bootloader
Client: "INFOerasing flash" 指示状态/进度为“擦除flash”
"INFOwriting flash" 指示状态/进度为“写入flash”
"OKAY" 刷新成功完成
下面看图十三示例分析:
在cmd中执行fastboot flash boot boot.img后,cmd窗口和串口日志窗口输出执行结果;
从日志可以看出,烧写boot.img,共有四条fastboot控制指令组成(不包括发送数据包过程);先获取boot分区是否存在,再获取最大下载的单包数据长度,再下发下载数据到DDR的指令,最后执行烧写到boot分区;
图十三
当LK出现crash 后,我们按照如下步骤进行分析:
1.生成含有C代码和汇编的lk混合调试文件,命令如下:
/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-objdump-S out/target/product/CPA8_931/obj/EMMC_BOOTLOADER_OBJ/build-msm8952/lk -g >lk.asm
2.根据lkcrash时的串口打印的PC指针内容,直接可以在lk.asm文件里面定位到lk是执行哪行代码时出现crash的;