Uboot启动流程

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

U-Boot启动过程
(国嵌)
开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。

看一下board/smdk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。

第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。

下面分两阶段介绍启动流程:
第一阶段
1.cpu/arm920t/start.S
这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

_start:breset//复位向量
ldrpc,_undefined_instruction
ldrpc,_software_interrupt
ldrpc,_prefetch_abort
ldrpc,_data_abort
ldrpc,_not_used
ldrpc,_irq//中断向量
ldrpc,_fiq//中断向量

/*theactualresetcode*/
reset://复位启动子程序
/*设置CPU为SVC32模式*/
mrsr0,cpsr
bicr0,r0,#0x1f
orrr0,r0,#0xd3
msrcpsr,r0
/*关闭看门狗*/
…………
relocate:/*把U-Boot重新定位到RAM*/
adrr0,_start/*r0是代码的当前位置*/
ldrr1,_TEXT_BASE/*_TEXT_BASE是RAM中的地址*/
cmpr0,r1/*比较r0和r1,判断当前是从Flash启动,还是RAM*/
beqstack_setup/*如果r0等于r1,跳过重定位代码*/
/*准备重新定位代码*/
ldrr2,_armboot_start
ldrr3,_bss_start
subr2,r3,r2/*r2得到armboot的大小*/
addr2,r0,r2/*r2得到要复制代码的末尾地址*/
copy_loop:/*重新定位代码*/
ldmiar0!,{r3-r10}/*从源地址[r0]复制*/
stmiar1!,{r3-r10}/*复制到目的地址[r1]*/
cmpr0,r2/*复制数据块直到源数据末尾地址[r2]*/
blecopy_loop
/*初始化堆栈等*/
stack_setup:
ldrr0,_TEXT_BASE/*上面是128KiB重定位的u-boot*/
subr0,r0,#CFG_MALLOC_LEN/*向下是内存分配空间*/
subr0,r0,#CFG_GBL_DATA_SIZE/*然后是bdinfo结构体地址空间*/
#ifdefCONFIG_USE_IRQ
subr0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
subsp,r0,#12/*为abort-stack预留3个字*/
clear_bss:
ldrr0,_bss_start/*找到bss段起始地址*/
ldrr1,_bss_end/*bss段末尾地址*/
movr2,#0x00000000/*清零*/
clbss_l:strr2,[r0]
/*bss段地址空间清零循环...*/
addr0,r0,#4
cmpr0,r1
bneclbss_l
/*跳转到start_armboot函数入口,_start_armboot字保存函数入口指针*/
ldrpc,_start_armboot
_start_armboot:.wordstart_armboot//start_armboot函数在lib_arm/board.c中实现
第二阶段
2.lib_arm/board.c
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

3.init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。

init_fnc_t*init_sequence[]={
cpu_init,/*基本的处理器相关配置--cpu/arm920t/cpu.c*/
board_init,/*基本的板级相关配置--board/smdk2410/smdk2410.c*/
interrupt_init,/*初始化中断处理--cpu/arm920t/s3c24x0/interrupt.c*/
env_init,/*初始化环境变量--common/cmd_flash.c*/
init_baudrate,/*初始化波特率设置--lib_arm/board.c*/
serial_init,/*串口通讯设置--cpu/arm920t/s3c24x0/serial.c*/
console_init_f,/*控制台初始化阶段1--common/console.c*/
display_banner,/*打印u-boot信息--lib_arm/board.c*/
dram_init,/*配置可用的RAM--board/smdk2410/smdk2410.c*/
display_dram_config,/*显示RAM的配置大小--lib_arm/board.c*/
NULL,
};
voidstart_armboot(void)
{
/*顺序执行init_sequence数组中的初始化函数*/
for(init_fnc_ptr=init_sequence;*init_fnc_ptr;++init_fnc_ptr){
if((*init_fnc_ptr)()!=0){
hang();
}
}
/*配置可用的Flash*/
size=flash_init();
display_flash_config(size);
/*_armboot_start在u-boot.lds链接脚本中定义*/
mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);
/*配置环境变量*/
env_relocate();
/*从环境变量中获取IP地址*/
gd->bd->bi_ip_addr=getenv_IPaddr("ipaddr");
/*以太网接口MAC地址*/
……
devices_init();/*获取列表中的设备*/
jumptable_init();
console_init_r();/*完整地初始化控制台设备*/
enable_interrupts();/*使能中断处理*/
/*通过环境变量初始化*/
if((s=getenv("loadaddr"))!=NULL){
load_addr=simple_strtoul(s,NULL,16);
}
/*main_loop()循环不断执行*/
for(;;)
{
main_loop();/*主循环函数处理执行用户命令--common/main.c*/
}
命令实现
U-Boot作为Bootloader,具备多种引导内核启动的方式。

常用的go和bootm命令可以直接引
导内核映像启动。

U-Boot与内核的关系主要是内核启动过程中参数的传递。

