linux/arch/arm/kernel/head.S

/*
 *  linux/arch/arm/kernel/head.S
 *
 *  Copyright (C) 1994-2002 Russell King
 *  Copyright (c) 2003 ARM Limited
 *  All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Kernel startup code for all 32-bit CPUs
 */
#include <linux/linkage.h>
#include <linux/init.h>

#include <asm/assembler.h>
#include <asm/domain.h>
#include <asm/ptrace.h>
#include <asm/asm-offsets.h>
#include <asm/memory.h>
#include <asm/thread_info.h>
#include <asm/system.h>

#if (PHYS_OFFSET & 0x001fffff)
#error "PHYS_OFFSET must be at an even 2MiB boundary!"
#endif

#define KERNEL_RAM_VADDR	(PAGE_OFFSET + TEXT_OFFSET)
#define KERNEL_RAM_PADDR	(PHYS_OFFSET + TEXT_OFFSET)

#define ATAG_CORE 0x54410001
#define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)


/*
 * swapper_pg_dir is the virtual address of the initial page table.
 * We place the page tables 16K below KERNEL_RAM_VADDR.  Therefore, we must
 * make sure that KERNEL_RAM_VADDR is correctly set.  Currently, we expect
 * the least significant 16 bits to be 0x8000, but we could probably
 * relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
 */
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif

	.globl	swapper_pg_dir
	.equ	swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000

	.macro	pgtbl, rd
	ldr	\rd, =(KERNEL_RAM_PADDR - 0x4000)
	.endm

#ifdef CONFIG_XIP_KERNEL
#define KERNEL_START	XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)
#define KERNEL_END	_edata_loc
#else
#define KERNEL_START	KERNEL_RAM_VADDR
#define KERNEL_END	_end
#endif


我们先对几个重要的宏进行说明(我们针对有MMU的情况):

宏 位置 默认值 说明
KERNEL_RAM_ADDR :0xc0008000 :kernel在RAM中的的虚拟地址
PAGE_OFFSET:0xc0000000 :内核空间的起始虚拟地址
TEXT_OFFSET :0x00008000 :内核相对于存储空间的偏移
TEXTADDR :0xc0008000 :kernel的起始虚拟地址
PHYS_OFFSET : 平台相关 :RAM的起始物理地址
这些定义在创建业表的时候用到。

/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * This is normally called from the decompressor code.  The requirements
 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 * r1 = machine nr, r2 = atags pointer.
 *
 * This code is mostly position independent, so if you link the kernel at
 * 0xc0008000, you call this at __pa(0xc0008000).
 *
 * See linux/arch/arm/tools/mach-types for the complete list of machine
 * numbers for r1.
 *
 * We're trying to keep crap to a minimum; DO NOT add any machine specific
 * crap here - that's what the boot loader (or in extreme, well justified
 * circumstances, zImage) is for.
 */

注释部分的说明:

通常从系统上电到执行到linux kenel这部分的任务是由boot loader来完成,boot loader在传递给内核的参数之后必须要保证如下条件

1. CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;
2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;
3. 数据cache(Data cache)必须是关闭的
4. 指令cache(Instruction cache)可以是打开的,也可以是关闭的,这个没有强制要求;
5. CPU 通用寄存器0 (r0)必须是 0;
6. CPU 通用寄存器1 (r1)必须是 ARM Linux machine type (关于machine type, 我们后面会有讲解)
7. CPU 通用寄存器2 (r2) 必须是 kernel parameter list 的物理地址(parameter list 是由boot loader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).

.section ".text.head", "ax"
	.type	stext, %function
ENTRY(stext)
	msr	cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ 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)?
	beq	__error_p			@ yes, error 'p'
	bl	__lookup_machine_type		@ r5=machinfo
	movs	r8, r5				@ invalid machine (r5=0)?
	beq	__error_a			@ yes, error 'a'
	bl	__vet_atags
	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_machine_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, __switch_data		@ address to jump to after
						@ mmu has been enabled
	adr	lr, __enable_mmu		@ return (PIC) address
	add	pc, r10, #PROCINFO_INITFUNC

内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的。

这段代码做了如下的工作:

确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.
确定 processor type

