Xvisor ARM32 启动分析

Linux内核历史悠久,特性丰富,但是代码量庞大,各个子系统交叉繁琐。对于想要将操作系统内核各个特性研究一遍的人,有时候也只好"望Linux兴叹"。Xvisor是一个较新的Type-1 Hypervisor,其官方地址在:http://xhypervisor.org/。Xvisor代码逻辑清晰,编码规范,虽然其也借鉴了Linux内核的一些特性,但它有很多地方都是完全重构,因为其目的是要实现一个Hypervisor,而不是一个通用的操作系统(尽管很多操作系统内核的基本元素都是必要的)。从这一个角度看,Xvisor可以是系统软件开发者的一个很好的研究项目,因为他不仅包含了操作系统内核的课题,还包含了虚拟化这一现代系统软件开发者的必修课。最主要的是,Xvisor还很新,许多地方都需要进一步丰富,这给了想要上手玩一玩的系统软件开发者许多Contribute的机会。最近笔者就业余把Xvisor拿来玩,做一些移植和分析的事情,期间会做一些记录,准备分享在本博客上。本文就从其启动部分开始分析。由于仅仅是个人业余玩玩,不保证内容的完全准确和清晰。

从主Makefile说起

根目录下有一个主Makefile。其中我们可以看到建立了许多规则。下面的规则建立起来的执行顺序要求先调用compile_cpp命令生成build_dir下面的linker.ld文件。

312$(build_dir)/vmm.bin: $(build_dir)/vmm.elf 
313    $(call compile_objcopy,$@,$<) 
314 
315$(build_dir)/vmm.elf: $(build_dir)/linker.ld $(all-y) $(build_dir)/system.o 
316    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y) $(build_dir)/system.o) 
317 
318$(build_dir)/system.map: $(build_dir)/vmm_tmp.elf 
319    $(call compile_nm,$@,$<) 
320 
321$(build_dir)/vmm_tmp.elf: $(build_dir)/linker.ld $(all-y) $(build_dir)/system_tmp1.o 
322    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y) $(build_dir)/system_tmp1.o) 
323 
324$(build_dir)/system_tmp1.map: $(build_dir)/vmm_tmp1.elf 
325    $(call compile_nm,$@,$<) 
326 
327$(build_dir)/vmm_tmp1.elf: $(build_dir)/linker.ld $(all-y) 
328    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y)) 
329 
330$(build_dir)/linker.ld: $(cpu_dir)/linker.ld 
331    $(call compile_cpp,$@,$<) 

 

而这里的compile_cpp命令如下:

compile_cpp = $(V)mkdir -p `dirname $(1)`; \

     echo " (cpp) $(subst $(build_dir)/,,$(1))"; \

     $(cpp) $(cppflags) $(2) | grep -v "\#" > $(1)

对于ATM32而言,这个规则实际是要从【/XVisor/arch/arm/cpu/arm32/linker.ld】这个文件生成build_dir下面的linker.ld。在用cpp进行预处理的过程中,cppflags会包含cpu-cppflags:

cppflags+=$(cpu-cppflags)

cppflags+=$(board-cppflags)

cppflags+=$(libs-cppflags-y)

这样,在【/XVisor/arch/arm/cpu/arm32/objects.mk】中的cpu-cppflags就会被包含进来,如下:

 

41cpu-cppflags+=-DCPU_TEXT_START=0xFF000000 

 

因此【/XVisor/arch/arm/cpu/arm32/linker.ld】的下面一段:

24OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 
25OUTPUT_ARCH("arm") 
26ENTRY(_start_vect) 
27 
28SECTIONS 
29{ 
30    . = CPU_TEXT_START; 
31 
32    . = ALIGN(0x100000); /* Need this to create proper sections */ 
33     
34    PROVIDE(_code_start = .); 
35 
36    .text : 
37     { 
38        PROVIDE(_text_start = .); 
39        *(.entry) 
40        *(.text) 
41        . = ALIGN(4); 
42        PROVIDE(_text_end = .); 
43    } 
44 
45    .data : 
46    { 
47        PROVIDE(_data_start = .); 
48        *(.data) 
49        . = ALIGN(4); 
50        PROVIDE(_data_end = .); 
51    } 
52 
53    .rodata : 
54    { 
55        PROVIDE(_rodata_start = .); 
56        *(.rodata .rodata.*) 
57        . = ALIGN(4); 
58        PROVIDE(_rodata_end = .); 
59    } 
60 
61    .percpu : 
62    { 
63        PROVIDE(_percpu_start = .); 
64        *(.percpu) 
65        . = ALIGN(4); 
66        PROVIDE(_percpu_end = .); 
67    } 

就会被定为在0xFF000000这个地方。这里正好可以稍微先分析链接脚本。显然,这个连接器脚本直接使用ENTRY(_start_vect)将_start_vect这个标号作为最终映像的入口地址。这个_start_vect定为于【/XVisor/arch/arm/cpu/arm32/cpu_entry.S】这个文件。

431    /* 
432     * Exception vector start. 
433     */
434    .section .entry, "ax", %progbits 
435    .globl _start_vect 
436_start_vect: 
437    ldr    pc, __reset 
438    ldr    pc, __undefined_instruction 
439    ldr    pc, __software_interrupt 
440    ldr    pc, __prefetch_abort 
441    ldr    pc, __data_abort 
442    ldr    pc, __not_used 
443    ldr    pc, __irq 
444    ldr    pc, __fiq 
445__reset: 
446    .word _reset 
447__undefined_instruction: 
448    .word _undef_inst 
449__software_interrupt: 
450    .word _soft_irq 
451__prefetch_abort: 
452    .word _prefetch_abort 
453__data_abort: 
454    .word _data_abort 
455__not_used: 
456    .word _not_used 
457__irq: 
458    .word _irq 
459__fiq: 
460    .word _fiq 
461    .global _end_vect 
462_end_vect: 
463    b    . 
464 
465    /* 
466     * Exception stacks. 
467     */
468__svc_stack_end: 
469    .word _svc_stack_end 
470__und_stack_end: 
471    .word _und_stack_end 
472__abt_stack_end: 
473    .word _abt_stack_end 
474__irq_stack_end: 
475    .word _irq_stack_end 
476__fiq_stack_end: 
477    .word _fiq_stack_end 
478 
479    /* 
480     * Reset exception handler. 
481     * Reset hardware state before starting Xvisor. 
482     */
483    .globl _reset 
484_reset: 

 

因此,我们可能会认为,当从bootloader跳转到映像执行时会先执行这里的_reset这个标号处的代码。然而,实际上并不是这样的。因为【/XVisor/arch/arm/cpu/arm32/cpu_entry.S】文件本身作为一个编译单元,会生成cpu_entry.o,该对象文件会被整体放置到一起,而_reset这个标号处于这个cpu_entry.S文件中,因此cpu_entry.o会被放置在.text段中的开始处。 

 

SECTIONS 
{ 
 . = 0xFF000000; 

 

 . = ALIGN(0x100000); 

 

 PROVIDE(_code_start = .); 

 

 .text : 
 { 
 PROVIDE(_text_start = .); 
 *(.entry) 
 *(.text) 
 . = ALIGN(4); 
 PROVIDE(_text_end = .); 
 } 
这里的*(.entry)也指定名为.entry的代码段应该在.text段开始处。也就是说,这个名为.entry的代码段才是第一个要执行的代码(这也符合了我们的预期)。 
38    .section .entry, "ax", %progbits 
39    .globl _start 
40    .globl _start_secondary 
41_start: 

 

关于kallsyms的那点事

在Makefile规定的编译顺序中,第一步会编译并链接vmm_tmp1.elf这个目标: 
$(build_dir)/vmm_tmp1.elf: $(build_dir)/linker.ld $(all-y) 
    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y)) 

 

第二步会对vmm_tmp1.elf进行进一步处理,提取出来这个映像中的所有符号:

$(build_dir)/system_tmp1.map: $(build_dir)/vmm_tmp1.elf

    $(call compile_nm,$@,$<)

这里的compile_nm命令如下:

compile_nm = $(V)mkdir -p `dirname $(1)`; \

     echo " (nm) $(subst $(build_dir)/,,$(1))"; \

     $(nm) -n $(2) | grep -v '\( [aNUw] \)\|\(__crc_\)\|\( \$[adt]\)' > $(1)

这样,vmm_tmp1.elf中的所有符号被使用nm -n vmm_tmp1.elf提取出来,进一步使用grep处理,提取出全局的符号(非static的符号,包括全局函数和全局变量),生成system_tmp1.map。注意到在【/XVisor/tools/rules.mk】中有如下规则:

68$(build_dir)/%.S: $(build_dir)/%.map $(build_dir)/tools/kallsyms/kallsyms 
69    $(V)mkdir -p `dirname $@` 
70    $(if $(V), @echo " (kallsyms) $(subst $(build_dir)/,,$@)") 
71    $(V)$(build_dir)/tools/kallsyms/kallsyms --all-symbols < $< > $@ 

 

因此,为了满足下面的编译目标,这个system_tmp1.map就会被上面这条规则处理,也就是使用kallsyms工具将system_tmp1.map转换成一个system_tmp1.S文件。

$(build_dir)/vmm_tmp.elf: $(build_dir)/linker.ld $(all-y) $(build_dir)/system_tmp1.o

    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y) $(build_dir)/system_tmp1.o)

这个规则将再次进行链接,这次链接加入了一个所有全局符号信息组成的system_tmp1.o,从而可以对这些符号按照名字和地址进行处理。在vmm_tmp.elf中,会增加下面几个全局变量:

  • kallsyms_addresses
  • kallsyms_num_syms
  • kallsyms_names
  • kallsyms_markers
  • kallsyms_token_table
  • kallsyms_token_index

kallsyms工具对这些符号和地址进行了压缩,以减小映像尺寸。更进一步,为了将这些符号也包括进入kallsyms库的分析中,下面的规则对vmm_tmp.elf进行了再次类似的处理:

$(build_dir)/vmm.elf: $(build_dir)/linker.ld $(all-y) $(build_dir)/system.o

    $(call compile_ld,$@,$(build_dir)/linker.ld,$(all-y) $(build_dir)/system.o)

