【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)

U-Boot

U-Boot的版本选择

U-Boot的下载地址:/pub/u-boot的索引

进入到U-Boot的下载页面,发现U-Boot的版本众多:

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第1张图片

那怎么选取U-Boot的版本呢?够用就行。

本文芯片选择S5PV210,U-Boot版本选择u-boot-2012.10版本。

U-Boot两个文件夹

在U-Boot的源码文件中,先简单介绍两个目录:

目录U-boot/arch/arm/cpu/armv7

arch目录是ARM公司设计的内核代码,即CPU部分的代码。

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第2张图片

在这个目录下,start.S文件是整个U-Boot的入口。也就是说,整个U-Boot运行的第一句代码就是从start.s的第一句开始的

目录U-boot/board/samsung

board目录是三星等半导体公司设计的代码部分,主要是IIC、USB、串口等各种外设的代码。

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第3张图片

其中,goni的核心CPU正是S5PV210。

U-Boot的start.S

既然,start.S文件是整个U-Boot的入口,那么就从start.S开始。由于start.S的内容较长,接下来就将其分割开来进行解释。

#include 
#include 
#include 
#include 
#include 

头文件部分,到哪里找这些头文件呢?

两个目录下:U-boot/includeU-boot/arc/arm/include。为什么是这两个目录呢?因为在makefile的配置文件中指定了,之后讲解makefile的时候再进行讲解。接下来:

.globl _start		//.globl指示告诉汇编器,_start是一个全局符号
_start: b	reset				//跳转到reset符号处
	ldr	pc, _undefined_instruction				//未定义指令异常
	ldr	pc, _software_interrupt				//软中断,Linux系统调用
	ldr	pc, _prefetch_abort					//预取址中止,取不到下一条指令
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq						//中断
	ldr	pc, _fiq						//快中断
#ifdef CONFIG_SPL_BUILD
_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
_pad:			.word 0x12345678 /* now 16*4=64 */
#else
_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
_pad:			.word 0x12345678 /* now 16*4=64 */
#endif	/* CONFIG_SPL_BUILD */

.global _end_vect			//异常向量和处理部分结束
_end_vect:

	.balignl 16,0xdeadbeef			//下一句需要16字节对齐,若没有满,就将0xdeadbeef填入,直至对其

.global声明_start为全局符号,_start就会被链接器链接到,也就是链接脚本中的入口地址了。之后是一系列ARM的异常向量表。8种异常分别占用4个字节,因此每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,reset异常是直接跳转到reset函数,其他7种异常是用ldr将处理函数入口地址加载到pc中

再下面出现了CONFIG_SPL_BUILD是否被定义的判断。SPL是什么呢?

这就牵扯到S5PV210的启动机制:

可能直接看起来有些晦涩,这就和它的“前辈”S5C6410有些渊源了:

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第4张图片

S5C6410上电之后,会先启动片内iROM(芯片固化的启动程序)的程序,这部分程序主要完成初始化时钟、看门狗等外围器件,并将Booting Device的头4k的内容(BL1)加载到片内RAM中运行。BL1的任务是配置好SDRAM,再将BootLoader的大部分(BL2,200k)加载到SDRAM,最后将程序跳转到BL2进行运行。BL2的任务是将Booting Device中的操作系统镜像、根文件系统加载到SDRAM中,并跳转到OS的入口处

这里解析一下:片内RAM、SDRAM、NOR FLASH、NAND FLASH:

  • 片内RAM,速度最快,CPU存取总线速度最快,静态存储器。但造价高而且是易失的(断电不保存数据),用于CPU数据指令暂存,位置在内核;SDRAM,属于动态RAM,位置在内核之外,甚至片外。速度比静态RAM慢,也是易失的,用于系统计算数据/指令存储。
  • NAND FLAHS、NOR FLASH等属于FLASH,断电可保存数据,用于存储程序代码和常量数据。两者的区别是:应用程序可以直接在NOR FLASH内运行,不必到RAM中运行,因此数据传输效率高(不需要传输),但是写入和擦除速度慢;NAND FLAHS存储密度高,并且写入和擦除的速度也很快,但不能直接运行应用程序。

S5PV210相比较于S5C6410,片内RAM增加到了96k,这是一种好意,本想增加片内RAM可以直接一次性将BootLoader的BL1和BL2加载运行。但是奈何BootLoader还是太大了,96k还是不够的。

