在第一章中,介绍了迅为4412 的 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 类似。
在串口初始化之前可以通过点灯来实现调试,串口初始化之后可以通过打印字符来跟踪调试代码。
衔接--嵌入式学习丨迅为4412开发板-uboot源码-汇编-源码分析(二)