上节烧写了uboot到开发板,不能运行。这节我们分析uboot重新编译uboot,由最后一条链接命令开始分析uboot

  下图为编译uboot后显示的最后一条链接命令。

1.分析start.S

  打开uboot.lds,发现链接地址为0,所以新的uboot只能在nor flash运行。运行开始文件为start.o。



  下面我们分析arch/arm/cpu/arm920t/start.S

  从start_code开始运行

.globl _start                                //声明_start全局符号,这个符号会被lds链接脚本用到
_start:
b start_code //跳转到start_code符号处,0x00
ldr pc, _undefined_instruction //0x04
ldr pc, _software_interrupt //0x08
ldr pc, _prefetch_abort //0x0c
ldr pc, _data_abort //0x10
ldr pc, _not_used //0x14
ldr pc, _irq //0x18
ldr pc, _fiq //0x20 _undefined_instruction: .word undefined_instruction
//定义_undefined_instruction指向undefined_instruction(32位地址) _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 .balignl 16,0xdeadbeef //balignl使用,参考http://www.cnblogs.com/lifexy/p/7171507.html

2._start会跳转到start_code处

start_code:

    /*设置CPSR寄存器,让CPU进入管理模式*/
mrs r0, cpsr //读出cpsr的值
bic r0, r0, #0x1f //清位
orr r0, r0, #0xd3 //位或
msr cpsr, r0 //写入cpsr #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start
ldr r1, =0x0 //r1等于异常向量基地址
mov r2, #16
copyex:
subs r2, r2, #1 //减16次,s表示每次减都要更新条件标志位
ldr r3, [r0], #4
str r3, [r1], #4 //将_start标号后的16个符号存到异常向量基地址0x0~0x3c处
bne copyex //直到r2减为0
#endif #ifdef CONFIG_S3C24X0 /* 关看门狗*/
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */ ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关看门狗,使WTCON寄存器=0 /*关中断*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] //关闭所有中断
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0] //关闭次级所有中断
# endif /* 设置时钟频率, FCLK:HCLK:PCLK = 1:2:4 ,而FCLK默认为120Mhz*/
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0] #ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //关闭mmu,并初始化各个bank #endif call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
bic sp, sp, #7 //sp=0x30000f80
ldr r0,=0x00000000
bl board_init_f

  上面的CONFIG_SYS_INIT_SP_ADDR =0x30000f80,是通过arm-linux-objdump -D u-boot>u-boot.dis生成反汇编,然后从u-boot.dis得到的。

3.然后进入第一个C数:board_init_f()

  该函数主要工作是:

  清空gd指向的结构体、通过init_sequence函数数组,来初  始化各个函数以及逐步填充gd结构体,最后划分内存区域,  将数据保存在gd里,然后调用relocate_code()对uboot重定位。

  (gd是用来传递给内核的参数)

1)具体代码如下所示:

void board_init_f(ulong bootflag) // bootflag=0x00000000
{
bd_t *bd;
init_fnc_t **init_fnc_ptr; //函数指针
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
/* Pointer is writable since we allocated a register for it */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);

  其中gd是一个全局变量,用来传递给内核的参数用的,如下图所示,在arch/arn/include/asm/global_data.h中定义,*gd指向r8寄存器,所以r8专门提供给gd使用



  而CONFIG_SYS_INIT_SP_ADDR,在之前得到=0x30000f80,所以gd=0x30000f80

2)继续来看board_init_f():

__asm__ __volatile__("": : :"memory");           //memory:让cpu重新读取内存的数据

      memset((void *)gd, 0, sizeof(gd_t));        //将0x30000f80地址上的gd_t结构体清0

      gd->mon_len = _bss_end_ofs;
// _bss_end_ofs =__bss_end__ - _start,在反汇编找到等于0xae4e0,所以mon_len等于uboot的数据长度
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob); for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
//调用init_sequence[]数组里的各个函数
{
if ((*init_fnc_ptr)() != 0) //执行函数,若函数执行出错,则进入hang()
{
           hang (); //打印错误信息,然后一直while
} }

  上面的init_sequence[]数组里存了各个函数,比如有:

  board_early_init_f():设置系统时钟,设置各个GPIO引脚

  timer_init():初始化定时器

  env_init():设置gd的成员变量

  init_baudrate():设置波特率

  dram_init():设置gd->ram_size= 0x04000000(64MB)

