S3C2440移植uboot之启动过程概述
上节烧写了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之启动过程概述的更多相关文章
- Linux内核启动过程概述
版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...
- Linux移植之内核启动过程start_kernel函数简析
在Linux移植之内核启动过程引导阶段分析中从arch/arm/kernel/head.S开始分析,最后分析到课start_kernel这个C函数,下面就简单分析下这个函数,因为涉及到Linux的内容 ...
- uboot的启动过程-FDT
uboot的启动过程,省略了汇编部分之后,第一个执行函数是board_init_f(),在uboot/common目录的board_f.c中 board_init_f函数,首先初始化了全局数据 # ...
- HDFS启动过程概述及集群安全模式操作
1.启动过程概述 Namenode启动时,首先将映像文件(fsimage)载入内存,并执行编辑日志(edits)中的各项操作.一旦在内存中成功建立文件系统元数据的映像,则创建一个新的fsimage文件 ...
- Linux移植之内核启动过程引导阶段分析
在Linux移植之make uImage编译过程分析中已经提到了uImage是一个压缩的包并且内含压缩程序,可以进行自解压.自解压完成之后内核代码从物理地址为0x30008000处开始运行.下面分析在 ...
- uboot 的启动过程及工作原理
启动模式介绍 大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人 员才有意义.但从最终用户的角度看, ...
- 【ARM-Linux开发】U-Boot启动过程--详细版的完全分析
---------------------------------------------------------------------------------------------------- ...
- tiny4412学习之u-boot启动过程
这个文档简要分析了tiny4412自带的u-boot的启动过程,这个u-boot启用了mmu,并且命令的接收和执行方式跟以前的不同. 文档下载地址: http://pan.baidu.com/s/1s ...
- Linux主机上使用交叉编译移植u-boot到树莓派
0环境 Linux主机OS:Ubuntu14.04 64位,运行在wmware workstation 10虚拟机 树莓派版本:raspberry pi 2 B型. 树莓派OS: Debian Jes ...
- 探索 Linux 系统的启动过程
引言 之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉.我是真的在日常生活和工作中都使用 Linux,比如在 ...
随机推荐
- 大语言模型底层架构丨带你认识Transformer
本文分享自华为云社区<大语言模型底层架构你了解多少?大语言模型底层架构之一Transfomer的介绍和python代码实现>,作者: 码上开花_Lancer . 语言模型目标是建模自然语言 ...
- [ABC313F] Flip Machines
Problem Statement There are $N$ cards numbered $1$ through $N$. Each face of a card has an integer w ...
- [P7880][Ynoi2006] rldcot
[Ynoi2006] rldcot 题目描述 给定一棵 \(n\) 个节点的树,树根为 \(1\),每个点有一个编号,每条边有一个边权. 定义 \(dep(x)\) 表示一个点到根简单路径上边权的和, ...
- 3D 高斯喷溅 🤗 为什么图形永远不会相同
高斯喷溅 (Gaussian Splatting) 技术是一种翻天覆地的渲染手段,能够以 144 帧每秒的速度渲染出高质量的场景,这和传统的图形处理流程截然不同 这种将高斯数据转换成图像的过程,与训练 ...
- offline RL | Pessimistic Bootstrapping (PBRL):在 Q 更新中惩罚 uncertainty,拉低 OOD Q value
论文题目:Pessimistic Bootstrapping for Uncertainty-Driven Offline Reinforcement Learning,ICLR 2022,6 6 8 ...
- lca 学习笔记
定义 最近公共祖先简称 \(LCA\) 两个节点的最近公共祖先,就是这两个点的公共祖先里,离根最远的的那个 为了方便,我们记某点集 \(S={v1,v2,...,vn}\) 的最近公共祖先为 \(LC ...
- 基于注解玩转excel导出导入-基于注解玩转excel导出导入
title: 基于注解玩转excel导出导入 date: 2021-05-01 15:55:13.53 updated: 2021-12-26 17:43:19.505 url: https://ww ...
- 【GIT-精讲】从零玩转Git-基础理论
关于版本控制 一.什么是版本控制 版本控制(Version Control Systems)版本控制(Revision control)是一种软件工程技巧 在开发的过程中,确保由不同人所编辑的同一档案 ...
- STM32CubeMX教程7 TIM 通用定时器 - 输入捕获
1.准备材料 开发板(STM32F407G-DISC1) ST-LINK/V2驱动 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) X ...
- 9 个让你的 Python 代码更快的小技巧
哈喽大家好,我是咸鱼 我们经常听到 "Python 太慢了","Python 性能不行"这样的观点.但是,只要掌握一些编程技巧,就能大幅提升 Python 的运 ...