最近一直在学习编写Bootloader,对于Bootloader也有了一些简单的认识。Vboot作为一个非常精简的bootloader程序,是十分值得刚入门的学习。把Vboot的源代码认真分析一遍之后,再去看其他bootloader,比如supervivi、u-boot等,应该就会好理解一些。
值得注意的是,vboot只有最基本的内核引导功能,基于S3C2440,从vboot启动。其他CPU芯片如果需要使用vboot需要根据实际情况来进行改写。
为了阅读源代码,我使用了一款工具Source Insight,目前还比较生疏,正在慢慢熟悉中。
vboot主要代码有三个:head.s main.c nand.c,其余文件都是一些头文件。从文件名称上来看,head.s由汇编语言编写,作为bootloader引导的第一个阶段,主要实现了硬件初始化(包括禁用看门狗,禁用所有中断,初始化系统时钟等)、初始化RAM为第二阶段做准备,初始化串口;main.c作为bootloader第二个阶段的入口。主要实现功能为:使能Dcache和Icache,初始化一些IO口,初始化Nand Flash,设置了内核映像在nand flash的起始地址和大小,还有设置内核映像被拷贝到ram的起始地址,然后开始从Nand Flash读,加载内核等。
- SECTIONS {
- . = 000000;
- .myhead ALIGN(0): {*(.text.FirstSector)}
- .text ALIGN(512): { *(.text) }
- .bss ALIGN(4) : { *(.bss*) *(COMMON) }
- .data ALIGN(4) : { *(.data*) *(.rodata*) }
- }
程序入口位于text.FirstSector这个段里(因为程序是从nand flash的0地址开始执行的),它在head.S文件里定义:
- @
- @ Exception vector table (physical address = 0×00000000)
- @
- .section .text.FirstSector
- .globl first_sector
- first_sector:
- @ 0×00: Reset
- b Reset
- @ 0×04: Undefined instruction exception
- UndefEntryPoint:
- b UndefEntryPoint
- @ 0×08: Software interrupt exception
- SWIEntryPoint:
- b SWIEntryPoint
- @ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
- PrefetchAbortEnteryPoint:
- b PrefetchAbortEnteryPoint
- @ 0×10: Data Access Memory Abort
- DataAbortEntryPoint:
- b DataAbortEntryPoint
- @ 0×14: Not used
- NotUsedEntryPoint:
- b NotUsedEntryPoint
- @ 0×18: IRQ(Interrupt Request) exception
- IRQEntryPoint:
- b IRQHandle
- @ 0x1c: FIQ(Fast Interrupt Request) exception
- FIQEntryPoint:
- b FIQEntryPoint
- @0×20: Fixed address global value. will be replaced by downloader.
- .long ZBOOT_MAGIC
- .byte OS_TYPE, HAS_NAND_BIOS, (LOGO_POS & 0xFF), ((LOGO_POS >>8) &0xFF)
- .long OS_START
- .long OS_LENGTH
- .long OS_RAM_START
- .string LINUX_CMD_LINE
其中7到37行表示安装异常向量表,在下面将会看到reset向量的具体执行代码,如下:
- .section .text
- eset:
- @ disable watch dog timer (禁用看门狗,向0×53000000寄存器中写值即可)
- mov r1, #0×53000000
- mov r2, #0×0
- str r2, [r1]
- @ disable all interrupts (禁用所有中断,关闭掩码寄存器中所有位即可,也就是赋1)
- mov r1, #INT_CTL_BASE
- mov r2, #0xffffffff
- str r2, [r1, #oINTMSK]
- ldr r2, =0x7ff
- str r2, [r1, #oINTSUBMSK]
- @ initialise system clocks (初始化系统时钟)
- mov r1, #CLK_CTL_BASE
- mvn r2, #0xff000000
- str r2, [r1, #oLOCKTIME]
- mov r1, #CLK_CTL_BASE
- ldr r2, clkdivn_value
- str r2, [r1, #oCLKDIVN]
- @set the asynchronous (到这里时钟即将分为三个频率,所以需要设置异步总线模式,应该是用到了协寄存器)
- mrc p15, 0, r1, c1, c0, 0 @ read ctrl register
- orr r1, r1, #0xc0000000 @ Asynchronous
- mcr p15, 0, r1, c1, c0, 0 @ write ctrl register
- mov r1, #CLK_CTL_BASE
- ldr r2, =S3C2440_UPLL_48MHZ_Fin12MHz
- str r2, [r1, #oUPLLCON]
- @(连续多个短延时)
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- nop
- ldr sp, DW_STACK_START @ setup stack pointer (设置栈指针,为main函数中的C语言程序做准备)
- ldr r2, mpll_value_USER @ clock user set 12MHz (设置FCLK=400MHz,HCLK=200MHz,PCLK=100MHz)
- str r2, [r1, #oMPLLCON]
- bl memsetup (跳到内存初始化函数)
- @ set GPIO for UART (初始化串口,为打印提示消息做准备:115200 8N1)
- mov r1, #GPIO_CTL_BASE
- add r1, r1, #oGPIO_H
- ldr r2, gpio_con_uart
- str r2, [r1, #oGPIO_CON]
- ldr r2, gpio_up_uart
- str r2, [r1, #oGPIO_UP]
- bl InitUART
- @ get read to call C functions (设置fp和a2寄存器)
- mov fp, #0 @ no previous frame, so fp=0
- mov a2, #0 @ set argv to NULL
- bl Main (跳到main函数执行)。
- : b 1b @
上述代码,16-18行,设置了系统时钟稳定(锁定)时间,20-22行,设置时钟分频比1:4:8,其他说明在上述已经标明。下面是上述程序调用的一个子例程,memsetup:
- memsetup:
- @ initialise the static memory
- @ set memory control registers
- mov r1, #MEM_CTL_BASE
- adrl r2, mem_cfg_val @ adrl跳转?
- add r3, r1, #52
- 1: ldr r4, [r2], #4
- str r4, [r1], #4
- cmp r1, r3
- bne 1b
- mov pc, lr
由于S3C2440是13个内存设置相关的寄存器,因此第7行是13*4=52,第8-11行依次循环设置13个寄存器。
到这里,汇编代码已经执行完毕,接下来进入C语言执行阶段。