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

    接着,执行下面的代码来将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的初始化部分,因此输出中还有一些错误消息可以看到,不过这个是下一步的事情了!)。

  • 相关阅读:
    Python中的list,tuple,dict,set
    linux上发布网站遇到的问题
    asp.net通过配置文件设置默认页
    半自动安装 linux 系统
    redis整合spring
    redis介绍。
    DAC模式登陆(解决对象名'sys.sysobjvalues'无效)
    对存储过程进行加密和解密(SQL 2008/SQL 2012)
    UML 系列
    Redis学习笔记~目录
  • 原文地址:https://www.cnblogs.com/coryxie/p/3803172.html
Copyright © 2011-2022 走看看