目录
一、romcode启动
一、CPU与Chip初始化阶段
在这一阶段中,RomCode 初始化所需的模块,包括 UART、Keypad、eFuse(非易失性存储器件)、CE(安全模块)、TrustZone(安全执行环境)、Timer和 Pin。
二、模式判断阶段
RomCode 会根据外部信号如 PTEST_FUNC_MODE 进入 Ptest 模式,再根据按键判断是否进入 SD Boot 等模式。如果是 Key[0][1],则从 SD Card 读取 bootloader,否则进入 Download 模式。
在 Download 模式下,进一步判断 USB Download 是否使能。
USB Download 使能,则进行 USB 下载。
USB Download 未使能,则监控 UART0 和 UART1。如有一个 UART 口接收到 3 个 0x7E 数据包则使用该 UART 口进行下载,如果 80ms 内没有 UART 收到 3 个 0x7E数据包就会下载超时,超时后会回到正常启动模式。
在正常启动模式下,RomCode 会根据 Strapping Pins 判断是 eMMC、SD 或是 UFS 启动。
eMMC 平台:RomCode 会从 eMMC 卡的 Boot0 分区中读取 70KB 的 bootloader 到 Share Memory 中。如果读取失败则尝试从 Boot1 分区重新读取 bootloader。
SD 平台:RomCode 读取 SD 卡 0x10000 开始位置 70KB 的 bootloader 到 Share Memory。如果读取失败则重新读取一次。
UFS 平台:RomCode 读取 UFS BootA 分区 70KB bootloader 到 Share Memory。如果读取失败则尝试从 BootB 分区重新读取 70KB bootloader。
注:当eMMC上电后,SOC可以通过简单的协议来读取eMMC存储设备中的数据。通常情况下,SOC的ROM代码会在上电后将eMMC BOOT分区的内容加载到SOC内部的SRAM中执行。
三、Security(安全) 启动阶段
Secure Boot 使能,则对 Read 和 Download 数据进行 HASH 和 RSA 校验,校验成功则跳转到对应模式的起始地址运行。Secure Boot 未使能,则直接跳转对应模式的起始地址运行。
二、emmc启动
在 eMMC 启动时,RomCode 需要通过 eMMC 控制器读取 eMMC 卡 Boot 分区的前 70KB 数据到 IRAM(soc内部ram)后才开始运行。
一、emmc初始化
打开 eMMC AHB clock,配置 eMMC 使用的 Pin,设置 base clock 为 26MHz;设置 SD clock 为小于或等于 380kHz;设置 eMMC 控制器为默认的 SDR12 模式;设置 bus width 为 1bit 模式。
- emmc初始化过程
- 发送 CMD0 使 eMMC 进入 idle (空闲)状态.
- 发送 CMD1 确认 eMMC 是否已经 ready,并且判断是否是高容量(大于或等于 2GB)eMMC。
- 发送 CMD2 读 CID。
- 发送 CMD3 设置 relative address(相对地址)。
- 发送 CMD9 读取 CSD(卡的容量、最大读取/写入速度、支持的命令集)。
- 发送 CMD7 选中 eMMC。
- 设置 block size 为 512Byte。
二、emmc boot
设置 base clock 为 26MHz,初始化时 380kHz,300kHz,200kHz,100kHz 轮询,传输数据时设置eMMC clock 为 6.5MHz。
设置使用分区为 Boot0 或 Boot1,并发送 CMD18 从分区中读取 SPL,做哈希校验。Boot0 和 Boot1 内容相同,如果 Boot0 校验失败,则使用 Boot1。
三、SPL 备份功能
eMMC Boot 先从 Partition0 分区中读取 SPL进行校验,如果校验成功,则进入 Secure Boot 流程,如果校验失败,则开始读取 Partition1 分区中的 SPL。
四、Secure Boot
Secure Boot是一种安全机制,用于确保系统启动过程中加载的软件镜像是经过验证且未被篡改的。使用加密算法去检验image文件是否被篡改。
三、SPL启动
SPL启动需要运行 Spl.bin,它存放于 eMMC 物理分区 Boot1 和 Boot2 中。执行过程如下:
RomCode 通过 Flash 控制器接口,从 Flash 读取 Spl.bin 到 IRAM 上。
对 Spl.bin 进行验证,验证完成后程序跳转到 Spl.bin 运行。
一旦BOOT分区的内容被加载,SPL(Secondary Program Loader)即开始执行。SPL负责初始化必要的硬件环境,为加载后续的引导加载程序(如U-Boot)做准备。其次,SPL还可能负责识别和驱动一些基本的硬件设备,比如内存(DDR)、串口等,以便能够进行基本的数据读写操作和调试输出。在某些情况下,SPL还可以进行一些安全性检查,例如校验数据的完整性或解密加密的启动数据。
四、ATF启动
SPL执行完 DDR 等硬件的初始化动作后,从 SML分区加载 ATF(ARM可信固件)、TOS(安全启动组件) 和 LK image 文件,然后跳转到ATF 入口代码开始在 DDR 上运行 ATF。
说明:ATF 启动过程中,如需支持 TOS,则会启动 TOS。TOS 启动完成后,再返回到 ATF 运行。如果无需支持 TOS,则 ATF 启动的最后阶段会跳转到 LK 入口代码启动 LK。
五、LK启动
lk.bin 由 sml-sign.bin 和 u-boot-spl-16k-sign.bin 加载,并且在 sml-sign.bin 执行完后跳转运行。
启动流程内部架:
Board Init
arm_reset:主要完成 bss 清除、栈设置等底层初始化,由汇编来完成,之后跳转到 lk_main 函数(C 语言)。
lk_main:完成线程初始化(thread_init_early);cache、mmu 初始化;使能(arch_early_init)早期驱动(如 serial、trace 等);创建 heap、bootstrap2 线程,为后面运行构建完整的 C 代码运行环境。
bootstrap2:完成 chipram参数获取(platform_init);外围设备(如 regulator、Flash、keypad等)初始化(target_init);Log 系统初始化,并进入 App_init。
App Init
当前内部仅注册一个 App:sprdboot
do<Select Mode>
选择启动模式并执行:
Calibration模式:这个模式主要用于系统校准,比如电池电量的校准或触摸屏的校准。通常这不是一个常规用户会频繁使用的模式,但对于设备维护和调试来说可能很重要。
Recovery模式:当用户按下特定组合键(通常是音量加键+电源键)时,系统会进入恢复模式,即recovery模式。在这种模式下,用户可以进行系统恢复操作,如清除数据/恢复出厂设置、从SD卡安装更新、应用备份和还原等。这相当于Android系统的一个维护模式,允许用户在系统出现问题时进行修复操作。
Fastboot模式:也称为快速启动模式,当用户按下另一组特定组合键(如音量减键+电源键)或者通过adb指令时,系统会进入此模式。在这个模式下,可以进行刷机操作,即通过电脑端软件将新的系统镜像文件安装到设备的EMMC存储中。它常用于刷新设备固件或在更深层次上对设备进行修改。
多核启动:
在 LK 的 project/<board>.mk 中配置 WITH_SMP:=1 开启多核启动。
多核启动流程说明:
arch_init()中 core0 为多核创建 secondarybootstrap2 线程,用于线程切换和下电使用。
遍历 boot funcs,根据 boot funcs 返回结果进入对应模式。如果进入的模式为 normal_mode(),则进行多核操作。
调用 secondary_cpu_poweron()给多核上电,多核上电后会执行到 secondarybootstrap2 线程中等待core0 创建完成实际任务。
core0 调用 secondary_task_create()创建每个核要执行的线程,最后将 thread_created_lock 置位。
如果多核在 secondarybootstrap2 线程中判断出 thread_created_lock 被置位,则进行线程切换执行对应的任务,开始正式的多核并行处理。
core0 执行 boot_load 线程从 Flash 中加载 boot.img 和 vendor_boot.img 到内存中;core1 执行 lcd_init线程进行 LCD 的初始化。
core0 执行完 boot_load 线程后会切换到 secure_verify 线程,在该线程中进行验签等操作;core1 同步执行 kernel_dtb_parse 线程来进行 Kernel 和 dtb 的处理,core1 执行完此线程后会下电。而 core0 执行线程完成后会等待 core1 下电,调用 start_linux()跳转到 Kernel 运行。
单核启动:
读取 misc 分区里的 A/B control 信息,如果信息不存在,则创建其信息。
从 logo 分区加载图片 image 进行开机 logo 显示。
从 boot_a 或 boot_b 分区读取 boot.img 的 Android header,根据 vendor_boot 和 boot image 中的 header信息,加载 ramdisk 到 DDR(memory layout 指定位置)。
根据 vendor_boot 和 boot image 中的 header 信息加载 dtb 到 DDR
从 dtbo 分区读取 dtbo 与 dtb 进行合并。
根据 vendor_boot 和 boot image 中的 header 信息,加载 initbootimage 中的 ramdisk 到 DDR。
跳转到 Kernel 入口。
六、kernel启动
一、Kernel 启动依赖的模
Kernel 启动依赖的模块有 ATF 与 LK。
在 ATF 中会完成对 CPU 及 GIC 的上电与硬件初始化工作。
在 LK 中会完成 Kernel 的引导工作和 cmdline/bootconfig 传递参数等功能。LK 会读取 Kernel 启动所必须的 boot.img 和 dtbo.img 文件,并解压到指定位置,跳转到指定地址运行 Kernel 代码。
二、 Kernel 引导的模块
Kernel 引导的模块为 Android 模块。
在 Kernel 启动的最后阶段,系统执行 ramdisk(RAM) 里 first stage init 程序,该程序位于 ramdisk 中,主要完成 fstab 的加载工作。之后改变 root 到 system,加载位于 system分区内的 second stage init 程序,该程序为Android init 进程,用于加载 Android service。
注:fstab是文件系统表(File System Table)的缩写。/etc/fstab是一个重要的系统配置文件,它存放了系统中各个存储设备和分区的静态信息,包括它们的挂载点、文件系统类型、挂载选项等。当Linux系统启动时,初始化进程(init进程)会根据/etc/inittab文件中的指示,读取/etc/fstab中的信息并自动执行文件系统的挂载操作。fstab中的每个条目都定义了一个文件系统及其相关的参数,包含:文件系统或分区的文件名、文件系统的挂载点、文件系统的文件格式等,通过这个文件,系统管理员可以控制哪些文件系统在启动时被挂载,以及它们的挂载行为。这样可以避免每次开机后手动挂载所需的分区,提高了效率和便利性。
三、kernel模块加载
内核映像加载:引导加载程序将内核映像(通常是zImage或Image)加载到内存中,并设置好相关的环境变量和参数,以便内核在启动时使用。
内核初始化:内核开始执行,并进行自身的初始化工作。这包括设置CPU、内存、外设等硬件设备的初始化,以及初始化内核内部的数据结构和功能模块。
启动设备驱动:内核会启动各种设备驱动程序,使得硬件设备能够正常工作。
挂载根文件系统:内核会挂载根文件系统(通常是ext4或其他文件系统),这是Android系统中所有其他文件系统的起点。
启动init进程:一旦根文件系统被挂载,内核会启动init进程。init进程是Android系统中的第一个进程,其PID(进程ID)为1。init进程负责进一步的系统初始化工作,包括启动各种系统服务和守护进程。
注:kernel版本不同加载机制也不同,在 Kernel5.4 和 Kernel5.15 上 ko 化的要求也有所不同,从 Kernel5.4 开始 64 位必须 ko 化,而Kernel5.15 使用的是 Google 的 image,image 没有的模块必须 ko 化。
七、Android 启动
Kernel 启动完成后,进入 Android init 启动阶段,入口在 system/core/init/main.cpp 中。Android init 启动流程中依据业务流程会多次通过 execv 来调用 system/bin/init 可执行文件,依据每次传递参数的不同整个Android init 进程启动可分为如下几个阶段:
FirstStageMain
SetupSelinux
SecondStageMain
一、init第一阶段FirstStageMain
在上述流程中,super 分区的挂载从 init 第一阶段 FirstStageMain (system/core/init/)函数开始。在 FirstStageMain 函数中,主要是进行 dev、sys、proc 等 tmpfs (临时文件系统)文件系统的挂载,并创建一些关键路径和文件,完成后再运行DoFirstStageMount 函数。
FirstStageMain 执行最后会通过 execv 重新调用可执行文件 init(参selinux_setup)来触发SetupSelinux。SetupSelinux主要是通过加载 Selinux policy 来确保 linux访问的安全性。
注:tmpfs(临时文件系统)是 Linux 内核中的一种虚拟内存文件系统,它可以用来创建临时的、非持久性的文件和目录。
execv用于替换当前的init进程映像,以执行新的system/bin/init可执行文件。execv是一个系统调用,用于在当前进程中运行一个新的程序。它将完全替换当前进程的映像、数据和堆栈等信息,用新的程序替代旧的程序继续执行。
二、nit 第二阶段 SecondStageMain
SetupSelinux通过 execv 调用可执行文件 init(参数 second_stage)来触发 init 第二阶段SecondStageMain。
PropertyInit:读取分区目录下的.prop 类型文件(例如 system/build.prop)初始化 property 属性值。
InstallSignalFdHandler:初始化 signal 句柄,用于监听 init 启动的子进程的状态变更,避免出现Zombie progress。
LoadBootScripts:解析各个分区/etc/init 目录下的“.rc”类型文件,将解析内容存入对应的数据结构(比如解析的 Service 类型语句信息会存放到 Service 链表中)供后续使用。
上述 init 启动的 service 中,对于 FrameWork 层及应用层来说最重要的进程就是 Zygote。
init 进程通过解析 init.zygote32.rc(32 位)或者 init.zygote64.rc(system/core/rootdir 64 位)配置文件来启动 Zygote 进程。Zygote 进程启动过程中会进行虚拟机的创建(startVm)以及 native 函数的注册(startReg),之后会调用ZygoteInit 完成预加载(preload)并启动 SystemServer 进程(startSystemServer负责启动和管理各种系统服务),创建 java 层的各个系统Services。
注:创建(startVm),这是为了初始化Dalvik虚拟机(运行Android应用程序的虚拟机),它是Android系统中Java应用程序的运行环境。注册(startReg),这是为了将一些C/C++函数注册到Java环境中,使得Java代码可以调用这些函数。preload预加载是指预先加载一些常用的类和资源,以便在应用程序启动时能够快速加载。
八、WCN 启动
WCN 主要包括 Wi-Fi、蓝牙、FM、GNSS 四个功能模块。
一、上电校准
系统上电初始化后 BSP Driver 进行加载,随之 GNSS/CP2 上电,获取 GNSS 和 CP2 的 Firmware 路径。最后将其加载到内存中并进行校准,校准完成后 GNSS/CP2 下电。
二、业务开启
系统上电后加载蓝牙和 FM 驱动,初始化 GNSS HAL 模块。此时,可以通过 UI 界面控制各项业务(Wi-Fi 业务、蓝牙业务、FM 业务、GNSS 业务)。如果此时 Wi-Fi/蓝牙/FM 业务都未开启,那么打开任何一个业务都会重新下载 Firmware 的 bin 到 CP2 并启动 CP2。同样开机定位功能也会启动GNSS Firmware 的加载。Firmware 的 bin 文件支持单分区和 AB 分区,并自动识别,主要通过文件路径后面是否加后缀或加_A 或_B 来区分业务开启流程。
九、Audio DSP 启动
如果项目存在 Audio DSP,那么它由 LK 进行加载。当 LK 中 loader_nvm搬运分区数据时,加载 Flash 上的 Audio DSP 代码到 DDR 上的指定位置。