1.go命令的实现
/*common/cmd_boot.c*/
intdo_go(cmd_tbl_t*cmdtp,intflag,intargc,char*argv[])
{
ulongaddr,rc;
intrcode=0;
if(argc<2){
printf("Usage:\n%s\n",cmdtp->usage);
return1;
}
addr=simple_strtoul(argv[1],NULL,16);
printf("##Startingapplicationat0x%08lX...\n",addr);
rc=((ulong(*)(int,char[]))addr)(--argc,&argv[1]);/*运行程序*/
if(rc!=0)rcode=1;
printf("##Applicationterminated,rc=0x%lX\n",rc);/*如果是运行linux,这条指令是否能运行?*/
returnrcode;
}
go命令调用do_go()函数,跳转到某个地址执行的。

如果在这个地址准备好了自引导的内核映像,就可以启动了。

尽管go命令可以带变参,实际使用时不用来传递参数。

2.bootm命令的实现
/*common/cmd_bootm.c*/
intdo_bootm(cmd_tbl_t*cmdtp,intflag,intargc,char*argv[])
{
…………
/*检查头部*/
if(crc32(0,(uchar*)data,len)!=checksum){
puts("BadHeaderChecksum\n");
SHOW_BOOT_PROGRESS(-2);
return1;
}
…………
/*解压缩*/
switch(hdr->ih_comp){
case IH_COMP_NONE:
if(ntohl(hdr->ih_load)==addr){
printf("XIP%s...",name);
}else{
#ifdefined(CONFIG_HW_WATCHDOG)||defined(CONFIG_WATCHDOG)
size_tl=len;
void*to=(void*)ntohl(hdr->ih_load);
void*from=(void*)data;
printf("Loading%s...",name);
while(l>0){
size_ttail=(l>CHUNKSZ)?CHUNKSZ:l;
WATCHDOG_RESET();
memmove(to,from,tail);
to+=tail;
from+=tail;
l-=tail;
}
#else /*!(CONFIG_HW_WATCHDOG||CONFIG_WATCHDOG)*/
memmove((void*)ntohl(hdr->ih_load),(uchar*)data,len);
#endif /*CONFIG_HW_WATCHDOG||CONFIG_WATCHDOG*/
}
break;
case IH_COMP_GZIP:
printf("Uncompressing%s...",name);
if(gunzip((void*)ntohl(hdr->ih_load),unc_len,
(uchar*)data,&len)!=0){
puts("GUNZIPERROR-mustRESETboardtorecover\n");
SHOW_BOOT_PROGRESS(-6);
do_reset(cmdtp,flag,argc,argv);
}
break;
#ifdef CONFIG_BZIP2
caseIH_COMP_BZIP2:
printf("Uncompressing%s...",name);
/*
*Ifwe'vegotlessthan4MBofmalloc()space,
*useslowerdecompressionalgorithmwhichrequires
*atmost2300KBofmemory.
*/
i=BZ2_bzBuffToBuffDecompress((char*)ntohl(hdr->ih_load),
&unc_len,(char*)data,len,
CFG_MALLOC_LEN<(4096*1024),0);
if(i!=BZ_OK){
printf("BUNZIP2ERROR%d-mustRESETboardtorecover\n",i);
SHOW_BOOT_PROGRESS(-6);
udelay(100000);
do_reset(cmdtp,flag,argc,argv);
}
break;
#endif/*CONFIG_BZIP2*/
default:
if(iflag)
enable_interrupts();
printf("Unimplementedcompressiontype%d\n",hdr->ih_comp);
SHOW_BOOT_PROGRESS(-7);
return1;
}
}
………………
switch(hdr->ih_os){
default: /*handledby(original)Linuxcase*/
case IH_OS_LINUX:
do_bootm_linux(cmdtp,flag,argc,argv,
addr,len_ptr,verify);
break;
case IH_OS_NETBSD:
do_bootm_netbsd(cmdtp,flag,argc,argv,
addr,len_ptr,verify);
break;
case IH_OS_RTEMS:
do_bootm_rtems(cmdtp,flag,argc,argv,
addr,len_ptr,verify);
break;
case IH_OS_VXWORKS:
do_bootm_vxworks(cmdtp,flag,argc,argv,
addr,len_ptr,verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf(cmdtp,flag,argc,argv,
addr,len_ptr,verify);
break;
}
bootm命令调用do_bootm函数。

这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。

引导Linux的时候,调用do_bootm_linux()函数。

3.do_bootm_linux函数的实现
/*lib_arm/armlinux.c*/
voiddo_bootm_linux(cmd_tbl_t*cmdtp,intflag,intargc,char*argv[],
ulongaddr,ulong*len_ptr,intverify)
{
theKernel=(void(*)(int,int,uint))ntohl(hdr->ih_ep);
…………
/*weassumethatthekernelisinplace*/
printf("\nStartingkernel...\n\n");
…………
theKernel(0,bd->bi_arch_number,bd->bi_boot_params);/*启动内核,传递启动参数*/ }
do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。

这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。

U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。

U-Boot启动过程完全分析
1.1U-Boot工作过程
U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下:
(1)第一阶段的功能
✍硬件设备初始化
✍加载U-Boot第二阶段代码到RAM空间
✍设置好栈
✍跳转到第二阶段代码入口
(2)第二阶段的功能
✍初始化本阶段使用的硬件设备
✍检测系统内存映射
✍将内核从Flash读取到RAM中
✍为内核设置启动参数
✍调用内核
1.1.1U-Boot启动第一阶段代码分析第一阶段对应的文件是cpu/arm920t/start.S和
board/samsung/mini2440/lowlevel_init.S。

U-Boot启动第一阶段流程如下:
图2.1U-Boot启动第一阶段流程
根据cpu/arm920t/u-boot.lds中指定的连接方式:
ENTRY(_start)
SECTIONS
{
.=0x00000000;
.=ALIGN(4);
.text:
{
cpu/arm920t/start.o(.text)
board/samsung/mini2440/lowlevel_init.o(.text)
board/samsung/mini2440/nand_read.o(.text)
*(.text)
}
……
}
第一个链接的是cpu/arm920t/start.o,因此u-boot.bin的入口代码在cpu/arm920t/start.o中,其源代码在cpu/arm920t/start.S中。

下面我们来分析cpu/arm920t/start.S的执行。

1.硬件设备初始化
(1)设置异常向量
cpu/arm920t/start.S开头有如下的代码:
.globl_start
_start:bstart_code/*复位*/
ldrpc,_undefined_instruction/*未定义指令向量*/
ldrpc,_software_interrupt/*软件中断向量*/
ldrpc,_prefetch_abort/*预取指令异常向量*/
ldrpc,_data_abort/*数据操作异常向量*/
ldrpc,_not_used/*未使用*/
/*
*setthecputoSVC32mode
*/
mrsr0,cpsr
bicr0,r0,#0x1f/*工作模式位清零*/
orrr0,r0,#0xd3/*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1*/ msrcpsr,r0
以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ 中断。

(3)设置控制寄存器地址
#ifdefined(CONFIG_S3C2400)
#definepWTCON
#defineINTMSK
#defineCLKDIVN
#else/*s3c2410与s3c2440下面4个寄存器地址相同*/
#definepWTCON/*WATCHDOG控制寄存器地址*/
#defineINTMSK0x4A000008/*INTMSK寄存器地址*/
#defineINTSUBMSK0x4A00001C/*INTSUBMSK寄存器地址*/
#defineCLKDIVN0x4C000014/*CLKDIVN寄存器地址*/
#endif
对与s3c2440开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置。

各个寄存器地址参见参考文献[4]。

(4)关闭看门狗
ldrr0,=pWTCON
movr1,#0x0
strr1,[r0]/*看门狗控制器的最低位为0时,看门狗不输出复位信号*/
以上代码向看门狗控制寄存器写入0,关闭看门狗。

否则在U-Boot启动过程中,CPU将不断重启。

(5)屏蔽中断
/*
*maskallIRQsbysettingallbitsintheINTMR-default
*/
movr1,#0xffffffff/*某位被置1则对应的中断被屏蔽*/
ldrr0,=INTMSK
strr1,[r0]
INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代
表的中断请求是否被CPU所处理。

根据参考文献4,INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK 寄存器全部位置一,从而屏蔽对应的中断。

#ifdefined(CONFIG_S3C2440)
ldrr1,=0x7fff
ldrr0,=INTSUBMSK
strr1,[r0]
#endif
INTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。

根据参考文献4,INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。

向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,从而屏蔽对应的中断。

(6)设置MPLLCON,UPLLCON,CLKDIVN
#ifdefined(CONFIG_S3C2440)
#defineMPLLCON0x4C000004
#defineUPLLCON0x4C000008
ldrr0,=CLKDIVN
movr1,#5
strr1,[r0]
ldrr0,=MPLLCON
ldrr1,=0x7F021
strr1,[r0]
ldrr0,=UPLLCON
ldrr1,=0x38022
strr1,[r0]
#else
/*FCLK:HCLK:PCLK=1:2:4*/
/*defaultFCLKis120MHz!*/
ldrr0,=CLKDIVN
movr1,#3
strr1,[r0]
#endif
CPU上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),CPU开始执行指令。

