一、start.S 引入
1、u-boot.lds 中找到 start.S 入口
(1) 在 C 语言中整个项目的入口就是 main 函数(这是 C 语言规定的),所以譬如说一个有 10000 个.c 文件的项目,第一个要分析的文件就是包含了 main 函数的那个文件。
(2) 在 uboot 中因为有汇编阶段参与,因此不能直接找 main.c。整个程序的入口取决于链接脚本中 ENTRY 声明的地方
ENTRY(_start)
。因此 _start
符号所在的文件就是整个程序的起始文件,_start
所在处的代码就是整个程序的起始代码。
二、start.S 解析1
1、不简单的头文件包含
(1) #include <config.h>
。config.h
是在 include 目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件。(详见 mkconfig 脚本)。这个文件的内容其实是包含了一个头文件:#include <configs/x210_sd.h>"
.
(2) 经过分析后,发现 start.S 中包含的第一个头文件就是:include/configs/x210_sd.h
,这个文件是整个 uboot 移植时的配置文件。这里面是好多宏。因此这个头文件包含将 include/configs/x210_sd.h
文件和 start.S
文件关联了起来。因此之后在分析start.S
文件时,主要要考虑的就是 x210_sd.h
文件。
(3) #include <version.h>
。include/version.h
中包含了 include/version_autogenerated.h
,这个头文件就是配置过程中自动生成的。里面就一行内容:#define U_BOOT_VERSION "U-Boot 1.3.4"
。这里面定义的宏 U_BOOT_VERSION 的值是一个字符串,字符串中的版本号信息来自于 Makefile 中的配置值。这个宏在程序中会被调用,在 uboot 启动过程中会串口打印出 uboot 的版本号,那个版本号信息就是从这来的。
(4) #include <asm/proc/domain.h>
。asm 目录不是 uboot 中的原生目录,uboot 中本来是没有这个目录的。asm 目录是配置时创建的一个符号链接,实际指向的就是 asm-arm(详解上一章节分析 mkconfig 脚本时).
(5) 经过分析后发现,实际文件是:include/asm-arm/proc-armv/domain.h
(6) 从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,因为找不到头文件。(所以 uboot 不能在 windows 的共享文件夹下配置编译,因为 windows 中没有符号链接)
思考:为什么 start.S 不直接包含 asm-arm/proc-armv/domain.h
,而要用 asm/proc/domain.h
。这样的设计主要是为了可移植性。因为如果直接包含,则 start.S 文件和 CPU 架构(和硬件)有关了,可移植性就差了。譬如我要把 uboot 移植到 mips 架构下,则 start.S 源代码中所有的头文件包含全部要修改。我们用了符号链接之后,则 start.S 中源代码不用改,只需要在具体的硬件移植时配置不同,创建的符号链接指向的不同,则可以具有可移植性。
三、start.S 解析2
1、启动代码的 16 字节头部
(1) 裸机中讲过,在 SD 卡启动/ Nand 启动等,整个镜像开头需要 16 字节的校验头。(mkv210image.c 中就是为了计算这个校验头)。我们以前做裸机程序时根本没考虑这 16 字节校验头,因为:
- 如果我们是 usb 启动直接下载的方式启动的,则不需要 16 字节校验头(irom application note);
- 如果是 SD 卡启动,mkv210image.c 中会给原镜像前加 16 字节的校验头。
(2) uboot 这里 start.S 中在开头位置放了 16 字节的填充占位,这个占位的 16 字节只是保证正式的 image 的头部确实有 16 字节,但是这 16 字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
2、异常向量表的构建
(1) 异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。
(2) 异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在 uboot 中并未非常细致的处理各种异常。
(3) 复位异常处的代码是:b reset
,因此在 CPU 复位后真正去执行的有效代码是 reset
处的代码,因此 reset
符号处才是真正的有意义的代码开始的地方。
3、有点意思的 deadbeef
(1) .balignl 16,0xdeadbeef
. 这一句指令是让当前地址对齐排布;如果当前地址不对齐,则自动向后走地址直到对齐,并且向后走的那些内存要用 0xdeadbeef
来填充。
(2) 0xdeadbeef
这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是 abcdef
之中的字母,而且这 8 个字母刚好组成了英文的 dead beef
这两个单词,字面意思是坏牛肉。
(3) 为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。
4、TEXT_BASE 等
(1) 第 100 行这个 TEXT_BASE
就是上个课程中分析 Makefile 时讲到的那个配置阶段的 TEXT_BASE
,其实就是我们链接时指定的 uboot 的链接地址。(值就是 c3e00000)
(2) 源代码中和配置 Makefile 中很多变量是可以互相运送的。简单来说有些符号的值可以从 Makefile 中传递到源代码中。
四、start.S 解析3
(1) CFG_PHY_UBOOT_BASE 33e00000
uboot 在 DDR 中的物理地址
1、设置 CPU 为 SVC 模式
(1) msr cpsr_c, #0xd3
将 CPU 设置为禁止 FIQ 、IRQ,ARM 状态,SVC 模式。
(2) 其实 ARM CPU 在复位时默认就会进入 SVC 模式,但是这里还是使用软件将其置为 SVC 模式。整个 uboot 工作时 CPU 一直处于 SVC 模式。
2、设置 L2、L1cache 和 MMU
(1) bl disable_l2cache
// 禁止L2 cache
(2) bl set_l2cache_auxctrl_cycle
// l2 cache 相关初始化
(3) bl enable_l2cache
// 使能 l2 cache
(4) 刷新 L1 cache
的 icache
和 dcache
。
(5) 关闭 MMU.
总结:上面这 5 步都是和 CPU 的 cache 和 mmu 有关的,不用去细看,大概知道即可。
3、识别并暂存启动介质的选择
(1) 从哪里启动是由 SoC 的 OM5:OM0 这 6 个引脚的高低电平决定的。
(2) 实际上在 210 内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据 OM 引脚的设置而自动设置值的。这个值反映的就是 OM 引脚的接法(电平高低),也就是真正的启动介质是谁。
(3) 我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是 Nand 还是 SD 还是其他的。
(4) start.S 的 225-227 行执行完后,在 r2 寄存器中存储了一个数字,这个数字等于某个特定值时就表示 SD 启动,等于另一个特定值时表示从 Nand 启动····
(5) 260 行中给 r3 中赋值#BOOT_MMCSD(0x03),这个在 SD 启动时实际会被执行,因此执行完这一段代码后 r3 中存储了0x03,以后备用。
4、设置栈(SRAM 中的栈)并调用 lowlevel_init
(1) 284-286 行:第一次设置栈。这次设置栈是在 SRAM 中设置的,因为当前整个代码还在 SRAM 中运行,此时 DDR 还未被初始化,还不能用
。栈地址 0xd0036000 是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
(2) 在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而 BL 只会将返回地址存储到 LR 寄存器中,但是我们只有一个 LR 寄存器,所以在第二层调用函数前,要先将 LR 寄存器入栈,否则函数返回时第一层函数的返回地址就丢了。
五、start.S 解析4
进入函数:lowlevel_init。
(1) 找到 lowlevel_init 函数真正的地方,是在 uboot/board/samsumg/x210/lowlevel_init.S 中。
1、检查复位状态
(1) 复杂 CPU 允许多种复位情况。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
(2) 判断哪种复位的意义在于:冷上电时,DDR 是需要初始化才能用的;而热启动或者低功耗状态下的复位,则不需要再次初始化 DDR
。
2、IO 状态恢复
(1) 这个和上一个 “1、检查复位状态”,与我们的主线启动代码都无关,因此不用去管他。
3、关看门狗
(1) 参考裸机中看门狗章节
4、一些 SRAM SROM 相关 GPIO 设置
(1) 与主线启动代码无关,不用管。
5、供电锁存
(1) lowlevel_init.S 的第 100-104 行,开发板供电锁存。
总结:在前 100 行,lowlevel_init.S 中并没有做太多有意义的事情(除了关看门狗、供电锁存外),然后下面从 110 行才开始进行有意义的操作。
源自朱有鹏老师.