uboot要做的工作
- 关看门狗
- 初始化时钟
- 初始化SDRAM
- 将程序从nand flash拷贝到SDRAM
- 设置栈
第一阶段源码分析
第一步,跳转到reset
.globl _start
_start: b reset
reset做了些啥,注释说设置cpu为SVC32模式
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
这里首先要介绍ARM架构的7种CPU模式
处理器模式 | 说明 | 备注 |
---|---|---|
用户(usr) | 正常程序工作模式 | 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换 |
系统(sys) | 用于支持操作系统的特权任务等 | 与用户模式类似,但具有可以直接切换到其它模式等特权 |
快中断(fiq) | 支持高速数据传输及通道处理 | FIQ异常响应时进入此模式 |
中断(irq) | 用于通用中断处理 | IRQ异常响应时进入此模式 |
管理(svc) | 操作系统保护代码 | 系统复位和软件中断响应时进入此模式 |
中止(abt) | 用于支持虚拟内存和/或存储器保护 | 在ARM7TDMI没有大用处 |
未定义(und) | 支持硬件协处理器的软件仿真 | 未定义指令异常响应时进入此模式 |
SVC模式又称超级用户模式,比SYS能控制的硬件更多,允许用户进一步控制计算机,所以初始化为SVC模式是更有利于进行系统的初始化工作的
下一步是关看门狗
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000
# define INTMOD 0X4A000004
# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* 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
*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
#if 0
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
进行CPU的关键初始化,这里面进行了一个比较,判断程序是从内部的4K RAM中启动,还是从SDRAM中指定的地址启动,只有当程序从SDRAM中启动时,才进行CPU的关键初始化
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
blne cpu_init_crit
#endif
接下来分析cpu_init_crit
- 第一步:flush v4 I/D caches,清cache
- 第二步:disable MMU stuff and caches,关闭mmu和caches,为什么要关掉caches,caches是CPU内部的一个2级缓存,它的作用是将常用的数据和指令放在CPU内部。Caches是通过CP15管理的,刚上电的时候,CPU还不能管理Caches。上电的时候指令Cache可关闭,也可不关闭,但数据Cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常
- 第三步:调用lowlevel_init,初始化存储控制器
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
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
/*
* 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.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
下一步设置栈指针,从TEXT_BASE,往下留下了一些空间做其它用途后,确定了sp寄存器的值
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
初始化时钟,老韦自己魔改官方的代码
/* 设置时钟, 使用汇编 */
#define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
#define S3C2440_UPLL_48MHZ ((0x38<<12)|(0x02<<4)|(0x02))
#define S3C2440_CLKDIV (0x05) // | (1<<3)) /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL/2 */
ldr r1, =CLKDIVN
mov r2, #S3C2440_CLKDIV
str r2, [r1]
mrc p15, 0, r1, c1, c0, 0 // read ctrl register
orr r1, r1, #0xc0000000 // Asynchronous
mcr p15, 0, r1, c1, c0, 0 // write ctrl register
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
// delay
mov r0, #0x200
1: subs r0, r0, #1
bne 1b
// Configure MPLL
ldr r0,=MPLLCON
ldr r1,=S3C2440_MPLL_400MHZ
str r1,[r0]
// delay
mov r0, #0x200
1: subs r0, r0, #1
bne 1b
//Configure UPLL
ldr r0, =UPLLCON
ldr r1, =S3C2440_UPLL_48MHZ
str r1, [r0]
// delay
mov r0, #0x200
1: subs r0, r0, #1
bne 1b
最后,将代码从nand flash搬运到SDRAM中,代码的重定位
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq clear_bss
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
#if 1
bl CopyCode2Ram /* r0: source, r1: dest, r2: size */
然后清除bss段,bss段是未初始化的全局和静态变量
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
最后一步,调用c函数,进入第二阶段
ldr pc, _start_armboot
_start_armboot: .word start_armboot
uboot第一阶段启动的总结
- 设置CPU为SVC模式
- 关看门狗
- 屏蔽中断
- 初始化SDRAM
- 设置栈
- 初始化时钟
- 代码从FLASH拷到SDRAM内存
- 清空bss段
- 进入start_armboot程序
uboot第二阶段代码分析
第二阶段都是用C语言编写的代码,从start_armboot开始阅读
以下这个for循环,会调用一些init_sequence内的函数指针完成各种初始化
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
这就是要进行的初始化操作,大致了解即可,不深究
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,
};
为了从nor flash和nand flash读出内核,所以必须对flash进行初始化,使uboot有读flash的功能,flash_init
完成了对nor flash的初始化
size = flash_init ();
对于nand flash的初始化,是nand_init
nand_init(); /* go init the NAND */
接下来看一下环境变量的初始化,环境变量首先来自于默认的,如果flash中保存的有环境变量,则使用flash的环境变量
/* initialize environment */
env_relocate ();
最后uboot进入一个循环
for (;;) {
main_loop ();
}
这个主循环中,有一段对倒数计时的判断,如果时间到了,就启动内核,其中启动的参数bootcmd来自于环境变量
bootcmd就两个参数,从nand flash读取内核,放到什么位置,如何启动内核
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
printf("Booting Linux ...\n");
run_command (s, 0);
}
如果不启动内核,则循环检测串口的数据,然后根据你输入的命令,执行相应的操作
len = readline (CFG_PROMPT);
rc = run_command (lastcommand, flag);
可以看出,uboot的核心其实就是这个run_command
函数