U-BOOT小全(五):BootLoader源码(SPL-UBoot 2)

简介: U-BOOT小全(五):BootLoader源码(SPL-UBoot 2)

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

目录
相关文章
|
6月前
|
Linux 编译器 C语言
U-BOOT小全(四):BootLoader源码(SPL-UBoot 1)
U-BOOT小全(四):BootLoader源码(SPL-UBoot 1)
206 0
|
6月前
|
Linux 内存技术
U-BOOT小全(六):BootLoader源码(UBoot-Kernel 1)
U-BOOT小全(六):BootLoader源码(UBoot-Kernel 1)
81 0
|
6月前
|
存储 Linux 存储控制器
BootLoader这个玩意
BootLoader这个玩意
80 0
|
存储 Linux Shell
uboot启动流程简要版(基于armv7)
uboot启动流程简要版(基于armv7)
1037 0
uboot启动流程简要版(基于armv7)
|
Linux 内存技术
Buildroot系列开发(五)bootloader简述
Buildroot系列开发(五)bootloader简述
129 0
Buildroot系列开发(五)bootloader简述
|
内存技术
uboot和spl有什么区别
uboot和spl有什么区别
|
Linux 芯片 SoC
uboot 中的spl 简单认识
uboot 中的spl 简单认识
221 0
|
Linux 芯片 SoC
uboot 中的spl 简单认识
uboot 中的spl 简单认识
524 0
|
Linux
自制操作系统Antz day01——Boot Sector
最近在看操作系统底层方面的东西,最开始的为什么是07c00h这个问题就让我对操作系统有了很大的兴趣。所以准备在看书之余顺便写一个操作系统(Anz)。至于为什么这个系统会被叫做Antz,可以参考Antz Uhl Kone, 日语为アインズ·ウール·ゴウン , 与之对应的还有接下来准备写的自制脚本语言AntzScript。
2429 0