但实际上,FCLK可以高于Fin,为了提高系统时钟,需要用软件来启用PLL。

这就需要设置CLKDIVN,MPLLCON,UPLLCON这3个寄存器。

)
320#ifndefCONFIG_SKIP_LOWLEVEL_INIT
321cpu_init_crit:
322/*
323*使数据cache与指令cache无效*/
324*/
325movr0,#0
326mcrp15,0,r0,c7,c7,0/*向c7写入0将使ICache与DCache无效*/
327mcrp15,0,r0,c8,c7,0/*向c8写入0将使TLB失效*/
328
329/*
330*disableMMUstuffandcaches
331*/
332mrcp15,0,r0,c1,c0,0/*读出控制寄存器到r0中*/
333bicr0,r0,#0x00002300@clearbits13,9:8(--V---RS)
334bicr0,r0,#0x00000087@clearbits7,2:0(B----CAM)
335orrr0,r0,#0x00000002@setbit2(A)Align
336orrr0,r0,#0x00001000@setbit12(I)I-Cache
337mcrp15,0,r0,c1,c0,0/*保存r0到控制寄存器*/
338
339/*
340*beforerelocating,wehavetosetupRAMtiming
341*becausememorytimingisboard-dependend,youwill
342*findalowlevel_init.Sinyourboarddirectory.
343*/
344movip,lr
345
346bllowlevel_init
347
348movlr,ip
349movpc,lr
350#endif/*CONFIG_SKIP_LOWLEVEL_INIT*/
代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。

其中c7是cache控制寄存器,c8是TLB 控制寄存器。

325~327行代码将0写入c7、c8,使Cache,TLB内容无效。

