设备树学习(四、内核head.S对uboot传参的处理)

之前在uboot学习中分析bootm原理的,我们知道了uboot启动内核是通过传入三个参数来启动的。

kernel_entry为内核zImage在内存的首地址。

之前我们传的三个参数分别是:

0,芯片的机器ID,uboot给内核参数tag在内存的首地址r2。

 

查看上面截图的代码,可以看到r2也可以是另设备树文件(dtb)在在内存的地址。

 

对于使用tag传参给内核,机器的ID是必须要和内核中配置的芯片一致的。

而对于使用设备树来启动内核,机器ID则不是必须的。

同时,对新的4.x的内核,默认支持设备树传参启动。

 

接下来我们简单分析一下内核的启动对三个传参r0,r1,r2的处理。

 

1. 此时系统所满足的条件:

  • MMU = off, D-cache = off, I-cache = dont care
  • r0 = 0, r1 = machine nr, r2 = atags or dtb pointer.

2. 如果配置了 CONFIG_ARM_VIRT_EXT(虚拟扩展),则跳转到 __hyp_stub_install 标签处。

 /arch/arm/kernel/head.S
 77     .arm
 78 
 79     __HEAD
 80 ENTRY(stext)
    ......
 87 
 88 #ifdef CONFIG_ARM_VIRT_EXT
 89     bl  __hyp_stub_install
 90 #endif

3. 借助 safe_svcmode_maskall 宏, 屏蔽 IRQ、FIQ 中断,切换到 SVC 模式。

 /arch/arm/kernel/head.S

 91     @ ensure svc mode and all interrupts masked
 92     safe_svcmode_maskall r9
 93 
 94     mrc p15, 0, r9, c0, c0      @ get processor id
 95     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
 96     movs    r10, r5             @ invalid processor (r5=0)?
 97  THUMB( it  eq )        @ force fixup-able long branch encoding
 98     beq __error_p           @ yes, error 'p'

注: safe_svcmode_maskall 宏被定义在 /arch/arm/include/asm/assembler.h - line 344 中。

.macro safe_svcmode_maskall reg:req
#if __LINUX_ARM_ARCH__ >= 6 && !defined(CONFIG_CPU_V7M)
	mrs	\reg , cpsr
	eor	\reg, \reg, #HYP_MODE
	tst	\reg, #MODE_MASK
	bic	\reg , \reg , #MODE_MASK
	orr	\reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE
THUMB(	orr	\reg , \reg , #PSR_T_BIT	)
	bne	1f
	orr	\reg, \reg, #PSR_A_BIT
	badr	lr, 2f
	msr	spsr_cxsf, \reg
	__MSR_ELR_HYP(14)
	__ERET
1:	msr	cpsr_c, \reg
2:
#else
/*
 * workaround for possibly broken pre-v6 hardware
 * (akita, Sharp Zaurus C-1000, PXA270-based)
 */
	setmode	PSR_F_BIT | PSR_I_BIT | SVC_MODE, \reg
#endif
.endm
  • 将模式位 M[4:0] 清 0
bic	\reg , \reg , #MODE_MASK
  • 通过设置低 8 位为 110 10011,达到了关闭 IRQ、FIQ、设置 CPU 工作模式为 SVC 模式的目标:
orr	\reg , \reg , #PSR_I_BIT | PSR_F_BIT | SVC_MODE

注:此处出现的 MODE_MASK、PSR_I_BIT 等常量被宏定义在 /arch/arm/include/uapi/asm/ptrace.h 中。

 

 

 

4. 加载 MIDR(Main ID Register) 的内容到 R9 中, 之后跳转到 __lookup_processor_type 标签处来寻找处理器类型。

 /arch/arm/kernel/head.S

 94     mrc p15, 0, r9, c0, c0      @ get processor id
 95     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid

__lookup_processor_type 详解

  • 获得 __lookup_processor_type_data 的链接地址和运行时的物理地址之间的偏移保存到 R3 中(offset = R3 - R4),之后使用这个偏移量来修正 R5 和 R6.
/arch/arm/kernel/head-common.S
162 /*
163  * Read processor ID register (CP#15, CR0), and look up in the linker-built
164  * supported processor list.  Note that we can't use the absolute addresses
165  * for the __proc_info lists since we aren't running with the MMU on
166  * (and therefore, we are not in the correct address space).  We have to
167  * calculate the offset.
168  *
169  *  r9 = cpuid
170  * Returns:
171  *  r3, r4, r6 corrupted
172  *  r5 = proc_info pointer in physical address space
173  *  r9 = cpuid (preserved)
174  */
175 __lookup_processor_type:
176     adr r3, __lookup_processor_type_data
177     ldmia   r3, {r4 - r6}
178     sub r3, r3, r4          @ get offset between virt&phys
179     add r5, r5, r3          @ convert virt addresses to
180     add r6, r6, r3          @ physical address space
181 1:  ldmia   r5, {r3, r4}            @ value, mask
182     and r4, r4, r9          @ mask wanted bits
183     teq r3, r4
184     beq 2f
185     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
186     cmp r5, r6
187     blo 1b
188     mov r5, #0              @ unknown processor
189 2:  ret lr
190 ENDPROC(__lookup_processor_type)
  • __lookup_processor_type_data 的定义如下所示:
/arch/arm/kernel/head-common.S
192 /*
193  * Look in <asm/procinfo.h> for information about the __proc_info structure.
194  */
195     .align  2
196     .type   __lookup_processor_type_data, %object
197 __lookup_processor_type_data:
198     .long   .
199     .long   __proc_info_begin
200     .long   __proc_info_end
201     .size   __lookup_processor_type_data, . - __lookup_processor_type_data

可以看到, 执行完 177 行的 ldmia 指令后, R4、R5、R6 中依次保存的是 __lookup_processor_type_data、 __proc_info_begin、__proc_info_end 的链接地址。

 

2] 通过循环来遍历 proc_info 列表,查找目标处理器;当 R3 = R4 & R9 时,说明找到了,跳出循环,R5 是“指向目标处理器的 proc_info 结构体的指针”; 若遍历完之后还没找到,则 R5 = 0.

/arch/arm/kernel/head-common.S

181 1:  ldmia   r5, {r3, r4}            @ value, mask
182     and r4, r4, r9          @ mask wanted bits
183     teq r3, r4
184     beq 2f
185     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
186     cmp r5, r6
187     blo 1b
188     mov r5, #0              @ unknown processor
189 2:  ret lr
190 ENDPROC(__lookup_processor_type)

注: __lookup_processor_type 例程中,
- r3, r4, r6 被修改
- r5 = 指向找到的 proc_info 结构体 或 为0(没找到)
- r9 = cpuid ( = MIDR),未被修改

 

  • proc_info 结构体的结构如下所示:

 

