在前面我们已经分析了4412的启动流程以及编译4412的Uboot的流程,这两个流程其实是并不适用于所有芯片的,可以说是4412的特有流程,别的芯片可能类似但是差别不会太小,这次我们分析Uboot的启动过程,这是从一上电到执行main_loop之前的操作,这个部分会有一部分的汇编代码,因为刚刚上电时系统并未准备好C语言的运行环境,需要使用汇编代码来搭建C语言的运行环境,搬移C代码,然后跳转到C语言中执行,所以说这个启动过程可以分为两个部分
当我们执行make命令来构建u-boot时,它的构建过程是:首先使用交叉编译工具将各目录下的源文件生成目标文件(.o),目标文件生成后,会将若干个目标文件组合成静态库文件(.a),最后通过链接各个静态库文件生成ELF格式的可执行文件。在链接的过程中,需要根据链接脚本(一般是各个以lds为后缀的文本文件),确定目标文件的各个段,链接文件是在uboot目录中的u-boot.lds文件。一般在链接脚本中通过
ENTRY(_start)
来指定入口为_start标号,通过文本段(.text)的第一个目标来指定Uboot入口文件。所以我们通过这个链接脚本文件可以确Uboot执行的入口
iTop-4412 Uboot的连接脚本内容为
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm_cortexa9/start.o (.text)
cpu/arm_cortexa9/s5pc210/cpu_init.o (.text)
board/samsung/smdkc210/lowlevel_init.o (.text)
common/ace_sha1.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.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);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
在本链接脚本文件中,定义了起始地址为0x00000000,每个段使用4字节对齐(.ALIGN(4)),几个段分别为代码段(.text)、只读数据段(.rodata)、数据段(.data)其中,代码段的第一个目标为cpu/arm_cortexa9/start.o,在其中定义了映像文件的入口_start
接下来我们分析start.s文件
1、首先进行了中断向量的设置,并且跳转到了reset
.globl _start //程序的全局入口,u-boot.lds中设置此入口地址为0x0000_0000
_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 //快速中断异常
//word的意思是将后边的符号所对应的32bit值赋予前面的符号
//下面的七条语句后面的符号正好是对应的中断异常服务程序的入口地址
//七个中断服务程序位于\cpu\arm_cortexa9\s5pc210\interrupts.c
_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
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef //16bytes对齐,并且使用0xdeadbeef填充
/*.balignl是.balign的变体
.align伪操作用于表示对齐方式:通过添加填充字节使当前位置满足一定的对齐方式。
.balign的作用同.align。
.align {alignment} {,fill} {,max}
其中:
alignment用于指定对齐方式,可能的取值为2的次幂,缺省为4。
fill是填充内容,缺省用0填充。
max是填充字节数最大值,如果填充字节数超过max,就不进行对齐*/
2、接下来我们分析reset,reset中首先设置CPU进入SVC32模式,然后关闭了IRQ和FIQ;接下来初始化了cache,然后通过以下的方式获取OM PIN的状态,以此确定CPU的启动模式
/* Read booting information */
ldr r0, =POWER_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
3、接下来根据r2寄存器的值来选取启动模式,我们的是从emmc启动所以如下,比较r2和0x28,如果相等将r3置为BOOT_EMMC441
/* eMMC441 BOOT */
cmp r2, #0x28
moveq r3, #BOOT_EMMC441
4、紧接着,我们的程序会跳转到lowlevel_init,去初始化PLL、MUX、MEM
bl lowlevel_init /* go setup pll,mux,memory */
5、我们看一下lowlevel_init 的代码
在lowlevel_init 的代码中调用了system_clock_init_scp来初始化系统的时钟,调用了mem_ctrl_asm_init_ddr3来初始化内存,又调用了tzpc_init来初始化TrustZone Protection Controller,
/* init system clock */
bl system_clock_init_scp
/* Memory initialize */
bl mem_ctrl_asm_init_ddr3
bl tzpc_init
接下来初始化了串口,方便调试,最后POP PC寄存器,回到start.s中
/* for UART */
bl uart_asm_init
pop {pc}
6、回到start.s后,配置寄存器使PS_HOLD输出高电平
ldr r0, =0x1002330C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005300 /* PS_HOLD output high */
str r1, [r0]
7、配置栈指针,准备进入C环境
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
8、接下来会进行一个延时操作
/* wait ?us */
mov r1, #0x10000
9: subs r1, r1, #1
bne 9b
9、然后跳转到emmc441_boot,进行代码从emmc到内存的复制,当复制完成后初始化MMU
cmp r1, #BOOT_EMMC441
beq emmc441_boot
emmc441_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
ldr r0, =CMU_BASE
ldr r2, =CLK_DIV_FSYS3_OFFSET
ldr r1, [r0, r2]
orr r1, r1, #0x3
str r1, [r0, r2]
#endif
bl emmc441_uboot_copy
//ly 20110824
ldr r0, =0x43e00000
ldr r1, [r0]
ldr r2, =0x2000
cmp r1, r2
bne second_mmcsd_boot
b after_copy
after_copy:
#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
/* Set the TTB register */
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
/* Enable the 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
10、这时初始化的操作已经基本完成,配置栈,清除bss段,准备进入C语言环境
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#if defined(CONFIG_USE_IRQ)
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
#endif
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
11、最后设置PC指针为_start_armboot,进入start_armboot函数,这就进入了C语言环境
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
start_armboot位于\lib_arm\board.c中
首先我们先看两个比较重要的结构体gd_t和bd_t,在初始化操作很多都要靠这两个数据结构来保存和传递
gd_t定义在\include\asm-arm\global_data.h中,其成员主要是一些全局的系统初始化参数。当使用gd_t时需用宏定义进行声明:DECLARE_GLOBAL_DATA_PTR,指定占用寄存器R8。
这个结构体其实可以有很多的成员变量,大部分是根据配置的宏来确定是否编译的,我删除了其中没有被定义的部分,剩下的就是我们会使用到的成员变量
typedef struct global_data {
volatile bd_t *bd; //与板子相关的结构
volatile unsigned long flags;//只是标志,如设备已经初始化标志等
volatile unsigned long baudrate;//串口波特率
volatile unsigned long have_console; /* serial_init() was called 串口初始化标志*/
volatile unsigned long env_addr; /* Address of Environment struct 环境变量参数地址*/
volatile unsigned long env_valid; /* Checksum of Environment valid? 检测环境变量参数是否有效*/
volatile unsigned long fb_base; /*frame buffer的基地址*/
#ifdef CONFIG_VFD
volatile unsigned char vfd_type; /* 显示类型 */
#endif
#ifdef CONFIG_FSL_ESDHC
volatile unsigned long sdhc_clk;//
#endif
volatile void **jt; /* jump table */
} gd_t;
gd_t定义在\include\asm-arm\u-boot.h
board info数据结构定义,主要是用来保存板子参数。
typedef struct bd_info {
int bi_baudrate; /* 串口终端的波特率*/
unsigned long bi_ip_addr; /* IP地址 */
struct environment_s *bi_env; //环境变量开始地址
//开发板ID 该变量标识每一种开发板相关的ID,该值将传递给内核,如果这个参数与内核配置的不相同,那么内核启动解压缩完成后将出现“Error:a”错误,开发板ID可以在内核arch/arm/tools/mach-types中查看*/
ulong bi_arch_number; /* unique id for this board */
//传递给内核的参数保存地址
ulong bi_boot_params; /* where this board expects params */
//内存的起始地址及大小
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
这是我第一次阅读分析Uboot的源码,这次着重分析的是整个的流程,而不是内部的各种细节,所以说对于start_armboot的分析我会比较浮于表面,不会去分析具体某个模块的初始化
1、首先定义了一些变量
init_fnc_t **init_fnc_ptr;
char *s;
int mmc_exist = 0;
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
2、接下来是给gd_t结构体的空间分配
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
这里面有几个要点
(1)gd这个指针事怎么来的,没有malloc,我们也无法转到定义,这个gd就好像是凭空出现。通过对源码的分析我们可以得到,在\lib_arm\board.c的73行有一行代码
DECLARE_GLOBAL_DATA_PTR;
转到定义后,到了\include\asm-arm\global_data.h,它是这样定义的
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。
register表示变量放在机器的寄存器
volatile用于指定变量的值可以由外部过程异步修改。
并且在上面start_armboot的代码中gd指针被初始化,指向了一个可写的地址,这样的gd就可以使用了
(2)在初始化gd后还有一行内联汇编比较难懂
__asm__ __volatile__("": : :"memory");
这里是一个特殊的用法:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
// init_fnc_t 定义如下
init_fnc_t *init_fnc_t [] = {
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
//#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
//#endif
//timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
//get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
off_charge, // xiebin.wang @ 20110531,for charger&power off device.
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 */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
//arm_pci_init,
#endif
display_dram_config,
NULL,
};
可以看出这个数组存放的就是一下函数指针,在for循环中会被循环调用,这里会初始化板级硬件接口,串口就可以使用了
4、接下来还有几个比较重要的初始化
env_relocate ();//初始化环境
jumptable_init ();//安装系统函数指针
console_init_r ();//完全初始化中断为一个设备
enable_interrupts ();//使能中断
5、接下来就进入了main_loop
for (;;) {
main_loop ();
}
main_loop中将会处理用户输入的命令以及完成对操作系统的引导,我们下篇博客将分析Uboot引导内核的流程