在科技飞速发展的当下,系统级芯片(SoC)宛如一颗璀璨的明星,广泛闪耀于手机、智能家居、车载设备等众多领域,成为推动现代电子设备不断革新的核心力量。它将 CPU、GPU、内存以及各类通信模块精妙集成于方寸之间,宛如把设备的 “智慧大脑” 浓缩进了一个小小的芯片里,为我们的生活带来了前所未有的便捷与智能体验。
然而,SoC 并非通电后就能即刻高效运作。其从芯片上电到系统顺畅运行,背后隐藏着一套复杂且精密的流程。这一过程犹如一场精心编排的交响乐,每个环节都紧密相扣、不可或缺,任何一个音符的差错都可能导致整个 “乐章” 的失准。接下来,让我们一同深入探索 SoC 启动的神秘之旅,揭开从芯片上电的那一瞬间,到系统最终稳定运行背后的重重奥秘。
Part1.SoC 是什么?
SoC,即 System on Chip,中文名为片上系统,是一种将计算机或其他电子系统的大部分或全部功能集成到单个芯片上的技术。这种集成化设计减少了外部组件的数量,降低了功耗和成本,同时提高了系统的可靠性和性能。SoC 的核心组件包括 CPU、GPU、内存、输入输出接口以及通信模块等 ,不同的组件负责不同的功能,共同协作实现系统的运行。
SoC 在现代电子设备中应用广泛,以智能手机为例,其 SoC 通常集成了高性能 CPU 和 GPU,能够提供流畅的用户体验,无论是日常应用还是高性能游戏都能轻松应对;集成的 4G/5G 通信模块使得智能手机能够快速连接互联网,实现高速数据传输;电源管理单元则能够优化电池使用,延长智能手机的续航时间。除了智能手机,SoC 还应用于智能家居设备、可穿戴设备、自动驾驶汽车等众多领域,推动着这些设备的智能化发展。
1.1Uboot 启动前准备
⑴链接脚本与程序入口
在 SoC 启动过程中,链接脚本起着关键作用,它定义了程序执行和动态链接所需的内存布局和功能,确定了程序的入口地址。以 uboot 的链接脚本为例,其中的 ENTRY (_start) 指明了程序入口地址为_start,这个地址标号定义在 arch/arm/lib/vectors.S 文件中 。通过链接脚本,我们可以清晰地了解文件和数据的组织方式,为后续分析程序流程奠定基础。
⑵镜像容器
镜像容器在 SoC 启动中用于存储和管理启动镜像,常见的镜像容器有 uboot、fit 等 。这些镜像容器包含了启动所需的各种文件,如内核、设备树等,它们按照一定的格式组织在一起,确保系统能够正确加载和启动。不同的镜像容器具有不同的特点和适用场景,开发者需要根据具体需求选择合适的镜像容器。
⑶SPL 的启动
SPL,即 Secondary Program Loader,在启动链中一般由 bootrom 加载,作为第二级启动镜像(bl2) 。它主要用于完成一些基础模块和 ddr 的初始化,以及加载下一级镜像 uboot。由于 spl 需要被加载到 sram 中执行,对于一些 sram size 比较小的系统,可能无法放入整个 spl 镜像,此时就需要引入 tpl 来解决该问题。在启动过程中,spl 首先完成 ddr 初始化,然后跳转回 bootrom,由 bootrom 完成 tpl 的加载,并由 tpl 完成最终 uboot 的加载。
SPL(Secondary Program Loader)程序流程如下:
- 初始化ARM处理器
- 初始化串口控制台
- 配置时钟和最基础的分频
- 初始化SDRAM
- 配置引脚多路复用功能
- 启动设备初始化(即上面选择的启动设备)
- 加载完整的u-boot/kernel程序并转交控制权
⑷ATF 的启动
ATF,即 Arm-trusted-firmware,是 arm 为了增强系统安全性引入的可信固件,只支持 armv7 和 armv8 架构 。其基本启动流程为 BL1 – BL2 – BL31 – BL32 – BL33(uboot),即在 bl32 启动完成后再启动 uboot,uboot 作为启动链中最后一级镜像,用于启动最终的 os。
在启动过程中,ATF 负责在将控制权传递给引导加载程序或操作系统之前为主 CPU 设置运行时服务,通过一系列的函数调用和操作,完成对 CPU、内存、中断等的初始化和配置,确保系统的安全启动。
1.2Uboo 的初始化过程
⑴Uboot 的启动
Uboot,全称 Universal Boot Loader,是遵循 GPL 条款的开放源码项目,其作用是系统引导,支持多种 OS 和多种指令集处理器 。Uboot 的启动从链接脚本中指定的入口地址开始执行,首先会进行一系列的初始化操作,如保存上一级镜像传入的参数、检查代码段是否为 4k 对齐、重设 sctlr 寄存器、设置异常向量表等 。这些初始化操作确保了 Uboot 能够在目标平台上正常运行,为后续的启动流程做好准备。
⑵驱动的初始化
在 Uboot 启动过程中,需要初始化各种驱动,以确保硬件设备能够正常工作。这些驱动包括串口驱动、网卡驱动、存储设备驱动等 。以串口驱动为例,其初始化过程通常包括设置串口的波特率、数据位、停止位等参数,使能串口中断等操作,确保 Uboot 能够通过串口与外部设备进行通信,输出调试信息和接收用户输入。
⑶交互原理
Uboot 提供了强大的命令行界面,用户可以通过命令行与 Uboot 进行交互,执行各种操作 。例如,使用 help 命令可以显示所有命令及其说明;使用 pri 命令可以查看所有环境变量;使用 setenv 命令可以修改某一个环境变量的值;使用 saveenv 命令可以将当前所有设置过的环境变量保存,掉电不丢失 。通过这些命令,用户可以方便地对系统进行配置和调试,满足不同的应用需求。
1.3kernel 的初始化过程
⑴内核运行的第一行代码
Linux 内核的入口为 stext,定义在文件 arch/arm/kernel/head.S 中 。当 Uboot 完成初始化并将控制权交给内核后,内核便从 stext 开始执行第一行代码。这一行代码通常是一些汇编指令,用于完成一些底层的初始化操作,如设置栈指针、初始化寄存器等,为后续的内核启动流程搭建环境。
⑵head.S 的执行过程
head.S 文件主要负责完成一些与体系结构相关的初始化工作,如初始化页表、开启 MMU(内存管理单元)、设置内核堆栈等 。在执行过程中,它会根据不同的处理器架构和平台配置,执行相应的初始化操作。例如,对于 ARM 架构的处理器,会设置异常向量表,以便在发生异常时能够正确地跳转和处理;会初始化系统控制寄存器,配置处理器的工作模式和状态。
⑶内核子系统启动的全部过程
在内核完成底层初始化后,会依次启动各个内核子系统,如进程管理子系统、内存管理子系统、设备驱动子系统、文件系统子系统等 。每个子系统都有其特定的初始化流程和功能,它们相互协作,共同构建起一个完整的内核运行环境。以进程管理子系统为例,它会创建系统的第一个进程 init,并启动内核线程,负责管理和调度系统中的所有进程;内存管理子系统会初始化内存分配器,管理系统的物理内存和虚拟内存,为进程和内核提供内存资源。
SoC 启动是一个复杂而有序的过程,涉及到硬件初始化、引导程序加载、内核启动等多个环节。了解 SoC 启动的原理和流程,对于嵌入式系统开发者来说至关重要,有助于他们更好地进行系统开发、调试和优化,提高系统的性能和稳定性。
Part2.SoC 启动全流程解析
2.1上电初始化
当 SoC 芯片上电时,电源管理单元会迅速启动,对各个模块进行初始化和供电控制,就像为一座即将运转的工厂提供稳定的电力供应。处理器核心则开始执行预设的启动程序,初始化系统的各个模块和外设接口,为芯片的正常运行搭建起基本框架。这一过程就像是在一场大型演出前,工作人员对舞台上的所有设备进行调试和准备,确保演出能够顺利进行。
在这个阶段,电源管理单元需要精确地控制各个模块的供电顺序和电压,避免因电压波动或供电不当导致设备损坏。处理器核心则需要按照预设的程序,逐步完成对系统时钟、中断控制器、内存控制器等关键组件的初始化,确保它们能够正常工作。例如,在初始化内存控制器时,处理器核心需要设置内存的工作频率、时序参数等,以保证内存能够高效地读写数据。
2.2加载操作系统或固件
在上电初始化完成后,处理器核心会加载操作系统或者嵌入的固件,这一步就像是为计算机安装操作系统,为芯片提供了必要的软件环境,使得芯片能够执行各种复杂的任务和功能。操作系统或固件就像是一位经验丰富的指挥官,负责管理芯片的各种资源,调度任务的执行,使得芯片能够有条不紊地运行。
在加载操作系统或固件的过程中,处理器核心首先需要从存储设备(如闪存、硬盘等)中读取引导程序,引导程序负责加载操作系统内核和相关驱动程序,并将控制权交给操作系统。这一过程需要确保引导程序的正确性和完整性,以及存储设备的可靠性。例如,在智能手机中,当用户按下电源键后,SoC 芯片会首先加载手机厂商定制的引导程序,然后引导程序会加载安卓操作系统内核和各种驱动程序,最终启动手机的用户界面,让用户能够使用手机的各种功能。
2.3任务执行阶段
一旦软件环境搭建完成,SoC 芯片就可以开始执行各种任务了,这些任务可能包括数据处理、通信、控制等,具体取决于 SoC 芯片的应用领域和设计目标。由于 SoC 芯片高度集成,因此它可以更快速地响应和处理各种任务,提高了系统的整体性能。这就像是一支训练有素的特种部队,能够迅速而高效地完成各种复杂的任务。
以智能家居设备为例,SoC 芯片可以负责处理传感器采集的数据,根据用户的设置控制家电设备的运行状态,同时还可以通过无线网络与用户的手机或其他智能设备进行通信,实现远程控制和智能化管理。在这个过程中,SoC 芯片需要快速地处理大量的数据,并且要保证任务的实时性和可靠性,以提供良好的用户体验。
Part3.不同类型 SoC 启动的特点
3.1以单片机为例的简单启动
单片机作为一种简单的 SoC,其启动过程相对简洁明了。通常,我们可以通过串口或者 JLINK 等工具将编译好的bin文件或者 hex 文件烧录到单片机的 flash 中。在程序运行时,堆栈会被分配到RAM 中,用于存储程序的变量和运行结果 。
单片机上电后,硬件会自动设置 SP(堆栈指针)和 PC(程序计数器)寄存器。PC 是 16 位程序计数器,专门用于在 CPU 取指令期间寻址程序存储器,它总是保存着下一条要执行的指令的 16 位地址。在一般情况下,当取出一个指令字节后,PC 自动加 1,程序顺序执行。而 SP 则用于入栈和出栈操作,在执行转移指令、子程序调用 / 返回指令或中断时,PC 的现行值会自动压入堆栈,保护起来,然后将子程序的入口地址或中断向量的地址送入 PC,程序流向发生变化 。
以常见的 51 单片机为例,其启动文件虽然在编写时可能不易察觉,但实际上开发软件已经帮我们内化了这部分内容。启动文件的主要作用包括初始化堆栈指针 SP 和程序计数器指针 PC,设置堆、栈的大小,设置异常向量表的入口地址,配置外部 SRAM(如果有的话)作为数据存储器,设置 C 库的分支入口__main(最终用来调用 main 函数) 。通过这些设置,单片机能够有条不紊地执行程序,从初始化汇编代码开始,将 data 段和 bss 段复制到 RAM 中,并建立好堆栈,然后调用程序的 main 函数,进入 C 程序的执行阶段 。
3.2复杂 SoC 的多阶段启动
对于复杂的 SoC,其启动过程往往需要多个阶段来完成,以确保系统能够稳定、高效地启动。以典型的 uboot 启动流程为例,通常包含 bootrom(或 xip)、spl、uboot 三个阶段 。
bootrom 是系统初始化时 CPU 执行的第一级启动镜像,一般由厂商固化在 SOC 内部存储器(通常为 ROM)上。这是因为在系统初始化时,CPU 只能访问可以直接寻址的存储器,如 ROM,而像 SPI FLASH 或 NAND FLASH 等外部存储器,都需要相应的驱动才可以访问,在启动的最初阶段 CPU 无法访问 。bootrom 的主要功能是加载并执行 BL2 镜像,BL2 镜像会被加载到固定的位置执行,这个位置一般为芯片内部的 SRAM 。
spl,即 Secondary Program Loader,是 uboot 启动流程中的第二阶段。它的主要功能包括设置 CPU 的状态,如cache、mmu、大小端设置等;准备 c 语言的执行环境,包括设置栈指针和清空BSS 段的内容;为 GD 分配内存空间;初始化 RAM,并将 BL2 的代码拷贝到RAM中执行 。由于 SRAM 的 size 较小,所以 spl 的 size 也不能太大,这也是将uboot 分为 SPL 和 uboot 两个阶段的原因之一 。
uboot 是真正的 Bootloader,运行在 DDR 内存中,提供完整的功能,如初始化更多外设(串口、网卡、USB 等),解析设备树(FDT),读取内核镜像(zImage、Image)和 DTB 文件,加载 Linux 内核,并跳转执行 。在这个阶段,uboot 会完成全面的硬件初始化和加载 OS 到内存中,接着运行 OS,实现系统的完整启动 。
Part4.SoC 启动与 MCU 启动大不同
在芯片的世界里,SoC 启动和 MCU 启动就像两条不同方向的轨道,有着各自独特的运行方式。
从硬件架构上看,SoC 就像一座功能齐全的大型城市,集成了处理器(如 ARM Cortex-A 系列)、内存、存储控制器、图形处理单元(GPU)、音频处理单元、I/O 接口等众多模块 。启动时,它不仅要让 “城市中心” 的 CPU 顺利启动,还要逐一唤醒并初始化各个功能区的模块,确保整个 “城市” 能正常运转。而 MCU 则像是一个小巧的村庄,通常集成处理器(如 ARM Cortex-M 系列或其它架构)、较少的内存(RAM 和 Flash)、基本的 I/O 接口和定时器等简单设施 。它的启动流程就如同唤醒村庄里的少数居民,相对简单,主要是初始化内存、配置时钟和 I/O 接口等基础操作。
启动速度上,SoC 由于集成的模块众多,系统如同一个庞大的机器,启动时需要加载大量的固件和操作系统,可能还涉及启动操作系统内核、驱动程序等复杂步骤 。这就好比启动一艘大型豪华游轮,需要准备的事项繁多,因此启动速度通常较慢,特别是在启动像 Linux 或 Android 这样复杂的操作系统时。而 MCU 的启动过程则像启动一艘小船,通常没有操作系统,或者运行的是轻量级的实时操作系统(RTOS) ,启动步骤少,时间较快,适合对实时性要求较高的场景,比如工业控制中的实时监测与响应。
启动模式方面,SoC 如同一个拥有多个入口的大型建筑,通常支持从 Flash、eMMC、SPI、SD 卡等多种外部存储器中启动 。并且,它在启动时可能还需要通过 Bootloader 这个 “引导员” 来引导操作系统内核,确保启动过程的顺利进行。而 MCU 则像一个只有简单入口的小房子,启动模式相对单一,通常从片上 Flash 或外部存储器中读取程序,启动时直接运行预置的固件或简单的启动代码,不需要复杂的引导过程。
电源管理上,SoC 由于各个模块的功耗和启动要求不同,需要逐步启动各个模块,以降低功耗或提高系统稳定性 。这就好比管理一个大型工厂的电力供应,需要精心安排各个车间的供电顺序和电量分配,通常需要电源管理单元(PMU)来控制不同模块的供电,启动时也涉及不同电源域的启动顺序。而 MCU 的电源管理就像管理一个小家庭的用电,较为简单,启动时只需启用基本的电源和时钟,功耗管理相对较少,通常用于低功耗嵌入式系统。
在应用场景上,SoC 凭借其强大的功能和复杂的启动流程,通常用于功能复杂、需要运行完整操作系统和应用程序的设备中,如智能手机、智能电视、汽车中的智能驾驶辅助系统等 ,就像大型城市能承载各种复杂的社会活动一样。而 MCU 则更多用于对资源要求较低、实时性要求较高的控制类应用,如家电控制、工业控制、汽车中的简单电子控制单元(ECU)等,就像小村庄能满足简单的生活和生产需求。
Part5.SoC 启动常见问题与解决办法
5.1硬件相关问题
在 SoC 启动过程中,硬件问题是常见的故障来源。BOOT 引脚的设置错误可能导致芯片无法识别正确的启动模式,进而无法加载启动文件。因为在 boot rom 中,芯片启动时会先读取 boot mode 寄存器中的值,根据该值选择启动模式(如 nand、nor、emmc、sd 等),所以如果 BOOT 引脚连接错误或电平设置不正确,就会使芯片选择了错误的启动模式 。
NRST 复位键若未拉高,电路会一直处于复位状态,无法往下执行。这就好比一个机器的重启按钮一直被按下,机器就无法正常运行。确认 VDD 和 VDDA 电压是否满足工作要求也至关重要,不仅要确保芯片的供电电压正常,还要保证存储介质的工作环境满足要求,尤其是 flash 的频率和电压 。
晶振和程序里面配置不匹配也可能导致问题。当晶振频率大于程序配置时,会出现超频现象,板子上电后可能会跑飞导致不启动。因为晶振为芯片提供时钟信号,若时钟信号与程序预期的不一致,芯片就无法按照预定的时序执行指令 。
固件和实际芯片型号或者类型不匹配同样会导致启动失败。例如,将适用于某一型号芯片的固件烧录到了另一型号的芯片上,由于不同型号芯片的硬件架构和指令集可能存在差异,固件无法正确识别和控制芯片的硬件资源,从而导致启动失败 。
对于这些硬件问题,排查时需要仔细检查硬件连接,确保 BOOT 引脚连接正确,NRST 复位键处于正常状态;使用万用表等工具测量电压,确认 VDD 和 VDDA 电压满足要求;对比晶振的实际频率和程序中的配置,确保两者一致;检查固件与芯片型号是否匹配,避免烧录错误的固件 。
5.2软件及其他问题
在软件方面,U-boot 加载内核时进行的重定位操作具有重要意义。U-boot 的加载过程分 SPL 和 U-boot 两个阶段,在 SPL 阶段,主要将 U-boot 代码从 Flash 中加载到 RAM 指定位置;在 U-boot 阶段,U-boot 会将自身从 RAM 的开始部分移动到 RAM 的末尾,占用高地址空间,从而让低地址空间可以作为一个连续的、大的内存空间供内核和其他应用程序使用 。这就像是在一个有限的空间里合理安排物品,为更重要的物品腾出合适的位置。
启动时间的裁剪也是 SoC 启动过程中需要关注的问题。优化 Bootloader 可以减小其代码大小,减少不必要的硬件初始化,只初始化必要的硬件设备,从而缩短启动时间。例如,去除一些在启动阶段不需要的功能模块,精简初始化代码。优化 Kernel 可以减少启动服务数量,优化服务的启动顺序,使用预加载技术等方法来实现。比如,将一些非关键的服务延迟启动,或者在系统空闲时再进行加载 。
一些 SoC 支持快速启动模式,这种模式下,SoC 会跳过一些不必要的硬件初始化和自检过程,从而更快地启动。这就好比在跑步比赛中,选手抄了近道,能够更快地到达终点。一些 SoC 还支持休眠和唤醒技术,这种技术可以将系统的状态保存到非易失性存储器中,然后关闭系统。当系统再次启动时,可以直接从非易失性存储器中恢复系统的状态,从而更快地启动 。就像电脑的休眠功能,再次开机时可以快速恢复到休眠前的状态。