本文以ast2500evb板子为例来说明uboot的启动过程:
在uboot/Makefile中,我们知道uboot.bin依赖uboot, uboot是由各种*.o链接而成,使用的链接脚本为uboot/board/ast2500evb/u-boot.lds.
在uboot/board/ast2500evb/u-boot.lds定义了程序的入口_start, 且其地址为0x00000000.
Arm上电后,从reset vector address处取指令来run。
所以,当我们编出u-boot.bin文件时,需要将其烧写到flash的0地址处,且falsh要映射到arm的0地址处。从u-boot.lds的描述看,在flash 0地址处是符号_start.
这个_start定义可以由uboot/Makefile中的$(OBJS)来确定。
uboot/Makefile
其中$(CPUDIR)定义在uboot/config.mk中。
uboot/config.mk
$(ARCH), $(CPU)定义在由make xxx_config产生的uboot/include/config.mk中。
所以,对于ast2500evb板子来说,u-boot.bin的入口_start定义在uboot/arch/arm/cpu/arm1176/start.S
Start.S的入口符号_start,直接来个跳转指令.
程序直接跳转到reset处执行。(跳转指令后面的ldr指令是cpu要求的,加载异常处理程序的指令,如软中断,快速中断,中断等)
我们来看一下reset做什么了。
‘reset’ located the same file with ‘_start’(uboot/arch/arm/cpu/arm1176/start.S)
几个指令介绍下:
mrs, msr:
bic, orr:
‘reset’ 操作主要是修改cpsr的值。
150行加载cpsr寄存器的值到寄存器r0;
151行清除bit0-bit5;
152行设置M域为0b10011,即supervisor模式,设置I,F,T位,即disable中断,disable快速中断,设置ARM指令模式;
cpsr寄存器的结构如下:
配置完cpsr寄存器后,到cpu_init_crit:
从上述代码看,主要是配置协处理器,arm1176协处理器的作用如下:
178行,设置r0寄存器为0;
179行,使得I-cache和D-cache失效(也叫刷新I-cache和D-cache);
180行,使得I-TLB和D-TLB失效(也叫刷新指令TLB和数据TLB),TLB全称translation lookaside buffer.
处理完cache和TLB后,处理MMU:
185行,读取协处理器p15的控制寄存器c1。
p15协处理器c1寄存器结构:
186行,清除V,R,S位。
清除V位,表明异常向量基地址使用c12 secure or non-sercure vector base address register配置的基地址。
187行,清除C,A,M位,即,disable L1-D-cache, disable MMU。
188行,设置A位,即,使能alignment fault checking.
189行,设置I位,即使能L1 I-cache.
配置好协处理器p15的c1寄存器(控制寄存器域)数据后,跳转到mmu_disable。
201行,将前面的协处理器p15的c1寄存器(控制寄存器域)配置过的数据重新导入到协处理器p15的c1寄存器(控制寄存器域),此时上面的对协处理器p15的c1寄存器(控制寄存器域)的设置就生效了,即disable了MMU, enable了L1 I-cache。
接下去,就到了237行了。
这里使用了bl指令来跳转到lowlevel_init处执行,bl和b指令的区别是,b指令是无条件跳转,即跳转后,不会回到b指令的下一条指令处;而bl指令使用了link register(r14)保存了bl指令下一条指令的地址,即当bl指令跳转后返回时,会执行bl指令的下一条指令,即 ‘bl _main’。
我们接着看lowlevel_init处代码:
结合uboot/Makefile我们知道lowlevel_init所在的文件:
uboot/Makefile:
uboot/arch/arm/cpu/arm1176/ast2500/Makefile
lowlevel_init在uboot/arch/arm/cpu/arm1176/ast2500/platform.S
294行,保存lr,即保存’b _main’指令地址到r4寄存器。
300-301行,伪指令,加载0x1e600000, 0xaeed1a03到寄存器r0和r1。
302行,将0x aeed1a03写到地址0x1e600000处。
这个0x1e600000地址是个什么东西?它是AHBC protection key register:
AHB(high performance bus)为系统总线,即ARM 连接使用的总线。
如上图AHBC00的描述,向AHBC00(0x1e600000, AHBC protection key register)写入0x aeed1a03就是设置unlocked.
305行,是向0x1e600084地址处(AHBC84: Interrupt Control/Status Register)写入0x00010000。
向AHBC84写入0x00010000,即向bit16 写入1,相当于清除该位,即清除总线锁中断状态。
308行,向0x1e600088地址处(AHBC88: AHB Bus Target Disable Control Register)写入0.
向AHBC88写入0,相当于disable上图显示的所有功能,比如,target SPI2/SPI3, VIC。即,此时,cpu不可通过总线访问这些。
类似地:
312行通过写0x1688a8a8到SCU(system control unit)寄存器SCU00(protection key register)
这里,就相当于配置unlock SCU registers.
318行,通过SCU70寄存器来检查eSPI(enhanced SPI)是否由flash attach.
319行,如果有flash attach, 即SCU70 bit26为1,则跳转到bypass_first_reset.
我们假定没有flash attach,接下去看代码:
0x1e785xxx为看门狗定时器寄存器基址。
0x1e785010为WDT10: WDT1 Timeout Status Register。
324行,判断到当前为止,是否发生过看门狗定时器发生过超时事件。没有,则跳转到start_first_reset.
start_first_reset第一个宏ASTMMC_INIT_RESET_MODE_FULL是没有定义的,我们直接看358开始的代码:
从357行开始到364行,都是在配置下面相关的寄存器:
基址0x1e78:7xxx和0x1e78:8xxx:
程序先配置virtual UART, virtual UART的功能描述如下:
我们来看一下代码怎么配置的:
361行配置FIFO Control Register为0b111, 即,reset VUART的FIFO.
接下去,配置VUART其他寄存器,直到配置PUART0x1e788000.
R2为0,这里就是disable UART的pass through.
接下去,配置PUART其他寄存器,直到LPC controller: 0x1e789000.
基址0x1e78:9xxx:
388行,对寄存器HICR2清零。
基址0x1e62:0xxx:
这个是配置firmwars SPI flash的,即ast2500evb存放uboot的flash。
基址0x1e78:5xxx:
517行,reset各种controller.
520行复位整个系统。
523行就是一直等待看门狗定时器超时后系统复位reset。复位之后,代码又从_start开始执行。
系统复位后,再次运行到lowlevel_init时:
这时,324行判断时,发现系统看门狗定时器发生过超时事件,故不跳转。程序就执行325行了。
325行之后,接着做一些配置,最终跳转到bypass_first_reset处执行。
我们接着看bypass_first_reset.
首先配置定时器。
这里写0XAE,即配置寄存器TMC38的bit0为1,即separate mode.
538行这里写TMC3C寄存器’1’。
结合533和538行可知,这里是将定时器的计数清零,并且disable掉。
547行,将定时器enable起来。
558行,设置dram初始化为SOC Firmware.
接下去,配置USB等,以及配置DDR内存。
Bypass_USB_init会向debug的UART口输出打印:DRAM Init-V
对于ddr4,再输出字符”4”
我们接着看DDR4。
0x1e6e0004为sdram的配置寄存器MCR04.
1168行设置MCR04为0x313, 即设置一次最大可写入的memory size为1G.
接下去配置内存的时序等信息。
配置完后,输出一个字符”0”
ASTMMC_DDR4_MANUAL_RPU没有使能,跳过这段。
如1315行的注释描述的程序后面最的事情,我们不详细看了,跳过。
程序运行轨迹: ddr_phy_init_process -> ddr_phy_init_success -> ddr4_phyinit_done -> Calibration_End
Calibration_End就是check一下DRAM size:
之后init DRAM cache.
Init DRAM cache后做一堆的check,然后跳转到set_scratch.
Set_scratch做一堆配置,比如MAC配置等,最后将之前保存的lr传给pc,这样pc就跑到了_main处了。
这个_main定义在哪里?我们来看uboot/Makefile
uboot/Makefile
uboot/arch/arm/lib/Makefile
故,_main为定义在uboot/arch/arm/lib/crt0.S中。
从注释来看,_main是要准备C语言运行环境的,即要设置好栈指针(SP).
83行配置了sp地址为CONFIG_SYS_INIT_SP_ADDR。
这个CONFIG_SYS_INIT_SP_ADDR在哪定义?
在crt0S的25行include了config.h, 这个config.h为uboot/include/config.h.
我们从Makefile中可知,头文件的搜索路径:
uboot/config.mk(被uboot/arch/arm/lib/Makefile包含)
uboot/include/config.h
在uboot/include/config.h的9行include了uboot/include/configs/ast2500evb.h.
uboot/include/configs/ast2500evb.h
在uboot/include/configs/ast2500evb的行9 include了configs/common.cfg.
在这个uboot/include/configs/common.cfg中定义了CONFIG_SYS_INIT_SP_ADDR。
上述定义中的CONFIG_SYS_SDRAM_BASE定义在uboot/include/configs/ast2500evb.h中,如下:
由上面的定义可知,这个板子的SDRAM在CPU的地址空间为基址0x80000000(2G), 当前阶段的栈指针为0x80000000 + 16K, 即此时栈的大小为16K. 2G之前是排给flash的(注意,这个CONFIG_SYS_SDRAM_BASE在uboot/include/configs/ast.cfg中也定义多了,但是被uboot/include/configs/ast2500evb.h覆盖)。
我们回到uboot/arch/arm/lib/crt0.S继续分析代码:
设置好栈指针后,我们在栈顶预留GD_SIZE的内存给struct global_data.
GD_SIZE定义在uboot/include/generated/generic-asm-offsets.h
Crt0.S在26行包含了头文件uboot/include/asm-offsets.h, 而asm-offsets.h在3行包含了头文件generated/generic-asm-offsets.h
uboot/arch/arm/lib/crt0.S
uboot/include/asm-offsets.h
uboot/include/generated/generic-asm-offsets.h
crt0.S在预留了struct global_data结构后,将这个结构的地址给r8保存,并设置r0为0(board_init_f的参数),之后跳转到board_init_f这个C函数处理(C语言使用的堆栈环境构建好了,虽然栈只有12K – GD_SIZE这么大小)。