IMX6Solo启动流程-Linux 内核启动 六

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列内核代码基于linux-3.0.35-imx。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!


接上篇分析完__lookup_processor_type函数,程序继续往下执行:

    adr r3, 2f
    ldmia   r3, {r4, r8}
    sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)
    add r8, r8, r4          @ PHYS_OFFSET

    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */     
    bl  __vet_atags
#ifdef CONFIG_SMP_ON_UP
    bl  __fixup_smp
#endif
    ...
    bl  __create_page_tables

计算出PAGE_OFFSET的物理地址。
调用__vet_atags、__fixup_smp、__create_page_tables,接下来我们只分析__vet_atags和__create_page_tables。

__vet_atags

定义在head-common.S中,主要作用就是检查UBoot传进来的参数是否合法。即是否为ATAG_CORE开头的内核参数或者以OF_DT_MAGIC开头的设备树。

__vet_atags:
    tst r2, #0x3            @ aligned?
    bne 1f        ;判断是否对齐,如果没有对齐直接跳出

    ldr r5, [r2, #0]    ;加载r2寄存器里的值指向的地址的数据
#ifdef CONFIG_OF_FLATTREE
    ldr r6, =OF_DT_MAGIC        @ is it a DTB?第二个参数可能是内核参数,也有可能是设备树
    cmp r5, r6
    beq 2f
#endif
    cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?ATAG_CORE是固定长度的
    cmpne   r5, #ATAG_CORE_SIZE_EMPTY
    bne 1f    ;不是ATAG_CORE或者ATAG_CORE长度不对,跳出
    ldr r5, [r2, #4]
    ldr r6, =ATAG_CORE    ;判断是否为ATAG_CORE
    cmp r5, r6
    bne 1f

2:  mov pc, lr              @ atag/dtb pointer is ok

1:  mov r2, #0
    mov pc, lr
ENDPROC(__vet_atags)

如果不合法,则将r2的寄存器值赋为0。


__create_page_tables

在开启MMU之前,要先做好一个页表,作为虚拟地址跟物理地址的映射,要将虚拟地址0x000000000~0xFFFFFFFF这4G的地址映射到实际的物理地址上面,由于是临时的,只是为了执行内核(因为内核编译的时候链接地址是PAGE_OFFSET+TEXT_OFFSET),与开启MMU前执行地址(即内核被bootload加载的地方,或者说内核自解压的地方)PHY_OFFSET(本开发板为0x10008000)不一致,涉及到一些变量的存放地址都是虚拟地址,所以要开启MMU做映射,才能正确执行内核。
映射只是将32位虚拟地址中高12位做映射,低20位不做映射,访问一个地址时,先取出它的高12位,然后读取页表偏移量为高12位的值的数据作为映射后的高12位,低20位未改变,如此就是物理地址。
做这样子映射总共需要4096(即2^12)个页表项,每个页表项要用4个字节保存,所以总共需要16K的空间(即0x10004000到0x10008000的物理地址空间),然后对这16K的空间进行赋值。每个页表项里面保存的值高12位是映射值,低20位是MMU flag值。
开启MMU之后,例如要访问一个地址为0x8000FFFF的变量,首先MMU会计算出它在页表项的位置,由于一个页表项代表1M(即2^20),所以0x8000FFFF的位置是0x2000(0x8000FFFF>>20),然后读取该页表项里面指向的物理地址页表,即读取0x10004000+0x2000的位置,假如此时位置的值是0x1000,那么虚拟地址0x8000FFFF对应的物理地址是((0x1000) << 20) | (0x8000FFFF & 0x000FFFFF)。

此时一些寄存器的值如下:

/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/

定义在head.S中,去掉未定义的宏所包括的代码:

__create_page_tables:
    pgtbl   r4, r8              @ page table address;pgtbl是一个宏,定义在同文件,计算出r8所保存的内核起始物理地址前16K的地址保存在r4寄存器

    /*
     * Clear the 16K level 1 swapper page table
     */
    mov r0, r4
    mov r3, #0
    add r6, r0, #0x4000
1:  str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    teq r0, r6
    bne 1b    ;清零这16K数据,作为一级交互页表

    ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags;加载struct proc_info_list->__cpu_mm_mmu_flags到r7

    /*
     * Create identity mapping to cater for __enable_mmu.
     * This identity mapping will be removed by paging_init().
     */
    adr r0, __enable_mmu_loc
    ldmia   r0, {r3, r5, r6}
    sub r0, r0, r3          @ virt->phys offset
    add r5, r5, r0          @ phys __enable_mmu,计算__enable_mmu物理地址
    add r6, r6, r0          @ phys __enable_mmu_end,计算__enable_mmu_end物理地址
    mov r5, r5, lsr #20    ;取高12位
    mov r6, r6, lsr #20    ;取高12位

;做__enable_mmu和__enable_mmu_end之间的平等映射
1:  orr r3, r7, r5, lsl #20     @ flags + kernel base,映射值或上MMU flag
    str r3, [r4, r5, lsl #2]        @ identity mapping,将映射值保存在对应的页表项中
    teq r5, r6
    addne   r5, r5, #1          @ next section
    bne 1b

    /*
     * Now setup the pagetables for our kernel direct
     * mapped region.
     */
    mov r3, pc
    mov r3, r3, lsr #20
    orr r3, r7, r3, lsl #20    ;r3保存当前PC的映射值
    add r0, r4,  #(KERNEL_START & 0xff000000) >> 18;KERNEL_START为映射后的内核起始地址,r0保存该地址所对应页表中的位置
    str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!;保存虚拟内核起始地址的映射值
    ldr r6, =(KERNEL_END - 1);KERNEL_END为内核映射终止地址
    add r0, r0, #4;跳过一个页表项
    add r6, r4, r6, lsr #18;r6保存虚拟内核终止地址所对应页表位置。
;设置映射值
1:  cmp r0, r6
    add r3, r3, #1 << 20
    strls   r3, [r0], #4
    bls 1b

    mov pc, lr
ENDPROC(__create_page_tables)   
    .ltorg
    .align
__enable_mmu_loc:
    .long   .
    .long   __enable_mmu
    .long   __enable_mmu_end
  1. 清零用来保存页表的16K数据。
  2. 对__enable_mmu和__enable_mmu_end之间做平等映射,所谓平等映射就是虚拟地址跟物理地址一样。这段代码是开启MMU,在开启MMU时PC的地址为物理地址,开启之后,PC只是继续读取下一条指令(即PC+=4),但是此时已变成虚拟地址,如果没有做平等映射,此时PC的值为虚拟地址,就会跑飞掉,但是有做平等映射后,虚拟地址跟物理地址一致,所以PC会正常执行,直到一个跳转指令的到来。
  3. 做内核起始地址和终止地址之间的映射。
  4. 经过这些映射,开启MMU之后,内核依然可以正常执行。

总结

由于链接地址跟加载地址的不一致,需要开启MMU,使用地址映射方法指之一致,所以需要稍微理解一下映射原理。还有一个是平等映射的关系,理解平等映射之后就不会对开启MMU时执行流程产生疑惑。

参考

暂无

你可能感兴趣的:(飞思卡尔i.MX6系列)