转自:https://zhuanlan.zhihu.com/p/519995589
聊聊SOC启动 armv8启动总体流程
在遥远的单片机时代,嵌入式设备功能比较单一,每个设备只需要执行一件简单的任务,因此在系统初始化完成后,程序就运行在一个大循环中,此时,系统启动流程和功能代码并没有很严格的区分。
随着CPU处理能力和芯片制造工艺的不断提升,高性能芯片逐渐演进成了SOC,单颗芯片上除了CPU外还会集成大量不同功能的IP核,如中断控制器、DSP、GPU、总线控制器、视频处理和编码模块、以及电源管理模块等。为了更好地管理这些软硬件资源,以及为上层用户提供统一的服务和接口,SOC上一般都需要运行操作系统。系统引导程序主要功能就是初始化系统的软硬件状态,为操作系统提供一个合适的软硬件环境,以及加载和启动操作系统映像。
2 软硬件假定
为了更好地描述启动流程的细节,后面的章节我们将选取一种主流的硬件平台和软件框架做讨论。硬件平台选用qemu的virt machine平台,其主要硬件配置如下:
(1)cpu为armv8架构的cortex-a53核
(2)中断控制器为gicv3
(3)ROM空间128k
(4)SRAM空间384k
(5)DDR内存空间3G
(6)支持semihosting半主机模式
选用qemu是因为其是一种硬件模拟器,可以通过软件模拟的方式实现不同硬件平台的功能,如可以在X86主机上模拟armv8架构的硬件,从而可以在不需要target硬件的情况下进行相关功能的调试。qemu调试环境的搭建可参考以下的链接:
其软件架构如下:
(1)初始启动流程使用arm ATF
(2)内核启动流程使用uboot
(3)内核为linux
Armv8的启动流程包含多个阶段,典型地有BL1、BL2、BL31、BL32、BL33,根据需求的不同,这些阶段可以适当地裁剪或添加。为了方便描述,后面我们的讨论将基于以上这些官方定义的标准阶段,它们的源码会被编译成独立的启动镜像,并被保存到特定的存储介质中。由于一般的存储介质(如spi flash、nand flash、emmc、ssd等)都不支持代码的直接执行,因此需要在启动时先将镜像加载到可直接执行代码的存储介质,如SRAM或DDR中,然后运行相关代码。其典型的加载流程如下:
除了硬件默认设置的寄存器值之外,其它寄存器都需要在系统启动时初始化,有些重要的寄存器在系统启动流程中会影响系统的运行行为,因此下面对它们做一简单介绍。 由于armv8很多寄存器都为不同异常等级提供了对应的bank定义,而其含义比较类似,为了方便介绍,后面介绍中我们都以EL3等级下的定义为例
该寄存器是系统控制寄存器,用于提供系统顶级的状态控制信息,其定义如下图:
该寄存器用于设置secure相关的属性,且在其它异常等级下不存在bank值,而是用于控制全局状态的。 因此在不同异常等级切换之前,需要先将该寄存器的值设置为目标异常等级所需的值,如从EL3切换到non secure EL1,则需要将其设置为non secure EL1相关的配置。寄存器的定义如下:
Armv8包含SP_EL0和SP_ELx两种栈指针寄存器,其中SP_ELx包含SP_EL1 – SP_EL3三个bank寄存器。SP_ELx寄存器为异常栈指针寄存器,即cpu进入异常后将会切到捕获该异常对应EL的SP_ELx寄存器,如陷入smc异常时栈指针寄存器将会切换到SP_ELx。SP_EL0可以被任何异常等级使用,因此在EL1 – EL3中除了异常处理流程外,执行其它代码逻辑时一般都会切换为SP_EL0栈指针。
通过中断或异常陷入EL3时,陷入异常之前的PSTATE寄存器将会被保存到SPSR_EL3寄存器中,当异常处理完成后通过ERET指令返回断点处继续执行之前,将用该寄存器的值恢复PSTATE的值。ELR_EL3与SPSR_EL3类似,指示在异常陷入之前该寄存器将用于保存返回地址,异常处理完成后则将该返回地址恢复到PC中。
前面我们聊到镜像跳转时可能需要使用到ERET或SMC指令,下面我们再看下跳转时的细节。
smc是armv8支持的异常跳转指令,它用于程序从EL1或EL2跳转到EL3异常等级。其跳转流程如下:
ERET指令用于从异常处理流程中返回,在介绍异常返回流程之前,我们先看一下armv8异常跳转的流程。Armv8触发异常或中断后硬件将会执行以下操作:
(1)将PSTATE寄存器的内容保存到SPSR_ELx中,其中x表示异常进入的异常等级,如smc异常将会陷入EL3,因此相应的寄存器就为SPSR_EL3
(2)将异常处理完成后需要返回的地址保存在ELR_ELx中
(3)设置中断和异常掩码DAIF,以关闭所有中断和异常
(4)若异常是同步异常或SError中断,异常状态信息将被保存在ESR_ELx中,该信息可用于分析异常发生的原因
(5)若异常为指令异常、数据异常或对齐错误等,则触发异常对应的内存地址将被保存到FAR_ELx寄存器中
(6)栈指针切换为目标异常等级的栈指针寄存器SP_ELx
(7)程序跳转到对应的异常处理入口,执行异常处理程序
在异常执行完成后,可通过ERET指令返回被异常中断程序的断点处继续执行,该指令将使硬件执行以下操作:
(1)用SPSR_ELx寄存器的内容恢复PSTATE寄存器
(2)用ELR_ELx寄存器的内容恢复PC值
从以上流程可以看到,执行ERET指令后程序的执行流将由SPSR_ELx和ELR_ELx决定。因此,我们在执行镜像之间的跳转,只要在ERET之前将ELR_ELx设置为待跳转镜像的入口地址,并设置正确的SPSR_ELx即可完成
嵌入式系统的内存一般包含ROM、SRAM和DDR,其中ROM和SRAM位于SOC片内,DDR位于芯片外部。它们的特点如下:
(1)ROM中的内容断电后不会消失,不仅可用于代码执行,还可以用于镜像存储,但其只有只读权限
(2)SRAM和DDR中的内容在断电后都会消失,因此只能被用于代码的动态执行,而不能用于镜像存储
(3)ROM和SRAM都是直接连接总线上,系统上电后即可直接执行。而DDR需要通过DDR phy和DDR controller连接到总线上,因此使用之前必须要先对其执行初始化操作
根据上述各种内存的特点和前面镜像加载启动流程的需求,在内存规划中我们需要考虑以下几个问题:
(1)由于BL1需要固化到ROM中,且是系统最先执行的,因此ROM地址需要被映射到cpu的重启地址处
(2)由于ROM是只读的,BL1镜像除了代码段和只读数据段之外还包含可读写数据段,这部分数据在BL1启动时需要从ROM重定位到SRAM中
(3)由于BL1被固化在ROM中,芯片出产后就不能更改,因此DDR初始化代码不能集成到BL1中。故BL2需要被加载到SRAM中执行,且在BL2中执行DDR初始化流程
(4)BL2之后的其它镜像既可以运行于SRAM中,也可以运行于DDR中
(5)从前面的镜像启动流程可知,若BL2运行于secure EL1下,当其执行完成后,需要通过smc再次陷入BL1去执行BL31流程,因此BL2和BL1的地址不能有重叠
(6)BL31除了执行启动流程外,在系统运行过程中还会以secure monitor的方式驻留,为normal空间的smc异常提供服务历程,以及为normal os和trust os之间提供消息转发、中断路由转发等功能。因此,BL31镜像需要永久驻留内存,在系统启动完成后不能被回收
(7)与BL31类似,BL32在启动后需要驻留内存为系统提供安全相关服务,因此为其所分配的内存也不能被回收(8)除此之外,BL1、BL2和BL33(一般为uboot)的内存在系统启动完成后都可以被释放给操作系统使用
根据以上内存规划原则,qemu virt machine各启动阶段的内存规划如下:
类型 | 起始地址 | 结束地址 | 长度 | 是否secure内存 | 作用 |
ROM | 0x00000000 | 0x00020000 | 128k | Yes | BL1(bootrom) |
SRAM | 0x0e04e000 | 0x0e060000 | 72k | Yes | Bl1(rw data) |
SRAM | 0x0e000000 | 0x0e001000 | 4k | Yes | Shared ram |
SRAM | 0x0e01b000 | 0x0e040000 | 148k | Yes | BL2 |
SRAM | 0x0e040000 | 0x0e060000 | 128k | Yes | BL31 |
SRAM | 0x0e001000 | 0x0e040000 | 252k | YES | BL32 |
DDR | 0x60000000 | 0x100000000 | 2.5G | NO | BL33 |