精华内容
参与话题
问答
  • uboot移植

    2019-05-30 21:22:40
    u-boot工程与编译系统 1.BootLoader介绍 对于计算机系统来说,从开机上电到操作...通过这段小程序,可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备...

    u-boot工程与编译系统

    1.BootLoader介绍

    • 对于计算机系统来说,从开机上电到操作系统启动需要一个引导过程。嵌入式Linux系统同样离不开引导程序,这个引导程序就叫作启动加载程序(Bootloader)。
    • Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序,可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。对于嵌入式系统,Bootloader是基于特定硬件平台实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于两块不同的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般都需要修改Bootloader的源程序。

    从上述描述,我们可以知道:

    • 在设备上电运行系统之前,需要先运行BootLoader。BootLoader为系统的运行准备好软硬件环境(软硬件初始化)
    • CPU架构、板载硬件资源任意一个不同,BootLoader程序代码都不同。
    • 虽然是这样,BootLoader仍然可以通过类似穷举一样,将各个CPU架构源代码都加入进来,使得整个源代码能支持更多的硬件,uboot就是其中做得比较好的,u-boot就同时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。
    • uboot源码实质就是一个工程,使用Makefile管理,最早是德国的DENX软件公司的CEO:Wolfgang Denk创建,慢慢完善之后,现在u-boot已经支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强,并且开发最积极的开源Bootloader。目前仍然由DENX的Wolfgang Denk维护。

    2.获取uboot源码方式

    • u-boot的源码包可以从其官方网站下载:DENX的u-boot主页:http://www.denx.de/wiki/u-boot。
    • git源码库:git://www.denx.de/git/u-boot.git 。

    3.u-boot源码结构

    从网站上下载得到u-boot源码包,如本书中使用的u-boot-2009.11.tar.bz2,将其解压就可以得到全部u-boot源程序。在顶层目录下有30多个子目录,分别存放和管理不同的源程序。这些目录中存放的文件有其规则,可以分为3类。

    • 第1类目录与处理器体系结构或开发板硬件直接相关。
    • 第2类目录是一些通用的函数或驱动程序。
    • 第3类目录是u-boot的应用程序、工具或文档。

    4.u-boot源码配置

    • u-boot的源码是通过GNU Makefile Makefile完成对开发板的整体配置,然后递归调用各级子目录下的 Makefile,最后把所有编译过的程序链接成u-boot映像。下面将以S3C2410处理器公板smdk2410为例,介绍u-boot的配置编译方法,并简单分析其原理。

    (1)基本的配置编译方法

    • 配置编译u-boot的方法十分简单,只需在解压并进入源码包后,在 u-boot顶层目录中执行如下两个命令:
    • make <board_name> config
    • make
    • 第一个命令用来配置u-boot,其中<board_name>要用具体的开发板名称代替。例如,用smdk2410替代后就是“make smdk2410_config”。执行第二个命令后即开始编译过程。

    (2)顶层目录下的Makefile

    • 每一种开发板在顶层 Makefile中都需要有自己的配置规制。例如,用smdk2410开发板的规制定义如下:
    smdk2410_config	:	unconfig
    	@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 samsung s3c24x0
    • 执行配置u-boot的命令:make smdk2410_config,将通过u-boot顶层目录下的mkconfig脚本生成配置文件 include/config.mk,内容如下。这些内容正是根据 Makefile对开发板的配置生成的。
    ARCH   = arm  
    CPU    = arm920t 
    BOARD  = smdk2410 
    VENDOR = samsung 
    SOC    = s3c24x0 
    • 上面的include/config.mk文件定义了ARCH、CPU、 BOARD、 VENDOR、SOC等变量这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2410平台相关目录如下:
    board/samsung/smdk2410/  
    cpu/arm920t/  
    cpu/arm920t/s3c24x0/  
    lib_arm/  
    include/asm-arm/  
    include/configs/smdk2410.h 
    
    • 再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了对前面所述变量的定义。
    # load ARCH, BOARD, and CPU configuration  
    include $(obj)include/config.mk  
    export  ARCH CPU BOARD VENDOR SOC 
    • Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在该文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在各自的lib_xxx(xxx表示体系架构,如lib_arm)目录下的config.mk文件中。 

    (3)开发板配置头文件

    • 除了编译顶层Makefile以外,还要在移植时为开发板定义配置选项或参数。这个头文件是include/configs/<board_name>.h。<board_name>用相应的BOARD定义代替。
    • 这些头文件中定义的选项或参数宏以CONFIG_为前缀,用来选择处理器、设备接口、命令、属性等。例如:
    #define   CONFIG_ARM920T         1  
    #define   CONFIG_DRIVER_CS8900   1 

    (4)编译结果

    • 编译完成后,可以得到u-boot各种格式的映像文件和符号表:

    • u-boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中即可。

    5.U-Boot命令详解

    • 帮助命令:help
    • 查看环境变量:printenv
    • 修改环境变量setenv 添加、修改、删除环境变量
    • 保存环境变量:saveenv 保存环境变量将当前定义的所有变量及其值存入flash中
    • 执行程序:bootm {addr} {arg},执行固定格式的2进制程序     范例:#bootm 0xc0008000

           查看、修改内存内容

    • md采用十六进制和ASCII码两种形式来显示存储单元的内容。这条命令还可以采用长度标识符 .l, .w和.b :
    • md [.b, .w, .l] address

           范例:

    • md.w 100000
    • 00100000: 2705 1956 5050 4342 6f6f 7420 312e 312e
    • 00100010: 3520 284d 6172 2032 3120 3230 3032 202d

           mm 修改内存,地址自动递增

    • mm [.b, .w, .l] address
    • mm 提供了一种互动修改存储器内容的方法。它会显示地址和当前值,然后提示用户输入。如果你输入了一个合法的十六进制数,这个新的值将会被写入该地址。然后提示下一个地址。如果你没有输入任何值,只是按了一下回车,那么该地址的内容保持不变。如果想结束输入,则输入空格,然后回车
    • => mm 100000
    • 00100000: 27051956 ? 0
    • 00100004: 50504342 ? AABBCCDD

           擦除nand flash

    • nand erase 起始地址start 长度len
    • 擦除start处开始的,长度为len的区域

           范例:

    • #nand erase 0x400000 0x500000

           写/读nand flash

    • nand write 内存起始地址 flash起始地址 长度len
    • 将内存起始地址处,长度为len的数据,写入flash起始地址处。
    • nand read 内存起始地址 flash起始地址 长度len
    • 将flash起始地址处,长度为len的数据,读到内存起始地址处。

           范例:

    • #nand write c0008000 400000 500000
    • #nand read c0008000 400000 500000

           设置自启动

    • 设置从nand flash自动启动
    • #setenv bootcmd nand read c0008000 400000 500000 \; bootm c0008000
    • 设置自动下载内核到内存后启动
    • #setenv bootcmd tftp c0008000 uImage \; bootm c0008000

    u-boot启动流程分析

    •  这里所说的 u-boot启动流程,指的是从上电开机执行u-bo,到u-boot加载操作系统的过程。这一过程可以分为两个阶段,各个阶段的功能如下。

    第一阶段的功能

    • 硬件设备初始化。
    • 加载u-boot第二阶段代码到RAM空间
    • 设置好栈。
    • 跳转到第二阶段代码入口。

    第二阶段的功能:

    • 初始化本阶段使用的硬件设备。
    • 检测系统内存映射。
    • 将内核从 Flash读取到RAM中。
    • 为内核设置启动参数。
    • 调用内核。

    1.u-boot启动第一阶段流程

    • 根据连接器脚本cpu/arm920t/u-boot.lds中指定的连接方式,我们知道, u-boot代码段第一个链接的是cpu/arm920t/start.o,因此u-boot的入口代码在对应源文件cpu/arm920t/start.S的执行。

    (1)设置异常向量表

    • 异常:因为内部或者外部的一些事件,导致处理器停下正在处理的工作,转而去处理正在发生的事件。
    • 异常向量:当一种异常发生的时候,ARM处理器会跳转到对应异常的固定地址去执行异常处理程序,而这个固定的地址,就称之为异常向量。ARM处理器支持7种类型的异常:

    • cpu/arm920t/start.S开头有如下代码,用于设置上述异常向量表。
    /* 设置异常向量 */
    .globl _start
    _start:	b	start_code
    	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
    
    	.balignl 16,0xdeadbeef
    • 在 start中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号,在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU跳转到对应的异常处理程序执行。其中复位异常向量的指令“ b  start_code”决定u-boot启动后将自动跳转到标号“start_code”处执行。
    start_code:
    	/*
    	 * set the cpu to SVC32 mode
    	 */
    	mrs	r0, cpsr
    	bic	r0, r0, #0x1f
    	orr	r0, r0, #0xd3
    	msr	cpsr, r0
    
    	bl	coloured_LED_init
    	bl	red_LED_on

    (2)设置svc工作模式(0b10011)

    • 在 start中,u-boot通过下面的代码将CPU的工作模式位设置为管理模式,并将普通中断IRQ和快速中断FIQ的禁止位置1,从而屏蔽IRQ和FIQ中断。
    1. 清0 bic指令
    2. 置1 orr指令
    3. msr、mrs指令(不能直接对CPSR、SPSR寄存器的值进行修改,必须先导到通用寄存器当中)
    mrs	r0, cpsr
    bic	r0, r0, #0x1f
    orr	r0, r0, #0xd3
    msr	cpsr, r0

    (3)设置控制寄存器地址

    • 对于smdk2410开发板,下面的代码完成 WATCHDOG、INTMSK、INTSUBMSK、CLKDIVN4个寄存器地址的设置。
    # if defined(CONFIG_S3C2400)
    #  define pWTCON	0x15300000
    #  define INTMSK	0x14400008	/* Interupt-Controller base addresses */
    #  define CLKDIVN	0x14800014	/* clock divisor register */
    #else
    #  define pWTCON	0x53000000
    #  define INTMSK	0x4A000008	/* Interupt-Controller base addresses */
    #  define INTSUBMSK	0x4A00001C
    #  define CLKDIVN	0x4C000014	/* clock divisor register */
    # endif

    (4)关闭看门狗

    1)看门狗的作用

    • 在嵌入式领域,有些系统需要长期运行在无人看守的环境。在运行过程中,难免不出现系统死机的情况,这时就需要系统自身带有一种自动重启的功能。watchdog一般是一个硬件模块,其作用就是在系统死机时,帮助系统实现自动重启

    2)看门狗的工作方式

    • Watchdog在硬件上实现了计时功能,启动计时后,用户(软件)必须在计时结束前重新开始计时,俗称“喂狗”,如果到超时的时候还没有重新开始计时,那么它就认为系统是死机了,就自动重启系统。

    3)看门狗这么有用,那uboot设置时为何要关闭看门狗?

    • Bootloader(uboot)在设置时软件较为简单,程序也不复杂,运行时也不会出现死机,如果不关闭看门狗,那么在程序运行时会不断去喂狗,麻烦,所以就关闭看门狗。
    ldr	r0, =pWTCON   // mov指令只能对通用寄存器进行操作,所以首先赋值给r1,再赋值给r0。
    mov	r1, #0x0
    str	r1, [r0]
    

    (5)屏蔽中断

    • 下面的代码向主中断屏蔽寄存器 INTMSK写入0xff,即将 INTMSK寄存器全部位置1,从而屏蔽对应的中断。INTMSK的每一位对应 SRCPND(源中断未决寄存器)中的一位,表明 SRCPND相应位代表的中断请求是否会被CPU处理。
    mov	r1, #0xffffffff
    ldr	r0, =INTMSK
    str	r1, [r0]
    • 同理, INTSUBMSK每一位对应 SUBSRCPND中的一位,表明 SUBSRCPND相应位代表的中断请求是否会被CPU所处理。下面的代码屏蔽了所有 SUBSRCPND对应的中断。
    # if defined(CONFIG_S3C2410)
    	ldr	r1, =0x3ff
    	ldr	r0, =INTSUBMSK
    	str	r1, [r0]
    # endif

    (6)设置MPLLCON、UPLLCON、CLKDIVN

    • CPU上电几毫秒后,晶振输出稳定,主频FCLK=fin(晶振频率),CPU开始执行指令但实际上,FCLK可以高于Fin,为了提高系统时钟,需要用软件来启用PLL。这就需要设置CLKDIVN、MPLLCON和 UPLLCON这3个寄存器。CLKDIVN寄存器用于设置FCLK、HCLK、PCLK三者间的比例:

    • 下面的代码将CLKDIVN寄存器设置为3,也就是将HDIVN和 PDIVN分别设置为二进制的01和1,因此HCLK=FCLK/2PCLK=HCLk/2。由此得出,FCLK、HCLK、PCLK三者的比例关系为4:2:1。
    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr	r0, =CLKDIVN
    mov	r1, #3
    str	r1, [r0]
    •  MPLLCON寄存器用于设置FCLK与Fin的倍数。 MPLLCON的位[19:12]称为MDV,位[9:4]称为PDIV,位[1:0]称为SDIV。S3c2410,fclk与fin满足如下面公式:
    • Mpll=(m*Fin)/(p*2^s)
    • 其中,m=(MDIV+8),p=(PDIV+2),s=SDIV
    • MPLLCON、UPLLCON的值可以参考S3C2410数据手册中“PLL VALUESELECTION TABLE”表设置。

    当将smdk2410系统主频设置为202.80Hz,将USB时钟频率设置为48MHz时,系统可以稳定运行,因此设置 MPLLCON、UPLLCON为:

     MPLLCON=(0xAl<<12)|(0x03<<4)(0x01)
     UPLLCON=(0x78<<12)(0x02<<4)(0x03)

    (7)关闭MMU和Cache(CP15)

    • 接下来
      ifndef CONFIG_SKIP_LOWLEVEL_INIT
      	bl	cpu_init_crit
      #endif
    •  cpu_init_crit这段代码在u-boot正常启动时才需要执行,若将u-boot从内存中启动,则应该注释掉这段代码。
    • 分析 cpu_init_crit到底做了什么。
    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    cpu_init_crit:
    	/*
    	 * flush v4 I/D caches
    	 */
    	mov	r0, #0
    	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */
    	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */
    
    	/*
    	 * disable MMU stuff and caches
    	 */
    	mrc	p15, 0, r0, c1, c0, 0
    	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
    	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
    	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
    	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
    	mcr	p15, 0, r0, c1, c0, 0
    
    	/*
    	 * before relocating, we have to setup RAM timing
    	 * because memory timing is board-dependend, you will
    	 * find a lowlevel_init.S in your board directory.
    	 */
    	mov	ip, lr
    
    	bl	lowlevel_init
    
    	mov	lr, ip
    	mov	pc, lr
    #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
    • 代码中的c0、c1、c7、c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache是控制寄存器,c8是TLB控制寄存器。第7~8行代码将0写入c7、c8,使 cache和TLB内容无效。
    • 第13~18行代码关闭了MMU。这是通过修改协处理器CP15的c1寄存器来实现的,CP15的c1寄存器的格式如下表,通过将cl的M位置0,来关闭MMU。

    cl寄存器中各个位的含义如下:

    1. V表示异常向量表所在的位置,0表示异常向量在000000;1表示异常向量在 0xFFFF0000。
    2. I为0表示关闭 ICaches,为1表示开启ICaches。
    3. R、S用来与页表中的描述符一起确定内存的访问权限。
    4. B为0表示CPU为小字节序;为1表示CPU为大字节序。
    5. C为0表示关闭DCaches,为1表示开启DCaches。
    6. A为0表示数据访问时不进行地址对齐检查;为1表示数据访问时进行地址对齐检查。
    7. M为0表示关闭MMU;为1表示开启MMU。

    (8)初始化存储控制器

    • lowlevel_init完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此 lowlevel_init代码一般放在board下面相应的目录中。对于smdk2410, lowlevel_init在 board/samsung/smdk2410/owlevel_init.s中定义如下
    _TEXT_BASE:
    	.word	TEXT_BASE
    
    .globl lowlevel_init
    lowlevel_init:
    	/* memory control configuration */
    	/* make r0 relative the current location so that it */
    	/* reads SMRDATA out of FLASH rather than memory ! */
    	ldr     r0, =SMRDATA
    	ldr	r1, _TEXT_BASE
    	sub	r0, r0, r1
    	ldr	r1, =BWSCON	/* Bus Width Status Controller */
    	add     r2, r0, #13*4
    0:
    	ldr     r3, [r0], #4
    	str     r3, [r1], #4
    	cmp     r2, r0
    	bne     0b
    
    	/* everything is fine now */
    	mov	pc, lr
    
    	.ltorg
    /* the literal pools origin */
    • lowlevel_init设置了13个寄存器来对存储器控制器时序进行初始化。当u-boot从Nor flash启动时,由于u-boot连接时确定的地址是内存中的地址,而此时u-boot还在Nor flash中, 因此最终需要将之从Nor flash中复制到RAM中运行。
    • 由于Nor flash开始的地址是0,而u-boot加载到内存中的地址是TEXT_BASE,所以 SMRDATA标号在Flash的地址就是SMRDATA-TEXT_BASE。
    • 综上所述,lowlevel_init SMRDATA开始的13个值复制给开始地址[BWSCON的13个寄存器,从而完成存储控制器的设置。

    (9)复制u-boot第二阶段代码到RAM

    • 存储控制器初始化完成之后,可以将u-boot复制到内存中运行,这部分代码如下:
    #ifndef CONFIG_SKIP_RELOCATE_UBOOT
    relocate:				/* relocate U-Boot to RAM	    */
    	adr	r0, _start		/* r0 <- current position of code   */
    	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
    	cmp	r0, r1			/* don't reloc during debug         */
    	beq	stack_setup
    
    	ldr	r2, _armboot_start
    	ldr	r3, _bss_start
    	sub	r2, r3, r2		/* r2 <- size of armboot            */
    	add	r2, r0, r2		/* r2 <- source end address         */
    
    copy_loop:
    	ldmia	r0!, {r3-r10}		/* copy from source address [r0]    */
    	stmia	r1!, {r3-r10}		/* copy to   target address [r1]    */
    	cmp	r0, r2			/* until source end addreee [r2]    */
    	ble	copy_loop
    #endif	/* CONFIG_SKIP_RELOCATE_UBOOT */

    (10)设置栈

    • 栈是执行C程序的必要条件,因此在进入C语言实现的初始化代码前,需要通过汇编进行栈的初始化。相关代码如下:
    stack_setup:
    	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
    	sub	r0, r0, #CONFIG_SYS_MALLOC_LEN	/* malloc area              */
    	sub	r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo                 */
    #ifdef CONFIG_USE_IRQ
    	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    #endif
    	sub	sp, r0, #12		/* leave 3 words for abort-stack    */
    • ARM处理器栈指针sp默认向下生长,因此只需将sp指针指向一段低地址没有被使用的内存,即可完成栈的设置。根据上面的代码可以得知,u-boot的内存使用情况如图所示。

    (11)清除BSS段

    • 在调用C代码之前,还需要由u-boot将BSS段中存放的未初始化全局变量、静态变量清零。代码如下:
    clear_bss:
    	ldr	r0, _bss_start		/* find start of bss segment        */
    	ldr	r1, _bss_end		/* stop here                        */
    	mov	r2, #0x00000000		/* clear                            */
    
    clbss_l:str	r2, [r0]		/* clear loop...                    */
    	add	r0, r0, #4
    	cmp	r0, r1
    	ble	clbss_l
    • 第2~4行:执行完成后,r0、r1将分别存放BSS段的起始地址和结束地址,r2被清零。
    • 第6~9行:执行循环,将r0、r1之间的区域清零。

    (12)跳转到第二阶段代码入口

    • 通过上述初始化过程,u-boot已经具备了在内存中执行C语言的能力,现在只需执行一条ldr指令就可以跳转至内存中的第二阶段初始化入口 start_armboot,代码如下:ldr    pc, _start_armboot

    2.u-boot启动第二阶段代码分析

    • start_armboot在lib_arm/board.c中定义,是u-boot第二阶段代码的入口。u-boot启动第二阶段大体流程如图所示。

     

    • 在分析 start_armboot函数前先来介绍一些重要的数据结构。

    (1)gd_t结构体

    • u-boot使用一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/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
    #if 0
    	unsigned long	cpu_clk;	/* CPU clock in Hz!		*/
    	unsigned long	bus_clk;
    	phys_size_t	ram_size;	/* RAM size */
    	unsigned long	reset_status;	/* reset status register at boot */
    #endif
    	void		**jt;		/* jump table */
    } gd_t;
    • u-boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址。
    #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
    • DECLARE_GLOBAL_DATA_PTR定义一个gdt全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其他的变量。对于任何想要访问全局数据区的代码,只要在其开头加入“ DECLARE_GLOBAL_DATA_PTR”一行代码,就可以使用gd指针访问全局数据区了。
    • 根据u-boot内存分配图可以计算gd的值,代码如下:
    • gd = TEXT_BASE- CONFIG_SYS_MALLOC_LEN- sizeof(gd_t),这与 start_armboot()函数中对gd指针的初始化一致,相关代码如下:
    /* Pointer is writable since we allocated a register for it */
    	gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
    • 其中,全局标号_armboot_start 在start.S中定义如下,可见 _armboot_start在此处取_start标号的值,也就是u-boot镜像的起始地址TEXT _BASE。
    _TEXT_BASE:
    	.word	TEXT_BASE
    
    .globl _armboot_start
    _armboot_start:
    	.word _start

    (2)bd_t结构体

    • bd_t结构体用于存放板级相关的全局数据,是gd_t中结构体指针成员bd的结构体类型,在 include/asm-arm/u-boot.h中的定义如下:
    typedef struct bd_info {
        int			bi_baudrate;	/* serial console baudrate */
        unsigned long	bi_ip_addr;	/* IP Address */
        struct environment_s	       *bi_env;
        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结构体中的信息来设置标记列表。

    (3) init_sequence数组

    • u-boot使用一个数数组init_sequence来存储大多数开发板都要执行的初始化函数的函数指针,init_sequence数组如下:
    init_fnc_t *init_sequence[] = {
    #if defined(CONFIG_ARCH_CPU_INIT)
    	arch_cpu_init,		/* basic arch cpu dependent setup */
    #endif
    	board_init,		/* basic board dependent setup */
    #if defined(CONFIG_USE_IRQ)
    	interrupt_init,		/* set up exceptions */
    #endif
    	timer_init,		/* initialize timer */
    	env_init,		/* initialize environment */
    	init_baudrate,		/* initialze baudrate settings */
    	serial_init,		/* serial communications setup */
    	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
    	display_dram_config,
    	NULL,
    };
    • 其中的board_init 函数在board/samsung/smdk2410/smdk2410.c中定义,该函数配置了MPLLCOM、UPLLCON和部分GPIO寄存器的值,还设置了u-boot机器码和内核启动参数地址。
    ​
    /* arch number of SMDK2410-Board */
    	gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
    
    	/* adress of boot parameters */
    	gd->bd->bi_boot_params = 0x30000100;
    
    ​
    • 其中的dram_init函数在board/samsung/smdk2410/smdk2410.c中定义如下:
    int dram_init (void)
    {
    	gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
    	gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
    
    	return 0;
    }
    • smdk2410使用两片32mB的 SDRAM组成了64MB的内存,这两片内存连接在存储控制器的BANK6上,地址空间0x300000000~0x34000000.
    • 在 include/configs/smdk2410.h中, PHYS_SDRAM_1和 PHYS_SDRAM_1_SIZE分别被定义为0x30000000和0x04000000(64M)。

    (4)start_armboot()顺序分析

    void start_armboot (void)
    {
    	init_fnc_t **init_fnc_ptr;
    	char *s;
    ...
    
    	/* 计算全局数据结构地址gd */
    	gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
    	/* compiler optimization barrier needed for GCC >= 3.4 */
    	__asm__ __volatile__("": : :"memory");
    
    	/* 设置板级相关数据 */
            memset ((void*)gd, 0, sizeof (gd_t));
    	gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
    	memset (gd->bd, 0, sizeof (bd_t));
    
    	gd->flags |= GD_FLG_RELOC;
    
    	monitor_flash_len = _bss_start - _armboot_start;
    
    	
            / *逐个调用init_sequence数组中的初始化函数 */
            for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    		if ((*init_fnc_ptr)() != 0) {
    			hang ();
    		}
    	}
    
    	/* 初始化(TEXT_BASE - CONFIG_SYS_MALLOC_LEN)为堆的起始地址 */
    	mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
    			CONFIG_SYS_MALLOC_LEN);
    
    #ifndef CONFIG_SYS_NO_FLASH
    	/* configure available FLASH banks */
    	display_flash_config (flash_init ());
    #endif /* CONFIG_SYS_NO_FLASH */
    
    #ifdef CONFIG_VFD
    #	ifndef PAGE_SIZE
    #	  define PAGE_SIZE 4096
    #	endif
    	/*
    	 * reserve memory for VFD display (always full pages)
    	 */
    	/* bss_end is defined in the board-specific linker script */
    	addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
    	vfd_setmem (addr);
    	gd->fb_base = addr;
    #endif /* CONFIG_VFD */
    
    #ifdef CONFIG_LCD
    	/* board init may have inited fb_base */
    	if (!gd->fb_base) {
    #		ifndef PAGE_SIZE
    #		  define PAGE_SIZE 4096
    #		endif
    		/*
    		 * reserve memory for LCD display (always full pages)
    		 */
    		/* bss_end is defined in the board-specific linker script */
    		addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
    		lcd_setmem (addr);
    		gd->fb_base = addr;
    	}
    #endif /* CONFIG_LCD */
    
    #if defined(CONFIG_CMD_NAND)
    	puts ("NAND:  ");
    	nand_init();		/* go init the NAND */
    #endif
    
    #if defined(CONFIG_CMD_ONENAND)
    	onenand_init();
    #endif
    
    #ifdef CONFIG_HAS_DATAFLASH
    	AT91F_DataflashInit();
    	dataflash_print_info();
    #endif
    
    	/* 配置环境变量,重新定位 */
    	env_relocate ();
    
    #ifdef CONFIG_VFD
    	/* must do this after the framebuffer is allocated */
    	drv_vfd_init();
    #endif /* CONFIG_VFD */
    
    #ifdef CONFIG_SERIAL_MULTI
    	serial_initialize();
    #endif
    
    	/* 从环境变量中获取IP地址 */
    	gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
    
    	stdio_init ();	/* get the devices list going. */
    
    	jumptable_init ();
    
    #if defined(CONFIG_API)
    	/* Initialize API */
    	api_init ();
    #endif
    
    	console_init_r ();	/* fully init console as a device */
    
    #if defined(CONFIG_ARCH_MISC_INIT)
    	/* miscellaneous arch dependent initialisations */
    	arch_misc_init ();
    #endif
    #if defined(CONFIG_MISC_INIT_R)
    	/* miscellaneous platform dependent initialisations */
    	misc_init_r ();
    #endif
    
    	/* 使能中断 */
    	enable_interrupts ();
    
    	/* Perform network card initialisation if necessary */
    #ifdef CONFIG_DRIVER_TI_EMAC
    	/* XXX: this needs to be moved to board init */
    extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
    	if (getenv ("ethaddr")) {
    		uchar enetaddr[6];
    		eth_getenv_enetaddr("ethaddr", enetaddr);
    		davinci_eth_set_mac_addr(enetaddr);
    	}
    #endif
    
    #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
    	/* XXX: this needs to be moved to board init */
    	if (getenv ("ethaddr")) {
    		uchar enetaddr[6];
    		eth_getenv_enetaddr("ethaddr", enetaddr);
    		smc_set_mac_addr(enetaddr);
    	}
    #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
    
    	/* Initialize from environment */
    	if ((s = getenv ("loadaddr")) != NULL) {
    		load_addr = simple_strtoul (s, NULL, 16);
    	}
    #if defined(CONFIG_CMD_NET)
    	if ((s = getenv ("bootfile")) != NULL) {
    		copy_filename (BootFile, s, sizeof (BootFile));
    	}
    #endif
    
    #ifdef BOARD_LATE_INIT
    	board_late_init ();
    #endif
    
    #ifdef CONFIG_GENERIC_MMC
    	puts ("MMC:   ");
    	mmc_initialize (gd->bd);
    #endif
    
    #ifdef CONFIG_BITBANGMII
    	bb_miiphy_init();
    #endif
    
    /* 网卡初始化 */
    #if defined(CONFIG_CMD_NET)
    #if defined(CONFIG_NET_MULTI)
    	puts ("Net:   ");
    #endif
    	eth_initialize(gd->bd);
    #if defined(CONFIG_RESET_PHY_R)
    	debug ("Reset Ethernet PHY\n");
    	reset_phy();
    #endif
    #endif
    	/* 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 */
    }
    
    • 从上述代码的注释可知,start_armboot()在进行一系列的初始化(包括对全局数据指针gd、堆、各种设备和环境变量的初始化)后,最终调用main_loop()进入u-boot主循环中。

    (5)main_loop函数分析

    • main loop函数在common/main.c中定义,做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。

    ①设置启动次数有关参数

    • 在进入 main_loop函数后,首先根据配置加载已经保留的启动次数,并且根据配置判断是否超过启动次数。代码如下:
    void main_loop (void)
    {
    ...
    #ifdef CONFIG_BOOTCOUNT_LIMIT
    	bootcount = bootcount_load();
    	bootcount++;
    	bootcount_store (bootcount);
    	sprintf (bcs_set, "%lu", bootcount);
    	setenv ("bootcount", bcs_set);
    	bcs = getenv ("bootlimit");
    	bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    • 第5~7行:加载保存的启动次数至变量 bootcount,并将启动次数加1后重新保存。
    • 第8~9行:打印启动次数,然后设置启动次数到环境变量“bootcount”。
    • 第10~11行:从环境变量“bootlimit”中读出启动变量限制变量bootlimit。
    • 第5~12行:实现启动次数限制功能。启动次数限制可以设置为一个启动次数,然后保存在 Flash存储器的特定位置,当到达启动次数后,u-boot无法启动。该功能适合一些商业产品,通过配置不同的 License限制用户重新启动系统。

    ②启用 Modem功能

    • 如果系统中有 Modem,打开该功能可以接受其他用户通过电话网络的拨号请求。 Modem功能通常供一些远程控制的系统使用,代码如下:
    ifdef CONFIG_MODEM_SUPPORT
    	debug ("DEBUG: main_loop:   do_mdm_init=%d\n", do_mdm_init);
    	if (do_mdm_init) {
    		char *str = strdup(getenv("mdm_cmd"));
    		setenv ("preboot", str);  /* set or delete definition */
    		if (str != NULL)
    			free (str);
    		mdm_init(); /* wait for modem connection */
    	}
    #endif  /* CONFIG_MODEM_SUPPORT */

    ③设置版本号、初始化命令自动完成等功能

    • 接下来设置u-boot的版本号、初始化命令自动完成功能等。代码如下:
    #ifdef CONFIG_VERSION_VARIABLE
    	{
    		extern char version_string[];
    
    		setenv ("ver", version_string);  /* set version variable */
    	}
    #endif /* CONFIG_VERSION_VARIABLE */
    
    #ifdef CONFIG_SYS_HUSH_PARSER
    	u_boot_hush_start ();
    #endif
    
    #if defined(CONFIG_HUSH_INIT_VAR)
    	hush_init_var ();
    #endif
    
    #ifdef CONFIG_AUTO_COMPLETE
    	install_auto_complete();
    #endif
    
    #ifdef CONFIG_PREBOOT
    	if ((p = getenv ("preboot")) != NULL) {
    # ifdef CONFIG_AUTOBOOT_KEYED
    		int prev = disable_ctrlc(1);	/* disable Control C checking */
    # endif
    
    # ifndef CONFIG_SYS_HUSH_PARSER
    		run_command (p, 0);
    # else
    		parse_string_outer(p, FLAG_PARSE_SEMICOLON |
    				    FLAG_EXIT_FROM_LOOP);
    # endif
    
    # ifdef CONFIG_AUTOBOOT_KEYED
    		disable_ctrlc(prev);	/* restore Control C checking */
    # endif
    	}
    #endif /* CONFIG_PREBOOT */
    • 第1~7行是动态版本号功能支持的代码,version_string变量是在其他文件中定义的一个字符串变量,当用户改变u-boot版本时会更新该变量。在打开动态版本支持功能后,u-boot在启动时会显示最新的版本号。
    • 第18行设置命令行自动完成功能,该功能与Linux_shell类似吗,当用户输入一部分命令后,可以按Tab键补全命令的剩余部分。
    • 第21~38行判断是否支持预启动命令如果支持,则从环境变量中取出相关命令执行。

    ④启动延时和启动菜单

    • 在进入主循环之前,如果配置了启动延时功能,需要等待用户从串口或网络接口输入。如果用户按下任意键打断启动流程,则向终端打印出一个启动菜单。代码如下:
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    	s = getenv ("bootdelay");
    	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
    
    	debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
    
    # ifdef CONFIG_BOOT_RETRY_TIME
    	init_cmd_timeout ();
    # endif	/* CONFIG_BOOT_RETRY_TIME */
    
    #ifdef CONFIG_POST
    	if (gd->flags & GD_FLG_POSTFAIL) {
    		s = getenv("failbootcmd");
    	}
    	else
    #endif /* CONFIG_POST */
    #ifdef CONFIG_BOOTCOUNT_LIMIT
    	if (bootlimit && (bootcount > bootlimit)) {
    		printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
    		        (unsigned)bootlimit);
    		s = getenv ("altbootcmd");
    	}
    	else
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    		s = getenv ("bootcmd");
    
    	debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
    
    	if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    # ifdef CONFIG_AUTOBOOT_KEYED
    		int prev = disable_ctrlc(1);	/* disable Control C checking */
    # endif
    
    # ifndef CONFIG_SYS_HUSH_PARSER
    		run_command (s, 0);
    # else
    		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
    				    FLAG_EXIT_FROM_LOOP);
    # endif
    
    # ifdef CONFIG_AUTOBOOT_KEYED
    		disable_ctrlc(prev);	/* restore Control C checking */
    # endif
    	}
    
    # ifdef CONFIG_MENUKEY
    	if (menukey == CONFIG_MENUKEY) {
    	    s = getenv("menucmd");
    	    if (s) {
    # ifndef CONFIG_SYS_HUSH_PARSER
    		run_command (s, 0);
    # else
    		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
    				    FLAG_EXIT_FROM_LOOP);
    # endif
    	    }
    	}
    #endif /* CONFIG_MENUKEY */
    #endif	/* CONFIG_BOOTDELAY */
    
    #ifdef CONFIG_AMIGAONEG3SE
    	{
    	    extern void video_banner(void);
    	    video_banner();
    	}
    #endif
    • 第1~5行,获得表示启动延时的环境变量“bootdelay”,如果不存在,则从配置宏 CONFIG_BOOTDELAY 获取,并保持在变量bootdelay中
    • 第17~24行,判断启动次数是否超过限制,如果是,则执行环境变量“altbootcmd表示替代启动命令,替代启动命令可以用来为未授权的商业产品保留一些功能。
    • 第25~43行,从环境变量 “bootcmd”获取启动命令,再看变量bootdelay表示的启动延时大于等于0且启动流程未被中止(abortboot()返回1)的情况下,执行启动命令
    • 第46~58行,检查是否是因为CONFIG_MENUKEY宏指定的按键被按下而中止了启动,如果是,则从环境变量“mennucmd”中取出菜单命令并执行,以调出启动菜单。
    • 第61~66行,如果配置了视频设备,则显示启动图标。

    ⑤执行命令循环

    • 各功能设置完毕后,main loop函数进入一个for死循环,该循环不断使用readline()函数t runcommando函数(第480行)执行该命令。代码如下:
    for (;;) {
    #ifdef CONFIG_BOOT_RETRY_TIME
    		if (rc >= 0) {
    			/* Saw enough of a valid command to
    			 * restart the timeout.
    			 */
    			reset_cmd_timeout();
    		}
    #endif
    		len = readline (CONFIG_SYS_PROMPT);
    
    		flag = 0;	/* assume no special flags for now */
    		if (len > 0)
    			strcpy (lastcommand, console_buffer);
    		else if (len == 0)
    			flag |= CMD_FLAG_REPEAT;
    #ifdef CONFIG_BOOT_RETRY_TIME
    		else if (len == -2) {
    			/* -2 means timed out, retry autoboot
    			 */
    			puts ("\nTimed out waiting for command\n");
    # ifdef CONFIG_RESET_TO_RETRY
    			/* Reinit board to run initialization code again */
    			do_reset (NULL, 0, 0, NULL);
    # else
    			return;		/* retry autoboot */
    # endif
    		}
    #endif
    
    		if (len == -1)
    			puts ("<INTERRUPT>\n");
    		else
    			rc = run_command (lastcommand, flag);
    
    		if (rc <= 0) {
    			/* invalid command or not repeatable, forget it */
    			lastcommand[0] = 0;
    		}
    	}
    #endif /*CONFIG_SYS_HUSH_PARSER*/
    }
    
    • 在上述代码中,第15、16行表示如果用户直接按回车键(此时命令长度len等于0),就会在第34行重复执行上一次的命令。第31、32行表示:如果用户按Ctrl+C组合键(此时命令长度len等于-1),终端将输出“<INTERRUPT>”,表示上次命令执行被中断了。

    总结:

    u-bot启动流程分3个2:

    • 2个阶段:汇编和C,大部分用C语言写,但是对于一些特殊功能寄存器,C没有权限访问,还有C之前需要有栈的准备,而汇编不需要,C代码比汇编代码更好维护
    • 2次代码搬移:自搬移u-boot到内存;搬移内核到内存。为了提速,内存>flash
    • 2次硬件初始化:汇编阶段,完成基本硬件的初始化;C阶段,完成大部分硬件的初始化。
    • 阶段一(汇编)
      设置为SVC模式,关闭中断,MMU,看门狗
      基本硬件设备初始化:初始化时钟、串口、flash、内存
      自搬移到内存、设置好栈跳转到C阶段
    • 阶段二(C语言)
      大部分硬件初始化
      搬移内核到内存
      运行内核
    展开全文
  • 史上最详细最全面的uboot启动过程分析,看完之后能对UBOOT有个全面的了解。绝对独家 史上最好的UBOOT分析教程。
  • Uboot启动流程分析

    千次阅读 2018-06-23 13:49:17
    转载地址:https://blog.csdn.net/zqixiao_09/article/details/50805720 uboot启动流程分析如下:第一阶段:a -- 设置cpu工作模式为SVC模式b -- 关闭中断,mmu,cachev -- 关看门狗d -- 初始化内存,串口e -- 设置栈...

    转载地址:https://blog.csdn.net/zqixiao_09/article/details/50805720

     uboot启动流程分析如下:

    第一阶段:

    a -- 设置cpu工作模式为SVC模式
    b -- 关闭中断,mmu,cache
    v -- 关看门狗
    d -- 初始化内存,串口
    e -- 设置栈
    f -- 代码自搬移
    g -- 清bss
    h -- 跳c

    第二阶段

    a -- 初始化外设,进入超循环
    b -- 超循环处理用户命令


    可见, U-Boot 属于两阶段的Bootloader

    第一阶段的文件:

    arch/arm/cpu/armv7 /start.S                       平台相关,CPU工作模式设为SVC模式,关MMU,关icahce(CPU相关)

    board/samsung/fs4412/lowlevel_init.S     开发板相关:关看门狗,内存初始化,时钟初始化,串口初始化(board相关,初始化最基本设备)


    第二阶段的文件:

    arch/arm/lib/crt0.S                     _main 函数所在处,初始化SP,为C语言准备,代码重定位,清BSS,设置R0 R1 R2 R8相应寄存器

    arch/arm/lib/board.c                   board_init_f 函数 ,填充GD结构体,初始化外设, main_loop()函数超循环

    arch/arm/cpu/armv7 /start.S 代码自搬移时会用到

    针对uboot2013启动流程图如下:



    下面是具体分析:

    一、U-Boot 第一阶段代码分析

    通常我们通过连接文件知晓程序入口点,入口查看 u-boot.lds


    通过链接脚本可知入口为_start,位于arch/arm/cpu/armv7/start.o


    第一阶段开始:

    1、进入arch/arm/cpu/armv7/start.S

    a -- 异常向量表设置



    b -- 设置CPU处于SVC工作模式



    d -- 协处理器 p15 的 c12 寄存器来重新定位



    e、Bl  cpu_init_cp15(使分支预测无效,数据)


    关闭数据预取功能;

    DSB:多核CPU对数据处理指令

    ISB:流水线清空指令;


    关闭MMU,使能I-cache

    NOTE:

    分支预测:在流水线里,会将后面的代码优先加载到处理器中,由于是循环,会使后面加载的代码无效,故出现了分支预测技术。(统计跳的次数来选择装载循环的代码还是下面的代码)。


    f、Bl  cpu_init_crit




    2、跳到Low_level_init,位于board/samsung/fs4412/lowlevel_init.S

    a、关闭看门狗


    b、比较当前pc指针域TEXT_BASE的高8位是否一样来判断,当前代码是否在内存中



    c、对系统时钟初始化



    d、对内存初始化


    e、对串口初始化


    结束后返回 start.S

    第一阶段结束,总结如下:

    1 前面总结过的部分,初始化异常向量表,设置svc模式

    2 配置cp15,初始化mmu cache tlb

    3 板级初始化,clk,memory,uart初始化


    二、第二阶段开始:


    按"CTRL + ] ", 发现 _main 在两处有定义:


    这里我们选择第一个Bl  _main ,跳转到arch/arm/lib/crt0.S


    1、初始c运行环境(看注释就知道,初始化C运行环境,并调用board_init_f 函数


    功能:

    初始化sp ,为支持C语言做准备;

    保存128B 放GD结构体,存放全局信息,GD的地址存放在r8中;

    跳转到 board_init_f 函数,其在arch/arm/lib/board.c 处定义;


    2、跳转到arch/arm/lib/board.c


    功能:

    对全局信息GD结构体进行填充:

    291行:mon_len 通过链接脚本可以知道存放的是uboot代码大小;

    294行:fdt_blob 存放设备数地址;

    303行:循环执行init_fnc_t数组的函数,作硬件初始化;


    a -- init_fnc_t数组的函数定义

         初始化硬件


    b -- Dram_init初始化成功之后,剩余代码将会对sdram空间进行规划。


    可以看到addr的值由CONFIG_SYS_SDRAM_BASE加上ram_size。也就是到了可用sdram的顶端。


    e--继续对gd结构体填充


    如果icahe 与 dcache 是打开的,就留出 64K 的空间作为 tlb 空间,最后 addr 就是tlb 地址,4K对齐。


    f --填充完成将信息拷贝到内存指定位置




    2 -- 继续回到 _main

    "CTRL + O"回到跳转前的函数,即 arch/arm/lib/crt0.S


    功能:

    将 r8 指向新的 gd 地址;

    代码重定位;

     对lr 的操作为了让返回时,返回的是重定位的here处


    3 -- 代码自搬移

    代码自搬移,防止与内核冲突,代码位于arch/arm/cpu/armv7/start.S

    循环将代码搬移到指定高地址

    这里只是将链接脚本中_image_copy_end_start中的代码,其它段还没有操作。

    在这里我们有疑惑就是将代码重定位到高地址,那运行的地址不就和链接地址不一样了,那运行可能不正常?这个疑惑就是.rel.dyn帮我们解决了,主要还是编译器帮我们做的工作,在链接中有如下:【参考:http://blog.csdn.net/skyflying2012/article/details/37660265


    4 -- 重定位到高地址之后,再次回到 _main(arch/arm/lib/crt0.S)

          此时回到的是刚才的重定位的 here 处


    关 icache,保证数据从SDRAM中更新,更新异常向量表,因为代码被重定位了;

    清BBS;



    调用board_init_r主要是对外设的初始化。

    R0=gd

    R1=RELOCADDR


    5 -- Main_loop 函数进入超循环(arch/arm/lib/board.c)


    Main_loop函数主要功能是处理环境变量,解析命令

    install_auto_complete();  //安装自动补全的函数,分析如下 

    getenv(bootcmd)

    bootdelay(自启动)

    如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。


    展开全文
  • uboot启动过程详解

    千次阅读 2017-04-15 17:50:05
    在android启动过程中,首先启动的便是ubootuboot是负责引导内核装入内存启动或者是引导recovery模式的启动。现在在很多android的uboot启动过程中,都需要对内核镜像和ramdisk进行验证,来保证android系统的安全...

    在android启动过程中,首先启动的便是uboot,uboot是负责引导内核装入内存启动或者是引导recovery模式的启动。现在在很多android的uboot的启动过程中,都需要对内核镜像和ramdisk进行验证,来保证android系统的安全性,如果在uboot引导过程中,如果内核镜像或ramdisk刷入的是第三方的未经过签名认证的相关镜像,则系统无法启动,这样便保证了android系统的安全性。

    在uboot启动过程中,是从start.S开始的,这里详细的细节不在赘述了,该篇文章主要学习uboot对内核镜像和ramdisk镜像的验证启动过程,同时学习一下里面的优秀巧妙的编码方式。

    我们从arch/arm/lib/board.c的函数board_init_r函数开始,我们来看一下该代码:

    
        void board_init_r(gd_t *id, ulong dest_addr)
        {
            gd = id;
    
            gd->flags |= GD_FLG_RELOC;  /* tell others: relocation done */
    
            monitor_flash_len = _end_ofs;
    
            debug("monitor flash len: %08lX\n", monitor_flash_len);
            board_init();   /* Setup chipselects */
    
        #if defined(CONFIG_MISC_INIT_R)
            /* miscellaneous platform dependent initialisations */
            misc_init_r();
        #endif
    
        #if defined(CONFIG_USE_IRQ)
            /* set up exceptions */
            interrupt_init();
    
            /* enable exceptions */
            enable_interrupts();
            printf("init interrupt done!\n");
        #endif
    
        #if defined(CONFIG_COMIP_FASTBOOT) && defined(CONFIG_LCD_SUPPORT)
            if (gd->fastboot) {
            /*register lcd support*/
        #if defined (CONFIG_LCD_AUO_OTM1285A_OTP)
                extern int lcd_auo_otm1285a_otp_init(void);
                lcd_auo_otm1285a_otp_init();
        #endif
        #if defined (CONFIG_LCD_AUO_R61308OTP)
                extern int lcd_auo_r61308opt_init(void);
                lcd_auo_r61308opt_init();
        #endif
        #if defined (CONFIG_LCD_AUO_NT35521)
                extern int lcd_auo_nt35521_init(void);
                lcd_auo_nt35521_init();
        #endif
        #if defined (CONFIG_LCD_SHARP_R69431)
                extern int lcd_sharp_eR69431_init(void);
                lcd_sharp_eR69431_init();
        #endif
                /*initialize lcdc & display logo*/
                extern int comipfb_probe(void);
                comipfb_probe();
            }
        #endif
    
        #if defined(CONFIG_COMIP_TARGETLOADER)
            extern int targetloader_init(void);
            targetloader_init();
        #elif defined(CONFIG_COMIP_FASTBOOT)
            if (gd->fastboot) {
                extern int fastboot_init(void);
                fastboot_init();
                while(1);
            }
        #endif
    
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_FOR_BOOT)
                extern  int secure_verify(void);
                extern void pmic_power_off(void);
                if(secure_verify()) {
                        printf("Secure verify failed! Shutdown now!\n");
                        pmic_power_off();
                } else {
                        printf("Secure verify succeed!\n");
                }
        #endif
    
            do_bootm_linux();
    
            /* 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 */
        }
    

    首先该函数做的是初始化board,调用board_init()函数。该函数位于board/****/***.c文件中,该文件由于属于板子厂家,所以暂时保密。我们来看一下这个函数:

    
        int board_init(void)
        {
            gd->bd->bi_arch_number = MACH_TYPE_LC186X;
            gd->bd->bi_boot_params = CONFIG_BOOT_PARAMS_LOADADDR;
    
        #ifndef CONFIG_COMIP_TARGETLOADER
            tl420_init();
    
            watchdog_init();
    
            comip_lc186x_coresight_config();
    
            comip_lc186x_sysclk_config();
    
            comip_lc186x_sec_config();
    
            comip_lc186x_bus_prior_config();
        #endif
    
        #if defined(COMIP_LOW_POWER_MODE_ENABLE)
            comip_lp_regs_init();
        #endif
            icache_enable();
            //dcache_enable();
        #if CONFIG_COMIP_EMMC_ENHANCE
            mmc_set_dma(1);
        #endif
            flash_init();
    
        #ifndef CONFIG_COMIP_TARGETLOADER
            pmic_power_on_key_check();
            boot_image();
            pmic_power_on_key_check();
        #endif
    
        #ifdef CONFIG_PMIC_VIBRATOR
            pmic_vibrator_enable_set();
        #endif
    
            return 0;
        }
    

    在该函数中,主要是用于初始化一些参数和硬件,包括arch版本号,boot加载地址,初始化watchdog,系统时钟,总线,flash,同时还需要做的就是,我们开机时的按钮监听,组合键按钮监听,启动镜像,开机震动等操作,在这里我们看一下boot_image()函数的实现。

    
        static void boot_image(void)
        {
            char *kernel_name = CONFIG_PARTITION_KERNEL;
            char *ramdisk_name = CONFIG_PARTITION_RAMDISK;
            int pu_reason;
            int key_code;
            int ret;
    
            pu_reason = pmic_power_up_reason_get();
            if ((pu_reason == PU_REASON_REBOOT_RECOVERY)
                    || (pu_reason == PU_REASON_REBOOT_FOTA)
                    || check_recovery_misc() || check_recovery_fota()) {
                gd->boot_mode = BOOT_MODE_RECOVERY;
                ramdisk_name = CONFIG_PARTITION_RAMDISK_RECOVERY;
        #if defined(CONFIG_USE_KERNEL_RECOVERY)
                kernel_name = CONFIG_PARTITION_KERNEL_RECOVERY;
        #endif
            } else {
                ret = keypad_init();
                if (ret)
                    printf("keypad init failed!\n");
    
                key_code = keypad_check();
                printf("key code: %d\n", key_code);
    
                 if(pu_reason == PU_REASON_USB_CHARGER
                    #if defined(CONFIG_COMIP_FASTBOOT)
                        && key_code != CONFIG_KEY_CODE_FASTBOOT
                    #endif
                ) {
                    gd->boot_mode = BOOT_MODE_NORMAL;
                    ramdisk_name = CONFIG_PARTITION_RAMDISK_AMT1;
                } else {
                    switch (key_code) {
                    case KEY_CODE_RECOVERY:
                        gd->boot_mode = BOOT_MODE_RECOVERY;
                        ramdisk_name = CONFIG_PARTITION_RAMDISK_RECOVERY;
                        #if defined(CONFIG_USE_KERNEL_RECOVERY)
                        kernel_name = CONFIG_PARTITION_KERNEL_RECOVERY;
                        #endif
                        break;
                    #if defined(CONFIG_USE_RAMDISK_AMT3)
                    case KEY_CODE_AMT3:
                        gd->boot_mode = BOOT_MODE_AMT3;
                        ramdisk_name = CONFIG_PARTITION_RAMDISK_AMT3;
                        break;
                    #endif
                    default:
                        gd->boot_mode = BOOT_MODE_NORMAL;
                        ramdisk_name = CONFIG_PARTITION_RAMDISK;
                        break;
                    }
    
                    #if defined(CONFIG_COMIP_FASTBOOT)
                    if (key_code == CONFIG_KEY_CODE_FASTBOOT) {
                        printf("goto fastmode!\n");
                        gd->fastboot = 1;
                    }
                    #endif
                }
            }
    
            printf("kernel name: %s, ramdisk name: %s\n", kernel_name, ramdisk_name);
    
            flash_partition_read(kernel_name, (u8*)(CONFIG_KERNEL_LOADADDR - IMAGE_ADDR_OFFSET), 0xffffffff);
    
            flash_partition_read(ramdisk_name, (u8*)(CONFIG_RAMDISK_LOADADDR - IMAGE_ADDR_OFFSET), 0xffffffff);
    
        #if defined(CONFIG_COMIP_FASTBOOT) && defined(CONFIG_LCD_SUPPORT)
            if (unlikely(gd->fastboot))
                flash_partition_read(CONFIG_PARTITION_FASTBOOT_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE);
            else
                flash_partition_read(CONFIG_PARTITION_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE);
        #else
            flash_partition_read(CONFIG_PARTITION_LOGO, (u8*)(unsigned int)gd->fb_base, CONFIG_FB_MEMORY_SIZE);
        #endif
    
            printf("boot image end\n");
        }
        #endif /* !CONFIG_COMIP_TARGETLOADER */
    

    在这里首先需要确定内核镜像和ramdisk镜像的地址,然后初始化按钮监听,根据不同的按钮组合按键启动不同的镜像,包括正常启动,也就是说启动内核,启动android;启动recovery镜像;启动工厂模式等。将这些镜像数据读取进入flash中引导启动。

    接着我们回到board.c,程序接着运行,接着初始化misc,初始化中断,使能中断;同时在这里判断是否进去fastboot模式。接着,进入我们的重点,也就是安全启动验证阶段。

        #if defined(CONFIG_ENABLE_SECURE_VERIFY_FOR_BOOT)
                extern  int secure_verify(void);
                extern void pmic_power_off(void);
                if(secure_verify()) {
                        printf("Secure verify failed! Shutdown now!\n");
                        pmic_power_off();
                } else {
                        printf("Secure verify succeed!\n");
                }
        #endif
    

    在这里,我们刚刚说了,已经把相应的内核镜像数据和ramdisk镜像数据读入到flash中了。

    那么uboot又是如何验证内核镜像和ramdisk镜像的呢?我们接着看。

    我们先来看函数secure_verify()函数。

    
        int secure_verify(void)
        {
                getverifyimage(VERIFY_KERNEL);
                if (image_rsa_verify()) {
                        printf("kernel verify failed!\n");
                        return 1;
                } else {
                        printf("kernel verify ok!\n");
                        getverifyimage(VERIFY_RAMDISK);
                        if(image_rsa_verify()) {
                                printf("ramdisk verify failed!\n");
                                return 1;
                        } else {
                                printf("ramdisk verify ok!\n");
                        }
                }
                return 0;
        }
    

    在这段代码中,我们可以看出,首先是获取内核镜像数据,然后进行rsa签名验证,接着获取ramdisk镜像数据,接着进行签名验证。

    我们来看一下如何获取内核镜像数据或者是ramdisk镜像数据,也就是getverifyimage()函数。

    
        void getverifyimage(int whichimage)
        {
                int i;
                unsigned int *cfgInfor = image_data_all;
                int ret;
    
                if(whichimage == VERIFY_KERNEL) {
                        image_data_all = CONFIG_KERNEL_LOADADDR - HEADINFOLEN;
                } else if(whichimage == VERIFY_RAMDISK) {
                        image_data_all = CONFIG_RAMDISK_LOADADDR - HEADINFOLEN;
                }
                cfgInfor = (unsigned int *)image_data_all;
                ORIGIN_IMAGE_LEN =  cfgInfor[0];
    
                for(i=0; i<(256/4); i++)
                {
                        RSASIGNATURE[i] = cfgInfor[i + (RSASIGNEDLEN / 4)];
                }
    
                for(i=0; i<(524/4); i++)
                {
                        RSAPUBKEYSTRU[i] = cfgInfor[i + (RSAPUBKEYLEN / 4)];
            }
    
                ORIGIN_IMAGE_BASEADDR = &image_data_all[HEADINFOLEN];
    
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                printf("image len:0x%x(%d)\n", ORIGIN_IMAGE_LEN, ORIGIN_IMAGE_LEN);
    
                dumphex("rsa pub key", RSAPUBKEYSTRU, 524/4);
                dumpint("rsa pub key", RSAPUBKEYSTRU, 524/4);
        #endif
        }
    

    我们以内核启动验证为例进行讲解,ramdisk是一样的。我先来画一下内核镜像数据在flash中的分布,这样分析起代码来便会更容易理解。

    这里写图片描述

    首先我们需要了解的是,我们刷入的内核镜像并不是可运行的内核镜像,因为我们在真正的内核镜像之前加入了一个小小的1.5K的头,该头里面包含了内核的大小,经过私钥对内核签名后的签名,以及需要使用的公钥生成的一些属性。所以在该获取镜像的函数中,我们获取了所有的内核镜像数据,内核镜像大小,签名数据以及公钥属性数据。

    下面我们就需要对其进行rsa验证。

    
          /******************************************************
                        Let image to do RSA verify. If verify OK,
                        return 0. Otherwise, return 1.
        *******************************************************/
        int image_rsa_verify(void)
        {
                        unsigned int value, i;
                        unsigned char *image_sha256;
                        unsigned char *signature;
                        RSAPublicKey *public_key;
                        SHA256_CTX ctx;
    
                        updateNum = 0;
                        value = rsaPubKey_sha256_verify();
                        if(value == 1)
                                return 1;
    
                        updateNum = 0;
                        image_sha256 = (unsigned char*)SHA256_hash(ORIGIN_IMAGE_BASEADDR, ORIGIN_IMAGE_LEN, image_sha256, &ctx);
    
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                        dumphex("current image hash", uboot_sha256, 32);
        #endif
    
                        for(i=0; i<32; i++)
                                ORIGIN_IMAGE_SHA[i] = image_sha256[i];
    
                        updateNum = 0;
                        signature = (unsigned char*)RSASIGNATURE;
                        public_key = (RSAPublicKey *)RSAPUBKEYSTRU;
                        value = RSA_verify(public_key, signature, 256, ORIGIN_IMAGE_SHA, 32);
    
                        if(value == 0)
                                return 1;
                        return 0;
        }
    
    

    通过这个函数可以看到,对内核进行了两次验证,一次是通过函数rsaPubKey_sha256_verify()进行验证,另外一个是通过RSA_verify进行验证,我们先来看第一个:

    
          /****************************************************
                        Use SHA256 to generate digest of RSA pub-key, 
                        which is 524 BYTES. Then compare the digest 
                        with the original digest which is store in the
                        EFUSE. If the new digest equals original digest,
                        it means RSA pub-key is right. Otherwise, means
                        the RSA pub-key is wrong. 
        *****************************************************/
        int rsaPubKey_sha256_verify(void)
        {
                unsigned int *digest, origDigest[8], i, result;
                unsigned char *newDigest;
                SHA256_CTX ctx;
                digest = (unsigned int *)SHA256_hash(RSAPUBKEYSTRU, 524, newDigest, &ctx);
        #if 1
                origDigest[0] = *RSA_SIGNATURE0;
                origDigest[1] = *RSA_SIGNATURE1;
                origDigest[2] = *RSA_SIGNATURE2;
                origDigest[3] = *RSA_SIGNATURE3;
                origDigest[4] = *RSA_SIGNATURE4;
                origDigest[5] = *RSA_SIGNATURE5;
                origDigest[6] = *RSA_SIGNATURE6;
                origDigest[7] = *RSA_SIGNATURE7;
        #else
                origDigest[0] = rsahash[0];
                origDigest[1] = rsahash[1];
                origDigest[2] = rsahash[2];
                origDigest[3] = rsahash[3];
                origDigest[4] = rsahash[4];
                origDigest[5] = rsahash[5];
                origDigest[6] = rsahash[6];
                origDigest[7] = rsahash[7];
        #endif
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                dumphex("pubkey hash", digest, 32);
                dumphex("read pubkey hash", origDigest, 32);
        #endif
                //new key and old key xor
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                printf("read pubkey hash:\n");
        #endif
                for(i=0; i<8; i++)
                {
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                    printf("%08x ", origDigest[i]);
        #endif
                    result = ((origDigest[i]) ^ (digest[i]));
                    if(result != 0) {
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                        printf("ERROR! %s %d\n", __func__,__LINE__);
        #endif
                        return 1;
                    }
                }
    
        #if defined(CONFIG_ENABLE_SECURE_VERIFY_DEBUG)
                        dumphex("pubkey hash", digest, 32);
        #endif
                        return 0;
    
    

    这里是对公钥进行sha256签名来验证公钥是否是对的,具体的函数实现不再学习。

    接着通过对内核镜像数据进行sha256获取哈希,然后,使用公钥和签名进行签名验证,验证内核镜像数据是否是正确的。这样,通过这两步,必须两步都对,才能进行内核的正常加载和运行。

    ramdisk镜像的签名验证也是如何,对内核镜像和ramdisk镜像签名验证之后,接着执行下面的操作,也就是执行do_bootm_linux()函数,该函数的实现如下:

    
          void do_bootm_linux(void)
        {
            bd_t *bd = gd->bd;
            void (*theKernel) (int zero, int arch, uint params);
            theKernel = (void (*)(int, int, uint))CONFIG_KERNEL_LOADADDR;
    
            params = (struct tag *)bd->bi_boot_params;
    
            params->hdr.tag = ATAG_CORE;
            params->hdr.size = tag_size(tag_core);
            params->u.core.flags = 0;
            params->u.core.pagesize = 0;
            params->u.core.rootdev = 0;
            params = tag_next(params);
    
            params = comip_set_boot_params(params);
    
            params->hdr.tag = ATAG_NONE;
            params->hdr.size = 0;
    
            /* we assume that the kernel is in place */
            printf("\nStarting kernel ...\n");
            cleanup_before_linux();
            theKernel(0, bd->bi_arch_number, bd->bi_boot_params);
        }
    

    在这里实际上就是通过一个theKernel函数指针,加载内核启动运行,这样,便进行内核的启动运行了。

    这样,我们便把uboot的启动流程以及对内核和ramdisk进行启动验证的过程进行了一个整体的学习,其内部的RSA算法实现不再赘述。

    展开全文
  • uboot启动流程

    千次阅读 2016-12-21 23:52:41
    一、uboot说明 1、uboot要做的事情 CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,...

    一、uboot说明

    1、uboot要做的事情

    CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,等待处理命令。
    在armv7架构的uboot,主要需要做如下事情

    • arch级的初始化

      • 关闭中断,设置svc模式
      • 禁用MMU、TLB
      • 关键寄存器的设置,包括时钟、看门狗的寄存器
    • 板级的初始化

      • 堆栈环境的设置
      • 代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2C\SPI等等的初始化
      • 进行代码重定向
      • 代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、emmc、nand flash、网络、中断等等的初始化。
      • 进入命令行状态,等待终端输入命令以及对命令进行处理

    上述工作,也就是uboot流程的核心。

    2、疑问

    • 在前面的文章中虽然已经说明了,在spl的阶段中已经对arch级进行了初始化了,为什么uboot里面还要对arch再初始化一遍?
      回答:spl对于启动uboot来说并不是必须的,在某些情况下,上电之后uboot可能在ROM上或者flash上开始执行而并没有使用spl。这些都是取决于平台的启动机制。因此uboot并不会考虑spl是否已经对arch进行了初始化操作,uboot会完整的做一遍初始化动作,以保证cpu处于所要求的状态下。

    • 和spl在启动过程的差异在哪里?
      回答:以tiny210而言,前期arch的初始化流程基本上是一致的,出现本质区别的是在board_init_f开始的。

      • spl的board_init_f是由board自己实现相应的功能,例如tiny210则是在board/samsung/tiny210/board.c中。其主要实现了复制uboot到ddr中,并且跳转到uboot的对应位置上。一般spl在这里就可以完成自己的工作了。
      • uboot的board_init_f是在common下实现的,其主要实现uboot relocate前的板级初始化以及relocate的区域规划,其还需要往下走其他初始化流程。

    3、代码入口

    project-X/u-boot/arch/arm/cpu/u-boot.lds

    ENTRY(_start)

    所以uboot-spl的代码入口函数是_start
    对应于路径project-X/u-boot/arch/arm/lib/vector.S的_start,后续就是从这个函数开始分析。

    二、代码整体流程

    1、首先看一下主枝干的流程(包含了arch级的初始化)

    在arch级初始化是和spl完全一致的
    _start———–>reset————–>关闭中断
    ………………………………|
    ………………………………———->cpu_init_cp15———–>关闭MMU,TLB
    ………………………………|
    ………………………………———->cpu_init_crit————->lowlevel_init————->关键寄存器的配置和初始化
    ………………………………|
    ………………………………———->_main————–>进入板级初始化,具体看下面

    2、板级初始化的流程

    _main————–>board_init_f_alloc_reserve —————>堆栈、GD、early malloc空间的分配
    …………|
    …………————->board_init_f_init_reserve —————>堆栈、GD、early malloc空间的初始化
    …………|
    …………————->board_init_f —————>uboot relocate前的板级初始化以及relocate的区域规划
    …………|
    …………————->relocate_code、relocate_vectors —————>进行uboot和异常中断向量表的重定向
    …………|
    …………————->旧堆栈的清空
    …………|
    …………————->board_init_r —————>uboot relocate后的板级初始化
    …………|
    …………————->run_main_loop —————>进入命令行状态,等待终端输入命令以及对命令进行处理

    三、arch级初始化代码分析

    1、_start

    上述已经说明了_start是整个uboot的入口,其代码如下:
    arch/arm/lib/vector.S

    _start:
    #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
        .word   CONFIG_SYS_DV_NOR_BOOT_CFG
    #endif
        b   reset

    会跳转到reset中。

    2、reset

    代码如下:
    arch/arm/cpu/armv7/start.S


    .globl  reset
        .globl  save_boot_params_ret
    
    reset:
        /* Allow the board to save important registers */
        b   save_boot_params
    save_boot_params_ret:
        /*
         * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
         * except if in HYP mode already
         */
        mrs r0, cpsr
        and r1, r0, #0x1f       @ mask mode bits
        teq r1, #0x1a       @ test for HYP mode
        bicne   r0, r0, #0x1f       @ clear all mode bits
        orrne   r0, r0, #0x13       @ set SVC mode
        orr r0, r0, #0xc0       @ disable FIQ and IRQ
        msr cpsr,r0
    @@ 以上通过设置CPSR寄存器里设置CPU为SVC模式,禁止中断
    @@ 具体操作可以参考《[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断》的分析
    
        /* the mask ROM code should have PLL and others stable */
    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
        bl  cpu_init_cp15
    @@ 调用cpu_init_cp15,初始化协处理器CP15,从而禁用MMU和TLB。
    @@ 后面会有一小节进行分析
    
        bl  cpu_init_crit
    @@ 调用cpu_init_crit,进行一些关键的初始化动作,也就是平台级和板级的初始化
    @@ 后面会有一小节进行分析
    #endif
    
        bl  _main
    @@ 跳转到主函数,也就是板级初始化函数
    @@ 下一节中进行说明。


    3、cpu_init_cp15

    cpu_init_cp15主要用于对cp15协处理器进行初始化,其主要目的就是关闭其MMU和TLB。
    代码如下(去掉无关部分的代码):
    arch/arm/cpu/armv7/start.S

    ENTRY(cpu_init_cp15)
        /* * 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 mcr p15, 0, r0, c7, c5,6   @ invalidate BParray
        mcr     p15, 0, r0, c7, c10, 4  @ DSB
        mcr     p15, 0, r0, c7, c5, 4   @ ISB
    @@ 这里只需要知道是对CP15处理器的部分寄存器清零即可。
    @@ 将协处理器的c7\c8清零等等,各个寄存器的含义请参考《ARM的CP15协处理器的寄存器》

        /* * 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 11 (Z---) BTB
    #ifdef CONFIG_SYS_ICACHE_OFF
        bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
    #else
        orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
    #endif
        mcr p15, 0, r0, c1, c0, 0
    @@ 通过上述的文章的介绍,我们可以知道cp15的c1寄存器就是MMU控制器
    @@ 上述对MMU的一些位进行清零和置位,达到关闭MMU和cache的目的,具体的话去看一下上述文章吧。

    ENDPROC(cpu_init_cp15)

    4、cpu_init_crit

    cpu_init_crit,进行一些关键寄存器的初始化动。其代码核心就是lowlevel_init,如下
    arch/arm/cpu/armv7/start.S

    ENTRY(cpu_init_crit)
        /*
         * 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.
         */
        b   lowlevel_init       @ go setup pll,mux,memory
    ENDPROC(cpu_init_crit)

    所以说lowlevel_init就是这个函数的核心。
    lowlevel_init一般是由板级代码自己实现的。但是对于某些平台来说,也可以使用通用的lowlevel_init,其定义在arch/arm/cpu/lowlevel_init.S中
    以tiny210为例,在移植tiny210的过程中,就需要在board/samsung/tiny210下,也就是板级目录下面创建lowlevel_init.S,在内部实现lowlevel_init。(其实只要实现了lowlevel_init了就好,没必要说在哪里是实现,但是通常规范都是创建了lowlevel_init.S来专门实现lowlevel_init函数)。

    在lowlevel_init中,我们要实现如下:

    • 检查一些复位状态
    • 关闭看门狗
    • 系统时钟的初始化
    • 内存、DDR的初始化
    • 串口初始化(可选)
    • Nand flash的初始化

    下面以tiny210的lowlevel_init为例(这里说明一下,当时移植tiny210的时候,是直接把kangear的这个lowlevel_init.S文件拿过来用的)
    这部分代码和平台相关性很强,简单介绍一下即可
    board/samsung/tiny210/lowlevel_init.S

    lowlevel_init:
        push    {lr}
    
        /* check reset status  */
    
        ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
        ldr r1, [r0]
        bic r1, r1, #0xfff6ffff
        cmp r1, #0x10000
        beq wakeup_reset_pre
        cmp r1, #0x80000
        beq wakeup_reset_from_didle
    @@ 读取复位状态寄存器0xE010_a000的值,判断复位状态。
    
        /* IO Retention release */
        ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
        ldr r1, [r0]
        ldr r2, =IO_RET_REL
        orr r1, r1, r2
        str r1, [r0]
    @@ 读取混合状态寄存器E010_e000的值,对其中的某些位进行置位,复位后需要对某些wakeup位置1,具体我也没搞懂。
    
        /* Disable Watchdog */
        ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */
        mov r1, #0
        str r1, [r0]
    @@ 关闭看门狗
    
    @@ 这里忽略掉一部分对外部SROM操作的代码
    
        /* 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, =0x00ffffff
        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     1f          /* r0 == r1 then skip sdram init   */
    @@ 判断是否已经在SDRAM上运行了,如果是的话,就跳过以下两个对ddr初始化的步骤
    @@ 判断方法如下:
    @@ 1、获取当前pc指针的地址,屏蔽其低24bit,存放与r1中
    @@ 2、获取_TEXT_BASE(CONFIG_SYS_TEXT_BASE)地址,也就是uboot代码段的链接地址,后续在uboot篇的时候会说明,并屏蔽其低24bit
    @@ 3、如果相等的话,就跳过DDR初始化的部分
    
        /* init system clock */
        bl system_clock_init
    @@ 初始化系统时钟,后续有时间再研究一下具体怎么配置的
    
        /* Memory initialize */
        bl mem_ctrl_asm_init
    @@ 重点注意:在这里初始化DDR的!!!后续会写一篇文章说明一下s5pv210平台如何初始化DDR.
    @@ 其实,在tiny210的项目中,已经在spl里面对ddr初始化了一遍,这里还是又重新初始化了一遍,从实际测试结果来看,并不影响正常的使用。
    
    1:
        /* for UART */
        bl uart_asm_init
    @@ 串口初始化,到这里串口会打印出一个'O'字符,后续通过写字符到UTXH_OFFSET寄存器中,就可以在串口上输出相应的字符。
    
        bl tzpc_init
    
    #if defined(CONFIG_NAND)
        /* simple init for NAND */
        bl nand_asm_init
    @@ 简单地初始化一下NAND flash,有可能BL2的镜像是在nand  flash上面的。
    #endif
    
        /* Print 'K' */
        ldr r0, =ELFIN_UART_CONSOLE_BASE
        ldr r1, =0x4b4b4b4b
        str r1, [r0, #UTXH_OFFSET]
    @@ 再串口上打印‘K’字符,表示lowlevel_init已经完成
    
        pop {pc}
    @@ 弹出PC指针,即返回。


    当串口中打印出‘OK’的字符的时候,说明lowlevel_init已经执行完成。

    三、板级初始化代码分析

    1、_main

    板级初始化代码的入口就是_main。从这里开始分析。

    代码如下,去除无关代码部分
    arch/arm/lib/crt0.S

    ENTRY(_main)
    
    /*
     * Set up initial C runtime environment and call board_init_f(0).
     */
        ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
        bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    
        mov r0, sp
        bl  board_init_f_alloc_reserve
        mov sp, r0
        /* set up gd here, outside any C code */
        mov r9, r0
        bl  board_init_f_init_reserve
    @@ 以上是堆栈、GD、early malloc空间的分配,具体参考《[uboot] (番外篇)global_data介绍》
    
        mov r0, #0
        bl  board_init_f
    @@ uboot relocate前的板级初始化以及relocate的区域规划,后续小节继续说明
    @@ 其中relocate区域规划也可以参考一下《[uboot] (番外篇)uboot relocation介绍》
    
    /*
     * Set up intermediate environment (new sp and gd) and call
     * relocate_code(addr_moni). Trick here is that we'll return
     * 'here' but relocated.
     */
    
        ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
        bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
        ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
        sub r9, r9, #GD_SIZE        /* new GD is below bd */
        adr lr, here
        ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
        add lr, lr, r0
        ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
        b   relocate_code
    here:
    /*
     * now relocate vectors
     */
        bl  relocate_vectors
    @@ GD、uboot、异常中断向量表的relocate,可以参考《[uboot] (番外篇)uboot relocation介绍》,这里不详细说明
    
    /* Set up final (full) environment */
        bl  c_runtime_cpu_setup /* we still call old routine here */
    @@ 通过操作协处理器的c7寄存器来关闭Icache
    
        ldr r0, =__bss_start    /* this is auto-relocated! */
        ldr r3, =__bss_end      /* this is auto-relocated! */
        mov r1, #0x00000000     /* prepare zero to clear BSS */
        subs    r2, r3, r0      /* r2 = memset len */
        bl  memset
    @@ 因为堆栈段已经被relocate,所以这里需要清空原来的堆栈段的内容
    
        bl coloured_LED_init
        bl red_led_on
    @@ LED灯的初始化,可以不实现,想要实现的话,可以在board里重新实现一个函数定义。
    
        /* call board_init_r(gd_t *id, ulong dest_addr) */
        mov     r0, r9                  /* gd_t */
        ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
        /* call board_init_r */
        ldr pc, =board_init_r   /* this is auto-relocated! */
        /* we should not return here. */
    @@ uboot relocate后的板级初始化,注意,uboot必须在这里就完成工作,或者在里面实现死循环,不应该返回。
    ENDPROC(_main)

    通过上述,有两个很重要的初始化函数,board_init_f和board_init_r,后续继续说明。

    2、board_init_f

    代码如下:
    common/board_f.c

    void board_init_f(ulong boot_flags)
    {
        gd->flags = boot_flags;
        gd->have_console = 0;
    // 设置global_data里面的一些标志位
    
        if (initcall_run_list(init_sequence_f))
            hang();
    // 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数,initcall_run_list这里不深究
    // 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉
    }


    打开DEBUG宏之后,可以通过log观察哪些init函数被调用,如下log:

    uboot log中有如下log:
    initcall: 23e005a4
    根据u-boot.map可以发现对应
     .text.print_cpuinfo
                    0x23e005a4        0x8 arch/arm/cpu/armv7/built-in.o
                    0x23e005a4                print_cpuinfo
    也就是说print_cpuinfo被initcall调用了。


    所以uboot relocate之前的板级初始化的核心就是init_sequence_f中定义的函数了。
    如下,这里只做简单的说明,需要的时候再具体分析:

    static init_fnc_t init_sequence_f[] = {
        setup_mon_len,
    // 计算整个镜像的长度gd->mon_len
        initf_malloc,
    // early malloc的内存池的设定
        initf_console_record,
    // console的log的缓存
        arch_cpu_init,      /* basic arch cpu dependent setup */
    // cpu的一些特殊的初始化
        initf_dm,
        arch_cpu_init_dm,
        mark_bootstage,     /* need timer, go after init dm */
        /* TODO: can any of this go into arch_cpu_init()? */
        env_init,       /* initialize environment */
    // 环境变量的初始化,后续会专门研究一下关于环境变量的内容
        init_baud_rate,     /* initialze baudrate settings */
    // 波特率的初始化
        serial_init,        /* serial communications setup */
    // 串口的初始化
        console_init_f,     /* stage 1 init of console */
    // console的初始化
        print_cpuinfo,      /* display cpu info (and speed) */
    // 打印CPU的信息
        init_func_i2c,
        init_func_spi,
    // i2c和spi的初始化
    
        dram_init,      /* configure available RAM banks */
    // ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
    // 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
    //========================================
        setup_dest_addr,
        reserve_round_4k,
        reserve_trace,
        setup_machine,
        reserve_global_data,
        reserve_fdt,
        reserve_arch,
        reserve_stacks,
    // ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》
        setup_dram_config,
        show_dram_config,
        display_new_sp,
        reloc_fdt,
        setup_reloc,
    // relocation之后gd一些成员的设置
        NULL,
    };

    注意,必须保证上述的函数都正确地返回0值,否则会导致hang

    3、board_init_r

    代码如下:
    common/board_r.c

    void board_init_r(gd_t *new_gd, ulong dest_addr)
    {
        if (initcall_run_list(init_sequence_r))
            hang();
    // 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究
    // 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉
    
        /* NOTREACHED - run_main_loop() does not return */
        hang();
    // uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。
    }
    

    所以uboot relocate之后的板级初始化的核心就是init_sequence_r中定义的函数了。
    如下,这里只做简单的说明,需要的时候再具体分析:
    common/board_r.c

    init_fnc_t init_sequence_r[] = {
        initr_trace,
    // trace相关的初始化
        initr_reloc,
    // gd中一些关于relocate的标识的设置
        initr_reloc_global_data,
    // relocate之后,gd中一些的成员的重新设置
        initr_malloc,
    // malloc内存池的设置
        initr_console_record,
        bootstage_relocate,
        initr_bootstage,
    #if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
        board_init, /* Setup chipselects */
    // 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数
    #endif
        stdio_init_tables,
        initr_serial,
    // 串口初始化
        initr_announce,
    // 打印uboot运行位置的log
        initr_logbuffer,
    // logbuffer的初始化
        power_init_board,
    #ifdef CONFIG_CMD_NAND
        initr_nand,
    // 如果使用nand flash,那么这里需要对nand进行初始化
    #endif
    #ifdef CONFIG_GENERIC_MMC
        initr_mmc,
    // 如果使用emmc,那么这里需要对nand进行初始化
    #endif
        initr_env,
    // 初始化环境变量
        initr_secondary_cpu,
        stdio_add_devices,
        initr_jumptable,
        console_init_r,     /* fully init console as a device */
        interrupt_init,
    // 初始化中断
    #if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
        initr_enable_interrupts,
    // 使能中断
    #endif
        run_main_loop,
    // 进入一个死循环,在死循环里面处理终端命令。
    };

    最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
    到此,uboot流程也就完成了,后续会专门说明uboot的run_main_loop是怎么运行的。


    展开全文
  • uboot启动过程教程详解

    千次阅读 2018-06-07 13:38:16
    1.1 U-Boot工作过程U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下:(1)第一阶段的功能Ø 硬件设备初始化Ø 加载U-Boot第二阶段代码到RAM空间Ø 设置好栈Ø 跳转到第二阶段代码入口(2)第二阶段的...
  • 0 目录 1 简介 1.1 uboot是什么? 1.2 存储器 ...3 uboot启动流程第1阶段 3.1 start.S文件分析 3.2 lowlevel_init.S文件分析 4 uboot启动流程第2阶段 4.1 初始化 4.2 加载内核 A 参考资料 ...
  • 4412 uboot启动过程分析之一

    千次阅读 2014-03-25 11:34:42
    _start: b reset //uboot启动从_start开始,跳转到reset ldr pc, _undefined_instruction //初始化中断向量表 ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ...
  • 2014.4新版uboot启动流程分析

    万次阅读 多人点赞 2014-05-14 16:10:40
    2014.4最新版本uboot启动过程详解 在网上搜索讲uboot启动过程的文章,大多都是比较老的版本,于是决定将新版uboot启动过程记录下来,以备后用。 2014.4版本uboot启动至命令行几个重要函数为:_start,_main,board_...
  • uboot启动过程分析

    2018-09-12 23:01:34
    uboot启动分为两个阶段 1. 上电硬件初始化 这个阶段主要可以分为以下几步: 设置异常向量表 .globl _start _start:bstart_code/*复位*/ ldrpc, _undefined_instruction/*未定义指令向量*/ ...
  • 详细分析了uboot启动流程,从汇编代码,经过硬件初始化等,跳转到C代码...
  • 03. Uboot启动流程详解

    千次阅读 2018-07-02 08:08:27
    1. start.S解析 1)uboot入口分析 要分析uboot流程首先要找到uboot的入口函数,从x210开发板的链接器脚本可以获得该信息 根据ENTRY(_start)可知,uboot的入口函数为start;再根据.text段的链接顺序,可知start函数...
  • [uboot] (第五章)uboot流程——uboot启动流程2016年11月07日 20:12:07阅读数:2230以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为例[uboot] uboot流程系列: [project X] tiny210(s5pv210)上电...
  • 一、uboot启动流程简介 与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段。BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现;BL2阶段主要是对外部设备...
  • 正点原子uboot启动流程笔记和思维导图,uboot的结构图,以及代码加了中文注释,学习笔记供参考。
  • 最近在做板卡的调试工作,需要调试板卡,所以先熟悉一下uboot启动过程(这里主要分析代码流程): 1:uboot:作用需要设置cpu状态,中断状态,MMU状态, 其中包括arch级初始化:关中断,设置svc模式,时钟,看门狗...
  • [RK3399][Android7.1] Uboot启动过程小结

    千次阅读 热门讨论 2018-01-04 14:00:58
    OS: Android 7.1 Board: Firefly-RK3399Uboot: v2017.02整个过程和rk3288平台的uboot(v2014.10)加载流程类似,可参考[RK3288][Android6.0] U-boot 启动流程小结bl board_init_f -&gt; crt0_64.S //初始化环境...
  • 4412 uboot启动流程

    2020-09-03 13:30:44
    我们首先看下三星提的供启动流程
  • 深入理解uboot (处理器启动流程分析)

    万次阅读 多人点赞 2018-05-15 10:32:42
    转载:https://blog.csdn.net/kernel_yx/article/details/53045424最近一段时间一直在做uboot移植相关的工作,...之前在学习uboot时,在网上看了很多文章,很多都是基于老版本的的uboot,并且很多都是直接从代码...
  • U-Boot 属于两阶段的Bootloader,第一阶段的文件为arch/arm/cpu/armv7 /start....uboot启动流程分析如下: 第一阶段: a -- 设置cpu工作模式为SVC模式 b -- 关闭中断,mmu,cache v -- 关看门狗 d -- 初始化内存,串口 e
  • Uboot启动流程和Kernel启动流程

    千次阅读 2016-11-27 16:31:47
    /**********************Uboot启动流程(分为两部分)**********************/ 第一部分(放在start.s中,汇编) 1).定义入口(通过链接器脚本来完成) 2)设置异常向量 3)设置CPU速度、时钟频率和中断控制...
  • uboot启动过程

    千次阅读 2016-10-20 16:52:49
    我理解这个东西正如它的名字那样First Stage Boot Loader(FSBL),主要完成以下几项工作: 1. 初始化单板,包括DDR控制器、PLL、时钟、MIO及部分外设的控制器寄存器配置; 2. 将bit文件下载到FPGA内;...
  • uboot启动流程
  • imx6 uboot启动流程分析

    2017-06-27 16:59:00
    这里以imx6平台为例,分析uboot启动流程对于任何程序,入口函数是在链接时觉得的,uboot的入口是由链接脚本决定的.uboot下armv7链接脚本默认目录为arch/arm/cpu/u-boot.lds.这个可以在配置文件中与CONFIG_SYS_LDSCR...
  • 通过上一节的分析我们知道:1、 Uboot的第一个启动文件为:cpu/arm920t/start.o2、 连接脚本/board/100ask24x0/u-boot.lds 连接地址为0x33f80000 下面开始分析Start.s.globl_start_start: b reset 一、首先...
  • uboot启动流程和架构

    千次阅读 2017-02-01 15:52:04
    1、启动流程 2、架构一、uboot流程图: 从上图中看到红色1,2,3,4,5,7,8,9的标号,下面分别说明每个过程: 1、启动入口: (1)确定链接脚本文件:在根目录下Makefile下LDSCRIPT宏值,就是指定链接脚本(如:...
  • Uboot启动流程(图+代码)

    千次阅读 2013-06-24 15:31:40
    Uboot是嵌入式系统中最常用的bootloader,这里我们以s3c2410为例分析一下uboot启动流程。首先通过uboot的链接文件,我们可以看到uboot运行是执行的第一段代码在start.S中。 ENTRY(_start)  SECTIONS  {  ...
  • Uboot启动流程

    千次阅读 2017-12-11 18:57:41
    依靠网上的资料和代码的Linux启动流程总结。很多地方只有大致功能,具体实现细节未知。
  • uboot 启动流程

    2018-02-23 17:46:14
    前言2017.01 UBoot包含两个阶段的启动,一个是SPL启动,一个是正常的启动我们称为第二阶段Uboot。当然,我们也可以选择使用SPL和不使用。 在编译的过程中,是先编译第二阶段Uboot,然后在编译SPL的。这两个阶段的...
  • 下载地址:https://pan.baidu.com/s/1sloi3Ch

空空如也

1 2 3 4 5 ... 20
收藏数 14,182
精华内容 5,672
关键字:

uboot环境变量长度限制