文章目录
- 以 cpu/s5pc11x、board/samsung/x210 为例讲解 uboot 启动流程
- 第一阶段
- **0. 头文件**
- **1. 填充16字节的校验位**
- **2. 设置异常向量表**
- **3. 禁能 IRQ(普通中断) 和 FIQ(快速中断),使能 SVC 模式(超级用户模式)**
- **4. 初始化相关全局变量**
- **5. 禁用 Cache 和 MMU**
- **6. 判断启动介质**
- **7. 第一次初始化栈(SRAM:cache)**
- **8. 跳转执行 lowlevel_init 部分**
- **9. 再次供电锁存**
- **10. 第二次初始化栈(内存)**
- **11. 再次检测当前程序执行地址(SRAM OR DDR)**
- **12. 通过引脚来判断启动介质**
- **13. 进行 uboot 重定位**
- **14. 建立虚拟地址映射表并开启 MMU**
- **15. 第三次初始化栈(DDR)**
- **16. 清 bss 段**
- **17. 远跳 uboot 第二阶段**
- 第二阶段
- **1. 初始化全局变量**
- **2. 运行 init_sequence 数组中所有的初始化函数**
- **3. 初始化堆内存**
- **4. 初始化外部存储设备**
- **5. 环境变量的重定位**
- **6. 获取 IP 地址和 MAC 地址**
- **7. 其他函数**
- **8. 进入 main_loop 循环**
以 cpu/s5pc11x、board/samsung/x210 为例讲解 uboot 启动流程
注意代码文件调用关系,以及每个代码文件实现的功能。
- 供电锁存意义?
- 函数指针类型?
- 链接地址和物理地址的区别?
- 地址的定义位置?为什么要判断启动介质?
- 什么是冷启动?
U-Boot 的启动过程分为两个阶段。
- 第一阶段【移植修改量小】:主要是 SOC 内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在/uboot/cpu/s5pc11x/start.S和/uboot/board/samsung/x210/lowlevel_init.S中。
- 第二阶段【板级层面,移植修改量大】:主要是板级的初始化,SOC 内部的初始化比较少,移植的修改量主要在此。此阶段由 C 语言编写,代码主体分布在/uboot/lib_arm/board.c中。
/uboot/cpu/s5pc11x/start.S
/include/config.h(/uboot/mkconfig)
/include/version.h(/uboot/Makefile)
具有符号链接的头文件(/uboot/mkconfig)
TEXT_BASE:UBOOT代码的链接地址(/uboot/board/samsung/config.mk(/uboot/Makefile->x210_sd_config 段))
CFG_PHY_UBOOT_BASE(在DDR 中的物理地址,uboot重定位将自身拷贝至DDR的_TEXT_PHY_BASE)(/uboot/include/configs/x210_sd_config)
烧录uboot的扇区以及uboot占的扇区(/uboot/include/movi.h)
uboot在DDR中的物理地址(/uboot/include/configs/x210_sd.h)
重定位,从SD卡拷贝到DDR(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy --> copy_b12(IRDOM)
board/x210/lowlevel_init
建立虚拟映射表
修改时钟参数、DDR 参数(uboot/include/configs/x210_sd.h)
/uboot/lib_arm/board.c
是否支持I2C(/uboot/include/configs/x210_sd.h)
全局变量结构体:
gd_t(/uboot/include/asm-arm/Global_data.h)
bd_t(/uboot/include/asm-arm/U-Boot.h)
移植的重点修改部分:
- 跳转到 lowlevel_init 部分
- 再次锁存供电
- 建立虚拟地址映射表并开启 MMU
- 运行 init_sequence 数组中所有的初始化函数
- 初始化外部存储设备
- 获取 IP 地址和 MAC 地址
第一阶段
0. 头文件
-
/uboot/include/config.h
文件是在配置过程中生成的(具体/uboot/mkconfig
文件中实现) -
version.h
文件是 uboot 的版本信息文件,是在编译过程中生成的。具体的 U-Boot 的 version 信息可以在==/uboot/Makefile
==中更改 - U-Boot 中的头文件包含其实都不是真正被包含的文件,它们大多是在配置编译阶段产生的符号链接或者是具有符号链接功能的头文件(==
/uboot/mkconfig
==中创建符号链接)。总之,它们的功能都类似于符号链接,目的是让 uboot 的源码更具灵活性和移植性。
1. 填充16字节的校验位
从 SD 卡和 NAND 启动是需要 16 字节校验头的,mkv10image.c 能够计算这个校验头。
2. 设置异常向量表
-
.globl
把 _start 这个标号全局化,是编译器的操作,_start 代表程序 start.S 的入口。 - 构建异常向量表,实际内容为空(uboot 不需要很细致地处理各种异常),除 reset 异常。
b reset 所处的位置是与异常向量表基地址偏移量为的 0 的地方,所以当复位异常发生时(开机也属于复位异常),CPU 会自动跳转执行 reset 部分的代码。
.globl _start
_start: b reset # 此处基地址为 0,意味着复位发生时将会跳转到异常表偏移量为 0 的地方
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
3. 禁能 IRQ(普通中断) 和 FIQ(快速中断),使能 SVC 模式(超级用户模式)
- 禁能目的:异常向量表未初始化,无法执行中断。
- SVC 模式主要用于 SWI(软件中断)和 OS,拥有特权。
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
@;mrs r0,cpsr
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
4. 初始化相关全局变量
- TEXT_BASE 是 U-Boot 代码的链接地址,在==
/uboot/board/samsung/config.mk
==文件中定义,该文件在 /uboot/Makefile->x210_sd_config 段
中生成。TEXT_BASE = 0xc3e00000。
_TEXT_BASE:
.word TEXT_BASE # 0xc3e00000 # Uboot 代码的链接地址
-
CFG_PHY_UBOOT_BASE
在开发板配置文件(/uboot/include/configs/x210_sd_config
)中定义,为 0x33e00000。
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE # 0x33e00000 # DDR中的物理地址
uboot 重定位将自身拷贝至 DDR 的 _TEXT_PHY_BASE。链接地址和物理地址的区别?
链接地址:链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)
运行地址:程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)
5. 禁用 Cache 和 MMU
- MMU:转换虚拟地址和物理地址。
- 关闭 caches 的原因:上电初始,DDR 未初始化,当 CPU 从 cache 中取数据时,可能导致数据预取异常。另一方面,当汇编指令读取缓存数据,而实际物理地址对应的数据发生变化,导致 CPU 不能获取最新的数据。
- 关闭 MMU 的原因:U-Boot 的作用是硬件初始化和引导操作系统,纯粹的初始化阶段,开启 MMU 会导致这个过程更复杂。
cpu_init_crit:
.................................................
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
6. 判断启动介质
- 先从(PRO_ID_BASE+OMR_OFFSET)地址的寄存器读取启动介质信息,经过数据处理之后放入r2寄存器中。
- 然后通过比较r2的值来判定启动介质,经过判断得到当前的启动介质为SD/MMC,在把BOOT_MMCSD宏写入r3中。
- 最后将启动介质信息从寄存器r3中写入(INF_REG_BASE+INF_REG3_OFFSET)地址的寄存器中。
地址的定义位置?为什么要判断启动介质?
/* Read booting information */
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
............................................
/* SD/MMC BOOT */
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
.............................................
ldr r0, =INF_REG_BASE
str r3, [r0, #INF_REG3_OFFSET]
7. 第一次初始化栈(SRAM:cache)
原因:不设置栈的话无法使用嵌套 bl 跳转指令,即双层函数调用,因为只有一个 LR 寄存器,而后面的 lowlevel_init 程序中有双层跳转,故这里开始设置 SRAM 中用的栈。
LR 寄存器用于记录源程序的断点位置,以便正确的异常返回。
ARM 处理异常时,会自动完成将当前的 PC 保存到 LR 寄存器。
ARM 处理器执行子程序调用指令(BL )时,会自动完成将 PC-4 保存到 LR 寄存器。
ARM处理器针对不同的模式,共有 6 个链接寄存器资源(LR ),其中用户模式和系统模式共用一个 LR,每种异常模式都有各自专用的 R14 寄存器(LR)。这些链接寄存器分别为 R14、R14_svc、R14_abt、R14_und、R14_irq、R14_fiq,
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
8. 跳转执行 lowlevel_init 部分
1)判断复位的类型(启动类型)
判断复位类型的意义在于:冷启动需要重新初始化 DDR(内存),而休眠唤醒不需要再次初始化 DDR。
什么是冷启动?
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
2)关闭看门狗
/* Disable Watchdog */ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */mov r1, #0str r1, [r0]
看门狗就是定期的查看芯片内部的情况,一旦发生错误就向芯片发出重启信号的电路。看门狗命令在程序的中断中拥有最高的优先级。防止程序跑飞。也可以防止程序在线运行时候出现死循环。
3)开发板供电锁存
/* PS_HOLD pin(GPH0_0) set to high 开发板供电上锁*/ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)ldr r1, [r0]orr r1, r1, #0x300 orr r1, r1, #0x1 str r1, [r0]
4)检测程序当前的执行位置(cache 中还是内存中?)
即 CPU 是冷启动还是休眠唤醒复位?,从而来决定是否要跳过后面的时钟、DDR 的初始化代码。
比较链接地址和当前地址的特定位,即比较 r1 和 r2。若比较链接地址和当前地址的特定位相等,说明当前代码处于 DDR 中(休眠唤醒),则跳转到标号 1 处执行后面的代码(即跳过时钟、DDR 的初始化代码);否则,继续执行后续的时钟、DDR 的初始化代码。
ldr r0, =0xff000fffbic r1, pc, r0 /* r0 <- current base addr of code */ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */bic r2, r2, r0 /* r0 <- current base addr of code */cmp r1, r2 /* compare r0, r1 */beq 1f /* r0 == r1 then skip sdram init */
5)初始化时钟、DDR、串口
时钟的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h
)中修改。
DDR 的相关参数可以在开发板配置文件(/uboot/include/configs/x210_sd.h
)中修改。
跳转执行串口初始化函数 uart_asm_init,成功执行后打印 O
,然后返回。O
可作为调试的帮助。
若定义了ONFIG_ONENAND
,则初始化 ONENAND;若定义了CONFIG_NAND
,则初始化 NAND。
在返回 start.S 前打印了K
,与之前的‘ O’组成“OK”,这是 uboot 的第一条打印信息,可以用来判断 lowlevel_init 是否正常运行。
返回到 start.S。
/* init system clock */bl system_clock_init/* Memory initialize */bl mem_ctrl_asm_init1:/* for UART */bl uart_asm_init //串口初始化函数,初始化完成打印'O'bl tzpc_init // 基本没用#if defined(CONFIG_ONENAND) // 条件编译,若定义了`ONFIG_ONENAND`,则初始化 ONENANDbl onenandcon_init#endif#if defined(CONFIG_NAND)/* simple init for NAND */bl nand_asm_init#endif .............................................. /* Print 'K' */ldr r0, =ELFIN_UART_CONSOLE_BASEldr r1, =0x4b4b4b4bstr r1, [r0, #UTXH_OFFSET]pop {pc}
9. 再次供电锁存
可能为代码的冗余。
ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */ldr r1, =0x00005301 /* PS_HOLD output high */str r1, [r0]
10. 第二次初始化栈(内存)
为了即将执行的 C 程序做准备,这里开始第二次设置栈,设置于 DDR 中。这里将栈设置在 _TEXT_PHY_BASE ,即 U-Boot 在 DDR 中的真正物理地址(U-Boot 运行地址)。由于栈是满减栈,所以紧挨着 uboot 放置也不会冲突。
uboot 在DDR中还存在?不仅是sram。可能有bl1和bl2两个部分,前者先导入sram中,做一些初始化工作,然后再将bl2导入到DDR中。
https://www.bilibili.com/video/BV1mV411h725?p=2
/* get ready to call C functions */ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */sub sp, sp, #12mov fp, #0 /* no previous frame, so fp=0 */
11. 再次检测当前程序执行地址(SRAM OR DDR)
决定是否要跳过重定位代码。
ldr r0, =0xff000fffbic r1, pc, r0 /* r0 <- current base addr of code */ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */bic r2, r2, r0 /* r0 <- current base addr of code */cmp r1, r2 /* compare r0, r1 */beq after_copy /* r0 == r1 then skip flash copy */
12. 通过引脚来判断启动介质
区别于之前判断启动介质的方法,这次是通过引脚来判断启动介质的。
将 0xD0037488 地址的寄存器的值放入 r0,该寄存器里的值可以判断 BL1 是从 SD/MMC 哪个通道启动的,将寄存器的值放入 r1。
将 0xEB200000 这个值放入 r2,该值的表示是 2 号方式启动,并将寄存器的值和r2(0xEB200000)进行对比。如果相同则说明 BL1 是从 SD 卡通道 2 启动,跳转入标号 mmcsd_boot 处执行 SD/MMC 重定位相关代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy
)。
若不是从SD卡通道2启动,则读取之前存储的启动介质信息(INF_REG_BASE+INF_REG3_OFFSET地址的寄存器),该信息有上一次启动介质检测所得。随后,跳转至启动介质相应的 xxx_boot 代码中执行重定位相关的代码(/uboot/cpu/s5pc11x/movi.c->movi_bl2_copy
)。(nand_boot、onenand_boot、mmcsd_boot、、)
#if defined(CONFIG_EVT1)/* If BL1 was copied from SD/MMC CH2 */ldr r0, =0xD0037488ldr r1, [r0]ldr r2, =0xEB200000cmp r1, r2beq mmcsd_boot#endifldr r0, =INF_REG_BASEldr r1, [r0, #INF_REG3_OFFSET]cmp r1, #BOOT_NAND /* 0x0 => boot device is nand */beq nand_bootcmp r1, #BOOT_ONENAND /* 0x1 => boot device is onenand */beq onenand_bootcmp r1, #BOOT_MMCSDbeq mmcsd_bootcmp r1, #BOOT_NORbeq nor_bootcmp r1, #BOOT_SEC_DEVbeq mmcsd_bootnand_boot:mov r0, #0x1000bl copy_from_nandb after_copyonenand_boot:bl onenand_bl2_copyb after_copymmcsd_boot:#if DELETEldr sp, _TEXT_PHY_BASE sub sp, sp, #12mov fp, #0#endifbl movi_bl2_copyb after_copynor_boot:bl read_hwordb after_copy
13. 进行 uboot 重定位
用来复制 SD/MMC 中内容到任意地址的函数。
copy_sd_mmc_to_mem
typedef u32(*copy_sd_mmc_to_mem) (u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
- 首先读取位于 0xD0037488 的寄存器值至变量 ch 中,再一次检查启动介质(SD卡的通道号)。
- 然后 0xD0037F98 地址中的指针变量强制转换为 copy_sd_mmc_to_mem 类型,并赋值给 copy_bl2。该指针变量指向一个函数,这个函数就是 CPU 的 IROM 中固化用来复制 SD/mmc 中的内容至任意地址的函数。
- 最后,根据ch变量的值判断 SD 的通道号,并将整个 U-Boot 从相应的 SD 通道号中拷贝至 DDR 的指定地址(CFG_PHY_UBOOT_BASE = _TEXT_PHY_BASE = 0x33e00000)处。
- MOVI_BL2_POS 是烧录uboot时的扇区,MOVI_BL2_BLKCNT 是 uboot 占的扇区数,具体的定义和计算都在==
/uboot/include/movi.h
中。CFG_PHY_UBOOT_BASE 为 U-Boot 的在 DDR 中的物理地址(运行地址),具体的定义和计算都在/uboot/include/configs/x210_sd.h
==中。
void movi_bl2_copy(void){ulong ch; ch = *(volatile u32 *)(0xD003A508); copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008)); u32 ret; if (ch == 0xEB000000) //SD卡通道0 { ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);//0表示通道0;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址 } else if (ch == 0xEB200000) { ................. }}
14. 建立虚拟地址映射表并开启 MMU
从 movi_bl2_copy 返回之后,将跳转至 start.S->after_copy
,建立虚拟地址映射表并开启 MMU。
bl movi_bl2_copyb after_copy
TTB(TranslationTableBase),即转换表的基地址。转换表是 MMU 将虚拟地址映射为物理地址的凭据,建立整个虚拟地址映射的关键就是建立转换表,此表存放在内存中,工作时不需要软件干涉。
只要将转换表 TTB 的基地址(_mmu_table_base)放入 cp15 的 c2 寄存器,MMU 就能自动使用虚拟地址映射。_mmu_table_base 定义在 start.S 其值为标号 mmu_table,mmu_table 在lowlevel_init 中定义。
最后通过设置 cp15 的 c1 寄存器来开启 MMU,以实现虚拟地址映射和内存访问权限管理等功能。
#if defined(CONFIG_ENABLE_MMU)/*使能域访问*/enable_mmu: ldr r5, =0x0000ffff mcr p15, 0, r5, c3, c0, 0 @load domain access register /*将转换表ttb的基地址放入cp15的c2寄存器,mmu就能自动使用虚拟地址映射*/ ldr r0, _mmu_table_base ldr r1, =CFG_PHY_UBOOT_BASE ldr r2, =0xfff00000 bic r0, r0, r2 orr r1, r0, r1 mcr p15, 0, r1, c2, c0, 0 /*通过设置cp15的c1寄存器来开启mmu,以实现虚拟地址映射和内存访问权限管理等功能*/ mmu_on: mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #1 mcr p15, 0, r0, c1, c0, 0 nop nop nop nop#endif
详细的建表步骤在 /uboot/board/x210/lowlevel_init
。
映射表中规定了内存映射和管理是以块为单位的,在 ARM 中支持 3 种块大小,细表 1KB、粗表4KB、段1MB。真正的转换表就是由若干个转换表单元构成的,每个单元负责1 个内存块,总体的转换表负责整个内存空间(0-4G,32位CPU)的映射。
带参宏将 FL_SECTION_ENTRY base,ap,d,c,b 定义成一个 word 大小的特定值,这个特定值就是转换表的填充量。
#ifdef CONFIG_ENABLE_MMU #ifdef CONFIG_MCP_SINGLE .macro FL_SECTION_ENTRY base,ap,d,c,b //macro指令是汇编中宏定义.word (\base << 20) | (\ap << 10) | \ (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1) .endm //即结束宏定义.section .mmudata, "a".align 14.globl mmu_tablemmu_table: .set __base,0 //设置变量__base值为/*.rept 0x100相当于for循环,一共循环0x100次,所以这一块代码创建了0x100(256)个转换表单元*/ .rept 0x100 /*利用刚才定义的带参宏创建转换表的内容,变量__base和3,0,0,0作为参数*/FL_SECTION_ENTRY __base,3,0,0,0 .set __base,__base+1 //相当于base=base+1.endr //结束循环.........................................//后续继续建表
15. 第三次初始化栈(DDR)
将栈设置 uboot 链接地址上方 2MB 处,这个位置合理、紧凑、安全。
stack_setup:#if defined(CONFIG_MEMORY_UPPER_CODE) ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000
16. 清 bss 段
bss 段:存放临时未初始化数据。
clear_bss:ldr r0, _bss_start /* find start of bss segment */ldr r1, _bss_end /* stop here */mov r2, #0x00000000 /* clear */clbss_l:str r2, [r0] /* clear loop... */add r0, r0, #4cmp r0, r1ble clbss_l
17. 远跳 uboot 第二阶段
从 SRAM 中远跳转到 DDR 中函数 start_armboot 处,start_armboot 定义在根目录下 ==/uboot/lib_arm/board.c
==中。
ldr pc, _start_armboot_start_armboot: .word start_armboot
第二阶段
/uboot/lib_arm/board.c->start_armboot
1. 初始化全局变量
1)定义一个函数指针类型
typedef int (init_fnc_t) (void);
2)定义并初始化 init_sequence 数组,元素为指向 init_func_t 类型函数的指针,这些函数用来初始化各个功能:
- cpu_init:CPU 初始化函数,但 cpu 初始化工作已在 U-Boot 的第一阶段完成,所以该函数为空。
- board_init:开始板级初始化函数,配置网卡用到的GPIO、机器码、内存传参地址,并填充至 gd 结构体。
- interrupt_init:定时器的初始化函数,和中断无关(应用,如bootdelay)。
- env_init:环境变量的初始化函数,由于环境变量还没从启动介质中取到 DDR 中,故此处的初始化只是对 DDR 中的环境变量(U-Boot 自带的环境变量)进行一个简单的判定,检测其是否可用。真正的初始化在 start_armboot 里面。U-Boot支持多种不同的启动介质(如 norflash、nandflash、sd/mmc),而各种介质存取操作 env 的方法都是不同的,故 U-Boot 中包含在多个文件中包含了 env_init 函数。而此 U-Boot 的启动介质为 SD/MMC,故当前的 env_init 函数在/uboot/common/Env_movi.c文件中。
- init_baudrate:串口波特率初始化函数,设置串口波特率,并填充至 gd 结构体。具体的串口初始化工作已在 U-Boot 的第一阶段完成(lowlevel_init)。
- serial_init:串口初始化函数,但具体的串口初始化工作已在U-Boot的第一阶段完成(lowlevel_init),所以该函数为空。
- console_init_f:控制台初始化函数,名字中的_f表示这是第一阶段的初始化,由于第二阶段的初始化之前需要夹杂一些前提代码,故将在 start_armboot 执行。
- display_banner:用来通过串口控制台显示 U-Boot 的版本信息(logo)。
- print_cpuinfo:打印 CPU 的信息。
- checkboard:确认开发板信息。即,打印出当前开发板的名称信息。
- init_func_i2c:I2C 初始化函数,可在开发板配置文件(/uboot/include/configs/x210_sd.h)中设置 U-Boot是否使 用 I2C。
- dram_init:DDR 初始化函数,由于DDR硬件层面的初始化已在第一阶段完成,故此函数只是通过宏来获取 DDR 相关信息,并填充至 gd 结构体。DDR 信息相关的宏在开发板配置文件(/uboot/include/configs/x210_sd.h)中修改。
- display_dram_config:打印 bd 中的 DDR 配置信息(由dram_init获取)
init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */#if defined(CONFIG_SKIP_RELOCATE_UBOOT)reloc_init, /* Set the relocation done flag, mustdo this AFTER cpu_init()*/#endif board_init, /* basic board dependent setup */ interrupt_init, /* set up exceptions */ env_init, /* initialize environment */ init_baudrate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_banner, /* say that we are here */#if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */#endif#if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */#endif#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c,#endif dram_init, /* configure available RAM banks */ display_dram_config, NULL,};
3)初始化全局变量结构体 gd_t、bd_t
gd_t:在==/uboot/include/asm-arm/Global_data.h
==中定义,保存全集变量的结构体类型。
typedef struct global_data { bd_t *bd;//该指针指向bd_t结构体,其具体内容涉与板级硬件资源信息相关的全局变量 unsigned long flags; unsigned long baudrate; //串口波特率 unsigned long have_console;//标志位,是否使用控制台console unsigned long reloc_off; //重定位有关偏移量 unsigned long env_addr; //环境变量结构体的偏移量 usigned long env_valid; //标志位,表示内存中的环境变量能否使用 unsigned long fb_base; //帧缓存基地址,和显示有关#ifdef CONFIG_VFD unsigned char vfd_type; /* display type */#endif void **jt; /* jump table *///基本无用} gd_t;
bd_t:在==/uboot/include/asm-arm/U-Boot.h
==中定义,存放和开发板硬件相关的全局变量。
typedef struct bd_info { int bi_baudrate; //串口波特率 unsigned long bi_ip_addr; //IP地址 unsigned char bi_enetaddr[6]; //MAC地址 struct environment_s *bi_env; ulong bi_arch_number; //机器码 ulong bi_boot_params; //U-Boot给内核传参的地址 struct //DDR相关信息 { ulong start; ulong size; } bi_dram[CONFIG_NR_DRAM_BANKS];#ifdef CONFIG_HAS_ETH1 unsigned char bi_enet1addr[6];#endif} bd_t;
2. 运行 init_sequence 数组中所有的初始化函数
利用 for 循环遍历 init_sequence 数组,并执行初始化函数。若函数返回值为 0,则说明初始化函数执行错误,挂起程序,进入死循环。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); }}
3. 初始化堆内存
配置堆内存的起止地址。
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);#else mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);#endif
4. 初始化外部存储设备
#if defined(CONFIG_SMDKC110)#if defined(CONFIG_GENERIC_MMC)puts ("SD/MMC: ");mmc_exist = mmc_initialize(gd->bd);if (mmc_exist != 0){puts ("0 MB\n");}#endif#if defined(CONFIG_MTD_ONENAND)puts("OneNAND: ");onenand_init();/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/#else//puts("OneNAND: (FSR layer enabled)\n");#endif#if defined(CONFIG_CMD_NAND)puts("NAND: ");nand_init();#endif#endif /* CONFIG_SMDKC110 */
5. 环境变量的重定位
环境变量的重定位,将环境变量从启动介质中读到 DDR 内,环境变量的位置是通过原始分区信息表中读到的。
/* initialize environment */env_relocate ();
6. 获取 IP 地址和 MAC 地址
放到 bd 中的全局变量内。
/* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");/* MAC Address */{int i;ulong reg;char *s, *e;char tmp[64];i = getenv_r ("ethaddr", tmp, sizeof (tmp));s = (i > 0) ? tmp : NULL;for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;if (s)s = (*e) ? e + 1 : e;}
7. 其他函数
设备驱动初始化函数等
devices_init (); /* get the devices list going. *///设备驱动初始化,从linux中移植而来
jumptable_init ();//跳转表初始化,其实没用
console_init_r (); //控制台的第二部分的初始化,有实质性的功能
enable_interrupts ();//开启中断,实质是一个空函数,U-Boot中并没有使用中断
8. 进入 main_loop 循环
至此,U-Boot 启动第二阶段结束。即整个 U-Boot 启动完成,进入 main_loop 循环。若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则 U-Boot 将启动内核。
for (;;)
{
main_loop ();
}