$(build_dir)/system.map: $(build_dir)/vmm_tmp.elf

    $(call compile_nm,$@,$<)

这样,vmm.elf中的符号就包含了上面这些新加入的符号的地址和名称,从而在【/XVisor/libs/include/libs/kallsyms.h】中的对这些符号的弱引用就是实际的引用了。

32/* 
33 * Tell the compiler that the count isn't in the small data section if the arch 
34 * has one (eg: FRV). 
35 */
36extern const unsigned long kallsyms_num_syms 
37 __attribute__ ((weak, section(".rodata"))); 
38extern const unsigned long kallsyms_addresses[] __attribute__ ((weak)); 
39extern const unsigned char kallsyms_names[] __attribute__ ((weak)); 
40extern const unsigned char kallsyms_token_table[] __attribute__ ((weak)); 
41extern const unsigned short kallsyms_token_index[] __attribute__ ((weak)); 
42extern const unsigned long kallsyms_markers[] __attribute__ ((weak)); 

保证加载到1MB 对齐的地址

下面接着分析【/XVisor/arch/arm/cpu/arm32/cpu_entry.S】这个文件。

28#include <cpu_defines.h>

29

30    /*

31     * _start: Primary CPU startup code

32     * _start_secondary: Secondary CPU startup code

33     *

34     * Note: Xvisor could be loaded any where in memory by boot loaders.

35     * The _start ensures that Xvisor executes from intended base address

36     * provided at compile time.

37     */

38    .section .entry, "ax", %progbits

39    .globl _start

40    .globl _start_secondary

41_start:

42    /* r4 -> load start

43     * r5 -> load end

44     * r6 -> execution start

45     * r7 -> execution end

46     * r10 -> core# in case of SMP

47     */

48    add    r4, pc, #-0x8

49    ldr    r6, __exec_start

50    ldr    r7, __exec_end

51    sub    r3, r7, r6

52    add    r5, r4, r3

正如注释所言,Xvisor可以被加载到任何内存位置。这里标号_start会被定位于在链接器脚本中指定的起始地址,并且由PROVIDE(_code_start = .);指定。