确定 machine type                 

创建页表                    
 调用平台特定的__cpu_flush函数        (在struct proc_info_list中)                          
开启mmu                 

切换数据                 

最终跳转到start_kernel                 (在__switch_data的结束的时候,调用了 b start_kernel)

按照这个主线,逐步的分析Code

1.确定 processor type

通过cp15协处理器的c0寄存器来获得processor id的指令. 关于cp15的详细内容可参考相关的arm手册
跳转到__lookup_processor_type.在__lookup_processor_type中,会把processor type 存储在r5中
判断r5中的processor type是否是0,如果是0,说明是无效的processor type,跳转到__error_p(出错)

__lookup_processor_type 函数主要是根据从cpu中获得的processor id和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中, 0表示没有找到对应的processor type.

/*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_info lists since we aren't running with the MMU on
 * (and therefore, we are not in the correct address space).  We have to
 * calculate the offset.
 *
 *	r9 = cpuid
 * Returns:
 *	r3, r4, r6 corrupted
 *	r5 = proc_info pointer in physical address space
 *	r9 = cpuid (preserved)
 */
	.type	__lookup_processor_type, %function
__lookup_processor_type:
	adr	r3, 3f
	ldmda	r3, {r5 - r7}
	sub	r3, r3, r7			@ 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

/*
 * This provides a C-API version of the above function.
 */
ENTRY(lookup_processor_type)
	stmfd	sp!, {r4 - r7, r9, lr}
	mov	r9, r0
	bl	__lookup_processor_type
	mov	r0, r5
	ldmfd	sp!, {r4 - r7, r9, pc}

/*
 * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
 * more information about the __proc_info and __arch_info structures.
 */
	.long	__proc_info_begin
	.long	__proc_info_end
3:	.long	.
	.long	__arch_info_begin
	.long	__arch_info_end/*
 * Lookup machine architecture in the linker-build list of architectures.
 * Note that we can't use the absolute addresses for the __arch_info
 * lists since we aren't running with the MMU on (and therefore, we are
 * not in the correct address space).  We have to calculate the offset.
 *
 *  r1 = machine architecture number
 * Returns:
 *  r3, r4, r6 corrupted
 *  r5 = mach_info pointer in physical address space
 */
 .type __lookup_machine_type, %function
__lookup_machine_type:
 adr r3, 3b
 ldmia r3, {r4, r5, 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: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
 teq r3, r1    @ matches loader number?
 beq 2f    @ found
 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
 cmp r5, r6
 blo 1b
 mov r5, #0    @ unknown machine
2: mov pc, lr
/*
 * This provides a C-API version of the above function.
 */
ENTRY(lookup_machine_type)
 stmfd sp!, {r4 - r6, lr}
 mov r1, r0
 bl __lookup_machine_type
 mov r0, r5
 ldmfd sp!, {r4 - r6, pc}


adr r3, 3f:取地址指令,这里的3f是向前symbol名称是3的位置,将地址放入r3.这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).

ldmda r3, {r5 - r7}执行后,r5存的是符号 __proc_info_begin的地址; 
        r6存的是符号 __proc_info_end的地址; 
        r7存的是3:处的地址.
        这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址).

__proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中: 
        __proc_info_begin = .; 
        *(.proc.info.init) 
         __proc_info_end = .;

        这里是声明了两个变量:__proc_info_begin 和 __proc_info_end,其中等号后面的"."是location counter(详细内容请参考ld.info)
        这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的 ".proc.info.init" 段的内容,然后紧接着是 __proc_info_end 的位置.

