uboot start.s源码分析
最近面试不少被问到了启动流程相关的东西,自己总结下吧。
linux操作系统的启动流程可以分为 bootloader-->kernel-->rootfs
,其中uboot
是一种常用的bootloader
工具,该篇文章就简单写一下自己对bootloader
过程的理解。
bootloader制作uboot
-
下载u-boot源码
u-boot源码下载地址 -
下载对应交叉编译工具并配置到环境变量
工具链下载地址
sudo apt-get install libc6-i386
安装32位库支持
.zshrc文件加上export PAHT=$PATH:/usr/arm-linux-toolschains4.4.3/bin
-
make smdk2410_config 确定平台
生成include/config.mk include/config.h -
make 生成uboot.bin文件
uboot代码体系
Bootloader的启动过程又分为两个阶段stage1和stage2。
stage1全部由汇编编写,即start.s
,它的主要工作是:
- 初始化硬件设备
- 为加载Bootlodader的stage2准备RAM空间
- 拷贝Bootloader的stage2到RAM空间
- 设置好堆栈段为stager2的C语言环境做准备。
stage2全部由C语言编写,其的主要工作是:
- 初始化本阶段要使用到的硬件设备
- 将内核映像和根文件系统映像从 flash 上读到RAM
- 调用内核
start.s代码分析
/* 汇编中的.globl或.global = C语言中的extern */
.globl _start /
_start: b start_code
ldr pc, _undefined_instruction /* 未定义异常 */
ldr pc, _software_interrupt /* 软中断 */
ldr pc, _prefetch_abort /* 预取中止 */
ldr pc, _data_abort /* 数据中止 */
ldr pc, _not_used /* 未使用 */
ldr pc, _irq /* 中断异常IRQ */
ldr pc, _fiq /* 快中断异常FIQ */
/* 申请一个字的空间将.word的地址写入 */
_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
/* .balignl来指定接下来的代码要16字节对齐,空缺的用0xdeadbeef,方便更加高效的访问内存 */
这部分是裸机开发最开始必须要写的,但不会一上电就执行,接下来我们看一下真正的start_code
start_code:
/* 上电后将系统设置为管理模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
bl coloured_LED_init
bl red_LED_on
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/* 重定位异常表 */
ldr r0, =_start
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
#ifdef CONFIG_S3C24X0
/* 关闭开门狗*/
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/* 屏蔽IRQ */
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* 初始化时钟 */
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
/* Set stackpointer in internal RAM to call board_init_f 设置栈指针 */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
/*------------------------------------------------------------------------------*/
这部分代码是上电之后真正会干的事情,简单总结以下:
- 执行设置CPSR程序程序状态寄存器为管理模式
- 关闭看门狗
- 屏蔽所有中断
- 进入
cpu_init_crit
函数关闭MMU,进入lowlevel_init初始化BANK寄存器来初始化SDRAM
接下来就看一下cpu_init_crit
函数具体干了啥,先记录一些基本概念
mcr/mrc:
MCR指令将ARM处理器的寄存器中的数据传送到协处理器的寄存器中
MRC指令将协处理器的寄存器中数值传送到ARM处理器的寄存器中
Caches:
是一种高速缓存存储器,用于保存CPU频繁使用的数据。在使用Cache技术的处理器上,当一条指令要访问内存的数据时,首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据。处理器会定期回写cache中的数据到内存。根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。
其中DCaches和ICaches分别用来存放数据和执行这些数据的指令
TLB:
就是负责将虚拟内存地址翻译成实际的物理内存地址,TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。
当应用程序访问一个虚拟地址的时候,会从TLB中查询出对应的物理地址,然后访问物理地址。
TLB通常是一个分层结构,使用与Cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。
这里禁用MMU,是方便后面直接使用物理地址来设置控制寄存器
/*
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
/* 关闭ICaches(指令缓存,关闭是为了降低MMU查表带来的开销)和DCaches(数据缓存,DCaches使用的是虚拟地址,开启MMU之前必须关闭) */
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/* 使无效整个数据TLB和指令TLB(TLB就是负责将虚拟内存地址翻译成实际的物理内存地址) */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
//bit8:系统不保护,bit9:ROM不保护,bit13:设置正常异常模式0x0~0x1c,即异常模式基地址为0X0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
//bit0~2:禁止MMU,禁止地址对齐检查,禁止数据Cache.bit7:设为小端模式
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
//bit2:开启数据Cache
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
//bit12:开启指令Cache
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init //跳转到lowlevel_init,前后的操作是保存和恢复环境的指令
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
接下来就看一下lowlevel_init
函数具体干了啥,在board
文件夹下随便找一个lowlevel_init.S
学习
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA // 将SMRDATA的首地址(0x33F806C8)存到r0中
ldr r1, _TEXT_BASE // r1等于_TEXT_BASE内容,也就是TEXT_BASE(0x33F80000)
sub r0, r0, r1 // 将0x33F806C8与0x33F80000相减,得到现在13个寄存器值在NOR Flash上存放的开始地址
ldr r1, =BWSCON /* Bus Width Status Controller 将BWSCON寄存器地址值存到r1中 (第一个存储器寄存器首地址) */
add r2, r0, #52 // r2 保存NOR Flash上13个寄存器值最后一个地址
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word 0x2211d644 /* d->Ethernet, 6->CPLD, 4->SRAM, 4->FLASH */
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) /* GCS0 */
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) /* GCS1 */
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) /* GCS2 */
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) /* GCS3 */
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) /* GCS4 */
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) /* GCS5 */
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) /* GCS6 */
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) /* GCS7 */
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
#ifndef CONFIG_RAM_16MB /* 32 MB RAM */
.word 0x10 /* BUSWIDTH=32, SCLK power saving mode, BANKSIZE 32M/32M */
#else /* CONFIG_RAM_16MB = 16 MB RAM */
.word 0x17 /* BUSWIDTH=32, SCLK power saving mode, BANKSIZE 16M/16M */
#endif /* CONFIG_RAM_16MB */
.word 0x20 /* MRSR6, CL=2clk */
.word 0x20 /* MRSR7 */
初始化SDRAM结束后继续返回start.S
看代码,之后的代码与我在网上参考的文章有些不同,参考文章代码会跳转到clock_init
函数设置时钟,而我的版本在之前已经设置好了时钟,应该是uboot
版本不同导致的,但是干的事情都大同小异,参考的文章链接将会放在文末。
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
/* Set up the stack */
stack_setup: //设置栈,方便调用C函数
mov sp, r4
adr r0, _start
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r2, _TEXT_BASE
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
copy_loop: /* 将uboot复制到SDRAM */
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
/.../
clear_bss:
#ifndef CONFIG_PRELOADER
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
ldr r3, _TEXT_BASE /* Text base */
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear 用来清bss所有段 */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_LED_on
#endif
该部分的主要功能是拷贝u-boot到SDRAM,接下来继续往下看
/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
ldr r0, _nand_boot_ofs
mov pc, r0
_nand_boot_ofs:
.word nand_boot
#else
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
_board_init_r_ofs:
.word board_init_r - _start
#endif
_rel_dyn_start_ofs:
.word __rel_dyn_start - _start
_rel_dyn_end_ofs:
.word __rel_dyn_end - _start
_dynsym_start_ofs:
.word __dynsym_start - _start
这段代码是跳转到nand_boot
函数处执行bootloader
的第二阶段,可以留意代码中的英文注释。
自此bootloader
的第一阶段结束,对第一阶段进行总结:
- 执行设置CPSR程序程序状态寄存器为管理模式
- 关看门狗
- 屏蔽中断
- 初始化时钟
- 关闭MMU,初始化SDRAM
- 设置栈
- 重定位(代码从Flash拷贝至SDRAM中)
- 清bss段(未初始的全局/静态变量)
- 跳转到
nand_boot
函数(位于u-boot-1.1.6/lib_arm/borad.c,用来实现第2阶段硬件相关的初始化)
第二阶段后面有时间再写~
以下为参考的博客,侵删~
第1阶段——uboot分析之硬件初始化start.S(4)