🗒️高通Android启动代码流程分析(SBL->ABL)
2023-8-18
| 2024-11-22
字数 7129阅读时长 18 分钟
type
status
date
slug
summary
tags
category
icon
password
URL

一、前言

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

二、芯片的冷启动

Cold boot flow: 冷启动
notion image
可以看出,在设备上电后,先跑的是 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越大等级越高,执行特权越高
  1. 执行在EL0,称为非特权执行
  1. EL2没有Secure state,只有Non-secure state
  1. EL3 只有Secure state,支持EL0/EL1的Secure 和Non-secure之间的切换
  1. EL0 & EL1 必须要实现,EL2/EL3则是可选实现
  1. 当接收到一个异常时,异常级别只能调高或保持;
  1. 当从异常返回时,异常级别只能调低或保持
  1. 在接收到异常将要切换或保持的异常级别称为目标异常级别
  1. 每个异常级别本身有一个默认固定的目标异常级别,还可以通过寄存器设置目标异常级别,目标异常级别不能为EL0
  1. 当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路径下,编译方式和之前也不同了
代码位于:${BP_ROOT}/BOOT.XF.1.4/boot_images/
notion image

四、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

4.1 sbl1_main_ctl函数

主要工作:一些重要外设的初始化,如RAM的初始化,为下一步程序的运行做准备
sbl1_main_ctl中使用了sbl1_boot_logger_init进行了logger的初始化,这其中就会调用boot_log_init_uart,将串口进行初始化
在这个阶段如果需要加log,有下面几种方式:
  1. boot_log_dump(boot_log_ram_to_uart)将ram中的所有log从串口输出
  1. boot_log_message,此函数是加log比较方便的方式,此函数会将需要打印的log同时写入ram以及输出到串口
上面分析的函数在开机串口log中的体现如下:
notion image

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,两个函数中分别对应相应系统加载前要做的初始化和配置操作以及加载后操作。
这部分的代码有点难以理解:
对应的串口如下:
notion image
notion image
在串口中对应的打印信息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

notion image

5.2 sbl1_hw_pre_ddr_init

5.2.1 boot_pm_device_init

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个字节,也就是4*8 = 32位,也就是2^32个单位空间,以扇区为单位进行寻址,也就是每次512Byte,那么: 2^32*512 = 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

notion image

7.2 xbl代码运行流程

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

7.2.1 SEC(安全验证)

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

7.2.1.1 ModuleEntryPoint.masm 代码分析

该汇编代码中,主要工作为:
  1. 关闭所有中断
  1. 关闭MMU和Caches
  1. 关闭TLB缓存表
  1. 获得当前运行的安全环境:EL1、EL2、EL3
  1. 初始化ELX 安全环境
  1. 使能 Cache
  1. 初始化栈
  1. 调用 CEntryPoint,传参 _StackBase、_StackSize

7.2.1.2 XBLCore\Sec.c 代码分析

前面汇编代码中主要目的是初始化C运行环境,初始化栈,以便可以调C代码运行。
SEC的C代码入口位于:BOOT.XF.4.1/boot_images/QcomPkg/XBLCore/Sec.c
notion image
主要就是main函数
lib源码位置:BOOT.XF.4.1/boot_images/QcomPkg/SocPkg/DivarPkg/LAA/Core.dsc
至于dsc、inf、fdf之间的关系可以查看文章:UEFI学习

7.2.1.2.1 LoadAndParsePlatformCfg

主要工作流程如下:
  1. 初始化相关全局变量
  1. 加载并解析 uefiplat.cfg 配置文件 #define UEFIPLATCFG_FILE "uefiplat.cfg",文件内容保存在 CfgBuffer 中,解析器描述符保存在MemParserDesc 中。在cfg文件中包含了内存相关的信息及系统相关的配置,BOOT.XF.4.1/boot_images/QcomPkg/SocPkg/DivarPkg/LAA/uefiplat.cfg
  1. 解析 uefiplat.cfg 中的 [Config] 区域
  1. 解析 uefiplat.cfg 中的 [MemoryMap] 区域
  1. 解析 uefiplat.cfg 中的 [RegisterMap] 区域,内容保存在mMemRegions中
  1. 解析 uefiplat.cfg 中的 [ConfigParameters] 区域,内容保存在 ConfigTable 中,ConfigTableEntryCount表示其内容的数量
  1. 解析 uefiplat.cfg 中的 [ChipIDConfig] 区域
 
notion image

7.2.1.2.2 UefiBootContinue

DisplayEarlyInfo() 显示模块早期初始化
在 DisplayEarlyInfo 中主要工作就是解析 UEFI version 版本号,然后根据版本号加载对应的镜像,接着打印系统启动的路径。
主要流程为:
  1. 获取UefiPlatCfg.c中的UEFI CORE字段信息,PlatConfigFileName="uefiplatLA.cfg"
  1. 获取固件版本号,定义在boot_images\QcomPkg\Sdm660Pkg\LA\Sdm660Pkg_Core.dsc中
gEfiMdeModulePkgTokenSpaceGuid.PcdFirmwareVersionString|L"4.2"
  1. 根据固件版本号查找对应的image镜像 Append Image version string component
  1. 打印UEFI固件版本号
  1. 选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
notion image
这段代码比较复杂,基本看不懂
主要工作流程如下:
  1. 初始化CPU异常处理
  1. 初始化内存等,初始化UEFI代码基础环境
  1. 初始化DXE调度器

7.2.3 BDS (启动设备选择)

代码路径:BOOT.XF.4.1/boot_images/QcomPkg/Drivers/BdsDxe/BdsDxe.inf
其主要工作为:
  1. 注册按键事件,按下按键后会回调到HotkeyEvent() 函数,最终调用到HotkeyCallback()函数中,解析其中的key scancode
  1. 平台BDS初始化
在其中会打印显示版本号,平台版本信息等等
调用LaunchDefaultBDSApps ()加载默认APP
调用SetupPlatformSecurity()初始化secureboot安全环境
挂载efisp分区
调用ReadAnyKey() 循环检测音量下键是否按下,从而更新对应的启动项
  1. 初始化所有 DriverOptionList 上的 驱动协议。
  1. 根据选择的启动方式,启动对应的的系统
notion image

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 数组中。
  1. 加载 QcomCharger App 应用程序,传参gMainFvGuid 和 “DefaultChargerApp”
  1. 解析 uefiplat.cfg 中的 DefaultBDSBootApp,字符串保存在 DefaultApp 数组中
  1. 加载 DefaultBDSBootApp 应用程序,加载后不再返回

八、UEFI之ABL

BOOT.XF.4.1/boot_images/QcomPkg/QcomToolsPkg/MenuApp/Uefi_Menu.cfg中可以定制相关的option
在LaunchDefaultBDSApps函数中load uefiplat.cfg中定义的DefaultChargerApp和DefaultBDSBootApp
notion image
而LinuxLoader是属于UEFI Application,这个代码是在ap侧的abl中
  • andorid
  • qcom
  • sbl
  • abl
  • xbl
  • MTK平台升级GKI流程Android编译原理之编译步骤
    Loading...