linux
分类: 嵌入式
2012-11-07 10:11:42
以下是本文档的部分产生背景。
产生原因:最近频繁遇见u-boot的下载地址及烧写地址的问题,对项目的进展产生影响,另外如果不将这 个坎迈过去,对以后的工作也会产生影响,所以决定将u-boot的启动流程详细分析下。
目的:通过的u-boot启动流程的分析,尽量了解熟悉u-boot的设计理念,u-boot的启动逻辑,能在以 后的工作中,用好u-boot这个工具。
分析时间:1天
效果:基本掌握了u-boot第一阶段的工作流程,但仍有许多不懂之处,比如fix rel.dyn段。
总结:在对一件事物进行分析的时候,不可能一次将其掌握,需要先了解框架,再有机会逐步深入。
以下即为at91(atmel公司)架构的u-boot启动流程分析:
首先在进行u-boot启动流程分析之前需要理清一个关系,那就是bootstrap与u-boot的关系。
at91架构的u-boot不像其他的u-boot,系统一上电之后就由u-boot来接管目标板,而是先由bootstrap来接管板子,然后再来加载u-boot,目前据我所知,bootstrap做了一部分的u-boot需要完成的任务,具体做了什么,我们暂时不管,但有一个细节需要注意,那就是bootstrap对u-boot的加载地址,因为之前它让我遇到一些阻碍,所以无论如何也要将这里搞清楚。假设bootstrap将u-boot从flash中加载到地址21f00000这个地址处,那么宏CONFIG_SYS_TEXT_BASE也需要设置成21f00000。CONFIG_SYS_TEXT_BASE这个地址目前的信息我知道,它是u-boot正文段的起始地址,即代码从这里开始运行,这解释了他们为什么必须相等,先把这个作为得到这个结论的基础,后面再来分析这个基础是怎么产生,以及时候真的合理。
First:
u-boot应该运行的第一段代码在哪里?从bootstrap来看这个地址应该是CONFIG_SYS_TEXT_BASE。那么这个地址放的是什么东西拉?但它在u-boot里面找不到根据,先把这个放在一边,现在要给出一个条件,那就是u-boot会通过/arch/arm/cpu/u-boot.lds来给出u-boot放到内存中去的结构,以及代码从哪里开始运行,下面看u-boot.lds中给出的一句话:
/开始运行的地方*/
ENTRY(_start)
虽然猜得出它的含义是从这个地方开始运行,但当中是怎样一个过程,仍不清楚,但肯定和链接有关,只能循循渐渐的,提升自己的认识,把握对整个系统理解,现在知道从这个地方开始运行并跳到这个地方去。_start是arch/arm/cpu/start.S中的一个地址,看start.S,这就是程序开始的地方。
Arch/arm/cpu/arm926ejs/start.S:
.globl _start
_start:
b reset
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/*上面的指令是对cpsr的操作,arm有几种工作模式,只有将这个寄存器给予某个值才能对硬件有操作权限,cpu才能让你的指令通过*/
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
/*cpu_init_crit的功能是设置重要的寄存器和memory,但现在u-boot已经有bootstrap启动起来了,所以肯定不会运行这里,不然memory又被搞死了。*/
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
/*call_board_init_f设置好堆栈之后跳入到了board_init_f函数当中。CONFIG_SYS_INIT_SP_ADDR在include/configs/at91sam9260当中定义
CONFIG_SYS_INIT_SP_ADDR = ATMEL_BASE_SRAM + 0x1000 -
GENERATED_GBL_DATA_SIZE当中。
ATMEL_BASE_SRAM = 0x300000.
可见这个时候的堆栈地址还在sram当中,大小为4k - GENERATED_GBL_DATA_SIZE,后面一个值是什么,现在不管。继续分析board_init_f,见后面*/
relocate_code:
.globl relocate_code
relocate_code:
/*ro参数1,r1参数2,r2参数3*/
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
stack_setup:
mov sp, r4 //在这里设置C语言的栈空间,从高到低的延伸地址
adr r0, _start
/*_start为u-boot在内存中的起始地址,r6=r2=addr=uboot的从新加载地址。在我看来,这种重新加载机制是为了方便系统管理*/
sub r9, r6, r0 /* r9 <- relocation offset */
cmp r0, r6
/*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 */
/*将_start到_bss_start的数据全部复制到addr出,即在board_init_f中指明的u-boot的重新加载地址处,,产生了问题,为什么bss处的数据不再转移了*/
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
/*这段代码是将bss段清0,bss(block started by symbol),在运行不占空间,它的清零也许会和malloc的实现相关*/
clear_bss:
#ifdef CONFIG_SPL_BUILD
/* No relocation for SPL */
ldr r0, =__bss_start
ldr r1, =__bss_end__
#else
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
#endif
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:
#ifndef CONFIG_SPL_BUILD
bl coloured_LED_init
bl red_led_on
#endif
u-boot的第一阶段到此为止,然后跳转到C语言入口,进行第二阶段。这个阶段主要做了3件事情,硬件初始化并设置gd_t数据结构,然后为C语言设置栈,重加载u-boot到内存。从细节上来看,先汇编设置片内的sdram栈,然后跳到C语言部分初始化硬件,设置好内存布局,然后返回汇编设置外部SDRAM栈,接着搬运u-boot到sdram.然后跳到C语言入口处。有一点需要注意的是u-boot是bootstrap搬运到内存当中的。
board_init_f位于arch/arm/lib/board.c当中:
/*bootflag = 0*/
void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif
/*BOOTSTAGE_ID_START_UBOOT_F定义于include/bootstage.h,其作用是如果u-boot失败了,它能知道自己处于一个什么样的状态,在什么地方挂掉了,把握对全局的控制*/
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
/* Pointer is writable since we allocated a register for it */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
/ *gd_t是一个全局数据结构,能够表示所有的资源信息,比如串口,资源主频,和u-boot相关的各种地址,gd_t包括bd_t结构体,它只和cpu,board,ram有关*/
/* compiler optimization barrier needed for GCC >= 3.4 */
/*内存屏蔽,是同步的一种机制,保证上面这段代码不被优化,按照顺序运行*/
__asm__ __volatile__("": : :"memory");
memset((void *)gd, 0, sizeof(gd_t));
/*_bss_end_ofs = __bss_end__ - _start (__bss_end_是在u-boot.lds当中定义的u-boot在内存中存放的最后位置),即整个u-boot的长度*/
gd->mon_len = _bss_end_ofs;
/*设备树,现在有什么用还不知道,暂时不管*/
#ifdef CONFIG_OF_EMBED
/* Get a pointer to the FDT */
gd->fdt_blob = _binary_dt_dtb_start;
#elif defined CONFIG_OF_SEPARATE
/* FDT is at end of image */
gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE);
#endif
/* Allow the early environment to override the fdt address */
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
(uintptr_t)gd->fdt_blob);
/*这是一个函数指针,指向各个需要做的函数,包括一些硬件初始化,比如dram_init,将sdam分成bank*/
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*CONFIG_SYS_SDRAM_BASE = 0x20000000,gd->ram_size = 0x4000000(32M),这个工作就是在dram_init当中完成的,可见addr=sdram的最高物理地址*/
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
/*接下来是保留各种内存,保留内存,lcd,malloc,u-boot.以下是保留u-boot区域*/
addr -= gd->mon_len;
addr &= ~(4096 – 1);
/*从上面看,我们得到一个比较重要的地址addr = SDARM的最高地址(物理地址) - 一系列保存地址(其中包括u-boot)这里不禁想我们不是在运行u-boot吗,为什么还要保留一份拉?这是一个大问题*/
/*这里就是我们传给linux的机器ID,如果没有定义,我们需要自己定义,否则是跑不起来的*/
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
addr_sp = addr – TOTAL_MALLOC_LEN;
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
irq_sp = addr_sp;
addr_sp -= 12;
/*以上这段代码给出了addr_sp指向哪里,irq_sp指向哪里,很明显可以看出addr_sp是栈指针,irq_sp是中断异常栈指针,现在需要明确一个观念地址是从高地址到低地址延伸的,另外现在还没有用addr_sp来设置CPU寄存器,所以现在我们用的栈还是在start.S当中设置的SOC地址,现在有一种感觉,那就是整体上有了一点想法,上电这个时候SDRAM还没有像以后有BANK,用的是内部SDRAM,现在变开始设置用外部SDRAM。从代码上来看是汇编设置栈,跳到C语言获取板子信息,初始化硬件(不包括CPU和内部SDRAM),然后即将设置栈,接着....*/
gd->bd->bi_baudrate = gd->baudrate; //获取波特率
dram_init_banksize();
/*设置物理地址 bank,这里只设置了SDRAM的bank
gd->bd->bi_dram[0].start = CONFIG_SYS_SDRAM_BASE;
gd->bd->bi_dram[0].size = gd->ram_size;
可见这些信息都保存在gd当中的,然后会保存到bd当中*/
/*这些信息关系u-boot会在哪里*/
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
memcpy(id,&gd,sizeof(gd_t));
relocate_code(addr_sp,id,addr);
/*board_init_f分析完了,可见它做了几件事情初始化部分硬件,设置堆栈指针,划分外部SDRAM区域,现在跳到relocate_code中去,位于start.S当中*/
}