3)继续来看board_init_f():

addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;  // addr=0x34000000
// CONFIG_SYS_SDRAM_BASE: SDRAM基地址,为0X30000000
// gd->ram_size: 等于0x04000000 #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4); //addr=33FFC000 addr &= ~(0x10000 - 1); // addr=33FF0000, gd->tlb_addr = addr; //将64kB分配给TLB,所以TLB地址为33FF0000~33FFFFFF
#endif /* round down to next 4 kB limit */
addr &= ~(4096 - 1); //4kb对齐, addr=33FF0000
debug("Top of RAM usable for U-Boot at: %08lx\n", addr); /*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len; // 在前面分析过gd->mon_len=0xae4e0,
//所以addr=33FF0000 -0xae4e0=33F41B20, addr &= ~(4096 - 1); //4095=0xfff,4kb对齐, addr=33F41000
//所以分配给uboot各个段的重定位地址为33F41000~33FFFFFF
debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr); #ifndef CONFIG_SPL_BUILD
addr_sp = addr - TOTAL_MALLOC_LEN; //分配一段malloc空间给addr_sp
//TOTAL_MALLOC_LEN=1024*1024*4,所以malloc空间为33BF1000~33F40FFF addr_sp -= sizeof (bd_t); //分配一段bd_t结构体大的空间
    bd = (bd_t *) addr_sp; //bd指向刚刚分配出来的bd_t结构体
    gd->bd = bd; // 0x30000f80处的gd变量的成员bd等于bd_t基地址     addr_sp -= sizeof (gd_t); //分配一个gd_t结构体大的空间
    id = (gd_t *) addr_sp; //id指向刚刚分配的gd_t结构体
    gd->irq_sp = addr_sp; //0x30000f80处的gd变量的成员irq_sp等于gd_t基地址     addr_sp -= 12;
    addr_sp &= ~0x07;
    ... ...     relocate_code(addr_sp, id, addr); //进入relocate_code()函数,重定位代码,以及各个符号
    // addr_sp: 栈顶,该栈顶向上的位置用来存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb),
    //id: 存放 gd_t结构体的首地址
    // addr: 等于存放uboot重定位地址33F41000
}

  执行完board_init_f()后,最终内存会划分如下图所示:



  其实此时uboot还在flash中运行,然后会进入start.S的relocate_code()里进行uboot重定位

4.接下来进入重定位

1)start.S的relocate_code()代码如下所示

relocate_code:
mov r4, r0 /* save addr_sp */ // addr_sp栈顶值
mov r5, r1 /* save addr of gd */ // id值
mov r6, r2 /* save addr of destination */ // addr值:uboot重定位地址 /* Set up the stack */
stack_setup:
mov sp, r4 //设置栈addr_sp
adr r0, _start //在顶层目录下system.map符号文件中找到_start =0,所以r0=0
cmp r0, r6 //判断_start(uboot重定位之前的地址)和addr(重定位地址)是否一样
beq clear_bss /* skip relocation */ mov r1, r6 /* r1 <- scratch for copy_loop */ //r1= addr(重定位地址)
ldr r3, _bss_start_ofs //_bss_start_ofs=__bss_start - _start(uboot代码大小)
add r2, r0, r3 /* r2 <- source end address*/ //r2= uboot重定位之前的结束地址 copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
//将r0处的两个32位数据拷到r9-r10中,然后r0+=8 stmia r1!, {r9-r10} /* copy to target address [r1]*/
//将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8 cmp r0, r2 /* until source end address [r2]*/ //判断拷贝的数据是否到结束地址
blo copy_loop

  上面只是把代码复制到SDRAM上,而链接地址内容却没有改变,比如异常向量0x04的代码内容还是0x1e0,

  我们以异常向量0x04为例,来看它的反汇编:



   如上图所示,即使uboot在SDRAM运行,由于代码没修改,PC也会跳到0x1e0(flash地址)上,和之前老的uboot有很大区别,以前老的uboot直接是使用的SDRAM链接地址,如下图所示:



  所以,新的uboot采用了动态链接地址的方法,在链接脚本uboot.lds中,可以看到这两个段(.rel.dyn、.dynsym):



  该两个段里,便是保存了各个文件的相对动态信息(.rel.dyn)、动态链接地址的符号(.dynsym)

  以上图的.rel.dyn段为例来分析,找到__rel_dyn_start符号处:



  如上图所示,其中0x17表示的是符号的结束标志位,我们以0x20为例来讲解:

  在之前,我们讲过0x20里面保存的是异常向量0x04跳转的地址(0x1e0),如下图所示:



  所以,接下来的代码,便会根据0x20里的值0x1e0(flash地址),将SDRAM的33F41000+0x20的内容改为33F41000+0x1e0(SDRAM地址),来改变uboot的链接地址