28SECTIONS 
29{ 
30    . = CPU_TEXT_START; 
31 
32    . = ALIGN(0x100000); /* Need this to create proper sections */ 
33     
34    PROVIDE(_code_start = .); 

这里为了防止CPU_TEXT_START被指定为一个没有对齐到1MB的地址,还专门用ALIGN(0x100000)对映像起始位置进行了对齐。尽管如此,映像也可能被bootloader随意加载到某处。因此,在代码一开始,就用一条"add    r4, pc, #-0x8"指令把实际的加载地址(bootloader加载映像的地址)存储于r4中,这条指令相当于"ADR R4, _code_start"。这里的__exec_start实际上是存放_code_start这个值的地方,这个_code_start符号是连接器脚本提供的符号,用于表示在链接过程中最后对齐之后的实际代码其实地址(因此可能跟编译时指定的CPU_TEXT_START不一样,如果CPU_TEXT_START没有1MB对齐的话)。

310__exec_start: 
311    .word _code_start 
312__exec_end: 
313    .word _code_end 

 

如果代码真的被加载到了一个没有对齐的地方,启动代码也会尝试将代码自动转移到一个对齐的地方。

155align_1m_boundary: 
156    /* Relocate code if load start is not 1MB aligned */
157    mov    r0, r4 
158    mov    r0, r0, lsr #20
159    mov    r0, r0, lsl #20
160    cmp    r0, r4 
161    /* Skip relocation if already aligned */
162    beq    _start_mmu_init 
163 
164    /* Relocate copy function at end after load end address */
165    ldr    r0, __copy_start 
166    ldr    r1, __copy_end 
167    sub    r2, r1, r0     /* r2 -> __copy size */
168    sub    r0, r0, r6 
169    add    r0, r0, r4     /* r0 -> load_address of copy_start */
170    mov    r1, r5         /* r1 -> load end */
171    bl    _copy         /* copy the _copy function after the code */
172 
173    /* Use newly relocated copy function to relocate entire code 
174     * to 1MB boundary 
175     */
176    mov    r0, r4         /* r0 -> load start */
177    mov    r1, r4 
178    mov    r1, r1, lsr #20
179    mov    r1, r1, lsl #20     /* r1 -> load start aligned to 1MB boundary */
180    sub    r2, r5, r4     /* r2 -> code size */
181    bl    _start_nextpc1 
182_start_nextpc1: 
183    add    lr, lr, #16     /* Adjust return address (lr) for jump to */
184    sub    lr, lr, r4     /* relocated address on return from _copy */
185    add    lr, lr, r1 
186    bx    r5         /* call _copy */
187    /* Update load start and load end */
188    mov    r0, r4 
189    mov    r0, r0, lsr #20
190    mov    r0, r0, lsl #20 /* r0 -> load_start aligned to 1MB boundary */
191    subs    r1, r4, r0     /* r1 -> offset between load start and aligned address */
192    subs    r4, r4, r1     /* r4 -> new load start */
193    subs    r5, r5, r1     /* r5 -> new load end */
194    ldr    r0, __load_start 
195    sub    r0, r0, r6 
196    add    r0, r0, r4 
197    str    r4, [r0] 
198    ldr    r0, __load_end 
199    sub    r0, r0, r6 
200    add    r0, r0, r4 
201    str    r5, [r0] 

 

这段代码无须更多解释。 

临时初始化MMU页表

如果对齐了的话,则会接着执行MMU初始化。

203_start_mmu_init: 
204    ldr    r0, __tmpl1_mem 
205    sub    r0, r0, r6 
206    add    r0, r0, r4 
207    ldr    r1, __tmpl1_mem_addr 
208    sub    r1, r1, r6 
209    add    r1, r1, r4 
210    str    r0, [r1] 
211    /* r0 -> default Level1 Table base address */
212    mov    r1, #1
213    lsl    r1, #14         /* r1 -> 16K */
214    mov    r2, #0 
215    mov    r3, r0 

 

记住在cpu_entry.S文件开始的注释说了的如下寄存器在这段代码中的作用:

  • r4 -> load start
  • r5 -> load end
  • r6 -> execution start
  • r7 -> execution end
  • r10 -> core# in case of SMP

这里,__tmpl1_mem是存放tmpl1_mem变量值的地方:

326__tmpl1_mem_addr: 
327    .word __tmpl1_mem 
328__tmpl1_mem: 
329    .word tmpl1_mem 

而tmpl1_mem是在文件【/XVisor/arch/arm/cpu/arm32/cpu_mmu.c】中的一个数组变量。

52u8 __attribute__((aligned(TTBL_L1TBL_SIZE))) tmpl1_mem[TTBL_L1TBL_SIZE]; 
53u8 __attribute__((aligned(TTBL_L1TBL_SIZE))) defl1_mem[TTBL_L1TBL_SIZE]; 

为了方便理解,我们用一次实际的编译结果来说明。在一次编译之后,通过分析前面说过的system.map文件,得知:

  • ff0002b8     t     __tmpl1_mem_addr
  • ff0002bc     t     __tmpl1_mem
  • ff098000     B     defl1_mem
  • ff09c000     B     tmpl1_mem

因此,前面start_mmu_init的开始处,实际上是从__tmpl1_mem地址处获得tmpl1_mem变量的地址将tmpl1_mem变量的地址加载到r0,并且减去r6再加上r4,实际上是为了得到这个tmpl1_mem变量加载后的实际内存地址存于r0。而__tmpl1_mem_addr实际上是保存了__tmpl1_mem变量加载后的实际地址存于r1。接着的"str    r0, [r1]"实际上是将tmpl1_mem变量的地址再次存于__tmpl1_mem变量加载后的实际地址处。接着执行下面的代码:

217    /* Clear default Level1 Table */
218_start_mmu_tmpl1_clear: 
219    str    r2, [r3] 
220    add    r3, r3, #4
221    sub    r1, r1, #4
222    cmp    r1, #0 
223    bgt    _start_mmu_tmpl1_clear 

 

这里的意思很明白,就是要清零tmpl1_mem变量(数组),大小为(1<<14),即为16KB。接着:

224 
225    /* Create entries in default Level1 Table 
226     * for execution & load addresses 
227     */
228    ldr    r3, __mmu_section_attr 
229    /* r1 -> load entry start address */
230    mov    r1, r4        /* r4 -> load start address */
231    lsr    r1, #18     /* r1 >> 20, r1 << 2 */
232    add    r1, r0, r1 
233    /* Setup load entry */
234    orr    r2, r3, r4 
235    str    r2, [r1] 
236    /* r1 -> execute entry start address */
237    mov    r1, r6 
238    lsr    r1, #18        /* r1 >> 20, r1 << 2 */
239    add    r1, r0, r1 
240    /* r2 -> execute entry end address */
241    sub    r2, r7, r6 
242    lsr    r2, #18        /* r2 >> 20, r2 << 2 */
243    add    r2, r1, r2 
244    /* r5 -> temporary value */
245    mov    r5, #0 
246_start_mmu_tmpl1_set: 
247    orr    r5, r3, r4 
248    str    r5, [r1] 
249    lsr    r4, #20
250    add    r4, r4, #1
251    lsl    r4, #20
252    add    r1, r1, #4
253    cmp    r1, r2 
254    blt    _start_mmu_tmpl1_set 

 

我们看到:

  • r3被加载为MMU的section的属性值
300__mmu_section_attr: 
301    .word SECTION_ATTR 

 

280#if (__ARM_ARCH_VERSION__ < 6) 
281#define TTBL_L1TBL_TTE_ARCH_ATTR    TTBL_L1TBL_TTE_REQ_MASK 
282#else 
283#define TTBL_L1TBL_TTE_ARCH_ATTR    (TTBL_L1TBL_TTE_C_MASK | \ 
284                    TTBL_L1TBL_TTE_B_MASK) 
285#endif 
286 
287#define SECTION_ATTR     ((TTBL_AP_SRW_U << TTBL_L1TBL_TTE_AP_SHIFT) | \ 
288            (TTBL_L1TBL_TTE_DOM_RESERVED << TTBL_L1TBL_TTE_DOM_SHIFT) | \ 
289            TTBL_L1TBL_TTE_ARCH_ATTR | \ 
290            TTBL_L1TBL_TTE_TYPE_SECTION) 
  • r1被设置为load start address (即r4)对应的Section在tmpl1_mem这个Table中的索引,加上tmpl1_mem变量的地址就是得到对应的entry地址。然后"orr    r2, r3, r4"实际上是把这个1MB对齐的load start address加上内存属性,接着使用一个"str    r2, [r1]"就设置了这个entry;也就是说,这里就映射了load start address开始的一个1MB为一个Section。参见下面的Translation Table的格式。这里是实现的V->P按照1:1映射的。
  • 然后对从execute entry start address到execute entry end address为止的执行空间,分别得到对应的在tmpl1_mem变量中的entry范围(分别存在于r1和r2中),将r5初始化为0,然后以r5依次设置这个执行空间对应的Translation Table中的所有entry,每个entry设置后r5和r4都分别增加1MB,从而映射了这个执行空间。这样完成的是将执行空间V映射到加载地址空间P,并不是1:1映射。例如,执行空间CPU_TEXT_START = 0xFF000000,但是加载空间可以是内存的任意1MB对齐的地址,例如0x108000000。
  • 也就是说,要使用加载地址访问,那么可以使用load start address开始的1MB,超过就不行;但是如果映像很大,超过1MB大小,那么要访问这些代码空间的代码和数据,则必须使用执行空间的地址。

