Uboot学习笔记(四)Uboot启动过程分析

简介

在前面我们已经分析了4412的启动流程以及编译4412的Uboot的流程,这两个流程其实是并不适用于所有芯片的,可以说是4412的特有流程,别的芯片可能类似但是差别不会太小,这次我们分析Uboot的启动过程,这是从一上电到执行main_loop之前的操作,这个部分会有一部分的汇编代码,因为刚刚上电时系统并未准备好C语言的运行环境,需要使用汇编代码来搭建C语言的运行环境,搬移C代码,然后跳转到C语言中执行,所以说这个启动过程可以分为两个部分

  • 第一阶段:初始化环境
  • 第二阶段:跳转到C语言运行环境,进行各种外设初始化,循环执行用户命令

主要流程图如下:
在这里插入图片描述
接下来我们做进一步的分析

链接脚本

当我们执行make命令来构建u-boot时,它的构建过程是:首先使用交叉编译工具将各目录下的源文件生成目标文件(.o),目标文件生成后,会将若干个目标文件组合成静态库文件(.a),最后通过链接各个静态库文件生成ELF格式的可执行文件。在链接的过程中,需要根据链接脚本(一般是各个以lds为后缀的文本文件),确定目标文件的各个段,链接文件是在uboot目录中的u-boot.lds文件。一般在链接脚本中通过

 ENTRY(_start)

来指定入口为_start标号,通过文本段(.text)的第一个目标来指定Uboot入口文件。所以我们通过这个链接脚本文件可以确Uboot执行的入口
iTop-4412 Uboot的连接脚本内容为

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  cpu/arm_cortexa9/start.o (.text)
  cpu/arm_cortexa9/s5pc210/cpu_init.o (.text)
  board/samsung/smdkc210/lowlevel_init.o (.text)
  common/ace_sha1.o (.text)
  *(.text)
 }
 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 . = ALIGN(4);
 .data : { *(.data) }
 . = ALIGN(4);
 .got : { *(.got) }
 __u_boot_cmd_start = .;
 .u_boot_cmd : { *(.u_boot_cmd) }
 __u_boot_cmd_end = .;
 . = ALIGN(4);
 __bss_start = .;
 .bss : { *(.bss) }
 _end = .;
}

在本链接脚本文件中,定义了起始地址为0x00000000,每个段使用4字节对齐(.ALIGN(4)),几个段分别为代码段(.text)、只读数据段(.rodata)、数据段(.data)其中,代码段的第一个目标为cpu/arm_cortexa9/start.o,在其中定义了映像文件的入口_start
接下来我们分析start.s文件

start.s分析

1、首先进行了中断向量的设置,并且跳转到了reset

.globl _start //程序的全局入口,u-boot.lds中设置此入口地址为0x0000_0000
_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 //快速中断异常

//word的意思是将后边的符号所对应的32bit值赋予前面的符号
//下面的七条语句后面的符号正好是对应的中断异常服务程序的入口地址
//七个中断服务程序位于\cpu\arm_cortexa9\s5pc210\interrupts.c
_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 //16bytes对齐,并且使用0xdeadbeef填充
   /*.balignl是.balign的变体
.align伪操作用于表示对齐方式:通过添加填充字节使当前位置满足一定的对齐方式。
.balign的作用同.align。
.align {alignment} {,fill} {,max}
其中:
    alignment用于指定对齐方式,可能的取值为2的次幂,缺省为4。
    fill是填充内容,缺省用0填充。
    max是填充字节数最大值,如果填充字节数超过max,就不进行对齐*/

2、接下来我们分析reset,reset中首先设置CPU进入SVC32模式,然后关闭了IRQ和FIQ;接下来初始化了cache,然后通过以下的方式获取OM PIN的状态,以此确定CPU的启动模式

