系统启动过程(基于三星s5p6818 uboot)

    总结下手中开发板的启动过程,uboot版本为2014.07,参考S5P6818 Application Processor User's Manual。

    首先,我们应该知道为什么需要uboot,而不是只知其然,不知其所以然。一言以概之:在我们能够在操作系统下运行程序之前,所有的努力都是为了能够让系统能够被搬运到内存中运行。有了这样的目的性,我们分析理解起来就简单多了。

    其实,高端的ARM芯片并不和STM32等Cortex-M系的ARM芯片相差很多,只是主频更高,核心增多,外设更丰富了。附图吧:



    IROM:

    和大部分ARM芯片一样,S5P6818内部也自带了RAM和ROM,分别为64K和20K。若官方提供下载器的话,我们也可以像STM32一样,让代码在ROM中运行,RAM中保存堆栈变量等信息,但那就大材小用了。显然,为了能够跑起linux,光是芯片内的这点空间是完全不够的,好在高端芯片强就强在能够带动大容量的外部RAM和ROM。所以我们一般会将系统存放在外部的SD卡或EMMC中。所以我们此时我们的目的也很明确,检查外部的存储中是否包含合法的内容。

    因此,在系统上电后,芯片首先会运行片内20KB ROM中的代码,此ROM一般为NOR FLASH。相对于外部存储经常使用的NAND FLASH,其优势在于代码可以芯片内运行,所以不需要像NAND FLASH一样将代码搬运到内存中。内部ROM地址一般即为0地址处,其作用是检测引脚的配置情况,确定外部存储器启动的优先级,选择对应的外部存储进行初始化,再将其中的前56K代码搬运到内部RAM中(因内部RAM只有64KB,且ROM自身会用掉一部分RAM),当搬运了512字节之后会进行Boot Header的检查,查看签名是否为0x4849534E。若是,则为正确代码,跳转到内部RAM即地址0xFFFF0000处运行;否则,选择下一个外部存储器进行判断测试。系统启动过程(基于三星s5p6818 uboot)_第1张图片

    如图,若想系统首先从SDMMC启动,则须将RST_CFG[2:0]这三位设置为5,这三位对应于原理图为SD2,SD1和SD0,从原理图可以看出我们确实是这样设置了。

系统启动过程(基于三星s5p6818 uboot)_第2张图片

    之后还可以通过SD3和CAM1_D3进行从哪一个SDMMC进行启动,共有三个端口可选。接下来就是从SD卡中复制代码到内部RAM中并跳转运行了,如图。

系统启动过程(基于三星s5p6818 uboot)_第3张图片


2ndboot:

    现在的状态是外部存储器可以读取使用了,但外部的大容量SDRAM还是不能使用,而我们最终的目的是要将代码放入到SDRAM中运行的,这里讲讲SRAM和DRAM的区别。S和D分别代表静态和动态刷新,SRAM在上电后只要不断电数据就一直存在,而DRAM需要在上电后过一段时间进行充电刷新,这样数据才能够保持不变,虽然相比SRAN变麻烦了,但价格降低,容量却提升了。这就是S5P6818内部采用小容量SRAM,外面挂接DDR3这样的SDRAM的原因,这里的S为串行的意思。所以现在很明确加载到内部SRAM中代码所需要做的事了:1、初始化芯片的内存控制器 2、初始化外部SDRAM 3、将SD卡中uboot搬运到内存中运行。

    当然除此之外,还有包括设置中断向量表,设置PLL,设置堆栈,初始化串口等,这就是s5p6818提供的2ndboot的内容,烧写在SD卡的block1~block64共32K字节的位置。

    以往这部分内容也有是在uboot中进行完成的,在接下来的uboot讲解中会有提到,把这部分初始化的内容提炼提炼出来的好处就是减少了uboot的自搬运过程。


uboot:

    2ndboot最后的动作便是将uboot从SD卡的第65块block搬运到内存0x42C00000的位置,接下来便会跳转到外部SDRAM进行uboot的运行。

    首先查看生成的uboot链接文件uboot.lds:

系统启动过程(基于三星s5p6818 uboot)_第4张图片

可知此uboot程序代码起始的运行位置为arch/arm/cpuslsiap/s5p6818/start.o的_stext处。

/*
 *************************************************************************
 *
 * Exception vectors as described in ARM reference manuals
 *
 * replace arm/lib/vectors.S
 *
 *************************************************************************
 */
	.globl	_stext
_stext:
	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

_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

首先定义_stext为一个全局标号,接下来就是一句跳转,跳转到reset标号处运行,下面的为异常处理,当系统反生异常时,会跳转到对应的地址运行,最后一句用一个魔数强制了16字节对齐。

    