Xvisor ARM32 启动分析_第1张图片

Xvisor ARM32 启动分析_第2张图片

接着,执行下面的代码来将Translation Table的基地址设置到TTBR0中。

256    /* 
257     * Secondary CPU startup code 
258     * 
259     * Note: From this point primary CPU startup is same as secondary CPU 
260     */
261_start_secondary: 
262    /* Setup Translation Table Base Register 0 */
263    ldr    r0, __tmpl1_mem 
264    mcr    p15, 0, r0, c2, c0, 0 

Xvisor ARM32 启动分析_第3张图片Xvisor ARM32 启动分析_第4张图片

Xvisor ARM32 启动分析_第5张图片Xvisor ARM32 启动分析_第6张图片

再接着,更新Domain Access Control Register,TTBL_L1TBL_TTE_DOM_RESERVED这个0号domain的访问权限设置为Client模式,即要求按照Translation Table的entry指定的属性字段来判断是否允许访问。

265 
266    /* Setup Domain Control Register */
267    ldr    r1, __dacr_mmu_val 
268    mcr    p15, 0, r1, c3, c0, 0 

 

302__dacr_mmu_val: 
303    .word (TTBL_DOM_CLIENT << (2 * TTBL_L1TBL_TTE_DOM_RESERVED)) 

 

在【/XVisor/arch/arm/cpu/arm32/include/cpu_defines.h】定义了TTBL_L1TBL_TTE_DOM_RESERVED:

282#define TTBL_L1TBL_TTE_DOM_RESERVED            0x0
283#define TTBL_L1TBL_TTE_DOM_VCPU_SUPER            0x1
284#define TTBL_L1TBL_TTE_DOM_VCPU_SUPER_RW_USER_R    0x2
285#define TTBL_L1TBL_TTE_DOM_VCPU_USER            0x3

 

Xvisor ARM32 启动分析_第7张图片Xvisor ARM32 启动分析_第8张图片

初始化系统控制寄存器并开启MMU

在初始化页表并将其基地址写入TTBR0之后,需要进一步初始化系统控制寄存器从而使能MMU。

270    /* Setup System Control Register */
271    bl    proc_setup 
272    mcr    p15, 0, r0, c1, c0, 0 

这里调用了一个函数proc_setup,该函数实现在【/XVisor/arch/arm/cpu/arm32/cpu_proc_v7.S】中。

95/* 
96 *    Boot-time processor setup 
97 * 
98 *    Initialise TLB, Caches, and MMU state ready to switch the MMU 
99 *    on. Return in r0 the new CP15 C1 control register setting. 
100 * 
101 *    This should be able to cover all ARMv7 cores. 
102 * 
103 *    It is assumed that: 
104 *    - cache type register is implemented 
105 * 
106 *    Note: We blindly use all registers because this will be 
107 *    called at boot-time when there is not stack 
108 */
109    .globl proc_setup 
110proc_setup: 

从函数的注释看,这个函数式要做一些跟MMU和Cache相关的系统级初始化,并且返回一个值用来在调用函数proc_setup后初始化System Control Register。下面我们一步步来看都做了些啥?

