IMX6Q u-boot启动流程分析

文章目录

  • u-boot第一阶段
    • 中断向量
    • reset复位向量代码
  • uboot第二阶段
    • 代码与中断向量重定位
    • 代码重定位过程
    • board_init_r函数
    • 启动Linux内核

u-boot第三篇,该介绍uboot在imx6q芯片上的启动流程了;网上介绍uboot启动流程的文章很多,因此我这里只记录代码的执行流程,不详细分析代码的细节。

u-boot第一阶段

中断向量

中断向量在arch/arm/lib/vector.S中被定义,imx6去是ARMV7版本。
第一句汇编代码,就是复位向量,跳转到reset出执行代码

	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

reset复位向量代码

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

/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
	/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
	mrc	p15, 0, r0, c1, c0, 0	@ Read CP15 SCTLR Register
	bic	r0, #CR_V		@ V = 0
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTLR Register

	/* Set vector address in CP15 VBAR register */
	ldr	r0, =_start
	mcr	p15, 0, r0, c12, c0, 0	@Set VBAR
#endif

	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_cp15
	bl	cpu_init_crit
#endif

	bl	_main

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)
  • 在start.S中,reset代码首先进入SVC模式、关闭中断,然后执行cpu_init_cp15、cpu_init_crit;
  • cpu_init_cp15:在start.S中定义
  • cpu_init_crit: 在arch\arm\cpu\armv7\ lowlevel_init.S中定义
  • cpu_init_crit的执行,会返回到start.S中执行bl _main,此处也就是第二阶段_main在arch\arm\lib\crt0.S中定义

uboot第二阶段

  • 在_main汇编代码中首先初始化SP栈指针,做8字节对齐,此时的栈位于内部RAM空间0x00900000处
  • 然后为gd全局变量保留256字节 + CONFIG_SYS_MALLOC_F 宏定义的的空间,由函数board_init_f_alloc_reserve完成
  • 由函数board_init_f_init_reserve完成board_init_f函数需要使用的CONFIG_SYS_MALLOC_F空间准备
  • 在board_init_f函数中调用initcall_run_list(init_sequence_f),完成初始化序列函数的执行
  • board_init_f函数返回后,调整gd全局变量,然后调用relocate_code与relocate_vectors进行代码重定位与中断向量重定位
ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
	ldr	sp, =(CONFIG_SPL_STACK)
#else
	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M)	/* v7M forbids using SP as BIC destination */
	mov	r3, sp
	bic	r3, r3, #7
	mov	sp, r3
#else
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
#endif
	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

	mov	r0, #0
	bl	board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
 * 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 */
#if defined(CONFIG_CPU_V7M)	/* v7M forbids using SP as BIC destination */
	mov	r3, sp
	bic	r3, r3, #7
	mov	sp, r3
#else
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */
#endif
	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
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				/* As required by Thumb-only */
#endif
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */
	b	relocate_code
here:
/*
 * now relocate vectors
 */

	bl	relocate_vectors

/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
	/* Use a DRAM stack for the rest of SPL, if requested */
	bl	spl_relocate_stack_gd
	cmp	r0, #0
	movne	sp, r0
	movne	r9, r0
# endif
	ldr	r0, =__bss_start	/* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
	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
#else
	ldr	r1, =__bss_end		/* this is auto-relocated! */
	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l:cmp	r0, r1			/* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
	itt	lo
#endif
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l
#endif

#if ! defined(CONFIG_SPL_BUILD)
	bl coloured_LED_init
	bl red_led_on
#endif
	/* 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 */
#if defined(CONFIG_SYS_THUMB_BUILD)
	ldr	lr, =board_init_r	/* this is auto-relocated! */
	bx	lr
#else
	ldr	pc, =board_init_r	/* this is auto-relocated! */
#endif
	/* we should not return here. */
#endif

ENDPROC(_main)

代码与中断向量重定位

上一篇关于uboot重定位的想法有错,实际还是发生了重定位,虽然uboot被imx6q内的ROMBOOT代码读到的链接地址0x17800000,但是还是发生的重定位,并且定位到8ff23000地址,
uboot代码增加debug提示信息后如下:

Reserving 819k for U-Boot at: 8ff23000
......
Relocation Offset is: 78723000
Relocating to 8ff23000, new gd at 8ef20eb8, sp at 8ef20e90

为什么要这样呢,我估计是为了远离Linux的加载地址0x12000000。
至于是怎么完成重定位过程的,这里我就不再详细描述,网上有很多介绍的文章:uboot代码重定位
这里我需要描述的是,uboot的重定位地址是怎么来的

  • 在setup_mon_len函数中设置:gd->mon_len = (ulong)&__bss_end - (ulong)_start = 0x000CCFD8
  • 在dram_init函数中设置:gd->ram_size = get_ram_size((void *)CONFIG_SYS_SDRAM_BASE, CONFIG_MAX_RAM_BANK_SIZE) = 2G;
  • 在setup_dest_addr函数中设置重定位初始地址:gd->relocaddr = gd->ram_top = 0x90000000;
  • 在reserve_mmu函数中保留TLB,预留64K空间,实际使用16K空间,此时gd->relocaddr = 0x8FFF0000
  • 在reserve_uboot函数中设置:gd->relocaddr -= gd->mon_len = 0x8FFF0000-0x000CCFD8 = 0x8FF23028;并进行4K对齐,最后gd->relocaddr = 0x8FF23000
  • 在setup_reloc函数完成代码重定位前的参数计算:gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE = 0x78723000

