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

时间:2021-11-20 03:48:04

由上篇的分析可以知道,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的操作还不明白。不知道页表地址、页表项数据是怎么算出来的。上文理解是我的主观看法,如读者发现错误之处,请留言讨论一下!