uboot 学习笔记 1

uboot 下载网址 ftp://ftp.denx.de/pub/u-boot/
uboot 版本选择 原则, 够用就可以了
在这里插入图片描述
这部分才是arm设计得ip内核 ,cpu arch/arm/cpu
在这里插入图片描述
外围部分才是半导体三星设计得部分,外设 ,
目录结构
在这里插入图片描述
cpu下有不同芯片得 目录文件
他们得大部分代码都是共用得, 只是每个公司加入了一小点点特色,
start.S 要特别关注, 这是整个uboot 的总入口 , 也就是整个 uboot 的运行的第一行代码就是从 start.S的第一句代码开始的,
在这里插入图片描述
在这里插入图片描述
goni 不是三星官方的smdk开发板, 可能是一个别的公司出的兼容版
这款开发板的核心CPU用跟V210是一样的, 但是S5PV210 不能直接使用这个配置, 因为它的主板和外围设计上的很多部分跟 S5PV210 是不同得,
2011 多了一个文件 universal_c210 用的 是Cortex-A9双核 ,也就是作者并没有给我们的S5PV210 开发板做一个官方的权威版本, 你把goni 或 universal_c210编译一下, 在我们的开发板上是跑不起来的, S5PV210 的uboot配置代码没有开源,
当我们完全了解了uboot 的大体结构以后, 要会改开发板的配置和相关代码, 这个就是以goni版本为范例做出一个完全能在S5PV210 下开发板运行起来的 bootloder
所以说你要针对 S5PV210 定制一个uboot 其实只要选2010.06月份的这个版本就可以了, 这个版本比较简洁, 改起来也比较容易 .
我选择比较新的 2012.10月份的,进行改
整个uboot的入口 是从start.S开始的, 我们就从这个start.S分析代码,

先看头文件信息, 到哪里去找头文件 ? 这些头文件一般包含在两个目录下面,
一个是uboot的根目录下的include目录,
一个就是uboot跟目录下的 arch/arm/include 目录
为什么要在这两个路径下找头文件 ? 因为是Makefile 指定的, 在Makefile里面定义的

一般来说 异常 要比 中断 的优先级要高, 因为中断你屏蔽可以不去处理它 ,而异常都是一些你不能屏蔽一定要处理的问题,
在这里插入图片描述
未定义指令异常 : CPU在运行过程当中,碰到了一句它无法识别到的指令 , 碰到这种情况程序已经不可能再往下执行了, 所以要停下来 来处理这个异常 ,

软中断异常 : 用在Linux 系统的系统调用, 也就是说从用户模式进入保护模式, 或者说进入超级用户模式,

(指令)预取终止异常 : 就是CPU取不到下一条指令 , 比方说, 开发板, 一共只有512M内存, 程序当中如果有一条指令让这个程序跳转到第 1G 的地方去运行, 那是不可能的, 因为1G这个地方内存根本不存在, 所以它取不到指令 , 就会产生这个 预取指终止异常 .

数据访问终止异常 : 跟上面的类似 , 也是访问一个不存在的地址导致的. CPU也取不到数据, 不得不停下来处理这个异常

_not_used 未使用异常 : 未使用异常

irq 一般意义上的中断 : 包括定时器中断, 外部触发中断,串口中断等等.

fiq 快中断 : 一般应用实时响应 要求很高的一些场合, 也是为了跟 以前的一些arm产品相兼容

_start: b reset /* 0地址,复位异常 */

ldr pc, _undefined_instruction /* 4地址,未定义指令 */

ldr pc, _software_interrupt /* 8地址,软中断指令 */

ldr pc, _prefetch_abort /* 12地址,指令预取异常,如果处理器预取的指令不存在,或者该地址不允许当前指令访问 */

ldr pc, _data_abort /* 14地址,数据访问终止,如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问 */

ldr pc, _not_used /* 未使用异常 */

ldr pc, _irq /* 24地址,中断异常 */

