Linux u-boot加载过程
----基于freescale i.MX6
近期做项目需要修改u-boot,刚好趁机研究一把linux u-boot。
以前没接触过u-boot,所以学习只能从头开始。
既然是从头开始, 那就从u-boot的加载过程起步吧。
本文主要包括以下内容:
1、cpu如何开始加载u-boot。
2、加载u-boot时的初期处理。
3、如何跳转到c代码。
c代码中的处理过程,以及后续处理,本文不作介绍。
做我们这个行当的,都知道程序需要跑在内存上(能够XIP的flash除外,此处不作介绍)。
但刚上电时,cpu并不知道它连了些什么东西,甚至连没连接内存也不知道,另外想在内存上执行,也总要把程序加载到内存才行。
如此看来,机器上电的处理比想象的要复杂不少!习惯了开机只是按下开关,想不到还有这么多东东隐藏在背后!!!
那么,程序到底是如何开始跑的呢?
启动过程一般在cpu的参考手册中都有介绍,我这边就不作详细介绍,但相关的部分,会稍微提一下。
上电后,cpu内部会做一些检查,如电压是否稳定,启动模式等等,之后会从地址0x00000000开始执行命令。
从地址0x00000000开始,一般是cpu的rom code,也就是cpu芯片上写死的。
rom code开始执行,会作很多处理,会处理什么呢?cpu厂商很清楚,呵呵。
其实,多数处理可以看看参考手册中的启动时序。
我们关心的:根据启动模式,确定启动设备;然后对启动设备进行验证,并进行初步初始化。
(也就是参考手册中有很多地方会说的,在启动过程中是什么样子,例如始终是 XX M,启动过程之后是另外一个样子,例如时钟变为 YY M)
初始化好了,然后就是去加载我们的程序了,也就是从启动设备上读取我们的程序。
从哪儿开始读取呢,当然是有道道的。参考手册中一般会指定各种启动设备中程序存放的地址。如i.MX6中,若从emmc启动,会从emmc的0x400 bytes地址开始读取程序(1K bytes的地方).
读过来的是个什么东东呢?直接可以跑的程序?
不是,参考手册中有说明。
i.MX6中读到的是一个IVT(image vector table)结构体,结构体的定义肯定是规定好的,不然rom code怎么知道如何解析该结构体?
i.MX6中对IVT结构体的定义:
header
entry: Absolute address of the first instruction to execute from the image
reserved1: Reserved and should be zero
dcd: Absolute address of the image DCD. The DCD is optional so this field may be set to NULL if no DCD is required. See
Device Configuration Data (DCD) for further details on DCD.
boot data: Absolute address of the Boot Data
self: Absolute address of the IVT. Used internally by the ROM
csf: Absolute address of Command Sequence File (CSF) used by the HAB library. See High Assurance Boot (HAB) for
details on secure boot using HAB. This field must be set to NULL when not performing a secure boot
reserved2: Reserved and should be zero
我们关系以下几个:
entry: 入口函数,在程序中指定,如:ENTRY(_start)
dcd: Device Configuration Data, 设备配置数据,用来配置设备的。因为刚上电,设备的参数都是默认的,不能达到最优效果,甚至有些设备不进行初始化根本就无法工作。
那怎么办呢?就需要在使用这些设备之前,进行一些设置,如内存等等。
dcd中可以设置如下这些东东:
Address range Start address Last Address
IOMUX Control (IOMUXC) registers 0x020E0000 0x020E3FFF
CCM register set 0x020C4000 0x020C7FFF
ANADIG registers 0x020C8000 0x020C8FFF
MMDC register set 0x021B0000 0x021B7FFF
IRAM Free Space 0x00907000 0x00937FF0
EIM 0x08000000 0x0FFEFFFF
DDR 0x10000000 0xFFFFFFFF
DCD中命令的格式这儿就不介绍了,参考手册中有详细说明。
来看看IVT定义的例子吧:
.section ".text.flasheader", "x"
b _start
.org CONFIG_FLASH_HEADER_OFFSET
ivt_header: .word 0x402000D1 /* Tag=0xD1, Len=0x0020, Ver=0x40 */
app_code_jump_v: .word _start
reserv1: .word 0x0
dcd_ptr: .word dcd_hdr
boot_data_ptr: .word boot_data
self_ptr: .word ivt_header
app_code_csf: .word 0x0
reserv2: .word 0x0
在该例子中,入口函数为_start,在start.S中定义;dcd_hdr 指向dcd image,在flashheader.S中定义;IVT结构体也定义在flashheader.S中;
boot_data是个结构体,定义在flashheader.S中:
boot_data: .word TEXT_BASE
image_len: .word _end_of_copy - TEXT_BASE + CONFIG_FLASH_HEADER_OFFSET
plugin: .word 0x0
TEXT_BASE定义在config.mk中:
TEXT_BASE = 0x27800000
CONFIG_FLASH_HEADER_OFFSET,看字面意思,就是flash header的偏移地址,flash header? 我们前面有个flashheader.S难道就是这个?
看了下其定义:
#define CONFIG_FLASH_HEADER_OFFSET 0x400
原来就是flash header在启动设备上的偏移地址,也就IVT结构体在启动设备中的偏移。
_end_of_copy的定义在u-boot.lds中定义,应该就是u-boot结束的地方。
image_len为 _end_of_copy - TEXT_BASE还可以理解,为什还要加个CONFIG_FLASH_HEADER_OFFSET?
难道是rom code是按照image len从启动设备的开始地址加载u-boot,所以要包含CONFIG_FLASH_HEADER_OFFSET??
另外在.text.flasheader段定义的地方有下面一行代码:
.org CONFIG_FLASH_HEADER_OFFSET
难道是.text.flasheader段的头部故意空出来了CONFIG_FLASH_HEADER_OFFSET个bytes的空间?
看了下System.map,有如下:
27800400 t ivt_header
其中27800000是u-boot加载地址,而IVT开始的地方,即ivt_header在u-boot加载地址偏移CONFIG_FLASH_HEADER_OFFSET(0x400)的地方。
注意,这儿说的是加载地址,也就是内存中的地址,不是在启动设备上存储的地址。
知道了IVT的定义,并且知道rom code是从启动设备的固定地址来加载IVT,那么是如何将IVT放到启动设备的固定地址的呢?
我们知道,u-boot是被写到了启动设备的固定地址,也就是说,只要IVT结构体在u-boot的头部,就能够保证rom code能够加载到IVT。
的确是这样的,IVT在flashheader.S的头部,而flashheader.o又在u-boot的头部,所以IVT就在u-boot的头部。
这是通过u-boot.lds来实现的:
(u-boot.lds文件不长,干脆全列出来吧)
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
/* WARNING - the following is hand-optimized to fit within */
/* the sector layout of our flash chips! XXX FIXME XXX */
board/freescale/mx6dl_14cytmap/flash_header.o (.text.flasheader)
cpu/arm_cortexa8/start.o
drivers/mmc/libmmc.a (.text)
/* 其他 text 段*/
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
_end_of_copy = .; /* end_of ROM copy code here */
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
从u-boot.lds可知,flashheader.o在代码段的首部,并且代码段在最前面,因此就保证了前面所说的IVT在启动设备上的固定位置。
rom code最初加载了哪些东东呢?
rom code根据启动设备的偏移地址,首先把uboot加载到iRame。
然后开始解析IVT结构体。
之后找到DCD所在的地址(在IVT)中,逐条执行DCD命令。主要是初始化内存等。
之后呢,找到entry函数,也就是IVT结构体中的entry成员,对应到上面的例子是_start函数,开始执行。
_start函数中start.S中定义:
.globl _start
_start: 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
...
可见_start的第一句就跳转到了reset。之后是一些异常处理的东东。
再看看reset,也在start.S中。
reset的名称是自定义的,有些地方称为start_code。
reset主要完成以下工作:
1、set the cpu to SVC32 mode
2、初始化cpu的一些东东,如果需要的话
3、如果有必要,重新定位u-boot。其实就是判断启动地址,即_start的地址,与加载地址,即TEXT_BASE是否相同,如果不同,则将uboot从启动地址copy到加载地址。
4、设置stack、bss、irq、fiq,其实就是在内存中留出来一些空间。
5、看是否需要配置MMU。
6、最后:
ldr pc, _start_armboot @ jump to C code
将_start_armboot加载到pc寄存器,pc寄存器保存的是吓一条指令的地址,所以也就是跳到_start_armboot执行。
_start_armboot定义:
_start_armboot: .word start_armboot
start_armboot 是c代码中定义的函数。在代码Board.c中定义。
终于从汇编出来了。