在第一章中,介绍了Exynos4412的iROM、启动方式、源码组成等;在第二章中,介绍
uboot 编译等。通过前面对编译的详细分析,了解到 uboot 源码中有以下几个文件是非常重
要的:
“cpu/ARM_cortexa9/start.S”
“board/samsung/smdkc210/lowlevel_init_SCP.S 或者 lowlevel_init_POP.S”
“include/configs/itop_4412_android.h 或者 itop_4412_ubuntu.h”
其中“cpu/arm_cortexa9/start.S”是 uboot 代码入口文件,分析 uboot 一般是从
“start.S”文件开始,“lowlevel_init_SCP.S”文件是内存初始化、时钟初始化和串口初始化
等的文件,start.S 文件在运行过程中会跳到这个文件中。
“itop_4412_android.h 或者 itop_4412_ubuntu.h”文件是重要的配置头文件,里面的
宏配置,会影响以上文件如何编译和运行,包括在下一章节中 uboot 源码的 C 语言部分,很
多代码编译和运行都会受到这个头文件的影响。
本章主要内容是,从“start.S”文件开始分析所有汇编代码,截止于 uboot 开始执行 C
代码。其中涉及到很多不常用概念,需要我们去了解和掌握;涉及到汇编语法,需要我们去了
解。
3.1 分析 uboot 汇编源码必要的知识和学习方法汇总
本小节,结合 datasheet 介绍 4412 的物理地址概念,这部分和单片机中类似;介绍汇编
语法如何学习以及要掌握到什么程度;汇编部分调试方法。
3.1.1 4412 的物理地址和虚拟地址介绍
如果用户学习过迅为的 linux 驱动教程,其中有一期,专门介绍物理地址和虚拟地址的概
念。几乎在所有现代操作系统中,物理地址都是通过 MMU(内存管理单元)映射为虚拟地
址。但是在 uboot 汇编部分,还是直接操作物理地址的。
物理地址的概念。
MPU 地址总线传来的地址,由硬件电路控制其具体含义。物理地址中很大一部分是留给
内存条中的内存的。物理地址空间,一部分给内存用,一部分给总线用,这是由硬件设计来决
定的,因此在 32 bits 地址线的处理器中,物理地址空间是 2 的 32 次方,即 4GB,但物理
RAM 一般不能上到 4GB,因为还有一部分要给总线用(总线上还挂着别的许多设备)。
对于有单片机基础的用户来说,物理地址还是比较好理解,例如在 51 单片机中,P0.0 表
示小灯的输出寄存器,给这个寄存器写 1 小灯灭,写 0 小灯亮,寄存器 P0.0 的地址就是物理
地址。
P0 = 0xfe;//小灯亮
P0 = 0xff;//小灯灭
P0 在 51 寄存器头文件中,有一个宏定义它的实际地址,也就是物理地址。
在 4412 中,物理地址太多了,根本没有办法全部介绍,2000 多页的 datasheet 中大部
分都是介绍寄存器,一个一个介绍是无法实现的。但是我们有必要掌握和理解其中的寄存器框
架和典型寄存器。
在 4412datasheet 第三章“Memory Map”中,如下图所示,这是 4412 全部基地址的描述。
注意上表中,0x4000_0000~0xA000_0000,0xA000_0000~0x0000_0000 这两个地址
区间,这两个区间是 DMC 内存控制器的寻址地址,也就是内存的物理地址。实际上 4412 最
大支持的内存可以达到 3G,32 位处理器理论上可以支持 2 的 32 次方(最大 4G),如上表
所示,其中 1G 的地址给了 iROM、iRAM 等等这些 MPU 内部寄存器使用,所以 32 位 MPU
是不可能达到 4G 内存的。
现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要
MMU(Memory Management Unit)的支持。MMU 通常是 CPU 的一部分,如果处理器
没有 MMU,或者有 MMU 但没有启用,CPU 执行单元发出的内存地址将直接传到芯片引脚
上,被内存芯片(物理内存)接收,这称为物理地址(Physical Address),如果处理器启用
了 MMU,CPU 执行单元发出的内存地址将被 MMU 截获,从 CPU 到 MMU 的地址称为虚拟
地址(Virtual Address),而 MMU 将这个地址翻译成另一个地址发到 CPU 芯片的外部地址
引脚上,也就是将虚拟地址映射成物理地址。通过内存管理单元,可以实现 4G 的虚拟内存。
在 uboot 代码中,需要多次用到以上地址的概念,其中内存管理单元被开启或者关闭,
所以有必要先介绍一下这几个地址的概念。
3.1.2 关于汇编语法
如果学习过单片机课程,会发现大部分都是使用 C 语言去编码,汇编使用的非常少了。
那么还有必要去学习汇编么?其实是没有必要的,因为在 uboot 中汇编代码量非常少,以
4412 的 uboot 源码为例,其中有效的汇编代码不足 200 行,我们根本不需要为了读懂 200
行代码专门去学习一门编程语言。
作者这里建议,首先我们的目标是一定要把这些代码读明白,如果不明白会影响后面 C
代码的阅读,以及 uboot 的移植;其次,我们要弄清楚每一行有效汇编代码的语法。
现在我们已经知道汇编是从“cpu/arm_cortexa9/start.S”这个文件开始执行,那么我们
就从第一行代码的语法开始学习,代码执行到或者跳到哪一行,我们就学习这一行代码的语
法。
在手册的附录部分,我们会依次介绍汇编代码中出现的语法,大家也可以通过互联网学习
每一行执行的汇编语法。
3.1.3 uboot 汇编代码初始化串口之前的简易调试方法
在前面教程中我们介绍过,从 A9 开始,开发板一般都不配 jtag,jtag 价格昂贵,在 A9
之前,由于引导程序 uboot 必须通过 jtag 来烧写,但是在 A9 处理器上,大部分都是支持 tf
卡引导,这样可以免去 jtag 的费用,烧写变的简单高效。
那么没有 jtag,对于 uboot 的调试,我们没法单步调试,如果有一行代码我们不是很确
定到底执行了没,或者跳到哪一行。如果代码已经执行到串口初始化阶段,当然是可以通过串
口打印字符来实现,在串口初始化之前,其实可以通过控制 LED 灯来跟踪代码。
以下是开发板上两个小灯控制的代码,可以将小灯点亮。
点亮 LED2 灯:ldr r0, =0x11000104 /* GPL2(0) */
ldr r1, =0x00000001 /* GPL2(0 output high) */
str r1, [r0]
ldr r0, =0x11000100 /* GPL2(0) */
ldr r1, =0x00000001 /* GPL2(0 output high) */
str r1, [r0]
点亮 LED3:
ldr r0, =0x11000060
ldr r1, =0x00000010
str r1, [r0]
ldr r0, =0x11000064
ldr r1, =0x00000002
str r1, [r0]
这里简单介绍下这几行汇编代码的含义。
ldr r0, =0x11000104
ldr 是将 0x11000104 值赋给 r0 寄存器。这个值地址为 GPL2DAT。
ldr r1, =0x00000001
ldr 是取 0x11000104 地址的值赋给 r1 寄存器。
str r1, [r0]
str 是将 r1 的值写入到 r0 数值对应物理地址寄存器中。将 0x00000001 写入到
0x11000104 地址寄存器中,0x11000104 地址是 GPL2DAT 寄存器。
ldr r0, =0x11000100 /* GPL2(0) */
ldr r1, =0x00000001 /* GPL2(0 output high) */
str r1, [r0]
将 0x00000001 写入到 0x11000100 地址寄存器中,0x11000100 地址是 GPL2CON 寄
存器。执行这两步就可以将 LED2 点亮。
点亮 LED3 和点亮 LED2 类似。
在串口初始化之前可以通过点灯来实现调试,串口初始化之后可以通过打印字符来跟踪调
试代码。