前面我们分析到了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》
1万+

被折叠的 条评论
为什么被折叠?



