前面我们分析到了s_init函数,现在继续。
1、s_init函数
然后调用s_init来进行更多模块的初始化。函数s_init在arch/arm/cpu/armv7/sunxi/board.c中定义,代码如下。
87 void s_init(void) 88 { 89 #if !defined CONFIG_SPL_BUILD && defined CONFIG_SUN7I 90 /* Enable SMP mode for CPU0, by setting bit 6 of Auxiliary Ctl reg */ 91 asm volatile( 92 "mrc p15, 0, r0, c1, c0, 1\n" 93 "orr r0, r0, #1 << 6\n" 94 "mcr p15, 0, r0, c1, c0, 1\n"); 95 #endif 96 97 watchdog_init(); 98 clock_init(); 99 timer_init(); 100 gpio_init(); 101 102 #ifdef CONFIG_SPL_BUILD 103 gd = &gdata; 104 preloader_console_init(); 105 106 #ifdef CONFIG_SPL_I2C_SUPPORT 107 /* Needed early by sunxi_board_init if PMU is enabled */ 108 i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE); 109 #endif 110 111 sunxi_board_init(); 112 #endif 113 }
在该函数内部,**对watchdog、时钟、定时器timer和gpio进行初始化,并对console终端进行初始化,然后进行i2c的初始化,**最后调用sunxi_board_init函数,该函数在board/sunxi/board.c中定义,代码如下。
2、sunxi_board_init
80 void sunxi_board_init(void) 81 { 82 int power_failed = 0; 83 unsigned long ramsize; 84 85 printf("DRAM:"); 86 ramsize = sunxi_dram_init(); 87 printf(" %lu MiB\n", ramsize >> 20); 88 if (!ramsize) 89 hang(); 90 91 #ifdef CONFIG_AXP152_POWER 92 power_failed = axp152_init(); 93 power_failed |= axp152_set_dcdc2(1400); 94 power_failed |= axp152_set_dcdc3(1500); 95 power_failed |= axp152_set_dcdc4(1250); 96 power_failed |= axp152_set_ldo2(3000); 97 #endif 98 #ifdef CONFIG_AXP209_POWER 99 power_failed |= axp209_init(); 100 power_failed |= axp209_set_dcdc2(1400); 101 #ifdef CONFIG_FAST_MBUS 102 power_failed |= axp209_set_dcdc3(1300); 103 #else 104 power_failed |= axp209_set_dcdc3(1250); 105 #endif 106 power_failed |= axp209_set_ldo2(3000); 107 power_failed |= axp209_set_ldo3(2800); 108 power_failed |= axp209_set_ldo4(2800); 109 #endif 110 111 /* 112 * Only clock up the CPU to full speed if we are reasonably 113 * assured it's being powered with suitable core voltage 114 */ 115 if (!power_failed) 116 #ifdef CONFIG_SUN7I 117 clock_set_pll1(912000000); 118 #else 119 clock_set_pll1(1008000000); 120 #endif 121 else 122 printf("Failed to set core voltage! Can't set CPU frequency\n");
该函数首先对DRAM进行初始化,然后利用前面初始化的i2c来对axp209电源管理芯片进行配置。
现在回到start.S当中,reset复位异常向量处理的最后一步是调用_main函数。_main函数存在于arch/arm/lib/crt0.S中,在分析代码之前,可以先看看这个函数的注释,注释写的非常详细。
3、_main函数
13 /* 14 * This file handles the target-independent stages of the U-Boot 15 * start-up where a C runtime environment is needed. Its entry point 16 * is _main and is branched into from the target's start.S file. 17 * 18 * _main execution sequence is: 19 * 20 * 1. Set up initial environment for calling board_init_f(). 21 * This environment only provides a stack and a place to store 22 * the GD ('global data') structure, both located in some readily 23 * available RAM (SRAM, locked cache...). In this context, VARIABLE 24 * global data, initialized or not (BSS), are UNAVAILABLE; only 25 * CONSTANT initialized data are available. 26 * 27 * 2. Call board_init_f(). This function prepares the hardware for 28 * execution from system RAM (DRAM, DDR...) As system RAM may not 29 * be available yet, , board_init_f() must use the current GD to 30 * store any data which must be passed on to later stages. These 31 * data include the relocation destination, the future stack, and 32 * the future GD location. 33 * 34 * (the following applies only to non-SPL builds) 35 * 36 * 3. Set up intermediate environment where the stack and GD are the 37 * ones allocated by board_init_f() in system RAM, but BSS and 38 * initialized non-const data are still not available. 39 * 40 * 4. Call relocate_code(). This function relocates U-Boot from its 41 * current location into the relocation destination computed by 42 * board_init_f(). 43 * 44 * 5. Set up final environment for calling board_init_r(). This 45 * environment has BSS (initialized to 0), initialized non-const 46 * data (initialized to their intended value), and stack in system 47 * RAM. GD has retained values set by board_init_f(). Some CPUs 48 * have some work left to do at this point regarding memory, so 49 * call c_runtime_cpu_setup. 50 * 51 * 6. Branch to board_init_r(). 52 */
我们对照注释先整理一下_main函数的执行顺序:
1)为调用board_init_f()函数准备最初的环境。
该环境仅仅提供一个栈和存储GD结构体(全局数据)的空间,它们都位于一些可用的RAM中(比如SRAM和锁定的缓存中)。在这样的环境下,可变的全局变量,包括已初始化的和未初始化的(BSS),都是不可用的;只有已初始化的常量才是可用的。
2)调用board_init_f()函数。
**该函数为后续在系统RAM(DRAM、DDR)中执行的代码准备好硬件环境。**由于此时系统RAM可能还不可用,board_init_f()函数必须使用当前的GD来存储任何必须传递到后续步骤的数据。这些数据包括重定位目的地址、重新定义的栈和重新定义的GD位置。
下面的步骤只有在non-SPL下才可用。
(这里我觉得其实对于我们实验室使用了安全启动的系统是不具备SPL的。因为加载uboot的东西是其他的image)
3)创建一个中间的环境:
栈和GD都在系统RAM中由board_init_f()函数分配,但是BSS和已初始化的非常量数据仍然不可用。
4)调用relocate_code()。
该函数将U-Boot从当前的位置重定位到由board_init_f()函数计算得出的重定位目的地址处。
5)为调用board_init_r()函数创建最终的环境。
该环境包括BSS(已经初始化为0)、已初始化的非常量数据(已初始化为预期的值)和系统RAM中的栈。GD保存着被board_init_f()函数设定的值。 某些CPU还有一些关于存储的工作要做,所以会调用c_runtime_cpu_setup函数。
6)跳转到board_init_r()函数。
阅览完代码注释后,再来分析一下代码。
54 /* 55 * entry point of crt0 sequence 56 */ 57 58 ENTRY(_main) 59 60 /* 61 * Set up initial C runtime environment and call board_init_f(0). 62 */ 63 64 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) 65 ldr sp, =(CONFIG_SPL_STACK) 66 #else 67 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) 68 #endif 69 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 70 sub sp, sp, #GD_SIZE /* allocate one GD above SP */ 71 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 72 mov r9, sp /* GD is above SP */ 73 mov r0, #0 74 bl board_init_f 75 76 #if ! defined(CONFIG_SPL_BUILD) 77 78 /* 79 * Set up intermediate environment (new sp and gd) and call 80 * relocate_code(addr_moni). Trick here is that we'll return 81 * 'here' but relocated. 82 */ 83 84 ldr sp, [r9, #GD_START_ADDR_SP]/* sp = gd->start_addr_sp */ 85 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ 86 ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ 87 sub r9, r9, #GD_SIZE /* new GD is below bd */ 88 89 adr lr, here 90 ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ 91 add lr, lr, r0 92 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ 93 b relocate_code 94 here: 95 96 /* Set up final (full) environment */ 97 98 bl c_runtime_cpu_setup /* we still call old routine here */ 99 100 ldr r0, =__bss_start /* this is auto-relocated! */ 101 ldr r1, =__bss_end /* this is auto-relocated! */ 102 103 mov r2, #0x00000000 /* prepare zero to clear BSS */ 104 105 clbss_l:cmp r0, r1 /* while not at end of BSS */ 106 strlo r2, [r0] /* clear 32-bit BSS word */ 107 addlo r0, r0, #4 /* move to next */ 108 blo clbss_l 109 110 bl coloured_LED_init 111 bl red_led_on 112 113 /* call board_init_r(gd_t *id, ulong dest_addr) */ 114 mov r0, r9 /* gd_t */ 115 ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ 116 /* call board_init_r */ 117 ldr pc, =board_init_r /* this is auto-relocated! */ 118 119 /* we should not return here. */ 120 121 #endif 122 123 ENDPROC(_main)
因为我们采用了SPL框架,所以后面的步骤36都不会执行,也就是代码行76121不会执行。第64~68行代码设定栈指针,然后保证栈指针的8字节对齐,接着在栈的顶部为GD结构体分配存储空间,由于栈指针在分配空间时往底部移动了,所以要重新保证栈指针的8字节对齐。在设定栈指针的字节对齐后,有一句mov r9,sp,因为前面的操作中r9保存的是GD的地址,所以这里表明GD位置位于栈指针之上。
最后调用board_init_f(0)函数,注意这个函数是带有参数的。参数为赋值为0的r0。具体的调用规则可以查看ATPCS(ARM-THUMB procedure call standard)。
关于ATPCS,ARM官网上有一篇名为《The ARM-THUMB Procedure Call Standard》的文档对其做了全面的介绍,网址是http://infocenter.arm.com/help/topic/com.arm.doc.espc0002/ATPCS.pdf。这里,我们简单了解一下ATPCS,ATPCS统一了APCS(ARM Procedure Call Standard)和TPCS(Thumb Procedure Call Standard)两种标准。 ATPCS标准提出的目的是: 1)同时支持ARM态和Thumb态。 2)支持ARM态和Thumb态的联合工作。 3)支持更小的代码体积、嵌入式应用的合适功能、高性能。 4)支持可选的浮点运算架构和指令集。 5)二进制兼容APCS和TPCS。 ATPCS规定了一些子程序之间的基本调用规则。这些基本规则包括子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。 为适应一些特定的需要,对这些基本的调用规则进行一些修改,可得到几种不同的子程序调用规则,这些特定的调用规则包括:支持数据栈限制检查的ATPCS、支持只读段位置无关的ATPCS、支持可读写段位置无关的ATPCS、支持ARM程序和Thumb程序混合使用的ATPCS、处理浮点运算的ATPCS。
在这里,board_init_f函数在arch/arm/lib里的board.c和spl.c中都有定义,因为这里还是SPL的启动阶段,所以应该用的是spl.c中的board_init_f函数。这可以从当前目录中的Makefile来确认一下:
ifndef CONFIG_SPL_BUILD ifdef CONFIG_ARM64 obj-y += relocate_64.o else obj-y += relocate.o endif ifndef CONFIG_SYS_GENERIC_BOARD obj-y += board.o endif obj-$(CONFIG_CPU_V7M) += cmd_boot.o obj-$(CONFIG_OF_LIBFDT) += bootm-fdt.o obj-$(CONFIG_CMD_BOOTM) += bootm.o obj-$(CONFIG_SYS_L2_PL310) += cache-pl310.o obj-$(CONFIG_USE_ARCH_MEMSET) += memset.o obj-$(CONFIG_USE_ARCH_MEMCPY) += memcpy.o else obj-$(CONFIG_SPL_FRAMEWORK) += spl.o endif
在定义了CONFIG_SPL_BUILD的条件下,我们链接的是spl.o中的board_init_f函数。
在arch/arm/lib/spl.c中定义的board_init_f函数有如下的注释:
/* * In the context of SPL, board_init_f must ensure that any clocks/etc for * DDR are enabled, ensure that the stack pointer is valid, clear the BSS * and call board_init_f. We provide this version by default but mark it * as __weak to allow for platforms to do this in their own way if needed. */
注释中说,在SPL启动阶段,board_init_f函数必须保证用于DDR的时钟等配置完成,并且保证栈指针是有效的,而且清除了BSS。这里也将它标记为__weak属性,如果需要的话,就可以重写该函数。
4、board_init_f
在arch/arm/lib/spl.c中定义的board_init_f函数如下。
26 void __weak board_init_f(ulong dummy) 27 { 28 /* Clear the BSS. */ 29 memset(__bss_start, 0, __bss_end - __bss_start); 30 31 /* Set global data pointer. */ 32 gd = &gdata; 33 34 board_init_r(NULL, 0); 35 }
该函数对BSS进行清零操作,然后调用common/spl/spl.c中的board_init_r函数,该函数首先判断从哪种存储设备启动,这里给出RAM、MMC和NAND的代码。
5、board_init_r
155 boot_device = spl_boot_device(); 156 debug("boot device - %d\n", boot_device); 157 switch (boot_device) { 158 #ifdef CONFIG_SPL_RAM_DEVICE 159 case BOOT_DEVICE_RAM: 160 spl_ram_load_image(); 161 break; 162 #endif 163 #ifdef CONFIG_SPL_MMC_SUPPORT 164 case BOOT_DEVICE_MMC1: 165 case BOOT_DEVICE_MMC2: 166 case BOOT_DEVICE_MMC2_2: 167 spl_mmc_load_image(); 168 break; 169 #endif 170 #ifdef CONFIG_SPL_NAND_SUPPORT 171 case BOOT_DEVICE_NAND: 172 spl_nand_load_image(); 173 break; 174 #endif
第158~162行:如果定义了CONFIG_SPL_RAM_DEVICE,并且设备是RAM,则执行spl_ram_load_image(),也就是将image下载到RAM中。
第163~169行:如果定义了CONFIG_SPL_MMC_SUPPORT,并且设备是MMC/SD,则执行spl_mmc_load_image(),也就是将image从mmc/sd里面读取到RAM中。
第170~174行:如果定义了CONFIG_SPL_NAND_SUPPORT,并且设备是Nand Flash,则执行spl_nand_load_image(),也就是将image从Nand Flash中读取到RAM中。
这里我们用的是common/spl/spl_mmc.c中的spl_mmc_load_image()函数,该函数首先初始化MMC接口。
spl_mmc_load_image()
80 mmc_initialize(gd->bd); 81 /* We register only one device. So, the dev id is always 0 */ 82 mmc = find_mmc_device(0); 83 printf("@@ debug by baikal use the only one device\n"); 84 if (!mmc) { 85 #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT 86 puts("spl: mmc device not found!!\n"); 87 #endif 88 hang(); 89 } 90 printf("@@ debug by baikal here we mmc_init\n"); 91 err = mmc_init(mmc); 92 if (err) { 93 #ifdef CONFIG_SPL_LIBCOMMON_SUPPORT 94 printf("spl: mmc init failed: err - %d\n", err); 95 #endif 96 hang(); 97 }
然后根据boot_mode将U-Boot加载到内存RAM中。
99 boot_mode = spl_boot_mode(); 100 if (boot_mode == MMCSD_MODE_RAW) { 101 debug("boot mode - RAW\n"); 102 #ifdef CONFIG_SPL_OS_BOOT 103 if (spl_start_uboot() || mmc_load_image_raw_os(mmc)) 104 #endif 105 err = mmc_load_image_raw(mmc, 106 CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR); 107 #ifdef CONFIG_SPL_FAT_SUPPORT 108 } else if (boot_mode == MMCSD_MODE_FAT) {
其中,CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR的定义如下:
#define CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR 80 /* 40KiB */
这个表明U-Boot放置在MMC的40KB偏移处,因为是在8KB偏移处放置32KB大小的SPL,所以40KB=8KB+32KB。其实SPL用不了32KB大小,但是因为SPL是放置在内部的32KB的SRAM中运行,所以给SPL预留32KB的空间。
在mmc_load_image_raw函数中解析镜像头,根据镜像头的结构体数据将U-Boot加载到合适的DDR地址。
我们可以看一下u-boot.img的头:
bash-4.2# tools/mkimage -l u-boot.img Image Name: U-Boot 2014.04-rc2-10390-g96510e Created: Sun Jun 29 16:51:19 2014 Image Type: ARM U-Boot Firmware (uncompressed) Data Size: 240744 Bytes = 235.10 kB = 0.23 MB Load Address: 4a000000 Entry Point: 00000000
根据该头部信息,我们得知SPL会将U-Boot加载到0x4a000000地址处运行。当要启动的image位于RAM中后,我们就可以启动它。
然后接着
224 switch (spl_image.os) { 225 case IH_OS_U_BOOT: 226 debug("Jumping to U-Boot\n"); 227 break; 228 #ifdef CONFIG_SPL_OS_BOOT 229 case IH_OS_LINUX: 230 debug("Jumping to Linux\n"); 231 spl_board_prepare_for_linux(); 232 jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR); 233 #endif 234 default: 235 debug("Unsupported OS image.. Jumping nevertheless..\n"); 236 } 237 jump_to_image_no_args(&spl_image);
其中,第224行:判断image的类型。
第225~227行:如果是U-Boot,则直接到237行去运行U-Boot。
第228~233行:如果是Linux,则到232行去启动Linux。
至此,SPL结束它的生命,控制权交于U-Boot或Linux。
(这玩意说明这个board_init_r也是有多个存在,或者是复用,不然这个玩意怎么能跳过UBoot自己去加载Linux。或者是说其实SPL本身也可以去加载Linux?因为他的功能不就是加载Uboot,然后Uboot去加载Kernel,现在我不要你了,我自己去加载kernel,省事。说起这个,对哦?为什么不直接加载Kernel,而要通过UBoot,通过我的认识,应该是SPL就那么大点,干不了那么对事情。通过分级加载来省了内存。其实要解决这个疑惑,可以后面来看看这个下一个阶段做了什么工作,是SPL不能完成的,)
在这里,整个过程是SPL→U-Boot→Linux。我们分析了**SPL调用U-Boot的过程,**接下来再分析一下U-Boot调用Linux的过程。
参考资料:
《深入理解BootLoader》