一看二做三总结
分类: LINUX
2010-09-05 19:55:54
Chapter 5: uboot启动流程
1. cpu/arm920t/start.s
1) 进入管理者模式
入口函数_start通过b命令直接跳转到reset执行。
这里首先是要设置cpsr寄存器的内容。Cpsr寄存器格式如下:
Mrs is used to transfer the PSR register’s value to common register. PSR contains two register: CPSR and SPSR. Msr is used to transfer the common register’s value to PSR register.
/* set the cpu to SVC32 mode */ Mrs r0,cpsr Bic r0,r0,#0x1f /* clear mode */ Orr r0,r0,#0xd3 /* set mode */ Msr cpsr,r0 |
结合寄存器说明,可以明白此处是为了禁止IRQ中断与FIRQ中断,并进入管理者模式SVC
2) 禁止看门狗
ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] |
pWTCON是看门狗寄存器的地址,我们定义如下:
#define pWTCON 0x53000000
Bit5用于使能/禁止WatchDog,所以给WTCON寄存器清零会禁止看门狗。
3) 关中断
/* 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 |
# define INTMSK 0x4A000008
# define INTSUBMSK 0x4A00001C
4) 时钟设置
S3c2440的内部有两个PLL,他们的源都是OSC。一个是MPLL,用于内核与总线;另一个是UPLL,用于USB设备。
时钟控制逻辑给整个芯片提供三种时钟:FCLK用于CPU核;HCLK用于AHB(Advanced High performance Bus)总线上的设备;PCLK用于APB(Advanced Peripheral Bus)总线上的设备上。其中,FCLK为PLL送进来的时钟频率,其他两个都是通过对FCLK分频得到的。
为了降低电磁干扰、降低布线难度,CPU外接的晶振频率都非常低,需要通过PLL倍频。刚上电时,PLL没有启动,此时FCLK频率就等于外部输入频率。启动后,软件会设置MPLL。下表是
下面是2440可以使用的频率表:
从上图中,可以看到HCLK与PCLK的频率除受HDIVN与PDIVN的影响外,还受到HCLK3_HALF与HCLK4_HALF的影响。这两个值可以通过CAMDIVN寄存器来设置:
HDIVN与PDIVN可以在CLKDINV寄存器设置:
可通过下列的会编码设置:
/* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] |
# define CLKDIVN 0x4C000014
此处使用默认值就可以。
5) cpu_init_crit
Cpu_init_crit is used to setup important registersand memory timing.
CP15 register is used for MMU, include TLB and Cache. As the chart following, It contains 16 registers and provides fully operations. For more details, browse the datasheet Arm920t.pdf.
/* 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
It is really hard to understand this code. Fortunitely, All of these code can be find in the ARM920T.pdf.
6) lowlevel_init (board\smdk2440nand\lowlevel_init.S)
lowlevel_init was used to transfer the memory controller paras from Flash to SDRAM. So we can set the memory controller registers by it. It will be used both uboot and linux.
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
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
7) 复制bootloader的第二阶段代码到RAM空间
这里会将整个uboot的代码都复制到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 stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
8) set stack
Set the SP register to a free momory address. Some memory is reserved to malloc area and global infos which include gd and bd_t.
/* 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 /* global info */
sub sp, r0, #12 /* leave 3 words for abort-stack */
9) clear 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
10) 进入第二阶段
ldr pc, _start_armboot
2 lib_arm /Board.c
1) start_armboot
_armboot_start is the first function in text segment. Some of the varibles are stored here.
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
void **jt; /* jump table */
} gd_t;
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
Text 段之前的一段空间被用于存储系统的启动变量gd_t与bd_t。在函数start_armboot中会完成该段的初始化。Uboot执行完后会切换到linux,Uboot需要传给linux的参数就是通过此处的参数区实现的。
2) 设备初始化
设备的初始化分两部分,第一部分是通过下面的代码实现:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
其中,init_sequence 中存储了需要初始化的设别的初始化函数。
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 */
dram_init, /* configure available RAM banks */
NULL,
};
第二部分为start_armboot函数后续调用的初始化函数。
初始化flash:flash_init()
初始化malloc:mem_malloc_init()
读入环境参数:env_relocate()
设置mac与ip:
初始化额外设备:devices_init()/* 注:此处的设备初始化不是必须的,可以根据实际情况裁剪 */
存储用到的一些函数指针:jumptable_init()
初始化控制台:console_init_r()
使能中断,这样才可以接收串口与网口数据:enable_interrupts()
初始化以太网:eth_initialize()
3) 进入接收命令的状态
for (;;) {
main_loop ();
}
Main_loop会直接调用内核启动函数,因此不需要再返回。
3 common/Main.c
1) main_loop
这个函数不需要多讲,主要就是打印提示信息并等待用户输入有输入则会进入run_command函数。
2) run_command
Run_command实现了从输入命令道函数指针的调用,内容为字符串解析。其中比较有意思的是find_cmd函数。
4 common/command.c
1) board\smdk2440nand\U-boot.lds
u-boot.lds是连接脚本,其中定义了一个特殊的段(.u_boot_cmd),用于存储bootmenu中可以使用的命令。
在文件u-boot.lds中有如下定义:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
该段的含义为定义一个新段.u_boot_cmd。__u_boot_cmd_start为该段的第一个符号;__u_boot_cmd_end为该段的最后一个符号。
如果要把一个变量放入.u_boot_cmd段,需要通过宏U_BOOOT_CMD实现。宏的定义如下:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name, maxargs, rep,cmd, usage, help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
例如:
我们定义变量
U_BOOT_CMD(bootm, 16, 1, do_bootm, “bootm--usage”, “help”)
展开后为:
cmd_tbl_t __u_boot_cmd_ bootm Struct_Section =
{“bootm”, 16, 1, do_bootm, “bootm--usage”}
如果把Struct_Section也展开,则为:
cmd_tbl_t __u_boot_cmd_ bootm __attribute__ ((unused,section (".u_boot_cmd"))) = \
{“bootm”, 16, 1, do_bootm, “bootm--usage”}
__attribute__((section(“bar”)))命令用于把某个符号放到特定段中,因此在这里符号__u_boot_cmd_ bootm会被放入段.u_boot_cmd中。
Uboot中所有命令都是存放在这个特殊段中的。当用户输入一个命令时(需要与定义时的name变量同名),会在函数find_cmd中从__u_boot_cmd_start开始循环匹配,匹配到后就执行对应变量中的cmd项;如果直到__u_boot_cmd_end也没有发现,则匹配失败。
例如:用户输入bootm,会找到__u_boot_cmd_bootm这个变量,然后会取出cmd命令(这里注册的是do_bootm)来执行。
2) find_cmd(const char *cmd)
依据上面的规则匹配到cmd后开始执行。常见的为下面几个命令:
bootm 从内存或rom加载
bootp 从网络加载
nboot 从flash加载
3) do_bootm
do_bootm中用到了宏SHOW_BOOT_PROGRESS来表示进度,一般这个宏不会做任何事情,但我们可以利用这个函数把do_bootm分为八部分。
前五部是基本的校验工作:
第一步:拷贝Image头并判断magic字段是否正确;
第二步:Image头crc校验;
第三步:Image文件进行crc校验;
第四步:校验Architecture字段;
第五步:校验Image类型
从第六步开始回进行异常向量等关键的操作:
第六步:根据压缩类型ih_comp解压Image;
第七步:根据ih_type进行相应操作,如果是kernel则不作任何操作;
第八步:根据操作系统类型ih_os调用不同的启动函数,linux会调用do_bootm_linux;
4) do_bootm_ linux
do_bootm_linux中大部分不会运行到,比较重要的是cleanup_before_linux函数,这个函数会禁止中断并禁止I-Cache与D-Cache,然后会根据ih_ep指向的地址跳入内核。
至此,uboot就结束自己的使命了。