/*
 *************************************************************************
 *
 * Reset handling
 *
 *************************************************************************
 */

	.globl reset

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

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

    reset首先会将当前的模式设置为SVC32管理模式,接下来是两个函数cpu_init_cp15和cpu_init_crit,第一个函数设置了ARM中重要的寄存器cp15协处理器,通过这个寄存器我们便可对TLB(快表),I-cache(指令缓存),D-cache(数据缓存),MMU(内存管理单元)等进行使能和失能。而cpu_init_crit则会调用lowlevel_init.S中的lowlevel_init函数。以往若没有2ndboot,则会在此函数中进行pll,外部SDRAM等的初始化。因为这部分我们已经在2ndboot中做了,所以此处只是进行了简单的cpu型号读取和设置多核SMP等,之后便返回到start.S继续运行。

     接下来是一个自搬运的函数,r0指向uboot运行的地址,r1指向需要搬运到的内存地址为0x42C00000。若此时uboot运行在flash中则会将uboot搬运到0x42C00000处运行,否则跳过搬运过程,将BSS段中的数据清零,设置栈的位置和uboot中大名鼎鼎的gd位置,之后便会跳转到common/board_f.c中的board_init_f函数运行。

#ifdef CONFIG_RELOC_TO_TEXT_BASE
relocate_to_text:
	/*
	 * relocate u-boot code on memory to text base
	 * for nexell arm core (add by jhkim)
	 */
	adr	r0, _stext				/* 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, _bss_start_ofs
	add	r2, r0, r2				/* r2 <- source end address         */

copy_loop_text:
	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_text

	ldr	r1, TEXT_BASE			/* restart at text base */
	mov pc, r1

设置栈和gd位置:

	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)
	bic	sp, sp, #7					/* 8-byte alignment for ABI compliance */
	sub	sp, #GD_SIZE				/* allocate one GD above SP */
	bic	sp, sp, #7					/* 8-byte alignment for ABI compliance */
	mov	r9, sp						/* GD is above SP */
	mov	r0, #0
	bl	board_init_f


board_init_f函数:

    执行前置的(front)初始化工作,若此时全局变量未能使用,则通过定义局部变量,在栈中创建并设置gd:

#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
	/*
	 * For some archtectures, global data is initialized and used before
	 * calling this function. The data should be preserved. For others,
	 * CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
	 * here to host global data until relocation.
	 */
	gd_t data;

	gd = &data;

	/*
	 * Clear global data before it is accessed at debug print
	 * in initcall_run_list. Otherwise the debug print probably
	 * get the wrong vaule of gd->have_console.
	 */
	zero_global_data();
#endif

    调用各个初始化函数,初始化串口,输出uboot,CPU,RAM等信息,初始化全局变量区:

static init_fnc_t init_sequence_f[] = {
#ifdef CONFIG_SANDBOX
	setup_ram_buf,
#endif
	setup_mon_len,
	setup_fdt,
	trace_early_init,
#if defined(CONFIG_MPC85xx) || defined(CONFIG_MPC86xx)
	/* TODO: can this go into arch_cpu_init()? */
	probecpu,
#endif
	arch_cpu_init,		/* basic arch cpu dependent setup */
#ifdef CONFIG_X86
	cpu_init_f,		/* TODO([email protected]): remove */
# ifdef CONFIG_OF_CONTROL
	find_fdt,		/* TODO([email protected]): remove */
# endif
#endif
	mark_bootstage,
#ifdef CONFIG_OF_CONTROL
	fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
	/* TODO: can any of this go into arch_cpu_init()? */
#if defined(CONFIG_PPC) && !defined(CONFIG_8xx_CPUCLK_DEFAULT)
	get_clocks,		/* get CPU and bus clocks (etc.) */
#if defined(CONFIG_TQM8xxL) && !defined(CONFIG_TQM866M) \
		&& !defined(CONFIG_TQM885D)
	adjust_sdram_tbs_8xx,
#endif
	/* TODO: can we rename this to timer_init()? */
	init_timebase,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS)
	timer_init,		/* initialize timer */
#endif
#ifdef CONFIG_SYS_ALLOC_DPRAM
#if !defined(CONFIG_CPM2)
	dpram_init,
#endif
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
	board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
	get_clocks,
#endif
	env_init,		/* initialize environment */
#if defined(CONFIG_8xx_CPUCLK_DEFAULT)
	/* get CPU and bus clocks according to the environment variable */
	get_clocks_866,
	/* adjust sdram refresh rate according to the new clock */
	sdram_adjust_866,
	init_timebase,
#endif
	init_baud_rate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
#ifdef CONFIG_SANDBOX
	sandbox_early_getopt_check,
#endif
#ifdef CONFIG_OF_CONTROL
	fdtdec_prepare_fdt,