ldr pc, _fiq /* 28地址,快中断异常 */

ldr pc, _undefined_instruction 意思是把 _undefined_instruction 标号中的内容放入 pc指针,
在这里插入图片描述
那么 _undefined_instruction 这里面的内容是什么呢 ? 我们看到下面有两个定义 一个是带 CONFIG_SPL_BUILD 的定义 一个是不带 CONFIG_SPL_BUILD 的情况下,
什么是 CONFIG_SPL_BUILD ?

要理解 S5PV210的启动机制, 先看 S3C6410 的启动机制 :
在这里插入图片描述
BL (Bootloader)
S3C6410 启动主要分为四个部分
上电以后, 会先启动片内的 iROM(BLO)(32KB) 程序 这个程序主要做两件事情
第一件: 初始化时钟, 看门狗 等一些器件
第二件 : 就是把 FLASH 或者 SD卡中的头4K 的内容, BL1加载到片内 Stepping Stone(8KB) 当中 去运行, 这个 4K非常小, 其实干不了什么事情, 只能做一些很简单的配置, 这个头4K的程序它会判断一下,自己是在静态Stepping Stone(8KB) 当中,还是在动态SDRAM 主存当中, 如果是在静态内存当中,它就会去配置SDRAM 主存,然后把整个Bootloader一起从FLASH加载到SDRAM主存当中 , 那么这 4K 主要是想让你配置好 SDRAM 主存比如说这个主存有512M , 等配完这个主存之后, 再把剩下的BL2 Bootloader的大部分,加载到 SDRAM 主存当中,然后Stepping Stone(8KB) 把程序的入口跳转到 SDRAM 主存 的BL2 进行运行 , BL2 就是你的主要的Bootloader ,然后再把FLASH 或者 SD 中的OS镜像(linux 或者 android) 再次加载到 SDRAM 主存当中 .SDRAM 再跳转到OS 的入口处, 进行运行 , 这样就完成了整个 Bootloader 的过程 .

针对 S3C6410 这种启动机制,我们的uboot的设定就分为了,两个部分,
这个头4K的程序它会判断一下,自己是在静态Stepping Stone(8KB) 当中,还是在动态SDRAM 主存当中, 如果是在静态内存当中,它就会去配置SDRAM 主存,然后把整个Bootloader一起从FLASH加载到SDRAM主存当中 ,然后再跳转到SDRAM主存运行 。 这个uboot入口也先做一件事情 ,判断一下自己在静态内存当中还是在主存当中, 如果确实在主存当中直接就跳到4K 以后的内容,开始运行Bootloader大部分内容, 包括驱动程序, 还有命令行等等等等, 然后在确定要加载OS的时候, 再把OS加载到主存当中, 再把程序直接跳转到OS的入口处,这样就完成了整个 Bootloader的启动过程 。
在这里插入图片描述
在这里插入图片描述
于S3C6410 相比, S5PV210 主要做了两方面的改进,
第一个 : 它增加了iROM(BLO)的大小,从32加到了64 ,它把一些固件的函数,读取FLASH的函数,读取MSC卡函数都放在里面,
第二个 : 它增加了片内的大小, 增加到了 96k ,它是希望你BL1 加载到 SRAM以后能够把整个Bootloader BL1 + BL2 同样 加载到SRAM里运行, 这样你就不用 局限于那个4k或者1 6k地址了,能够再BL2中从容不迫的去配置 SDRAM 和一些初始化功能,再接着把OS加载到SDRAM当中去运行 ,
看上去优化了很多, 但是uboot设计者没有买账 , uboot一般编译下来起码有 100多k到200k的大小,这个96k根本装不下, 所以uboot绕开了这个机制从新搞了一套机制, 把uboot做成了两个部分, 一部分比较小的叫uboot-spl.bin 大小16k, 另一部分仍然叫uboot.bin 大小200k左右,
启动过程 : 上电以后把uboot-spl.bin 大小16k加载到SRAM当中去, 然后它绕开了BL2, 直接去配置这个SDRAM ,然后再把这个210k的主体部分,加载到内存当中 ,再直接跳转到主体的uboot.bin开始运行,
在这里插入图片描述
在这里插入图片描述
这里又引出一个问题, 异常向量的地址映射问题.
容易被忽略的问题, 根据ARM的手册 一般异常发生以后 会被映射到 0x0000,0000 或者 0xffff,0000 这两者可通过CP15协处理器去配 , 这不是我们讲的重点, 这里要说的重点就是,你如何确定 _undefined_instruction 地址正好就在0x0000,0004 这个地址,
我们看到 0x0000,0000这个地址 是ROM 的, 你是不能改的, 你的镜像 SPL镜像, 会被加载到 BL1这个地方, 而这个地方的地址 是 0x0002,0000 也就是说你第一句指令 b reset 的地址 会是 0x0002,0010 , 前面一部分是校验和, 不用管, ldr pc, _undefined_instruction 这条指令的地址会被放在 0x0002 , 0014 这个地址,发现异常以后这个 会跳到 ROM 地址 0x0000,0004 这个地址运行, 而不是你这个 0x0002 , 0014 这个地址, 这个你怎么解释 ?
这就牵扯到 异常向量和地址的映射问题 .

