u-boot第一阶段分析(五)

注:本次分析的u-boot是九鼎官方的u-boot代码
下载地址:链接:http://pan.baidu.com/s/1gfpDZqj 密码:7cqe


接着上一章的重定位,重定位之后就跳转到after_copy,代码如下:

bl      movi_bl2_copy
b       after_copy

after_copy后的代码是和虚拟地址映射有关的内容。

虚拟地址映射

一 虚拟地址的有关概念
1.虚拟地址和物理地址
(1)物理地址就是物理设备设计生产时赋予的地址。像裸机中使用的寄存器的地址就是CPU设计时指定的,这个就是物理地址。物理地址是硬件编码的,是设计生产时确定好的,一旦确定了就不能改了。
(2)虚拟地址意思就是在我们软件操作和硬件被操作之间增加一个层次,叫做虚拟地址映射层。有了虚拟地址映射后,软件操作只需要给虚拟地址,硬件操作还是用原来的物理地址,映射层建立一个虚拟地址到物理地址的映射表。当我们软件运行的时候,软件中使用的虚拟地址在映射表中查询得到对应的物理地址再发给硬件去执行。

2.MMU单元的作用
(1)虚拟地址映射就是通过MMU单元来实现的,MMU全称是memory management unit(内存管理单元),MMU实际上是SOC中一个硬件单元(虚拟地址到物理地址的映射是不可能通过软件来实现的),它的主要功能就是实现虚拟地址到物理地址的映射。
(2)MMU单片在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。

3.虚拟地址映射的额外收益
(1)访问控制
a. 访问控制就是:在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,然后在每一块的映射关系中同时还实现了访问控制(对该块可读、可写、只读、只写、不可访问等控制)
b. 回想在C语言中编程中经常会出现一个错误:Segmentation fault。实际上这个段错误就和MMU实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若干个内存块),如果当前程序指针出错访问了不该访问的内存块则就会触发段错误。
(2)cache
a. cache的工作和虚拟地址映射有关系。
b. cache是快速缓存,意思就是比CPU慢但是比DDR块。CPU嫌DDR太慢了,于是乎把一些DDR中常用的内容事先读取缓存在cache中,然后CPU每次需要找东西时先在cache中找。如果cache中有就直接用cache中的;如果cache中没有才会去DDR中寻找。

详细的虚拟地址映射内容参考博客:
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22891521&id=2109284

二 代码分析
1.首先使能域访问(cp15的c3寄存器)

/* enable domain access */
	ldr	r5, =0x0000ffff
	mcr	p15, 0, r5, c3, c0, 0	

(1)cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp15协处理器其实就是操作cp15的这些寄存器。
(2)c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。

2.然后设置TTB(cp15的c2寄存器)

/* Set the TTB register */
	ldr	r0, _mmu_table_base
	ldr	r1, =CFG_PHY_UBOOT_BASE
	ldr	r2, =0xfff00000
	bic	r0, r0, r2
	orr	r1, r0, r1
	mcr	p15, 0, r1, c2, c0, 0

(1)TTB就是translation table base,转换表基地址。首先要明白什么是TT(translation table转换表),TTB其实就是转换表的基地址。
(2)转换表是建立一套虚拟地址映射的关键。转换表分2部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射中基本规定中规定了内存映射和管理是以块为单位的,至于块有多大,要看你的MMU的支持和你自己的选择。在ARM中支持3种块大小,细表1KB、粗表4KB、段1MB)。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0-4G)的映射。
(3)整个建立虚拟地址映射的主要工作就是建立这张转换表
(4)转换表放置在内存中的,放置时要求起始地址在内存中要xx位对齐。转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时会自动去查转换表。
(5)通过查找_mmu_table_base,找到转换表在lowlevel_init.S文件的593行。代码如下:

mmu_table:
	.set __base,0
	// Access for iRAM
	.rept 0x100
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

	// Not Allowed
	.rept 0x200 - 0x100
	.word 0x00000000
	.endr

	.set __base,0x200
	// should be accessed
	.rept 0x600 - 0x200
	FL_SECTION_ENTRY __base,3,0,1,1
	.set __base,__base+1
	.endr

	.rept 0x800 - 0x600
	.word 0x00000000
	.endr

	.set __base,0x800
	// should be accessed
	.rept 0xb00 - 0x800
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

/*	.rept 0xc00 - 0xb00
	.word 0x00000000
	.endr */

	.set __base,0xB00
	.rept 0xc00 - 0xb00
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

	.set __base,0x300
	//.set __base,0x200
	// 256MB for SDRAM with cacheable
	.rept 0xD00 - 0xC00
	FL_SECTION_ENTRY __base,3,0,1,1
	.set __base,__base+1
	.endr

	// access is not allowed.
	@.rept 0xD00 - 0xC80
	@.word 0x00000000
	@.endr

	.set __base,0xD00
	// 1:1 mapping for debugging with non-cacheable
	.rept 0x1000 - 0xD00
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr	

宏观上理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。
ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G范围内需要4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候并没有依次单个处理这4096个单元,而是把4096个分成几部分,然后每部分用for循环做相同的处理。
选取代码中的一段来分析:

.set __base,0
	// Access for iRAM
	.rept 0x100
	FL_SECTION_ENTRY __base,3,0,0,0
	.set __base,__base+1
	.endr

.set指令将__base设置为零,.rept和.endr分别代表循环开始和循环结束,0x100代表循环了256次,每循环一次 __base加1。

具体的地址映射转换表如下:
这里写图片描述
根据x210的DRAM的有效范围
DMC0: 0x30000000-0x3FFFFFFF
DMC1: 0x40000000-0x4FFFFFFF
可知:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。

所以我们现在应该理解为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。

而且根据上述分析,c3e00000会被映射到33e00000,而33e00000同样也会被映射到33e00000,这就解释了为什么开启了MMU单元有时还能够用物理地址,其实用的不是物理地址,是因为虚拟地址和物理地址相同。

3.最后使能MMU单元

mrc	p15, 0, r0, c1, c0, 0
orr	r0, r0, #1
mcr	p15, 0, r0, c1, c0, 0

cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。

cp15的寄存器详解:
https://wenku.baidu.com/view/cad34bd226fff705cc170af1.html


MMU相关部分结束之后,第一阶段内容基本就快结束了;
1.第三次设置栈
(1)第三次设置栈。这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。
(2)ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
这次要设置栈的地址为33e00000+(210241024)-0x1000,实际上是将栈设置在uboot起始地址上方2MB处,这样安全的栈空间是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。

2.清理bss

ldr	r0, _bss_start		
ldr	r1, _bss_end

_bss_start与_bss_end是从链接脚本u-boot.lds得来的。

3.跳转到第二阶段

ldr	pc, _start_armboot

(1)start_armboot是uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。
(2)远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。

总结:第一阶段的内容有哪些?
(1)构建异常向量表
(2)设置CPU为SVC模式
(3)关看门狗
(4)开发板供电置锁
(5)时钟初始化
(6)DDR初始化
(7)串口初始化并打印"OK"
(8)重定位
(9)建立映射表并开启MMU
(10)跳转到第二阶段

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值