kernel 使用struct proc_info_list来描述processor type.
         在 include/asm-arm/procinfo.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; 
         };
        
        我们当前以at91为例,其processor是926的.
                在arch/arm/mm/proc-arm926.S 中: 
        .section ".proc.info.init", #alloc, #execinstr 
         .type __arm926_proc_info,#object 
        __arm926_proc_info: 
         .long 0x41069260 @ ARM926EJ-S (v5TEJ) 
        .long 0xff0ffff0 
          .long   PMD_TYPE_SECT | \ 
         PMD_SECT_BUFFERABLE | \ 
         PMD_SECT_CACHEABLE | \ 
         PMD_BIT4 | \ 
        PMD_SECT_AP_WRITE | \ 
         PMD_SECT_AP_READ 
         .long   PMD_TYPE_SECT | \ 
         PMD_BIT4 | \ 
         PMD_SECT_AP_WRITE | \ 
        PMD_SECT_AP_READ 
         b __arm926_setup 
         .long cpu_arch_name 
         .long cpu_elf_name 
         .long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HWCAP_EDSP|HWCAP_JAVA 
          .long cpu_arm926_name 
          .long arm926_processor_functions 
          .long v4wbi_tlb_fns 
         .long v4wb_user_fns 
        .long arm926_cache_fns 
         .size __arm926_proc_info, . - __arm926_proc_info 
         
        我们可以看到 __arm926_proc_info 被放到了".proc.info.init"段中.
        对照struct proc_info_list,我们可以看到 __cpu_flush的定义是__arm926_setup.        
从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址, r6中的__proc_info_end是proc_info_list的结束地址.

sub r3, r3, r7: 从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.
add r5, r5, r3: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址
add r6, r6, r3: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址
ldmia r5, {r3, r4}: 对照struct proc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3, r4中
and r4, r4, r9: r9中存储了processor id(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值
teq r3, r4: 将153行中得到的值与r3中的cpu_val进行比较
beq 2f: 如果相等,说明我们找到了对应的processor type,跳到2:,返回
add r5, r5, #PROC_INFO_SZ: (如果不相等) , 将r5指向下一个proc_info, 
cmp r5, r6: 和r6比较,检查是否到了__proc_info_end.
blo 1b: 如果没有到__proc_info_end,表明还有proc_info配置,返回1:继续查找
mov r5, #0: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknown processor)
mov pc, lr: 返回

2. 确定 machine type

确定 machine type 的逻辑和区别处理器类别的逻辑差不多,主要区别是处理器的参数是通过协处理器和bootloader 传入的,机器的信息是通过宏MACHINE_START来定义machine type.

3. 创建页表

通过前面的两步,我们已经确定了processor type 和 machine type.
此时,一些特定寄存器的值如下所示:
r8 = machine info       (struct machine_desc的基地址)
r9 = cpu id             (通过cp15协处理器获得的cpu id)
r10 = procinfo          (struct proc_info_list的基地址)

创建页表是通过函数 __create_page_tables 来实现的. 
这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)
L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(section entry). 每个页表项是32 bits(4 bytes)
因而L1主页表占用 4096 *4 = 16k的内存空间.
调用平台特定的 __cpu_flush 函数 

当 __create_page_tables 返回之后

此时,一些特定寄存器的值如下所示:
r4 = pgtbl              (page table 的物理基地址)
r8 = machine info       (struct machine_desc的基地址)
r9 = cpu id             (通过cp15协处理器获得的cpu id)
r10 = procinfo          (struct proc_info_list的基地址)
在我们需要在开启mmu之前,做一些必须的工作:清除ICache, 清除 DCache, 清除 Writebuffer, 清除TLB等.
这些一般是通过cp15协处理器来实现的,并且是平台相关的. 这就是 __cpu_flush 需要做的工作.

r10存储的是procinfo的基地址, PROCINFO_INITFUNC 将pc设为 proc_info_list的 __cpu_flush 函数的地址, 即下面跳转到该函数.
        在分析 __lookup_processor_type 的时候,我们已经知道,对于 ARM926EJS 来说,其__cpu_flush指向的是函数 __arm926_setup

在执行__arm926_setup完成后执行mov pc, lr  ,即跳转到   __enable_mmu

5. 开启mmu
在进入 __enable_mmu 的时候, r0中已经存放了控制寄存器c1的一些配置(在上一步中进行的设置), 但是并没有真正的打开mmu,
        在 __enable_mmu 中,我们将打开mmu.
        
        此时,一些特定寄存器的值如下所示:
r0 = c1 parameters      (用来配置控制寄存器的参数)        
r4 = pgtbl              (page table 的物理基地址)
r8 = machine info       (struct machine_desc的基地址)
r9 = cpu id             (通过cp15协处理器获得的cpu id)
r10 = procinfo          (struct proc_info_list的基地址) 
     在执行完成后,跳转到r13,即     __switch_data 