/arch/arm/include/asm/procinfo.h
/*
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
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;
};

故181行 的 ldmia 指令完成后,R3 = cpu_val (CPU 的硬件 ID 号), R4 = cpu_mask.

 

5. 若没有找到目标处理器类型,则 R5 = 0,执行 beq 指令,跳转到 __error_p 标签处陷入死循环(若配置了 CONFIG_DEBUG_LL,则会在陷入死循环之前输出错误信息);否则继续向下执行。

/arch/arm/kernel/head.S

 95     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
 96     movs    r10, r5             @ invalid processor (r5=0)?
 97  THUMB( it  eq )        @ force fixup-able long branch encoding
 98     beq __error_p           @ yes, error 'p'

注:默认不配置 CONFIG_ARM_LPAE(Large Physical Address Extensions),这里暂且跳过里面的内容。

 

6. 根据计算出的 运行地址 和 链接地址 之间的偏移量,保存到 R4 中; R8 = PHYS_OFFSET(内核 Image 起初处的物理地址) = 此偏移 + PAGE_OFFSET.

/arch/arm/kernel/head.S

108 #ifndef CONFIG_XIP_KERNEL
109     adr r3, 2f
110     ldmia   r3, {r4, r8}
111     sub r4, r3, r4          @ (PHYS_OFFSET - PAGE_OFFSET)
112     add r8, r8, r4          @ PHYS_OFFSET
113 #else
114     ldr r8, =PLAT_PHYS_OFFSET       @ always constant in this case
115 #endif



164 #ifndef CONFIG_XIP_KERNEL
165 2:  .long   .
166     .long   PAGE_OFFSET
167 #endif
  1. 此处 "2f" 标签所在处如下所示,可知 R3 = 标签2 的运行地址,R4 = 标签2 的链接地址, R8 = PAGE_OFFSET(内核 Image 起初处的虚拟地址).
  2. R8 += R4,此时 R8 = PAGE_OFFSET(内核 Image 起初处的物理地址)

注:默认不配置 CONFIG_XIP_KERNEL,这里不考虑执行 #else 中的内容; 此处的 XIP 是 eXecute In Place(就地执行)的缩写。

Kernel XIP 原理如下,内核映像在 Flash 设备上执行以后,只把映像中要读写的 .data 和 .bss 拷贝到 SDRAM 主存中,同时设置好系统的 MMU,内核运行过程中,代码段 .text 指向 Flash 空间,.data 和 .bss 指向 SDRAM 主存空间。相对于全映射的执行方式,系统节省了解压缩和拷贝代码段的时间,节省了代码段占用的 RAM 主存空间。

 

7. 跳转到 __vet_atags 标签处,校验保存在 R2 中的值(atgs pointer 或者 dtb pointer)。

 /arch/arm/kernel/head.S
117     /*
118      * r1 = machine no, r2 = atags or dtb,
119      * r8 = phys_offset, r9 = cpuid, r10 = procinfo
120      */
121     bl  __vet_atags
  1. 跳转前的寄存器的值如下所示:

    • r1 = machine no
    • r2 = atags pointer 或 dtb pointer
    • r8 = phys_offset
    • r9 = cpuid (即 MIDR 寄存器的值)
    • r10 = procinfo ( = R5,指向找到的 proc_info 结构体的指针)
  2. __vet_atags, 验证 atags pointer 或 dtb pointer.

