16.U-boot的工作流程分析-2440
分析的流程:
- 程序入口
- 第一阶段程序分析
- 第二阶段程序分析
2440开发板:
1.uboot的入口:
要看uboot工程的入口,首先打开顶层目录的Makefile:
Uboot所支持的开发板,在顶层的Makefile中都会有一个配置选项。比如2440,在Makefile中的配置选项是smdk2440_config:在vim的命令模式按下/,然后输入smdk6410_config回车会定位到这里:
这是Makefile里的一个目标。这是来配置2440开发板的。看到上图第二行的smdk2440,这个参数决定了开发板的名称。这个名称是有作用的。接下来看看他的作用。
首先是找一下目录:
可以看到这里有很多smdk的子目录,也包括smdk2440,这两个是对应的。该目录存放的就是2440开发板相关的文件。里面有一个叫uboot.lds的文件,前面知道lds文件是连接器脚本。Uboot的整个过程的链接,是通过该脚本来链接控制的。打开该链接器脚本:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/s3c24xx/start.o (.text) //1可以看到位于代码段前端的文件是start.o,对应的是一个汇编文件。这个汇编文件会最先被运行。但是这个汇编代码里最先执行的代码是哪些呢?
cpu/s3c24xx/s3c2440/cpu_init.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
.mmudata : { *(.mmudata) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
首先是找到该文件:
上面u-boot-lds文件里,在sections的标识之上,有一行:ENTRY(_start)是整个程序的入口。所以就找找start.S文件里有没_start这个标识呢?一搜会马上看到该标号:
到这里,看到这个_start才是整个uboot工程的入口。
接下来是第一阶段的代码:
在Linux里,打开上一节创建好的,Source Insight里的uboot工程:
然后找到smdk2440的start.S:
接下来看uboot做了什么事,主要是通过注释来分析:
开发板一上电,它会跳转到start.S的中断向量表的开始处执行:
从它的注释:
所以1,:知道上电后是跳到中断向量表来执行。执行的第一条指令是b start_code。
Start_code的实在start.S的下面定义的:
从注释知道,这是actual start code。进入之后,set the cpu to svc32 mode,设置cpu为SVC模式。
接着执行的代码是:
3.刷新I/D caches。
4.然后是关闭MMU和cache:
后加
关闭系统时钟,在lowlevel _init函数里bl system_clock_init函数。进入该函数:
可以看到2440是在初始化系统时钟里关闭看门狗。
下面还有屏蔽中断的操作:
后加
5.接着是运行的是一个函数:看看是定义在那个文件的。
点击:,然后在Symbol里输入:lowlevel_init:
会看到出现很多,然后在下面可以看到它们各自的目录。其实每一个开发板都对应一个lowlevel_init.S:
进入该文件:
5看到系统进入该文件做的第一件事是:初始化系统时钟:
6.接着初始化串口:
7.对nand进行简单初始化:
8.接下来的代码很重要:
由前面的学习知道,当我们开发一个uboot在开发阶段,就是在调试uboot的时候,不用烧到NandFlash去运行,可以下载到内存里面去调试运行。这时候就不需要代码搬移bl0,bl1,bl2的过程。就不需要进行拷贝工作了。所以上面的代码就是判断,判断uboot代码是在内存运行,还是在NandFlash运行。如果没有运行在内存当中,就是从NandFlash启动。就需要对内存进行初始化,就是跳转到mem_con_init处执行,进行内存初始化。执行完内存初始化之后就要返回了。
返回到:
接着往下执行:
9.判断是NandFlash启动还是Nor Flash启动:
如果是NandFlash启动这是执行下面的:把代码从NandFlash拷贝到内存去。
NorFlash这执行下面,把代码从NorFlash拷贝到内存。
这里讲NandFlash启动,所以跳转到nand_copy:
10,接着是设置堆栈:
11.接着清除BSS段:
到这里第二阶段的代码就执行完了。
三:
执行完上面,程序跳转到_start_armboot处执行:
上面的代码,通过伪指令ldr把_start_armboot的值装入pc指针,程序就会跳转到_start_armboot处执行。而此处的地址是start_armboot的地址。就是,程序会跳转到start_armboot函数处执行。就是把我们的pc指针跳转到内存去执行了。
下面看看start_armboot的地址是不是在内存中。
配置uboot:make smdk2440_config,然后执行make。
然后看到生成的文件:
其中,u-boot是elf文件,u-boot.bin是二进制文件。
接下来对u-boot的elf文件,进行反汇编,看看start_armboot函数的地址:
arm-linux-objdump -D -S u-boot >dump
查看:
看到函数的其实地址30009100的地址是在内存里的。所以start.S里的:
实现了从垫脚石跳转到内存。但是,此时会发现在这里的起始地址被变为了30008000:
在第一阶段里,不是说启动地址是在0吗?为什么这里是30008000呢?还有就是为什么是这个地址。
前面的学习知道,当去链接一个程序的时候,程序由多个文件构成,起始地址是由链接器脚本决定的。在/home/samba/uboot/Uboot/2440/uboot/board/samsung/smdk2440里的u-boot.lds:
起始地址是0:
这里是0,为什么那里会是30008000呢?我们回到uboot的顶层目录,打开config.mk:
搜索text_base:
找到:
在这里-T $(LDSCRIPT)就是定义使用链接器脚本。后面的-Ttext 是制定代码段的基地址的。$(TEXT_BASE)。这里有两个起始地址,然而程序运行的时候以后面的TEXT_BASE的地址为准。它会覆盖掉LDSCRIPT这个地址。
TEXT_BASE是在board/Samsung/smdk2440/config.mk里定义的。最后一行:
接下来就是测试验证一下,把他修改为30005000。然后程序编译:
Make smdk2440_config->make:
反汇编:
arm-linux-objdump -D -S u-boot >dump
上面的起始地址变了,刚才制定的。
那起始地址为什么不是0呢?下图:
从上图可以看到,ldr PC,=start_armboot的跳转,把地址从垫脚石跳转到内存里。在前面的代码里,有用到b reset等跳转指令,为什么这不会跳转到内存去执行,而是还在垫脚石里呢?
例如:
上面的跳转,bl lowlevel_init的跳转地址:
为什么PC指针还是在垫脚石中呢?
这就得讲两个词了。绝对跳转和相对跳转。
B和bl是相对跳转。
Ldr伪指令是绝对跳转:
第三阶段:
是从此函数Start_armboot进入的。主要完成的硬件和软件的初始化,只是一些基础的初始化。
该函数里有一个for循环:
在for里首先是让一个指针数组,把里面的函数指针依次调用一次,if里的判断语句就是函数指针。那么指针数组里有哪些函数指针呢。
指针数组:
可以看到里面都是函数指针。这里软件的初始化就不看了,我们只看硬件的初始化。在这些函数指针里,硬件初始化的有串口初始化。Serial_init。接着是lcd的初始化:
初始化网卡:
初始化led:
接着进入一个主循环:
执行用户输入的命令。例如tftp命令。这里是一个死循环。老是等待执行用户继续输入命令。在第一个阶段,每个开发板可能有不同的地方,但是在这个地方都是一样的。就是,在第二阶段都是跳到start_armboot处执行代码。2440的第一阶段是在start.S的b reset开始。