第332~337行代码关闭了MMU。

这是通过修改CP15的c1寄存器来实现的,先看CP15的c1寄存器的格式(仅列出代码中用到的位):
146bne0b
147
148/*everythingisfinenow*/
149movpc,lr
150
151.ltorg
152/*theliteralpoolsorigin*/
153
154SMRDATA:/*下面是13个寄存器的值*/
155.word……
156.word……
……
lowlevel_init初始化了13个寄存器来实现RAM时钟的初始化。

lowlevel_init函数对于U-Boot从NANDFlash 或NORFlash启动的情况都是有效的。

U-Boot.lds链接脚本有如下代码:
.text:
{
cpu/arm920t/start.o(.text)
board/samsung/mini2440/lowlevel_init.o(.text)
board/samsung/mini2440/nand_read.o(.text)
……
}
board/samsung/mini2440/lowlevel_init.o将被链接到cpu/arm920t/start.o后面,因此
board/samsung/mini2440/lowlevel_init.o也在U-Boot的前4KB的代码中。

U-Boot在NANDFlash启动时,lowlevel_init.o将自动被读取到CPU内部4KB的内部RAM中。

因此第
137~146行的代码将从CPU内部RAM中复制寄存器的值到相应的寄存器中。

对于U-Boot在NORFlash启动的情况,由于U-Boot连接时确定的地址是U-Boot在内存中的地址,而此时U-Boot还在NORFlash中,因此还需要在NORFlash中读取数据到RAM中。

由于NORFlash的开始地址是0,而U-Boot的加载到内存的起始地址是TEXT_BASE,SMRDATA标号在Flash 的地址就是SMRDATA-TEXT_BASE。

综上所述,lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址[BWSCON]的13个寄存器,从而完成了存储控制器的设置。

(9)复制U-Boot第二阶段代码到RAM
cpu/arm920t/start.S原来的代码是只支持从NORFlash启动的,经过修改现在U-Boot在NORFlash和NANDFlash上都能启动了,实现的思路是这样的:
blbBootFrmNORFlash/*判断U-Boot是在NANDFlash还是NORFlash启动*/
cmpr0,#0/*r0存放bBootFrmNORFlash函数返回值,若返回0表示NANDFlash启动,否则表示在NORFlash 启动*/
beqnand_boot/*跳转到NANDFlash启动代码*/
/*NORFlash启动的代码*/
bstack_setup/*跳过NANDFlash启动的代码*/
nand_boot:
/*NANDFlash启动的代码*/
stack_setup:
/*其他代码*/
其中bBootFrmNORFlash函数作用是判断U-Boot是在NANDFlash启动还是NORFlash启动,若在NORFlash 启动则返回1,否则返回0。

根据ATPCS规则,函数返回值会被存放在r0寄存器中,因此调用bBootFrmNORFlash 函数后根据r0的值就可以判断U-Boot在NANDFlash启动还是NORFlash启动。

