下面将详细叙述MMU的设置,也是本人花费时间最多的一部分内容,无论是2410、6410甚至是Cortex-A8核的ARM,MMU的设置基本都一样,所以移植时这部分可以直接搬过来,只需要更改全局内存映射表的映射关系即可。
先说说为什么在
EBoot
要设置
MMU
?其实有大牛们讨论过这个话题,在系统启动时会对页表进行重新映射,包括二级页表的设置,而在
EBoot
中只进行了一级页表的设置,最后也没有给出明确的答案,有的说是
WinCE
规定的,这里先不追究了,等以后研究了系统启动后的代码,再来讨论这个问题。
OAL
作为
WinCE
的开始需要启用虚拟内存,需要为
MMU
设置正确的页表进行地址映射,另外
WinCE
编译系统产生的二进制文件
.bib
使用的内存地址都是虚拟地址,这些虚拟地址是编译系统对二进制代码进行地址重定位的重要依据,所以在
Eboot
中对
MMU
进行设置,定义内存映射表的依据是全局内存映射表,在
PLATFORM\SMDK6410\SRC\INC\oemaddrtab_cfg.inc
文件中定义,内容如下:
g_oalAddressTable
; mDDR 128 MB
;DCD 0x80000000, 0x50000000, 64 ; 64 MB DRAM
[ SMDK6410_X5D
DCD 0x80000000, 0x60000000, 64 ; 64 MB DRAM
|
DCD 0x80000000, 0x50000000, 128 ; 128 MB DRAM
]
DCD 0x90000000, 0x70000000, 4 ; SROM SFR
DCD 0x00000000, 0x00000000, 0 ; end of table
; mDDR 128 MB
;DCD 0x80000000, 0x50000000, 64 ; 64 MB DRAM
[ SMDK6410_X5D
DCD 0x80000000, 0x60000000, 64 ; 64 MB DRAM
|
DCD 0x80000000, 0x50000000, 128 ; 128 MB DRAM
]
DCD 0x90000000, 0x70000000, 4 ; SROM SFR
DCD 0x00000000, 0x00000000, 0 ; end of table
它是以
g_oalAddressTable
宏开始的,
DCD
用来分配一片连续的字存储单元,第一个参数是虚拟地址值,第二个参数是对应的物理地址值,第三个参数指明分配的大小,在映射表的最后,将三个参数的值均设为
0
,表示是映射表的结束。下面看
startup.s
文件中的源码。
;------------------------------------
; Initialize MMU Table
;------------------------------------
;----------------------------
; Compute physical address of the OEMAddressTable.
20
add r11, pc, #g_oalAddressTable -(. + 8)
ldr r10, =PT_1ST_BASE ; (r10) = 1st level page table
; Initialize MMU Table
;------------------------------------
;----------------------------
; Compute physical address of the OEMAddressTable.
20
add r11, pc, #g_oalAddressTable -(. + 8)
ldr r10, =PT_1ST_BASE ; (r10) = 1st level page table
上面的代码将
R11
赋值为内存映射表的地址,
R10
赋值为页表存储的地址,
PT_1ST_BASE
在最开始处已经进行了定义,是页表基地址
0x50010000
,解释一下
R11
的赋值语句。
ARM
处理器是流水线结构,允许指令预取,所以
PC
一般等于当前执行指令下面的第
2
条指令的地址,即
PC=
当前指令地址值
+8
字节,而“
.
”代表的是当前指令的地址值,
PC=
(
. + 8
)
,
这样
R11
获得的是
g_oalAddressTable
的地址。那为什么不直接
mov r11 #g_oalAddressTable
进行赋值呢?确实不能这样,这样可以编译通过,但是执行不通过,只能采用和
PC
的相对值来换算。
add r10, r10, #0x2000 ; (r10) = ptr to 1st PTE for "unmapped space"
这一条代码,将
R10
的值增加了
0x2000
,因为第
1
级
MMU
的入口是地址值的高
14
为(过滤掉低
18
位),
g_oalAddressTable
中看出,
DRAM
的起始虚拟地址为
0x80000000
,而
0x80000000>>18=0x2000
,正好是
0x80000000
地址对应的第
1
级
MMU
入口的偏移值,也就是说
R10
现在存储的是
0x80000000
虚拟地址对应的页表的存储地址。那么为什么要右移
18
位?第
1
级页表将
4GB
的地址空间划分为多个
1MB
的段,对应的就有
4096
个页表项,而
ARM
地址映射时,虚拟地址被分为两部分:高位
+
低位,高位用来表示虚拟地址对应的页表针对页表首地址的偏移值,而低位表示在页表
1MB
地址空间中的偏移量,页表中的一个页表项可以描述
4
字节的虚拟页(因为
ARM
是
32
位的,每次都是读取
4
字节的数据),那么
1MB
的空间就需要
256KB
个这样的页表项才可以描述(
256K*4=1M
),而要表示
256KB
的大小,需要虚拟地址的低
18
位,剩下的高
14
位就可以用来计算虚拟地址对应页表相对于页表首地址的偏移量了。
mov r0, #0x0E ; (r0) = PTE for 0: 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
orr r0, r0, #0x400 ; set kernel r/w permission
上面的两条代码用来设置页表项的高速缓冲、写缓冲以及读写属性,具体的设置请参看
CP15
协处理器
C1
寄存器的功能,以后有时间会整理一篇关于
CP15
协处理器的说明。其实读者如果仔细看会发现,设置后
R0=0x40E
,和文件开始声明的
PT_1ST_ENTRY_CNB
变量的值是一样的,所以也可以用
mov r0 PT_1ST_ENTRY_CNB
来代替两条语句,但是本人觉得用两条语句更能表明设置了什么,语义更明确。
25
mov r1, r11 ; (r1) = ptr to MemoryMap array
30
ldr r2, [r1], #4 ; (r2) = virtual address to map Bank at
ldr r3, [r1], #4 ; (r3) = physical address to map from
ldr r4, [r1], #4 ; (r4) = num MB to map
mov r1, r11 ; (r1) = ptr to MemoryMap array
30
ldr r2, [r1], #4 ; (r2) = virtual address to map Bank at
ldr r3, [r1], #4 ; (r3) = physical address to map from
ldr r4, [r1], #4 ; (r4) = num MB to map
首先将
R11
也就是
g_oalAddressTable
指向的全局内存映射表的地址赋给
R1
(这就是高手的代码书写习惯,不会直接操作
R11
),然后依次将
R1
指向的地址处的数据赋值给
R2
、
R3
和
R4
寄存器,“
#4
”是表示每次操作完后,
R1
的地址值增加
4
字节,指向下一个数据,其实对照
oemaddrtab_cfg.inc
的映射表,很容易发现,
R2
获得的是虚拟地址的值,
R3
获得的是对应物理地址的值,
R4
则是这段存储空间的大小。
cmp r4, #0 ; End of table?
beq %F40
beq %F40
上面两条代码用来判断是否到了
oemaddrtab_cfg.in
中映射表的结尾,上面已经介绍过,映射表的最后一条的三个参数全部都为
0
,标识映射表的结束。
beq
是一条跳转语句,表示如果相等,则跳转,
%F40
表明向后寻找名称为
40
的标号(
F=After
)。
本文转自jazka 51CTO博客,原文链接:http://blog.51cto.com/jazka/572644,如需转载请自行联系原作者