uboot之uboot第一阶段(BL1)

一、u-boot.lds中的start.S

    1、在c语言中整个项目的入口就是main函数(这是c语言规定的),所以譬如说一个10000个.c文件的项目,

        第一个分析的文件就是main函数的文件。

   2、uboot中因为有汇编阶段的参与,因此不能直接找到main.c。整个程序的入口取决于链接脚本ENTRY

       (_start)因此_start符号所在的文件就是整个程序的起始文件,_start所在处的代码就是整个程序的起始代码。

二、start.S解析一

    1、先看看头文件包含

#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>

    (1)、#include <config.h>。config.h是include目录下的,这个文件不是源码中本身存在的,而是在配置过程

        中自动生成的。(上一篇讲到过)这个文件其实就包含了一个头文件“#include <configs/x210_sd.h>”

    (2)、#include <version.h>。include /version.h中包含了include/version_autogenerated.h,

         这个头文件就是配置过程中自动生成的。里面就一行内容:#define U_BOOT_VERSION "U-Boot 1.3.4"

         这个就是uboot中的版本号。

    (3)、#include <asm/proc/domain.h>。asm目录不是uboot中的原生目录,uboot中本来是没有这个目录的。

         asm目录是配置时创建的一个符号链接,实际指向的是就是asm-arm。(上一篇中讲到过)实际对应的文件:

        include/asm-arm/proc-armv/domain.h。

    (4)、从这里可以看出之前配置时创建的符号链接的作用,如果没有这些符号链接则编译时根本通不过,

            因为找不到头文件。(所以uboot不能在windows的共享文件夹下配置编译,因为windows中没有符号链接)

思考:为什么start.S不直接包含asm-arm/proc-armv/domain.h,而要用asm/proc/domain.h。这样的设计主要是

            为了可移植性。因为如果直接包含,则start.S文件和CPU架构(和硬件)有关了,可移植性就差了。譬如我

            要把uboot移植到mips架构下,则start.S源代码中所有的头文件包含全部要修改。我们用了符号链接之后,

            则start.S中源代码不用改,只需要在具体的硬件移植时配置不同,创建的符号链接指向的不同,则可以具有可

            移植性。

三、start.S解析二

    1、启动代码的16字节头部

        (1)裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中就是为了

            计算这个校验头)。我们以前做裸机程序时根本没考虑这16字节校验头,因为:1、如果我们是usb启动直接

           下载的方式启动的则不需要16字节校验头(irom application note);2、如果是SD卡启动mkv210image.c

            中会给原镜像前加16字节的校验头。

        (2)uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部

            确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。