6. 切换数据
在 arch/arm/kernel/head-common.S 中:
完成相关操作后最终跳转到start_kernel。

下面是国嵌培训文档(Linux2.6内核启动流程(国嵌))

arch/arm/boot/compressed/start.S

 

Start:

                .type   start,#function

                .rept   8

                mov     r0, r0

                .endr

 

                b       1f

                .word   0x016f2818              @ Magic numbers to help the loader

                .word   start                   @ absolute load/run zImage address

                .word   _edata                  @ zImage end address

1:              mov     r7, r1                  @ save architecture ID

                mov     r8, r2                  @ save atags pointer

这也标志着u-boot将系统完全的交给了OSbootloader生命终止。之后代码在133行会读取cpsr并判断是否处理器处于supervisor模式——u-boot进入kernel,系统已经处于SVC32模式;而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入

 

                mrs     r2, cpsr                @ get current mode

                tst     r2, #3                  @ not user?

                bne     not_angel

                mov     r0, #0x17               @ angel_SWIreason_EnterSVC

                swi     0x123456                @ angel_SWI_ARM

not_angel:

                mrs     r2, cpsr                @ turn off interrupts to

                orr     r2, r2, #0xc0           @ prevent angel from running

                msr     cpsr_c, r2

 然后在LC0地址处将分段信息导入r0-r6ipsp等寄存器,并检查代码是否运行在与链接时相同的目标地址,以决定是否进行处理。由于现在很少有人不使用loadertags,将zImage烧写到rom直接从0x0位置执行,所以这个处理是必须的(但是zImage的头现在也保留了不用loader也可启动的能力)。arm架构下自解压头一般是链接在0x0地址而被加载到0x30008000运行,所以要修正这个变化。涉及到

 

r5寄存器存放的zImage基地址

r6r12(即ip寄存器)存放的gotglobal offset table

r2r3存放的bss段起止地址

sp栈指针地址

很简单,这些寄存器统统被加上一个你也能猜到的偏移地址 0x30008000。该地址是s3c2410相关的,其他的ARM处理器可以参考下表

 

PXA2xx0xa0008000

IXP2x00IXP4xx0x00008000

Freescale i.MX31/370x80008000

TI davinci DM64xx0x80008000

TI omap系列是0x80008000

AT91RM/SAM92xx系列是0x20008000

Cirrus EP93xx0x00008000

这些操作发生在代码172行开始的地方,下面只粘贴一部分

 

                add     r5, r5, r0

                add     r6, r6, r0

                add     ip, ip, r0

后面在211行进行bss段的清零工作

 

not_relocated:     mov     r0, #0

1:              str     r0, [r2], #4            @ clear bss

                str     r0, [r2], #4

                str     r0, [r2], #4

                str     r0, [r2], #4

                cmp     r2, r3

                blo     1b

 然后224行,打开cache,并为后面解压缩设置64KB的临时malloc空间

 

                bl      cache_on

 

                mov     r1, sp                  @ malloc space above stack

                add     r2, sp, #0x10000        @ 64k max  接下来238行进行检查,确定内核解压缩后的Image目标地址是否会覆盖到zImage头,如果是则准备将zImage头转移到解压出来的内核后面

 

                cmp     r4, r2

                bhs     wont_overwrite

                sub     r3, sp, r5              @ > compressed kernel size

                add     r0, r4, r3, lsl #2      @ allow for 4x expansion

                cmp     r0, r5

                bls     wont_overwrite

 

                mov     r5, r2                  @ decompress after malloc space

                mov     r0, r5

                mov     r3, r7

                bl      decompress_kernel

真实情况——在大多数的应用中,内核编译都会把压缩的zImage和非压缩的Image链接到同样的地址,s3c2410平台下即是0x30008000。这样做的好处是,人们不用关心内核是Image还是zImage,放到这个位置执行就OK,所以在解压缩后zImage头必须为真正的内核让路。

 

