02 uboot分析之源码

uboot要做的工作

  • 关看门狗
  • 初始化时钟
  • 初始化SDRAM
  • 将程序从nand flash拷贝到SDRAM
  • 设置栈

第一阶段源码分析

第一步,跳转到reset

.globl _start
_start:	b       reset

reset做了些啥,注释说设置cpu为SVC32模式

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
	bic	r0,r0,#0x1f
	orr	r0,r0,#0xd3
	msr	cpsr,r0

这里首先要介绍ARM架构的7种CPU模式

处理器模式 说明 备注
用户(usr) 正常程序工作模式 此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换
系统(sys) 用于支持操作系统的特权任务等 与用户模式类似,但具有可以直接切换到其它模式等特权
快中断(fiq) 支持高速数据传输及通道处理 FIQ异常响应时进入此模式
中断(irq) 用于通用中断处理 IRQ异常响应时进入此模式
管理(svc) 操作系统保护代码 系统复位和软件中断响应时进入此模式
中止(abt) 用于支持虚拟内存和/或存储器保护 在ARM7TDMI没有大用处
未定义(und) 支持硬件协处理器的软件仿真 未定义指令异常响应时进入此模式

SVC模式又称超级用户模式,比SYS能控制的硬件更多,允许用户进一步控制计算机,所以初始化为SVC模式是更有利于进行系统的初始化工作的

下一步是关看门狗

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON		0x53000000
# define INTMOD     0X4A000004
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif

然后是关中断

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]
# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif

#if 0
	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
#endif
#endif	/* CONFIG_S3C2400 || CONFIG_S3C2410 */

进行CPU的关键初始化,这里面进行了一个比较,判断程序是从内部的4K RAM中启动,还是从SDRAM中指定的地址启动,只有当程序从SDRAM中启动时,才进行CPU的关键初始化

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	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         */
	blne	cpu_init_crit
#endif

接下来分析cpu_init_crit

  • 第一步:flush v4 I/D caches,清cache
  • 第二步:disable MMU stuff and caches,关闭mmu和caches,为什么要关掉caches,caches是CPU内部的一个2级缓存,它的作用是将常用的数据和指令放在CPU内部。Caches是通过CP15管理的,刚上电的时候,CPU还不能管理Caches。上电的时候指令Cache可关闭,也可不关闭,但数据Cache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从Cache里面取,而这时候RAM中数据还没有Cache过来,导致数据预取异常
  • 第三步:调用lowlevel_init,初始化存储控制器
#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 */

下一步设置栈指针,从TEXT_BASE,往下留下了一些空间做其它用途后,确定了sp寄存器的值

/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_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    */

初始化时钟,老韦自己魔改官方的代码

/* 设置时钟, 使用汇编 */
#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))
#define S3C2440_UPLL_48MHZ      ((0x38<<12)|(0x02<<4)|(0x02))
#define S3C2440_CLKDIV          (0x05) // | (1<<3))    /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL/2 */

	ldr r1, =CLKDIVN
	mov r2, #S3C2440_CLKDIV
	str r2, [r1]

	mrc p15, 0, r1, c1, c0, 0		// read ctrl register 
	orr r1, r1, #0xc0000000 		// Asynchronous	
	mcr p15, 0, r1, c1, c0, 0		// write ctrl register

    ldr r0,=LOCKTIME
    ldr r1,=0xffffff
    str r1,[r0]
    // delay
    mov     r0, #0x200
1:  subs    r0, r0, #1
    bne     1b

    // Configure MPLL
    ldr r0,=MPLLCON          
    ldr r1,=S3C2440_MPLL_400MHZ
    str r1,[r0]
    // delay
    mov     r0, #0x200
1:  subs    r0, r0, #1
    bne     1b

    //Configure UPLL
    ldr     r0, =UPLLCON          
    ldr     r1, =S3C2440_UPLL_48MHZ
    str     r1, [r0]
    // delay
    mov     r0, #0x200
1:  subs    r0, r0, #1
    bne     1b

最后,将代码从nand flash搬运到SDRAM中,代码的重定位

扫描二维码关注公众号,回复: 11256160 查看本文章
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     clear_bss
	
	ldr	r2, _armboot_start
	ldr	r3, _bss_start
	sub	r2, r3, r2		/* r2 <- size of armboot            */
#if 1
	bl  CopyCode2Ram	/* r0: source, r1: dest, r2: size */

然后清除bss段,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

最后一步,调用c函数,进入第二阶段

	ldr	pc, _start_armboot

_start_armboot:	.word start_armboot

uboot第一阶段启动的总结

  • 设置CPU为SVC模式
  • 关看门狗
  • 屏蔽中断
  • 初始化SDRAM
  • 设置栈
  • 初始化时钟
  • 代码从FLASH拷到SDRAM内存
  • 清空bss段
  • 进入start_armboot程序

uboot第二阶段代码分析

第二阶段都是用C语言编写的代码,从start_armboot开始阅读
以下这个for循环,会调用一些init_sequence内的函数指针完成各种初始化

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)

这就是要进行的初始化操作,大致了解即可,不深究

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
	board_init,		/* basic board dependent setup */
	interrupt_init,		/* set up exceptions */
	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
	dram_init,		/* configure available RAM banks */
	display_dram_config,
	NULL,
};

为了从nor flash和nand flash读出内核,所以必须对flash进行初始化,使uboot有读flash的功能,flash_init完成了对nor flash的初始化

size = flash_init ();

对于nand flash的初始化,是nand_init

nand_init();		/* go init the NAND */

接下来看一下环境变量的初始化,环境变量首先来自于默认的,如果flash中保存的有环境变量,则使用flash的环境变量

/* initialize environment */
	env_relocate ();

最后uboot进入一个循环

for (;;) {
		main_loop ();
	}

这个主循环中,有一段对倒数计时的判断,如果时间到了,就启动内核,其中启动的参数bootcmd来自于环境变量
bootcmd就两个参数,从nand flash读取内核,放到什么位置,如何启动内核

s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    printf("Booting Linux ...\n");            
    run_command (s, 0);
}

如果不启动内核,则循环检测串口的数据,然后根据你输入的命令,执行相应的操作

len = readline (CFG_PROMPT);
rc = run_command (lastcommand, flag);

可以看出,uboot的核心其实就是这个run_command函数

猜你喜欢

转载自blog.csdn.net/whitefish520/article/details/106301119
今日推荐