欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-08 22:10:21
...

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

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

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

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


    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)

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

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

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

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


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)

可知此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(aaa@qq.com): remove */
# ifdef CONFIG_OF_CONTROL
	find_fdt,		/* TODO(aaa@qq.com): 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拷贝到内存中运行。