从大方面来说,u-boot的启动分成两个阶段,第一个阶段主要的职责是准备初始化的环境,主要有以下几点
①设置异常向量表
②把CPU的工作模式设置为SVC32模式
③关闭中断、MMU和cache
④关闭看门狗
⑤初始化内存、时钟、串口
⑥设置堆栈
⑦代码搬移
⑧清bss段
⑨跳转到c语言中执行(第二阶段)
此时系统还没有进入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 ", gd->mon_len); /* * Ram is setup, size stored in gd !! */ debug ("ramsize: %08lX ", 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 ", 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 ", monitor_flash_len); board_init(); /* Setup chipselects */ debug ("Now running in RAM - U-Boot at: %08lx ", 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分析
②启动内核过程
。。。。。。