#endif
	display_options,	/* say that we are here */
	display_text_info,	/* show debugging info if required */
#if defined(CONFIG_MPC8260)
	prt_8260_rsr,
	prt_8260_clks,
#endif /* CONFIG_MPC8260 */
#if defined(CONFIG_MPC83xx)
	prt_83xx_rsr,
#endif
#ifdef CONFIG_PPC
	checkcpu,
#endif
	print_cpuinfo,		/* display cpu info (and speed) */
#if defined(CONFIG_MPC5xxx)
	prt_mpc5xxx_clks,
#endif /* CONFIG_MPC5xxx */
#if defined(CONFIG_DISPLAY_BOARDINFO)
	checkboard,		/* display board info */
#endif
	INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
	misc_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
	init_func_i2c,
#endif
#if defined(CONFIG_HARD_SPI)
	init_func_spi,
#endif
#ifdef CONFIG_X86
	dram_init_f,		/* configure available RAM banks */
	calculate_relocation_address,
#endif
	announce_dram_init,
	/* TODO: unify all these dram functions? */
#ifdef CONFIG_ARM
	dram_init,		/* configure available RAM banks */
#endif
#if defined(CONFIG_MIPS) || defined(CONFIG_PPC)
	init_func_ram,
#endif
#ifdef CONFIG_POST
	post_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
	testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
	init_post,
#endif
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Now that we have DRAM mapped and working, we can
	 * relocate the code and continue running from DRAM.
	 *
	 * Reserve memory at end of RAM for (top down in that order):
	 *  - area that won't get touched by U-Boot and Linux (optional)
	 *  - kernel log buffer
	 *  - protected RAM
	 *  - LCD framebuffer
	 *  - monitor code
	 *  - board info struct
	 */
	setup_dest_addr,
#if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)
	reserve_logbuffer,
#endif
#ifdef CONFIG_PRAM
	reserve_pram,
#endif
	reserve_round_4k,
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
		defined(CONFIG_ARM)
	reserve_mmu,
#endif
#ifdef CONFIG_LCD
	reserve_lcd,
#endif
	reserve_trace,
	/* TODO: Why the dependency on CONFIG_8xx? */
#if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) \
		&& !defined(CONFIG_ARM) && !defined(CONFIG_X86)
	reserve_video,
#endif
	reserve_uboot,
#ifndef CONFIG_SPL_BUILD
	reserve_malloc,
	reserve_board,
#endif
	setup_machine,
	reserve_global_data,
	reserve_fdt,
	reserve_stacks,
	setup_dram_config,
	show_dram_config,
#ifdef CONFIG_PPC
	setup_board_part1,
	INIT_FUNC_WATCHDOG_RESET
	setup_board_part2,
#endif
	display_new_sp,
#ifdef CONFIG_SYS_EXTBDINFO
	setup_board_extra,
#endif
	INIT_FUNC_WATCHDOG_RESET
	reloc_fdt,
	setup_reloc,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
	jump_to_copy,
#endif
	NULL,
};
if (initcall_run_list(init_sequence_f))
		hang();

    当然这里也会有dram_init对外部DRAM进行初始化,若没有2ndboot的话,在这里用户可以实现dram_init来对外部DRAM初始化,再跳回start.S进行relocation操作,将u-boot自搬运到DRAM中。board_init_f之后又会返回到start.S,调用gdt_reset函数,参数为新的栈地址和gd地址,更改gdt中对应的地址内容。接下来便会调用到board_init_r进行后置的板级初始化。


board_init_r函数:

    /common/board_r.c进行后置的(rear)板级初始化工作,在运行了之前board_init_f后,全局变量区,BSS段,堆栈等C的运行环境才建立起来。这里使用和board_init_f同样的方法定义一个函数指针数组,依次遍历其中的元素,进行各项初始化,包括使能cache,初始化malloc,重新初始化串口,使能中断,外设初始化(LED,LCD),网络初始化等,最终调用run_main_loop,进入到uboot的命令行。

init_fnc_t init_sequence_r[] = {
	initr_trace,
	initr_reloc,
	/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
	initr_caches,
	board_init,	/* Setup chipselects */
#endif
	/*
	 * TODO: printing of the clock inforamtion of the board is now
	 * implemented as part of bdinfo command. Currently only support for
	 * davinci SOC's is added. Remove this check once all the board
	 * implement this.
	 */
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info, /* Setup clock information */
#endif
	initr_reloc_global_data,
	initr_serial,
	initr_announce,
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_PPC
	initr_trap,
#endif
#ifdef CONFIG_ADDR_MAP
	initr_addr_map,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_R)
	board_early_init_r,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_LOGBUFFER
	initr_logbuffer,
#endif
#ifdef CONFIG_POST
	initr_post_backlog,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_SYS_DELAYED_ICACHE
	initr_icache_enable,
