U-boot第二阶段分析
一、U-boot第二阶段概述
上面有两篇文关于u-boot第一阶段的介绍,这两篇文章是从网上找到的,由于分析的很详细,看完这后觉得对这两篇文章u-boot第一阶段的介绍已经比较完美了,所以分享出来。从这篇文章开始分析u-boot的第二阶段。
如果你只把u-boot理解成引导kernel的一段代码的话,u-boot完全没有必要设计成现在这样的一种软件框架,直接写几个文件就能完成kernel的引导和启动。U-boot的功能很大一部还有起到调试的作用,也就是u-boot命令行的部分。所以它才有了现在这种相对比较复杂的框架。U-boot的第二阶段可以认为是初始化u-boot的软件框架,并实现引导kernel启动和命令行调试环境的过程
U-boot第二阶段总结来说主要可以概括为下面几点
1、硬件的初始化
2、运行环境的初始化
3、载入内核并启动内核
4、运行u-boot调试的命令机制
其中1和2阶段可以看成是u-boot软件框架的初始化过程,只不过包括了硬件设备的初始化和软件相关结构体的初始化。而3就是引导kernel启动过程,而4是调试的命令行环境运行过程。
本文首先沿着第一阶段调用_start_armboot函数开始第二阶段的分析。首先完成一个概述,之后会对一些关键的问题做专题分析。
二、硬件和软件框架的初始化
首先初始化一个全局性的数据结构gd_t
/* Pointer is writable since we allocated a register for it */ |
我们看到这里u-boot动态的分配了gd_t和bd_t这两个数据结构,只是比较疑惑的是这两个数据结构完全可以定义成全局的数据,何必这样动态的分配呢?由于这个时候完全没有malloc这样的环境,u-boot只好跑马圈地,很野蛮的在_armboot_start前面画出一块位置作为上面结构体区域。这里我们就两个疑问?1、u-boot到底被第一阶段的代码拷贝到什么地方去执行了?第二个疑问就是u-boot在内存区域中相关代码和数据区域是怎么分布的?
Question1:
Answer:
查看一下下面的配置文件就可以知道,mini2440开发板sdram的地址范围是3000’0000到3400’0000,一共是64M。U-boot首先把自己载入了最后的512K的地址空间中,坐在墙角的位置。
u-boot-1.1.6\board\open24x0\config.mk
#
# SMDK2410 has 1 bank of 64 MB DRAM
#
# 3000'0000 to 3400'0000
#
# Linux-Kernel is expected to be at 3000'8000, entry 3000'8000
# optionally with a ramdisk at 3080'0000
#
# we load ourself to 33F8'0000
#
# download area is 3300'0000
#
TEXT_BASE = 0x33F80000
Question2:
Answer:
U-boot被完全载入到内存后,各部分在内存中的位置如下图所示:
需要注意的是图中TEXT_BASE的地址在整个SDRAM空间的最后512K位置了。
初始化函数调用
接着u-boot对单板的硬件和软件框架做相应的初始化
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) |
U-boot软件框架定义了这样的一个初始化表,不同的单板具体实现下面的函数进行相应的初始化动作
init_fnc_t *init_sequence[] = |
后续的文章会将上面的初始化函数做逐一的分析,但是本篇本意是想对第二阶段的处理过程做一个提纲挈领的描述,所以暂时举一个例子分析如下:
int cpu_init (void) |
Cpu_init函数基本上就是初始化了一下栈区空间和软件中断的栈空间
在完成了单板相应硬件初始化之后,u-boot在后继的代码里可以自由的使用u-boot框架提供的一些服务接口,比如从串口获取输入,从nandflash读写数据,网络接口通信等等。然后u-boot进入了一个大的循环中
for (;;) |
在main_loop函数中,有两条比较关键的路径需要分析,一条路径就是引导内核启动,另外一条路径就是用户通过串口输入中断了u-boot启动内核过程而进入了u-boot的命令行调试运行环境中。
三、u-boot引导内核启动的过程。
首先我们来分析一下u-boot引导内核启动的过程
1、首先获取等待用户串口按键中断引导kernel的时间,一般为3秒
s = getenv ("bootdelay"); |
2、获取启动内核的命令,这个靠编译u-boot之前开发者的配置
s = getenv ("bootcmd"); |
而这条启动命令在mini2440的配置就是如下,其实是两条命令组成的
#define CONFIG_BOOTCOMMAND "nand read.jffs2 0x32000000 kernel; bootm 0x32000000"
3、调用abortboot函数检查在等待的3s中内是不是从串口有用户输入了空格,如果有就说明用户想进入命令行调试模式,如果没有就说明可以开始运行启动内核的命令。
if (bootdelay >= 0 && s && !abortboot (bootdelay)) |
4、abortboot中比较关键的代码就是在一个循环中不断检测是不是有用户的串口输入,使用了一个udelay函数,10000us等于10ms,循环100次就是1s
while ((bootdelay > 0) && (!abort)) |
5、用户不中断kernel引导过程的话,最后会调用到run_command函数,这个函数不需要怎么分析,它的作用就就是解析输入的命令,然后根据命令的名字查找相应的命令执行函数进行调用,上面我们看到,启动的时候需要调用两个命令,一个是nand命令一个bootm命令。
6、Nand命令完成将kernel载入到内存中指定的位置,而bootm则是最后调用固定位置的一个kernel启动函数进入kernel的代码中开始执行。
7、执行nand命令,u-boot在其软件框架中定义了一个命令nand如下所示,执行这个命令最后会调用到函数do_nand
U_BOOT_CMD(nand, 5, 1, do_nand, |
这里也没有必要去深入探究do_nand的作用,它完成的工作就是将kernel从nandflash中准确的载入到内存指定的位置。
当nand命令将kernel读入到内存后,接着执行bootm命令,最后执行linux内核的code
U_BOOT_CMD( |
这个命令的执行最后会调用到do_bootm函数
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) |
最后调用do_linux_bootm完成内核启动,这个函数主要做三个工作,首先是拷贝inird到确定位置,然后初始化内核参数到确定的位置,最后调用内核的启动函数,并传递两个参数给内核一个是cpu number,一个是内核参数位置。
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], |
四、U-boot命令行调试状态
上面是对u-boot载入引导kernel的分析。如果用户通过串口打断引导过程,或者引导过程失败了,就会进入u-boot命令行调试状态。U-boot进入一个无限的for循环等待用户从串口输入命令,找到匹配的命令后执行相应的操作。
for (;;) |
Andy Yixin Deng
2013-08-25 南京