当程序发生 异常或者中断时, 它会自动跳转到异常向量表, 0x0000,0000 这个地方去运行, 然而这个地方由于是ROM区,是不可更改的,所以你没有办法在这里跳转到你自己想要的,异常处理程序当中,那么怎么办。三星知道这样设计有问题, 处了这个映射区域以外 ,它在片内 RAM区当中又开辟了一块 ExceptionVector Table , 开辟了一块它自己的异常处理向量表, 这样当程序再发生异常或者中断的时候,它不会跳转到 0x0000,0000 ROM,而是直接跳转到ExceptionVector Table 这里去运行,,所以你希望在你Bootloader 在片内运行的时候能够处理异常的话,你需要人工的在程序当中, 把你的向量表程序拷贝到 ExceptionVector Table 这个地方, 这样的话当你再发生中断的时候,它跳 ExceptionVector Table 因为这里已经是你的 异常向量表了, 然后它再会跳到 Reserved 处理异常程序,

如果 程序是在片外的DDR运行的时候怎么处理中断异常? 假设在程序跑起来以后,我们的uBoot 被拷贝到片外的这一块内存当中运行,由于在DDR当中 会启用mmu就是内存管理映射功能, 也就是说不在片内运行了,ARM考虑到了这种情况,在CP15协处理器里面开了VBAR的寄存器, 你可以把你的程序的开头的地址拷贝到VBAR寄存器里面, 当你 设置好以后再配置一些 别的启动使能变量,当这样配好以后,你的程序再次发生异常的以后 , 不是跳转到0x0000,0000, 也不是跳转到 ExceptionVector Table 这里 ,而是跳转到VBAR指定的这个地址去运行你的中断向量表,

