Linux内核引导程序分析之u-boot启动第一阶段

  ARM结构的CPU在上电后,会从地址0x00000000开始执行。嵌入式开发板中,需要把存储器件ROM或Flash等映射到这个地址,u-boot就存放在这个地址,CPU一上电就先执行u-boot。

u-boot的目标是启动内核,要能够完成两个目标过程:

1.从Flash上把内核读入内存。
 a.能读Flash;
 b.关看门狗、初始化时钟、内存等。

2.启动内核。
 a.设置参数;
 b.跳转执行。

根据需要完成的目标过程,需要针对具体的电路板对u-boot做相应的修改设置。

u-boot通常可分为两个阶段,第一个阶段要做的事情有:

使用汇编,完成一些依赖于CPU体系结构的初始化,包括:
  a.硬件设备的初始化(包括关WATCHDOG、屏蔽中断、设置CPU的速度和时钟分频系数、SDRAM初始化等,但并非必需);
  b.为加载好的bootloader的第二阶段代码准备RAM空间;
  c.复制bootloader的第二阶段代码到RAM空间中;
  d.设置好栈;
  e.跳转到第二阶段代码的C入口点。
 
首先根据u-boot.lds链接脚本可以知道:
  a.指定输出文件是elf格式,32位ARM指令,小端模式;
  b.指定输出可执行文件的运行平台为ARM;
  c.ENTRY(_start)指定_start函数为程序的入口,代码在u-boot-2012.04.01\arch\arm\cpu\arm920t\start.S中。
  e.真正的启动运行地址段在编译时由CONFIG_SYS_TEXT_BASE宏定义,即TEXT_BASE = 0x00000000

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)

我们看看代码,u-boot-2012.04.01\arch\arm\cpu\arm920t\start.S:

.globl _start
_start:	b	start_code		/* 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是一个符号,符号在汇编程序中代表一个地址,汇编程序经过汇编器的处理之后,所有的符号都被替换成它所代表的地址值。

  _start就像C程序的main函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。

  以上代码是设置arm的异常向量表,此处有7种异常,每种异常占用4个字节,每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,用ldr将处理函数入口地址加载到pc中。

	.balignl 16,0xdeadbeef

  该指令为汇编伪操作符,表示以当前地址为开始,找到第一次出现的以第一个参数为整数倍的地址,并将其作为结束地址,在这个结束地址前面存储一个字(4字节)长度的数据,存储内容正是第二个参数。如果当前地址正好是第一个参数的倍数,则没有数据被写入到内存。它们的作用就是为内存标记,表示从这个位置往后的一段有特殊作用的内存,而这个位置往前,禁止访问。

继续看代码

start_code:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr			/* 将cpsr状态寄存器的值读出来,保存到r0寄存器中 */
	bic	r0, r0, #0x1f		/* 清除r0寄存器的bit[4:0](即cpsr的模式域),并将结果放入r0寄存器中 */
	orr	r0, r0, #0xd3		/* orr,逻辑或,将r0寄存器中的值或11010011,并将结果存入r0寄存器,bit[4:0] = 10011(设置cpu模式),bit5(工作状态),bit[7:6](FIQ、IRQ中断禁止) */
	msr	cpsr, r0			/* 将r0寄存器中的值传回到程序状态寄存器中 */

设置cpu为管理模式,关中断完成,继续阅读代码:

	/* turn off the watchdog */
	/* S3C2440的各个寄存器地址 
	 * 这里要根据自己使用的具体cpu进行相应设置
	 */
#  define pWTCON	0x53000000	
#  define INTMSK	0x4A000008	/* Interrupt-Controller base addresses */
#  define INTSUBMSK	0x4A00001C
#  define CLKDIVN	0x4C000014	/* clock divisor register */

	ldr	r0, =pWTCON				/* 将pWTCON地址处的字(4字节)数据放入r0寄存器中 */
	mov	r1, #0x0				/* 将立即数0放入r1寄存器 */
	str	r1, [r0]				/* 将r1中的值传送到地址值为r0的内存中 */		

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]

	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]

	/* FCLK:HCLK:PCLK = 1:4:8 */
	ldr r0, =CLKDIVN
	mov r1, #0x05;			  
	str r1, [r0]

该段代码完成关看门狗,屏蔽中断,设置时钟分频系数。
继续分析代码:

bl	cpu_init_crit
cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0					/* 将立即数传入r0寄存器中 */
	mcr	p15, 0, r0, c7, c7, 0	/* 使无效整个icache和dcache */
	mcr	p15, 0, r0, c8, c7, 0	/* 使无效整个数据和指令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

  关于mcr与mrc指令的搭配可以查看该文章,写的非常清楚,在此也感谢该作者:
原文:https://blog.csdn.net/gameit/article/details/13169405
  这段代码关闭icache和dcache,并关闭MMU,并进入lowlevel_init函数对存储控制器进行初始化。

继续分析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			/* r0的值(SMRDATA地址)减r1的值(_TEXT_BASE地址),结果放入r0寄存器中 */
	ldr	r1, =BWSCON			/* Bus Width Status Controller */
	add     r2, r0, #13*4	/* r0的值加上立即数(13个寄存器*每个寄存器的4字节地址)总的地址长度传给r2寄存器 */
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 */

SMRDATA:
		.long	0x22011110		@ BWSCON
		.long	0x00000700		@ BANKCON0
		.long	0x00000700		@ BANKCON1
		.long	0x00000700		@ BANKCON2
		.long	0x00000700		@ BANKCON3	
		.long	0x00000700		@ BANKCON4
		.long	0x00000700		@ BANKCON5
		.long	0x00018005		@ BANKCON6
		.long	0x00018005		@ BANKCON7
		.long	0x008C07A3		@ REFRESH
		.long	0x000000B1		@ BANKSIZE
		.long	0x00000030		@ MRSRB6
		.long	0x00000030		@ MRSRB7

  完成对s3c2440存储控制器的各个寄存器进行设置,之后存储控制器才可以使用,并开始准备设置堆栈寄存器sp。

后面开始调用C程序,所以需要先进行栈指针sp的初始化:

#define PHYS_SDRAM_1		0x30000000 	/* s3c2440的SDRAM6的基地址为0x30000000,SDRAM Bank #1 */
#define CONFIG_SYS_SDRAM_BASE	PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR	(CONFIG_SYS_SDRAM_BASE + 0x1000 - GENERATED_GBL_DATA_SIZE)

/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)		/* sp为栈指针,0x30000f80 */
	bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */
	ldr	r0,=0x00000000
	bl	board_init_f

在反汇编u-boot.dis文件中:

00000098 <call_board_init_f>:
      98:	e59fd3d8 	ldr	sp, [pc, #984]	; 478 <fiq+0x58>
      9c:	e3cdd007 	bic	sp, sp, #7	; 0x7
      a0:	e3a00000 	mov	r0, #0	; 0x0
      a4:	eb0007f1 	bl	2070 <board_init_f>
/* 在反汇编文件中查找 478 */
	478:	30000f80 	.word	0x30000f80

  然后进入到单板功能board_init_f函数中,board_init_f函数主要是根据配置对全局信息结构体gd进行初始化:

void board_init_f(ulong bootflag)
{
	...
	/* gd是一个全局结构体 ,用来保存uboot的一些全局信息,需要单独的一块内存 
	 * 在后面的所有代码中要使用gd结构体
	 * 必须在文件中加入DECLARE_GLOBAL_DATA_PTR宏定义
	 */
	gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);	/* gd指向0x30000f80地址 */	
	
	memset((void *)gd, 0, sizeof(gd_t));	/* 用0来填充gd_t结构体大小的一块空间,指针gd就指向该块空间 */
	
	gd->mon_len = _bss_end_ofs;
	
	/* Allow the early environment to override the fdt address */
	gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
						(uintptr_t)gd->fdt_blob);
	
	/* 遍历调用函数数组init_sequence中的各个函数 */
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

init_sequence定义如下:

init_fnc_t *init_sequence[] = {
	...
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
	...
	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
	...
	dram_init,		/* configure available RAM banks */
	NULL,
};

在board_early_init_f函数中,设置系统时钟,设置GPIO端口:

int board_early_init_f(void)
{
	struct s3c24x0_clock_power * const clk_power =
					s3c24x0_get_base_clock_power();
	struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();

	/* to reduce PLL lock time, adjust the LOCKTIME register */
	writel(0xFFFFFF, &clk_power->locktime);

	/* configure MPLL */
	writel((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV,
	       &clk_power->mpllcon);

	/* some delay between MPLL and UPLL */
	pll_delay(4000);

	/* configure UPLL */
	writel((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV,
	       &clk_power->upllcon);

	/* some delay between MPLL and UPLL */
	pll_delay(8000);

	/* set up the I/O ports */
	writel(0x007FFFFF, &gpio->gpacon);
	writel(0x00044555, &gpio->gpbcon);
	writel(0x000007FF, &gpio->gpbup);
	writel(0xAAAAAAAA, &gpio->gpccon);
	writel(0x0000FFFF, &gpio->gpcup);
	writel(0xAAAAAAAA, &gpio->gpdcon);
	writel(0x0000FFFF, &gpio->gpdup);
	writel(0xAAAAAAAA, &gpio->gpecon);
	writel(0x0000FFFF, &gpio->gpeup);
	writel(0x000055AA, &gpio->gpfcon);
	writel(0x000000FF, &gpio->gpfup);
	writel(0xFF95FFBA, &gpio->gpgcon);
	writel(0x0000FFFF, &gpio->gpgup);
	writel(0x002AFAAA, &gpio->gphcon);
	writel(0x000007FF, &gpio->gphup);

	return 0;
}

初始化定时器:

int timer_init(void)
{
	struct s3c24x0_timers *timers = s3c24x0_get_base_timers();
	ulong tmr;

	/* use PWM Timer 4 because it has no output */
	/* prescaler for Timer 4 is 16 */
	writel(0x0f00, &timers->tcfg0);
	if (gd->tbu == 0) {
		/*
		 * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
		 * (default) and prescaler = 16. Should be 10390
		 * @33.25MHz and 15625 @ 50 MHz
		 */
		gd->tbu = get_PCLK() / (2 * 16 * 100);
		gd->timer_rate_hz = get_PCLK() / (2 * 16);
	}
	/* load value for 10 ms timeout */
	writel(gd->tbu, &timers->tcntb4);
	/* auto load, manual update of timer 4 */
	tmr = (readl(&timers->tcon) & ~0x0700000) | 0x0600000;
	writel(tmr, &timers->tcon);
	/* auto load, start timer 4 */
	tmr = (tmr & ~0x0700000) | 0x0500000;
	writel(tmr, &timers->tcon);
	gd->lastinc = 0;
	gd->tbl = 0;

	return 0;
}

再进行其他的一些函数初始化之后,回到board_init_f函数中:

void board_init_f(ulong bootflag)
{
	...
	debug("monitor len: %08lX\n", gd->mon_len);
	/*
	 * Ram is setup, size stored in gd !!
	 */
	debug("ramsize: %08lX\n", gd->ram_size);
	addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;	/* ram_size初始值是64M,所以addr初始值为0x34000000 */

/* 如果打开了icache和dcache,预留出16K大小的TLB */
if !(defined(CONFIG_SYS_ICACHE_OFF) &&defined(CONFIG_SYS_DCACHE_OFF))
	/* reserve TLB table */
	addr -= (4096 * 4);			/* 0x34000000 - 0x4000 = 0x33FFC000*/

	/* round down to next 64 kB limit */
	addr &= ~(0x10000 - 1);		/* 0x33ffc000 & ~(0xffff)= 0x33ff0000 */

	gd->tlb_addr = addr;
	debug("TLB table at: %08lx\n", addr);
#endif

	/* round down to next 4 kB limit */
	addr &= ~(4096 - 1);
	debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
	
	/*
	 * reserve memory for U-Boot code, data & bss
	 * round down to next 4 kB limit
	 */
	addr -= gd->mon_len;		/* 大小为697KB,gd->mon_len = 0x000ae400,adrr =  0x34ff0000,addr - gd->mon_len = 0x33f41c00 */
	addr &= ~(4096 - 1);		/* addr = (0x33f41c00 & ~(0xfff)) = 0x33f41000 */
}

其中:

gd->mon_len = _bss_end_ofs;

在start.S文件中,有定义_bss_end_ofs:

.globl _bss_start_ofs
_bss_start_ofs:
	.word __bss_start - _start

.globl _bss_end_ofs
_bss_end_ofs:
	.word __bss_end__ - _start

通过查看反汇编文件,可以知道

00000000 <__image_copy_start>:		/* _start的地址 */
00000048 <_bss_end_ofs>:
      48:	000ae4e0 	.word	0x000ae4e0	/* _bss_end__的地址 */

  预留出了一块uboot大小的内存,(0x34ff0000 - 0x33f41000) = 0xaf000 = 700KB,addr指向该块内存的起始地址0x33f41000,也是后续代码从flash中重定位过来存放的起始地址。

继续看代码:

	/*
	 * reserve memory for malloc() arena
	 */
	addr_sp = addr - TOTAL_MALLOC_LEN;	/* TOTAL_MALLOC_LEN大小为0x400000,4M*/
	debug("Reserving %dk for malloc() at: %08lx\n",
			TOTAL_MALLOC_LEN >> 10, addr_sp);
	/*
	 * (permanently) allocate a Board Info struct
	 * and a permanent copy of the "global" data
	 */
	addr_sp -= sizeof (bd_t);
	bd = (bd_t *) addr_sp;
	gd->bd = bd;
	debug("Reserving %zu Bytes for Board Info at: %08lx\n",
			sizeof (bd_t), addr_sp);
			
	addr_sp -= sizeof (gd_t);
	id = (gd_t *) addr_sp;
	debug("Reserving %zu Bytes for Global Data at: %08lx\n",
			sizeof (gd_t), addr_sp);

	/* setup stackpointer for exeptions */
	gd->irq_sp = addr_sp;
	
	/* leave 3 words for abort-stack    */
	addr_sp -= 12;

	/* 8-byte alignment for ABI compliance */
	addr_sp &= ~0x07;
	......
}

首先预留malloc len,这里是0x400000.

注释中说明,为bd,gd做一个永久的copy。

留出了全局信息bd_t结构体的空间,首地址存在gd->bd。

留出gd_t结构体的空间。首地址存在id中。

将此地址保存在gd->irq_sp中作为异常栈指针。uboot中我们没有用到中断。

最后留出12字节,for abort stack,这个没看懂。

到这里addr_sp值确定,总结一下addr_sp之上空间分配。

由高到低 :
  addr–>malloc len(0x400000)–>bd len–>gd len–>12 byte–>addr_sp(栈往下增长,addr_sp之下空间作为栈空间)。

最后在board_init_f函数中:

	...
	gd->bd->bi_baudrate = gd->baudrate;
	
	/* Ram ist board specific, so move it to board code ... */
	
	dram_init_banksize();
	
	display_dram_config();	/* and display it */

	gd->relocaddr = addr;		/* 目标addr */
	
	gd->start_addr_sp = addr_sp;		/* 目标addr_sp */
	
	gd->reloc_off = addr - _TEXT_BASE;	/* 目标addr和现在实际code起始地址的偏移 */
	
	debug("relocation Offset is: %08lx\n", gd->reloc_off);
	
	memcpy(id, (void *)gd, sizeof(gd_t));

	relocate_code(addr_sp, id, addr);	/* r0 = addr_sp,r1 = id, r2 = addr */

	/* NOTREACHED - relocate_code() does not return */
}

  给bd->bi_baudrate赋值gd->baudrate,gd->baudrate是在前面baudrate_init中初始化。
  dram_init_banksize()是需要实现的板级函数。根据板上ddrc获取ddr的bank信息。填充在gd->bd->bi_dram[CONFIG_NR_DRAM_BANKS]。

