從大方面來講,u-boot的啓動分紅兩個階段,第一個階段主要的職責是準備初始化的環境,主要有如下幾點數組
①設置異常向量表數據結構
②把CPU的工做模式設置爲SVC32模式ide
③關閉中斷、MMU和cache函數
④關閉看門狗工具
⑤初始化內存、時鐘、串口oop
⑥設置堆棧fetch
⑦代碼搬移ui
⑧清bss段this
⑨跳轉到c語言中執行(第二階段)spa
此時系統尚未進入C語言的運行階段,並無堆棧,也就不須要額外的RAM。
第二階段在上一段創建好C語言運行環境的基礎上,進行各類外設的初始化,並循環執行用戶命令。主要流程圖以下
當咱們執行make命令來構建u-boot時,它的構建過程是:首先使用交叉編譯工具將各目錄下的源文件生成目標文件(*.o),目標文件生成後,會將若干個目標文件組合成靜態庫文件(*.a),最後經過連接各個靜態庫文件生成ELF格式的可執行文件。在連接的過程當中,須要根據連接腳本(通常是各個以lds爲後綴的文本文件),肯定目標文件的各個段,連接文件一般是board/<board>/目錄中的u-boot.lds文件。通常在連接腳本中經過
ENTRY(_start)
來指定入口爲_start標號,經過文本段(.text)的第一個目標來制定u-boot入口文件。因此咱們經過這個連接腳本文件能夠肯定u-boot執行的入口。
Tiny4412 u-boot的連接腳本內容爲
// board/samsung/tiny4412/u-boot.lds OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .text : { arch/arm/cpu/armv7/start.o (.text) board/samsung/tiny4412/libtiny4412.o (.text) arch/arm/cpu/armv7/exynos/libexynos.o (.text) *(.text) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); . = .; __u_boot_cmd_start = .; .u_boot_cmd : { *(.u_boot_cmd) } __u_boot_cmd_end = .; . = ALIGN(4); .rel.dyn : { __rel_dyn_start = .; *(.rel*) __rel_dyn_end = .; } .dynsym : { __dynsym_start = .; *(.dynsym) } .bss __rel_dyn_start (OVERLAY) : { __bss_start = .; *(.bss) . = ALIGN(4); _end = .; } /DISCARD/ : { *(.dynstr*) } /DISCARD/ : { *(.dynamic*) } /DISCARD/ : { *(.plt*) } /DISCARD/ : { *(.interp*) } /DISCARD/ : { *(.gnu*) } }
在本連接腳本文件中,定義了起始地址爲0x00000000,每一個段使用4字節對齊(.ALIGN(4)),幾個段分別爲代碼段(.text)、只讀數據段(.rodata)、數據段(.data)其中,代碼段的第一個目標爲arch/arm/cpu/armv7/start.o,在其中定義了映像文件的入口_start。
下面來具體分析一下這個start.S。
在文件的一開始定義了映像的入口_start和中斷向量表。
.globl _start //定義u-boot入口 _start: 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 ldr pc, _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 _pad: .word 0x12345678 /* now 16*4=64 */
系統開機進入到u-boot運行時,首先進入到u-boot的入口_start標號處,而後經過 b reset 跳轉到reset標號處,咱們就到reset標號一探究竟。
/* * the actual reset code */ reset: /* *設置CPU工做模式爲SVC32模式 * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr,r0 //...... //調用 cpu_init_crit #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif
首先會將CPU的工做模式設置爲svc32模式,而後便調用 cpu_init_crit ,須要注意的是,這裏使用的是 bl 指令,也就是說在運行完 cpu_init_crit 標號處的代碼以後,會經過
mov pc, lr @ back to my caller
指令回到reset中繼續執行
bl cpu_init_crit
下面的指令(因此這裏咱們應該使用 調用來描述更爲貼切)。下面咱們去看一下 cpu_init_crit 指令處作了哪些事
/************************************************************************* * * CPU_init_critical registers * * setup important registers * setup memory timing * *************************************************************************/ cpu_init_crit: //調用 cache_init bl cache_init /* *使 L1 I/D 無效 * Invalidate L1 I/D */ mov r0, #0 @ set up for MCR mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs mcr p15, 0, r0, c7, c5, 0 @ invalidate icache /* * 關閉 MMU 和 cache * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @ clear bits 13 (--V-) bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB mcr p15, 0, r0, c1, c0, 0 /* * Jump to board specific initialization... * The Mask ROM will have already initialized * basic memory. Go here to bump up clock rate and handle * wake up conditions. */ mov ip, lr @ persevere link reg across call //調用 lowlevel_init bl lowlevel_init @ go setup pll,mux,memory mov lr, ip @ restore link //返回到 reset 標號繼續執行 mov pc, lr @ back to my caller /*
首先分析 cache_init ,它被定義在 board/samsung/tiny4412/lowlevel_init.S 文件中
.globl cache_init cache_init: mov pc, lr
能夠看出來,這是一個空函數(暫且將它叫作函數-_-!!)。
接下來咱們就要去分析 lowlevel_init 了,它也被定義在board/samsung/tiny4412/lowlevel_init.S 文件中
.globl lowlevel_init lowlevel_init: //初始化串口 bl uart_asm_init //Read booting information //讀取啓動信息 bl read_om /* when we already run in ram, we don't need to relocate U-Boot. * and actually, memory controller must be configured before U-Boot * is running in ram. */ ldr r0, =0xff000fff bic r1, pc, r0 /* r0 <- current base addr of code */ ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */ bic r2, r2, r0 /* r0 <- current base addr of code */ cmp r1, r2 /* compare r0, r1 */ beq after_copy /* r0 == r1 then skip sdram init and u-boot.bin loading */ //初始化內存 /* Memory initialize */ bl mem_ctrl_asm_init //初始化系統時鐘 /* init system clock */ bl system_clock_init /* eg: 1: ;A cmp r0, #0 beq 1f ; r0==0那麼向前跳轉到B處執行 bne 1b ; 不然向後跳轉到A處執行 :1: ;B */ // 向前跳轉到1: 標號處執行 b 1f 1: //初始化 trust zone bl tzpc_init b load_uboot after_copy: #ifdef CONFIG_ENABLE_MMU bl enable_mmu #endif /* store second boot information in u-boot C level variable */ ldr r0, =CONFIG_PHY_UBOOT_BASE sub r0, r0, #8 ldr r1, [r0] ldr r0, _second_boot_info str r1, [r0] /* Print 'K' */ ldr r0, =S5PV310_UART_CONSOLE_BASE ldr r1, =0x4b4b4b4b str r1, [r0, #UTXH_OFFSET] //第二階段入口,調用C語言函數:board_init_f ldr r0, _board_init_f mov pc, r0 _board_init_f: .word board_init_f load_uboot: ldr r0, =INF_REG_BASE ldr r1, [r0, #INF_REG3_OFFSET] cmp r1, #BOOT_NAND beq nand_boot cmp r1, #BOOT_ONENAND beq onenand_boot cmp r1, #BOOT_MMCSD beq mmcsd_boot cmp r1, #BOOT_EMMC beq emmc_boot cmp r1, #BOOT_EMMC_4_4 beq emmc_boot_4_4 cmp r1, #BOOT_NOR beq nor_boot cmp r1, #BOOT_SEC_DEV beq mmcsd_boot nand_boot: mov r0, #0x1000 bl copy_uboot_to_ram b after_copy onenand_boot: bl onenand_bl2_copy /*goto 0x1010*/ b after_copy mmcsd_boot: #ifdef CONFIG_SMDKC220 //#ifdef CONFIG_CLK_BUS_DMC_200_400 ldr r0, =ELFIN_CLOCK_BASE ldr r2, =CLK_DIV_FSYS2_OFFSET ldr r1, [r0, r2] orr r1, r1, #0xf str r1, [r0, r2] //#endif #else #if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200) ldr r0, =ELFIN_CLOCK_BASE ldr r2, =CLK_DIV_FSYS2_OFFSET ldr r1, [r0, r2] orr r1, r1, #0xf str r1, [r0, r2] #endif #endif bl movi_uboot_copy b after_copy emmc_boot: #if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200) ldr r0, =ELFIN_CLOCK_BASE ldr r2, =CLK_DIV_FSYS1_OFFSET ldr r1, [r0, r2] orr r1, r1, #0xf str r1, [r0, r2] #endif bl emmc_uboot_copy b after_copy emmc_boot_4_4: /* read TCBCNT to get Transferred CIU card byte count */ ldr r0, =0x1255005c ldr r1, [r0] ldr r2, =0x6000 cmp r1, r2 /* store second boot information in DRAM */ ldr r0, =CONFIG_PHY_UBOOT_BASE sub r0, r0, #8 mov r3, #0 movlo r3, #1 str r3, [r0] /* if transferred CIU card byte count >= 0x6000 (24 KB) */ /* BL1 and BL2 are loaded from emmc 4.4 */ /* Otherwise BL1 and BL2 are loaded from sdmmc ch2. */ blo mmcsd_boot /* mmc ch4 devider value change */ bl mmc_ch4_devider_change /* u-boot image copy from boot partition to DRAM. */ bl emmc_4_4_uboot_copy /* Exit Boot mood */ bl emmc_4_4_endbootOp_eMMC b after_copy
在 lowlevel_init 中,主要作了一些初始化工做,好比系統時鐘、內存、串口等的初始化工做,而後初始化堆棧、清bss段,並進行了代碼搬移,爲第二階段C語言程序運行提供保障。最後經過
ldr r0, _board_init_f mov pc, r0
指令跳轉到第二階段C語言函數 board_init_f 函數處。接着咱們就去分析一下這個函數。
在分析board_init_f函數以前,先來了解如下gd_t數據結構
// arch/arm/include/asm/global_data.h typedef struct global_data { bd_t *bd; unsigned long flags; unsigned long baudrate; unsigned long have_console; /* serial_init() was called */ 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 #ifdef CONFIG_FSL_ESDHC unsigned long sdhc_clk; #endif #ifdef CONFIG_AT91FAMILY /* "static data" needed by at91's clock.c */ unsigned long cpu_clk_rate_hz; unsigned long main_clk_rate_hz; unsigned long mck_rate_hz; unsigned long plla_rate_hz; unsigned long pllb_rate_hz; unsigned long at91_pllb_usb_init; #endif #ifdef CONFIG_ARM /* "static data" needed by most of timer.c on ARM platforms */ unsigned long timer_rate_hz; unsigned long tbl; unsigned long tbu; unsigned long long timer_reset_value; unsigned long lastinc; #endif unsigned long relocaddr; /* Start address of U-Boot in RAM */ phys_size_t ram_size; /* RAM size */ unsigned long mon_len; /* monitor len */ unsigned long irq_sp; /* irq stack pointer */ unsigned long start_addr_sp; /* start_addr_stackpointer */ unsigned long reloc_off; #if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE)) unsigned long tlb_addr; #endif void **jt; /* jump table */ char env_buf[32]; /* buffer for getenv() before reloc. */ } gd_t; #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
u-boot中使用一個結構體gd_t來存儲全局區的數據,使用一個存儲在寄存器中的指針gd來記錄全局數據區的地址。
DECLARE_GLOBAL_DATA_PTR
在board/samsung/tiny4412/tiny4412.c被聲明。
u-boot 中還有一個數據結構 bd_t用來存放板級相關的全局數據,是gd_t中結構體指針成員bd的結構體類型。
// arch/arm/include/asm/u-boot.h typedef struct bd_info { int bi_baudrate; /* serial console baudrate */ unsigned long bi_ip_addr; /* IP Address */ 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]; } bd_t;
u-boot啓動內核時要給內核傳遞參數,這時須要使用gd_t、bd_t結構體中的信息來設置標記列表。瞭解了這兩個數據結構咱們就去分析一下board_init_f函數
// arch/arm/lib/board.c void board_init_f(ulong bootflag) { bd_t *bd; init_fnc_t **init_fnc_ptr; gd_t *id; ulong addr, addr_sp; //計算全局數據結構的地址,保存在gd指針中 /* Pointer is writable since we allocated a register for it */ gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset((void*)gd, 0, sizeof (gd_t)); gd->mon_len = _bss_end_ofs; 逐個調用init_sequence數組的初始化函數 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang(); } } debug ("monitor len: %08lX\n", gd->mon_len); /* * Ram is setup, size stored in gd !! */ debug ("ramsize: %08lX\n", gd->ram_size); //填充gd數據結構 gd->bd->bi_baudrate = gd->baudrate; /* Ram ist board specific, so move it to board code ... */ 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)); //調用arch/arm/cpu/armv7/start.S relocate_code relocate_code(addr_sp, id, addr); /* NOTREACHED - relocate_code() does not return */ }
u-boot使用一個init_sequence數組來存儲大多數開發板都要執行的初始化函數的函數指針
// arch/arm/lib/board.c typedef int (init_fnc_t)(void); init_fnc_t *init_sequence[] = { #if defined(CONFIG_ARCH_CPU_INIT) arch_cpu_init, /* basic arch cpu dependent setup */ #endif #if defined(CONFIG_BOARD_EARLY_INIT_F) board_early_init_f, #endif timer_init, /* initialize timer */ #ifdef CONFIG_FSL_ESDHC get_clocks, #endif env_init, /* initialize environment */ #if defined(CONFIG_S5P6450) && !defined(CONFIG_S5P6460_IP_TEST) init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ #endif 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 #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c, #endif dram_init, /* configure available RAM banks */ #if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI) arm_pci_init, #endif NULL, };
board_init_f函數在調用完初始化函數指針、填充完gd結構以後,調用了arch/arm/cpu/armv7/start.S中的relocate_code去看一下relocate_code作了什麼
.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 */ /* Set up the stack */ stack_setup: mov sp, r4 adr r0, _start #if defined(CONFIG_S5PC110) && defined(CONFIG_EVT1) && !defined(CONFIG_FUSED) sub r0, r0, #16 #endif #ifndef CONFIG_PRELOADER cmp r0, r6 beq clear_bss /* skip relocation */ #endif mov r1, r6 /* r1 <- scratch for copy_loop */ ldr r2, _TEXT_BASE 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 #ifndef CONFIG_PRELOADER /* * fix .rel.dyn relocations */ 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 clear_bss: ldr r0, _bss_start_ofs ldr r1, _bss_end_ofs ldr r3, _TEXT_BASE /* Text base */ mov r4, r6 /* reloc addr */ add r0, r0, r4 add r1, r1, r4 mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 bne clbss_l #endif /* #ifndef CONFIG_PRELOADER */ /* * We are done. Do not return, instead branch to second part of board * initialization, now running from RAM. */ //調用board_init_r jump_2_ram: 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函數
// arch/arm/lib/board.c void board_init_r(gd_t *id, ulong dest_addr) { char *s; bd_t *bd; ulong malloc_start; gd = id; bd = gd->bd; gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */ monitor_flash_len = _bss_start_ofs; debug ("monitor flash len: %08lX\n", monitor_flash_len); board_init(); /* Setup chipselects */ debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr); /* The Malloc area is immediately below the monitor copy in DRAM */ malloc_start = dest_addr - TOTAL_MALLOC_LEN; mem_malloc_init(malloc_start, TOTAL_MALLOC_LEN); //初始化MMC #ifdef CONFIG_GENERIC_MMC mmc_initialize(bd); #endif //初始化環境變量 /* initialize environment */ env_relocate(); //將環境變量中的IP填充到gd結構體 /* IP Address */ gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr"); stdio_init(); /* get the devices list going. */ jumptable_init(); //初始化並使能中斷 /* set up exceptions */ interrupt_init(); /* enable exceptions */ enable_interrupts(); /* Initialize from environment */ if ((s = getenv("loadaddr")) != NULL) { load_addr = simple_strtoul(s, NULL, 16); } //進入到main_loop /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop(); } /* NOTREACHED - no way out of command loop except booting */ }
最後調用了main_loop
待完成:
①main_loop分析
②啓動內核過程
。。。。。。