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

10 篇文章 0 订阅

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值