linux内核 cmpset,Linux内核启动流程

语句“mrc p15, 0, r9, c0, c0”将协处理器寄存器CP15读入寄存器r9,此寄存器保存CPUID;之后调用函数__lookup_processor_type查找处理器类型;

__lookup_processor_type函数:

__lookup_processor_type:

ARM( adr r3, 3f )

ARM( ldmda r3, {r5 - r7} )

THUMB( adr r3, 3f+4 )

THUMB( ldmdb r3, {r5 - r7} )

THUMB( sub r3, r3, #4 )

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

ENDPROC(__lookup_processor_type)

/*

* Look in 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

语句“adr r3, 3f”向前寻找到标号3的地址,赋给寄存器r3;

语句“ldmda r3, {r5 - r7}”将寄存器r3所指地址处的数据分别读入r5,r6,r7,由于ldmda是“过后减少装载”方式,因此r6与r7分别读入的应是__proc_info_end和__proc_info_begin;

函数返回后,有一个操作:

movs r10, r5

将寄存器r5中的proc_info入口地址保存在寄存器r10中,这是为了后来调用__create_page_tables准备的

__lookup_machine_type函数:

__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

ENDPROC(__lookup_machine_type)

此函数紧挨着__lookup_processor_type,与__lookup_processor_type共用标号3,不同的是语句“adr r3, 3b”为向后查找标号3,将标号3的地址赋给寄存器r3;

接下来的语句“ldmia r3, {r4, r5, r6}”采用了过后增加装载指令查表,将__arch_info_begin、__arch_info_end地地址分别装入寄存器r5、r6;

回顾一下标号3:

/*

* Look in 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_processor_type函数通过ldmda指令查找__proc_info_表格,__lookup_machine_type函数则通过ldmia指令查找__arch_info_表格。

__arch_info_表格的生成,可见arch/arm/include/asm/mach/arch.h文件中定义的宏MACHINE_START:

/*

* Set of macros to define architecture features.  This is built into

* a table by the linker.

*/

#define MACHINE_START(_type,_name) /

static const struct machine_desc __mach_desc_##_type /

__used /

__attribute__((__section__(".arch.info.init"))) = { /

.nr = MACH_TYPE_##_type, /

.name = _name,

#define MACHINE_END /

};

以TI OMAP3 参考板为例,MACHINE_START表格的具体填写,在arch/arm/mach-omap2/board-zoom2.c文件中:

MACHINE_START(OMAP_ZOOM2, "OMAP ZOOM2 board")

.phys_io = 0x48000000,

.io_pg_offst = ((0xd8000000) >> 18) & 0xfffc,

.boot_params = 0x80000100,

.map_io = omap_ldp_map_io,

.init_irq = omap_ldp_init_irq,

.init_machine = omap_ldp_init,

.timer = &omap_timer,

MACHINE_END

回到__lookup_machine_type函数,寄存器r1在kernel入口时已被赋予了type的数值,从u-boot代码include/asm-arm/mach-types.h文件中定义:

#define MACH_TYPE_OMAP_ZOOM2          1967

可知,boot向Kernel传入的数据应为1967,kernel中,OMAP_ZOOM2对应的type值由脚本文件arch/arm/tools/mach-types指定,该脚本中,行

omap_zoom2 MACH_OMAP_ZOOM2 OMAP_ZOOM2 1967

指定OMAP_ZOOM2对应的type值为1967,因此,函数__lookup_machine_type的查表过程成功,寄存器r5中返回ZOOM2对应信息的数据结构首地址;函数返回后,有一个操作:

movs r8, r5

将寄存器r5中的__arch_info入口地址保存在寄存器r8中,这也是为了后来调用__create_page_tables准备的

需要关注的数据结构machine_desc(在文件arch/arm/include/asm/mach/arch.h中定义):

struct machine_desc {

/*

* Note! The first four elements are used

* by assembler code in head.S, head-common.S

*/

unsigned int nr; /* architecture number */

unsigned int phys_io; /* start of physical io */

unsigned int io_pg_offst; /* byte offset for io

* page tabe entry */

const char *name; /* architecture name */

unsigned long boot_params; /* tagged list */

unsigned int video_start; /* start of video RAM */

unsigned int video_end; /* end of video RAM */

unsigned int reserve_lp0 :1; /* never has lp0 */

unsigned int reserve_lp1 :1; /* never has lp1 */

unsigned int reserve_lp2 :1; /* never has lp2 */

unsigned int soft_reboot :1; /* soft reboot */

void (*fixup)(struct machine_desc *,

struct tag *, char **,

struct meminfo *);

void (*map_io)(void);/* IO mapping function */

void (*init_irq)(void);

struct sys_timer *timer; /* system tick timer */

void (*init_machine)(void);

};

有许多事,需要回到u-boot才能说清楚……

include/configs/XXXX.h中(XXXX视具体平台而定),一般会作类似如下定义:

#define CONFIG_BOOTCOMMAND "mmcinit; fatload mmc 0 0x81c00000 uImage; bootm 0x81c00000"

编译时该宏CONFIG_BOOTCOMMAND传递给一个ENV项bootcmd,而在common/main.c中,函数main_loop取出了该env项,作为boot的过程开始启动kernel:

s = getenv ("bootcmd");

debug ("### main_loop: bootcmd=/"%s/"/n", s ? s : "");

if (bootdelay >= 0 && s && !abortboot (bootdelay)) {

# ifdef CONFIG_AUTOBOOT_KEYED

int prev = disable_ctrlc(1); /* disable Control C checking */

# endif

# ifndef CFG_HUSH_PARSER

run_command (s, 0);

我们关心的是最后一条命令bootm,在文件common/cmd_bootm.c中已经定义:

U_BOOT_CMD(

bootm, CFG_MAXARGS, 1, do_bootm,

"bootm   - boot application image from memory/n",

"[addr [arg ...]]/n    - boot application image stored in memory/n"

"/tpassing arguments 'arg ...'; when booting a Linux kernel,/n"

"/t'arg' can be the address of an initrd image/n"

);

即,真正要运行的程序是do_bootm。

一些关键的数据结构:

1、gd_t

在文件include/asm-arm/global_data.h中定义,在u-boot中广泛使用,通过宏:

DECLARE_GLOBAL_DATA_PTR;

将该数据结构的指针放入寄存器r8。

gd这个数据结构,在lib-arm/board.c文件中的start_armboot函数中进行初始化,详细分析以后再看。

2、bd_t

在文件include/asm-arm/u-boot.h中定义,作为u-boot的接口。

3、image_header_t

在文件include/image.h中定义,是u-boot即将加载的镜像文件头信息,u-boot将根据该头信息确定镜像文件是否正确,校验,镜像文件的起始地址以及入口地址等信息。

typedef struct image_header {

uint32_t ih_magic; /* Image Header Magic Number*/

uint32_t ih_hcrc; /* Image Header CRC Checksum*/

uint32_t ih_time; /* Image Creation Timestamp*/

uint32_t ih_size; /* Image Data Size

*/

uint32_t ih_load; /* Data Load  Address*/

uint32_t ih_ep; /* Entry Point Address

*/

uint32_t ih_dcrc; /* Image Data CRC Checksum*/

uint8_t ih_os; /* Operating System */

uint8_t ih_arch; /* CPU architecture

*/

uint8_t ih_type; /* Image Type */

uint8_t ih_comp; /* Compression Type

*/

uint8_t ih_name[IH_NMLEN]; /* Image Name

*/

} image_header_t;

do_bootm函数会读出即将加载的镜像文件头,填入由该数据结构声明的全局变量header中,do_bootm函数通过对header的分析,确定是一个Linux镜像,则会调用do_bootm_linux函数,开始启动Linux的Kernel。

do_bootm_linux函数(在文件lib_arm/armlinux.c中),目前只关心最后一句:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

theKernel在前面被定义为:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

hdr->ih_ep使用的是do_bootm函数从image头中读取的入口,对于我们的板子来说,就是0x80008000;

bd->bi_arch_number, bd->bi_boot_params两个入口参数在的board_init函数中定义,bd->bi_arch_number赋值为板子的ID,bd->bi_boot_params赋值为0x80000100,该地址保存着do_bootm_linux函数创建的一系列ATAG。

参数的传递要看APCS(ARM过程调用标准),前四个参数会依次放入a1~a4,这是APCS为寄存器起的别名,对应于r0~r3,即,调用到kernel时,寄存器r0为0,r1为1967,r2为0x80000100。

u-boot的事情还远未说清楚,但是与目前有关的,大致差不多了。

欢迎回到Kernel中……(看了2.6.29内核,做了些修订,关于it指令的)

继续说__vet_atags函数,这个函数仍旧定义在arch/arm/kernel/head-common.s文件中:

/* Determine validity of the r2 atags pointer.  The heuristic requires

* that the pointer be aligned, in the first 16k of physical RAM and

* that the ATAG_CORE marker is first and present.  Future revisions

* of this function may be more lenient with the physical address and

* may also be able to move the ATAGS block if necessary.

*

* r8  = machinfo

*

* Returns:

*  r2 either valid atags pointer, or zero

*  r5, r6 corrupted

*/

__vet_atags:

tstr2, #0x3@ aligned?

bne1f

ldrr5, [r2, #0]@ is first tag ATAG_CORE?

subsr5, r5, #ATAG_CORE_SIZE

bne1f

ldrr5, [r2, #4]

ldrr6, =ATAG_CORE

cmpr5, r6

bne1f

movpc, lr@ atag pointer is ok

1:movr2, #0

movpc, lr

ENDPROC(__vet_atags)

由TI的芯片手册知,OMAP3430的SDRAM地址空间自0x80000000起,计1GB空间。函数开始的注释明确要求ATAG表需要在RAM物理地址前16KB,即0x80000000-0x80004000范围内,r2的0x80000100满足此要求。简单的判断了一下ATAG表的起始地址是否对齐,是否以ATAG_CORE作为开头标志,该函数返回。

__create_page_tables函数,这是要分析的重点函数,该函数就在arch/arm/kernel/head.s文件末尾。

先来看一个宏定义:

.macro pgtbl, rd

ldr /rd, =(KERNEL_RAM_PADDR - 0x4000)

.endm

KERNEL_RAM_PADDR的定义为:

#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)

PHYS_OFFSET在文件arch/arm/plat-omap/include/mach/memory.h中(怎么找到这里的,其实是arch/arm/kernel/head.s中包含了arch/arm/include/asm/memory.h中又包含了arch/arm/plat-omap/include/mach/memory.h的缘故……)定义为0x80000000,而TEXT_OFFSET则需要在Makefile中寻找到答案。arch/arm/Makefile中定义

……

textofs-y := 0x00008000

……

TEXT_OFFSET := $(textofs-y)

可见,TEXT_OFFSET的值为0x00008000,再回到宏pgtbl,可计算出KERNEL_RAM_PADDR的值为0x80000000+0x00008000=0x80008000最后赋给寄存器值为0x80008000-0x4000=0x80004000。

__create_page_tables函数的第一条语句为:

pgtbl r4 @ page table address

根据前面计算,寄存器r4赋值0x80004000,该地址是页表的起始地址,在kernel的入口地址(0x80008000)之前16KB。

之后,对该页表进行初始化操作。

第一步、将这16KB空间清零:

/*

* 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

第二步、填写内核所在页的页目录项

ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

这里需要再来回顾一下proc_info_list数据结构(在arch/arm/include/asm/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;

};

指令ldr r7, [r10, #PROCINFO_MM_MMUFLAGS]取到了结构中的数据__cpu_mm_mmu_flags,送入寄存器r7,该参数在arch/arm/mm/proc-v7.s文件中定义如下:

.long   PMD_TYPE_SECT | /

PMD_SECT_BUFFERABLE | /

PMD_SECT_CACHEABLE | /

PMD_SECT_AP_WRITE | /

PMD_SECT_AP_READ

计算下来,应该是:0b0000110000001110,即0x0c0e。

参照如下表格:arm采用的是段式页表,按照VMSA(Virtual Memory System Architecture)规定,每个表项描述1MB空间,16KB可存放4K个表项,覆盖4GB虚拟地址空间。关于页表的详细说明,可参阅ARM Architecture Reference Manual DDI0406B的B3章节。

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

当前pc的高12位,即内核所在物理地址的索引放入寄存器r6;

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

索引号与寄存器r7中的标志字节相或,合并后的描述符放入寄存器r3;

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

每个描述符占4字节,所以内核的起始页目录项地址应该在[r4]+[r6]x4处,将r3的描述符内容送入其中;

第三步、根据当前内核所划分的虚拟地址空间,建立内核页表项,Linux默认内核在最高1G,因此内核起始虚拟地址一般为0xc0000000。

add r0, r4,  #(KERNEL_START & 0xff000000) >> 18

KERNEL_START的定义如下:

#define KERNEL_START KERNEL_RAM_VADDR

KERNEL_RAM_VADDR的定义如下:

#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)

PAGE_OFFSET的定义,如果别处没有关怀过,那就应该是arch/arm/include/asm/memory.h文件中的如下定义了:

/*

* Page offset: 3GB

*/

#ifndef PAGE_OFFSET

#define PAGE_OFFSET UL(0xc0000000)

#endif

计算所得,的值应为0xc0008000,这是Kernel起始的虚拟地址,截取高14位,取高8位有效与表头地址相加,作为基址送入寄存器r0;这里之所以截取高14位,是由于section页表,可记录4K个表项,正好取虚拟地址的高12位作为页表的索引,每个表项占用4字节,则索引号需乘4,为14位。

str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!

寄存器r3内容,是前面已经计算过的包含物理地址索引的页描述符,将其存放入寄存器r0增加偏移虚拟地址高12位中的低4位所指表项中,并更新r0的内容。

ldr r6, =(KERNEL_END - 1)

寄存器r6保留内核代码长度;

add r0, r0, #4

寄存器r0指向下一页表项;

add r6, r4, r6, lsr #18

根据内核长度计算出内核页表的最后入口;

1: cmp r0, r6

比较内核页表是否已填写完成;

add r3, r3, #1 << 20

物理地址索引加一;

it ls

小于等于判断,it指令是if-then块,具体内容,请参阅RealView编译工具《汇编器指南》(发现2.6.29内核把这句去掉了,草!);

strls r3, [r0], #4

如果小于等于成立,填写该表项;

bls 1b

如果小于等于则跳回标号1;

第四步、填写内存开始时的1MB段,因为这里有boot传来的内核启动参数。

/*

* Then map first 1MB of ram in case it contains our boot params.

*/

add r0, r4, #PAGE_OFFSET >> 18

orr r6, r7, #(PHYS_OFFSET & 0xff000000)

.if (PHYS_OFFSET & 0x00f00000)

orr r6, r6, #(PHYS_OFFSET & 0x00f00000)

.endif

str r6, [r0]

刚好一页,不用判断了。

最后,返回了……

mov pc, lr

回到arch/arm/kernel/head.s文件中填充完页表后的初始化程序:

/*

* 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

badr lr, __enable_mmu @ return (PIC) address

准备好了返回地址,调用如下:

ARM( add pc, r10, #PROCINFO_INITFUNC

)

前面的初始化保证,寄存器r10指向procinfo,在文件arch/arm/kernel/asm-offsets.c文件中宏PROCINFO_INITFUNC的定义如下:

DEFINE(PROCINFO_INITFUNC, offsetof(struct proc_info_list, __cpu_flush));

到这里,是该轻易莲步关注arch/arm/include/asm/procinfo.h文件中的数据结构proc_info_list的时候了:

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;

};

此数据结构中,__cpu_flush一项,在arch/arm/mm/proc-v7.s中,定义为一条调用指令:b __v7_setup

因此add pc, r10, #PROCINFO_INITFUNC这一句辗转调用了__v7_setup函数。

/*

* __v7_setup

*

* Initialise TLB, Caches, and MMU state ready to switch the MMU

* on.  Return in r0 the new CP15 C1 control register setting.

*

* We automatically detect if we have a Harvard cache, and use the

* Harvard cache control instructions insead of the unified cache

* control instructions.

*

* This should be able to cover all ARMv7 cores.

*

* It is assumed that:

* - cache type register is implemented

*/

__v7_setup:

adr r12, __v7_setup_stack @ the local stack

stmia r12, {r0-r5, r7, r9, r11, lr}

寄存器入栈__v7_setup_stack是个11个word的局部栈,就在本函数后面定义,stmia表明此处用的是升序栈;

bl v7_flush_dcache_all

清除数据缓存;这个函数定义在arch/arm/mm/cache-v7.s文件中(这里为什么不调用v7_flush_cache_all()将数据、指令缓冲区全部清除,而只清除数据缓冲区呢?);

ldmia r12, {r0-r5, r7, r9, r11, lr}

寄存器出栈;

/*

* On OMAP3 devices the auxilary control register can be accessed

* only is secure mode using SMI /PPA. The IBE bit is enabled at the

* u-boot level using SMI service. So no need to set that bit again.

*/

#ifndef CONFIG_ARCH_OMAP3430

#ifdef CONFIG_ARM_ERRATA_430973

mrc p15, 0, r10, c1, c0, 1 @ read aux control register

orr r10, r10, #(1 << 6) @ set IBE to 1

mcr p15, 0, r10, c1, c0, 1 @ write aux control register

#endif

#endif

清除分支预测缓冲区,对OMAP3来京不需要。

mov r10, #0

#ifdef HARVARD_CACHE

mcr p15, 0, r10, c7, c5, 0 @ I+BTB cache invalidate

#endif

哈佛结构的cache,v7不支持。

dsb

数据同步屏障是一种特殊的内存屏障。 只有当此指令执行完毕后,才会执行程序中位于此指令后的指令。 当满足以下条件时,此指令才会完成:

位于此指令前的所有显式内存访问均完成。

位于此指令前的所有高速缓存、跳转预测和 TLB 维护操作全部完成。

#ifdef CONFIG_MMU

mcr p15, 0, r10, c8, c7, 0 @ invalidate I + D TLBs

置指令和数据TLB无效(寄存器r10在前面已清零);

mcr p15, 0, r10, c2, c0, 2 @ TTB control register

TLB控制寄存器清零;

orr r4, r4, #TTB_RGN_OC_WB @ mark PTWs outer cacheable, WB

寄存器r4仍然保存着页表起始地址0x80004000,TTB_RGN_OC_WB被定义为3<<3,对应于转换表基址寄存器的RNG位,此配置为使能TLB缓存,写回模式,写时不分配空间;

mcr p15, 0, r4, c2, c0, 0 @ load TTB0

mcr p15, 0, r4, c2, c0, 1 @ load TTB1

将该参数写入转换表基址寄存器0和1;

mov r10, #0x1f @ domains 0, 1 = manager

mcr p15, 0, r10, c3, c0, 0 @ load domain access register

区域访问许可控制器配置为,D0、D1区域的访问不与TLB中的访问许可位校验,D3区域的访问会与TLB中的访问许可位校验(为什么这样配置,我现在还不清楚);

#endif

#if defined(CONFIG_ARCH_OMAP3)

@ OMAP3: L2EN bit accessed in nonsecure mode

@ L2 cache is enabled in the aux control register

mrc     p15, 0, r0, c1, c0, 1

#ifdef CONFIG_CPU_L2CACHE_DISABLE

bic     r0, r0, #0x2            @ disable L2 Cache

#else

orr     r0, r0, #0x2            @ enable L2 Cache

#endif

mcr     p15, 0, r0, c1, c0, 1   @ Enable the L2EN banked bit as well

#endif

使能L2 Cache;

#ifdef CONFIG_ARCH_OMAP34XX

#ifdef CONFIG_CPU_LOCKDOWN_TO_64K_L2

mov r10, #0xfc

mcr     p15, 1, r10, c9, c0, 0

#endif

#ifdef CONFIG_CPU_LOCKDOWN_TO_128K_L2

mov r10, #0xf0

mcr     p15, 1, r10, c9, c0, 0

#endif

#ifdef CONFIG_CPU_LOCKDOWN_TO_256K_L2

mov r10, #0x00

mcr     p15, 1, r10, c9, c0, 0

#endif

以上是OMAP自定义的寄存器,配置了OMAP的L2 Cache大小,OMAP3430是256K;

#ifdef CONFIG_CPU_USER_L2_PLE_ACCESS

mov r10, #0x3

mcr     p15, 0, r10, c11, c1, 0

使能应用程序访问PLE(preloading engine)寄存器通道0、1

#endif

#endif

adr r5, v7_crval

ldmia r5, {r5, r6}

mrc p15, 0, r0, c1, c0, 0 @ read control register

bic r0, r0, r5 @ clear bits them

orr r0, r0, r6 @ set them

mov pc, lr @ return to head.S:__ret

见如下定义:

v7_crval:

ARM( crval clear=0x0120c302, mmuset=0x00c0387d, ucset=0x00c0187c)

THUMB( crval clear=0x0120c302, mmuset=0x40c0387d, ucset=0x40c0187c)

控制寄存器值读入寄存器r0,清了一些位,又置了一些位,关键是,寄存器r0的值,已经使能了mmu,但是很显然,这里还没有写入CP15,这个激动人心的操作将由__enable_mmu函数来完成。

ENDPROC(__v7_setup)

打完收工!

结案陈词,这段程序关闭了指令和数据缓冲区,配置TLB寄存器及相应的控制寄存器,以及,计算好了mmu控制寄存器的初始化参数,就等着打开mmu,进入一个虚幻的世界了。

从__v7_setup函数返回来,进入到__enable_mmu过程,做起飞前的最后确认:

__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))

结合数据手册,此段是将D0、D1两个域,D1对应于USER域,D0对应于KERNEL和TABLE域,配置为管理模式,对这两个域的访问和执行不受TLB中相应标志位影响,D2域,对应于IO域,配置为客户模式,对此域的访问受TLB中相应标志位的检查;

mcr p15, 0, r5, c3, c0, 0 @ load domain access register

写入域访问控制寄存器,其实和__v7_setup函数中写入的值相同;

mcr p15, 0, r4, c2, c0, 0 @ load page table pointer

__v7_setup函数中也已经写过这个寄存器了;

b __turn_mmu_on

__turn_mmu_on就在后面紧跟着,不明白为何还要b一下;

ENDPROC(__enable_mmu)

再看看紧接着的__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

*  r13 = *virtual* address to jump to upon completion

*

* other registers depend on the function called upon completion

*/

.align 5

原来是因为有这个,所以要b过来,但为什么是以5对齐?在网上搜索了一下,原来.align n的语法,有按照n对齐的,也有按照2的n次方对齐的,而arm-linux是按照2的n次方对齐的,即,是以20字节位置对齐的,详见某篇博文:http://www.eetop.cn/blog/html/45/11145-1211.html

__turn_mmu_on:

mov r0, r0

暂时认为这是常规的nop语句……

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

终于将寄存器r0的值写入到了mmu控制寄存器中,此时的CPU终于可以“内牛满面”!

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

对于Cortex A8处理器来说,此时寄存器r3的值也许是:0x413fc082字样,其中41代表ARM,c08就是Cortex A8;

mov r3, r3

mov r3, r3

继续认为这是nop语句,或者说,在这里是清除流水线,那么,之后的CPU,已经平稳过渡到了虚拟地址空间的环境?(我不敢确认)……

mov pc, r13

无需废话了,很久以前,寄存器r13就准备好了__switch_data的入口地址;

ENDPROC(__turn_mmu_on)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值