1. 程式人生 > >U-boot引導流程分析一

U-boot引導流程分析一

大多數BootLoader都分為stage1和stage2兩大部分,U-boot也不例外。依賴於cpu體系結構的程式碼(如裝置初始化程式碼等)通常都放在stage1且可以用匯編語言來實現,而stage2則通常用C語言來實現,這樣可以實現複雜的功能,而且有更好的可讀性和移植性。 1、 stage1(start.s程式碼結構) U-boot的stage1程式碼通常放在start.s檔案中,它用匯編語言寫成,其主要程式碼部分如下: (1) 定義入口。由於一個可執行的image必須有一個入口點,並且只能有一個全域性入口,通常這個入口放在rom(Flash)的0x0地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改聯結器指令碼來完成。 (2)設定異常向量(exception vector)。 (3)設定CPU的速度、時鐘頻率及中斷控制暫存器。 (4)初始化記憶體控制器 。 (5)將rom中的程式複製到ram中。 (6)初始化堆疊 。 (7)轉到ram中執行,該工作可使用指令ldrpc來完成。 2、 stage2(C語言程式碼部分) lib_arm/board.c中的start armboot是C語言開始的函式,也是整個啟動程式碼中C語言的主函式,同時還是整個u-boot(armboot)的主函式,該函式主要完成如下操作: (1)呼叫一系列的初始化函式。 (2)初始化flash裝置。 (3)初始化系統記憶體分配函式。 (4)如果目標系統擁有nand裝置,則初始化nand裝置。 (5)如果目標系統有顯示裝置,則初始化該類裝置。 (6)初始化相關網路裝置,填寫ip,c地址等。 (7)進入命令迴圈(即整個boot的工作迴圈),接受使用者從串列埠輸入的命令,然後進行相應的工作。 下面結合SMDK2410(ARM920T)的U-Boot的原始碼來分析下U-Boot的整個引導流程。

Stage I過程分析

從檔案層面上看主要流程是在這幾個檔案中進行的:cpu/xxx/start.S、board/xxx/lowlevel_init.S、lib_arm_board.c。

設定中斷向量表

cpu/arm920t/start.S開頭有如下程式碼:
/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl _start
_start:	b       reset						/* 復位,CPU復位,b是不帶返回的跳轉,即無條件直接跳轉至reset處執行 */
	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					/* 16位元組對齊,不足之處,用0xdeadbeef填充(搞笑,用“死牛”填充) */   
紅色標記的"undefined_instruction"是一個標號,即地址值,對應的就是發生"未定義指令"中斷時,系統處理該中斷的程式碼的地址。
/*
 * exception handlers
 */
	.align  5
undefined_instruction:
	get_bad_stack
	bad_save_user_regs
	bl do_undefined_instruction

	.align	5
software_interrupt:
	get_bad_stack
	bad_save_user_regs
	bl 	do_software_interrupt

	.align	5
prefetch_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_prefetch_abort

	.align	5
data_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_data_abort

	.align	5
not_used:
	get_bad_stack
	bad_save_user_regs
	bl 	do_not_used

#ifdef CONFIG_USE_IRQ

	.align	5
irq:
	get_irq_stack
	irq_save_user_regs
	bl 	do_irq
	irq_restore_user_regs

	.align	5
fiq:
	get_fiq_stack
	/* someone ought to write a more effiction fiq_save_user_regs */
	irq_save_user_regs
	bl 	do_fiq
	irq_restore_user_regs

#else

	.align	5
irq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_irq

	.align	5
fiq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_fiq