2)重定位的剩余代码,如下所示:

#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */ //r0=text段基地址=0
sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位后的偏移值=33F41000
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
//_dynsym_start_ofs =__dynsym_start - _start=0x73608
//所以r10=动态符号表的起始偏移值=0x73608 add r10, r10, r0 /* r10 <- sym table in FLASH */
//r10=flash上的动态符号表基地址=0x73608 ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
//r2=__rel_dyn_start - _start=0x6b568
//所以r2=相对动态信息的起始偏移值=0x6b568 add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
//r2=flash上的相对动态信息基地址=0x6b568 ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
// _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608
//所以r3=相对动态信息的结束偏移值=00073608 add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
//r3=flash上的相对动态信息结束地址=0x6b568 fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
//以0x20为例,r0=0x6b568地址处的内容= 0x20 add r0, r0, r9 /* r0 <- location to fix up in RAM */
//r0=33F41000+0x20=33F41020 ldr r1, [r2, #4] //r1= 33F41024地址处的内容=0x17
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */ //0x17=23,所以相等
beq fixrel //跳到:fixerl cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0] //r1=33F41020地址处的内容=0x1e0
add r1, r1, r9 //r1=0x1e0+33F41000= 33F411e0 fixnext:
str r1, [r0] //改变链接地址里的内容, 33F41020=33F411e0 (之前为0x1e0)
add r2, r2, #8 //r2等于下一个相对动态信息(0x24)的地址
cmp r2, r3 //若没到尾部__rel_dyn_end,便继续执行: fixloop
blo fixloop
#endif

5.清除bss段

/*重定位完成后,清除bss段*/
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs //获取flash上的bss段起始位置
ldr r1, _bss_end_ofs //获取flash上的bss段结束位置
mov r4, r6 /* reloc addr */ //获取r6(SDRAM上的uboot基地址)
add r0, r0, r4 //加上重定位偏移值,得到SDRAM上的bss段起始位置
add r1, r1, r4 //得到SDRAM上的bss段结束位置
mov r2, #0x00000000 /* clear*/ clbss_l:
str r2, [r0] /* clear loop... */ //开始清除SDRAM上的bss段
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_led_on
#endif

5.1继续往下分析

#ifdef CONFIG_NAND_SPL                   //未定义,所以不执行
... ...
#else //执行else ldr r0, _board_init_r_ofs //r0=flash上的board_init_r()函数地址偏移值
adr r1, _start //0
add lr, r0, r1 //返回地址lr=flash上的board_init_r()函数
add lr, lr, r9 //加上重定位偏移值(r9)后,lr=SDRAM上的board_init_r()函数 /* setup parameters for board_init_r */
mov r0, r5 /* gd_t */ //r0=id值
mov r1, r6 /* dest_addr */ //r1=uboot重定位地址
/* jump to it ... */
mov pc, lr //跳转: board_init_r()函数 _board_init_r_ofs:
.word board_init_r - _start //获取在flash上的board_init_r()函数地址偏移值 #endif

  从上面代码看出, 接下来便会进入uboot的board_init_r()函数,该函数会对各个外设初始化、环境变量初始化等.

uboot的启动过程到此便结束了。

  下一节我们将新建一块单板支持S3C2440。

有任何问题,均可通过公告中的二维码联系我