/* Read booting information */
       ldr	r0, =POWER_BASE
       ldr	r1, [r0,#OMR_OFFSET]
       bic	r2, r1, #0xffffffc1

3、接下来根据r2寄存器的值来选取启动模式,我们的是从emmc启动所以如下,比较r2和0x28,如果相等将r3置为BOOT_EMMC441

	/* eMMC441 BOOT */
	cmp		r2, #0x28
	moveq	r3, #BOOT_EMMC441

4、紧接着,我们的程序会跳转到lowlevel_init,去初始化PLL、MUX、MEM

扫描二维码关注公众号,回复: 9126876 查看本文章
bl	lowlevel_init	/* go setup pll,mux,memory */

5、我们看一下lowlevel_init 的代码
在lowlevel_init 的代码中调用了system_clock_init_scp来初始化系统的时钟,调用了mem_ctrl_asm_init_ddr3来初始化内存,又调用了tzpc_init来初始化TrustZone Protection Controller,

/* init system clock */
bl system_clock_init_scp

/* Memory initialize */
bl mem_ctrl_asm_init_ddr3

bl tzpc_init

接下来初始化了串口,方便调试,最后POP PC寄存器,回到start.s中

/* for UART */
bl uart_asm_init


pop	{pc}

6、回到start.s后,配置寄存器使PS_HOLD输出高电平

ldr	r0, =0x1002330C  /* PS_HOLD_CONTROL register */
ldr	r1, =0x00005300	 /* PS_HOLD output high	*/
str	r1, [r0]

7、配置栈指针,准备进入C环境

/* get ready to call C functions */
ldr	sp, _TEXT_PHY_BASE	/* setup temp stack pointer */
sub	sp, sp, #12
mov	fp, #0			/* no previous frame, so fp=0 */

8、接下来会进行一个延时操作

	/* wait ?us */
	mov	r1, #0x10000
9:	subs	r1, r1, #1
	bne	9b

9、然后跳转到emmc441_boot,进行代码从emmc到内存的复制,当复制完成后初始化MMU

cmp     r1, #BOOT_EMMC441
beq     emmc441_boot
emmc441_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
	ldr	r0, =CMU_BASE
	ldr	r2, =CLK_DIV_FSYS3_OFFSET
	ldr	r1, [r0, r2]
	orr r1, r1, #0x3
	str r1, [r0, r2]
#endif
	bl		emmc441_uboot_copy
//ly 20110824
	ldr   r0, =0x43e00000
	ldr   r1, [r0]
	ldr   r2, =0x2000
	cmp r1, r2
	bne  second_mmcsd_boot
	b	after_copy
after_copy:


#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
	/* enable domain access */
	ldr	r5, =0x0000ffff
	mcr	p15, 0, r5, c3, c0, 0		@load domain access register

	/* Set the TTB register */
	ldr	r0, _mmu_table_base
	ldr	r1, =CFG_PHY_UBOOT_BASE
	ldr	r2, =0xfff00000
	bic	r0, r0, r2
	orr	r1, r0, r1
	mcr	p15, 0, r1, c2, c0, 0

	/* Enable the MMU */
mmu_on:
	mrc	p15, 0, r0, c1, c0, 0
	orr	r0, r0, #1
	mcr	p15, 0, r0, c1, c0, 0
	nop
	nop
	nop
	nop
#endif

10、这时初始化的操作已经基本完成,配置栈,清除bss段,准备进入C语言环境

stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
	ldr	sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CONFIG_SYS_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
#if defined(CONFIG_USE_IRQ)
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub	sp, r0, #12		/* leave 3 words for abort-stack    */

#endif

clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */

11、最后设置PC指针为_start_armboot,进入start_armboot函数,这就进入了C语言环境

clbss_l:
	str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l
	
	ldr	pc, _start_armboot

_start_armboot:
	.word start_armboot

start_armboot分析

start_armboot位于\lib_arm\board.c中
首先我们先看两个比较重要的结构体gd_t和bd_t,在初始化操作很多都要靠这两个数据结构来保存和传递

gd_t

gd_t定义在\include\asm-arm\global_data.h中,其成员主要是一些全局的系统初始化参数。当使用gd_t时需用宏定义进行声明:DECLARE_GLOBAL_DATA_PTR,指定占用寄存器R8。
这个结构体其实可以有很多的成员变量,大部分是根据配置的宏来确定是否编译的,我删除了其中没有被定义的部分,剩下的就是我们会使用到的成员变量

typedef	struct	global_data {
	volatile bd_t		*bd;	//与板子相关的结构
	volatile unsigned long	flags;//只是标志,如设备已经初始化标志等
	volatile unsigned long	baudrate;//串口波特率
	volatile unsigned long	have_console;	/* serial_init() was called 串口初始化标志*/
	volatile unsigned long	env_addr;	/* Address  of Environment struct 环境变量参数地址*/
	volatile unsigned long	env_valid;	/* Checksum of Environment valid? 检测环境变量参数是否有效*/
	volatile unsigned long	fb_base;	/*frame buffer的基地址*/
#ifdef CONFIG_VFD
	volatile unsigned char	vfd_type;	/* 显示类型 */
#endif
#ifdef CONFIG_FSL_ESDHC
	volatile unsigned long	sdhc_clk;//
#endif
	volatile void		**jt;		/* jump table */
} gd_t;

gd_t

gd_t定义在\include\asm-arm\u-boot.h
board info数据结构定义,主要是用来保存板子参数。

typedef struct bd_info {
    int			bi_baudrate;	/* 串口终端的波特率*/
    unsigned long	bi_ip_addr;	/* IP地址 */
    struct environment_s	       *bi_env; //环境变量开始地址
    //开发板ID 该变量标识每一种开发板相关的ID,该值将传递给内核,如果这个参数与内核配置的不相同,那么内核启动解压缩完成后将出现“Error:a”错误,开发板ID可以在内核arch/arm/tools/mach-types中查看*/
    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;

start_armboot代码分析

这是我第一次阅读分析Uboot的源码,这次着重分析的是整个的流程,而不是内部的各种细节,所以说对于start_armboot的分析我会比较浮于表面,不会去分析具体某个模块的初始化
1、首先定义了一些变量

init_fnc_t **init_fnc_ptr;
	char *s;
	int mmc_exist = 0;
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
	unsigned long addr;
#endif

2、接下来是给gd_t结构体的空间分配

/* Pointer is writable since we allocated a register for it */
	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));