但是U-Boot不买三星的账,就将U-Boot做成两个镜像,u-boot-spl.bin(16k)和u-boot.bin(200k)。两个镜像的启动方式和S5C6410的方式差不多,小的BootLoader配置好SDRAM,并加载大的BootLoader;大的BootLoader加载内核和根文件系统。

这就是SPL的含义。

也就是说,如果CONFIG_SPL_BUILD定义了,即处于BL1的阶段,此时发生_undefined_instruction(未定义指令异常),那么就将_undefined_instruction赋给pc,也就是跳转到_undefined_instruction本身,即死循环;如果CONFIG_SPL_BUILD没有定义,即处于BL2的阶段,此时发生_undefined_instruction(未定义指令异常),那么就将undefined_instruction赋给pc,也就是跳转到undefined_instruction的部分。

接下来:

/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *
 *************************************************************************/

.globl _TEXT_BASE
_TEXT_BASE:
	.word	CONFIG_SYS_TEXT_BASE		//定义的宏,U-Boot被拷贝到SDRAM中的起始地址

/*
 * 计算一些段的标号
 */
.globl _bss_start_ofs
_bss_start_ofs:
	.word __bss_start - _start

.global	_image_copy_end_ofs
_image_copy_end_ofs:
	.word 	__image_copy_end - _start

.globl _bss_end_ofs
_bss_end_ofs:
	.word __bss_end__ - _start

.globl _end_ofs
_end_ofs:
	.word _end - _start

#ifdef CONFIG_USE_IRQ					//如果使用中断IRQ(肯定用到的)
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START					//表示堆栈指针开始的地方
IRQ_STACK_START:
	.word	0x0badc0de					//此时不知道堆栈在哪里,就放入没有意义的非法地址,运行时该值会被修改

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
#endif

/* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
	.word	0x0badc0de

前面看了这么多,现在终于进入到了reset符号处了:

reset:
	bl	save_boot_params		//跳转到save_boot_params符号处
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0, cpsr				//将cpsr寄存器的值读到r0中
	bic	r0, r0, #0x1f				//清除cpsr寄存器的M0~M4位
	orr	r0, r0, #0xd3			//禁止IRQ,FIQ中断,并将处理器置于SVC模式
	msr	cpsr,r0				//将r0的内容写入cpsr寄存器

真正的初始化从这里开始了。其实在CPU一上电以后就是跳到这里执行的,更改处理器模式为SVC模式。这就需要查看ARMv7的芯片资料了:

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第5张图片

其中,M[4:0]是对处理器模式进行配置的位:

【Linux】U-Boot启动文件start.S详解(超详细讲解,上篇)_第6张图片

也就是配置成10011。同理需要配置禁止IRQFIQ中断,即I、F位为1。这样算来,需要给的值为1101 0011,即0xD3。接着:

/*
 * 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))		//不是OMAP44XX芯片,宏定义为真
	/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
	mrc	p15, 0, r0, c1, c0, 0	@ Read CP15 SCTRL Register			//将SCTRL的值赋给r0(SCTRL的值通过0,c1,c0,0查表来确定)
	bic	r0, #CR_V		@ V = 0
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTRL Register			//将r0写到SCTRL里

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

这部分代码通过对协处理器CP15进行操作,设置了处理器的异常向量入口地址_start。这是因为ARM默认的异常向量表入口在0x0地址,然而S5PV210中0x0地址存放的是IROM,不可修改,自然不可能存放异常向量表,所以需要修改异常向量表入口,将它们映射到其他位置上去。

通过设置CBAR寄存器的内容(_start的地址),一旦出现异常,就会到该寄存器填写的地址中去。

到这里,先总结一下之前完成的内容,都是跟异常有关:

  • 初始化异常向量表
  • 设置CPU SVC模式,关中断
  • 配置CP15,设置异常向量入口

接下去:

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

这两句跳转都是比较重点的内容,需要进行分析:

/*************************************************************************
 *
 * cpu_init_cp15
 *				//配置CP15,关闭的cache、MMU、TLBs。icache视情况
 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
 * CONFIG_SYS_ICACHE_OFF is defined.
 *
 *************************************************************************/