111#ifdef CONFIG_SMP 
112#if defined(CONFIG_CPU_CORTEX_A9) 
113    mov    r10, #(1 << 0)            @ TLB ops broadcasting 
114#elif defined(CONFIG_CPU_CORTEX_A15) 
115    mov    r10, #0 
116#endif 
117    mrc    p15, 0, r0, c1, c0, 1
118#if 0 
119    ALT_UP(mov    r0, #(1 << 6))        @ fake it for UP 
120#endif 
121    tst    r0, #(1 << 6)            @ SMP/nAMP mode enabled? 
122    orreq    r0, r0, #(1 << 6)        @ Enable SMP/nAMP mode 
123    orreq    r0, r0, r10            @ Enable CPU-specific SMP bits 
124    mcreq    p15, 0, r0, c1, c0, 1
125#endif 

这里首先是读取一个实现特定的Auxiliary Control Register使能SMP模式,并且使能了Cache and TLB maintenance broadcast这种保持一致性的广播操作。ARMv7的TRM文档中对这个寄存器描述如下:

Xvisor ARM32 启动分析_第9张图片

那么,对于我们的i.MX6,属于Cortex A9,因此应该看Cortex A9的TRM文档里是怎么描述的:

Xvisor ARM32 启动分析_第10张图片

Xvisor ARM32 启动分析_第11张图片 Xvisor ARM32 启动分析_第12张图片

接下来是读取芯片的版本信息,并更具版本信息来应用一些特定于某些版本的Errata修复。

126    mrc    p15, 0, r0, c0, c0, 0        @ read main ID register 
127    and    r10, r0, #0xff000000        @ ARM? 
128    teq    r10, #0x41000000
129    bne    3f
130    and    r5, r0, #0x00f00000        @ variant 
131    and    r6, r0, #0x0000000f        @ revision 
132    orr    r6, r6, r5, lsr #20-4        @ combine variant and revision 
133    ubfx    r0, r0, #4, #12            @ primary part number 
134 
135    /* Cortex-A8 Errata */
136    ldr    r10, =0x00000c08        @ Cortex-A8 primary part number 
137    teq    r0, r10 
138    bne    2f
139#ifdef CONFIG_ARM_ERRATA_430973 
140    teq    r5, #0x00100000            @ only present in r1p* 
141    mrceq    p15, 0, r10, c1, c0, 1        @ read aux control register 
142    orreq    r10, r10, #(1 << 6)        @ set IBE to 1
143    mcreq    p15, 0, r10, c1, c0, 1        @ write aux control register 
144#endif 
145#ifdef CONFIG_ARM_ERRATA_458693 
146    teq    r6, #0x20            @ only present in r2p0 
147    mrceq    p15, 0, r10, c1, c0, 1        @ read aux control register 
148    orreq    r10, r10, #(1 << 5)        @ set L1NEON to 1
149    orreq    r10, r10, #(1 << 9)        @ set PLDNOP to 1
150    mcreq    p15, 0, r10, c1, c0, 1        @ write aux control register 
151#endif 
152#ifdef CONFIG_ARM_ERRATA_460075 
153    teq    r6, #0x20            @ only present in r2p0 
154    mrceq    p15, 1, r10, c9, c0, 2        @ read L2 cache aux ctrl register 
155    tsteq    r10, #1 << 22
156    orreq    r10, r10, #(1 << 22)        @ set the Write Allocate disable bit 
157    mcreq    p15, 1, r10, c9, c0, 2        @ write the L2 cache aux ctrl register 
158#endif 
159    b    3f
160 
161    /* Cortex-A9 Errata */
1622:    ldr    r10, =0x00000c09        @ Cortex-A9 primary part number 
163    teq    r0, r10 
164    bne    3f
165#ifdef CONFIG_ARM_ERRATA_742230 
166    cmp    r6, #0x22            @ only present up to r2p2 
167    mrcle    p15, 0, r10, c15, c0, 1        @ read diagnostic register 
168    orrle    r10, r10, #1 << 4        @ set bit #4
169    mcrle    p15, 0, r10, c15, c0, 1        @ write diagnostic register 
170#endif 
171#ifdef CONFIG_ARM_ERRATA_742231 
172    teq    r6, #0x20            @ present in r2p0 
173    teqne    r6, #0x21            @ present in r2p1 
174    teqne    r6, #0x22            @ present in r2p2 
175    mrceq    p15, 0, r10, c15, c0, 1        @ read diagnostic register 
176    orreq    r10, r10, #1 << 12        @ set bit #12
177    orreq    r10, r10, #1 << 22        @ set bit #22
178    mcreq    p15, 0, r10, c15, c0, 1        @ write diagnostic register 
179#endif 
180#ifdef CONFIG_ARM_ERRATA_743622 
181    teq    r5, #0x00200000            @ only present in r2p* 
182    mrceq    p15, 0, r10, c15, c0, 1        @ read diagnostic register 
183    orreq    r10, r10, #1 << 6        @ set bit #6
184    mcreq    p15, 0, r10, c15, c0, 1        @ write diagnostic register 
185#endif 
186#if defined(CONFIG_ARM_ERRATA_751472) && defined(CONFIG_SMP) 
187    cmp    r6, #0x30            @ present prior to r3p0 
188    mrclt    p15, 0, r10, c15, c0, 1        @ read diagnostic register 
189    orrlt    r10, r10, #1 << 11        @ set bit #11
190    mcrlt    p15, 0, r10, c15, c0, 1        @ write diagnostic register 
1911: 
192#endif 

Xvisor ARM32 启动分析_第13张图片 Xvisor ARM32 启动分析_第14张图片

我们假设i.MX6不存在这几个Errata,因此就会执行到下面的代码,执行一个Instruction cache invalidate all操作:

1943:    mov    r10, #0 
195    mcr    p15, 0, r10, c7, c5, 0        @ I+BTB cache invalidate 
196    dsb 

Xvisor ARM32 启动分析_第15张图片 Xvisor ARM32 启动分析_第16张图片

Xvisor ARM32 启动分析_第17张图片 Xvisor ARM32 启动分析_第18张图片

在Invalidate所有的指令Cache之后,执行下面的代码:

198    adr    r5, v7_crval 
199    ldmia    r5, {r5, r6} 
200#ifdef CONFIG_SWP_EMULATE 
201    orr r5, r5, #(1 << 10) @ set SW bit in "clear"
202    bic r6, r6, #(1 << 10) @ clear it in "mmuset"
203#endif 
204     mrc    p15, 0, r0, c1, c0, 0        @ read control register 
205    bic    r0, r0, r5            @ clear bits them 
206    orr    r0, r0, r6            @ set them 
207    mov    pc, lr 
208 
209    /* AT 
210     * TFR EV X F I D LR S 
211     * .EEE ..EE PUI. .T.T 4RVI ZWRS BLDP WCAM 
212     * rxxx rrxx xxx0 0101 xxxx xxxx x111 xxxx < forced 
213     * 0 0 110 0001 1100 .111 1101 < we want 
214     */
215    .align    2
216    .type    v7_crval, #object 
217v7_crval: 
218    .word    0x0120c302 /* clear */
219    .word    0x00c01c7d /* mmuset */

这里是要将System Control Register寄存器读取到r0中,并且使用r5里的值作为掩码清除掉一些字段,然后用r6中的值设置上一些字段。我们看了这个系统控制寄存器的格式之后就知道具体清除或者设置了哪些字段。

Xvisor ARM32 启动分析_第19张图片 Xvisor ARM32 启动分析_第20张图片

Xvisor ARM32 启动分析_第21张图片 Xvisor ARM32 启动分析_第22张图片

Xvisor ARM32 启动分析_第23张图片 Xvisor ARM32 启动分析_第24张图片

Xvisor ARM32 启动分析_第25张图片Xvisor ARM32 启动分析_第26张图片

因此,使用0x0120c302这个值清掉的字段包括:

  • VE,Interrupt Vectors Enable,从而Use the FIQ and IRQ vectors from the vector table, see the V bit entry。
  • FI,Fast interrupts configuration enable,从而All performance features enabled。
  • RR,Round Robin select,从而Cache的replacement strategy就是Normal replacement strategy, for example, random replacement。
  • Alignment check enable,从而就是Alignment fault checking disabled。

类似,使用0x00c01c7d这个值设置的指端包括:

  • U, In ARMv7 this bit is RAO/SBOP,就是说这个bit是Read-As-One, Should-Be-One-or-Preserved on writes的,所以设置为1是必然的。
  • I, Instruction cache enable,这样就使能了指令Cache。
  • Z,Branch prediction enable,这样就使能了分支预取功能。
  • CP15BEN,CP15 barrier enable,这样就使能了CP15 DMB, DSB, and ISB barrier操作。
  • M, MMU enable,这样就使能了MMU,即PL1&0 stage 1 MMU enabled。
  • SW,SWP and SWPB enable,从而SWP and SWPB指令是使能的。

注意上面的操作中没有碰那个TRE字段,应该是采用了从bootloader加载过来的值。根据前面分析的Section页表属性:

280#if (__ARM_ARCH_VERSION__ < 6) 
281#define TTBL_L1TBL_TTE_ARCH_ATTR    TTBL_L1TBL_TTE_REQ_MASK 
282#else 
283#define TTBL_L1TBL_TTE_ARCH_ATTR    (TTBL_L1TBL_TTE_C_MASK | \ 
284                    TTBL_L1TBL_TTE_B_MASK) 
285#endif 

 