最后将gd结构体的数据拷贝到新的地址id上。

至此,board_init_f函数结束,回到start.S中进行代码的重定位操作。

代码重定向部分单独写了一篇文章:
u-boot的relocate_code部分分析

查看反汇编文件得到:

00000044 <_bss_start_ofs>:
      44:	0006b568 	.word	0x0006b568

00000048 <_bss_end_ofs>:
      48:	000ae4e0 	.word	0x000ae4e0

清除bss段:

clear_bss:
	ldr	r0, _bss_start_ofs	/* r0 = 0x0006b568 */
	ldr	r1, _bss_end_ofs	/* r1 = 0x000ae4e0 */
	mov	r4, r6				/* 将目标地址0x33f41000传入r4寄存器 */
	add	r0, r0, r4			/* r0 = 0x0006b568 + 0x33f41000 = 0x33fac568 */
	add	r1, r1, r4			/* r1 = 0x000ae4e0 + 0x33f41000 = 0x33fef4e0 */
	mov	r2, #0x00000000		/* 由代码重定向得到r2 = 0x00073608,将立即数0传入该地址 */
	
clbss_l:str	r2, [r0]		/* clear loop...		    */
	add	r0, r0, #4
	cmp	r0, r1
	bne	clbss_l

	bl coloured_LED_init
	bl red_led_on

  接下来分别调用了coloured_LED_init以及red_led_on,很多开发板都会有led指示灯,这里可以实现上电指示灯亮,有调试作用。

到这里,第一阶段已经完成,跳到第二阶段入口处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值