#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
使能域访问(cp15的c3寄存器)
1)cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用。我们通过mrc和mcr指令来访问这些寄存器。所谓的操作cp协处理器其实就是操作cp15的这些寄存器。
2)c3寄存器在mmu中的作用是控制域访问。域访问是和MMU的访问控制有关的。
/* Set the TTB register */
ldr r0, _mmu_table_base
#ifdef CONFIG_ENABLE_MMU
_mmu_table_base:
.word mmu_table
#endif
-----------------------lowlever_init.s中的代码讲解-----------------------
#ifdef CONFIG_ENABLE_MMU
我们定义了
#ifdef CONFIG_MCP_SINGLE
我们也定义了
/*
* MMU Table for SMDKC110
* 0x0000_0000 -- 0xBFFF_FFFF => Not Allowed
* 0xB000_0000 -- 0xB7FF_FFFF => A:0xB000_0000 -- 0xB7FF_FFFF
* 0xC000_0000 -- 0xC7FF_FFFF => A:0x3000_0000 -- 0x37FF_FFFF
* 0xC800_0000 -- 0xDFFF_FFFF => Not Allowed
* 0xE000_0000 -- 0xFFFF_FFFF => A:0xE000_0000 -- 0XFFFF_FFFF
*/
/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
/*这里开始设置虚拟映射转换表,这里使用的是段式映射模式,即以段(1MB)为单位进行映射*/
/*因此表中的一个单元只能管1MB,整个4G内存需要创建4096个表单元,所以后面采用了循环的方法来创建*/
/*查表的方法是:例如虚拟地址的高12位是x,则去查第x个表单元,该表单元的高12位就是物理地址的高12位,其实这一部分只要知道一些大概的原理即可,不必深究,等用到了再去看*/
/* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b /*.macro指令是汇编中宏定义的意思,此带参宏将FL_SECTION_ENTRY base,ap,d,c,b定义成一个word大小的特定值*/
.word (\base << 20) | (\ap << 10) | \ /*这个特定值就是转换表的填充量,其中,参数base是映射出来的段地址的基地址,从第20位开始。*/
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)/*20位的大小正好是1MB,由此可知转换表中未保留前20位,映射出来的地址之间间隔为1MB*/
/*故这里采用的是段式映射;继续来分析参数,ap是访问控制位,从第10位开始。d、c、b都是一些权限位*/
.endm /*.endm,即结束宏定义*/
定义一个函数。类似C中的宏定义
MMU初始化过程中有一步是将页表基址(CFG_PHY_UBOOT_BASE + mmu_table)存入TTBR0中,在lowlevel_init.S中可以看到对页表的初始化。
关于页表:ARMv7的MMU进行地址映射时涉及到两种页表,一级页表(first level page table)和二级页表(coarse page table)。
关于映射方式:映射方式有两种,段映射和页映射。段映射只用到一级页表,页映射用到一级页表和二级页表。
关于映射粒度:段映射的映射粒度有两种,1M section和16M supersection;页映射的映射粒度也有两种,4K small page和64K large page。
这些信息可以从页表的入口描述符中获得。
定义一个宏FL_SECTION_ENTRY用来设置页表入口描述符,base即物理基址,ap即access permission,d即domain,c即cacheable,b即bufferable。
.section .mmudata, "a"
.align 14
定义一个名为mmudata的段,段属性为"a",allowable,该段16K对齐。从u-boot.lds中可以看到,u-boot的各个段在内存中的分布依次为:.text,.rodata,.data,.got,.u_boot_cmd,.mmudata,.bss。
为什么页表是16K对齐呢?
在上一节我们讲过:有两个寄存器TTBR0和TTBR1用来存放一级页表基址,操作系统把虚拟地址空间划分为用户空间和内核空间,0x0 -> 1<<(32-N)为用户空间,由TTBR0控制,1<<(32-N) -> 4GB为内核空间,由TTBR1控制,N的大小由TTBCR寄存器决定。
由于u-boot主要作用是硬件初始化和引导操作系统,所以没有必要对虚拟地址空间进行划分,即N=0,整个虚拟地址空间由TTBR0控制,TTBR0的格式如下:
N=0时,[31:14]存放页表基址,即一级页表的基址为([31:14]<<14),2^14为16K。
虚拟地址到物理地址的映射过程如下:
虚拟地址的[31:24]位存放一级页表的入口index,[23:0]位存放段偏移;
从TTBR(translation table base register,协处理器CP15中的一个寄存器,用于存放一级页表的基址)寄存器中获取一级页表的基址;
一级页表基址+ VA[31:24] = 该虚拟地址对应的页表描述符的入口地址;
页表描述符的[31:24]位为该虚拟地址对应的物理段基址;
物理段基址+ VA[23:0]段偏移= 物理地址
由映射图可知,一个虚拟地址可以索引2^8个一级页表入口,每个入口映射2^24大小的内存,故虚拟地址可以映射的最大物理内存为:2^8 * 2^24,即4G。
// the following alignment creates the mmu table at address 0x4000.
.globl mmu_table
mmu_table:
.set __base,0
// Access for iRAM
.rept 0x100
.set __base,0 /*设置变量__base值为0*/
// Access for iRAM
.rept 0x100 /*.rept 0x100相当于for循环,一共循环0x100次,所以这一块代码创建了0x100(256)个转换表单元*/
FL_SECTION_ENTRY __base,3,0,0,0 /*利用刚才定义的带参宏创建转换表的内容,变量__base和3,0,0,0作为参数*/
.set __base,__base+1 /*相当于base=base+1*/
.endr /*.endr对应.rept,表示循环体的结束*/
Syntax: .rept count 重复
Repeat the sequence of lines between the .rept directive and the next .endr directive count times.
For example, assembling
.rept 3
.long 0
.endr
is equivalent to assembling
.long 0
.long 0
.long 0
FL_SECTION_ENTRY __base,3,0,0,0
对虚拟地址0x0-0x10000000作平行映射(flat mapping),即把虚拟地址0x0-0x10000000映射到物理地址0x0-0x10000000。
.set __base,__base+1
.endr
// Not Allowed
.rept 0x200 - 0x100
.word 0x00000000
.endr
不对虚拟地址空间0x10000000-0x20000000作映射,即禁止访问虚拟地址空间0x10000000-0x20000000。
Syntax: .word expressions 分配一段内存单元,并用expressions 初始化
This directive expects zero or more expressions, of any section, separated by commas. For each expression, as emits a 16-bit number for this target.
.set __base,0x200
// 128MB for SDRAM 0xC0000000 -> 0x50000000 这里应该是256M,需要根据自己的开发板配置进行更改
// should be accessed
.rept 0x600 - 0x200
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
把虚拟地址空间0x20000000-0x6000000映射到物理地址空间0x20000000-0x6000000,0x20000000-0x6000000为sdram的地址空间,此时sdram有1G。
.rept 0x800 - 0x600
.word 0x00000000
.endr
不对虚拟地址空间0x60000000-0x80000000作映射,即禁止访问虚拟地址空间0x60000000-0x80000000。
.set __base,0x800
// should be accessed
.rept 0xb00 - 0x800
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
对虚拟地址0x80000000-0B0000000作平行映射(flat mapping),即把虚拟地址0x80000000-0xB0000000映射到物理地址0x80000000-0xB0000000。
/* .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
对虚拟地址0xB0000000-0xC0000000作平行映射(flat mapping),即把虚拟地址0xB0000000-0xC0000000映射到物理地址0xB0000000-0xC0000000。
// 0xC000_0000鏄犲皠鍒?x2000_0000
.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
把虚拟地址空间0xC0000000-0xD000000映射到物理地址空间0xC0000000-0xD000000,0xC0000000-0xD000000为sdram的地址空间,此时sdram有1G。
// 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循环做相同的处理。VA是虚拟地址,PA是物理地址。
VA PA length
0-10000000 0-10000000 256MB
10000000-20000000 0 256MB
20000000-60000000 20000000-60000000 1GB 512-1.5G
60000000-80000000 0 512MB 1.5G-2G
80000000-b0000000 80000000-b0000000 768MB 2G-2.75G
b0000000-c0000000 b0000000-c0000000 256MB 2.75G-3G
c0000000-d0000000 30000000-40000000 256MB 3G-3.25G
d-完 d-完 768MB 3.25G-4G
DRAM有效范围:
DMC0: 0x30000000-0x3FFFFFFF
DMC1: 0x40000000-0x4FFFFFFF
结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。
思考:为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。
-----------------------lowlever_init.s中的代码讲解结束-----------------------
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
设置TTB(cp15的c2寄存器)
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工作时会自动去查转换表。
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop
#endif
cp15的c1寄存器的bit0控制MMU的开关。只要将这一个bit置1即可开启MMU。开启MMU之后上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。