ENTRY(cpu_init_cp15)
	/*
	 * Invalidate L1 I/D
	 */
	mov	r0, #0			@ set up for MCR
	mcr	p15, 0, r0, c8, c7, 0	@ invalidate TLBs			//失效TLBs
	mcr	p15, 0, r0, c7, c5, 0	@ invalidate icache			//失效icache
	mcr	p15, 0, r0, c7, c5, 6	@ invalidate BP array		//失效分支预测
	mcr     p15, 0, r0, c7, c10, 4	@ DSB				//数据同步屏障
	mcr     p15, 0, r0, c7, c5, 4	@ ISB				//指令同步屏障

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0			//将SCTLR的值赋给r0
	bic	r0, r0, #0x00002000	@ clear bits 13 (--V-)		//清零第13位,异常向量映射到0x00000000
	bic	r0, r0, #0x00000007	@ clear bits 2:0 (-CAM)		//失效dcache,失效对齐检查,禁用MMU
	orr	r0, r0, #0x00000002	@ set bit 1 (--A-) Align			//使能对齐检查模式
	orr	r0, r0, #0x00000800	@ set bit 11 (Z---) BTB				//使能分支预测
#ifdef CONFIG_SYS_ICACHE_OFF						//该宏被定义
	bic	r0, r0, #0x00001000	@ clear bit 12 (I) I-cache	
#else
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-cache		//使能icache
#endif
	mcr	p15, 0, r0, c1, c0, 0			//将r0的值赋给SCTLR
	mov	pc, lr			@ back to my caller					//子过程运行结束,跳转回去
ENDPROC(cpu_init_cp15)

cpu_init_cp15函数是通过配置CP15协处理器相关寄存器来进行一些设置。设置内容主要是失效TLBs(这与MMC有关,后面再讲)、失效icache、失效BP array

什么是icache

缓存(cache)是主存(DARAM)和CPU之间设置的一个高速的、容量相对较小的存储器,把正在执行的指令地址附近的一部分指令或数据从主存调入这个存储器,供CPU在一段时间内使用,以提高程序的运行速度

出于对简化设计的考虑,也为了提高系统的性能,设计采用了指令Cache(icache)、数据Cache(dcache)分开的方式。在icache中存储有微处理器需要的指令,在微处理器的取指阶段,通过程序计数器PC提供给icache的地址,微处理器可以获取需要的指令;而dcache则是作为一个数据的存储,并提供对于Load/Store指令所要操作地址的数据,它地址则来自于ALU运算的结果。

不理解的可以参考博文:cache为什么分为i-cache和d-cache以及Cache的层次设计。

什么是BP array

BP,Branch Predictor,即分支预测。条件分支指令通常具有两路后续执行分支。即不采取跳转,顺序执行后面紧挨JMP的指令;以及采取跳转,到另一块程序内存去执行那里的指令。是否条件跳转,只有在该分支指令在指令流水线中通过了执行阶段才能确定。

如果没有分支预测器,处理器将会等待分支指令通过了指令流水线的执行阶段,才把下一条指令送入流水线的第一个阶段——取指令阶段或者将后续流水线全部清空。这种技术叫做流水线停顿、流水线冒泡。

什么是DSBISB

  • DMB:数据存储器隔离。DMB 指令保证:仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的存储器访问操作
  • DSB:数据同步隔离。比 DMB 严格:仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访问操作);
  • ISB:指令同步隔离。最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令

需要注意到,为什么U-Boot需要失效dcache、禁止MMU,却不失效icache

Cache是CPU内部的缓存,它的作用是将常用的数据和指令放在CPU内部。Cache是通过CP15管理的,刚上电的时候,CPU还不能管理Cache。上电的时候icache可关闭,也可不关闭,但dcache一定要关闭,否则可能导致刚开始的代码里面,去取数据的时候,从dcache里面取,而这时候DARAM中数据还没有过来,导致数据预取异常 。而在使用MMU之前要进行一系列的初始化,并且过程比较复杂,并且U-Boot暂时用不到所以要关闭它。

接下来:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
/*************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
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)
#endif

这部分的内容,跳转到lowlevel_init符号处,这各部分是在另一个文件中定义的。后面的内容在下次博文中讲解。

你可能感兴趣的:(《操作系统》Linux系统移植)