u-boot2020.04移植(2、从链接脚本开始)

首先分析一下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 <asm/arch/boot0.h>
#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寄存器各个位的定义:

图1

 其中与模式相关的位是bit[4:0],详细的定义参见下图,可以看到armv7总共有九种模式:

图2

再来看一下关于中断和异常相关的位描述:

图3

现在继续看后面的程序就轻松很多了,下面这段内容就是关闭中断,并且设置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

继续往后看,是协处理器相关的内容,还是一样,先从手册找到其相关的描述,建议先了解一下关于协处理器指令的使用方法,再理解这段代码就会轻松很多:

扫描二维码关注公众号,回复: 11479100 查看本文章
图4 关于SCTLR寄存器的位定义
图5 关于V位的描述

 下面看代码:

#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相关的内容了。

欢迎扫码关注我的微信公众号

漫长当下

猜你喜欢

转载自blog.csdn.net/a1598025967/article/details/106878100