uboot:源码分析-启动第一阶段-start.S解析
start.S引入
进入start.S文件中,发现57行中就是_start标号的定义处
SourceInsight中添加行号
在SI中,如果我们知道我们要找的文件的名字,但是我们又不知道他在哪个目录下,我们要怎样找到并打开这个文件?方法是在SI中先打开右边的工程项目管理栏目,然后点击最左边那个(这个是以文件为单位来浏览的),然后在上面输入栏中输入要找的文件的名字。我们在输入的时候,SI在不断帮我们进行匹配,即使你不记得文件的全名只是大概记得名字,也能帮助你找到你要找的文件。
start.S解析
不简单的头文件包含
#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>#ifndef CONFIG_ENABLE_MMU
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE CFG_UBOOT_BASE
#endif
#endif
(1)#include <config.h>。config.h是在include目录下的,这个文件不是源码中本身存在的文件,而是配置过程中自动生成的文件。mkconfig文件的134-141行创建(默认情况)/追加(make -a时追加)include/config.h文件,这里仅仅添加了一个头文件。这个文件的内容其实是包含了一个头文件:#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,这个头文件就是配置过程中自动生成的。version_autogenerated.h里面就一行内容:#define U_BOOT_VERSION “U-Boot 1.3.4”。这里面定义的宏U_BOOT_VERSION的值是一个字符串,字符串中的版本号信息来自于Makefile中的配置值。这个宏在程序中会被调用,在uboot启动过程中会串口打印出uboot的版本号,那个版本号信息就是从这来的。
主Makefile的29行VERSION_FILE = $(obj)include/version_autogenerated.h:版本信息将被写入到构建输出目录的include子目录下名为version_autogenerated.h的文件中。编译过后的目录当中才会有这个文件,内容是编译过后的版本号
(4)#include <asm/proc/domain.h>。asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。asm目录是配置时创建的一个符号链接,实际指向的是就是asm-arm,在mkconfig文件的第33行到第118行都是创建符号链接
(5)经过分析后发现,实际文件是:include/asm-arm/proc-armv/domain.h
(6)从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)
这样的设计主要是为了可移植性。因为如果直接包含,则start.S文件和CPU架构(和硬件)有关了,可移植性就差了。譬如我要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。我们用了符号链接之后,则start.S中源代码不用改,只需要在具体的硬件移植时配置不同,创建的符号链接指向的不同,则可以具有可移植性。
启动代码的16字节头部
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED).word 0x2000.word 0x0.word 0x0.word 0x0
#endif
1.#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
:这是一个预处理条件指令。它检查CONFIG_EVT1是否已定义(即是否在代码的某处通过#define CONFIG_EVT1进行了定义),并且检查CONFIG_FUSED是否未定义(即没有通过#define CONFIG_FUSED进行定义)。只有当这两个条件同时满足时,紧随其后的代码块才会被编译。
2…word 0x2000
、.word 0x0、.word 0x0、.word 0x0:这四行代码是汇编语言指令,用于在生成的机器代码中插入字面量值。.word指令通常用于在内存中分配一个或多个字(word)的空间,并将指定的值存储在那里。在这个例子中,它分配了四个连续的32位(或相应大小的)空间,并分别初始化为0x2000、0x0、0x0、0x0。
3.#endif
:这标志着预处理条件指令的结束。
在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中就是为了计算这个校验头)。
uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
/home/aa/work0630/pro/uboottest/uboot/sd_fusing/C110-EVT1-mkbl1.c 内容就是mkv210image.c的内容
异常向量表的构建
b resetldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq
ldr pc, _label指令的意思是“将标签_label所代表的地址加载到程序计数器(PC)中。
1._undefined_instruction
:未定义指令异常。当执行了一条未定义的指令时,会跳转到这个地址。
2._software_interrupt
:软件中断。这是由软件触发的中断,通常用于操作系统或应用程序之间的通信。
3._prefetch_abort
:预取中止异常。当ARM处理器试图预取一条指令,但由于某种原因(如内存访问权限问题)无法完成时,会发生这种异常。
4._data_abort
:数据中止异常。当处理器试图访问数据(而不是指令)时,由于某种原因(如内存访问权限问题)无法完成,会发生这种异常。
5._not_used
:未使用的异常向量。在ARMv6及更早的版本中,这个位置是保留的,没有被使用。
6._irq
:IRQ(中断请求)异常。这是由外部设备或内部定时器触发的硬件中断。
7._fiq
:FIQ(快速中断请求)异常。这是另一种硬件中断,但优先级高于IRQ,通常用于需要快速响应的情况。
(1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。
(2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。
(3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。
有点意思的deadbeef
.balignl 16,0xdeadbeef
.balignl 16,0xdeadbeef 这条指令是ARM汇编语言中的一条伪指令,用于在代码中插入对齐填充,以确保后续的代码或数据位于一个特定的地址对齐。
具体来说:
- .balignl 是指示进行长字(long word,即4字节或32位)对齐的伪指令。
- 16 表示对齐的边界,即对齐到16字节(128位)的边界上。
- 0xdeadbeef 是一个填充值,用于在对齐过程中填充到内存中,以确保对齐。这个值通常是一个魔数(magic number),用于调试或标记对齐位置。
(1).balignl 16,0xdeadbeef. 这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。
(2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。
(3)为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。
TEXT_BASE等
_TEXT_BASE:.word TEXT_BASE
- _TEXT_BASE:
是一个标签,它通常用于表示一个代码段或者数据段的起始地址。在链接(linking)过程中,这个标签会被替换成一个具体的内存地址。 - .word
是一条伪指令,用于在当前位置插入一个32位的字(word)。这个字的内容就是紧跟在 .word 后面的表达式或立即数的值。 - TEXT_BASE
是一个符号(symbol),它代表了一个具体的数值或者地址。这个符号在链接过程中会被解析成一个具体的地址值。
(1)第100行这个TEXT_BASE就是上个课程中分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们链接时指定的uboot的链接地址。(值就是c3e00000)
TEXT_BASE变量。最终来源是Makefile中配置对应的命令中,在make xxx_config时得到的
关联知识点:指定程序的链接地址有2种方法
(2)源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。
_TEXT_PHY_BASE:.word CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000
#define MEMORY_BASE_ADDRESS 0x30000000
(1)CFG_PHY_UBOOT_BASE 33e00000 uboot在DDR中的物理地址
虚拟地址是C3e00000
设置CPU为SVC模式
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
- msr 是 Move to Special Register 的缩写,表示将数据移动到特殊寄存器。
- cpsr_c 表示当前程序状态寄存器的控制域。CPSR 是一个32位的寄存器,包含了当前处理器的状态和模式信息。其中,c域(控制域)包含了中断(I)和快中断(F)的使能位。
- #0xd3 是一个立即数,表示要写入c域的值。在ARM架构中,寄存器的值通常以十六进制表示。
- @ I & F disable, Mode: 0x13 - SVC 是一条注释,解释了这条指令的作用:禁用中断(I)和快中断(F),并将处理器模式设置为0x13,即SVC(Supervisor)模式。
具体来说,#0xd3 这个值包含了处理器模式和中断使能位的信息。在ARM架构中,CPSR的M[4:0]位用于表示处理器模式,而I位和F位用于控制中断和快中断的使能。 - M[4:0] = 0x13 表示处理器处于SVC模式。
- I = 1 表示禁用中断。
- F = 1 表示禁用快中断(在某些ARM版本中,F位可能用于其他目的,但在这里我们假设它用于控制快中断)。
(1)msr cpsr_c, #0xd3 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。
(2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。
设置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有关的,不用去细看,大概知道即可。
识别并暂存启动介质选择
ldr r0, =PRO_ID_BASEldr r1, [r0,#OMR_OFFSET]bic r2, r1, #0xffffffc1
(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,以后备用。
/* SD/MMC BOOT */cmp r2, #0xcmoveq r3, #BOOT_MMCSD
设置栈(SRAM中的栈)并调用lowlevel_init
/** Go setup Memory and board specific bits prior to relocation.*/ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */sub sp, sp, #12 /* set stack */mov fp, #0
(1)284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。
(2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。
学习记录,侵权联系删除。
来源:朱老师物联网大课堂