作者
彭东林
平台
TQ2440
Qemu+vexpress-ca9
Linux-4.10.17
正文
继续分析head.S:
此时r2存放的是设备树镜像的物理起始地址,r8是物理内存的起始地址,r9是从CP15的C0中读到的cpu id,r10是与该cpu id匹配的proc_info_list的物理地址
TQ2440:
r8: 0x3000_0000,r9: 0x41129200
vexpress:
r8: 0x6000_0000,r9: 0x414FC091
8、第30行调用__create_page_tables建立段式页表。
下面是__create_page_tables的定义: 其中涉及到几个宏定义:
PG_DIR_SIZE: 0x4000
PROCINFO_MM_MMUFLAGS:0x8
SECTION_SHIFT:20
PMD_ORDER:2
/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*
* Returns:
* r0, r3, r5-r7 corrupted
* r4 = physical page table address
*/
__create_page_tables:
pgtbl r4, r8 @ page table address /*
* Clear the swapper page table
*/
mov r0, r4
mov r3, #
add r6, r0, #PG_DIR_SIZE
: str r3, [r0], #
str r3, [r0], #
str r3, [r0], #
str r3, [r0], #
teq r0, r6
bne 1b ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags /*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __turn_mmu_on_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __turn_mmu_on
add r6, r6, r0 @ phys __turn_mmu_on_end
mov r5, r5, lsr #SECTION_SHIFT
mov r6, r6, lsr #SECTION_SHIFT : orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base
str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping
cmp r5, r6
addlo r5, r5, # @ next section
blo 1b /*
* Map our RAM from the start to the end of the kernel .bss section.
*/
add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
ldr r6, =(_end - )
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
: str r3, [r0], # << PMD_ORDER
add r3, r3, # << SECTION_SHIFT
cmp r0, r6
bls 1b /*
* Then map boot params address in r2 if specified.
* We map 2 sections in case the ATAGs/DTB crosses a section boundary.
*/
mov r0, r2, lsr #SECTION_SHIFT
movs r0, r0, lsl #SECTION_SHIFT
subne r3, r0, r8
addne r3, r3, #PAGE_OFFSET
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
orrne r6, r7, r0
strne r6, [r3], # << PMD_ORDER
addne r6, r6, # << SECTION_SHIFT
strne r6, [r3] ret lr
ENDPROC(__create_page_tables)
.ltorg
.align
__turn_mmu_on_loc:
.long .
.long __turn_mmu_on
.long __turn_mmu_on_end
第13行,计算段式页表在物理内存当中的起始地址,方法如下:
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET
sub \rd, \rd, #PG_DIR_SIZE
.endm
r4 = r8 + 0x8000 - 0x4000,通过前文知道r8存放的是物理内存的起始地址,所以对于TQ2440,r4为0x3000_4000,对于vexpress是0x6000_4000,也就是段式页表占据了kernel代码段(0x3000_8000或0x6000_8000)之前的16KB的物理内存
第18到第26行,将段式页表的占据的那16KB的物理内存清零
第28行,加载MMU Flags到r7中。通过之前的分析知道,r10中存放的是与cpu id匹配的proc_info_list的首地址,这里访问的是proc_info_list->__cpu_mm_mmu_flags。
对于TQ2440,是0x00000c1e, 对于vexpress来说是0x00011c0e。下面以TQ2440为例说明。参考手册 ARM920T Technical Reference Manual 的 3.3.3 Level one descriptor
对于段式页表,bit[1:0]是0x10,对于TQ2440的0xC1E,满足这一条件,所以是段式页表。具体段式页表的页表项的含义如下:
每个bit位的含义如下:
对于段式页表,只需要一级,地址转换过程如下:
理解了段式页表地址翻译的过程,也就容易理解接下来建立段式页表的代码了。
可以现在start_kernel的入口处将段式页表中的内容打印出来:
diff --git a/init/main.c b/init/main.c
index 09beb7f..fa6edd6
--- a/init/main.c
+++ b/init/main.c
@@ -, +, @@ asmlinkage __visible void __init start_kernel(void)
char *command_line;
char *after_dashes; +
+ unsigned int i, addr, value;
+ for (i=; i < 0x1000; i++) {
+ addr = 0xC0004000 + i*;
+ value = *((unsigned int *)addr);
+ if (value) {
+ pr_notice("0x%04x: hw: 0x%08x, virt: 0x%08x, value: 0x%08x map: [0x%08x ~ 0x%08x ==> 0x%08x ~ 0x%08x]\n",
+ i, __pa(addr), addr, *((int *)addr), i*(<<), i*(<<)+(<<)-,
+ ((value>>)<<), ((value>>)<<)+(<<)-);
+ }
+ }
+
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
上面加的代码会将段式表中建立了映射的段表项的编号、存放每个段表项的物理地址和虚拟地址以及段表项的内容、该段表实现的虚拟地址跟物理地址之间的映射关系都打印出来。
对于TQ2440,段表内容如下:
0x0300: hw: 0x30004c00, virt: 0xc0004c00, value: 0x30000c1e map: [0x30000000 ~ 0x300fffff ==> 0x30000000 ~ 0x300fffff]
0x0c00: hw: 0x30007000, virt: 0xc0007000, value: 0x30000c1e map: [0xc0000000 ~ 0xc00fffff ==> 0x30000000 ~ 0x300fffff]
0x0c01: hw: 0x30007004, virt: 0xc0007004, value: 0x30100c1e map: [0xc0100000 ~ 0xc01fffff ==> 0x30100000 ~ 0x301fffff]
0x0c02: hw: 0x30007008, virt: 0xc0007008, value: 0x30200c1e map: [0xc0200000 ~ 0xc02fffff ==> 0x30200000 ~ 0x302fffff]
0x0c03: hw: 0x3000700c, virt: 0xc000700c, value: 0x30300c1e map: [0xc0300000 ~ 0xc03fffff ==> 0x30300000 ~ 0x303fffff]
0x0c04: hw: 0x30007010, virt: 0xc0007010, value: 0x30400c1e map: [0xc0400000 ~ 0xc04fffff ==> 0x30400000 ~ 0x304fffff]
0x0c05: hw: 0x30007014, virt: 0xc0007014, value: 0x30500c1e map: [0xc0500000 ~ 0xc05fffff ==> 0x30500000 ~ 0x305fffff]
0x0c06: hw: 0x30007018, virt: 0xc0007018, value: 0x30600c1e map: [0xc0600000 ~ 0xc06fffff ==> 0x30600000 ~ 0x306fffff]
0x0c07: hw: 0x3000701c, virt: 0xc000701c, value: 0x30700c1e map: [0xc0700000 ~ 0xc07fffff ==> 0x30700000 ~ 0x307fffff]
0x0c3a: hw: 0x300070e8, virt: 0xc00070e8, value: 0x33a00c1e map: [0xc3a00000 ~ 0xc3afffff ==> 0x33a00000 ~ 0x33afffff]
0x0c3b: hw: 0x300070ec, virt: 0xc00070ec, value: 0x33b00c1e map: [0xc3b00000 ~ 0xc3bfffff ==> 0x33b00000 ~ 0x33bfffff]
0x0f70: hw: 0x30007dc0, virt: 0xc0007dc0, value: 0x50000c12 map: [0xf7000000 ~ 0xf70fffff ==> 0x50000000 ~ 0x500fffff]
对于vexpress,段表内容如下:
0x0601: hw: 0x60005804, virt: 0xc0005804, value: 0x60111c0e map: [0x60100000 ~ 0x601fffff ==> 0x60100000 ~ 0x601fffff]
0x0c00: hw: 0x60007000, virt: 0xc0007000, value: 0x60011c0e map: [0xc0000000 ~ 0xc00fffff ==> 0x60000000 ~ 0x600fffff]
0x0c01: hw: 0x60007004, virt: 0xc0007004, value: 0x60111c0e map: [0xc0100000 ~ 0xc01fffff ==> 0x60100000 ~ 0x601fffff]
0x0c02: hw: 0x60007008, virt: 0xc0007008, value: 0x60211c0e map: [0xc0200000 ~ 0xc02fffff ==> 0x60200000 ~ 0x602fffff]
0x0c03: hw: 0x6000700c, virt: 0xc000700c, value: 0x60311c0e map: [0xc0300000 ~ 0xc03fffff ==> 0x60300000 ~ 0x603fffff]
0x0c04: hw: 0x60007010, virt: 0xc0007010, value: 0x60411c0e map: [0xc0400000 ~ 0xc04fffff ==> 0x60400000 ~ 0x604fffff]
0x0c05: hw: 0x60007014, virt: 0xc0007014, value: 0x60511c0e map: [0xc0500000 ~ 0xc05fffff ==> 0x60500000 ~ 0x605fffff]
0x0c06: hw: 0x60007018, virt: 0xc0007018, value: 0x60611c0e map: [0xc0600000 ~ 0xc06fffff ==> 0x60600000 ~ 0x606fffff]
0x0c07: hw: 0x6000701c, virt: 0xc000701c, value: 0x60711c0e map: [0xc0700000 ~ 0xc07fffff ==> 0x60700000 ~ 0x607fffff]
0x0c08: hw: 0x60007020, virt: 0xc0007020, value: 0x60811c0e map: [0xc0800000 ~ 0xc08fffff ==> 0x60800000 ~ 0x608fffff]
0x0c09: hw: 0x60007024, virt: 0xc0007024, value: 0x60911c0e map: [0xc0900000 ~ 0xc09fffff ==> 0x60900000 ~ 0x609fffff]
0x0c0a: hw: 0x60007028, virt: 0xc0007028, value: 0x60a11c0e map: [0xc0a00000 ~ 0xc0afffff ==> 0x60a00000 ~ 0x60afffff]
0x0c80: hw: 0x60007200, virt: 0xc0007200, value: 0x68011c0e map: [0xc8000000 ~ 0xc80fffff ==> 0x68000000 ~ 0x680fffff]
0x0c81: hw: 0x60007204, virt: 0xc0007204, value: 0x68111c0e map: [0xc8100000 ~ 0xc81fffff ==> 0x68100000 ~ 0x681fffff]
0x0f80: hw: 0x60007e00, virt: 0xc0007e00, value: 0x10000c12 map: [0xf8000000 ~ 0xf80fffff ==> 0x10000000 ~ 0x100fffff]
第34行到第36行,用于将__turn_mmu_on到__turn_mmu_on_end的虚拟地址转换成物理地址,然后将得到的物理地址当做虚拟地址建立页表。这样做的目的是什么呢?这两个标号之间的代码完成的任务是开启MMU,问题来了,再打开MMU那一刻,紧接着后面的地址都会被认为虚拟地址,都需要经过MMU进行转换,而在开启MMU的那条指令前后的PC是连续的,开启之前PC存放的是物理地址(0x30008000或者0x6000_8000级别的),开启之后,如果没有特殊的操作,紧邻的指令和数据的地址也是0x30008000或0x60008000级别,而此时的地址被认为是虚拟地址。如果不对这部分虚拟地址建立页表,程序里立刻出问题。
以TQ2440为例,在上面的打印的log中的第1行就是映射__turn_mmu_on到__turn_mmu_on_end之间代码地址的段表项的信息:
1 0x0300: hw: 0x30004c00, virt: 0xc0004c00, value: 0x30000c1e map: [0x30000000 ~ 0x300fffff ==> 0x30000000 ~ 0x300fffff]
我们按照代码的计算方法手动计算一下该段表项:
首先我们打开System.map文件,该文件中列出了kernel里一些符号对应的虚拟地址,我们找到__turn_mmu_on和__turn_mmu_on_end对应的虚拟地址:
c0008200 T __turn_mmu_on
c0008200 T _stext
c0008218 t __turn_mmu_on_end
然后将该虚拟地址转成对应的物理地址,可以看到这两个标号之间相差很小,在1MB以内,由于每个段表项都可以映射1MB的地址空间,所以只需要一个段表项即可。对于TQ2440,就是0x30008200和0x30008218,然后右移20位就都变成了0x300,然后再左移20位并或上0x00000c1e,就变成了0x3000_0c1e,这个就是段表项的内容,该表项的物理地址是多少呢?0x30004000 + 0x300<<2 = 0x30004C00,画出图的话:
第51行到58行完成的任务是对0xC000_0000到_end标号之间的虚拟地址进行映射,映射到物理内存的起始地址处,查询一下System.map,可以发现标号_end的虚拟地址是0xc07404e0, 需要的段表项的个数是:(0xc07404e0 - 0xc0000000 + 0x100000)/ 0x100000 = 8
在上面打印出来的log中下面的8个段表项就负责完成这部分的映射:
2 0x0c00: hw: 0x30007000, virt: 0xc0007000, value: 0x30000c1e map: [0xc0000000 ~ 0xc00fffff ==> 0x30000000 ~ 0x300fffff]
3 0x0c01: hw: 0x30007004, virt: 0xc0007004, value: 0x30100c1e map: [0xc0100000 ~ 0xc01fffff ==> 0x30100000 ~ 0x301fffff]
4 0x0c02: hw: 0x30007008, virt: 0xc0007008, value: 0x30200c1e map: [0xc0200000 ~ 0xc02fffff ==> 0x30200000 ~ 0x302fffff]
5 0x0c03: hw: 0x3000700c, virt: 0xc000700c, value: 0x30300c1e map: [0xc0300000 ~ 0xc03fffff ==> 0x30300000 ~ 0x303fffff]
6 0x0c04: hw: 0x30007010, virt: 0xc0007010, value: 0x30400c1e map: [0xc0400000 ~ 0xc04fffff ==> 0x30400000 ~ 0x304fffff]
7 0x0c05: hw: 0x30007014, virt: 0xc0007014, value: 0x30500c1e map: [0xc0500000 ~ 0xc05fffff ==> 0x30500000 ~ 0x305fffff]
8 0x0c06: hw: 0x30007018, virt: 0xc0007018, value: 0x30600c1e map: [0xc0600000 ~ 0xc06fffff ==> 0x30600000 ~ 0x306fffff]
9 0x0c07: hw: 0x3000701c, virt: 0xc000701c, value: 0x30700c1e map: [0xc0700000 ~ 0xc07fffff ==> 0x30700000 ~ 0x307fffff]
我们可以拿0xC000_0000到0xC00F_FFFF映射为例,段表项的地址:(0xC000_0000 >> 18 ) + 0xC000_4000 = 0xC000_7000,段表项的内容: r8 | r7 = 0x3000_0000 | 0xC1E = 0x3000_0C1E
对应的映射图如下:
第64到72行做的工作是对r2指向的设备树镜像文件所在的物理地址空间镜像映射,这里kernel为其建立了两个段表项,一共可以映射2MB的空间。
还是以TQ2440为例,对应的就是上面log中这两个:
10 0x0c3a: hw: 0x300070e8, virt: 0xc00070e8, value: 0x33a00c1e map: [0xc3a00000 ~ 0xc3afffff ==> 0x33a00000 ~ 0x33afffff]
11 0x0c3b: hw: 0x300070ec, virt: 0xc00070ec, value: 0x33b00c1e map: [0xc3b00000 ~ 0xc3bfffff ==> 0x33b00000 ~ 0x33bfffff]
在TQ2440启动的时候uboot将设备树dtb加载到了物理内存0x33aa7000处:
## Booting kernel from Legacy Image at ...
Image Name: Linux-4.10.+
Created: -- :: UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: Bytes = 3.3 MiB
Load Address:
Entry Point:
Verifying Checksum ... OK
## Flattened Device Tree blob at
Booting using the fdt blob at 0x32000000
Loading Kernel Image ... OK
Loading Device Tree to 33aa7000, end 33aac168 ... OK Starting kernel ... Uncompressing Linux... done, booting the kernel.
下面按照上面的方法,手动计算一下: 段表项的内容:(0x33aa7000 >> 20)<< 20 | 0x00000c1e = 0x33a0_0c1e 段表项的存放的地址: ((0x33aa7000 >> 20)<<20 - 0x30000000 + 0xC0000000) >> 18 + 0x3000_4000 = 0x30e8 + 0x3000_4000 = 0x3000_70e8
此时,dtb占用的地址空间的映射图如下:
未完待续。