这里面有几个要点
(1)gd这个指针事怎么来的,没有malloc,我们也无法转到定义,这个gd就好像是凭空出现。通过对源码的分析我们可以得到,在\lib_arm\board.c的73行有一行代码

DECLARE_GLOBAL_DATA_PTR;

转到定义后,到了\include\asm-arm\global_data.h,它是这样定义的

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。
register表示变量放在机器的寄存器
volatile用于指定变量的值可以由外部过程异步修改。
并且在上面start_armboot的代码中gd指针被初始化,指向了一个可写的地址,这样的gd就可以使用了
(2)在初始化gd后还有一行内联汇编比较难懂

__asm__ __volatile__("": : :"memory");

这里是一个特殊的用法:

  • asm用于指示编译器在此插入汇编语句。
  • volatile用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
  • memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
  • “”:::表示这是个空指令。
    3、接下来是循环调用一个初始化函数序列
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
//	init_fnc_t 定义如下
init_fnc_t *init_fnc_t [] = {
#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 */
#ifdef CONFIG_FSL_ESDHC
	//get_clocks,
#endif
	env_init,		/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	off_charge,		// xiebin.wang @ 20110531,for charger&power off device.

	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,
};

可以看出这个数组存放的就是一下函数指针,在for循环中会被循环调用,这里会初始化板级硬件接口,串口就可以使用了
4、接下来还有几个比较重要的初始化

env_relocate ();//初始化环境
jumptable_init ();//安装系统函数指针
console_init_r ();//完全初始化中断为一个设备
enable_interrupts ();//使能中断

5、接下来就进入了main_loop

for (;;) {
	main_loop ();
}

main_loop中将会处理用户输入的命令以及完成对操作系统的引导,我们下篇博客将分析Uboot引导内核的流程

发布了123 篇原创文章 · 获赞 598 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/a568713197/article/details/102625156