U-Boot的启动过程分为两个阶段:处理器初始化阶段和板级初始化阶段。U-Boot启动完成后根据用户的选择进入不同的处理状态,如果在循环等待阶段按任意键则进入命令行状态,接受用户输入的命令,可以执行环境变量设置、下载烧写程序、运行程序等操作。如果在循环等待阶段没有用户干预,则进入操作系统启动模式,复制操作系统并跳转到操作系统入口处执行µClinux内核。
目标板外部存储空间包括SDRAM和Flash,地址范围如表4.9所示。
表4.9 目标板外部存储空间
U-Boot程序的代码段、数据段等各部分内容在存储器中的位置由/board/bf537-stamp/u-boot.lds文件决定。u-boot.lds文件是链接脚本文件,链接器bfin-uclinux-ld根据该文件决定各部分的存储和执行位置。打开u-boot.lds文件,在SECTIONS中可以清楚地看到代码段的定义:
. = ((32 * 1024*1024) - (256 << 10));
.text :
{
cpu/bf537/start.o (.text)
cpu/bf537/start1.o (.text)
cpu/bf537/traps.o (.text)
cpu/bf537/interrupt.o (.text) cpu/bf537/serial.o (.text)
common/dlmalloc.o (.text)
lib_generic/crc32.o (.text)
. = DEFINED(env_offset) ? env_offset : .;
common/environment.o (.text)
*(EXCLUDE_FILE (board/bf537-stamp/post-memory.o) .text)
*(.fixup)
*(.got1)
} > ram
首先定义代码段的起始地址是((32 * 1024*1024) - (256 << 10)),即0x1fc0000,这个地址就是配置文件中定义的CFG_MONITOR_BASE的地址,也就是U-Boot的起始地址。存放的第一个文件是/cpu/bf537/start.o,该文件是U-Boot的入口文件,即最早执行的文件,其次是cpu/bf537/start1.o文件。看上去U-Boot是从0x1fc0000开始执行的,事实上并非如此,因为U-Boot是烧写在Flash中的,而目标板的启动模式是从Flash直接启动,也就是说是从Flash的起始地址0x20000000开始执行的。关于启动模式的问题将在下节讨论,这里仅研究U-Boot的启动过程。实际上U-Boot的确是从0x20000000开始执行的,本阶段的启动过程如图4.12所示。
图4.12 处理器初始化阶段的启动过程
启动代码在/cpu/bf537/start.S文件中,代码复制与跳转方法设计得很巧妙,具体参看下面的程序注释。
call get_pc; //获得当前的执行地址,应该是指向Flash空间的地址
offset:
r2.l = offset;
r2.h = offset;
r3.l = start;
r3.h = start;
r1 = r2 - r3;
r0 = r0 - r1;
p1 = r0; //计算U-Boot在Flash中的起始地址,结果存放在p1中
p2.l = (CFG_MONITOR_BASE & 0xffff);
p2.h = (CFG_MONITOR_BASE >> 16); //获得U-Boot在SDRAM空间的地址
p3 = 0x04;
p4.l = ((CFG_MONITOR_BASE + CFG_MONITOR_LEN) & 0xffff);
p4.h = ((CFG_MONITOR_BASE + CFG_MONITOR_LEN) >> 16);
loop1:
r1 = [p1 ++ p3];
[p2 ++ p3] = r1;
cc=p2==p4;
if !cc jump loop1; //把Flash中的代码循环复制到SDRAM中
p0.l = (EVT_IVG15_ADDR & 0xFFFF);
p0.h = (EVT_IVG15_ADDR >> 16);
p1.l = _real_start;
p1.h = _real_start;
[p0] = p1; //把_real_start地址设置到软件中断向量表p0.l = (IMASK & 0xFFFF);
p0.h = (IMASK >> 16);
r0.l = LO(IVG15_POS);
r0.h = HI(IVG15_POS); //使能软件中断
[p0] = r0;
raise 15; //触发软件中断,从而跳转_real_start处执行,_real_start指向SDRAM地址
_real_start是第二阶段启动的开始,此时已经跳转到SDRAM空间。本阶段的启动过程如图4.13所示。
图4.13 板级初始化阶段启动过程
Main_loop函数在/common/main.c中实现,进入该函数后U-Boot等待用户输入,如果有任意键按下就会进入命令行状态,如果在设置的延时内没有输入则执行bootcmd命令。bootcmd是U-Boot的环境变量,为了引导µClinux通常使用以下命令设置bootcmd :
setenv bootcmd bootm 0x20020000
其中0x20020000是µClinux内核的存放地址。由于bootcmd的内容是bootm,所以程序会跳转到do_bootm()执行bootm命令。do_bootm函数在/common/cmd_bootm.c中实现,下面重点分析该函数的启动过程。在分析该函数之前,首先了解一个数据结构image_header_t,该结构存放µClinux映像的头信息,在bootm过程中用到。
typedef struct image_header {
uint32_t ih_magic;
uint32_t ih_hcrc;
uint32_t ih_time;
uint32_t ih_size; uint32_t ih_load;
uint32_t ih_ep; uint32_t ih_dcrc;
uint8_t ih_os;
uint8_t ih_arch;
uint8_t ih_type;
uint8_t ih_comp;
uint8_t ih_name[IH_NMLEN];
} image_header_t;
image_header_t在include/image.h文件中定义,宏IH_NMLEN定义为32,因此整个头信息的长度为64字节,这个64字节的头信息是U-Boot启动特有的要求。µClinux内核映像必须加上64字节的头信息才能被U-Boot引导,否则不能被识别,各成员变量的含义如表4.10所示。
表4.10 µClinux映像头信息的含义
(续表)
o_bootm()的启动流程如图4.14所示。
图4.14 do_boom()启动过程
do_bootm()中的解压函数实现了把µClinux映像从Flash到SDRAM的数据搬移,这样保证了以后µClinux的运行是在SDRAM空间完成的。加载的地址不是随便确定的,而是要和µClinux映像生成的参数保持一致。do_bootm()函数执行到最后会调用do_bootm_linux()进入µClinux的启动过程,do_bootm_linux()在/lib_blackfin/bf533_linux.c中定义,该函数的实现如下所示:
void do_bootm_linux(cmd_tbl_t * cmdtp, int flag, int argc, char
*argv[],
ulong addr, ulong * len_ptr, int verify)
{
int (*appl)(char *cmdline);
char *cmdline;
appl = (int (*)(char *))ntohl(header.ih_ep); printf("Starting Kernel at = %x\n", appl);
cmdline = make_command_line();
if (icache_status()) {
flush_instruction_cache();
icache_disable();
}
if (dcache_status()) {
flush_data_cache();
dcache_disable();
}
(*appl)(cmdline);
}
do_linux_bootm()的实现比较简单,首先把映像头信息中的入口地址ih_ep转换为一个函数指针,然后根据配置文件中定义的BOOTARGS变量生成传递给µClinux内核的命令行参数,最后以该命令行为参量调用入口函数,完成µClinux的启动工作。实际上ih_ep指向的地址是µClinux的_start()函数,即µClinux运行的起始地址,上述调用实现了到µClinux内核的跳转工作,程序控制不再返回U-Boot,而是µClinux内核接管了对处理器的控制。命令行的定义如下:
#define CONFIG_BOOTARGS "root=/dev/mtdblock0 rw console=ttyBF0,115200"