287#define SECTION_ATTR     ((TTBL_AP_SRW_U << TTBL_L1TBL_TTE_AP_SHIFT) | \ 
288            (TTBL_L1TBL_TTE_DOM_RESERVED << TTBL_L1TBL_TTE_DOM_SHIFT) | \ 
289            TTBL_L1TBL_TTE_ARCH_ATTR | \ 
290            TTBL_L1TBL_TTE_TYPE_SECTION) 

Xvisor ARM32 启动分析_第27张图片

 

我们注意到这里的Section属性设置了下面的字段: 
  • NS, Non-secure bit,设置为0。
  • AP, Access Permissions bits,即设置为TTBL_AP_SRW_U 0x1,即在PL1/PL2下可读可写。
  • Xvisor ARM32 启动分析_第28张图片
  • Domain,设置为默认的0号domain。
  • C,设置为1,参照下表(同时TEX[2:0]=000)。
  • B,设置为1,参照下表(同时TEX[2:0]=000)。
    • TEX[2:0]=000,C=1,B=1,也就是说,将内存设置为Outer and Inner Write-Back, no Write-Allocate,并且是Normal内存,其Page Shareable属性由S位控制,但是这里S位被设置为0,也就是说不可共享。

 

Xvisor ARM32 启动分析_第29张图片 Xvisor ARM32 启动分析_第30张图片