#endif
#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
	initr_unlock_ram_in_cache,
#endif
#if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do early PCI configuration _before_ the flash gets initialised,
	 * because PCU ressources are crucial for flash access on some boards.
	 */
	initr_pci,
#endif
#ifdef CONFIG_WINBOND_83C553
	initr_w83c553f,
#endif
	initr_barrier,
	initr_malloc,
	bootstage_relocate,
#ifdef CONFIG_DM
	initr_dm,
#endif
#ifdef CONFIG_ARCH_EARLY_INIT_R
	arch_early_init_r,
#endif
	power_init_board,
#ifndef CONFIG_SYS_NO_FLASH
	initr_flash,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PPC) || defined(CONFIG_X86)
	/* initialize higher level parts of CPU like time base and timers */
	cpu_init_r,
#endif
#ifdef CONFIG_PPC
	initr_spi,
#endif
#if defined(CONFIG_X86) && defined(CONFIG_SPI)
	init_func_spi,
#endif
#ifdef CONFIG_CMD_NAND
	initr_nand,
#endif
#ifdef CONFIG_CMD_ONENAND
	initr_onenand,
#endif
#ifdef CONFIG_GENERIC_MMC
	initr_mmc,
#endif
#ifdef CONFIG_HAS_DATAFLASH
	initr_dataflash,
#endif
	initr_env,
	INIT_FUNC_WATCHDOG_RESET
	initr_secondary_cpu,
#ifdef CONFIG_SC3
	initr_sc3_read_eeprom,
#endif
#ifdef	CONFIG_HERMES
	initr_hermes,
#endif
#if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET)
	mac_read_from_eeprom,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do pci configuration
	 */
	initr_pci,
#endif
	stdio_init,
	initr_jumptable,
#ifdef CONFIG_API
	initr_api,
#endif
	console_init_r,		/* fully init console as a device */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
	show_model_r,
#endif
#ifdef CONFIG_ARCH_MISC_INIT
	arch_misc_init,		/* miscellaneous arch-dependent init */
#endif
#ifdef CONFIG_MISC_INIT_R
	misc_init_r,		/* miscellaneous platform-dependent init */
#endif
#ifdef CONFIG_HERMES
	initr_hermes_start,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
	initr_kgdb,
#endif
#ifdef CONFIG_X86
	board_early_init_r,
#endif
	interrupt_init,
#if defined(CONFIG_ARM) || defined(CONFIG_x86)
	initr_enable_interrupts,
#endif
#ifdef CONFIG_X86
	timer_init,		/* initialize timer */
#endif
#if defined(CONFIG_STATUS_LED) && defined(STATUS_LED_BOOT)
	initr_status_led,
#endif
	/* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
	initr_ethaddr,
#endif
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init,
#endif
#ifdef CONFIG_CMD_SCSI
	INIT_FUNC_WATCHDOG_RESET
	initr_scsi,
#endif
#ifdef CONFIG_CMD_DOC
	INIT_FUNC_WATCHDOG_RESET
	initr_doc,
#endif
#ifdef CONFIG_BITBANGMII
	initr_bbmii,
#endif
#ifdef CONFIG_CMD_NET
	INIT_FUNC_WATCHDOG_RESET
	initr_net,
#endif
#ifdef CONFIG_POST
	initr_post,
#endif
#if defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_CMD_IDE)
	initr_pcmcia,
#endif
#if defined(CONFIG_CMD_IDE)
	initr_ide,
#endif
#ifdef CONFIG_LAST_STAGE_INIT
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Some parts can be only initialized if all others (like
	 * Interrupts) are up and running (i.e. the PC-style ISA
	 * keyboard).
	 */
	last_stage_init,
#endif
#ifdef CONFIG_CMD_BEDBUG
	INIT_FUNC_WATCHDOG_RESET
	initr_bedbug,
#endif
#if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
	initr_mem,
#endif
#ifdef CONFIG_PS2KBD
	initr_kbd,
#endif
	run_main_loop,
};

    此后若未在bootdelay时间内按下任意键,则会调用bootm或者后来的booti将内核、文件系统、设备树等拷贝到内存中,并执行相应的操作。自此,linux便在内存中跑起来了。

总结:

    uboot将程序代码分成芯片级和板级,分别进行操作。芯片级最重要的莫过于start.S和lowlevel_init.S,其中包含关键的cp15协处理器设置,SDRAM初始化,以后uboot的重定位,这些需要根据特定的芯片进行设置。板级则是更多的和板型相关的部分,通过board_init_f和board_init_r两个板级的初始化接口,用户可以实现其中的包括串口、SDRAM、LED等外设的初始化,最终将linux拷贝到内存中运行。



你可能感兴趣的:(uboot)