最近在 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 或者在此留言。