开启在执行空间之旅

当代码返回proc_setp的调用者后,r0寄存器中存放着用来设置系统控制寄存器的值,因此直接写入该寄存器,就实现了MMU的使能。

270    /* Setup System Control Register */
271    bl    proc_setup 
272    mcr    p15, 0, r0, c1, c0, 0 

到此为止,MMU已经在发挥作用了。我们可以看出,对于MMU的配置,目前主要有两个内存区间:

  • 加载地址处的1:1映射区间,这样可以使得按照加载地址读写还可以继续可用。
  • 执行地址处的非1:1映射区间,这样就可用通过一个对PC的直接加载而跳转到"执行地址处"。

 

274    /* Jump to reset code */
275    ldr    pc, __reset 

 

445__reset: 
446    .word _reset 

 

479    /* 
480     * Reset exception handler. 
481     * Reset hardware state before starting Xvisor. 
482     */
483    .globl _reset 
484_reset: 
485    /* Clear a register for temporary usage */
486    mov    r8, #0 
487    /* Disable IRQ & FIQ */
488    mrs    r8, cpsr_all 
489    orr    r8, r8, #(CPSR_IRQ_DISABLED | CPSR_FIQ_DISABLED) 
490    msr    cpsr_cxsf, r8 

 

这样,代码转而进入_reset,接着执行,但是现在已经在"执行内存空间"中了。进来的第一步就是禁止中断(IRQ和FIQ),这样以后的代码就可以一线执行下去,直到后面使能中断。

491    /* Set Supervisor Mode Stack */
492    CMODE    r8, CPSR_MODE_SUPERVISOR 
493    ldr    sp, __svc_stack_end 
494#ifdef CONFIG_SMP 
495    mrc    p15, 0, r10, c0, c0, 5
496    ands    r10, r10, #0xFF
497    mov    r9, #CONFIG_IRQ_STACK_SIZE 
498    mul    r9, r9, r10 
499    sub    sp, sp, r9 
500#endif 
501    /* Set Undefined Mode Stack */
502    CMODE    r8, CPSR_MODE_UNDEFINED 
503    ldr    sp, __und_stack_end 
504#ifdef CONFIG_SMP 
505    mrc    p15, 0, r10, c0, c0, 5
506    ands    r10, r10, #0xFF
507    mov    r9, #0x100
508    mul    r9, r9, r10 
509    sub    sp, sp, r9 
510#endif 
511    /* Set Abort Mode Stack */
512    CMODE    r8, CPSR_MODE_ABORT 
513    ldr    sp, __abt_stack_end 
514#ifdef CONFIG_SMP 
515    mrc    p15, 0, r10, c0, c0, 5
516    ands    r10, r10, #0xFF
517    mov    r9, #0x100
518    mul    r9, r9, r10 
519    sub    sp, sp, r9 
520#endif 
521    /* Set IRQ Mode Stack */
522    CMODE    r8, CPSR_MODE_IRQ 
523    ldr    sp, __irq_stack_end 
524#ifdef CONFIG_SMP 
525    mrc    p15, 0, r10, c0, c0, 5
526    ands    r10, r10, #0xFF
527    mov    r9, #0x100
528    mul    r9, r9, r10 
529    sub    sp, sp, r9 
530#endif 
531    /* Set FIQ Mode Stack */
532    CMODE    r8, CPSR_MODE_FIQ 
533    ldr    sp, __fiq_stack_end 
534#ifdef CONFIG_SMP 
535    mrc    p15, 0, r10, c0, c0, 5
536    ands    r10, r10, #0xFF
537    mov    r9, #0x100
538    mul    r9, r9, r10 
539    sub    sp, sp, r9 
540#endif 

 

这一段代码其实很简单,就是切换到不同的处理器模式来设置在该模式下的堆栈指针,因此无需多说。要注意的是,对于SMP的情形,这段代码是BSP和AP都会走过的,因此对于AP而言,需要相应地调整堆栈指针。接下来,代码切换回到Supervisor Mode,并且跳转到C代码cpu_init处执行。cpu_init()会进而调用vmm_init(),进而在初始化其他子系统,必然中断和调度等,因此不会再返回。

541    /* Set to Supervisor Mode */
542    CMODE    r8, CPSR_MODE_SUPERVISOR 
543    /* Call CPU init function */
544    b    cpu_init 
545    /* We should never reach here */
546    b    . 

对于初始化的基本流程就分析到这里。下面是这个周末移植到i.MX6Q上的结果(基本的串口,时钟,中断,调度已经可用,但是还没有调试SMP从核的启动,也还没有调试Guest的初始化部分,因此输出中还有一些错误消息可以看到,不过这个是下一步的事情了!)。
Xvisor ARM32 启动分析_第31张图片

你可能感兴趣的:(ARM)