S3C2440移植uboot之启动过程概述的更多相关文章

  1. Linux内核启动过程概述

    版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...

  2. Linux移植之内核启动过程start_kernel函数简析

    在Linux移植之内核启动过程引导阶段分析中从arch/arm/kernel/head.S开始分析,最后分析到课start_kernel这个C函数,下面就简单分析下这个函数,因为涉及到Linux的内容 ...

  3. uboot的启动过程-FDT

    uboot的启动过程,省略了汇编部分之后,第一个执行函数是board_init_f(),在uboot/common目录的board_f.c中   board_init_f函数,首先初始化了全局数据 # ...

  4. HDFS启动过程概述及集群安全模式操作

    1.启动过程概述 Namenode启动时,首先将映像文件(fsimage)载入内存,并执行编辑日志(edits)中的各项操作.一旦在内存中成功建立文件系统元数据的映像,则创建一个新的fsimage文件 ...

  5. Linux移植之内核启动过程引导阶段分析

    在Linux移植之make uImage编译过程分析中已经提到了uImage是一个压缩的包并且内含压缩程序,可以进行自解压.自解压完成之后内核代码从物理地址为0x30008000处开始运行.下面分析在 ...

  6. uboot 的启动过程及工作原理

    启动模式介绍 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人 员才有意义.但从最终用户的角度看, ...

  7. 【ARM-Linux开发】U-Boot启动过程--详细版的完全分析

    ---------------------------------------------------------------------------------------------------- ...

  8. tiny4412学习之u-boot启动过程

    这个文档简要分析了tiny4412自带的u-boot的启动过程,这个u-boot启用了mmu,并且命令的接收和执行方式跟以前的不同. 文档下载地址: http://pan.baidu.com/s/1s ...

  9. Linux主机上使用交叉编译移植u-boot到树莓派

    0环境 Linux主机OS:Ubuntu14.04 64位,运行在wmware workstation 10虚拟机 树莓派版本:raspberry pi 2 B型. 树莓派OS: Debian Jes ...

  10. 探索 Linux 系统的启动过程

    引言 之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉.我是真的在日常生活和工作中都使用 Linux,比如在 ...

随机推荐

  1. redis集群搭建注意事项

    官方教程:https://redis.io/docs/management/scaling/ 其他参考: https://note.youdao.com/ynoteshare/index.html?i ...

  2. python操作mongodb实现读写分离

    读写分离 默认情况下,MongoClient 实例将查询发送到副本集的主要成员. 要使用副节点作为查询,以实现读写分离,我们必须更改读取首选项: 读取首选项在模块pymongo.ReadPrefere ...

  3. 华企盾DSC无法从网页下载客户端(无法访问web端)

    解决方法1:服务器安装目录需要安装在英文目录,否则DSCApache.exe会启动不了,导致无法访问5580网页. 解决方法2:5580端口占用也会导致DSCApache.exe启动不了,可打开服务器 ...

  4. MySQL运维15-一主一从读写分离

    一.读写分离介绍 读写分离,是把数据库的读和写分开操作,以应对不同的数据库服务器.主数据库提供写操作,从数据库提供读操作,这样能有效的减轻单台数据库的压力. 二.一主一从原理 MySQL的主从复制是基 ...

  5. Net 高级调试之十五:经典的锁故障

    一.简介 今天是<Net 高级调试>的第十五篇文章,这个系列的文章也快结束了,但是我们深入学习的脚步还不能停止.上一篇文件我们介绍了C# 中一些锁的实现逻辑,并做到了眼见为实的演示给大家它 ...

  6. 【玩转腾讯混元大模型】怎么说?我用混元AI大模型开发了个IDEA插件

    前言 halo 我是杨不易呀,在混元大模型内测阶段就已经体验了一番当时打开页面的时候灵感模块让我大吃一惊这么多角色模型真的太屌了,随后我立马进行了代码处理水平和上下文的效果结果一般般但是到如今混元大模 ...

  7. Redis 使用的 10 个小技巧

    Redis 在当前的技术社区里是非常热门的.从来自 Antirez 一个小小的个人项目到成为内存数据存储行业的标准,Redis已经走过了很长的一段路. 随之而来的一系列最佳实践,使得大多数人可以正确地 ...

  8. Pikachu漏洞靶场 Unsafe Fileupload(文件上传)

    Unsafe Fileupload 文章目录 Unsafe Fileupload 1.client check 2.MIME type 3.getimagesize 1.client check 标题 ...

  9. 介绍一个prometheus监控数据生成工具

    prometheus-data-generator Prometheus数据模拟工具旨在通过配置文件模拟Prometheus数据,用于测试和开发目的.该工具允许您生成用于测试和开发的合成数据. 配置 ...

  10. linux中创建新用户并且放到用户组中

    1.打开终端并以 root 用户身份登录到 Linux 系统 2.使用以下命令创建一个新用户 sudo useradd -m username 将 "username" 替换为你要 ...