在这里插入图片描述
我们先看在主存中运行的情况
ldr pc, _undefined_instruction 就是把 _undefined_instruction 标号指定的内容 undefined_instruction的地址放到 _undefined_instruction 那么这句 不带下划线的 undefined_instruction 的地址在哪里呢 ? 在第495行 , 为了更加直观的认识, 我们反编译这段代码看一看 ,反编译出来是不是我们希望它有的这个样子,
在这里插入图片描述
我们先进去uboot的目录, 我们先看看怎么样编译,
首先你要生成一个配置文件
make s5p_goni_config 这句话的意思是生成一系列的配置文件,给你后面的编译配置要用,生成完以后你再键入一个make
接着就开始正式的编译了,这个过程可能会比较慢
编译完之后我们先看一下编译出来些什么东西,
生成了一系列uboot开头的文件, 这里分别表示什么意思呢?为了防止搞晕, 只说两个,一个是u-boot 和 u-boot.bin 我们把它单独列出来,
绿颜色的 u-boot 是怎么来的 ?
start.s 会编译成 start.o 的目标文件 然后别的一些c文件, 全都会变成.o文件, 然后我们的连接器会通过一个连接命令把所有的.o文件拼接起来, 拼成一个不带后缀的u-boot 这个u-boot 通常是很大的,我们这里是905k , 因为这里不仅包含了一些运行的代码它还包含了一些其他信息, 比方说段的信息, 一些原始代码信息,还有一些标号信息,地址信息等等, 所以这个文件是不能直接拷贝到板子里面运行的,如果我们要拷贝到板子里面,要把这里面其他的乱七八糟的信息,全部剥离掉 ,strp 命令剥离掉,然后生成一个纯粹的 u-boot.bin 这个 u-boot.bin 就小多了,我们看的我们这个u-boot.bin 只有187k 这个u-boot.bin 里面是非常紧凑的,里面就是一句代码紧接着一句, 一句占4个字节, 里面没有任何调试信息,包括注释信息等等 , 所以我们一般要烧到板子里面去运行, 就烧这个 u-boot.bin 而我们要分析里面的代码, 看调试里面的信息我们就分析这个不带后缀的 u-boot 。

接下来我们来看一个调试工具 ,它可以用来查看我们编译好的反汇编代码,是非常有利的一个利器,
我们输入命令 arm-linux-objdump -S u-boot
-S的意思就是显示源代码和 反汇编代码, 直接键入的话会比较多,没有办法看 我们用一个小技巧,
| less:用管道重定向less命令,可以上下翻页查看
arm-linux-objdump -S u-boot | less
输入这个命令用管道输入输出到 less 命令就可以 看了,就可以上下翻页啊都可以,
编译器:把C语言编译成汇编语言

汇编器:把汇编语言(.S文件)真正编译成二进制机器代码

反汇编出来的代码格式 :每行代码分为两行,
在这里插入图片描述
第一行 : _start: b reset
就是真正再.s文件中输入的汇编代码 , 下面一行有数字 34800000 是这句代码所在的内存地址, 这个34800000是怎么来的呢 ? 也是在Makefile 里面定义的,以后讲Makefile系统的时候再讲, 这个ea000014 就是根据 b reset 指令真正汇编出来的机器代码 , 编译器和汇编器, 严格来讲是两个概念, 所谓编译器就是把C语言编译成汇编语言, 这个叫编译器, 汇编器就是把汇编语言.s文件真正编译成 01001010 这种二进制的机器代码,这个叫汇编器,
ea000014 就是真正的二进制的机器指令, 也就是再u-boot.bin 里面所放的那个指令, 后面的那句 b 34800058 就是根据机器指令 ea000014 对应的汇编指令 ,