#endif
在cpu/arm920t/interrupts.c中有對應的do_undefined_instruction方法。
void do_undefined_instruction (struct pt_regs *pt_regs)
{
	printf ("undefined instruction\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_software_interrupt (struct pt_regs *pt_regs)
{
	printf ("software interrupt\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_prefetch_abort (struct pt_regs *pt_regs)
{
	printf ("prefetch abort\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_data_abort (struct pt_regs *pt_regs)
{
	printf ("data abort\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_not_used (struct pt_regs *pt_regs)
{
	printf ("not used\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_fiq (struct pt_regs *pt_regs)
{
	printf ("fast interrupt request\n");
	show_regs (pt_regs);
	bad_mode ();
}


void do_irq (struct pt_regs *pt_regs)
{
#if defined (CONFIG_USE_IRQ) && defined (CONFIG_ARCH_INTEGRATOR)
	/* ASSUMED to be a timer interrupt  */
	/* Just clear it - count handled in */
	/* integratorap.c                   */
	*(volatile ulong *)(CFG_TIMERBASE + 0x0C) = 0;
#else
	printf ("interrupt request\n");
	show_regs (pt_regs);
	bad_mode ();
#endif
}

當一箇中斷髮生,CPU首先會在中斷向量表中查詢對應的中斷向量,然後跳轉到對應的中斷處理程式處執行。

CPU復位操作,會使CPU進入SVC模式。

設定CPU進入SVC模式

ARM處理器有7中模式,由CPSR的[4:0]決定。

使用者模式USR:正常程式執行的工作模式。只能讀CPSR。

系統模式SYS:與使用者模式共用一套暫存器。用於支援作業系統的特權任務模式,它可以直接切換到其他模式。

管理模式SVC:作業系統的特權任務模式。系統復位和軟體中斷時才能進入該模式。

除了使用者模式外,其他模式都是特權模式。只有在特權模式下,才允許對當前的程式狀態暫存器的控制位直接進行讀寫。特權模式中除了系統模式外,都是異常模式。特權模式可以訪問所有系統資源。一般,進入特權模式是為了處理中斷、異常或者訪問被保護的資源。

ARM處理器的工作模式:
處理器模式 特權模式 說明 CPSR[4:0]
使用者模式USR 使用者程式執行模式 10000
系統模式SYS 執行特權級的作業系統級任務
管理模式SVC 提供作業系統使用的保護模式 10011
異常終止模式ABT 用於虛擬儲存及儲存保護 10111
未定義模式UND 用於支援通過軟體模擬硬體的協處理器 11011
一般中斷模式IRQ 使用者通常的中斷使用 10010
快速中斷模式FIQ 用於高速資料傳輸和通道處理 10001

CPSR,Current Program Status Register,當前程式狀態暫存器。

/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
	bic	r0,r0,#0x1f		/* 位清零,工作模式為清零 */
	orr	r0,r0,#0xd3		/* 邏輯或,工作模式為設為'(110)10011',即設定CPU為SVC模式,並將中斷禁止位和快中斷禁止位置1(遮蔽)*/
	msr	cpsr,r0

設定控制暫存器,關看門狗,關中斷

看門狗是一個硬體模組,當系統出現宕機現象,看門狗就會自動重啟系統。看門狗的硬體邏輯:其硬體有一個記錄超時功能,它要求使用者每隔一段時間(根據需求配置)對某個暫存器置位(簡稱“喂狗”),若超時為進行“喂狗”動作,系統就會重啟。
/* 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 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	/* 向看門狗暫存器寫入0 */
	str     r1, [r0]
	/*
	* mask all IRQs by setting all bits in the INTMR - default
	*/
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK	/* 遮蔽所有中斷 */
	str	r1, [r0]

INTMSK暫存器是一個32位暫存器,每一位對應一箇中斷,向其中寫入0xffffffff,即將INTMSK暫存器全部置1,從而遮蔽對應的中斷。

INTSUBMSK暫存器也是一個32位暫存器,但是隻使用了低15位,向其中寫入0x7fffffff,即可遮蔽對應的中斷。

以上程式碼完成了對pWTCON、INTMSK、INTSUBMSK、CLKDIVN四個暫存器的地址設定,並且通過向看門狗暫存器寫入0,禁止看門狗的復位功能(即重啟功能),否則,在U-Boot啟動過程中,CPU將不斷重啟。

初始化CPU時鐘

關閉MMU和Cache

MMU,Memory Management Unit,即記憶體管理單元。MMU是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到底做了哪些操作呢?
#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 向c7寫入0 */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB 向c8寫入0 */

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0	/* 讀出控制暫存器到r0中 */
	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	/* 儲存r0到控制暫存器中 */

	/*
	 * 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		/* 跳轉到lowlevel_init,執行完lowlevel_init後返回 */
	mov	lr, ip
	mov	pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

注:TLB,Translation Lookaside Buffer,即旁路緩衝,它的作用是在處理器訪問記憶體資料的時候做地址轉換。TLB中存放了一些頁表文件,檔案中記錄了虛擬地址和實體地址的對映關係。當程式訪問呢一個虛擬地址,會從TLB中查詢出對應的實體地址,然後訪問實體地址。

程式碼中的c0、c1、c7、c8是ARM920T的協處理器CP15的暫存器。其中,c7是cache控制暫存器,c8是TLB控制暫存器。

關閉MMU是通過修改CP15協處理器的c1暫存器來實現的。CP15的c1暫存器格式如下:

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

·

·

V

I

·

·

R

S

B

·

·

·

·

C

A

M

對應位的意義如下:

置0

置1

V

異常向量在0x00000000

異常向量在0xFFFF0000

I

關閉ICaches

開啟ICaches

R、S

用來與頁表中的描述符一起確定記憶體的訪問許可權

B

CPU為小位元組序

CPU為大位元組序

C

關閉DCaches

開啟DCaches

A

資料訪問時不進行地址對齊檢查

資料訪問時進行地址對齊檢查

M

關閉MMU

開啟MMU

初始化記憶體

lowlevel_init完成了記憶體初始化的任務,由於記憶體初始化時依賴開發板的,所以lowlevel_init的程式碼一般放在board下面相應的目錄。對於SMDK2410,lowlevel_init的程式碼在board/smdk2410/lowlevel_init.S中。
_TEXT_BASE:
	.word	TEXT_BASE

.globl lowlevel_init
lowlevel_init:
	/* 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

	/* everything is fine now */
	mov	pc, lr

	.ltorg
/* the literal pools origin */
SMRDATA:	/* 13個暫存器的值 */
    .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

lowlevel_init的作用就是將SMRDATA開始的13個值複製給開始地址[BWSCON]13個暫存器,從而完成了儲存控制器的設定。

複製StageII的程式碼到RAM

上面初始化玩記憶體之後會跳轉回start.S繼續執行。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
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 */

relocate處首先比較_start和_TEXT_BASE的地址,如果相同則說明程式以及在記憶體中,無須載入。copy_loop處則實現了迴圈複製Flash的資料到記憶體中,每次複製8個字長的資料。

設定堆疊

只要將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    */

根據上面的程式碼知道U-Boot記憶體使用情況。


清除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

BSS段中存放有初始值為0得變數、無初始值的全域性變數和一些靜態變數。清除BSS段,就是將這些變數初始化賦值0,否則這些變數的初始值將是一個隨機的值,若有程式直接使用這些值將會引起未知的後果。

跳轉到Stage II程式碼入口

#if 0
	/* try doing this stuff after the relocation */
	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, =INTMR
	str	r1, [r0]

	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
	/* END stuff after relocation */
#endif

	ldr	pc, _start_armboot

_start_armboot:	.word start_armboot

由程式碼可以看出,Stage II程式碼的入口處在start_armboot。