走读 arch/arm64/kernel/head.S 代码时,发现一些关键点需要厘清,这里记录下来:
支持MMU功能的CPU在MMU没有开启(如上电复位时或者人为关闭)的情况下都有相关机制,ARM中采用flat address mapping。
ARM在DDI0487A_k_armv8_arm文档中对flat address mapping描述如下:
Flat address mapping
Is where the physical address for every access is equal to its virtual address.
也就是pa==va,没有基地址+偏移。
同时该文档在D4.2.8 The effects of disabling a stage of address translation谈到对AARCH64 MMU stage1/stage2 disable时特性,包括数据与地址访问。
至于MMU什么时候disable,在SCTLR_EL1.M/SCTLR_EL2.M/SCTLR_EL3.M描述时指出When this register has an architecturally-defined reset value, this field resets to 0.
这样在CPU复位上电后MMU是Disable。
ARM64 Linux内核在引导时也要MMU disable,具体见head.S文件:
// arch/arm64/kernel/head.S
/*
* Kernel startup entry point.
* ---------------------------
*
* The requirements are:
* MMU = off, D-cache = off, I-cache = on or off,
* x0 = physical address to the FDT blob.
*
* This code is mostly position independent so you call this at
* __pa(PAGE_OFFSET + TEXT_OFFSET).
*
* Note that the callee-saved registers are used for storing variables
* that are useful before the MMU is enabled. The allocations are described
* in the entry routines.
*/
归纳起来,Linux在引导时MMU关闭,pa==va。这里实际上也是内核对boot loader类程序在跳入Linux内核执行时对CPU状态设置要求。
主要包括两部分:物理内存地址空间和虚拟地址空间
物理内存地址空间包含DRAM内存空间、mapped IO(PCIe映射的IO)、保留空间、先前内存空间。对DRAM内存空间,架构内存映射(DEN0001C_principles_of_arm_memory_maps)提出一些要求,关键如下:
3.1.3 DRAM
P9.Recommended that DRAM should be populated in large contiguous regions.
Operating systems and hypervisors vary in their ability to manage fragmented physical memory regions. Having fewer memory regions containing contiguous RAM can help increase software compatibility and efficient implementation.
Regions of contiguous physical RAM are often required when handing control over between software environments, such as; boot loaders, hypervisors and operating systems.
P10.Recommend that DRAM is populated into the lowest addresses memory regions first.
Since there is memory pressure on lower addressed regions to provide interworking space.
3.1.6 32bit Systems Constraints
P19.The 32-bit DRAM region address space should be fully populated, before DRAM regions in higher address spaces.
The pure 32-bit DRAM will be under utilization pressure for interworking between 32-bit peripherals, and use by all non-LPAE software.
P20.Dynamic I/O regions (if present) should exist in the 32-bit address space.
32-bit only operating systems will require access to dynamic I/O, such as PCIe.
4.1 32-bit Memory Map
4GB +-----------------+ <- 32-bit
| DRAM |
| |
2GB +-----------------+
| Mapped I/O |
1GB +-----------------+
| ROM & RAM & I/O |
0GB +-----------------+ 0
Figure 2 32-bit memory map
DRAM映射在32bit低地址空间全部映射后再往高地址空间映射,若没有PCIe IO设备(64位CPU这部分空间不映射PCIe设备),DRAM内存会从1GB(0x40000000)物理地址开始。
ARM64目前版本支持的最大物理地址空间是2**48,也就是48位物理地址空间。
CPU直接访问的是虚拟地址,64位CPU就有64位(虚拟)地址位,CPU会把这么大的地址空间(2**64)分成几部分来使用,比如高几位作为是内核态使用还是用户态和内核态使用,抑或是不是经过mmu转换等等,这些都是由CPU架构规范决定。
ARM64 CPU中某一段虚拟空间是内核态代码使用还是用户态代码使用没有限制,目前也只支持最大48位虚拟地址空间。只是把64位地址空间分成三部分:
高VA空间,0xFFFF000000000000 --- 0xFFFFFFFFFFFFFFFF
VA空洞,0x0001000000000000 --- 0xFFFEFFFFFFFFFFFF
低VA空间,0x0000000000000000 --- 0x0000FFFFFFFFFFFF
高VA空间由TTBR1_EL1确定页表1基地址后进行地址VA–>PA转换,低VA空间有TTBR0_EL1确定页表0基地址后进行转换。
(64位虚拟地址的高8位bits[63:56]也可以作为地址tag(由bit[55]决定)使用,这里不展开)
在Linux 内核引导时会用到TTBR0_EL1和TTBR1_EL1,后面内核起来后只用了TTBR1_EL1,TTBR0_EL1只给用户进程用,也说明用户进程只能使用低VA空间地址,Linux 内核起来后只使用高VA空间。
注意EL2/EL3是虚拟地址空间范围最大也只有48位:
EL2/EL3 VA空间: 0x0000000000000000 — 0x0000FFFFFFFFFFFF
进行mmu转换也只能由TTBR0_EL2/TTBR0_EL3决定页表基地址。
CPU直接使用64位虚拟地址,这个64位虚拟地址由三种方式生成:
Register indexed addressing: 64bit base register ± 64-bit index register,直接生成地址主要是BLR/BR/RET/ERET/SVC/BRK/SMC/HVC这些指令
PC-relative addressing: 主要是位置无关代码需要,PC±32KB/±1MB/±128MB
Load/Store addressing modes:X0--X30或SP ±offset或ADR指令 PC±4GB
但是在代码中情况比较复杂:
内核代码最终会落在高VA地址范围(0xFFFF000000000000 — 0xFFFFFFFFFFFFFFFF),所以要求代码的编译地址空间要落在这里。
但是引导内核时MMU被关闭,并且DRAM内存只能落在物理地址空间0x40000000以上的低VA空间,va==pa,这段引导代码必须与位置无关,只能用相对方式寻址。
在MMU使能后,要跳转到编译时生成的高VA地址,这涉及到怎么获得高VA地址。
这里涉及到编译(逻辑)地址、运行地址,虚地址、实地址以及mmu关闭时flat address mapping、从mmu关闭到mmu使能切换、mmu使能时TTBR0/1_EL1页表设置等等。
运行(虚)地址,采用位置无关的指令生成的代码可以做到位置无关。在MMU关闭时VA==PA。
逻辑(虚)地址,连接器把相关代码连接成可执行程序时使用逻辑地址,也就是编译链接生成的地址,变量或函数都有确定的地址,可通过nm查看函数或变量的地址。编译链接器具体使用哪个地址可以由链接脚本去决定。
在MMU使能后,运行地址==逻辑地址==链接地址==VA。
用链接脚本标号或程序标号,在不超过相对偏移(±128MB)时使用与位置无关的PC-relative指令。
// arch/arm64/kernel/head.S
ldr x8, =__primary_switched // get __primary_switched VA
// LDR pseudo-instruction
// ldr x8, 0x40887270
// 0x40887270: 0xffff000008c3017c // store link addr
adrp x0, __PHYS_OFFSET // __PHYS_OFFSET==(KERNEL_START - TEXT_OFFSET)==(_text - TEXT_OFFSET)
// _text == KIMAGE_VADDR + TEXT_OFFSET
// while mmu off, so __PHYS_OFFSET==KIMAGE_PADDR
br x8
LDR pseudo-instruction
LDR Xd, =expr
LDR Xd, =label_expr
When using the LDR pseudo-instruction, the assembler places the value of expr or label_expr in a literal pool and generates a PC-relative LDR instruction that reads the constant from the literal pool.
• An address loaded in this way is fixed at link time, so the code is not position-independent.
• The address holding the constant remains valid regardless of where the linker places the ELF section
containing the LDR instruction.
ARM需要identity mapping是由于MMU从disable到re-enable转换过程中只是SCTLR_EL1.M位被置上,实际PC还没有更新。这时候MMU已经启用,同时这时候PC落在低VA空间,所以页表基地址选择TTBR0_EL1,这里只是借用一下TTBR0_EL1,后面会释放掉以便用户进程使用。
后面再分析其他方面。
其实也可以通过(http://blog.csdn.net/txhuaz/article/details/54233225)介绍gdb方式来跟踪head.S代码执行过程。
参考资料:
AEG0014G_ARM_corporate_glossary
DEN0001C_principles_of_arm_memory_maps
DDI0487A_k_armv8_arm
DUI0801G_armasm_user_guide
Debugging with gdb/The gnu Source-Level Debugger/Tenth Edition, for gdb version 7.12/(GDB)
http://embeddedvenkatpari.blogspot.com/2016/08/mmu-initialization-on-arm.html