.globl _start
_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 */
.global _end_vect
_end_vect:

	.balignl 16,0xdeadbeef

    2、异常向量表的构建

        (1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。

        (2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致

            的处理各种异常。

        (3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才

            是真正的有意义的代码开始的地方。

    3、关于代码.balignl 16,0xdeadbeef.

        (1)这句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存全部

            用0xdeadbeef来填充。

        (2) 0xdeadbeef这是一个16进制的数字但是其字面意思却组成了英文deadbeef(坏牛肉)。

        (3)内存对其可以让访问效率提高,有时候是硬件的特殊需求。

    4、TEXT_BASE等

        (1)第100行这个TEXT_BASE就是上个课程中分析Makefile时讲到的那个配置阶段的TEXT_BASE,其实就是我们

            链接时指定的uboot的链接地址。(值就是c3e00000)

        (2)源代码中和配置Makefile中很多变量是可以互相运送的。简单来说有些符号的值可以从Makefile中传递到源代码中。

    5、(1)CFG_PHY_UBOOT_BASE 33e00000 uboot在DDR中的物理地址

    6、设置CPU为SVC模式

        (1)msr cpsr_c, #0xd3 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。

        (2)其实ARM CPU在复位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时

        CPU一直处于SVC模式。

    7、设置L2,L1cache和MMU

cpu_init_crit:
	
	bl	disable_l2cache

	bl	set_l2cache_auxctrl_cycle

	bl	enable_l2cache

        (1)bl  disable_l2cache // 禁止L2 cache

        (2)bl  set_l2cache_auxctrl_cycle // l2 cache相关初始化

        (3)bl  enable_l2cache // 使能l2 cache

        (4)刷新L1 cache的icache和dcache。

        (5)关闭MMU

    8、识别并暂存启动介质选择

        (1)从哪里启动是由SoC的OM5:OM0这6个引脚的高低电平决定的。

        (2)实际上在210内部有一个寄存器(地址是0xE0000004),这个寄存器中的值是硬件根据OM引脚的设置而

        自动设置值的。这个值反映的就是OM引脚的接法(电平高低),也就是真正的启动介质是谁。

        (3)我们代码中可以通过读取这个寄存器的值然后判断其值来确定当前选中的启动介质是Nand还是SD还是其他的。

        (4)start.S的225-227行执行完后,在r2寄存器中存储了一个数字,这个数字等于某个特定值时就表示SD启动,等

        于另一个定值时表示从Nand启动····

        (5)260行中给r3中赋值#BOOT_MMCSD(0x03),这个在SD启动时实际会被执行,因此执行完这一段代码后

        r3中存储了0x03,以后备用。

    9、设置栈(SRAM中的栈)并调用lowlevel_init

        (1)284-286行第一次设置栈。这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,

        此时DDR还未被初始化还不能用。栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,

        不会被别人占用。

        (2)在调用函数前初始化栈,主要原因是在被调用的函数内还有再次调用函数,而BL只会将返回地址存储

        到LR中,但是我们只有一个LR,所以在第二层调用函数前要先将LR入栈,否则函数返回时第一层的返回地址就丢了。

    10、接下来是一些有关初始化

        (1)、检查复位状态:复杂CPU允许多种复位。譬如冷上电,热启动,睡眠(低功耗)启动。

          判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;热启动或睡眠状态下是不需要初始化DDR的。

         (2)、I/O恢复、关看门狗、关于SRAM SROM相关GPIO设置。

         (3)、供电锁存,lowlevel_init.S的第100-104行,开发板供电锁存。

         (4)、lowlevel_init.S的110-115行这几行代码是用来判定当前代码的位置在SRAM中还是在DDR中。

                    为什么要做这个判定?

                   原因1:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前

                   代码因该是在SRAM中运行BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。

                   原因2:我们判定当前代码的地址是有用的,可以指导后面的代码运行。     

         (5)、具体判断方法:

                    bic r1, pc, r0 这句代码的意义是:将pc的值中的某些bit位清0,剩下一些特殊的bit位赋值给r1

                  (r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff)

                    ldr r2, _TEXT_BASE 加载链接地址到r2,然后将r2的相应位清0剩下特定位。最后比较r1和r2.             

         总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行

                    是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。

        (6)、关于时钟的配置

                    在x210_sd.h中的300行到428行,都是和时钟相关的配置值。这些宏就决定210的时钟配置是多少。

                    也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,

                    如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。

        (7)、DDR初始化和串口初始化(串口初始化成功会给向串口发送一个'o'),tzpc_init,pop {pc}以返回

                (返回前通过串口打印'k')。

        总结:lowlevel_init.S中总共做了哪些事情:检查复位状态,关看门狗,开发板供电锁存、时钟初始化、DDR

                初始化、串口初始化、串口初始化并打印'o'、tzpc初始化化、打印'k'。

四、start.S解析三 

    1、再次设置栈(DDR中的栈)

        (1)、再次开发板供电锁存。第一,做2次是不会错的;第二,做2次则第2次无意义;做代码移植时有一

          个古怪谨慎保守策略就是尽量添加代码而不要删除代码。    

        (2)、之前在调用lowlevel_init程序前设置过1次栈(start.S 284-287行),那时候因为DDR尚未初始化,

           因此程序执行都是在SRAM中,所以在SRAM中分配了一部分内存作为栈。本次因为DDR已经被初始化了,

           因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次(start.S 297-299行);这里实际设置

           的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。

        (3)、为什么要重新设置栈?DDR已经初始化了,已经有大片的内存可以用了,没必要在把栈放在容量较小

           的SRAM中,因为较小的栈它溢出的几率就大,所以将栈放在DDR。这是第二次设置栈(start.S 297-299行)

           这里实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。

      2、再次判断当前地址以确定是否进行重定位

          (1)、再次用相同的代码判断运行地址是在SRAM中还是DDR中,不过本次判断的目的不同(上次判断是为

            了决定是否要执行初始化时钟和DDR的代码)这次判断是为了决定是否进行uboot的relocate。

          (2)、 冷启动时当前情况是uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中正在运行,

            uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的

             第一阶段已经即将结束了(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中

            链接地址处(33e00000),这个加载过程就叫重定位。       

     3、uboot重定位详解

            (1)、D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中

             SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000

            从SD2通道启动时,这个值为EB200000。

            (2)、我们在start.S的260行确定了从MMCSD启动,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存

            器中存储着。然后又在322行读出来,再和#BOOT_MMCSD去比较,确定是从MMCSD启动。最终跳转到mmc

            sd_boot函数中去执行重定位动作。

            (3)、真正的重定位是通过movi_b12_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中。是一个c语言函数

            (4)、copy_b12(2,MOVI_BL2_POS,MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE,0);

 分析参数:2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot

时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;CFG_PHY_UBOOT_BASE是重定位时将uboot

的第二部分复制到DDR中的起始地址(33E00000).       

    4、建立映射表,使能MMU单元,第三次设置栈,将栈设置在DDR中比较安全的地方,uboot起始地址上方2MB处。

          清bss

    5、ldr pc, _start_armboot 

           (1)start_armboot是uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。

           这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR

           中的第二阶段开始地址处。

            (2)远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从

            SRAM中的第一阶段跳转到DDR中的第二阶段。

            (3)这里这个远跳转就是uboot第一阶段和第二阶段的分界线。

总结:uboot第一阶段到底做了什么?

    构建异常向量表

    设置CPU为SVC模式

    关看门狗

    开发板供电锁存

    时钟初始化

    DDR初始化

    串口初始化并打印"OK"

    重定位

    建立映射表并开启MMU

    跳转到第二阶段

欢迎各位指出不足之处

        


       

猜你喜欢

转载自blog.csdn.net/qq_41003024/article/details/80376404