u-boot实现原理完全分析
- 格式:doc
- 大小:1.02 MB
- 文档页数:52
U-BOOT
作者:***
邮箱:***************
博客:/
目录
一U-BOOT目录结构 (1)
二U-BOOT的启动与内核引导 (3)
1、U-BOOT的启动分析 (3)
1.1设置异常向量表 (3)
1.2 U-BOOT的存储映射 (4)
1.3 硬件设备的初始化 (5)
1.4 硬件平台前期初始化 (9)
1.5代码重定位 (13)
1.6 硬件平台后期初始化 (16)
2、内核引导 (20)
2.1 u-boot命令实现原理 (20)
2.2内核引导 (24)
三U-BOOT编译流程分析 (35)
1、U-BOOT编译命令 (35)
2、U-BOOT配置流程 (35)
2.1环境初始化 (35)
2.2 make smdk2410_config命令的执行过程 (38)
2.3 make smdk2410命令的执行过程 (42)
2.4 U-BOOT的编译流程 (43)
U-BOOT原理分析
一U-BOOT目录结构
api
api目录对应于一些扩展应用的独立的api
arch
arch存放与CPU架构有关的目录,下面每一个目录就代表一种架构的CPU
board
board目录是与硬件平台相关的目录,特定于某种硬件平台的文件以子目录的形式存放在该目录下
common
common目录存放了u-boot所支持的所有命令
disk
disk目录存放了磁盘驱动和相关的代码
doc
doc目录存放了u-boot的参考文档
drivers
u-boot支持的所有驱动都存放在driver目录下,这些驱动大都根据linux驱动改写而来dts
dts目录包含一个平台设备树相关的makefile,可编译生成设备树镜像文件
examples
example下时一些在u-boot上运行的事例程序
fs
fs目录下是u-boot所支持的文件系统
include
include目录包含了u-boot的头文件以及各种硬件平台的系统配置文件
lib
lib目录下是u-boot的库文件
nand_spl
支持从nand flash启动的相关代码
net
u-boot的网络子系统
onenand_ipl
支持从onenand 启动的代码
post
Post下是支持上电自检功能的目录
spl
与nand_spl相关的makefile,编译支持从nand flash启动的u-boot二进制文件tools
tools目录下是u-boot的一些辅助工具,比如生成u-boot镜像文件等
二U-BOOT的启动与内核引导
1、U-BOOT的启动分析
u-boot支持多种架构类型的cpu,支持多种硬件平台,本文以smdk2410为例来讲解u-boot 的功能原理。
u-boot的入口在文件在arch/arm/cpu/arm920t/start.S中,因为在文件在链接文件arch/arm/cpu/u-boot.lds中被第一个链接,如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
__image_copy_start = .;
CPUDIR/start.o (.text) //start.o对应源代码就是start.S
*(.text)
}
…………
1.1设置异常向量表
.globl _start
_start: b start_code //复位
ldr pc, _undefined_instruction //未定义指令向量
ldr pc, _software_interrupt //软件中断向量
ldr pc, _prefetch_abort //预取指令异常向量
ldr pc, _data_abort //数据操作异常向量
ldr pc, _not_used //未使用
ldr pc, _irq //irq中断向量
ldr pc, _fiq // 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
1)定义全局变量_start存储u-boot的起始地址,由链接文件arch/arm/cpu/u-boot.lds可知该值为0x0。
2).word定义一个4字节的变量并赋给一个初始值,
如:undefined_instruction: .word undefined_instruction
3)定义变量_undefined_instruction,并将未定义指令异常中断处理函数地址赋给它。
4).balignl 16, 0xdeadbeef
地址以16字节对其,不对齐的用常量0xdeadbeef来填充。
1.2 U-BOOT的存储映射
.globl _TEXT_BASE
_TEXT_BASE:
.word CONFIG_SYS_TEXT_BASE
//在配置文件include/configs/smdk2410.h中定义
#define CONFIG_SYS_TEXT_BASE 0x0
下面是定义了一些全局变量,这些全局变量存储了一些偏移地址或是栈地址,这些地址是根据链接文件arch/arm/cpu/u-boot.lds中对应段的地址计算出来的。
.globl _bss_start_ofs //bss数据区相对于起始位置的偏移
_bss_start_ofs:
.word __bss_start - _start
.globl _bss_end_ofs //bss数据区结束处相对于起始位置的偏移
_bss_end_ofs:
.word __bss_end__ - _start
.globl _end_ofs
_end_ofs:
.word _end - _start
#ifdef CONFIG_USE_IRQ
.globl IRQ_STACK_START //IRQ的栈地址,在运行时计算,现在赋初值0x0badc0de IRQ_STACK_START:
.word 0x0badc0de
.globl FIQ_STACK_START
FIQ_STACK_START: //FIQ的栈地址,在运行时计算,现在赋初值0x0badc0de .word 0x0badc0de
#endif
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
//该变量存储IRQ的栈地址+8字节的位置,在运行时计算,现在赋初值0x0badc0de .word 0x0badc0de
对于u-boot存储映射有一张很经典的图描述了u-boot运行中,在内存中的映射关系。
U-boot在启动的初始阶段的主要工作就是,将自己从存储设备中搬移到内存中,并摆出如下布局:
1.3 硬件设备的初始化
(1)CPU进入SVC模式
start_code:
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
当系统复位时程序就跳转到start_code处执行,上面的代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽IRQ和FIQ中断。
(2)关闭看门狗
#ifdef CONFIG_S3C24X0
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
设置一些寄存器的地址
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
系统启动时看门狗寄存器是使能的,如果不按时喂狗系统就会复位,所以这里要关掉看门狗。
(3)禁止所有中断
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
INTMSK是主中断屏蔽寄存器,INTSUBMSK是子中断屏蔽寄存器,只要相应位被置一,对应的中断请求就得不到处理。
上面代码即是屏蔽所有中断以及子中断。
(4)设置时钟除数寄存器
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
CLKDIVN寄存器用于设置FCLK,HCLK,PCLK三者间的比例。
1)CLKDIVN位[2:1]用于设置FCLK和HCLK的比例,设置的值和对应比例如下:
00 : HCLK = FCLK/1.
01 : HCLK = FCLK/2.
10 : HCLK = FCLK/4
2)CLKDIVN位的第0位用于设置HCLK和PCLK的比例,设置的值和对应比例如下:0: PCLK = HCLK/1
1:PCLK = HCLK/2
(5)关闭MMU与cache
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
//调用函数cpu_init_crit,使指令cache与数据cache无效,使TLB无效。
该函数在arch/arm/cpu/arm920t/start.S中实现,如下:
cpu_init_crit:
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
//上面代码是禁止MMU
mov ip, lr
//lr寄存器保存了调用子程序时当前地址,以便子程序调用结束时返回。
这里处于子程//序中又将再次调用下一级子程序,所以这里保存上一次调用是保存在lr中的值。
bl lowlevel_init
mov lr, ip
mov pc, lr
//函数lowlevel_init是在文件board/samsung/smdk2410/lowlevel_init.S中定义的,主要实
现ram 控制寄存器的初始化。
函数结束时将保存在ip 中的地址恢复到lr 中然后让pc 指向lr 中的地址处(子程序返回)。
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
代码中的c0,c1,c7,c8都是ARM920T 的协处理器CP15的寄存器。
其中c7是cache 控制寄存器,c8是TLB 控制寄存器。
关闭MMU 是通过修改CP15的c1寄存器来实现的。
下面是CP15的c1寄存器的几个需要关心的位:
(6)RAM 控制寄存器的初始化 _TEXT_BASE:
.word CONFIG_SYS_TEXT_BASE
.globl lowlevel_init
lowlevel_init:
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str
r3, [r1], #4
cmp r2, r0
bne 0b
mov pc, lr
.ltorg
SMRDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)
+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4) +(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4) +(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4) +(B2_Tacp<<2)+(B2_PMC))
.word((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4) +(B3_Tacp<<2)+(B3_PMC))
.word((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4) +(B4_Tacp<<2)+(B4_PMC))
.word((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4) +(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0x32
.word 0x30
.word 0x30
这段RAM控制寄存器配置程序在文件board/samsung/smdk2410/lowlevel_init.S中实现。
关键字.ltorg定义了一个文字池,其中存储了RAM的13个寄存器对应的值。
上面那段汇编代码便是将这文字池中的数据存入RAM对应的寄存器中。
1.4 硬件平台前期初始化
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
初始化栈指针为调用C程序做准备
(CONFIG_SYS_INIT_SP_ADDR在文件include/configs/smdk2410.h中定义)。
栈指针8字节对齐,r0中的值为函数board_init_f的参数。
函数board_init_f在文件arch/arm/lib/board.c中定义如下:
void board_init_f(ulong bootflag)
{
......
//记录启动到了哪一步。
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
//gd_t是一个包含了很多全局数据的结构体,gd是一个指向这个全局数据结构体的指针,该指针是通过宏DECLARE_GLOBAL_DATA_PTR来定义的。
在文件
arch/arm/include/asm/global_data.h中宏DECLARE_GLOBAL_DATA_PTR的定义如下:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
指针gd存放在指定的寄存器r8中。
这个声明可避免编译器把r8分配给其它的变量。
任何想要访问全局数据结构体的代码,只要代码开头加入
“DECLARE_GLOBAL_DATA_PTR”一行代码,
然后就可以使用gd指针来访问全局数据区了。
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
//下面一行内嵌汇编的作用是,告诉编译器内存被修改过了。
__asm__ __volatile__("": : :"memory");
memset((void *)gd, 0, sizeof(gd_t));
//gd->mon_len保存U-BOOT镜像的长度
gd->mon_len = _bss_end_ofs;
.....
//通过一个循环依次调用数组init_sequence中的各个函数,对平台做一些早期的初始化,后面将会做进一步讲解。
【1】for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
......
//CONFIG_SYS_SDRAM_BASE是内存在CPU地址空间中的起始位子在文件
include/configs/smdk2410.h中配置,gd->ram_size中保存了内存的大小,所以addr指向内存的最高地址处。
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
......
//如果使用了指令cache与数据cache就分配16K空间用于建立块表。
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) /* reserve TLB table */
addr -= (4096 * 4);
/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1);
gd->tlb_addr = addr;
debug("TLB table at: %08lx\n", addr);
#endif
addr &= ~(4096 - 1);
//如果配置了CONFIG_LCD就分配一块空间作为LCD数据缓存区
#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
gd->fb_base = CONFIG_FB_ADDR;
#else
/* reserve memory for LCD display (always full pages) */
addr = lcd_setmem(addr);
gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */
//gd->mon_len保存着u-boot镜像大小,下面代码是分配一个区域用于保存u-boot镜像addr -= gd->mon_len;
addr &= ~(4096 - 1);
debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
#ifndef CONFIG_SPL_BUILD
//分配堆区,TOTAL_MALLOC_LEN在的大小最终在include/configs/smdk2410.h中配置addr_sp = addr - TOTAL_MALLOC_LEN;
debug("Reserving %dk for malloc() at: %08lx\n",
TOTAL_MALLOC_LEN >> 10, addr_sp);
//结构体bd_t用于存储一些平台信息,可通过全局结构体的字段gd->bd来访问addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
......
//为全局结构体gd_t分配空间,上面只是将它保存在一个临时的位置,后面将会把那个临时的gd_t内容拷贝到现在分配的存储空间里。
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
//将IRQ栈顶地址保存到gd->irq_sp中
gd->irq_sp = addr_sp;
//分配IRQ和FIQ栈空间
#ifdef CONFIG_USE_IRQ
addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
#else
addr_sp += 128; /* leave 32 words for abort-stack */
gd->irq_sp = addr_sp;
#endif
//上电自检相关代码
#ifdef CONFIG_POST
post_bootmode_init();
post_run(NULL, POST_ROM | post_bootmode_get(0));
#endif
//保存一些平台相关的信息,串口波特率,内存起始地址和大小。
gd->bd->bi_baudrate = gd->baudrate;
dram_init_banksize();
display_dram_config(); /* and display it */
//保存代码重定位的起始位置,用户栈的栈顶地址,代码重定位的偏移量。
将全局数据//结构体从临时存放位置拷贝到上面分配的地址处。
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t));
//调用函数relocate_code进行代码重定位。
传入的参数为用户栈地址,全局数据结构体//地址,和代码重定位的起始地址。
这三个参数分别保存在寄存器R0,R1,R2中。
//函数relocate_code用汇编实现,在文件arch/arm/cpu/arm920t/start.S中实现。
relocate_code(addr_sp, id, addr);
/* NOTREACHED - relocate_code() does not return */
}
/****************************************【1】***************************************/ 下面将对标号【1】处做进一步的分析
init_fnc_t *init_sequence[] = {
......
//配置mpllcon 和upllcon还有对IO口做初始化配置
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
.....
//初始化平台时钟
timer_init, /* initialize timer */
//环境变量初始化。
假设代码是从nand flash启动的,如果配置了ENV_IS_EMBEDDED //表明环境变量是嵌在U-BOOT镜像中的,代码就做一些crc检查。
实际上一般不做这//项配置,所以函数env_init要做的就是将gd->env_addr指向数组default_environment, //这个数组中保//存了一些常用的环境变量,比如,启动参数,串口波特率,IP地址等。
//另外,函数还将标//识环境变量有效gd->env_valid = 1,这将在环境变量重定位函数//env_relocate中检测。
env_init, /* initialize environment */
//从环境变量中获取串口波特率。
init_baudrate, /* initialze baudrate settings */
//串口设备初始化,对于s3c2410来说最终将调用文件drivers/serial/serial_s3c24x0.c中函数serial_init_dev来做硬件初始化。
serial_init, /* serial communications setup */
//下面函数所做的主要工作就是gd->have_console = 1;表明支持控制台操作。
如果配置了CONFIG_PRE_CONSOLE_BUFFER还将初始化控制台缓存。
console_init_f, /* stage 1 init of console */
//现在可以打印一些信息了。
display_banner, /* say that we are here */
......
//获取内存大小gd->ram_size = PHYS_SDRAM_1_SIZE,PHYS_SDRAM_1_SIZE在文件include/configs/smdk2410.h中配置。
dram_init, /* configure available RAM banks */
NULL,
};
/*************************************************************************************/ 1.5代码重定位
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
//代码重定位函数relocate_code在文件arch/arm/cpu/arm920t/start.S中以汇编形式实现,//它有三个参数,用户栈地址,全局数据结构体地址,和代码重定位的起始地址,分别保存在寄//存器R0,R1,R2中,在函数开头将这三个参数保存起来。
stack_setup:
mov sp, r4
adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
//这段代码实现了将u-boot镜像搬移到内存中指定地址(r6中存储地址处)。
首
//先判断u-boot是否已经重定位,通过将u-boot的入口_start与重定位地址(r6)进
//行比较,如果不相等就将u-boot镜像拷贝到r6指定地址处,
//否则直接跳到clear_bss处,因为此时u-boot引进重定位。
#ifndef CONFIG_SPL_BUILD
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */ ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif
在链接文件/arch/arm/cpu/u-boot.lds中有两个段:.dynsym 动态符号表,.rel.dyn动态重定位表。
上面程序的主要工作就是将.dynsym和.rel.dyn重定位,并遍历.rel.dyn,根据重定位表中的信息将符号表中的函数地址等进行重定位。
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */
clbss_l:cmp r0, r1 /* clear loop... */
bhs clbss_e /* if reached end of bss, exit */
str r2, [r0]
add r0, r0, #4
b clbss_l
clbss_e:
bl coloured_LED_init
bl red_led_on
#endif
//找到BSS段的起始地址与结束地址,然后循环清零整个BSS段。
1.6 硬件平台后期初始化
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
_board_init_r_ofs:
.word board_init_r - _start
首先找到函数board_init_r重定位后的位置,然后将全局数据结构体gd_t 和重定位目标地址存分别入r0和r1中,作为函数board_init_r的参数。
跳到C函数board_init_r 处执行,该函数在文件/arch/arm/lib/board.c中实现,如下:
void board_init_r(gd_t *id, ulong dest_addr)
{
//让gd指向全局数据结构体。
gd = id;
gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
//将u-boot二进制文件长度保存在monitor_flash_len中。
monitor_flash_len = _end_ofs;
//使能缓存功能
enable_caches();
//函数board_init主要做了下面几件事:将平台机器码保存在gd->bd->bi_arch_number //中;在gd->bd->bi_boot_params中设置传递给内核的参数的起始地址。
使能指令cache //和数据cache。
board_init(); /* Setup chipselects */
......
//分配堆区存储空间,并将该区域清零。
malloc_start = dest_addr - TOTAL_MALLOC_LEN;
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
......
//配置可用的flash区
flash_size = flash_init();
.....
//初始化nandflash的控制寄存器,获取芯片的类型和相关参数,初始化一些结构体成员//建立起相关数据结构。
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init(); /* go init the NAND */
#endif
.....
//环境变量重定位。
在函数env_relocate中首先检测gd->env_valid,在之前的函数env_init //中已经将它置1,所以函数将会把保存在nandflash(假设环境变量保存在nandflash中)//中的环境变量读入内存中然后加入哈希表中。
env_relocate();
......
//在函数stdio_init中将函数I2C,LCD,KEYBOARD等设备封装成设备stdio_dev,然后将//其注册到链表devs上。
stdio_init(); /* get the devices list going. */
//初始化u-boot的应用函数集
jumptable_init();
.....
//函数console_init_r中,首先尝试从环境变量中获取标准输入、标准输出、标准错误输//出的设备名,然后到设备链表devs中区搜索同名的设备,如果没有找到就到devs中找//名为"serial"的设备,最后将找到的设备存入stdio_devices数组中。
console_init_r(); /* fully init console as a device */
......
//在文件/arch/arm/cpu/arm920t/start.S中定义了三个全局变量:
//IRQ_STACK_START,IRQ_STACK_START_IN和FIQ_STACK_START,
//下面函数就是对这三个变量赋值。
interrupt_init();
/* enable exceptions */
//能使IRQ中断
enable_interrupts();
......
//从环境变量中获取内核镜像的加载地址
load_addr = getenv_ulong("loadaddr", 16, load_addr);
.....
//网卡接口控制器的初始化
#if defined(CONFIG_CMD_NET)
puts("Net: ");
eth_initialize(gd->bd);
#endif
/上电自检程序
#ifdef CONFIG_POST
post_run(NULL, POST_RAM | post_bootmode_get(0));
#endif
......
//进入命令循环函数
for (;;) {
main_loop();
}
}
//函数main_loop在文件/common/main.c中实现,该函数中如果配置了系统启动命令,//则直接启动系统,否则进入U-BOOT命令行等待用户输入u-boot命令。
void main_loop (void)
{
......
//记录启动次数,获取启动次数的最大限制
#ifdef CONFIG_BOOTCOUNT_LIMIT
bootcount = bootcount_load();
bootcount++;
bootcount_store (bootcount);
sprintf (bcs_set, "%lu", bootcount);
setenv ("bootcount", bcs_set);
bcs = getenv ("bootlimit");
bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
......
//如果配置了延时启动则从环境变量中获取延时的秒数
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; ......
//如果设置了限制启动次数则比较启动次数是否超过了启动次数的最大限制
#ifdef CONFIG_BOOTCOUNT_LIMIT
if (bootlimit && (bootcount > bootlimit)) {
printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv ("altbootcmd");
}
#endif
//从环境变量中获取启动命令
s = getenv ("bootcmd");
//函数abortboot实现延时倒计时,如果有任何按键按下将返回1.
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
//禁止ctrl+c检查
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
//运行系统启动命令
run_command(s, 0);
}
......
//进入u-boot菜单模式
# ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s)
run_command(s, 0);
}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
......
for (;;) {
......
//获取一行数据,并在数据开头加上命令提示符"SMDK2410 # ",将获取的命令字符串//存取console_buffer中。
len = readline (CONFIG_SYS_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
......
//运行用户输入的命令
if (len == -1)
puts ("<INTERRUPT>\n");
else
rc = run_command(lastcommand, flag);
......
}
}
2、内核引导
2.1 u-boot命令实现原理
struct cmd_tbl_s {
char *name; /*命令名*/
int maxargs; /*命令的最大参数个数*/
int repeatable; /*是否自动重复*/
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []); /*命令执行函数*/
char *usage; /*简单的使用说明*/
#ifdef CONFIG_SYS_LONGHELP
char *help; /*详细使用说明*/
#endif
#ifdef CONFIG_AUTO_COMPLETE /*自动补全参数*/
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
在文件include/command.h中定义了结构体cmd_tbl_s,各成员含义如上面注释。
U-boot 的每一条命令都将封装成结构体cmd_tbl_s存到链接文件中指定的.u_boot_cmd区。
当向u-boot中添加命令时都将调用宏
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)来初始化一个cmd_tbl_s 结构体。
比如
系统启动命令被添加到u-boot中时:
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
......
)
//命令被添加到u-boot中之后就可以调用该命令了,run_command就是运行命令的函数,该函数在文件common/main.c中实现
int run_command(const char *cmd, int flag)
{
......
if (builtin_run_command(cmd, flag) == -1)
return 1;
......
}
//函数run_command将运行命令的主要工作交给了函数builtin_run_command。
static int builtin_run_command(const char *cmd, int flag)
{
char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CONFIG_SYS_CBSIZE];
char *str = cmdbuf;
char *argv[CONFIG_SYS_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
......
strcpy (cmdbuf, cmd);
......
while (*str) {
//命令行可以输入多条命令,命令之间用“;”分割,分隔符“;”也可写成转义形式“\;”
for (inquotes = 0, sep = str; *sep; sep++) {
if ((*sep=='\'') &&
(*(sep-1) != '\\'))
inquotes=!inquotes;
if (!inquotes &&
(*sep == ';') && /* separator */
( sep != str) && /* past string start */
(*(sep-1) != '\\')) /* and NOT escaped */
break;
}
//Token指向命令行str指向的命令,然后让str指向下一条命令。
token = str;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
//在命令中可能包含一些环境变量,函数process_macros将获取这些环境变量将命令展//开存入finaltoken中。
process_macros (token, finaltoken);
//将命令参数存入argv中并返回参数的个数
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
//函数cmd_process中将通过命令名在.u_boot_cmd区中查找命令对应结构体//cmd_tbl_s,并调用命令执行函数,后面详细分析。
if (cmd_process(flag, argc, argv, &repeatable))
rc = -1;
......
}
//函数cmd_process在文件common/command.c中实现分析如下:
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable)
{
enum command_ret_t rc = CMD_RET_SUCCESS;
cmd_tbl_t *cmdtp;
//通过命令名在.u_boot_cmd区中查找命令对应结构体cmd_tbl_s。
cmdtp = find_cmd(argv[0]);
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[0]);
return 1;
}
......
//调用命令执行函数(cmdtp->cmd)(cmdtp, flag, argc, argv);
if (!rc) {
rc = cmd_call(cmdtp, flag, argc, argv);
*repeatable &= cmdtp->repeatable;
}
if (rc == CMD_RET_USAGE)
rc = cmd_usage(cmdtp);
return rc;
}
//__u_boot_cmd_end和__u_boot_cmd_start在链接文件arch/arm/cpu/u-boot.lds中定义cmd_tbl_t *find_cmd (const char *cmd)
{
int len = &__u_boot_cmd_end - &__u_boot_cmd_start;
return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);
}
cmd_tbl_t *find_cmd_tbl (const char *cmd, cmd_tbl_t *table, int table_len)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = table; /*Init value */
const char *p;
int len;
int n_found = 0;
......
//遍历.u_boot_cmd区通过命令名找到将要运行的命令
for (cmdtp = table;
cmdtp != table + table_len;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}
return NULL; /* not found or ambiguous command */
}
2.2内核引导
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulong iflag;
ulong load_end = 0;
int ret;
boot_os_fn *boot_fn;
......
if (argc > 1) {
char *endp;
//如果第一个参数是数字就正常启动,如果是子命令就调用do_bootm_subcommand去//运行该子命令。
simple_strtoul(argv[1], &endp, 16);
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
//从内核镜像中获取image_header和bootm_headers的相关信息
【1】if (bootm_start(cmdtp, flag, argc, argv))
return 1;
//禁止中断
iflag = disable_interrupts();
//停止USB功能在内核启动期间禁止写内存
#if defined(CONFIG_CMD_USB)
usb_stop();
#endif
将内核镜像从启动位置放到加载位置,比如u-boot将内核镜像从flash中拷到内存中的0x30800000处,0x30800000就是内核的启动位置,加载位置是从内核镜像头中解析出来的,比如0x30008000就是linux内核常用的加载位置
【2】ret = bootm_load_os(images.os, &load_end, 1);
......
//将内核镜像区域放到lmb中保护起来
lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
......
bootstage_mark(BOOTSTAGE_ID_CHECK_BOOT_OS);
//U-boot支持多种操作系统,每种操作体统都有对应的启动函数,这些函数指针都放在数组boot_os中。
boot_fn = boot_os[images.os.os];
......
//调用操作系统启动函数,引导操作系统启动
【3】boot_fn(0, argc, argv, &images);
......
return 1;
}
*****************************************【1】*************************************** typedef struct image_header {
uint32_t ih_magic; /*镜像幻数*/
uint32_t ih_hcrc; /* 镜像头CRC校验值*/
uint32_t ih_time; /* 镜像创建时间*/
uint32_t ih_size; /* 镜像大小*/
uint32_t ih_load; /* 数据加载地址*/
uint32_t ih_ep; /* 镜像入口*/
uint32_t ih_dcrc; /* 镜像数据区的CRC校验值*/
uint8_t ih_os;/* 操作系统类型*/
uint8_t ih_arch; /* CPU架构*/
uint8_t ih_type; /* 镜像类型*/
uint8_t ih_comp; /* 压缩类型*/
uint8_t ih_name[IH_NMLEN];/* 镜像名*/
} image_header_t;
Linux编译出的二进制文件是zImage,uboot中提供mkimage工具可将zImage制作成uImage,实际上就是在zImage前加一个image_header头。
制作uImage的命令如下。
./mkimage -n 'linux-2.6.36' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage.bin
Mkimage工具参数的含义如下:
-A ==> 设置体系架构类型
-O ==> 设置操作系统类型
-T ==> 设置镜像类型
-C ==> 设置压缩类型
-a ==> 设置加载地址
-e ==> 设置镜像入口位置
-n ==> 设置镜像名
-d ==> 设置生成最终镜像所需的文件
-x ==> 设置片上执行
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) {
void *os_hdr;
int ret;
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify");
......
//获取内核镜像头image_header_t,如后面详细分析
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
if (images.os.image_len == 0) {
puts("ERROR: can't get kernel image!\n");
return 1;
}
switch (genimg_get_format(os_hdr)) {
//如果镜像用旧的头格式就用内核镜像头来初始化bootm_headers的相关字段case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr);
p = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);
images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr);
break;
......
default:
puts("ERROR: unknown image format type!\n");
return 1;
}
//获取内核镜像入口点
if (images.legacy_hdr_valid) {
images.ep = image_get_ep(&images.legacy_hdr_os_copy); ......
} else {
puts("Could not find kernel entry point!\n");
return 1;
}
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
//如果支持ramdisk就找到ramdisk镜像地址
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX)) {
/* find ramdisk */
ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
if (ret) {
puts("Ramdisk image is corrupt or invalid\n");
return 1;
}
......
//获取内核镜像的起始位置
images.os.start = (ulong)os_hdr;
images.state = BOOTM_STATE_START;
return 0;
}
static void *boot_get_kernel(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images, ulong *os_data,
ulong *os_len)
{
image_header_t *hdr;
ulong img_addr;
......
//获取镜像在内存中当前的存储位置
if (argc < 2) {
img_addr = load_addr;
......
} else {
img_addr = simple_strtoul(argv[1], NULL, 16);
}
//如果镜像存在dataflash中,将镜像从dataflash中拷贝到CONFIG_SYS_LOAD_ADDR。