Linux 内核启动过程--head.S(arch/xxx/kernel下的)

由上篇的分析可以知道,uImage是zImage加上64字节的头信息得到的,而zImage又是compressed下的vmlinux经过objcopy得到的,compressed下的vmlinux是由vmlinux.lds、 head.S 和 piggy.gzip.S misc.c编译而成的,其实就是在piggy.gzip中添加了解压代码。piggy.gzip是Image经过gzip -n -f -9得到的,Image是源码目录下的vmlinux经过objcopy后得到的。因此如果zImage进行自解压,解压后的指令序列跟源码目录下的vmlinux的指令序列就应该是一样的。所以,zImage进行自解压后,最后一句ARM( mov pc, r4 )就跳转到了源码根目录下的vmlinux中。
那么vmlinux的执行过程怎么分析呢?入口在哪?
还是先找链接文件。编译生成源码目录下的vmlinux的过程中链接的文件是arm/arm/kernel/vmlinux.lds文件,这个lds文件是有vmlinux.lds.S生成的。
分析下面的链接文件可知,vmlinux的入口函数定义在arch/arm/kernel/head.S中。
入口函数是stext。

 41 OUTPUT_ARCH(arm)
 42 ENTRY(stext)
 43 
 44 #ifndef __ARMEB__
 45 jiffies = jiffies_64;
 46 #else
 47 jiffies = jiffies_64 + 4;
 48 #endif
 49 
 50 SECTIONS
 51 {
 52         /*
 53          * XXX: The linker does not define how output sections are
 54          * assigned to input sections when there are multiple statements
 55          * matching the same input section name.  There is no documented
 56          * order of matching.
 57          *
 58          * unwind exit sections must be discarded before the rest of the
 59          * unwind sections get included.
 60          */
 61         /DISCARD/ : {
 62                 *(.ARM.exidx.exit.text)
 63                 *(.ARM.extab.exit.text)
 64                 ARM_CPU_DISCARD(*(.ARM.exidx.cpuexit.text))
 65                 ARM_CPU_DISCARD(*(.ARM.extab.cpuexit.text))
 66                 ARM_EXIT_DISCARD(EXIT_TEXT)
 67                 ARM_EXIT_DISCARD(EXIT_DATA)
 68                 EXIT_CALL
 69 #ifndef CONFIG_HOTPLUG
 70                 *(.ARM.exidx.devexit.text)
.....

首先看几个宏

#ifndef ENTRY
#define ENTRY(name) \
  .globl name; \
  ALIGN; \
  name:
#endif
#endif /* LINKER_SCRIPT */

#ifndef WEAK
#define WEAK(name)     \
    .weak name;    \
    name:
#endif

#ifndef END
#define END(name) \
  .size name, .-name
#endif

/* If symbol 'name' is treated as a subroutine (gets called, and returns)
 * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of
 * static analysis tools such as stack depth analyzer.
 */
#ifndef ENDPROC
#define ENDPROC(name) \
  .type name, @function; \
  END(name)
#endif

看看arch/arm/kernel下的head.S

    .arm

    __HEAD
ENTRY(stext)

 THUMB( adr r9, BSYM(1f)    )   @ Kernel is always entered in ARM.
 THUMB( bx  r9      )   @ If this is a Thumb-2 kernel,
 THUMB( .thumb          )   @ switch to Thumb now.
 THUMB(1:           )

    setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
                        @ and irqs disabled
    mrc p15, 0, r9, c0, c0      @ get processor id
    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    movs    r10, r5             @ invalid processor (r5=0)?
 THUMB( it  eq )        @ force fixup-able long branch encoding
    beq __error_p           @ yes, error 'p'


    ldr r8, =PHYS_OFFSET        @ always constant in this case   0x8000 0000

    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */
    bl  __vet_atags //解析uBoot的tag,存放在bd->bi_boot_params中,其地址由Uboot传送.
#ifdef CONFIG_SMP_ON_UP
    bl  __fixup_smp //条件成立
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
    bl  __fixup_pv_table
#endif
    bl  __create_page_tables

    /*
     * The following calls CPU specific code in a position independent
     * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
     * xxx_proc_info structure selected by __lookup_processor_type
     * above.  On return, the CPU will be ready for the MMU to be
     * turned on, and r0 will hold the CPU control register value.
     */
    ldr r13, =__mmap_switched       @ address to jump to after
                        @ mmu has been enabled
    adr lr, BSYM(1f)            @ return (PIC) address
    mov r8, r4              @ set TTBR1 to swapper_pg_dir
 ARM(   add pc, r10, #PROCINFO_INITFUNC )
 THUMB( add r12, r10, #PROCINFO_INITFUNC    )
 THUMB( mov pc, r12             )
1:  b   __enable_mmu
ENDPROC(stext)

这里面,首先
1.setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
确保arm工作在SVC模式且IRQ、FIRQ都已经关闭了。

2、通过协处理器的操作得到processorID
“mrc p15, 0, r9, c0, c0”@ get processor id
然后通过__lookup_processor_type查看一下当前的内核是否支持这个ID。

__lookup_processor_type:
    adr r3, __lookup_processor_type_data
    ldmia   r3, {r4 - r6}
    sub r3, r3, r4          @ get offset between virt&phys
    add r5, r5, r3          @ convert virt addresses to
    add r6, r6, r3          @ physical address space
1:  ldmia   r5, {r3, r4}            @ value, mask
    and r4, r4, r9          @ mask wanted bits
    teq r3, r4
    beq 2f
    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    cmp r5, r6
    blo 1b
    mov r5, #0              @ unknown processor
2:  mov pc, lr
.......
ENDPROC(__lookup_processor_type)
    .align  2
    .type   __lookup_processor_type_data, %object
__lookup_processor_type_data:
    .long   .
    .long   __proc_info_begin
    .long   __proc_info_end
    .size   __lookup_processor_type_data, . - __lookup_processor_type_data

asm-offsets.c:124:  DEFINE(PROC_INFO_SZ,        sizeof(struct proc_info_list));

在kbuild.h中定义个这个宏
#define DEFINE(sym, val) \
        asm volatile("\n->" #sym " %0 " #val : : "i" (val)) 
        /*这个汇编没看懂,我的内核源码还没有经过编译,可能编译后会生成#define PROC_INFO_SZ  sizeof(struct proc_info_list)) 之类的吧。 */

//在proinfo.h中定义了结构体
struct proc_info_list {
    unsigned int        cpu_val;
    unsigned int        cpu_mask;
    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */
    unsigned long       __cpu_io_mmu_flags; /* used by head.S */
    unsigned long       __cpu_flush;        /* used by head.S */
    const char      *arch_name;
    const char      *elf_name;
    unsigned int        elf_hwcap;
    const char      *cpu_name;
    struct processor    *proc;
    struct cpu_tlb_fns  *tlb;
    struct cpu_user_fns *user;
    struct cpu_cache_fns    *cache;
};

 //在vmlinux.lds文件中定义了__proc_info_begin和__proc_info_end
 这两地址之间定义了当前的内核能支持的proc_info。可以用grep  '*proc.info.init*' -nR搜索一下,看看有哪些processor编译进了内核。
  VMLINUX_SYMBOL(__proc_info_begin) = .;                          \
 15         *(.proc.info.init)                                              \
 16         VMLINUX_SYMBOL(__proc_info_end) = .;

在__lookup_processor_type中修正__proc_info_begin和__proc_info_end的位置,使能与当前的运行地址匹配。然后从__proc_info_begin开始遍历,直到匹配成功将procinfo的首地址保存到r5返回,如果到了__proc_info_end还没有匹配成功,则将r5赋值为0返回。关于 proc_info_init段的定义一般都在对应的汇编文件中。
这里一般不会出错,r8记录了PHYS_OFFSET,我这里是0x8000_0000
然后执行__vet_atags,解析uboot传进来的参数(r2寄存器指定的参数)

__vet_atags:
    tst r2, #0x3            @ aligned?
    bne 1f

    ldr r5, [r2, #0]    //uboot的bd->bi_boot_params的size,在uboot中已经被填充为5,存在DDRBASE+0x100即0x80000100处.
#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?   //20>>2 0x14>>2 =5
    cmpne   r5, #ATAG_CORE_SIZE_EMPTY
    bne 1f
    ldr r5, [r2, #4]    //取tag  /* struct tag_header {  u32 size;   u32 tag;};*/
    ldr r6, =ATAG_CORE  //Uboot的setup_start_tag中    params->hdr.tag = 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的内容是不是4字节对齐。我们这里是0x8000_0000+0x100
显然是4字节对齐的。
下面就是解析atags的大小内容了。检查size,检查是不是以ATAG_CORE开始的,如果是则成功返回。
我们这里定义了CONFIG_SMP_ON_UP,还会执行__fixup_smp
我们这里也是成立的

#ifdef CONFIG_SMP_ON_UP
    __INIT
__fixup_smp:
    //  r9  0x414fc091
    and r3, r9, #0x000f0000 @ architecture version  0x000f0000
    teq r3, #0x000f0000     @ CPU ID supported?
    bne __fixup_smp_on_up   @ no, assume UP

    bic r3, r9, #0x00ff0000 //r3=0x4100c091
    bic r3, r3, #0x0000000f @ mask 0xff00fff0   r3=0x4100c090
    mov r4, #0x41000000
    orr r4, r4, #0x0000b000 //r4=0x4100b000
    orr r4, r4, #0x00000020 @ val 0x4100b020    //r4=0x4100b020
    teq r3, r4          @ ARM 11MPCore?
    moveq   pc, lr          @ yes, assume SMP

    mrc p15, 0, r0, c0, c0, 5   @ read MPIDR    //MPIDR is 0x80000000
    and r0, r0, #0xc0000000 @ multiprocessing extensions and
    teq r0, #0x80000000     @ not part of a uniprocessor system?
    moveq   pc, lr          @ yes, assume SMP
/*3535的MPIDR是0x80000000 条件成立,成功返回*/

我们这里也定义了CONFIG_ARM_PATCH_PHYS_VIRT,会执行__fixup_pv_table

__fixup_pv_table:
    adr r0, 1f

    ldmia   r0, {r3-r5, r7}

    sub r3, r0, r3  @ PHYS_OFFSET - PAGE_OFFSET

    add r4, r4, r3  @ adjust table start address
    add r5, r5, r3  @ adjust table end address
    add r7, r7, r3  @ adjust __pv_phys_offset address
    str r8, [r7]    @ save computed PHYS_OFFSET to __pv_phys_offset
    mov r6, r3, lsr #24 @ constant for add/sub instructions
    teq r3, r6, lsl #24 @ must be 16MiB aligned
THUMB(  it  ne      @ cross section branch )
    bne __error
    str r6, [r7, #4]    @ save to __pv_offset
    b   __fixup_a_pv_table
ENDPROC(__fixup_pv_table)

    .align
1:              //r0
    .long   .   //r3
    .long   __pv_table_begin    //r4    
    .long   __pv_table_end      //r5
2:  .long   __pv_phys_offset    //r7
    .text
__fixup_a_pv_table:

主要是修正__fixup_pv_table,其内容在链接脚本中定义.
__pv_table_begin = .;
167 *(.pv_table)
168 __pv_table_end = .;

然后执行__create_page_tables,设置TLB。
这个过程有点复杂,设计到页表基地址的计算,页表项内容,还有MMU设置。

__create_page_tables:
    pgtbl   r4, r8              @ page table address
/*
    .macro  pgtbl, rd, phys
 55     add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
 56     .endm
 47 #define PG_DIR_SIZE 0x4000
 48 #define PMD_ORDER   2
 在arch/arm/boot/compressed#中有-DTEXT_OFFSET=0x00008000
 pgtbl  r4, r8:
    add r4,0x8000_0000 0x4000
    r4=0x8000_0x4000,0x4000 2的14次方是 16K
 */

    /*
     * Clear the swapper page table .
     将  在0x80008000之下的16K的内存清空.
     */
    mov r0, r4  //0x8000_0x4000
    mov r3, #0
    add r6, r0, #PG_DIR_SIZE    //加16k r6=0x8000_0x8000,
1:  str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    teq r0, r6
    bne 1b
    ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
        adr r0, __turn_mmu_on_loc
    ldmia   r0, {r3, r5, r6}
    sub r0, r0, r3          @ virt->phys offset
    add r5, r5, r0          @ phys __turn_mmu_on
    add r6, r6, r0          @ phys __turn_mmu_on_end
    mov r5, r5, lsr #SECTION_SHIFT//物理地址右移了20位,可以看出段地址下标
    mov r6, r6, lsr #SECTION_SHIFT //物理地址右移了20位
    cmp r5, r6
    addlo   r5, r5, #1          @ next section
    blo 1b
1:  orr r3, r7, r5, lsl #SECTION_SHIFT  @ flags + kernel 
base
    str r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping  //PMD_ORDER =2 

__turn_mmu_on_loc:
    .long   .
    .long   __turn_mmu_on
    .long   __turn_mmu_on_end
/*   c0008108 <__turn_mmu_on_loc>:
   c0008108:   c0008108    .word   0xc0008108
   c000810c:   c0408440    .word   0xc0408440
   c0008110:   c0408460    .word   0xc0408460
* 可以看出__turn_mmu_on到__turn_mmu_end之间只有0x20字节,地址间距1M范围内。/    

上面的代码应该是将__turn_mmu_on_loc的运行地址进行映射,映射到上面地方呢?orr r3, r7, r5, lsl #SECTION_SHIFT
str r3, [r4, r5, lsl #PMD_ORDER]
其中r7位MMU_FLAG,r5为__turn_mmu_on的运行时地址(物理地址)
不知道MMU是怎么设置的,猜一下,应该是将__turn_mmu_on的物理硬质映射到了__turn_mmu_on的物理地址,即平映射。为什么平映射,现在还不清楚。
而这个页表的首地址就是r4即,大概是0x8000_4000//因为不明白MMU是怎么设置的,因此先做个假设,_str r3, [r4, r5, lsl #PMD_ORDER]中的[r4]就是页表地址,r5就是虚拟地址的段地址,即虚拟地址的高12位,r3就是物理地址。暂时这么认为。这不影响我们了解页表初始化。

/*
     * Now setup the pagetables for our kernel direct
     * mapped region.
     * 看注释。映射kernel了
     */
    mov r3, pc
    mov r3, r3, lsr #SECTION_SHIFT  
    orr r3, r7, r3, lsl #SECTION_SHIFT  //r7 MMUFLAG
    add r0, r4,  #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)    //KERNEL_START 0xc0008000
    //r0=r4+0xc000 0000>>18=0xc000>>2+0x8000_0x4000=0x8000_7000 
        str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!//此处,r3是当前的运行地址,可以看做是物理地址。那么这句话就应该是将物理地址r3进行映射了。映射到哪了呢?应该是KERNEL_START 之后的地址了。因为不知道页表项内容是怎么计算的,所以先分析到这。
    ldr r6, =(KERNEL_END - 1)//#define  KERNEL_END _end
    add r0, r0, #1 << PMD_ORDER //r0=r0+4
    add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
    1:  cmp r0, r6
    add r3, r3, #1 << SECTION_SHIFT//    //将该1M空间的物理起始地址存储到页表中相应虚拟地址页中  

    strls   r3, [r0], #1 << PMD_ORDER //0x8007030开始处,存放[KERNEL_START,END]的虚拟地址,1M为单位
    bls 1b

上面的代码作用就是初始化页表,页表中的数据内容指示了当前运行地址映射到的虚拟地址,这个虚拟地址应该是跟我们编译时的链接地址是一致的。
继续向下看

    /*
     * Then map boot params address in r2 or the first 1MB (2MB with LPAE)
     * of ram if boot params address is not specified.
     */
    mov r0, r2, lsr #SECTION_SHIFT
    movs    r0, r0, lsl #SECTION_SHIFT
    moveq   r0, r8
    sub r3, r0, r8
    add r3, r3, #PAGE_OFFSET    //+ 0xc000 0100
    add r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
    orr r6, r7, r0
    str r6, [r3]


    mov pc, lr

这里终于看到了个mov pc,lr表示函数要返回了,creat_page_table终于结束了。
从注释上看应该映射boot params,即uboot传进来的参数了。但是到现在为止只是初始化了这种页表,MMU依然没有打开。
在向下看

    /*
     * The following calls CPU specific code in a position independent
     * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
     * xxx_proc_info structure selected by __lookup_processor_type
     * above.  On return, the CPU will be ready for the MMU to be
     * turned on, and r0 will hold the CPU control register value.
     */
    ldr r13, =__mmap_switched       @ address to jump to after
                        @ mmu has been enabled
    adr lr, BSYM(1f)            @ return (PIC) address
    mov r8, r4              @ set TTBR1 to swapper_pg_dir
 ARM(   add pc, r10, #PROCINFO_INITFUNC )
 THUMB( add r12, r10, #PROCINFO_INITFUNC    )
 THUMB( mov pc, r12             )
1:  b   __enable_mmu
ENDPROC(stext)

看到这里就要分析add pc, r10, #PROCINFO_INITFUNC
PROCINFO_INITFUNC 这个宏在
Asm-offsets.c (z:\code\hi3535kernel\linux-3.4.y\arch\arm\kernel): DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));
这个值可能是16
r10又是什么呢?前面分析过,r10保存了proc_info_list的首地址。
用proc_info_list的首地址+offsetof(struct proc_info_list, __cpu_flush))就是我们现在的proc_info_list中的__cpu_flush。
看到这里你会想到 unsigned long __cpu_flush; /* used by head.S */

pc=myproc_info.__cpu_flush
看来必须找到myproc_info是在哪定义的了。看看他的__cpu_flush代表的是什么。
在arch/arm下搜索
grep -nr ‘proc.info.init’ *
出来了很多

root@ubuntu:/home/work/code/hi3535kernel/linux-3.4.y/arch/arm# grep -nr 'proc.info.init' *
Binary file kernel/.vmlinux.lds.S.swp matches
kernel/vmlinux.lds.S:15:    *(.proc.info.init)              \
mm/proc-sa1100.S:247:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm1022.S:449:  .section ".proc.info.init", #alloc, #execinstr
mm/proc-v6.S:262:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm1020e.S:466: .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm926.S:475:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm720.S:191:       .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm922.S:427:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-xsc3.S:501: .section ".proc.info.init", #alloc, #execinstr
mm/proc-mohawk.S:393:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm925.S:495:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm1026.S:444:  .section ".proc.info.init", #alloc, #execinstr
mm/proc-xscale.S:613:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm920.S:449:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm7tdmi.S:81:      .section ".proc.info.init", #alloc, #execinstr
mm/proc-sa110.S:204:    .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm946.S:410:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-v7.S:301:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm740.S:134:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-feroceon.S:558: .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm9tdmi.S:75:      .section ".proc.info.init", #alloc, #execinstr
mm/proc-fa526.S:196:    .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm6_7.S:289:       .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm940.S:356:   .section ".proc.info.init", #alloc, #execinstr
mm/proc-arm1020.S:508:  .section ".proc.info.init", #alloc, #execinstr
root@ubuntu:/home/work/code/hi3535kernel/linux-3.4.y/arch/arm# grep -nr 'proc.info.init' *

在这里,我们用的是armv7,分析一下mm/proc-v7.S

    .section ".proc.info.init", #alloc, #execinstr

    /*
     * Standard v7 proc info content
     */
.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0
    ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
    ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
            PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
    .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
        PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
    W(b)    \initfunc
    .long   cpu_arch_name
    .long   cpu_elf_name
    .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
        HWCAP_EDSP | HWCAP_TLS | \hwcaps
    .long   cpu_v7_name
    .long   v7_processor_functions
    .long   v7wbi_tlb_fns
    .long   v6_user_fns
    .long   v7_cache_fns
.endm

#ifndef CONFIG_ARM_LPAE
    /*
     * ARM Ltd. Cortex A5 processor.
     */
    .type   __v7_ca5mp_proc_info, #object
__v7_ca5mp_proc_info:
    .long   0x410fc050
    .long   0xff0ffff0
    __v7_proc __v7_ca5mp_setup
    .size   __v7_ca5mp_proc_info, . - __v7_ca5mp_proc_info

    /*
     * ARM Ltd. Cortex A9 processor.
     */
    .type   __v7_ca9mp_proc_info, #object
__v7_ca9mp_proc_info:
    .long   0x410fc090
    .long   0xff0ffff0
    __v7_proc __v7_ca9mp_setup
    .size   __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info
#endif  /* CONFIG_ARM_LPAE */

    /*
     * ARM Ltd. Cortex A7 processor.
     */
    .type   __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
    .long   0x410fc070
    .long   0xff0ffff0
    __v7_proc __v7_ca7mp_setup, hwcaps = HWCAP_IDIV
    .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info

    /*
     * ARM Ltd. Cortex A15 processor.
     */
    .type   __v7_ca15mp_proc_info, #object
__v7_ca15mp_proc_info:
    .long   0x410fc0f0
    .long   0xff0ffff0
    __v7_proc __v7_ca15mp_setup, hwcaps = HWCAP_IDIV
    .size   __v7_ca15mp_proc_info, . - __v7_ca15mp_proc_info

    /*
     * Match any ARMv7 processor core.
     */
    .type   __v7_proc_info, #object
__v7_proc_info:
    .long   0x000f0000      @ Required ID value
    .long   0x000f0000      @ Mask for ID
    __v7_proc __v7_setup
    .size   __v7_proc_info, . - __v7_proc_info

看最后一个__v7_proc_info,将其展开后,你会发现

    .section ".proc.info.init", #alloc, #execinstr
__v7_proc_info:
    .long   0x000f0000  
    .long   0x000f0000
    .long       \mm_mmuflags
    .long       \__cpu_io_mmu_flags;    
     b  __v7_setup  //这就是我们要查的函数
    .long   \cpu_arch_name
    .long   \cpu_elf_name
    .long   \hwcaps
    .long   cpu_v7_name
    .long   v7_processor_functions
    .long   v7wbi_tlb_fns
    .long   v6_user_fns
    .long   v7_cache_fns

因为我们用的是就是__v7_proc_info,因此可以看看__v7_setup 这个函数干的什么事儿。

__v7_setup:
    adr r12, __v7_setup_stack       @ the local stack
    stmia   r12, {r0-r5, r7, r9, r11, lr}
    bl  v7_flush_dcache_all
    ldmia   r12, {r0-r5, r7, r9, r11, lr}

    mrc p15, 0, r0, c0, c0, 0       @ read main ID register
    and r10, r0, #0xff000000        @ ARM?
    teq r10, #0x41000000
    bne 3f
    and r5, r0, #0x00f00000     @ variant
    and r6, r0, #0x0000000f     @ revision
    orr r6, r6, r5, lsr #20-4       @ combine variant and revision
    ubfx    r0, r0, #4, #12         @ primary part number

    /* Cortex-A8 Errata */
    ldr r10, =0x00000c08        @ Cortex-A8 primary part number
    teq r0, r10
    bne 2f
#ifdef CONFIG_ARM_ERRATA_430973
    teq r5, #0x00100000         @ only present in r1p*
    mrceq   p15, 0, r10, c1, c0, 1      @ read aux control register
    orreq   r10, r10, #(1 << 6)     @ set IBE to 1
    mcreq   p15, 0, r10, c1, c0, 1      @ write aux control register
#endif
#ifdef CONFIG_ARM_ERRATA_458693
    teq r6, #0x20           @ only present in r2p0
    mrceq   p15, 0, r10, c1, c0, 1      @ read aux control register
    orreq   r10, r10, #(1 << 5)     @ set L1NEON to 1
    orreq   r10, r10, #(1 << 9)     @ set PLDNOP to 1
    mcreq   p15, 0, r10, c1, c0, 1      @ write aux control register
#endif
#ifdef CONFIG_ARM_ERRATA_460075
    teq r6, #0x20           @ only present in r2p0
    mrceq   p15, 1, r10, c9, c0, 2      @ read L2 cache aux ctrl register
    tsteq   r10, #1 << 22
    orreq   r10, r10, #(1 << 22)        @ set the Write Allocate disable bit
    mcreq   p15, 1, r10, c9, c0, 2      @ write the L2 cache aux ctrl register
#endif
    b   3f

    /* Cortex-A9 Errata */
2:  ldr r10, =0x00000c09        @ Cortex-A9 primary part number
    teq r0, r10
    bne 3f
#ifdef CONFIG_ARM_ERRATA_742230
    cmp r6, #0x22           @ only present up to r2p2
    mrcle   p15, 0, r10, c15, c0, 1     @ read diagnostic register
    orrle   r10, r10, #1 << 4       @ set bit #4
    mcrle   p15, 0, r10, c15, c0, 1     @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_742231
    teq r6, #0x20           @ present in r2p0
    teqne   r6, #0x21           @ present in r2p1
    teqne   r6, #0x22           @ present in r2p2
    mrceq   p15, 0, r10, c15, c0, 1     @ read diagnostic register
    orreq   r10, r10, #1 << 12      @ set bit #12
    orreq   r10, r10, #1 << 22      @ set bit #22
    mcreq   p15, 0, r10, c15, c0, 1     @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_743622
    teq r5, #0x00200000         @ only present in r2p*
    mrceq   p15, 0, r10, c15, c0, 1     @ read diagnostic register
    orreq   r10, r10, #1 << 6       @ set bit #6
    mcreq   p15, 0, r10, c15, c0, 1     @ write diagnostic register
#endif
#if defined(CONFIG_ARM_ERRATA_751472) && defined(CONFIG_SMP)
    ALT_SMP(cmp r6, #0x30)          @ present prior to r3p0
    ALT_UP_B(1f)
    mrclt   p15, 0, r10, c15, c0, 1     @ read diagnostic register
    orrlt   r10, r10, #1 << 11      @ set bit #11
    mcrlt   p15, 0, r10, c15, c0, 1     @ write diagnostic register
1:
#endif

3:  mov r10, #0
    mcr p15, 0, r10, c7, c5, 0      @ I+BTB cache invalidate
    dsb
#ifdef CONFIG_MMU
    mcr p15, 0, r10, c8, c7, 0      @ invalidate I + D TLBs
    v7_ttb_setup r10, r4, r8, r5        @ TTBCR, TTBRx setup
    ldr r5, =PRRR           @ PRRR
    ldr r6, =NMRR           @ NMRR
    mcr p15, 0, r5, c10, c2, 0      @ write PRRR
    mcr p15, 0, r6, c10, c2, 1      @ write NMRR
#endif
#ifndef CONFIG_ARM_THUMBEE
    mrc p15, 0, r0, c0, c1, 0       @ read ID_PFR0 for ThumbEE
    and r0, r0, #(0xf << 12)        @ ThumbEE enabled field
    teq r0, #(1 << 12)          @ check if ThumbEE is present
    bne 1f
    mov r5, #0
    mcr p14, 6, r5, c1, c0, 0       @ Initialize TEEHBR to 0
    mrc p14, 6, r0, c0, c0, 0       @ load TEECR
    orr r0, r0, #1          @ set the 1st bit in order to
    mcr p14, 6, r0, c0, c0, 0       @ stop userspace TEEHBR access
1:
#endif
    adr r5, v7_crval
    ldmia   r5, {r5, r6}
#ifdef CONFIG_CPU_ENDIAN_BE8
    orr r6, r6, #1 << 25        @ big-endian page tables
#endif
#ifdef CONFIG_SWP_EMULATE
    orr     r5, r5, #(1 << 10)              @ set SW bit in "clear"
    bic     r6, r6, #(1 << 10)              @ clear it in "mmuset"
#endif
    mrc p15, 0, r0, c1, c0, 0       @ read control register
    bic r0, r0, r5          @ clear bits them
    orr r0, r0, r6          @ set them
 THUMB( orr r0, r0, #1 << 30    )   @ Thumb exceptions
    mov pc, lr              @ return to head.S:__ret
ENDPROC(__v7_setup)

这个函数很长啊,但是有很多编译条件是我们不需要的。
看英文注释大概知道,他里面还区分A8 A9,我们用的是A9
看最后一个mov pc, lr
这就是在刚才的 ARM( add pc, r10, #PROCINFO_INITFUNC )
后返回了下一条指令。
下一条指令就是b __enable_mmu

 ARM(   add pc, r10, #PROCINFO_INITFUNC )
 THUMB( add r12, r10, #PROCINFO_INITFUNC    )
 THUMB( mov pc, r12             )
1:  b   __enable_mmu
////////////////////////////
__enable_mmu:
#if defined(CONFIG_ALIGNMENT_TRAP) && __LINUX_ARM_ARCH__ < 6
    orr r0, r0, #CR_A
#else
    bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
    bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
    bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
    bic r0, r0, #CR_I
#endif
#ifdef CONFIG_ARM_LPAE
    mov r5, #0
    mcrr    p15, 0, r4, r5, c2      @ load TTBR0
#else
    mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
              domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    mcr p15, 0, r5, c3, c0, 0       @ load domain access register
    mcr p15, 0, r4, c2, c0, 0       @ load page table pointer
#endif
    b   __turn_mmu_on
ENDPROC(__enable_mmu)

    .align  5
    .pushsection    .idmap.text, "ax"
ENTRY(__turn_mmu_on)
    mov r0, r0
    instr_sync
    mcr p15, 0, r0, c1, c0, 0       @ write control reg
    mrc p15, 0, r3, c0, c0, 0       @ read id reg
    instr_sync
    mov r3, r3
    mov r3, r13
    mov pc, r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)

__enable_mmu的功能比较简单,就是加载 页表,并执行,在__turn_mmu_on中,最后用mov pc,r3再次跳转。
由`bl __create_page_tables

/*
 * The following calls CPU specific code in a position independent
 * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
 * xxx_proc_info structure selected by __lookup_processor_type
 * above.  On return, the CPU will be ready for the MMU to be
 * turned on, and r0 will hold the CPU control register value.
 */
ldr r13, =__mmap_switched       @ address to jump to after`

可以知道,开启MMU后又跳转到了__mmap_switched

__mmap_switched:
    adr r3, __mmap_switched_data

    ldmia   r3!, {r4, r5, r6, r7}
    cmp r4, r5              @ Copy data segment if needed
1:  cmpne   r5, r6
    ldrne   fp, [r4], #4
    strne   fp, [r5], #4
    bne 1b

    mov fp, #0              @ Clear BSS (and zero fp)
1:  cmp r6, r7
    strcc   fp, [r6],#4
    bcc 1b

 ARM(   ldmia   r3, {r4, r5, r6, r7, sp})
 THUMB( ldmia   r3, {r4, r5, r6, r7}    )
 THUMB( ldr sp, [r3, #16]       )
    str r9, [r4]            @ Save processor ID
    str r1, [r5]            @ Save machine type
    str r2, [r6]            @ Save atags pointer
    bic r4, r0, #CR_A           @ Clear 'A' bit
    stmia   r7, {r0, r4}            @ Save control register values
    b   start_kernel
ENDPROC(__mmap_switched)

原来从这个函数跳到了start_kernel。终于看到希望了,最后有个b start_kernel,C函数。

ENTRY(stext)

    setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
mrc p15, 0, r9, c0, c0      @ get processor id
    bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    ldr r8, =PHYS_OFFSET
    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */
    bl  __vet_atags //解析uBoot的tag,存放在bd->bi_boot_params中,其地址由Uboot传送.
    #ifdef CONFIG_SMP_ON_UP
    bl  __fixup_smp //条件成立
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
    bl  __fixup_pv_table
#endif
    bl  __create_page_tables
    ldr r13, =__mmap_switched
    adr lr, BSYM(1f)            @ return (PIC) address
    mov r8, r4              @ set TTBR1 to swapper_pg_dir
    ARM(    add pc, r10, #PROCINFO_INITFUNC )
1:  b   __enable_mmu
ENDPROC(stext)

下面再回顾一下:

1.设置arm工作模式在SVC并关闭FIRQ IRQ
2.探测processor 类型
3.解析uboot传进来的atags
4.__fixup_smp __fixup_pv_table
5.__create_page_tables
6.执行由proc_info_list 的__cpu_flush;指定的函数。如__v7_setup
7.开启MMU
8.__mmap_switched
9.b start_kernel
以上只是我个人对arch/arm/kernel/head.S的理解。

有很多MMU的操作还不明白。不知道页表地址、页表项数据是怎么算出来的。上文理解是我的主观看法,如读者发现错误之处,请留言讨论一下!

你可能感兴趣的:(Linux 内核启动过程--head.S(arch/xxx/kernel下的))