首先分析一下u-boot的链接脚本,这样就能够知道u-boot本身的大体组成及分布,如果想更详细的了解,可以查看生成的u-boot.map文件,这个文件就能看出u-boot各个段的排布。在上一篇文章中,已经完成了u-boot的编译,在u-boot根目录下可以看到生成了一个u-boot.lds文件,这个文件就是u-boot的链接脚本,它是由arch\arm\cpu\u-boot.lds文件经过处理后得到的,其内容如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
/*整个u-boot的入口*/
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
/*这个和以前旧的u-boot版本不一样,这个现在被定义在arch/arm/lib/sections.c文件中,
对应头文件为include/asm-generic/sections.h,
用的是零长数组来实现,不占内存空间,相当于只是放了一个符号,u-boot重定位时就是从这个地址开始拷贝*/
*(.__image_copy_start)
/*存放向量表的段,位于arch/arm/lib/vectors.S文件*/
*(.vectors)
/*这个就相当于是u-boot代码执行的开始了(但第一句执行的代码不在这儿)*/
arch/arm/cpu/armv7/start.o (.text*)
}
/*表示efi_runtime段的开始*/
.__efi_runtime_start : {
*(.__efi_runtime_start)
}
.efi_runtime : {
*(.text.efi_runtime*)
*(.rodata.efi_runtime*)
*(.data.efi_runtime*)
}
/*表示efi_runtime段的结束*/
.__efi_runtime_stop : {
*(.__efi_runtime_stop)
}
.text_rest :
{
/*u-boot代码段*/
*(.text*)
}
. = ALIGN(4);
/*u-boot只读数据段*/
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
/*u-boot数据段*/
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
/*u-boot自定义段,u-boot命令、硬件驱动、环境变量相关的一些东西等放在此段(*号是通配符),具体的段可以查看u-boot.map文件*/
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.efi_runtime_rel_start :
{
*(.__efi_runtime_rel_start)
}
.efi_runtime_rel : {
*(.rel*.efi_runtime)
*(.rel*.efi_runtime.*)
}
.efi_runtime_rel_stop :
{
*(.__efi_runtime_rel_stop)
}
. = ALIGN(4);
.image_copy_end :
{
/*u-boot重定位拷贝结束的地址*/
*(.__image_copy_end)
}
.rel_dyn_start :
{
/*动态符号表的开始*/
*(.__rel_dyn_start)
}
.rel.dyn : {
/*放置了动态符号表,也是重定位的时候需要的*/
*(.rel*)
}
.rel_dyn_end :
{
/*动态符号表的结束*/
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
/*整个u-boot bin文件的结束*/
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
/*MMU页表相关*/
*(.mmutable)
}
/*bss段,建立C语言运行环境的时候需要*/
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
/*后面是一些其它的段*/
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
从其内容可以得知整个u-boot程序的入口为_start这个标号,在以前的u-boot中,入口都是在start.S文件中,后面新增了一个vectors.S文件,现在_start标号就位于此文件中,所以先从此文件开始分析。
arch/arm/lib/vectors.S
/*向量表的定义*/
.macro ARM_VECTORS
/*未定义*/
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
/*从这儿可以看到,芯片上电后立马就会执行复位*/
b reset
#endif
/*这里只是相当于只是一个函数指针,真正的实现在本文件的后面*/
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
.endm
/*从这儿可以看出,向量表被放到了专门的.vectors段中*/
.section ".vectors", "ax"
/*未定义,这是给有些芯片用来在u-boot头部定义一些数据使用的*/
#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/*
省略注释
*/
#include
#else
/*
省略注释
*/
/*这就是整个u-boot的入口*/
_start:
/*未定义*/
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
/*放置的向量表,定义在此文件的上面,和C语言的宏定义一样*/
ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
下面的内容相当于给异常处理的函数指针绑定一个函数实现的实体:
/*未定义*/
#ifdef CONFIG_ARCH_K3
_reset: .word reset
#endif
/*这里定义了异常产生后,该去哪儿执行异常处理的过程*/
_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
以undefined_instruction为例,产生异常后,先执行ldr pc, _undefined_instruction,它会将undefined_instruction这个过程(函数)地址加载到pc指针中,这样就相当于调用undefined_instruction这个函数了:
/*32字节对齐,2的5次方*/
.align 5
undefined_instruction:
/*下面这些过程都是在本文件实现的,就不详细介绍了*/
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
向量表分析了,接着执行了b reset后,下面就进入arch/arm/cpu/armv7/start.S文件了:
reset:
/* Allow the board to save important registers */
/*什么也没做*/
b save_boot_params
save_boot_params_ret:
/*未定义*/
#ifdef CONFIG_ARMV7_LPAE
/*
* check for Hypervisor support
*/
mrc p15, 0, r0, c0, c1, 1 @ read ID_PFR1
and r0, r0, #CPUID_ARM_VIRT_MASK @ mask virtualization bits
cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
要看懂后面的内容,先得了解armv7架构的一些内容,需要参考arm的相关手册(ARMv7-A -R Architecture Reference Manual.pdf),先看一下cpsr寄存器各个位的定义:
其中与模式相关的位是bit[4:0],详细的定义参见下图,可以看到armv7总共有九种模式:
再来看一下关于中断和异常相关的位描述:
现在继续看后面的程序就轻松很多了,下面这段内容就是关闭中断,并且设置CPU进入SVC32模式,关于其过程这里就不推导了,对照上面的图,很简单就能够推导出来:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
/*看是否处于HYP模式*/
teq r1, #0x1a @ test for HYP mode
/*如果未在HYP模式就设置为SVC模式*/
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
继续往后看,是协处理器相关的内容,还是一样,先从手册找到其相关的描述,建议先了解一下关于协处理器指令的使用方法,再理解这段代码就会轻松很多:
下面看代码:
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
#ifdef CONFIG_HAS_VBAR
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
#endif
从cp15协处理器SCTLR寄存器V位的描述来看,设置为0的话,向量表基地址就是0x00000000,并且向量表基地址是可以被重映射的,而设置为1的话,向量表基地址就是0xffff0000,不能被重映射,因为后面u-boot会被拷贝到DRAM中运行,如果向量表还在内部IRAM的话,如果我们想使用中断,那就访问不到了,所以得将向量表基地址重映射,这样产生中断后才能正确的被响应,因此这里将V位设置为0允许重映射,然后将向量表基地址设置为_start这个标号所在的地址,从前面的内容我们知道,_start标号开始的地方就是放置的中断向量表。
接下来cpu_init_cp15函数又是对协处理器的一堆操作,就不详细讲了,想继续深入分析的可以参考ARMv7-A -R Architecture Reference Manual.pdf文档,直接点击链接就可下载,下面简要的注释一下相关的内容:
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
/*TLB无效*/
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
/*指令缓存无效*/
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
/*分支预测无效*/
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
/*数据和指令同步屏障*/
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
/*设置向量表基地址,前面已经设置过*/
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
/*数据和缓存一致性失能,对齐错误检查失能,MMU失能*/
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
/*对齐错误检查使能*/
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
/*程序流预测使能*/
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
/*未定义*/
#if CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
/*指令缓存使能*/
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
/*勘误相关的都没有定义*/
/*
省略勘误相关的内容
*/
mov r5, lr @ Store my Caller
/*读取芯片的一些信息,如版本号、架构等,但实际上没用到*/
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)
mov r3, r1, lsr #20 @ get variant field
and r3, r3, #0xf @ r3 has CPU variant
and r4, r1, #0xf @ r4 has CPU revision
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2 has combined CPU variant + revision
/* Early stack for ERRATA that needs into call C code */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
/*设置一个临时的栈用于下面的勘误相关内容的执行,这个地址三星设置在了DRAM里面,
要是现在我们还没有初始化DRAM,那执行C函数的时候,程序肯定就跑飞了,好在这部分内容都没有执行*/
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
/*
省略勘误相关的内容
*/
/*这里就返回了*/
mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)
到了这里,关于arm内核相关的东西就结束了,后面就是SOC相关的内容了。