转载:http://blog.chinaunix.net/uid-28236237-id-3865045.html
u-boot第一阶段分析(一)
u-boot 第一阶段分析(二)
u-boot 第二阶段分析
U-boot第一阶段分析
u-boot是在单板刚启动的时候执行的一段程序。其作用是将内核从flash、硬盘等介质上拷贝到内存中,并传递给内核一些启动参数并跳转到内核去执行。
由于u-boot的代码非常的依赖于不 同的体系结构。对u-boot的分析也只能提炼出不同体系结构的共性,做相应的总结。总的说来,u-boot所做到工作分为两个阶段。第一个阶段是将u- boot第二阶段的代码拷贝到ram空间,跳转到第二阶段代码执行。第二阶段是将内核代码拷贝到ram并跳转到内核去执行。
具体执行过程中做到操作如下
· 第一阶段的功能
1. 硬件设备初始化
2. 加载U-Boot第二阶段代码到RAM空间
3. 设置好栈
4. 跳转到第二阶段代码入口
· 第二阶段的功能
5. 初始化本阶段使用的硬件设备
6. 检测系统内存映射
7. 将内核从Flash读取到RAM中
8. 为内核设置启动参数
9. 调用内核
下面以ARM9 u-boot执行过程做一个分析,之后如果有时间再继续分析mips架构的u-boot执行过程,由于u-boot中涉及到许多汇编和体系结构的知识,对我们学习不同的体系结构有很大的帮助。如果相关知识不熟悉,阅读代码的时候会遇到很多疑惑。
第一阶段--硬件设备初始
.globl _start
_start:b reset
ldrpc, _undefined_instruction
ldrpc, _software_interrupt
ldrpc, _prefetch_abort
ldrpc, _data_abort
ldrpc, _not_used
ldrpc, _irq
ldrpc, _fiq
_undefined_instruction:.word undefined_instruction
_software_interrupt:.word software_interrupt
_prefetch_abort:.word prefetch_abort
_data_abort:.word data_abort
_not_used:.word not_used
_irq:.word irq
_fiq:.word fiq
.balignl 16,0xdeadbeef
地址 |
异常 |
进入模式 |
描述 |
0x00000000 |
复位 |
管理模式 |
复位电平效时,产生复位异常,程序跳转到复位处理程序处执行 |
0x00000004 |
未定义指令 |
未定义模式 |
遇到不能处理的指令时,产生未定义指令异常 |
0x00000008 |
软件中断 |
管理模式 |
执行SWI指令产生,用于用户模式下的程序调用特权操作指令 |
0x0000000c |
预存指令 |
中止模式 |
处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常 |
0x00000010 |
数据操作 |
中止模式 |
处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常 |
0x00000014 |
未使用 |
未使用 |
未使用 |
0x00000018 |
IRQ |
IRQ |
外部中断请求效,且CPSR中的I位为0时,产生IRQ异常 |
0x0000001c |
FIQ |
FIQ |
快速中断请求引脚效,且CPSR中的F位为0时,产生FIQ异常 |
在之前的文章中曾经提及过arm处理器有8中运行方式,上电后,cpu首先跳转到0x00000000 地址开始执行
reset:
/*
* set the cpu to SVC32 mode
*/
mrsr0,cpsr
bicr0,r0,#0x1f
orrr0,r0,#0xd3
msrcpsr,r0
mrs r0,cpsr //将当前cpsr的状态为保存到r0中。
bic r0,r0,#0x1f //bic,位清零指令。0x1f=00011111,相当于清除低5位。
刚好是模式位。
orr r0,r0,#0xd3 //或指令。置模式位。0xd3=11010011以及设置5,6,7位的
状态位。禁止FIQ,IRQ,处于arm状态。低5位为10011,则对应超级用户态。msr cpsr,r0 //在将r0中的值赋给状态寄存器cps
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON0x15300000
# define INTMSK0x14400008/* Interupt-Controller base addresses */
# define CLKDIVN0x14800014/* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON0x53000000
# define INTMOD 0X4A000004
# define INTMSK0x4A000008/* Interupt-Controller base addresses */
# define INTSUBMSK0x4A00001C
# define CLKDIVN0x4C000014/* clock divisor register */
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
关闭看门狗定时器
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
movr1, #0xffffffff
ldrr0, =INTMSK
strr1, [r0]
# if defined(CONFIG_S3C2410)
ldrr1, =0x3ff
ldrr0, =INTSUBMSK
strr1, [r0]
# endif
关闭禁止各个中断
#ifdef CONFIG_S3C2440
#define MPLLCON 0x4c000004
#define UPLLCON 0x4c000008
ldr r0, =CLKDIVN
mov r1, #5
str r1, [r0]
ldr r0, =MPLLCON
ldr r1, =0x7f021
str r1, [r0]
ldr r0, =UPLLCON
ldr r1, =0x38022
str r1, [r0]
#else
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldrr0, =CLKDIVN
movr1, #3
strr1, [r0]
#endif
设置cpu各个模块的主频大小
#endif/* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
adrr0, _start/* r0 <- current position of code */
ldrr1, _TEXT_BASE/* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
blnecpu_init_crit
#endif
如果cpu还是在flash或者step stone的内存中运行的话,跳转到cpu_init_crit运行
cpu_init_crit清空I cache和D cache并清空掉TLB表项,暂时关闭MMU,跳转到lowlevel_init对ram控制寄存器进行初始化
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
movr0, #0
mcrp15, 0, r0, c7, c7, 0/* flush v3/v4 cache */
mcrp15, 0, r0, c8, c7, 0/* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrcp15, 0, r0, c1, c0, 0
bicr0, r0, #0x00002300@ clear bits 13, 9:8 (--V- --RS)
bicr0, r0, #0x00000087@ clear bits 7, 2:0 (B--- -CAM)
orrr0, r0, #0x00000002@ set bit 2 (A) Align
orrr0, r0, #0x00001000@ set bit 12 (I) I-Cache
mcrp15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
movip, lr
bllowlevel_init
movlr, ip
movpc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
设置堆栈指针
stack_setup:
ldrr0, _TEXT_BASE/* upper 128 KiB: relocated uboot */
subr0, r0, #CFG_MALLOC_LEN
u-boot 第一阶段分析(二)
设置堆栈指针
stack_setup:
ldrr0, _TEXT_BASE/* upper 128 KiB: relocated uboot */
subr0, r0, #CFG_MALLOC_LEN/* malloc area */
subr0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
subr0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
subsp, r0, #12/* leave 3 words for abort-stack */
设置cpu的时钟
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl clock_init
#endif
#define S3C2410_MPLL_200MHZ ((0x5c<<12)|(0x04<<4)|(0x00))
#define S3C2410_UPLL_48MHZ ((0x28<<12)|(0x01<<4)|(0x02))
#define S3C2410_CLKDIV 0x03 /* FCLK:HCLK:PCLK = 1:2:4 */
void clock_init(void)
{
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000;
/* support both of S3C2410 and S3C2440, by www.arm9.net */
if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
{
/* FCLK:HCLK:PCLK = 1:2:4 */
clk_power->CLKDIVN = S3C2410_CLKDIV;
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" /* Asynchronous */
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1"
);
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF;
/* configure UPLL */
clk_power->UPLLCON = S3C2410_UPLL_48MHZ;
/* some delay between MPLL and UPLL */
delay (4000);
/* configure MPLL */
clk_power->MPLLCON = S3C2410_MPLL_200MHZ;
/* some delay between MPLL and UPLL */
delay (8000);
}
else
{
/* FCLK:HCLK:PCLK = 1:4:8 */
clk_power->CLKDIVN = S3C2440_CLKDIV;
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" /* Asynchronous */
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1"
);
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF;
/* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ;
/* some delay between MPLL and UPLL */
delay (4000);
/* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ;
/* some delay between MPLL and UPLL */
delay (8000);
}
}
硬件初始化完成,堆栈设置完成,之后将u-boot第二阶段的代码拷贝到内存
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:/* relocate U-Boot to RAM */
adrr0, _start/* r0 <- current position of code */
ldrr1, _TEXT_BASE/* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq clear_bss
ldrr2, _armboot_start
ldrr3, _bss_start
subr2, r3, r2/* r2 <- size of armboot */
#if 1
bl CopyCode2Ram/* r0: source, r1: dest, r2: size */
#else
addr2, r0, r2/* r2 <- source end address */
copy_loop:
ldmiar0!, {r3-r10}/* copy from source address [r0] */
stmiar1!, {r3-r10}/* copy to target address [r1] */
cmpr0, r2/* until source end addreee [r2] */
blecopy_loop
#endif
#endif/* CONFIG_SKIP_RELOCATE_UBOOT */
上面的代码过程是,首先看uboot是不是已经在内存中运行了,如果是这样直接将bss区间置0就可以了,如果不是运行在内存中设置
r0 为flash起始位置
r1 为希望将u-boot拷贝到的ram位置
r2 u-boot代码段的长度
int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)
{
unsigned int *pdwDest;
unsigned int *pdwSrc;
int i;
if (bBootFrmNORFlash())
{
pdwDest = (unsigned int *)buf;
pdwSrc = (unsigned int *)start_addr;
/* 从 NOR Flash启动 */
for (i = 0; i < size / 4; i++)
{
pdwDest[i] = pdwSrc[i];
}
return 0;
}
else
{
/* 初始化NAND Flash */
nand_init_ll();
/* 从 NAND Flash启动 */
nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK));
return 0;
}
}
将bss区域清0
clear_bss:
ldrr0, _bss_start/* find start of bss segment */
ldrr1, _bss_end/* stop here */
mov r2, #0x00000000/* clear */
clbss_l:strr2, [r0]/* clear loop... */
addr0, r0, #4
cmpr0, r1
bleclbss_l
跳转到内存中执行u-boot的第二阶段过程
ldrpc, _start_armboot
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 */ gd = (gd_t *)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset ((void *)gd, 0, sizeof (gd_t)); gd->bd = (bd_t *)((char *)gd - sizeof(bd_t)); memset (gd->bd, 0, sizeof (bd_t)); monitor_flash_len = _bss_start - _armboot_start; |
我们看到这里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) { if ((*init_fnc_ptr)() != 0) { hang (); } } |
U-boot软件框架定义了这样的一个初始化表,不同的单板具体实现下面的函数进行相应的初始化动作
init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */ board_init, /* basic board dependent setup */ interrupt_init, /* set up exceptions */ env_init, /* initialize environment */ init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_banner, /* say that we are here */ #if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */ #endif #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif dram_init, /* configure available RAM banks */ display_dram_config, NULL, }; |
后续的文章会将上面的初始化函数做逐一的分析,但是本篇本意是想对第二阶段的处理过程做一个提纲挈领的描述,所以暂时举一个例子分析如下:
int cpu_init (void) { /* * setup up stacks if necessary */ #ifdef CONFIG_USE_IRQ IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #else FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #endif return 0; } |
Cpu_init函数基本上就是初始化了一下栈区空间和软件中断的栈空间
在完成了单板相应硬件初始化之后,u-boot在后继的代码里可以自由的使用u-boot框架提供的一些服务接口,比如从串口获取输入,从nandflash读写数据,网络接口通信等等。然后u-boot进入了一个大的循环中
for (;;) { main_loop (); } |
在main_loop函数中,有两条比较关键的路径需要分析,一条路径就是引导内核启动,另外一条路径就是用户通过串口输入中断了u-boot启动内核过程而进入了u-boot的命令行调试运行环境中。
三、u-boot引导内核启动的过程。
首先我们来分析一下u-boot引导内核启动的过程
1、首先获取等待用户串口按键中断引导kernel的时间,一般为3秒
s = getenv ("bootdelay"); bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; |
2、获取启动内核的命令,这个靠编译u-boot之前开发者的配置
而这条启动命令在mini2440的配置就是如下,其实是两条命令组成的
#define CONFIG_BOOTCOMMAND"nand read.jffs2 0x32000000 kernel; bootm 0x32000000"
3、调用abortboot函数检查在等待的3s中内是不是从串口有用户输入了空格,如果有就说明用户想进入命令行调试模式,如果没有就说明可以开始运行启动内核的命令。
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { { printf("Booting Linux ...\n"); run_command (s, 0); } } |
4、abortboot中比较关键的代码就是在一个循环中不断检测是不是有用户的串口输入,使用了一个udelay函数,10000us等于10ms,循环100次就是1s
while ((bootdelay > 0) && (!abort)) { int i; --bootdelay; /* delay 100 * 10ms */ for (i = 0; !abort && i < 100; ++i) { if (tstc()) /* we got a key press */ { # ifdef CONFIG_MENUKEY abort = 1; /* don't auto boot */ bootdelay = 0; /* no more delay */ menukey = getc(); break; # else /* consume input */ if (getc() == ' ') { abort = 1; /* don't auto boot */ bootdelay = 0; /* no more delay */ break; } # endif } udelay (10000); } printf ("\b\b\b%2d ", bootdelay); } |
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, "nand - NAND sub-system\n", "info - show available NAND devices\n" "nand device [dev] - show or set current device\n" "nand read[.jffs2] - addr off|partition size\n" "nand write[.jffs2] - addr off|partiton size - read/write `size' bytes starting\n" " at offset `off' to/from memory address `addr'\n" "nand read.yaffs addr off size - read the `size' byte yaffs image starting\n" " at offset `off' to memory address `addr'\n" "nand write.yaffs addr off size - write the `size' byte yaffs image starting\n" " at offset `off' from memory address `addr'\n" "nand read.raw addr off size - read the `size' bytes starting\n" " at offset `off' to memory address `addr', without oob and ecc\n" "nand write.raw addr off size - write the `size' bytes starting\n" " at offset `off' from memory address `addr', without oob and ecc\n" "nand erase [clean] [off size] - erase `size' bytes from\n" " offset `off' (entire device if not specified)\n" "nand bad - show bad blocks\n" "nand dump[.oob] off - dump page\n" "nand scrub - really clean NAND erasing bad blocks (UNSAFE)\n" "nand markbad off - mark bad block at offset (UNSAFE)\n" "nand biterr off - make a bit error at offset (UNSAFE)\n" "nand lock [tight] [status] - bring nand to lock state or display locked pages\n" "nand unlock [offset] [size] - unlock section\n"); |
这里也没有必要去深入探究do_nand的作用,它完成的工作就是将kernel从nandflash中准确的载入到内存指定的位置。
当nand命令将kernel读入到内存后,接着执行bootm命令,最后执行linux内核的code
U_BOOT_CMD( bootm, CFG_MAXARGS, 1, do_bootm, "bootm - boot application image from memory\n", "[addr [arg ...]]\n - boot application image stored in memory\n" "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" "\t'arg' can be the address of an initrd image\n" #ifdef CONFIG_OF_FLAT_TREE "\tWhen booting a Linux kernel which requires a flat device-tree\n" "\ta third argument is required which is the address of the of the\n" "\tdevice-tree blob. To boot that kernel without an initrd image,\n" "\tuse a '-' for the second argument. If you do not pass a third\n" "\ta bd_info struct will be passed instead\n" #endif ); |
这个命令的执行最后会调用到do_bootm函数
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { ulong iflag; ulong addr; ulong data, len, checksum; ulong *len_ptr; uint unc_len = CFG_BOOTM_LEN; int i, verify; char *name, *s; int (*appl)(int, char *[]); image_header_t *hdr = &header; s = getenv ("verify"); verify = (s && (*s == 'n')) ? 0 : 1; /*argv[1]传入的是从nandflash中载入到内存的地址0x32000000是32M的位置*/ if (argc < 2) { addr = load_addr; } else { addr = simple_strtoul(argv[1], NULL, 16); } SHOW_BOOT_PROGRESS (1); printf ("## Booting image at %08lx ...\n", addr); /*使用u-boot启动的内核在制作镜像的时候会增加一个64byte的头,这里要对这个头做相应的处理*/ memmove (&header, (char *)addr, sizeof(image_header_t)); /*检查这个header中的幻数对不对*/ if (ntohl(hdr->ih_magic) != IH_MAGIC) { { puts ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-1); return 1; } } SHOW_BOOT_PROGRESS (2); data = (ulong)&header; len = sizeof(image_header_t); /*对这个header进行crc校验,比较原来保存的crc看是不是一致*/ checksum = ntohl(hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (uchar *)data, len) != checksum) { puts ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-2); return 1; } SHOW_BOOT_PROGRESS (3); /* for multi-file images we need the data part, too */ print_image_hdr ((image_header_t *)addr); data = addr + sizeof(image_header_t); len = ntohl(hdr->ih_size); if (verify) { puts (" Verifying Checksum ... "); if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-3); return 1; } puts ("OK\n"); } SHOW_BOOT_PROGRESS (4); len_ptr = (ulong *)data; /*cpu体系结构玛是不是正确*/ #if defined(__PPC__) if (hdr->ih_arch != IH_CPU_PPC) #elif defined(__ARM__) if (hdr->ih_arch != IH_CPU_ARM) #elif defined(__I386__) if (hdr->ih_arch != IH_CPU_I386) #elif defined(__mips__) if (hdr->ih_arch != IH_CPU_MIPS) #elif defined(__nios__) if (hdr->ih_arch != IH_CPU_NIOS) #elif defined(__M68K__) if (hdr->ih_arch != IH_CPU_M68K) #elif defined(__microblaze__) if (hdr->ih_arch != IH_CPU_MICROBLAZE) #elif defined(__nios2__) if (hdr->ih_arch != IH_CPU_NIOS2) #elif defined(__blackfin__) if (hdr->ih_arch != IH_CPU_BLACKFIN) #elif defined(__avr32__) if (hdr->ih_arch != IH_CPU_AVR32) #else # error Unknown CPU type #endif { printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch); SHOW_BOOT_PROGRESS (-4); return 1; } SHOW_BOOT_PROGRESS (5); switch (hdr->ih_type) { case IH_TYPE_STANDALONE: name = "Standalone Application"; /* A second argument overwrites the load address */ if (argc > 2) { hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16)); } break; case IH_TYPE_KERNEL: name = "Kernel Image"; break; case IH_TYPE_MULTI: name = "Multi-File Image"; len = ntohl(len_ptr[0]); /* OS kernel is always the first image */ data += 8; /* kernel_len + terminator */ for (i = 1; len_ptr[i]; ++i) data += 4; break; default: printf ("Wrong Image Type for %s command\n", cmdtp->name); SHOW_BOOT_PROGRESS (-5); return 1; } SHOW_BOOT_PROGRESS (6); /* * We have reached the point of no return: we are going to * overwrite all exception vector code, so we cannot easily * recover from any failures any more... */ iflag = disable_interrupts(); #ifdef CONFIG_AMIGAONEG3SE /* * We've possible left the caches enabled during * bios emulation, so turn them off again */ icache_disable(); invalidate_l1_instruction_cache(); flush_data_cache(); dcache_disable(); #endif /*这个位置需要注意,ih_load表示内核自己希望运行的位置,如果这个位置和 boot载入内核到内存的位置data不一样,叫进行拷贝*/ switch (hdr->ih_comp) { case IH_COMP_NONE: if(ntohl(hdr->ih_load) == data) { printf (" XIP %s ... ", name); } else { memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); } break; case IH_COMP_GZIP: printf (" Uncompressing %s ... ", name); if (gunzip ((void *)ntohl(hdr->ih_load), unc_len, (uchar *)data, &len) != 0) { puts ("GUNZIP ERROR - must RESET board to recover\n"); SHOW_BOOT_PROGRESS (-6); do_reset (cmdtp, flag, argc, argv); } break; #ifdef CONFIG_BZIP2 case IH_COMP_BZIP2: printf (" Uncompressing %s ... ", name); /* * If we've got less than 4 MB of malloc() space, * use slower decompression algorithm which requires * at most 2300 KB of memory. */ i = BZ2_bzBuffToBuffDecompress ((char *)ntohl(hdr->ih_load), &unc_len, (char *)data, len, CFG_MALLOC_LEN < (4096 * 1024), 0); if (i != BZ_OK) { printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i); SHOW_BOOT_PROGRESS (-6); udelay(100000); do_reset (cmdtp, flag, argc, argv); } break; #endif /* CONFIG_BZIP2 */ default: if (iflag) enable_interrupts(); printf ("Unimplemented compression type %d\n", hdr->ih_comp); SHOW_BOOT_PROGRESS (-7); return 1; } puts ("OK\n"); SHOW_BOOT_PROGRESS (7); switch (hdr->ih_type) { case IH_TYPE_STANDALONE: if (iflag) enable_interrupts(); /* load (and uncompress), but don't start if "autostart" * is set to "no" */ if (((s = getenv("autostart")) != NULL) && (strcmp(s, "no") == 0)) { char buf[32]; sprintf(buf, "%lX", len); setenv("filesize", buf); return 0; } appl = (int ( *)(int, char * []))ntohl(hdr->ih_ep); (*appl)(argc - 1, &argv[1]); return 0; case IH_TYPE_KERNEL: case IH_TYPE_MULTI: /* handled below */ break; default: if (iflag) enable_interrupts(); printf ("Can't boot image type %d\n", hdr->ih_type); SHOW_BOOT_PROGRESS (-8); return 1; } SHOW_BOOT_PROGRESS (8); /*最后我们一般启动的linux内核,调用do_bootm_linux进行处理*/ switch (hdr->ih_os) { default: /* handled by (original) Linux case */ case IH_OS_LINUX: #ifdef CONFIG_SILENT_CONSOLE fixup_silent_linux(); #endif 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; #ifdef CONFIG_LYNXKDI case IH_OS_LYNXOS: do_bootm_lynxkdi (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #endif case IH_OS_RTEMS: do_bootm_rtems (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #if (CONFIG_COMMANDS & CFG_CMD_ELF) 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; #endif /* CFG_CMD_ELF */ #ifdef CONFIG_ARTOS case IH_OS_ARTOS: do_bootm_artos (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; #endif } SHOW_BOOT_PROGRESS (-9); #ifdef DEBUG puts ("\n## Control returned to monitor - resetting...\n"); do_reset (cmdtp, flag, argc, argv); #endif return 1; } |
最后调用do_linux_bootm完成内核启动,这个函数主要做三个工作,首先是拷贝inird到确定位置,然后初始化内核参数到确定的位置,最后调用内核的启动函数,并传递两个参数给内核一个是cpu number,一个是内核参数位置。
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int verify) { ulong len = 0, checksum; ulong initrd_start, initrd_end; ulong data; void (*theKernel)(int zero, int arch, uint params); image_header_t *hdr = &header; bd_t *bd = gd->bd; #ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); #endif /*ih_ep的值正是kernel启动的第一条code的位置*/ theKernel = (void ( *)(int, int, uint))ntohl(hdr->ih_ep); /* * Check if there is an initrd image */ if (argc >= 3) { SHOW_BOOT_PROGRESS (9); addr = simple_strtoul (argv[2], NULL, 16); printf ("## Loading Ramdisk Image at %08lx ...\n", addr); /* Copy header so we can blank CRC field for re-calculation */ #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash (addr)) { read_dataflash (addr, sizeof (image_header_t), (char *) &header); } else #endif memcpy (&header, (char *) addr, sizeof (image_header_t)); if (ntohl (hdr->ih_magic) != IH_MAGIC) { printf ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-10); do_reset (cmdtp, flag, argc, argv); } data = (ulong) & header; len = sizeof (image_header_t); checksum = ntohl (hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (unsigned char *) data, len) != checksum) { printf ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-11); do_reset (cmdtp, flag, argc, argv); } SHOW_BOOT_PROGRESS (10); print_image_hdr (hdr); data = addr + sizeof (image_header_t); len = ntohl (hdr->ih_size); #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash (addr)) { read_dataflash (data, len, (char *) CFG_LOAD_ADDR); data = CFG_LOAD_ADDR; } #endif if (verify) { ulong csum = 0; printf (" Verifying Checksum ... "); csum = crc32 (0, (unsigned char *) data, len); if (csum != ntohl (hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-12); do_reset (cmdtp, flag, argc, argv); } printf ("OK\n"); } SHOW_BOOT_PROGRESS (11); if ((hdr->ih_os != IH_OS_LINUX) || (hdr->ih_arch != IH_CPU_ARM) || (hdr->ih_type != IH_TYPE_RAMDISK)) { printf ("No Linux ARM Ramdisk Image\n"); SHOW_BOOT_PROGRESS (-13); do_reset (cmdtp, flag, argc, argv); } #if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO) /* *we need to copy the ramdisk to SRAM to let Linux boot */ memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); data = ntohl(hdr->ih_load); #endif /* CONFIG_B2 || CONFIG_EVB4510 */ /* * Now check if we have a multifile image */ } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) { ulong tail = ntohl (len_ptr[0]) % 4; int i; SHOW_BOOT_PROGRESS (13); /* skip kernel length and terminator */ data = (ulong) (&len_ptr[2]); /* skip any additional image length fields */ for (i = 1; len_ptr[i]; ++i) data += 4; /* add kernel length, and align */ data += ntohl (len_ptr[0]); if (tail) { data += 4 - tail; } len = ntohl (len_ptr[1]); } else { /* * no initrd image */ SHOW_BOOT_PROGRESS (14); len = data = 0; } #ifdef DEBUG if (!data) { printf ("No initrd\n"); } #endif if (data) { initrd_start = data; initrd_end = initrd_start + len; } else { initrd_start = 0; initrd_end = 0; } SHOW_BOOT_PROGRESS (15); debug ("## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel); /*设置要传递的内核参数到内存开始的512字节位置,然后启动linux内核,并 向linux 内核传递两个参数,一个参数是cpu类型,一个参数是内核参数位置*/ #if defined (CONFIG_SETUP_MEMORY_TAGS) || \ defined (CONFIG_CMDLINE_TAG) || \ defined (CONFIG_INITRD_TAG) || \ defined (CONFIG_SERIAL_TAG) || \ defined (CONFIG_REVISION_TAG) || \ defined (CONFIG_LCD) || \ defined (CONFIG_VFD) setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG setup_serial_tag (?ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (?ms); #endif #ifdef CONFIG_SETUP_MEMORY_TAGS setup_memory_tags (bd); #endif #ifdef CONFIG_CMDLINE_TAG setup_commandline_tag (bd, commandline); #endif #ifdef CONFIG_INITRD_TAG if (initrd_start && initrd_end) setup_initrd_tag (bd, initrd_start, initrd_end); #endif #if defined (CONFIG_VFD) || defined (CONFIG_LCD) setup_videolfb_tag ((gd_t *) gd); #endif setup_end_tag (bd); #endif /* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n"); #ifdef CONFIG_USB_DEVICE { extern void udc_disconnect (void); //udc_disconnect (); // cancled by www.arm9.net } #endif cleanup_before_linux (); theKernel (0, bd->bi_arch_number, bd->bi_boot_params); } |
四、U-boot命令行调试状态
上面是对u-boot载入引导kernel的分析。如果用户通过串口打断引导过程,或者引导过程失败了,就会进入u-boot命令行调试状态。U-boot进入一个无限的for循环等待用户从串口输入命令,找到匹配的命令后执行相应的操作。
for (;;) { /*从串口读出命令*/ len = readline (CFG_PROMPT); flag = 0; /* assume no special flags for now */ if (len > 0) strcpy (lastcommand, console_buffer); else if (len == 0) flag |= CMD_FLAG_REPEAT; #ifdef CONFIG_BOOT_RETRY_TIME else if (len == -2) { /* -2 means timed out, retry autoboot */ puts ("\nTimed out waiting for command\n"); # ifdef CONFIG_RESET_TO_RETRY /* Reinit board to run initialization code again */ do_reset (NULL, 0, 0, NULL); # else return; /* retry autoboot */ # endif } #endif /*执行相应的命令*/ if (len == -1) puts ("<INTERRUPT>\n"); else rc = run_command (lastcommand, flag); if (rc <= 0) { /* invalid command or not repeatable, forget it */ lastcommand[0] = 0; } } #endif /*CFG_HUSH_PARSER*/ |