250行解压完毕,内核长度返回值存放在r0寄存器里。在内核末尾空出128字节的栈空间用,并且使其长度128字节对齐。

 

                add     r0, r0, #127 + 128      @ alignment + stack

                bic     r0, r0, #127            @ align the kernel length

算出搬移代码的参数:计算内核末尾地址并存放于r1寄存器,需要搬移代码原来地址放在r2,需要搬移的长度放在r3。然后执行搬移,并设置好sp指针指向新的栈(原来的栈也会被内核覆盖掉)

 

                add     r1, r5, r0              @ end of decompressed kernel

                adr     r2, reloc_start

                ldr     r3, LC1

                add     r3, r2, r3

1:              ldmia   r2!, {r9 - r14}         @ copy relocation code

                stmia   r1!, {r9 - r14}

                ldmia   r2!, {r9 - r14}

                stmia   r1!, {r9 - r14}

                cmp     r2, r3

                blo     1b

                add     sp, r1, #128            @ relocate the stack

搬移完成后刷新cache,因为代码地址变化了不能让cache再命中被内核覆盖的老地址。然后跳转到新的地址继续执行

 

                bl      cache_clean_flush

                add     pc, r5, r0              @ call relocation code

注意——zImage在解压后的搬移和跳转会给gdb调试内核带来麻烦。因为用来调试的符号表是在编译是生成的,并不知道以后会被搬移到何处去,只有在内核解压缩完成之后,根据计算出来的参数告诉调试器这个变化。以撰写本文时使用的zImage为例,内核自解压头重定向后,reloc_start地址由0x30008360变为0x30533e60。故我们要把vmlinux的符号表也相应的从0x30008000后移到0x30533b00开始,这样gdb就可以正确的对应源代码和机器指令。

 

随着头部代码移动到新的位置,不会再和内核的目标地址冲突,可以开始内核自身的搬移了。此时r0寄存器存放的是内核长度(严格的说是长度外加128Byte的栈),r4存放的是内核的目的地址0x30008000r5是目前内核存放地址,r6CPU IDr7machine IDr8atags地址。代码从501行开始

 

reloc_start:    add     r9, r5, r0

                sub     r9, r9, #128            @ do not copy the stack

                debug_reloc_start

                mov     r1, r4

1:

                .rept   4

                ldmia   r5!, {r0, r2, r3, r10 - r14}    @ relocate kernel

                stmia   r1!, {r0, r2, r3, r10 - r14}

                .endr

 

                cmp     r5, r9

                blo     1b

                add     sp, r1, #128            @ relocate the stack

接下来在516行清除并关闭cache,清零r0,将machine ID存入r1atags指针存入r2,再跳入0x30008000执行真正的内核Image

 

call_kernel:    bl      cache_clean_flush

                bl      cache_off

                mov     r0, #0                  @ must be zero

                mov     r1, r7                  @ restore architecture number

                mov     r2, r8                  @ restore atags pointer

                mov     pc, r4                  @ call kernel

 

 

 

 

 

内核代码入口在arch/arm/kernel/head.S文件的83行。首先进入SVC32模式,并查询CPU ID,检查合法性

 

        msr     cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ 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)?

        beq     __error_p                       @ yes, error 'p'

接着在87行进一步查询machine ID并检查合法性

 

        bl      __lookup_machine_type           @ r5=machinfo

        movs    r8, r5                          @ invalid machine (r5=0)?

        beq     __error_a                       @ yes, error 'a'

其中__lookup_processor_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的149行,该函数首将标号3的实际地址加载到r3,然后将编译时生成的__proc_info_begin虚拟地址载入到r5,__proc_info_end虚拟地址载入到r6,标号3的虚拟地址载入到r7。由于adr伪指令和标号3的使用,以及__proc_info_begin等符号在linux-2.6.24-moko-linuxbj/arch/arm/kernel/vmlinux.lds而不是代码中被定义,此处代码不是非常直观,想弄清楚代码缘由的读者请耐心阅读这两个文件和adr伪指令的说明。

 

r3和r7分别存储的是同一位置标号3的物理地址(由于没有启用mmu,所以当前肯定是物理地址)和虚拟地址,所以儿者相减即得到虚拟地址和物理地址之间的offset。利用此offset,将r5和r6中保存的虚拟地址转变为物理地址

 

