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】这个文件。
31 * _start: Primary CPU startup code
32 * _start_secondary: Secondary CPU startup code
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.
38 .section .entry, "ax", %progbits
46 * r10 -> core# in case of SMP
正如注释所言,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 #20159    mov    r0, r0, lsl #20160    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 #20179    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 #20190    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, #1213    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, #4221    sub    r1, r1, #4222    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, #20250    add    r4, r4, #1251    lsl    r4, #20252    add    r1, r1, #4253    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大小,那么要访问这些代码空间的代码和数据,则必须使用执行空间的地址。


接着,执行下面的代码来将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




再接着,更新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            0x0283#define
					TTBL_L1TBL_TTE_DOM_VCPU_SUPER            0x1284#define
					TTBL_L1TBL_TTE_DOM_VCPU_SUPER_RW_USER_R    0x2285#define
					TTBL_L1TBL_TTE_DOM_VCPU_USER            0x3


初始化系统控制寄存器并开启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, 1118#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, 1125#endif
				这里首先是读取一个实现特定的Auxiliary Control Register使能SMP模式,并且使能了Cache and TLB maintenance broadcast这种保持一致性的广播操作。ARMv7的TRM文档中对这个寄存器描述如下:

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

 
 
接下来是读取芯片的版本信息,并更具版本信息来应用一些特定于某些版本的Errata修复。
126    mrc    p15, 0, r0, c0, c0, 0        @ read main ID register
				127    and    r10, r0, #0xff000000        @ ARM?
128    teq    r10, #0x41000000129    bne    3f130    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    2f139#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 1143    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 1149    orreq    r10, r10, #(1 << 9)        @ set PLDNOP to 1150    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 << 22156    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    3f160
				161    /* Cortex-A9 Errata */1622:    ldr    r10, =0x00000c09        @ Cortex-A9 primary part number
163    teq    r0, r10
164    bne    3f165#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 #4169    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 #12177    orreq    r10, r10, #1 << 22        @ set bit #22178    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 #6184    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 #11190    mcrlt    p15, 0, r10, c15, c0, 1        @ write diagnostic register
						1911:
192#endif
				 
 
我们假设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
 
 
 
 
在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    2216    .type    v7_crval, #object
217v7_crval:
218    .word    0x0120c302
							/* clear */219    .word    0x00c01c7d
							/* mmuset */这里是要将System Control Register寄存器读取到r0中,并且使用r5里的值作为掩码清除掉一些字段,然后用r6中的值设置上一些字段。我们看了这个系统控制寄存器的格式之后就知道具体清除或者设置了哪些字段。
 
 
 
 
 
 


因此,使用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)

我们注意到这里的Section属性设置了下面的字段:
- NS, Non-secure bit,设置为0。
- AP, Access Permissions bits,即设置为TTBL_AP_SRW_U 0x1,即在PL1/PL2下可读可写。
![]() 
- 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,也就是说不可共享。
 
 
 
开启在执行空间之旅
当代码返回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, 5496    ands    r10, r10, #0xFF497    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, 5506    ands    r10, r10, #0xFF507    mov    r9, #0x100508    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, 5516    ands    r10, r10, #0xFF517    mov    r9, #0x100518    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, 5526    ands    r10, r10, #0xFF527    mov    r9, #0x100528    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, 5536    ands    r10, r10, #0xFF537    mov    r9, #0x100538    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的初始化部分,因此输出中还有一些错误消息可以看到,不过这个是下一步的事情了!)。
 
                    
                     
                    
                 
                    
                 

 
        
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号