分类: 嵌入式
2010-10-19 13:01:23
MMU与PTS表格
最近在FPGA上仿真调试Virgo(基于ARM11的一款处理器)芯片。MMU部分总是出错,具体的现象是查看物理地址和虚拟地址的映射时候芯片经常会挂掉。先是怀疑MMU的寄存器配置有问题,后来又怀疑MMU映射使用的PTS表格有问题,最后发现竟然是RAM没有清零导致的,唉,竟然犯了这种的错误,实在是雷人。
为了解决问题,这两天对这部分代码进行了分析和调试,担心过两天会忘掉,赶紧写下来。
1.MMU初始化代码分析
其实MMU的初始化过程就是PTS表格的初始化过程。
那么啥是PTS表格呢?
PTS表格是供MMU进行地址映射和察看内存属性信息的表格。
PTS表格主要记录了两方面的信息,第一:虚拟地址对应的物理地址在哪个位置,第二:虚拟地址的属性信息,如上面的0x402/0x40e/0x41e。
MMU详细的初始化过程参照下面的代码解释:
b .
INCLUDE oemaddrtab_cfg.inc
;------------------------------------------------------------------
; Compute physical address of the OEMAddressTable.
20 add r11, pc, #g_oalAddressTable - (. + 8)
ldr r10, =PTs ; (r10) = 1st level page table
; Setup 1st level page table (using section descriptor)
; Fill in first level page table entries to create "un-mapped" regions
; from the contents of the MemoryMap array.
;
; (r10) = 1st level page table
; (r11) = ptr to MemoryMap array
; 接下来这三行代码是配置ptr指针的位置,以及初始化DRAM部分物理地址在PTS映射表中的标记,即E
; 后面会将这个标记放置到PTS映射表中
add r10, r10, #0x2000 ; (r10) = ptr to 1st PTE for "unmapped space"
mov r0, #0x0E ; (r0) = PTE for 0: 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
25 mov r1, r11 ; (r1) = ptr to MemoryMap array
; 获取g_oalAddressTable的参数
; 哈哈,这个就不用解释了
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
; g_oalAddressTable表格的最后一行是DCD 0x00000000, 0x00000000, 0
; 也即r4 = 0
cmp r4, #0 ; End of table?
beq %f40
; 这里也不用说了,就是限定最大值为MB和GB
ldr r5, =0x1FF00000
and r2, r2, r5 ; VA needs 512MB, 1MB aligned.
ldr r5, =0xFFF00000
and r3, r3, r5 ; PA needs 4GB, 1MB aligned.
; 值得一提的是下面的这个值,网上的争论也比较多
; PTS表格中的最小元素代表了MB的物理地址空间,这也是g_oalAddressTable中映射的最小单位是MB的原因
; 假如说有MB的DRAM需要进行映射,每MB在PTS表格中占据一个元素(四个字节的位置),最终就是:
; 第一个MB放置在PTS表格偏移为x0的位置,假如说这段MB DRAM 的物理地址是x3000 0000,则存放到这里的数据就是x3000 0000
; 第二个MB放置在PTS表格中偏移为x4的位置,数据是x3010 0000[即这MB空间的起始物理地址]
; 第三个MB放置在PTS表格中偏移为x8的位置,数据是x3020 0000[即这MB空间的起始物理地址]
; 第四个MB放置在PTS表格中偏移为xc的位置,数据是x3030 0000[即这MB空间的起始物理地址]
; 如果DRAM很大的话,依次类推
; 注意观察一下上面的偏移x0,其实可以通过(x3000 0000-0x3000 0000)>>18计算出来
; 注意观察一下上面的偏移x4,其实可以通过(x3010 0000-0x3000 0000)>>18计算出来
; 注意观察一下上面的偏移x8,其实可以通过(x3020 0000-0x3000 0000)>>18计算出来
; 注意观察一下上面的偏移xc,其实可以通过(x3030 0000-0x3000 0000)>>18计算出来
; 很明显,这个为的右移值是由PTS的最小元素所代表的物理空间大小决定的
add r2, r10, r2, LSR #18
add r0, r0, r3 ; (r0) = PTE for next physical page
; 接下来这四行代码就是将DRAM或者寄存器对应的物理地址填充到PTS表格中,r2是表格的指针,r0是待映射的物理地址
35 str r0, [r2], #4
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page
sub r4, r4, #1 ; Decrement number of MB left
cmp r4, #0
bne %b35 ; Map next MB
bic r0, r0, #0xF0000000 ; Clear Section Base Address Field
bic r0, r0, #0x0FF00000 ; Clear Section Base Address Field
; 查询g_oalAddressTable表格的下一个Element(不知道该咋翻译)
; 起始一个Element就对应表格g_oalAddressTable的一行,如DCD 0x93300000, 0xD0102000, 1就是一个element
b %b30 ; Get next element
; 下面这行代码是用来将g_oalAddressTable表格中的物理地址同时也映射到xa000 0000~0xbfff ffff这个Uncache空间中
; tst r0, #8和bic r0, r0, #0x0C是用来计算后需要填充PTS表格中的标记,其实结果就是x402
; 第三行add r10, r10, #0x0800中的x0800其实就是xa000 0000在PTS表格中的相对偏移(相对于虚拟地址x8000 0000
; 在pts表格中位置的偏移),可以这样计算
; (0xa000 0000-0x8000 0000)>>18 = 0x0800
; 第行代码没有意义,可以删除
40 tst r0, #8
bic r0, r0, #0x0C ; clear cachable & bufferable bits in PTE
add r10, r10, #0x0800 ; (r10) = ptr to 1st PTE for "unmapped uncached space"
bne %b25 ; go setup PTEs for uncached space
sub r10, r10, #0x3000 ; (r10) = restore address of 1st level page table ?
; 接下来是将虚拟地址x0000 0000~0x000f ffff这段空间映射到物理RAM的前MB空间
; 该芯片上RAM的物理基址在x7000 0000,所以对应的就是x7000 0000~0x700f ffff
; 值得说明的是x7000040E,表示位于x7000 0000这MB空间的基址
; 而r0表示虚拟地址x0000 0000在PTS表格中的位置,其实就在PTS表格中的最开始位置
; 1. Setup mmu to map (VA == 0) to (PA == 0x70000000).
; 1-1. cached area.
ldr r0, =PTs ; PTE entry for VA = 0
ldr r1, =0x7000040E ; cache/buffer/rw, PA base == 0x70000000
;ldr r1, =0x70000402 ; cache/buffer/rw, PA base == 0x70000000
str r1, [r0]
; 下面三行其实和上面的四行代码类似,表示将虚拟地址x2000 0000映射到物理地址x7000 0000
; 第一行代码中的x0800表示虚拟地址x2000 0000在PTS表格中的偏移
; 而是UNCACHE ram的标记
; 1-2. uncached area.
add r0, r0, #0x0800 ; PTE entry for VA = 0x0200.0000 , uncached
ldr r1, =0x70000402 ; uncache/unbuffer/rw, base == 0x70000000
str r1, [r0]
; 接下来这段代码将虚拟地址x7000 0000映射到物理地址x7000 0000,这段映射空间的大小是MB
; 即DRAM空间的大小
; Comment:
; The following loop is to direct map RAM VA == PA. i.e.
; VA == 0x70XXXXXX => PA == 0x70XXXXXX for Virgo
; Fill in 8 entries to have a direct mapping for DRAM
;
ldr r10, =PTs ; restore address of 1st level page table
ldr r0, =PHYBASE
; 下面这一行#(0x7000 / 4)同样是计算虚拟地址x7000 0000在PTS表格中的偏移
; 下面这段代码我没有改,抄袭了三星的做法,它们没有写好,正确的写法应该是:
; (0x7000 0000>>18),是不是搞得你云里雾里的,鄙视Samsung,将来Vrigo的方案
; 出去之后,一定要把公版BSP给改的简单易懂,要不然OEM厂家又要骂了
add r10, r10, #(0x7000 / 4) ; (r10) = ptr to 1st PTE for (0x70000000>>16)/sizeof(DWORD)
; 下面的#0x1E和#0x400最终组合成一个标记x40e,类似于前面的x402和x40e。
add r0, r0, #0x1E ; 1MB cachable bufferable
orr r0, r0, #0x400 ; set kernel r/w permission
mov r1, #0
mov r3, #64 ; 128MB SDRAM
;mov r3, #128 ; 128MB SDRAM
; 下面的r2表示当前的映射在PTS表格中的偏移
; 第三行代码纯属三星的人发贱,正确易懂的写法是add r2, r10, r2, LSL2
; 干脆用C语言写更加易懂一些,就是r2 = r2*4 + r10,这里的左移Bit主要原因还是PTS中的每个元素是个字节
45 mov r2, r1 ; (r2) = virtual address to map Bank at
cmp r2, #0x20000000:SHR:BANK_SHIFT
add r2, r10, r2, LSL #BANK_SHIFT-18
strlo r0, [r2]
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page
subs r3, r3, #1
add r1, r1, #1
bgt %b45
; 兄弟们肯定在想,我考你在这里搞了大半天,修改的都是PTS,那MMU咋能知道呢?
; 呵呵,不要急,到了,下面的p15, 0, r10, c2, c0, 0不是把PTS的地址给MMU了么,哈哈,大功告成
; 就剩下启动MMU了
ldr r10, =PTs ; (r10) = restore address of 1st level page table
; The page tables and exception vectors are setup.
; Initialize the MMU and turn it on.
mov r1, #1
mcr p15, 0, r1, c3, c0, 0 ; setup access to domain 0
mcr p15, 0, r10, c2, c0, 0
mcr p15, 0, r0, c8, c7, 0 ; flush I+D TLBs
; mrc p15,0,r1,c1,c0,0
orr r1, r1, #0x0071 ; Enable: MMU
orr r1, r1, #0x0004 ; Enable the cache
ldr r0, =VirtualStart
cmp r0, #0 ; make sure no stall on "mov pc,r0" below
; OK,终于把MMU给enable了,可以用了,哈哈,爽
mcr p15, 0, r1, c1, c0, 0
mov pc, r0 ; & jump to new virtual address
nop
; MMU & caches now enabled.
; (r10) = physcial address of 1st level page table
;
;------------------------------------------------------------------
VirtualStart
mrs r0, cpsr
; 下面这段是堆栈的配置,如果你发现EBoot下面的变量和数组比较多的话,一定要调整下面
; 如Samsung的whimory.eboot就需要相当大的Stack空间,小的话就会出莫名其妙的问题
; 哦,对了,差点忘了,Stack是从上朝下增长的,而Ram是从从下朝上增长的,不要越界了
bic r0, r0, #Mode_MASK
orr r1, r0, #Mode_IRQ | NOINT
msr cpsr_cxsf, r1 ; IRQMode
mov sp, #0x80000000
add sp, sp, #0x3d000 ;
bic r0, r0, #Mode_MASK | NOINT
orr r1, r0, #Mode_SVC
msr cpsr_cxsf, r1 ; SVCMode
mov sp, #0x80000000
add sp, sp, #0x40000 ;
b main
ENTRY_END
LTORG
2.最终生成的PTS表格
上面的代码太抽象了,我把PTS表格Dump出来之后用表格列写了以下,如下:
其中,第四列表示虚拟地址,第三列表示虚拟地址对应物理地址,第一列表示PTS表格中的位置偏移,而第二列为PTS表格中存放的数据。每1MB的虚拟地址在下面的表格中都对应一行。
address
value
physical address
VIRTUAL ADD
CHIP
SPACE
0X70012000
0X7000040E
0X70000000
0x80000000
DDR
Cached Space
0X70012004
0X7010040E
0X70100000
0X70012008
0X7020040E
0X70200000
0X7001200C
0X7030040E
0X70300000
…
…
…
0X700121FC
0X77F0040E
0X77F00000
0x700124C8
0xD010040E
0xD0101000
0x93200000
UART0
0X70012800
0x70000402
0X70000000
0xA0000000
DDR
UNCACHED SPACE
0X70012804
0x70100402
0X70100000
0xA0100000
…
…
…
0x70012CC8
0xD0100402
0xD0101000
0xA3200000
UART0
0X70010000
0X7000040E
0X70000000
0X00000000
DDR
映射0地址到物理内存的开始位置,这里只映射1MB的空间,属性为Cache
0X70010800
0xD0100402
0X70000000
0X20000000
DDR
映射0x20000000地址到物理内存的开始位置,这里也是仅仅映射1MB的空间,属性为UnCache
0X70011C00
0X7000041E
0X70000000
X70000000
DDR
映射地址0X70000000到物理内存开始的位置
0X70011C04
0X7010041E
0X70100000
0X70100000
0X70011C08
0X7020041E
0X70200000
0X70200000
…
…
…
…
0X70011DFC
0X77F0041E
0X77F00000
0X77F00000
最终赋值给MMU的值如下:
Value
MMU.SFR
About
1
c3&c0
Open MMU
PTs(0x70010000)
c2&c0
set up access to domain 0
7800041e
c8&c7
flush I+D TLBs
5007d
c0&c1
Enable: MMU and cache
3.物理地址和虚拟地址映射关系图形化显示
感觉上面的物理地址和虚拟地址的映射不够形象,我把他们的映射关系用下面的图形表示。
1> 0x0000 0000~0x000f ffff和0x7000 0000~0x700f ffff的映射如下
2> 0x2000 0000~0x200f ffff和0x7000 0000~0x700f ffff的映射如下
3> 0x8000 0000~0x83ff ffff与0x7000 0000~0x73ff ffff的映射如下
4> 0xa000 0000~0xa3ff ffff与0x7000 0000~0x73ff ffff的映射如下
5> UART寄存器的映射如下
4.附g_oalAddressTable表格
; Export Definition
EXPORT g_oalAddressTable[DATA]
;------------------------------------------------------------------------------
;
; TABLE FORMAT
; cached address, physical address, size
;------------------------------------------------------------------------------
g_oalAddressTable
DCD 0x80000000, 0x70000000, 64 ; 512 MB DRAM BANK
DCD 0x93200000, 0xD0101000, 1 ; uart0 slv register
DCD 0x00000000, 0x00000000, 0 ; end of table
;------------------------------------------------------------------------------
END