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 |
累死我了,终于写完了。
如果有没写清楚的地方,欢迎发邮件到guopeixin@126.com或者在此留言。