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大小,那么要访问这些代码空间的代码和数据,则必须使用执行空间的地址。

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

初始化系统控制寄存器并开启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文档中对这个寄存器描述如下:

那么,对于我们的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, #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

我们假设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    2
216    .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, 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 启动分析的更多相关文章

  1. SpringBoot源码解析:tomcat启动分析

    >> spring与tomcat的启动分析:war包形式 tomcat:xml加载规范 1.contex-param: 初始化参数 2.listener-class: contextloa ...

  2. Nginx学习笔记(八) Nginx进程启动分析

    Nginx进程启动分析 worker子进程的执行循环的函数是ngx_worker_process_cycle (src/os/unix/ngx_process_cycle.c). 其中,捕获事件.分发 ...

  3. mkimage工具 加载地址和入口地址 内核启动分析

    第三章第二节 mkimage工具制作Linux内核的压缩镜像文件,需要使用到mkimage工具.mkimage这个工具位于u-boot-2013. 04中的tools目录下,它可以用来制作不压缩或者压 ...

  4. Android Binder------ServiceManager启动分析

    ServiceManager启动分析   简述: ServiceManager是一个全局的manager.调用了Jni函数,实现addServicew getService checkService ...

  5. Android系统--输入系统(七)Reader_Dispatcher线程启动分析

    Android系统--输入系统(七)Reader_Dispatcher线程启动分析 1. Reader/Dispatcher的引入 对于输入系统来说,将会创建两个线程: Reader线程(读取事件) ...

  6. 第3阶段——内核启动分析之start_kernel初始化函数(5)

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只是浅尝辄止的描述一下函数的功能,很多函数 ...

  7. 一起读源码之zookeeper(1) -- 启动分析

    从本文开始,不定期分析一个开源项目源代码,起篇从大名鼎鼎的zookeeper开始. 为什么是zk,因为用到zk的场景实在太多了,大部分耳熟能详的分布式系统都有zookeeper的影子,比如hbase, ...

  8. Tomcat启动分析(转自:http://docs.huihoo.com/apache/tomcat/heavyz/01-startup.html)

    Tomcat启动分析 1 - Tomcat Server的组成部分 1.1 - Server A Server element represents the entire Catalina servl ...

  9. FPGA低温不能启动分析(转)

    FPGA低温不能启动分析 现象描述:在给medium板光端机做低温试验时,分别给发送版.接收板断电重新启动,发现有的板子在-40°可以启动,而有些板子在-20°都不能启动,需要升高温度到0°以上才能启 ...

随机推荐

  1. 72.调用req.flash('error', '用户已存在!'); 时候 报错 "req.flash is not a function"

    在app.js 中调用app.use 的顺序有关 app.use(session({ secret: settings.cookieSecret, key: settings.db,//cookie ...

  2. 蓝桥杯训练 2n皇后

    问题描述 给定一个n*n的棋盘,棋盘中有一些位置不能放皇后.现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行.同一列或同一条对角线上,任意的两个白皇后都不在同一行.同一列或同一 ...

  3. bash命令集---文件的操作

    git bash命令集: clear:清除窗口中的内容 ls touch cat more head tail mv cp rm diff chmod gzip gunzip gzcat lpr lp ...

  4. Vue 导出表格为Excel

    放法有多种,我这里是直接转JSON数据为Excel. 1.既然要使用,那首先当然是安装依赖,在终端命令输入: npm install -S file-saver xlsx npm install -D ...

  5. 【Codeforces Round #428 (Div. 2) A】Arya and Bran

    [Link]: [Description] [Solution] 傻逼题 [NumberOf WA] [Reviw] [Code] #include <bits/stdc++.h> usi ...

  6. 几个不错的开源的.net界面控件

    转自原文 几个不错的开源的.net界面控件 (转) 几个不错的开源的.net界面控件 - zt 介绍几个自己觉得不错的几个开源的.net界面控件,不知道是否有人介绍过. DockPanel Suite ...

  7. CSS3:元素的边框、背景和大小

    边框 和边框相关的属性例如以下. border-width 用于设置边框的宽度,可选择包含: 1)<长度值>:将边框宽度设为以CSS度量单位(如em.px.cm)表达的长度值. 2)< ...

  8. 洛谷P1143 进制转换

    题目描述 请你编一程序实现两种不同进制之间的数据转换. 输入输出格式 输入格式: 输入数据共有三行,第一行是一个正整数,表示需要转换的数的进制n(2≤n≤16),第二行是一个n进制数,若n>10 ...

  9. Tuple<int, int> Dictionary<string, object>妙用

    Tuple<int, int> Dictionary<string, object>妙用

  10. BZOJ3262: 陌上花开(三维偏序,CDQ分治)

    Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),用三个整数表示. 现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量. 定义一朵花A比另一朵花B要美 ...