__lookup_processor_type:

    adr    r3, 3f

    ldmda    r3, {r5 - r7}

    sub    r3, r3, r7            @ get offset between virt&phys

    add    r5, r5, r3            @ convert virt addresses to

    add    r6, r6, r3            @ physical address space

然后从proc_info中读出内核编译时写入的processor ID和之前从cpsr中读到的processor ID对比,查看代码和CPU硬件是否匹配(想在arm920t上运行为cortex-a8编译的内核?不让!)。如果编译了多种处理器支持,如versatile板,则会循环每种type依次检验,如果硬件读出的ID在内核中找不到匹配,则r5置0返回

 

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

 __lookup_machine_type在linux-2.6.24-moko-linuxbj/arch/arm/kernel/head-common.S文件的197行,编码方法与检查processor ID完全一样,请参考前段

 

__lookup_machine_type:

       adr  r3, 3b

       ldmia     r3, {r4, r5, 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:    ldr   r3, [r5, #MACHINFO_TYPE]    @ get machine type

       teq  r3, r1                           @ matches loader number?

       beq 2f                         @ found

       add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc

       cmp       r5, r6

       blo  1b

       mov       r5, #0                          @ unknown machine

2:    mov       pc, lr

代码回到head.S第92行,检查atags合法性,然后创建初始页表

 

       bl    __vet_atags

       bl    __create_page_tables

 创建页表的代码在218行,首先将内核起始地址-0x4000到内核起始地址之间的16K存储器清0

 

__create_page_tables:

       pgtbl     r4                         @ page table address

 

       /*

        * Clear the 16K level 1 swapper page table

        */

       mov       r0, r4

       mov       r3, #0

       add r6, r0, #0x4000

1:    str   r3, [r0], #4

       str   r3, [r0], #4

       str   r3, [r0], #4

       str   r3, [r0], #4

       teq  r0, r6

       bne 1b

 然后在234行将proc_info中的mmu_flags加载到r7

 

       ldr   r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags在242行将PC指针右移20位,得到内核第一个1MB空间的段地址存入r6,在s3c2410平台该值是0x300。接着根据此值存入映射标识

 

       mov       r6, pc, lsr #20                   @ start of kernel section

       orr  r3, r7, r6, lsl #20        @ flags + kernel base

       str   r3, [r4, r6, lsl #2]        @ identity mapping

完成页表设置后回到102行,为打开虚拟地址映射作准备。设置sp指针,函数返回地址lr指向__enable_mmu,并跳转到linux-2.6.24-moko-linuxbj/arch/arm/mm/proc-arm920.S的386行,清除I-cache、D-cache、write buffer和TLB

 

__arm920_setup:

       mov       r0, #0

       mcr p15, 0, r0, c7, c7        @ invalidate I,D caches on v4

       mcr p15, 0, r0, c7, c10, 4         @ drain write buffer on v4

#ifdef CONFIG_MMU

       mcr p15, 0, r0, c8, c7        @ invalidate I,D TLBs on v4

#endif然后返回head.S的158行,加载domain和页表,跳转到__turn_mmu_on

 

__enable_mmu:

#ifdef CONFIG_ALIGNMENT_TRAP

       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

       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

       b     __turn_mmu_on在194行把mmu使能位写入mmu,激活虚拟地址。然后将原来保存在sp中的地址载入pc,跳转到head-common.S的__mmap_switched,至此代码进入虚拟地址的世界

 

       mov       r0, r0

       mcr p15, 0, r0, c1, c0, 0           @ write control reg

       mrc p15, 0, r3, c0, c0, 0           @ read id reg

       mov       r3, r3

       mov       r3, r3

       mov       pc, r13

在head-common.S的37行开始清除内核bss段,processor ID保存在r9,machine ID报存在r1,atags地址保存在r2,并将控制寄存器保存到r7定义的内存地址。接下来跳入linux-2.6.24-moko-linuxbj/init/main.c的507行,start_kernel函数。这里只粘贴部分代码

 

__mmap_switched:

       adr  r3, __switch_data + 4

 

       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


 

你可能感兴趣的:(linux/arch/arm/kernel/head.S)