第二行 : ldr pc, _undefined_instruction
前面说过这句话的意思要把下面 地址标号 _undefined_instruction 处的内容 34800200 放到这个pc指针里面去,但是这样问题就来了这个地址要占用四个字节, 然而呢这段机器代码 ARM机器代码总共也就只有四个字节 ,再加上前面的 ldr pc这些内容显然是放不下的,那么怎么办呢 ? 这里是个伪指令, 汇编器在这里做了一个处理, 它这里利用了一下pc指针, 也就是说把pc指针加了20 在这里插入图片描述 ldr pc,[pc,#20] 然后把加了20的里面的内容保存到pc里面去, 这句话的意思是 pc的地址+20 比如是 中括号[pc,#20] 相加后的地址里面的内容取出来,
以下内容我带着疑问 ? why?
(4+20=24 换算成 十六进制就是 18 , 34800018我们发现不是 34800200 这个内容 而是e59ff014 这个又是怎么回事呢 ? 这里就要讲一讲流水线的概念了, 这个也是单片机里没有的一个概念 , 我们知道在一些比较高级的CPU,大多采用流水线这个概念, 什么意思 ? 就是每一句pc的指令, 每一句的指令它运行,它不是直接就运行的, 它是经过三个步骤,一般都要先经过 取值 、 译码 、 运行 。 取值就是把代码从内核当中取到CPU里面 , 译码就是把这句机器指令 e59ff014 这个东西翻译成机器能识别的 ldr pc,[pc,#20] ,然后再进行运行 ,也就是说根据这样的流水线,我这句话在运行的时候 pc指针其实已经跑到我前面去了,已经取值好一条指令, 并且译码好了指令, 所以在ARM里面pc指针并不是指向在当前运行的这条命令,而是指向我们当前运行的这条指令 +8
流水线操作:取址—>译码—>运行;

把内存中的代码取到CPU中,把e59ff014翻译成对应的命令,ldr pc,[pc,#20],然后运行。所以pc指针并不是指向当前运行的命令,而是指向当前运行的指令加8,加8超前两个指令,一个用来取址,一个用来译码,即:

pc=0x34800004+0x08=0x3480000c, pc= 0x3480000c+0x14(即20)=0x34800020

即把0x34800020地址处的数字34800200放到pc指针里面,即直接跳转到0x34800200地址处运行。)
那么 34800200 这句话在哪里呢 ?

在这里插入图片描述
这里就是 不带下划线的 undefined_instruction 异常处理代码 处的地址 。
在这里插入图片描述
我们在看一下 _pad 这个地方, 它这里没有放跳转地址,只是为了我们反汇编的时候调试方便, 放了一个标识符 0x12345678
在这里插入图片描述
我们看一下反汇编, 这里 确实 把12345678放到了里面
当这一切异常向量表结束的时候呢 ? 它输出了一个全局标号 _end_vect 说明到此为止。
我们的异常向量和中断处理部分,全都结束了。

在这里插入图片描述
上面的代码使下面的代码开始处16字节对齐,即当上段的代码运行完后不是16字节对齐,就填充0xdeadbeef,直到使下段的代码开始处16字节对齐。
那我们在代码当中看一下具体有没有填充 ?
在这里插入图片描述
0x34800040正好16字节对齐,所以中间没有填充

接着
.globl _TEXT_BASE
_TEXT_BASE 这个标号里放了一个内容, 这个内容就是CONFIG_SYS_TEXT_BASE 这个宏的内容
这个宏其实是在我们的配置文件定义的,就是之前敲的命令 s5p_goni_config 这个文件定义的,至于为什么在这个文件里面定义我们到后面讲Makefile的时候具体细讲,你现在只要知道这个东西就是我们的 u-boot 被拷贝到DDR里的起始地址, 我们通过反汇编可以看到这个起始地址就是 34800000
在这里插入图片描述

86 - 103 行这一段这些东西其实都没有太大的作用,只是计算一些段的标号,现在讲肯定不是很理解, 以后讲完Makefile再来看这些段就知道是做什么用的,放到这里呢, 只是做一个说明,在程序里面也没有真正的用到这些东西,

在这里插入图片描述
下面是IRQ中断,放了一个IRQ_STACK_START标号,标号内容为:0x0badc0de,此内容没有意义,是为了说明他是用来表示堆栈指针开始的地方,程序刚刚运行,还没有进入初始化,根本不知道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,作为以后初始化以后堆栈指针建立好后,将要修改代码的时候放的一个地方,在后面程序运行的时候会修改这个地方的代码,括号内的注释为在运行时才计算。
在这里插入图片描述

IRQ_STACK_START 放了一个0x0badc0de 没有意义的东西,为什么 ?它这个地方是用来表示堆栈指针开始的地址,但是我们现在程序刚刚运行,还没有进入初始化,根本不知道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,作为以后初始化以后堆栈指针建立好后,将要修改代码的时候放的一个地方,在后面程序运行的时候会修改这个地方的代码,括号内的注释也看到 : 为在运行时才计算。
FIQ_STACK_START 也是一样
IRQ_STACK_START_IN 也是不能确定堆栈指针地址,加8后填充无效的数字:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值