1] 检测是否 4byte 对齐,若没有对齐,则 R2 = 0,返回原来的执行流;否则继续向下执行。

 /arch/arm/kernel/head-common.S

 36 /* Determine validity of the r2 atags pointer.  The heuristic requires
 37  * that the pointer be aligned, in the first 16k of physical RAM and
 38  * that the ATAG_CORE marker is first and present.  If CONFIG_OF_FLATTREE
 39  * is selected, then it will also accept a dtb pointer.  Future revisions
 40  * of this function may be more lenient with the physical address and
 41  * may also be able to move the ATAGS block if necessary.
 42  *
 43  * Returns:
 44  *  r2 either valid atags pointer, valid dtb pointer, or zero
 45  *  r5, r6 corrupted
 46  */
 47 __vet_atags:
 48     tst r2, #0x3            @ aligned?
 49     bne 1f
 50 
 51     ldr r5, [r2, #0]
 52 #ifdef CONFIG_OF_FLATTREE
 53     ldr r6, =OF_DT_MAGIC        @ is it a DTB?
 54     cmp r5, r6
 55     beq 2f
 56 #endif
 57     cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?
 58     cmpne   r5, #ATAG_CORE_SIZE_EMPTY
 59     bne 1f
 60     ldr r5, [r2, #4]
 61     ldr r6, =ATAG_CORE
 62     cmp r5, r6
 63     bne 1f
 64 
 65 2:  ret lr              @ atag/dtb pointer is ok
 66 
 67 1:  mov r2, #0
 68     ret lr
 69 ENDPROC(__vet_atags)

2] 加载 [R2, #0] 到 R5 中;如若配置了 CONFIG_OF_FLATTREE(默认会配置此选项),则进行校验是否是合法的 DTB. 若通过校验,则直接跳转到 "2f" 处,返回到调用 __vet_atags 之前的执行流;否则继续向下执行。

 /arch/arm/kernel/head-common.S

 51     ldr r5, [r2, #0]
 52 #ifdef CONFIG_OF_FLATTREE
 53     ldr r6, =OF_DT_MAGIC        @ is it a DTB?
 54     cmp r5, r6
 55     beq 2f
 56 #endif

3] 因为 tag 列表是以 ATAG_CORE 节点开始的,以此作为根据,分别校验第一个 Node 的 size 和 tag 是否符合 ATAG_CORE,以确定 atag 指针无误。

 /arch/arm/kernel/head-common.S
 57     cmp r5, #ATAG_CORE_SIZE     @ is first tag ATAG_CORE?
 58     cmpne   r5, #ATAG_CORE_SIZE_EMPTY
 59     bne 1f
 60     ldr r5, [r2, #4]
 61     ldr r6, =ATAG_CORE
 62     cmp r5, r6
 63     bne 1f
 64 
 65 2:  ret lr              @ atag/dtb pointer is ok
 66 
 67 1:  mov r2, #0
 68     ret lr
 69 ENDPROC(__vet_atags)

注:即此处通过校验的条件是两个
1. [r2, #0] == ATAG_CORE_SIZE 或 ATAG_CORE_SIZE_EMPTY
2. [r2, #4] == ATAG_CORE

 

  • struct tag 的定义:
/arch/arm/include/uapi/asm/setup.h
struct tag {
	struct tag_header hdr;
	union {
		struct tag_core		core;
		struct tag_mem32	mem;
		struct tag_videotext	videotext;
		struct tag_ramdisk	ramdisk;
		struct tag_initrd	initrd;
		struct tag_serialnr	serialnr;
		struct tag_revision	revision;
		struct tag_videolfb	videolfb;
		struct tag_cmdline	cmdline;

		/*
		 * Acorn specific
		 */
		struct tag_acorn	acorn;

		/*
		 * DC21285 specific
		 */
		struct tag_memclk	memclk;
	} u;
};
  • struct tag_header 和 struct tag_core 的定义:
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE	0x00000000

struct tag_header {
	__u32 size;
	__u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE	0x54410001

struct tag_core {
	__u32 flags;		/* bit 0 = read-only */
	__u32 pagesize;
	__u32 rootdev;
};
  • 根据上文中的信息可知, ATAG_CORE 的 size = (2 + 3) * 4 = 20byte,其 tags = 0x54410001.
#define ATAG_CORE 0x54410001
#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
#define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)

注:执行完 __vet_atags 例程后, R5, R6 被修改。

8. 跳转到 __fixup_smp 标签处(默认会配置 CONFIG_SMP_ON_UP,含义是 允许在单处理器系统上引导 SMP 内核),以修正 ALT_SMP 为 ALT_UP.

/arch/arm/kernel/head.S

122 #ifdef CONFIG_SMP_ON_UP
123     bl  __fixup_smp
124 #endif
125 #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
126     bl  __fixup_pv_table
127 #endif
128     bl  __create_page_tables
129 
  • R3 = MIDR 的 Bits[19:16] Architecture codes. 若 R3 = 0xF 则继续执行,否则(MIDR 的 Bits[19:16] 不为 0xF,说明肯定是 non-SMP 处理器)跳转到 __fixup_smp_on_up 标签处。
    /arch/arm/kernel/head.S
503 
504 #ifdef CONFIG_SMP_ON_UP
505     __HEAD
506 __fixup_smp:
507     and r3, r9, #0x000f0000 @ architecture version
508     teq r3, #0x000f0000     @ CPU ID supported?
509     bne __fixup_smp_on_up   @ no, assume UP
  • R3 = R9 & 0xff00fff0,R4 = 0x4100b020,若 R3 == R4,说明 CPU 是 ARM11 MPCore,则无需额外的操作,直接返回原执行流;否则继续向下执行。
    /arch/arm/kernel/head.S
511     bic r3, r9, #0x00ff0000
512     bic r3, r3, #0x0000000f @ mask 0xff00fff0
513     mov r4, #0x41000000
514     orr r4, r4, #0x0000b000
515     orr r4, r4, #0x00000020 @ val 0x4100b020
516     teq r3, r4          @ ARM 11MPCore?
517     reteq   lr          @ yes, assume SMP
  • 读取 MPIDR(Multiprocessor Affinity Register) 到 R0 中,之后 R0 &= 0xC0000000,则 R0 = 0xC000,0000(U, bit[30] = 1) 或者 0x8000,0000(U, bit[30] = 0). 当 U, bit[30] = 1 时,直接返回原执行流(查阅 ARM 手册可知,此时是多处理器系统,确实无需额外的操作);否则(系统是单处理器系统)继续向下执行。
 /arch/arm/kernel/head.S
519     mrc p15, 0, r0, c0, c0, 5   @ read MPIDR
520     and r0, r0, #0xc0000000 @ multiprocessing extensions and
521     teq r0, #0x80000000     @ not part of a uniprocessor system?
522     bne    __fixup_smp_on_up    @ no, assume UP
523 

MPIDR 的位分配如下图所示

U, bit[30]:
Indicates a Uniprocessor system, as distinct from processor 0 in a multiprocessor system. The possible values of this bit are:
0 Processor is part of a multiprocessor system.
1 Processor is part of a uniprocessor system.
  • __fixup_smp_on_up 例程

1] 计算链接地址和运行地址之间的偏移量保存到 R3 中,之后用 R3 来修正 R4 和 R5,接下来跳转到 __do_fixup_smp_on_up 标签处。

/arch/arm/kernel/head.S
544 __fixup_smp_on_up:
545     adr r0, 1f
546     ldmia   r0, {r3 - r5}
547     sub r3, r0, r3
548     add r4, r4, r3            @ R4 = __smpalt_begin 的物理地址
549     add r5, r5, r3            @ R5 = __smpalt_end 的物理地址
550     b   __do_fixup_smp_on_up
551 ENDPROC(__fixup_smp)
  • "1f" 标签处的内容,
/arch/arm/kernel/head.S
552 
553     .align
554 1:  .word   .
555     .word   __smpalt_begin
556     .word   __smpalt_end
557 
558     .pushsection .data
559     .align  2
560     .globl  smp_on_up
561 smp_on_up:
562     ALT_SMP(.long   1)
563     ALT_UP(.long    0)
564     .popsection

注解:关于 ALT_SMP 和 ALT_UP, 这两个宏的定义如下所示

 


#ifdef CONFIG_SMP
#define ALT_SMP(instr...)					\
9998:	instr
/*
 * Note: if you get assembler errors from ALT_UP() when building with
 * CONFIG_THUMB2_KERNEL, you almost certainly need to use
 * ALT_SMP( W(instr) ... )
 */
#define ALT_UP(instr...)					\
	.pushsection ".alt.smp.init", "a"			;\
	.long	9998b						;\
9997:	instr							;\
	.if . - 9997b == 2					;\
		nop						;\
	.endif							;\
	.if . - 9997b != 4					;\
		.error "ALT_UP() content must assemble to exactly 4 bytes";\
	.endif							;\
	.popsection
#define ALT_UP_B(label)					\
	.equ	up_b_offset, label - 9998b			;\
	.pushsection ".alt.smp.init", "a"			;\
	.long	9998b						;\
	W(b)	. + up_b_offset					;\
	.popsection
#else
#define ALT_SMP(instr...)
#define ALT_UP(instr...) instr
#define ALT_UP_B(label) b label
#endif

可以看到,当配置了 CONFIG_SMP 时,ALT_UP 将 ALT_SMP 指令的地址和自己的指令放到了 ".alt.smp.init" 这个段中,而这个段则是以 __smpalt_begin 开始,以 __smpalt_end 结束。

/arch/arm/kernel/vmlinux.lds.S
111 #ifdef CONFIG_SMP_ON_UP
112     .init.smpalt : {
113         __smpalt_begin = .;
114         *(.alt.smp.init)
115         __smpalt_end = .;
116     }
117 #endif
118

2] __do_fixup_smp_on_up 例程,可以看到是循环读取从 R4 处开始向高地址的 8byte(即可能被取代的 ALT_SMP 指令的地址和 ALT_UP 指令) 的内容到寄存器 R0 和 R6 中,之后又将 R6 的内容存储到 [R0 + R3] 处,直到 R4 >= R5 后跳出循环,回到原先的执行流。

/arch/arm/kernel/head.S
568 __do_fixup_smp_on_up:
569     cmp r4, r5
570     reths   lr
571     ldmia   r4!, {r0, r6}
572  ARM(   str r6, [r0, r3]    )
573  THUMB( add r0, r0, r3  )
574 #ifdef __ARMEB__
575  THUMB( mov r6, r6, ror #16 )   @ Convert word order for big-endian.
576 #endif
577  THUMB( strh    r6, [r0], #2    )   @ For Thumb-2, store as two halfwords
578  THUMB( mov r6, r6, lsr #16 )   @ to be robust against misaligned r3.
579  THUMB( strh    r6, [r0]    )
580     b   __do_fixup_smp_on_up
581 ENDPROC(__do_fixup_smp_on_up)
582 

R0 = ALT_SMP 指令的链接地址;
R3 = 链接地址和运行地址之间的偏移量;
R6 = ALT_UP 指令;
则 [R0 + R3] = R6, 完成了运行时的 ALT_SMP 指令被替换为 ALT_UP 指令。
(更详细的解释,请参考 这篇文章 )

9. 跳转到 __fixup_pv_table 标签处。(默认会配置 CONFIG_ARM_PATCH_PHYS_VIRT,含义是 "Patch physical to virtual translations at runtime")

/arch/arm/kernel/head.S
125 #ifdef CONFIG_ARM_PATCH_PHYS_VIRT
126     bl  __fixup_pv_table
127 #endif
  • __fixup_pv_table 例程,修正 .init.pv_table 段中保存的指令的地址。
  1. 获得链接地址和运行地址之间的偏移量保存到 R3 中,使用该值来调整 R4, R5, R6, R7.
602 /* __fixup_pv_table - patch the stub instructions with the delta between
603  * PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and
604  * can be expressed by an immediate shifter operand. The stub instruction
605  * has a form of '(add|sub) rd, rn, #imm'.
606  */
607     __HEAD
608 __fixup_pv_table:
609     adr r0, 1f
610     ldmia   r0, {r3-r7}
611     mvn ip, #0
612     subs    r3, r0, r3  @ PHYS_OFFSET - PAGE_OFFSET
613     add r4, r4, r3  @ adjust table start address
614     add r5, r5, r3  @ adjust table end address
615     add r6, r6, r3  @ adjust __pv_phys_pfn_offset address
616     add r7, r7, r3  @ adjust __pv_offset address

1] "1f" 标签处的内容:

 /arch/arm/kernel/head.S
628     .align
629 1:  .long   .
630     .long   __pv_table_begin
631     .long   __pv_table_end

2] __pv_table_begin 和 __pv_table_end 的定义如下所示:

 /arch/arm/kernel/vmlinux.lds
 51  .init.pv_table : {
 52   __pv_table_begin = .;
 53   *(.pv_table)
 54   __pv_table_end = .;
 55  }

可以看到,它们是 ".init.pv_table" 段的起始地址和结束地址。

3] .pv_table 中存放的内容是什么呢?查找资料可知,该段中的内容是由 __pv_stub 这个宏所添加,其值是每一处调用了 __pv_stub 这个宏后被插入的 " "instr" %0, %1, %2\n" 这条指令的链接地址。
该宏的定义如下所示:

 /arch/arm/include/asm/memory.h


/*
 * Physical vs virtual RAM address space conversion.  These are
 * private definitions which should NOT be used outside memory.h
 * files.  Use virt_to_phys/phys_to_virt/__pa/__va instead.
 *
 * PFNs are used to describe any physical page; this means
 * PFN 0 == physical address 0.
 */


#if defined(CONFIG_ARM_PATCH_PHYS_VIRT)

/*
 * Constants used to force the right instruction encodings and shifts
 * so that all we need to do is modify the 8-bit constant field.
 */
#define __PV_BITS_31_24	0x81000000
#define __PV_BITS_7_0	0x81

extern unsigned long __pv_phys_pfn_offset;
extern u64 __pv_offset;
extern void fixup_pv_table(const void *, unsigned long);
extern const void *__pv_table_begin, *__pv_table_end;

#define PHYS_OFFSET	((phys_addr_t)__pv_phys_pfn_offset << PAGE_SHIFT)
#define PHYS_PFN_OFFSET	(__pv_phys_pfn_offset)

#define __pv_stub(from,to,instr,type)			\
	__asm__("@ __pv_stub\n"				\
	"1:	" instr "	%0, %1, %2\n"		\
	"	.pushsection .pv_table,\"a\"\n"		\
	"	.long	1b\n"				\
	"	.popsection\n"				\
	: "=r" (to)					\
	: "r" (from), "I" (type))

#define __pv_stub_mov_hi(t)				\
	__asm__ volatile("@ __pv_stub_mov\n"		\
	"1:	mov	%R0, %1\n"			\
	"	.pushsection .pv_table,\"a\"\n"		\
	"	.long	1b\n"				\
	"	.popsection\n"				\
	: "=r" (t)					\
	: "I" (__PV_BITS_7_0))

#define __pv_add_carry_stub(x, y)			\
	__asm__ volatile("@ __pv_add_carry_stub\n"	\
	"1:	adds	%Q0, %1, %2\n"			\
	"	adc	%R0, %R0, #0\n"			\
	"	.pushsection .pv_table,\"a\"\n"		\
	"	.long	1b\n"				\
	"	.popsection\n"				\
	: "+r" (y)					\
	: "r" (x), "I" (__PV_BITS_31_24)		\
	: "cc")

static inline phys_addr_t __virt_to_phys_nodebug(unsigned long x)
{
	phys_addr_t t;

	if (sizeof(phys_addr_t) == 4) {
		__pv_stub(x, t, "add", __PV_BITS_31_24);
	} else {
		__pv_stub_mov_hi(t);
		__pv_add_carry_stub(x, t);
	}
	return t;
}

static inline unsigned long __phys_to_virt(phys_addr_t x)
{
	unsigned long t;

	/*
	 * 'unsigned long' cast discard upper word when
	 * phys_addr_t is 64 bit, and makes sure that inline
	 * assembler expression receives 32 bit argument
	 * in place where 'r' 32 bit operand is expected.
	 */
	__pv_stub((unsigned long) x, t, "sub", __PV_BITS_31_24);
	return t;
}

#else

#define PHYS_OFFSET	PLAT_PHYS_OFFSET
#define PHYS_PFN_OFFSET	((unsigned long)(PHYS_OFFSET >> PAGE_SHIFT))

static inline phys_addr_t __virt_to_phys_nodebug(unsigned long x)
{
	return (phys_addr_t)x - PAGE_OFFSET + PHYS_OFFSET;
}

static inline unsigned long __phys_to_virt(phys_addr_t x)
{
	return x - PHYS_OFFSET + PAGE_OFFSET;
}

#endif

可以得知,实际上是当配置了 CONFIG_ARM_PATCH_PHYS_VIRT 时, 函数 __virt_to_phys() 和函数 __phys_to_virt() 通过使用 __pv_stub 这个宏来完成虚拟地址和物理地址之间的转换。
物理地址 = 虚拟地址 +(add) __PV_BITS_31_24;
物理地址 = 物理地址 -(sub) __PV_BITS_31_24;
__PV_BITS_31_24 被宏定义为 0x81000000, 作为假想的物理地址和虚拟地址之间的偏移量。

  • __pv_phys_offset([R7]) = PHYS_OFFSET(R8); 若 R3(运行地址 - 链接地址) 的低 24 位不为 0(即不是 16MB 对齐的),则跳转到 __error;否则继续向下执行
 /arch/arm/kernel/head.S
617     mov r0, r8, lsr #PAGE_SHIFT @ convert to PFN
618     str r0, [r6]    @ save computed PHYS_OFFSET to __pv_phys_pfn_offset
619     strcc   ip, [r7, #HIGH_OFFSET]  @ save to __pv_offset high bits
620     mov r6, r3, lsr #24 @ constant for add/sub instructions
621     teq r3, r6, lsl #24 @ must be 16MiB aligned
622 THUMB(  it  ne      @ cross section branch )
623     bne __error
  • [R7 + 4] = R3,然后跳转到 __fixup_a_pv_table 处。
 /arch/arm/kernel/head.S

624     str r3, [r7, #LOW_OFFSET]   @ save to __pv_offset low bits
625     b   __fixup_a_pv_table
626 ENDPROC(__fixup_pv_table)



#ifdef __ARMEB__
#define LOW_OFFSET	0x4
#define HIGH_OFFSET	0x0
#else
#define LOW_OFFSET	0x0
#define HIGH_OFFSET	0x4
#endif

1] R7 中保存的是 __pv_phys_offset 的物理地址,则此处 "__pv_phys_offset + 4" 保存的是 __pv_offset 的物理地址,如下所示:

File: /arch/arm/kernel/head.S
628     .align
629 1:  .long   .
630     .long   __pv_table_begin
631     .long   __pv_table_end
632 2:  .long   __pv_phys_pfn_offset
633     .long   __pv_offset
634 
  • __fixup_pv_table 例程(CONFIG_THUMB2_KERNEL和CONFIG_CPU_ENDIAN_BE8默认不配置)
635     .text
636 __fixup_a_pv_table:
637     adr r0, 3f
638     ldr r6, [r0]
639     add r6, r6, r3
640     ldr r0, [r6, #HIGH_OFFSET]  @ pv_offset high word
641     ldr r6, [r6, #LOW_OFFSET]   @ pv_offset low word
642     mov r6, r6, lsr #24
643     cmn r0, #1
644 #ifdef CONFIG_THUMB2_KERNEL
    ......
677 #else
678 #ifdef CONFIG_CPU_ENDIAN_BE8
    ......
680 #else
681     moveq   r0, #0x400000   @ set bit 22, mov to mvn instruction
682 #endif
683     b   2f
684 1:  ldr ip, [r7, r3]
685 #ifdef CONFIG_CPU_ENDIAN_BE8
    ......
677 #else
678 #ifdef CONFIG_CPU_ENDIAN_BE8
    ....
680 #else
681     moveq   r0, #0x400000   @ set bit 22, mov to mvn instruction
682 #endif
683     b   2f
684 1:  ldr ip, [r7, r3]
685 #ifdef CONFIG_CPU_ENDIAN_BE8
    ......
692 #else
693     bic ip, ip, #0x000000ff
694     tst ip, #0xf00  @ check the rotation field
695     orrne   ip, ip, r6  @ mask in offset bits 31-24
696     biceq   ip, ip, #0x400000   @ clear bit 22
697     orreq   ip, ip, r0  @ mask in offset bits 7-0
698 #endif
699     str ip, [r7, r3]
700 2:  cmp r4, r5
701     ldrcc   r7, [r4], #4    @ use branch for delay slot
702     bcc 1b
703     ret lr
704 #endif
705 ENDPROC(__fixup_a_pv_table)

先直接跳转到 "2f" 标签处,当 R4 < R5 时,循环进行 R7 = [R4],R4 += 4,跳转到 "1b" 标签处,将 [R7 + R3] 读取到 IP(Intra-Procedure-call scratch register, R12) 中,根据上文中对 __pv_stub 这个宏的分析,可以得知 IP = 被插入的那条指令的二进制形式
然后对 IP 进行低8位清零,接着 IP |= R6,最后将新得到的 IP 的值保存到 R7 + R3 处。
如下图所示(R6 = R3 的高 8bit):

 

10. 跳转到 __create_page_tables, 创建页表

/arch/arm/kernel/head.S
128     bl  __create_page_tables

具体的创建页表的代码,这里先不分析了,给出结论先。

跳转到 __create_page_tables 之前时寄存器的值如下所示:

  • r1 = machine no
  • r2 = atags pointer 或 dtb pointer
  • r8 = phys_offset
  • r9 = cpuid (即 MIDR 寄存器的值)
  • r10 = procinfo ( = R5,指向找到的 proc_info 结构体的指针)

执行完 __create_page_tables 之后寄存器的状态:

  • r0, r3, r5-r7 corrupted
  • r4 = physical page table address

 

 

11. 为开启 MMU 做一些准备工作,之后跳转到 __enable_mmu 例程。

  • 加载 __mmap_switched 标签的地址(链接时的地址,这个地址是 MMU 使能后第一个跳转到的 虚拟地址)到 R13(SP) 中;将标签 "1f" 处(即 line-142)的地址加载到 LR(R14) 中,保存了返回地址;复制 R4(页表的起始地址) 到 R8 中。

 

130     /*
131      * The following calls CPU specific code in a position independent
132      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
133      * xxx_proc_info structure selected by __lookup_processor_type
134      * above.
135      *
136      * The processor init function will be called with:
137      *  r1 - machine type
138      *  r2 - boot data (atags/dt) pointer
139      *  r4 - translation table base (low word)
140      *  r5 - translation table base (high word, if LPAE)
141      *  r8 - translation table base 1 (pfn if LPAE)
142      *  r9 - cpuid
143      *  r13 - virtual address for __enable_mmu -> __turn_mmu_on
144      *
145      * On return, the CPU will be ready for the MMU to be turned on,
146      * r0 will hold the CPU control register value, r1, r2, r4, and
147      * r9 will be preserved.  r5 will also be preserved if LPAE.
148      */
149     ldr r13, =__mmap_switched       @ address to jump to after
150                         @ mmu has been enabled
151     badr    lr, 1f              @ return (PIC) address
152 #ifdef CONFIG_ARM_LPAE    /* 默认没配置 */
153     mov r5, #0              @ high TTBR0
154     mov r8, r4, lsr #12         @ TTBR1 is swapper_pg_dir pfn
155 #else
156     mov r8, r4              @ set TTBR1 to swapper_pg_dir
157 #endif

注:

  • __mmap_switched 例程的详解见后文。
  • r12 指向 proc_info_list->__cpu_flush, 这使得执行相对应的处理器初始化函数,然后返回到到 __enable_mmu 例程。
149     ldr r13, =__mmap_switched       @ address to jump to after
150                         @ mmu has been enabled
151     badr    lr, 1f              @ return (PIC) address
152 #ifdef CONFIG_ARM_LPAE
153     mov r5, #0              @ high TTBR0
154     mov r8, r4, lsr #12         @ TTBR1 is swapper_pg_dir pfn
155 #else
156     mov r8, r4              @ set TTBR1 to swapper_pg_dir
157 #endif
158     ldr r12, [r10, #PROCINFO_INITFUNC]
159     add r12, r12, r10
160     ret r12
161 1:  b   __enable_mmu
162 ENDPROC(stext)

R10: 指向找到的 proc_info 结构体的指针

注: PROCINFO_INITFUNC 的定义如下,即 proc_info_list 结构体中 __cpu_flush 这个成员的偏移量。

/include/generated/asm-offsets.h
#define PROCINFO_INITFUNC 16 /* offsetof(struct proc_info_list, __cpu_flush)    @ */
  • 处理器的初始化,此处是通过上文中所述的修改 PC 来完成。
     以 Cortex-A8 处理器为例,它的 proc_list 结构体如下所示:
/arch/arm/mm/proc-v7.S
	/*
	 * ARM Ltd. Cortex A8 processor.
	 */
	.type	__v7_ca8_proc_info, #object
__v7_ca8_proc_info:
	.long	0x410fc080
	.long	0xff0ffff0
	__v7_proc __v7_ca8_proc_info, __v7_setup, proc_fns = ca8_processor_functions
	.size	__v7_ca8_proc_info, . - __v7_ca8_proc_info

其中 __v7_proc 是个宏,定义如下:

 /arch/arm/mm/proc-v7.S
	/*
	 * Standard v7 proc info content
	 */
.macro __v7_proc name, initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions, cache_fns = v7_cache_fns
	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
	initfn	\initfunc, \name
	.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	\proc_fns
	.long	v7wbi_tlb_fns
	.long	v6_user_fns
	.long	\cache_fns
.endm

所以R12 实际指向的指是上述宏中的 "initfunc", 即 __v7_ca8mp_setup

 

__v7_setup:

  • R10 = 1, 跳转到 "1f" 标签处。
/arch/arm/mm/proc-v7.S
/*
 *	__v7_setup
 *
 *	Initialise TLB, Caches, and MMU state ready to switch the MMU
 *	on.  Return in r0 the new CP15 C1 control register setting.
 *
 *	r1, r2, r4, r5, r9, r13 must be preserved - r13 is not a stack
 *	r4: TTBR0 (low word)
 *	r5: TTBR0 (high word if LPAE)
 *	r8: TTBR1
 *	r9: Main ID register
 *
 *	This should be able to cover all ARMv7 cores.
 *
 *	It is assumed that:
 *	- cache type register is implemented
 */
.....

__v7_setup:
	adr	r0, __v7_setup_stack_ptr
	ldr	r12, [r0]
	add	r12, r12, r0			@ the local stack
	stmia	r12, {r1-r6, lr}		@ v7_invalidate_l1 touches r0-r6
	bl      v7_invalidate_l1
	ldmia	r12, {r1-r6, lr}

 __v7_setup

  • 加载 __v7_setup_stack_ptr 的地址到 R0 中,然后在 R0 所指向区域保存如下所示的 7 个寄存器的值,之后调用v7_invalidate_l1,再之后还原那7个寄存器的值。
  • 这里是先把r12指到保留空间的高地址,再保存7个寄存器,后面在恢复。因为这里类似C语言调用函数,进函数前要把用到的寄存器压栈。
/arch/arm/mm/proc-v7.S
	.align	2
__v7_setup_stack_ptr:
	.word	PHYS_RELATIVE(__v7_setup_stack, .)

	.bss
	.align	2
__v7_setup_stack:
	.space	4 * 7				@ 7 registers



#define PHYS_RELATIVE(v_data, v_text) ((v_data) - (v_text))

 

执行函数调用

/arch/arm/mm/proc-v7.S
	stmia	r12, {r1-r6, lr}		@ v7_invalidate_l1 touches r0-r6
	bl      v7_invalidate_l1
	ldmia	r12, {r1-r6, lr}

注释也说了,不想在初始化无效,decache的时候一堆无效数据刷进内存,所以自己把dacache。搞干净。

/arch/arm/mm/proc-v7.S
/*
 * The secondary kernel init calls v7_flush_dcache_all before it enables
 * the L1; however, the L1 comes out of reset in an undefined state, so
 * the clean + invalidate performed by v7_flush_dcache_all causes a bunch
 * of cache lines with uninitialized data and uninitialized tags to get
 * written out to memory, which does really unpleasant things to the main
 * processor.  We fix this by performing an invalidate, rather than a
 * clean + invalidate, before jumping into the kernel.
 *
 * This function is cloned from arch/arm/mach-tegra/headsmp.S, and needs
 * to be called for both secondary cores startup and primary core resume
 * procedures.
 */
ENTRY(v7_invalidate_l1)
       mov     r0, #0
       mcr     p15, 2, r0, c0, c0, 0
       mrc     p15, 1, r0, c0, c0, 0

       movw    r1, #0x7fff
       and     r2, r1, r0, lsr #13

       movw    r1, #0x3ff

       and     r3, r1, r0, lsr #3      @ NumWays - 1
       add     r2, r2, #1              @ NumSets

       and     r0, r0, #0x7
       add     r0, r0, #4      @ SetShift

       clz     r1, r3          @ WayShift
       add     r4, r3, #1      @ NumWays
1:     sub     r2, r2, #1      @ NumSets--
       mov     r3, r4          @ Temp = NumWays
2:     subs    r3, r3, #1      @ Temp--
       mov     r5, r3, lsl r1
       mov     r6, r2, lsl r0
       orr     r5, r5, r6      @ Reg = (Temp<<WayShift)|(NumSets<<SetShift)
       mcr     p15, 0, r5, c7, c6, 2
       bgt     2b
       cmp     r2, #0
       bgt     1b
       dsb     st
       isb
       ret     lr
ENDPROC(v7_invalidate_l1)

上面函数这个模块掉用完,返回后就是根据不同的处理器类型问题之类。读取 SCTLR(System Control Register) 到 R0 中。最后返回的head.S中


__v7_setup_cont:
	and	r0, r9, #0xff000000		@ ARM?
	teq	r0, #0x41000000
	bne	__errata_finish
	and	r3, r9, #0x00f00000		@ variant
	and	r6, r9, #0x0000000f		@ revision
	orr	r6, r6, r3, lsr #20-4		@ combine variant and revision
	ubfx	r0, r9, #4, #12			@ primary part number

	/* Cortex-A8 Errata */
	ldr	r10, =0x00000c08		@ Cortex-A8 primary part number
	teq	r0, r10
	beq	__ca8_errata

	/* Cortex-A9 Errata */
	ldr	r10, =0x00000c09		@ Cortex-A9 primary part number
	teq	r0, r10
	beq	__ca9_errata

	/* Cortex-A12 Errata */
	ldr	r10, =0x00000c0d		@ Cortex-A12 primary part number
	teq	r0, r10
	beq	__ca12_errata

	/* Cortex-A17 Errata */
	ldr	r10, =0x00000c0e		@ Cortex-A17 primary part number
	teq	r0, r10
	beq	__ca17_errata

	/* Cortex-A15 Errata */
	ldr	r10, =0x00000c0f		@ Cortex-A15 primary part number
	teq	r0, r10
	beq	__ca15_errata

__errata_finish:
	mov	r10, #0
	mcr	p15, 0, r10, c7, c5, 0		@ I+BTB cache invalidate
#ifdef CONFIG_MMU
	mcr	p15, 0, r10, c8, c7, 0		@ invalidate I + D TLBs
	v7_ttb_setup r10, r4, r5, r8, r3	@ TTBCR, TTBRx setup
	ldr	r3, =PRRR			@ PRRR
	ldr	r6, =NMRR			@ NMRR
	mcr	p15, 0, r3, c10, c2, 0		@ write PRRR
	mcr	p15, 0, r6, c10, c2, 1		@ write NMRR
#endif
	dsb					@ Complete invalidations
#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	r3, #0
	mcr	p14, 6, r3, 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	r3, v7_crval
	ldmia	r3, {r3, r6}
 ARM_BE8(orr	r6, r6, #1 << 25)		@ big-endian page tables
#ifdef CONFIG_SWP_EMULATE
	orr     r3, r3, #(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, r3			@ clear bits them
	orr	r0, r0, r6			@ set them
 THUMB(	orr	r0, r0, #1 << 30	)	@ Thumb exceptions
	ret	lr				@ return to head.S:__ret

12. __enable_mmu 例程,开启 MMU, 然后跳转到 __mmap_switched 处。

  • 根据配置,修改 R0 的值(R0 中保存着 SCTLR 的值)。
/arch/arm/kernel/head.S
435 /*
436  * Setup common bits before finally enabling the MMU.  Essentially
437  * this is just loading the page table pointer and domain access
438  * registers.  All these registers need to be preserved by the
439  * processor setup function (or set in the case of r0)
440  *
441  *  r0  = cp#15 control register
442  *  r1  = machine ID
443  *  r2  = atags or dtb pointer
444  *  r4  = TTBR pointer (low word)
445  *  r5  = TTBR pointer (high word if LPAE)
446  *  r9  = processor ID
447  *  r13 = *virtual* address to jump to upon completion
448  */
449 __enable_mmu:
450 #if defined(CONFIG_ALIGNMENT_TRAP) && __LINUX_ARM_ARCH__ < 6
451     orr r0, r0, #CR_A
452 #else
453     bic r0, r0, #CR_A
454 #endif
455 #ifdef CONFIG_CPU_DCACHE_DISABLE
456     bic r0, r0, #CR_C
457 #endif
458 #ifdef CONFIG_CPU_BPREDICT_DISABLE
459     bic r0, r0, #CR_Z
460 #endif
461 #ifdef CONFIG_CPU_ICACHE_DISABLE
462     bic r0, r0, #CR_I
463 #endif
464 #ifdef CONFIG_ARM_LPAE
465     mcrr    p15, 0, r4, r5, c2      @ load TTBR0
466 #else
467     mov r5, #DACR_INIT
468     mcr p15, 0, r5, c3, c0, 0       @ load domain access register
469     mcr p15, 0, r4, c2, c0, 0       @ load page table pointer
470 #endif
471     b   __turn_mmu_on
472 ENDPROC(__enable_mmu)

 CR_? 定义如下:


/*
 * CR1 bits (CP#15 CR1)
 */
#define CR_M	(1 << 0)	/* MMU enable				*/
#define CR_A	(1 << 1)	/* Alignment abort enable		*/
#define CR_C	(1 << 2)	/* Dcache enable			*/
#define CR_W	(1 << 3)	/* Write buffer enable			*/
#define CR_P	(1 << 4)	/* 32-bit exception handler		*/
#define CR_D	(1 << 5)	/* 32-bit data address range		*/
#define CR_L	(1 << 6)	/* Implementation defined		*/
#define CR_B	(1 << 7)	/* Big endian				*/
#define CR_S	(1 << 8)	/* System MMU protection		*/
#define CR_R	(1 << 9)	/* ROM MMU protection			*/
#define CR_F	(1 << 10)	/* Implementation defined		*/
#define CR_Z	(1 << 11)	/* Implementation defined		*/
#define CR_I	(1 << 12)	/* Icache enable			*/
#define CR_V	(1 << 13)	/* Vectors relocated to 0xffff0000	*/
#define CR_RR	(1 << 14)	/* Round Robin cache replacement	*/
#define CR_L4	(1 << 15)	/* LDR pc can set T bit			*/
#define CR_DT	(1 << 16)
#ifdef CONFIG_MMU
#define CR_HA	(1 << 17)	/* Hardware management of Access Flag   */
#else
#define CR_BR	(1 << 17)	/* MPU Background region enable (PMSA)  */
#endif
#define CR_IT	(1 << 18)
#define CR_ST	(1 << 19)
#define CR_FI	(1 << 21)	/* Fast interrupt (lower latency mode)	*/
#define CR_U	(1 << 22)	/* Unaligned access operation		*/
#define CR_XP	(1 << 23)	/* Extended page tables			*/
#define CR_VE	(1 << 24)	/* Vectored interrupts			*/
#define CR_EE	(1 << 25)	/* Exception (Big) Endian		*/
#define CR_TRE	(1 << 28)	/* TEX remap enable			*/
#define CR_AFE	(1 << 29)	/* Access flag enable			*/
#define CR_TE	(1 << 30)	/* Thumb exception enable		*/

 更多关于 SCTLR 的信息,参考 ARM 手册 

  • 设置位域权限,先存放到 R5( = DACR_INIT) 中,之后保存到 DACR(Domain Access Control Register,) 中;加载页表起始地址(保存在 R4 中)到 TTBR0(Translation Table Base Register 0);跳转到 __turn_mmu_on 处。

DACR_INIT的定义如下:

#define domain_val(dom,type)	((type) << (2 * (dom)))

#define DACR_INIT \
	(domain_val(DOMAIN_USER, DOMAIN_NOACCESS) | \
	 domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
	 domain_val(DOMAIN_IO, DOMAIN_CLIENT) | \
	 domain_val(DOMAIN_VECTORS, DOMAIN_CLIENT))

 DOMAIN_? 这些常数的定义如下所示:

 

#ifndef CONFIG_IO_36                /* 默认没定义 */
#define DOMAIN_KERNEL	0
#define DOMAIN_USER	1
#define DOMAIN_IO	2
#else

#define DOMAIN_KERNEL	2
#define DOMAIN_USER	1
#define DOMAIN_IO	0
#endif
#define DOMAIN_VECTORS	3

/*
 * Domain types
 */
#define DOMAIN_NOACCESS	0
#define DOMAIN_CLIENT	1
#ifdef CONFIG_CPU_USE_DOMAINS    /* 没定义 */
#define DOMAIN_MANAGER	3
#else
#define DOMAIN_MANAGER	1
#endif
  • __turn_mmu_on 例程

1] nop; 刷新处理器的流水线;加载 R0 中的内容到 SCTLR 中,完成了开启 MMU 的工作;读取 MIDR(Main ID Registe) 到 R3 中; 刷新处理器的流水线。

/arch/arm/kernel/head.S
474 /*
475  * Enable the MMU.  This completely changes the structure of the visible
476  * memory space.  You will not be able to trace execution through this.
477  * If you have an enquiry about this, *please* check the linux-arm-kernel
478  * mailing list archives BEFORE sending another post to the list.
479  *
480  *  r0  = cp#15 control register
481  *  r1  = machine ID
482  *  r2  = atags or dtb pointer
483  *  r9  = processor ID
484  *  r13 = *virtual* address to jump to upon completion
485  *
486  * other registers depend on the function called upon completion
487  */
488     .align  5
489     .pushsection    .idmap.text, "ax"
490 ENTRY(__turn_mmu_on)
491     mov r0, r0
492     instr_sync
493     mcr p15, 0, r0, c1, c0, 0       @ write control reg
494     mrc p15, 0, r3, c0, c0, 0       @ read id reg
495     instr_sync
496     mov r3, r3                      //这句是干啥?? 
497     mov r3, r13
498     ret r3
499 __turn_mmu_on_end:
500 ENDPROC(__turn_mmu_on)

2] R3 = R13(__mmap_switched 标签的地址,运行的第一个虚拟地址);PC = R3, 完成跳转到 __mmap_switched 处。

  • instr_sync 的宏定义如下所示:
/*
 * Instruction barrier
 */
	.macro	instr_sync
#if __LINUX_ARM_ARCH__ >= 7
	isb
#elif __LINUX_ARM_ARCH__ == 6
	mcr	p15, 0, r0, c7, c5, 4
#endif
	.endm

ISB之类,arm官方给的是一个同步屏障用的。

 

13. __mmap_switched 例程

 

先看在__mmap_switched 切换前地址保存在r13寄存器,先看怎么进去的。

 

	/*
	 * 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.
	 *
	 * The processor init function will be called with:
	 *  r1 - machine type
	 *  r2 - boot data (atags/dt) pointer
	 *  r4 - translation table base (low word)
	 *  r5 - translation table base (high word, if LPAE)
	 *  r8 - translation table base 1 (pfn if LPAE)
	 *  r9 - cpuid
	 *  r13 - virtual address for __enable_mmu -> __turn_mmu_on
	 *
	 * On return, the CPU will be ready for the MMU to be turned on,
	 * r0 will hold the CPU control register value, r1, r2, r4, and
	 * r9 will be preserved.  r5 will also be preserved if LPAE.
	 */
	ldr	r13, =__mmap_switched		@ address to jump to after
						@ mmu has been enabled
	badr	lr, 1f				@ return (PIC) address
#ifdef CONFIG_ARM_LPAE
	mov	r5, #0				@ high TTBR0
	mov	r8, r4, lsr #12			@ TTBR1 is swapper_pg_dir pfn
#else
	mov	r8, r4				@ set TTBR1 to swapper_pg_dir
#endif
	ldr	r12, [r10, #PROCINFO_INITFUNC]
	add	r12, r12, r10
	ret	r12
1:	b	__enable_mmu
ENDPROC(stext)





/*
 * Setup common bits before finally enabling the MMU.  Essentially
 * this is just loading the page table pointer and domain access
 * registers.  All these registers need to be preserved by the
 * processor setup function (or set in the case of r0)
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags or dtb pointer
 *  r4  = TTBR pointer (low word)
 *  r5  = TTBR pointer (high word if LPAE)
 *  r9  = processor ID
 *  r13 = *virtual* address to jump to upon completion
 */
__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
	mcrr	p15, 0, r4, r5, c2		@ load TTBR0
#else
	mov	r5, #DACR_INIT
	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




/*
 * Enable the MMU.  This completely changes the structure of the visible
 * memory space.  You will not be able to trace execution through this.
 * If you have an enquiry about this, *please* check the linux-arm-kernel
 * mailing list archives BEFORE sending another post to the list.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags or dtb pointer
 *  r9  = processor ID
 *  r13 = *virtual* address to jump to upon completion
 *
 * other registers depend on the function called upon completion
 */
	.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
	ret	r3
__turn_mmu_on_end:
ENDPROC(__turn_mmu_on)
	.popsection

可以看到在最开始把__mmap_switched       放在r13后就再没动过,之后   ,刚好在后面的两句使用进行了跳转

	mov	r3, r13
	ret	r3

同时要说明的是,从入口开始,r1和r2寄存器从未使用该。在__v7_setup的最后面,把cp#15 control register给到了r0.

 

先列出这里的所有代码


/*
 * The following fragment of code is executed with the MMU on in MMU mode,
 * and uses absolute addresses; this is not position independent.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags/dtb pointer
 *  r9  = processor ID
 */
	__INIT
__mmap_switched:

	mov	r7, r1
	mov	r8, r2
	mov	r10, r0

	adr	r4, __mmap_switched_data
	mov	fp, #0

#if defined(CONFIG_XIP_DEFLATED_DATA)
   ARM(	ldr	sp, [r4], #4 )
 THUMB(	ldr	sp, [r4] )
 THUMB(	add	r4, #4 )
	bl	__inflate_kernel_data		@ decompress .data to RAM
	teq	r0, #0
	bne	__error
#elif defined(CONFIG_XIP_KERNEL)
   ARM(	ldmia	r4!, {r0, r1, r2, sp} )
 THUMB(	ldmia	r4!, {r0, r1, r2, r3} )
 THUMB(	mov	sp, r3 )
	sub	r2, r2, r1
	bl	memcpy				@ copy .data to RAM
#endif

   ARM(	ldmia	r4!, {r0, r1, sp} )
 THUMB(	ldmia	r4!, {r0, r1, r3} )
 THUMB(	mov	sp, r3 )
	sub	r2, r1, r0
	mov	r1, #0
	bl	memset				@ clear .bss

	ldmia	r4, {r0, r1, r2, r3}
	str	r9, [r0]			@ Save processor ID
	str	r7, [r1]			@ Save machine type
	str	r8, [r2]			@ Save atags pointer
	cmp	r3, #0
	strne	r10, [r3]			@ Save control register values
	mov	lr, #0
	b	start_kernel
ENDPROC(__mmap_switched)

 

 

 

 

  • 先把r0,r1,r2保存下来,R4 = __mmap_switched_data 标签的地址
/*
 * The following fragment of code is executed with the MMU on in MMU mode,
 * and uses absolute addresses; this is not position independent.
 *
 *  r0  = cp#15 control register
 *  r1  = machine ID
 *  r2  = atags/dtb pointer
 *  r9  = processor ID
 */
	__INIT
__mmap_switched:

	mov	r7, r1
	mov	r8, r2
	mov	r10, r0

	adr	r4, __mmap_switched_data
	mov	fp, #0
  • __mmap_switched_data 标签处的内容如下所示:

	.align	2
	.type	__mmap_switched_data, %object
__mmap_switched_data:
#ifdef CONFIG_XIP_KERNEL
    ...  //没定义,里面内容不看
#endif

	.long	__bss_start			@ r0
	.long	__bss_stop			@ r1
	.long	init_thread_union + THREAD_START_SP @ sp

	.long	processor_id			@ r0
	.long	__machine_arch_type		@ r1
	.long	__atags_pointer			@ r2
#ifdef CONFIG_CPU_CP15
	.long	cr_alignment			@ r3
#else
	.long	0				@ r3
#endif
	.size	__mmap_switched_data, . - __mmap_switched_data

可以看到,这里就是用汇编语言定义的一些变量。只要在C语言文件中声明这些变量,即可使用。

其中__bss_start和__bss_stop            都是在连接脚本定义的。

 

 

初始化bss段。

	mov	fp, #0

#if defined(CONFIG_XIP_DEFLATED_DATA)
     ...    //没定义
#elif defined(CONFIG_XIP_KERNEL)
     ...    //没定义
#endif

   ARM(	ldmia	r4!, {r0, r1, sp} )        
 THUMB(	ldmia	r4!, {r0, r1, r3} )
 THUMB(	mov	sp, r3 )
	sub	r2, r1, r0
	mov	r1, #0
	bl	memset				@ clear .bss

对照上面很容易看到。

r0 = __bss_start //BSS段的起始地址 
r1 = __bss_end   //bss段的结束地址

r2 = r1 - r0     //即bss段的长度

 

最后调用memset函数把bss段清0.传参分贝为,r0 = __bss_start ,r1 = 0,r2 = __bss_end  - __bss_start

void *memset(void *addr, int c, size_t len)

 

接下来继续分析:

//把r4地址开始的连续三个地址的值分别放入r0,r1,sp后r4自动增加, 
之后R4将指向__mmap_switched_data标签开始的processor_id变量地址
  ARM(	ldmia	r4!, {r0, r1, sp} )    
 THUMB(	ldmia	r4!, {r0, r1, r3} )
 THUMB(	mov	sp, r3 )
	sub	r2, r1, r0        
	mov	r1, #0
	bl	memset				@ clear .bss

//加载从processor开始的第个long长度的分别到r0,r1,r2,r3
	ldmia	r4, {r0, r1, r2, r3}    
//把__mmap_switched开始时r1,r2保存在r7,r8寄存器中uboot传的参数,放到几个全局变量中
	str	r9, [r0]			@ Save processor ID
	str	r7, [r1]			@ Save machine type
	str	r8, [r2]			@ Save atags pointer
//r3是在__mmap_switched_data表的最后一个cr_alignment,默认是一个地址的标签,所以不是0	
	cmp	r3, #0
	strne	r10, [r3]			@ Save control register values 这里把这个控制寄存器的值保存在这个四字节空间
	mov	lr, #0               //这里lr返回地址清0,表示start_kernel不会返回,返回肯定出错来,则从0地址开始复位。
	b	start_kernel        //最后跳转到C语言函数入口


/* 下标放的值分别是几个全局变量的地址 */
__mmap_switched_data:
	.long	__bss_start			@ r0
	.long	__bss_stop			@ r1
	.long	init_thread_union + THREAD_START_SP @ sp

	.long	processor_id			@ r0
	.long	__machine_arch_type		@ r1
	.long	__atags_pointer			@ r2  dtb or tag in ram address
#ifdef CONFIG_CPU_CP15           //默认定义的
	.long	cr_alignment			@ r3
#else
	.long	0				@ r3
#endif
	.size	__mmap_switched_data, . - __mmap_switched_data

 

cr_alignment为保留在中断向量表下面的一个4字节空间

三者分别如下定义


arch/arm/kernel/setup.h

88 unsigned int processor_id;

90 unsigned int __machine_arch_type __read_mostly;

95 unsigned int __atags_pointer __initdata;

 

后面C语言阶段会对这几个参数进行分析,解析uboot对nei'内核的传参等等

 

Reference

https://blog.csdn.net/luckyapple1028/article/details/45287617

https://www.jianshu.com/p/a9804054eaea

https://www.jianshu.com/p/e3a7154c90e8

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页