bBootFrmNORFlash函数在board/samsung/mini2440/nand_read.c中定义如下:
intbBootFrmNORFlash(void)
{
volatileunsignedint*pdw=(volatileunsignedint*)0;
unsignedintdwVal;
dwVal=*pdw;/*先记录下原来的数据*/
*pdw=
if(*pdw!=/*写入失败,说明是在NORFlash启动*/
{
return1;
}
else/*写入成功,说明是在NANDFlash启动*/
{
*pdw=dwVal;/*恢复原来的数据*/
return0;
}
}
无论是从NORFlash还是从NANDFlash启动,地址0处为U-Boot的第一条指令“bstart_code”。

对于从NANDFlash启动的情况,其开始4KB的代码会被自动复制到CPU内部4K内存中,因此可以通过直接赋值的方法来修改。

对于从NORFlash启动的情况,NORFlash的开始地址即为0,必须通过一定的命令序列才能向NORFlash中写数据,所以可以根据这点差别来分辨是从NANDFlash还是NORFlash启动:向地址0写入一个数据,然后读出
来,如果发现写入失败的就是NORFlash,否则就是NANDFlash。

下面来分析NORFlash启动部分代码:
208adrr0,_start/*r0<-currentpositionofcode*/
209ldrr1,_TEXT_BASE/*testifwerunfromflashorRAM*/
/*判断U-Boot是否是下载到RAM中运行,若是,则不用再复制到RAM中了,这种情况通常在调试U-Boot时才发生*/
210cmpr0,r1/*_start等于_TEXT_BASE说明是下载到RAM中运行*/
211beqstack_setup
212/*以下直到nand_boot标号前都是NORFlash启动的代码*/
213ldrr2,_armboot_start
214ldrr3,_bss_start
215subr2,r3,r2/*r2<-sizeofarmboot*/
216addr2,r0,r2/*r2<-sourceendaddress*/
217/*搬运U-Boot自身到RAM中*/
218copy_loop:
219ldmiar0!,{r3-r10}/*从地址为[r0]的NORFlash中读入8个字的数据*/
220stmiar1!,{r3-r10}/*将r3至r10寄存器的数据复制给地址为[r1]的内存*/
221cmpr0,r2/*untilsourceendaddreee[r2]*/
222blecopy_loop
223bstack_setup/*跳过NANDFlash启动的代码*/
下面再来分析NANDFlash启动部分代码:
nand_boot:
movr1,#NAND_CTL_BASE
ldrr2,=((7<<12)|(7<<8)|(7<<4)|(0<<0))
strr2,[r1,#oNFCONF]/*设置NFCONF寄存器*/
/*设置NFCONT,初始化ECC编/解码器,禁止NANDFlash片选*/
ldrr2,=((1<<4)|(0<<1)|(1<<0))
strr2,[r1,#oNFCONT]
ldrr2,=(0x6)/*设置NFSTAT*/
strr2,[r1,#oNFSTAT]
/*复位命令,第一次使用NANDFlash前复位*/
movr2,#0xff
strbr2,[r1,#oNFCMD]
movr3,#0
/*为调用C函数nand_read_ll准备堆栈*/
ldrsp,DW_STACK_START
movfp,#0
/*下面先设置r0至r2,然后调用nand_read_ll函数将U-Boot读入RAM*/
ldrr0,=TEXT_BASE/*目的地址:U-Boot在RAM的开始地址*/
movr1,#0x0/*源地址:U-Boot在NANDFlash中的开始地址*/
movr2,#0x30000/*复制的大小,必须比u-boot.bin文件大,并且必须是NANDFlash块大小的整数倍,这里设置为0x30000(192KB)*/
blnand_read_ll/*跳转到nand_read_ll函数,开始复制U-Boot到RAM*/
tstr0,#0x0/*检查返回值是否正确*/
beqstack_setup
bad_nand_read:
loop2:bloop2//infiniteloop
.align2
DW_STACK_START:.wordSTACK_BASE+STACK_SIZE-4
其中NAND_CTL_BASE,oNFCONF等在include/configs/mini2440.h中定义如下:
#defineNAND_CTL_BASE0x4E000000//NANDFlash控制寄存器基址
#defineSTACK_BASE0x33F00000//baseaddressofstack
#defineSTACK_SIZE0x8000//sizeofstack
#defineoNFCONF0x00/*NFCONF相对于NAND_CTL_BASE偏移地址*/
#defineoNFCONT0x04/*NFCONT相对于NAND_CTL_BASE偏移地址*/
#defineoNFADDR0x0c/*NFADDR相对于NAND_CTL_BASE偏移地址*/
#defineoNFDATA0x10/*NFDATA相对于NAND_CTL_BASE偏移地址*/
#defineoNFCMD0x08/*NFCMD相对于NAND_CTL_BASE偏移地址*/
#defineoNFSTAT0x20/*NFSTAT相对于NAND_CTL_BASE偏移地址*/
#defineoNFECC0x2c/*NFECC相对于NAND_CTL_BASE偏移地址*/
NANDFlash各个控制寄存器的设置在S3C2440的数据手册有详细说明,这里就不介绍了。

代码中nand_read_ll函数的作用是在NANDFlash中搬运U-Boot到RAM,该函数在
board/samsung/mini2440/nand_read.c中定义。

NANDFlash根据page大小可分为2种:512B/page和2048B/page的。

这两种NANDFlash的读操作是不同的。

因此就需要U-Boot识别到NANDFlash的类型,然后采用相应的读操作,也就是说nand_read_ll函数要能自动适应两种NANDFlash。

参考S3C2440的数据手册可以知道:根据NFCONF寄存器的Bit3(AdvFlash(Readonly))和Bit2(PageSize(Readonly))可以判断NANDFlash的类型。

Bit2、Bit3与NANDFlash的block类型的关系如下表所示:
表2.4NFCONF的Bit3、Bit2与NANDFlash的关系
Bit2Bit301
0256B/page512B/page
11024B/page2048B/page
由于的NANDFlash只有512B/page和2048B/page这两种,因此根据NFCONF寄存器的Bit3即可区分这两种NANDFlash了。

完整代码见board/samsung/mini2440/nand_read.c中的nand_read_ll函数,这里给出伪代码:intnand_read_ll(unsignedchar*buf,unsignedlongstart_addr,intsize)
{
//根据NFCONF寄存器的Bit3来区分2种NANDFlash
if(NFCONF&0x8)/*Bit是1,表示是2KB/page的NANDFlash*/
{
////////////////////////////////////
读取2Kblock的NANDFlash
////////////////////////////////////
}
else/*Bit是0,表示是512B/page的NANDFlash*/
{
/////////////////////////////////////
读取512Bblock的NANDFlash
/////////////////////////////////////
}
return0;
}
(10)设置堆栈
/*设置堆栈*/
stack_setup:
ldrr0,_TEXT_BASE/*upper128KiB:relocateduboot*/
subr0,r0,#CONFIG_SYS_MALLOC_LEN/*mallocarea*/
subr0,r0,#CONFIG_SYS_GBL_DATA_SIZE/*跳过全局数据区*/
#ifdefCONFIG_USE_IRQ
subr0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
subsp,r0,#12/*leave3wordsforabort-stack*/
只要将sp指针指向一段没有被使用的内存就完成栈的设置了。

根据上面的代码可以知道U-Boot内存使用情况了,如下图所示:
图2.2U-Boot内存使用情况
(11)清除BSS段
clear_bss:
ldrr0,_bss_start/*BSS段开始地址,在u-boot.lds中指定*/
ldrr1,_bss_end/*BSS段结束地址,在u-boot.lds中指定*/
movr2,#0x00000000
clbss_l:strr2,[r0]/*将bss段清零*/
addr0,r0,#4
cmpr0,r1
bleclbss_l
初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。

应该将这些变量的初始值赋为0,否则这些变量的初始值将是一个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果。

(12)跳转到第二阶段代码入口
ldrpc,_start_armboot
_start_armboot:.wordstart_armboot
跳转到第二阶段代码入口start_armboot处。

1.1.2U-Boot启动第二阶段代码分析start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。

U-Boot启动第二阶段流程如下:
图2.3U-Boot第二阶段执行流程
在分析start_armboot函数前先来看看一些重要的数据结构:
(1)gd_t结构体
U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h 中定义如下:
typedefstructglobal_data{
bd_t*bd;
unsignedlongflags;
unsignedlongbaudrate;
unsignedlonghave_console;/*serial_init()wascalled*/
unsignedlongenv_addr;/*AddressofEnvironmentstruct*/
unsignedlongenv_valid;/*ChecksumofEnvironmentvalid?*/
unsignedlongfb_base;/*baseaddressofframebuffer*/
void**jt;/*jumptable*/
}gd_t;
U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
#defineDECLARE_GLOBAL_DATA_PTRregistervolatilegd_t*gdasm("r8")
DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。

这个声明也避免编译器把r8分配给其它的变量。

任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。

根据U-Boot内存使用图中可以计算gd的值:
gd=TEXT_BASE-CONFIG_SYS_MALLOC_LEN-sizeof(gd_t)
(2)bd_t结构体
bd_t在include/asm-arm.u/u-boot.h中定义如下:
typedefstructbd_info{
intbi_baudrate;/*串口通讯波特率*/
unsignedlongbi_ip_addr;/*IP地址*/
structenvironment_s*bi_env;/*环境变量开始地址*/
ulongbi_arch_number;/*开发板的机器码*/
ulongbi_boot_params;/*内核参数的开始地址*/
struct/*RAM配置信息*/
{
ulongstart;
ulongsize;
}bi_dram[CONFIG_NR_DRAM_BANKS];
}bd_t;
U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。

(3)init_sequence数组
U-Boot使用一个数组init_sequence来存储对于大多数开发板都要执行的初始化函数的函数指针。

init_sequence数组中有较多的编译选项,去掉编译选项后init_sequence数组如下所示:
typedefint(init_fnc_t)(void);
init_fnc_t*init_sequence[]={
board_init,/*开发板相关的配置--board/samsung/mini2440/mini2440.c*/
timer_init,/*时钟初始化--cpu/arm920t/s3c24x0/timer.c*/
env_init,/*初始化环境变量--common/env_flash.c或common/env_nand.c*/
init_baudrate,/*初始化波特率--lib_arm/board.c*/
serial_init,/*串口初始化--drivers/serial/serial_s3c24x0.c*/
console_init_f,/*控制通讯台初始化阶段1--common/console.c*/
display_banner,/*打印U-Boot版本、编译的时间--geditlib_arm/board.c*/
dram_init,/*配置可用的RAM--board/samsung/mini2440/mini2440.c*/
display_dram_config,/*显示RAM大小--lib_arm/board.c*/
NULL,
};
其中的board_init函数在board/samsung/mini2440/mini2440.c中定义,该函数设置了MPLLCOM,UPLLCON,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核启动参数地址:/*MINI2440开发板的机器码*/
gd->bd->bi_arch_number=MACH_TYPE_MINI2440;
/*内核启动参数地址*/
gd->bd->bi_boot_params=
其中的dram_init函数在board/samsung/mini2440/mini2440.c中定义如下:
intdram_init(void)
{
/*由于mini2440只有*/
gd->bd->bi_dram[0].start=PHYS_SDRAM_1;
gd->bd->bi_dram[0].size=PHYS_SDRAM_1_SIZE;
return0;
}
mini2440使用2片32MB的SDRAM组成了64MB的内存,接在存储控制器的BANK6,地址空间是。

在include/configs/mini2440.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE分别被定义为和0x04000000(64M)。

分析完上述的数据结构,下面来分析start_armboot函数:
voidstart_armboot(void)
{
init_fnc_t**init_fnc_ptr;
char*s;
……
/*计算全局数据结构的地址gd*/
gd=(gd_t*)(_armboot_start-CONFIG_SYS_MALLOC_LEN-sizeof(gd_t));
……
memset((void*)gd,0,sizeof(gd_t));
gd->bd=(bd_t*)((char*)gd-sizeof(bd_t));
memset(gd->bd,0,sizeof(bd_t));
gd->flags|=GD_FLG_RELOC;
monitor_flash_len=_bss_start-_armboot_start;
/*逐个调用init_sequence数组中的初始化函数*/
for(init_fnc_ptr=init_sequence;*init_fnc_ptr;++init_fnc_ptr){
if((*init_fnc_ptr)()!=0){
hang();
}
}
/*armboot_start在cpu/arm920t/start.S中被初始化为u-boot.lds连接脚本中的_start*/ mem_malloc_init(_armboot_start-CONFIG_SYS_MALLOC_LEN,
CONFIG_SYS_MALLOC_LEN);
/*NORFlash初始化*/
#ifndefCONFIG_SYS_NO_FLASH
/*configureavailableFLASHbanks*/
display_flash_config(flash_init());
#endif/*CONFIG_SYS_NO_FLASH*/
……
/*NANDFlash初始化*/
#ifdefined(CONFIG_CMD_NAND)
puts("NAND:");
nand_init();/*goinittheNAND*/
#endif
……
/*配置环境变量,重新定位*/
env_relocate();
……
/*从环境变量中获取IP地址*/
gd->bd->bi_ip_addr=getenv_IPaddr("ipaddr");
stdio_init();/*getthedeviceslistgoing.*/
jumptable_init();
……
console_init_r();/*fullyinitconsoleasadevice*/
……
/*enableexceptions*/
enable_interrupts();
#ifdefCONFIG_USB_DEVICE
usb_init_slave();
#endif
/*Initializefromenvironment*/
if((s=getenv("loadaddr"))!=NULL){
load_addr=simple_strtoul(s,NULL,16);
}
#ifdefined(CONFIG_CMD_NET)
if((s=getenv("bootfile"))!=NULL){
copy_filename(BootFile,s,sizeof(BootFile));
}
#endif
……
/*网卡初始化*/
#ifdefined(CONFIG_CMD_NET)
#ifdefined(CONFIG_NET_MULTI)
puts("Net:");
#endif
eth_initialize(gd->bd);
……
#endif
/*main_loop()canreturntoretryautoboot,ifsojustrunitagain.*/
for(;;){
main_loop();
}
/*NOTREACHED-nowayoutofcommandloopexceptbooting*/
}
main_loop函数在common/main.c中定义。

一般情况下,进入main_loop函数若干秒内没有
1.1.3U-Boot启动Linux过程U-Boot使用标记列表(taggedlist)的方式向Linux传递参数。

标记的数据结构式是tag,在U-Boot源代码目录include/asm-arm/setup.h中定义如下:
structtag_header{
u32size;/*表示tag数据结构的联合u实质存放的数据的大小*/
u32tag;/*表示标记的类型*/
};
structtag{
structtag_headerhdr;
union{
structtag_corecore;
structtag_mem32mem;
structtag_videotextvideotext;
structtag_ramdiskramdisk;
structtag_initrdinitrd;
structtag_serialnrserialnr;
structtag_revisionrevision;
structtag_videolfbvideolfb;
structtag_cmdlinecmdline;
/*
*Acornspecific
*/
structtag_acornacorn;
/*
*DC21285specific
*/
structtag_memclkmemclk;
}u;
};
U-Boot使用命令bootm来启动已经加载到内存中的内核。

而bootm命令实际上调用的是do_bootm函数。

对于Linux内核,do_bootm函数会调用do_bootm_linux函数来设置标记列表和启动内核。

do_bootm_linux 函数在lib_arm/bootm.c中定义如下:
59intdo_bootm_linux(intflag,intargc,char*argv[],bootm_headers_t*images)
60{
61bd_t*bd=gd->bd;
62char*s;
63intmachid=bd->bi_arch_number;
64void(*theKernel)(intzero,intarch,uintparams);
65
66#ifdefCONFIG_CMDLINE_TAG
67char*commandline=getenv("bootargs");/*U-Boot环境变量bootargs*/
68#endif
……
73theKernel=(void(*)(int,int,uint))images->ep;/*获取内核入口地址*/
……
86#ifdefined(CONFIG_SETUP_MEMORY_TAGS)||\
87defined(CONFIG_CMDLINE_TAG)||\
88defined(CONFIG_INITRD_TAG)||\
89defined(CONFIG_SERIAL_TAG)||\
90defined(CONFIG_REVISION_TAG)||\
91defined(CONFIG_LCD)||\
92defined(CONFIG_VFD)
93setup_start_tag(bd);/*设置ATAG_CORE标志*/
……
100#ifdefCONFIG_SETUP_MEMORY_TAGS
101setup_memory_tags(bd);/*设置内存标记*/
102#endif
103#ifdefCONFIG_CMDLINE_TAG
104setup_commandline_tag(bd,commandline);/*设置命令行标记*/
105#endif
……
113setup_end_tag(bd);/*设置ATAG_NONE标志*/
114#endif
115
116/*weassumethatthekernelisinplace*/
117printf("\nStartingkernel...\n\n");
……
126cleanup_before_linux();/*启动内核前对CPU作最后的设置*/
127
128theKernel(0,machid,bd->bi_boot_params);/*调用内核*/
129/*doesnotreturn*/
130
131return1;
132}
其中的setup_start_tag,setup_memory_tags,setup_end_tag函数在lib_arm/bootm.c中定义如下:(1)setup_start_tag函数
staticvoidsetup_start_tag(bd_t*bd)
{
params=(structtag*)bd->bi_boot_params;/*内核的参数的开始地址*/
params->hdr.tag=ATAG_CORE;
params->hdr.size=tag_size(tag_core);
=0;
=0;
=0;
params=tag_next(params);
}
标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE 标记。

(2)setup_memory_tags函数
staticvoidsetup_memory_tags(bd_t*bd)
{
inti;
/*设置一个内存标记*/
for(i=0;i<CONFIG_NR_DRAM_BANKS;i++){
params->hdr.tag=ATAG_MEM;
params->hdr.size=tag_size(tag_mem32);
=bd->bi_dram[i].start;
=bd->bi_dram[i].size;
params=tag_next(params);
}
}
setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。

(3)setup_end_tag函数
staticvoidsetup_end_tag(bd_t*bd)
{
params->hdr.tag=ATAG_NONE;
params->hdr.size=0;
}
标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。

U-Boot设置好标记列表后就要调用内核了。

但调用内核前,CPU必须满足下面的条件:
(1)CPU寄存器的设置
✍r0=0
✍r1=机器码
✍r2=内核参数标记列表在RAM中的起始地址
(2)CPU工作模式
✍禁止IRQ与FIQ中断
✍CPU为SVC模式
(3)使数据Cache与指令Cache失效
do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。

cleanup_before_linux函数在cpu/arm920t/cpu.中定义:
intcleanup_before_linux(void)
{
/*
*thisfunctioniscalledjustbeforewecalllinux
*itpreparestheprocessorforlinux
*
*weturnoffcachesetc...
*/
disable_interrupts();/*禁止FIQ/IRQ中断*/
/*turnoffI/D-cache*/
icache_disable();/*使指令Cache失效*/
dcache_disable();/*使数据Cache失效*/
/*flushI/D-cache*/
cache_flush();/*刷新Cache*/
return0;
}
由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。

do_bootm_linux中:
64void(*theKernel)(intzero,intarch,uintparams);
……
73theKernel=(void(*)(int,int,uint))images->ep;
……
128theKernel(0,machid,bd->bi_boot_params);
第73行代码将内核的入口地址“images->ep”强制类型转换为函数指针。

根据ATPCS规则,函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。

因此第128行的函数调用则会将0放入r0,机器码machid 放入r1,内核参数地址bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址。

到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。

1.1.4U-Boot添加命令的方法及U-Boot命令执行过程下面以添加menu命令(启动菜单)为例讲解U-Boot添加命令的方法。

(1)建立common/cmd_menu.c
习惯上通用命令源代码放在common目录下,与开发板专有命令源代码则放在board/<board_dir>目录下,并且习惯以“cmd_<命令名>.c”为文件名。

(2)定义“menu”命令
在cmd_menu.c中使用如下的代码定义“menu”命令:
_BOOT_CMD(
menu,3,0,do_menu,
"menu-displayamenu,toselecttheitemstodosomething\n",
"-displayamenu,toselecttheitemstodosomething"
);
其中U_BOOT_CMD命令格式如下:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
各个参数的意义如下:
name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
maxargs:命令的最大参数个数
rep:是否自动重复(按Enter键是否会重复执行)
cmd:该命令对应的响应函数
usage:简短的使用说明(字符串)
help:较详细的使用说明(字符串)
在内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。

若在include/configs/mini2440.h中定义了CONFIG_SYS_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示usage和help字段的内容,否则就只显示usage字段的内容。

U_BOOT_CMD宏在include/command.h中定义:
#defineU_BOOT_CMD(name,maxargs,rep,cmd,usage,help)\
cmd_tbl_t__u_boot_cmd_##nameStruct_Section={#name,maxargs,rep,cmd,usage,help}
“##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串。

其中的cmd_tbl_t在include/command.h中定义如下:
structcmd_tbl_s{
char*name;/*命令名*/
intmaxargs;/*最大参数个数*/
intrepeatable;/*是否自动重复*/
int(*cmd)(structcmd_tbl_s*,int,int,char*[]);/*响应函数*/
char*usage;/*简短的帮助信息*/
#ifdefCONFIG_SYS_LONGHELP
char*help;/*较详细的帮助信息*/
#endif
#ifdefCONFIG_AUTO_COMPLETE
/*自动补全参数*/
int(*complete)(intargc,char*argv[],charlast_char,intmaxv,char*cmdv[]);
#endif
};
typedefstructcmd_tbl_scmd_tbl_t;
一个cmd_tbl_t结构体变量包含了调用一条命令的所需要的信息。

其中Struct_Section在include/command.h中定义如下:
#defineStruct_Section__attribute__((unused,section(".u_boot_cmd")))
凡是带有__attribute__((unused,section(".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。

在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段:
.=.;
__u_boot_cmd_start=.;/*将__u_boot_cmd_start指定为当前地址*/
.u_boot_cmd:{*(.u_boot_cmd)}
__u_boot_cmd_end=.;/*将__u_boot_cmd_end指定为当前地址*/
这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。

这样只要将U-Boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在__u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了。

因此“menu”命令的定义经过宏展开后如下:
cmd_tbl_t__u_boot_cmd_menu__attribute__((unused,section(".u_boot_cmd")))={menu,3,0,do _menu,"menu-displayamenu,toselecttheitemstodosomething\n","-displayamenu,toselecttheitems todosomething"}
实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。

编译器将该结构体放在
“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的cmd_tbl_t类型结构体。

(3)实现命令的函数
在cmd_menu.c中添加“menu”命令的响应函数的实现。

具体的实现代码略:
intdo_menu(cmd_tbl_t*cmdtp,intflag,intargc,char*argv[])
{
/*实现代码略*/
}
(4)将common/cmd_menu.c编译进u-boot.bin
在common/Makefile中加入如下代码:
COBJS-$(CONFIG_BOOT_MENU)+=cmd_menu.o。

相关文档
最新文档