代码重定位过程

在执行board_init_f函数中调用initcall_run_list(init_sequence_f)的最后一个函数时,函数指针为NULL,返回值为0,这时继续回到_main汇编代码。
在执行relocate_code函数之前我们先来关注_main汇编代码以下5句

  • 第一句:转呗here返回地址,此时here的值仍处于重定位之前的地址段
  • 第二句:获取重定位的地址偏移量
  • 第三句:将重定位的地址偏移量加到lr,函数执行返回地址寄存器,此时lr中存储的here地址变为重定位后的地址段
  • 第四句:准备执行relocate_code函数需要的参数;即重定位地址放到r0寄存器
  • 第五句:执行代码重定位,当此函数返回时,使用lr寄存器,因此返回到重定位后的地址段here
	adr	lr, here
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */
	add	lr, lr, r0
	...
	ldr	r0, [r9, #GD_RELOCADDR]
	b	relocate_code
here:
	bl	relocate_vectors

在完成重定位过程后,继续bss段的清零工作,然后执行board_init_r函数,此函数不在返回。

board_init_r函数

board_init_r函数目录:\u-boot-2016.03\common\board_r.c。
在board_init_r函数中调用initcall_run_list(init_sequence_r),进行初始化序列,在序列的最后一个函数是run_main_loop,进行死循环阶段。
run_main_loop函数调用\u-boot-2016.03-r0\common\main.c的main_loop函数
其中在init_sequence_r序列函数中的第二个函数initr_reloc,用于向重定位后的代码中全局变量gd->flag设置gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT标志位,用于指示现处于重定位地址段执行uboot函数,这样在每次循环执行函数前都会提示重定位信息;例如:initcall: 1781287c (relocated to 8ff3587c)

init_sequence_r序列中的函数主要是板级 的初始化函数,与具体的硬件相关;至于这些函数的介绍也许会另写一篇文章介绍,这里我们主要关注是如何启动Linux内核的

启动Linux内核

  • 在main_loop函数中会调用bootdelay_process函数;
  • 此函数获取启动linux内核之前的延时boot_delay与启动Linux内核的环境变量bootcmd
  • 然后执行autoboot_command函数,此函数会在boot_delay延时之后,执行上一步获取的bootcmd环境变量
  • bootcmd="run findfdt;mmc dev ${mmcdev};if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi"
  • bootcmd环境变量是一个shell脚本,需要执行一系列命令,由run_command_list函数完成。
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifndef CONFIG_SYS_GENERIC_BOARD
	puts("Warning: Your board does not use generic board. Please read\n");
	puts("doc/README.generic-board and take action. Boards not\n");
	puts("upgraded by the late 2014 may break or be removed.\n");
#endif

#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();

	run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
	update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();
}
const char *bootdelay_process(void)
{
	char *s;
	int bootdelay;
#ifdef CONFIG_BOOTCOUNT_LIMIT
	unsigned long bootcount = 0;
	unsigned long bootlimit = 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#ifdef CONFIG_BOOTCOUNT_LIMIT
	bootcount = bootcount_load();
	bootcount++;
	bootcount_store(bootcount);
	setenv_ulong("bootcount", bootcount);
	bootlimit = getenv_ulong("bootlimit", 10, 0);
#endif /* CONFIG_BOOTCOUNT_LIMIT */

	s = getenv("bootdelay");
	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

#ifdef is_boot_from_usb
	if (is_boot_from_usb()) {
		disconnect_from_pc();
		printf("Boot from USB for mfgtools\n");
		bootdelay = 0;
		set_default_env("Use default environment for \
				 mfgtools\n");
	} else {
		printf("Normal Boot\n");
	}
#endif

#ifdef CONFIG_OF_CONTROL
	bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
			bootdelay);
#endif

	debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);

#if defined(CONFIG_MENU_SHOW)
	bootdelay = menu_show(bootdelay);
#endif
	bootretry_init_cmd_timeout();

#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");

#ifdef is_boot_from_usb
	if (is_boot_from_usb()) {
		s = getenv("bootcmd_mfg");
		printf("Run bootcmd_mfg: %s\n", s);
	}
#endif

	process_fdt_options(gd->fdt_blob);
	stored_bootdelay = bootdelay;

	return s;
}
void autoboot_command(const char *s)
{
	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "");

	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		int prev = disable_ctrlc(1);	/* disable Control C checking */
#endif

		run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		disable_ctrlc(prev);	/* restore Control C checking */
#endif
	}

#ifdef CONFIG_MENUKEY
	if (menukey == CONFIG_MENUKEY) {
		s = getenv("menucmd");
		if (s)
			run_command_list(s, -1, 0);
	}
#endif /* CONFIG_MENUKEY */
}

关于uboot命令实现与执行的特点需要另写一篇文章来记录,网上也有很多关于uboot命令实现文章,本片文章就先记录到这里吧。

你可能感兴趣的:(嵌入式,Uboot)