#define PROC_ENTRY_SIZE (4*5)
.align 5
cache_on: mov r3, #8 @ cache_on function
b call_cache_fn
call_cache_fn: adr r12, proc_types
#ifdef CONFIG_CPU_CP15
mrc p15, 0, r9, c0, c0 @ get processor ID
#elif defined(CONFIG_CPU_V7M)
/*
* On v7-M the processor id is located in the V7M_SCB_CPUID
* register, but as cache handling is IMPLEMENTATION DEFINED on
* v7-M (if existant at all) we just return early here.
* If V7M_SCB_CPUID were used the cpu ID functions (i.e.
* __armv7_mmu_cache_{on,off,flush}) would be selected which
* use cp15 registers that are not implemented on v7-M.
*/
bx lr
#else
ldr r9, =CONFIG_PROCESSOR_ID
#endif
1: ldr r1, [r12, #0] @ get value
ldr r2, [r12, #4] @ get mask
eor r1, r1, r9 @ (real ^ match)
tst r1, r2 @ & mask
ARM( addeq pc, r12, r3 ) @ call cache function
THUMB( addeq r12, r3 )
THUMB( moveq pc, r12 ) @ call cache function
add r12, r12, #PROC_ENTRY_SIZE
b 1b
首先读MIDR寄存器,这里参考cortex-A7 mpcore
接着遍历proc_types
最后会匹配到
.word 0x000f0000 @ new CPU Id
.word 0x000f0000
W(b) __armv7_mmu_cache_on
W(b) __armv7_mmu_cache_off
W(b) __armv7_mmu_cache_flush
所以这里的cache_on实际调用的是_armv7_mmu_cache_on
首先有个问题,使能cache为什么要开启mmu?
可以看到i-chche使用的是VIPT,需要虚拟地址,所以要开启mmu。
当然如果只是使用d-cache的话,不需要开启mmu。
可以看这个表
__armv7_mmu_cache_on:
mov r12, lr
#ifdef CONFIG_MMU
mrc p15, 0, r11, c0, c1, 4 @ read ID_MMFR0
tst r11, #0xf @ VMSA
movne r6, #CB_BITS | 0x02 @ !XN
blne __setup_mmu
mov r0, #0
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
tst r11, #0xf @ VMSA
mcrne p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
#endif
读取ID_MMFR0的值,判断是不是支持VMSA
支持的话,会执行下面的带ne条件的指令
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
#define CB_BITS 0x08
#else
#define CB_BITS 0x0c
#endif
没有配置CPU_DCACHE_WRITETHROUGH, 所以CB_BITS值是 0x0c
此时r6的值 0x0c | 0x02
__setup_mmu: sub r3, r4, #16384 @ Page directory size
bic r3, r3, #0xff @ Align the pointer
bic r3, r3, #0x3f00
r4存放的是内核要解压到的址,这里将其减去16k做页表,并且做地址对齐操作
/*
* Initialise the page tables, turning on the cacheable and bufferable
* bits for the RAM area only.
*/
mov r0, r3
/* 这里原值是18, rockchip改成了20
* 也就是由256k对齐改成了1M对齐、
* 256KB alignment is not work for (textofs & 0xf0000) > 0x40000. Change to 1MB.
*/
mov r9, r0, lsr #20
mov r9, r9, lsl #20 @ start of RAM
/* r9是ram的起始地址
* r10是ram的结束地址,大小256M
*/
add r10, r9, #0x10000000 @ a reasonable RAM size
mov r1, #0x12 @ XN|U + section mapping
orr r1, r1, #3 << 10 @ AP=11
以上设置可以知道使用段方式映射,XN=1,AP[2:0] = 011
XN=1表示不可以执行
对于ap有这样的解释
这里SCTLR.AFE值为0,所以使用AP[2:0]
这里的配置的是011,有完全的读写权限
注意这里的r1其实是虚拟地址+标志位,起虚拟地址是从0开始段映射的,bit[31:20]要填的是PA,那么这么为什么是VA? 因为这里要做1:1的映射,所以就没有VA之说,都是PA
/* r3是页表基地址
* r2是页表结束地址
*/
add r2, r3, #16384
1: cmp r1, r9 @ if virt > start of RAM
cmphs r10, r1 @ && end of RAM > virt
bic r1, r1, #0x1c @ clear XN|U + C + B
orrlo r1, r1, #0x10 @ Set XN|U for non-RAM
orrhs r1, r1, r6 @ set RAM section settings
str r1, [r0], #4 @ 1:1 mapping
add r1, r1, #1048576
teq r0, r2
bne 1b
//c code
while (r0 != r2) {
r1 &= ~(0x1c); //clear XN|U + C + B
if((r1 >= r9) && (r1 < r10)) {
//r6的值 0x0c | 0x02
r1 |= r6 @ set RAM section settings
} else {
r1 |= 0x10 @ Set XN|U for non-RAM
}
*r0 = r1
r0 += 4
r1 += 1048576 //1Mbyte
}
这里继续分析一下映射的可执行区域的配置,上面配置的CB是11
对于这里的write-Back和no Write-Allo其实是的针对cache的
对于cache可以去看 浅谈cache memory 文章写的非常给力
以上的工作只是创建了页表,并没有开启MMU
/*
* If ever we are running from Flash, then we surely want the cache
* to be enabled also for our execution instance... We map 2MB of it
* so there is no map overlap problem for up to 1 MB compressed kernel.
* If the execution is in RAM then we would only be duplicating the above.
*/
orr r1, r6, #0x04 @ ensure B is set for this
orr r1, r1, #3 << 10
mov r2, pc
mov r2, r2, lsr #20
/* r3中保存的是页表起始地址
* 这里r2左移2的目的是找到对应的页表项
* 页表项大小是4byte,所以要左移2
*/
orr r1, r1, r2, lsl #20
add r0, r3, r2, lsl #2
str r1, [r0], #4
/* 映射下一个1M区域 */
add r1, r1, #1048576
str r1, [r0]
mov pc, lr
比如这种情况
这里就是以当前地址对齐后将包含压缩内核的2M区域进行映射,因为解压的时候,压缩内核是要被读取的。
另外注释中也说了,如果不是这种情况,那么实际上就是将ram再次重新映射一遍而已,没有影响。这里其实有个疑惑,如果压缩内核大小超过2MB怎么办?另外注释里面的从flash启动,这句话也没理解!(uboot在norflash, kernel sysfs在nandflash或者emmc这种?类似mini2440?)
回到 __armv7_mmu_cache_on继续分析
__armv7_mmu_cache_on:
mov r12, lr
#ifdef CONFIG_MMU
mrc p15, 0, r11, c0, c1, 4 @ read ID_MMFR0
tst r11, #0xf @ VMSA
movne r6, #CB_BITS | 0x02 @ !XN
blne __setup_mmu
mov r0, #0
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
tst r11, #0xf @ VMSA
mcrne p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
#endif
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
这里是一个数据同步屏障指令,当所有在它前面的存储访问操作指令都执行完毕后,才会执行在它后面的指令,即任何指令都要等待DSB前面的存储访问完成。位于此指令前的所有缓存,如分支预测和TLB( Translation Look- aside Buffer)维护操作全部完成。
对于arm VMSA
使无效整个数据TLB和指令TLB
mrc p15, 0, r0, c1, c0, 0 @ read control reg
bic r0, r0, #1 << 28 @ clear SCTLR.TRE
TRE清0, 禁用 TEX 重映射。 TEX[2:0] 与 C 和 B 位一起用于描述内存区域属性。
orr r0, r0, #0x5000 @ I-cache enable, RR cache replacement
首先是I-cache使能
cortex-A7 Mpcore 这里的bit14是不支持的。如果缓存实现支持使用具有更容易预测的最坏情况性能的替代替换策略,这里会选择这个bit。
orr r0, r0, #0x003c @ write buffer
bic r0, r0, #2 @ A (no unaligned access fault)
- dcache使能
- 没有未对齐的访问错误
orr r0, r0, #1 << 22 @ U (v6 unaligned access model)
@ (needed for ARM1176)
armv7不支持这个bit
mrcne p15, 0, r6, c2, c0, 2 @ read ttb control reg
orrne r0, r0, #1 @ MMU enabled
/* 01 客户端。 D0 根据转换表中的权限位检查访问。
* armv7有16个domain, 每个domain可以和页表条目绑定,用于描述访问权限
* 每个域的可能设置是:
* 无访问
* 使用转换表描述符的任何访问都会产生域故障。
* 客户端
* 在使用转换表描述符进行访问时,检查访问权限属性。
* 因此,访问可能会产生权限错误。
* 管理者
* 在使用转换表描述符的访问中,不检查访问权限属性。
* 因此,访问不能产生 Permission 故障。
* 这里使用D0的原因,页表里面设置的就是domain 0
*/
movne r1, #0xfffffffd @ domain 0 = client
bic r6, r6, #1 << 31 @ 32-bit translation system
bic r6, r6, #(7 << 0) | (1 << 4) @ use only ttbr0
/* r3是页表的基地址,写到ttbr0中,标志位没有设置 ?*/
mcrne p15, 0, r3, c2, c0, 0 @ load page table pointer
/* 写 DACR 页表domain对应使用D0 client */
mcrne p15, 0, r1, c3, c0, 0 @ load domain access control
/* 写 TTBCR 只使用ttbr0 */
mcrne p15, 0, r6, c2, c0, 2 @ load ttb control
/* 指令同步屏障刷新处理器中的流水线,以便在指令完成后,
* 从高速缓存或内存中取出 ISB 之后的所有指令。
* 它确保在 ISB 指令之前执行的上下文更改操作的效果对于在 ISB 之后获取的指令是可见的。
* 上下文更改操作包括更改地址空间标识符 (ASID)、TLB 维护操作、分支预测器维护操作以
* 及对 CP15 寄存器的所有更改。
* 此外,在 ISB 指令之后按程序顺序出现的任何分支都将写入分支预测逻辑,
* 其上下文在 ISB 指令之后可见。
* 这是确保指令流正确执行所必需的。
*/
mcr p15, 0, r0, c7, c5, 4 @ ISB
/* 写SCTLR MMU启动 写入之后, 系统立即生效*/
mcr p15, 0, r0, c1, c0, 0 @ load control register
mrc p15, 0, r0, c1, c0, 0 @ and read it back
mov r0, #0
/* 再次调用 ISB 指令 */
mcr p15, 0, r0, c7, c5, 4 @ ISB
mov pc, r12