ARM Linux启动代码分析

发布者:Howard_Sun最新更新时间:2016-06-16 来源: eefocus关键字:ARM  Linux  启动代码 手机看文章 扫描二维码
随时随地手机看文章
前言

         在学习、分析之前首先要弄明白一个问题:为什么要分析启动代码?

      因为启动代码绝大部分都是用汇编语言写的,对于没学过或者不熟悉汇编语言的同学确实有一定难度,但是如果你想真正深入地学习Linux,那么读、分析某一个体系结构(比如ARM)的启动代码或者其他底层代码是必不可少的。当分析之后会发现这是有很多好处的:分析启动代码可以加深对汇编语言的理解;可以学习汇编语言的使用技巧;可以学习如何编写位置无关的代码,可以知道从启动到start_kernel()函数之前内核到底干了什么事情,从而为后续其他内核子系统的学习打下基础。

      废话不多说,下面基于s3c6410,以Linux-2.6.36版本为基础进行分析。ARM Linux的启动代码有两处,一处是经过压缩的,一处是没有经过压缩的,压缩的最终还是会调用没有压缩的,没有压缩的入口在arch/arm/kernel/head.S文件中,如下所示:

77     __HEAD
78 ENTRY(stext)
79     setmode    PSR_F_BIT  PSR_I_BIT  SVC_MODE, r9 @ ensure svc mode
80                              @ and irqs disabled
81     mrc    p15, 0, r9, c0, c0             @ get processor id
82     bl    __lookup_processor_type             @ r5=procinfo r9=cpuid
83     movs    r10, r5                     @ invalid processor (r5=0)?
84     beq    __error_p                 @ yes, error 'p'
85     bl    __lookup_machine_type             @ r5=machinfo
86     movs    r8, r5                     @ invalid machine (r5=0)?
87     beq    __error_a                 @ yes, error 'a'
88     bl    __vet_atags
89     bl    __create_page_tables
90 
91     /*
92      * The following calls CPU specific code in a position independent
93      * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
94      * xxx_proc_info structure selected by __lookup_machine_type
95      * above.  On return, the CPU will be ready for the MMU to be
96      * turned on, and r0 will hold the CPU control register value.
97      */
98     ldr    r13, __switch_data        @ address to jump to after
99                         @ mmu has been enabled
00100     adr    lr, BSYM(__enable_mmu)        @ return (PIC) address
00101  ARM(    add    pc, r10, #PROCINFO_INITFUNC    )
00102  THUMB(    add    r12, r10, #PROCINFO_INITFUNC    )
00103  THUMB(    mov    pc, r12                )
00104 ENDPROC(stext)

79行就是要分析的第一行代码,设置CPU为管理模式,这也是CPU一上电所处的模式,关闭CPU普通中断和CPU快速中断。

81行,读协处理器p15获取CPU ID,结果存在r9寄存器里,待会会用到。

82行,跳转到__lookup_processor_type标号处,在arch/arm/kernel/head-common.S文件里定义:

00160 __lookup_processor_type:
00161     adr    r3, 3f
00162     ldmia    r3, {r5 - r7}
00163     add    r3, r3, #8
00164     sub    r3, r3, r7            @ get offset between virt&phys
00165     add    r5, r5, r3            @ convert virt addresses to
00166     add    r6, r6, r3            @ physical address space
00167 1:    ldmia    r5, {r3, r4}            @ value, mask
00168     and    r4, r4, r9            @ mask wanted bits
00169     teq    r3, r4
00170     beq    2f
00171     add    r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)
00172     cmp    r5, r6
00173     blo    1b
00174     mov    r5, #0                @ unknown processor
00175 2:    mov    pc, lr
00176 ENDPROC(__lookup_processor_type)
……
00193     .align    2
00194 3:    .long    __proc_info_begin
00195     .long    __proc_info_end
00196 4:    .long    .
00197     .long    __arch_info_begin
00198     .long    __arch_info_end

在汇编语言中,标号代表的是地址,准确来说是链接地址。adr和ldr都是伪指令,它们两者的作用都是将标号处所代表的地址存放到寄存器中。但是adr采用基于PC值的相对地址(PC+偏移值),而ldr采用的是绝对地址(直接采用标号的值),另外adr要求指令与标号位于同一个段中。

161行,因此当前PC值是存放的是一个物理地址,为什么是物理地址?为了搞清楚这个问题,下面简单说说上一个“年代”的bootloader是怎么引导、启动内核的,主要的流程如下:

(1)上电

(2)必要的设置

(3)关看门狗

(4)初始化SDRAM、初始化Nand Flash

(5)把bootloader拷贝到SDRAM的高处

(6)清BSS段

(7)跳到SDRAM继续执行

(8)把Nand Flash中的内核Image拷贝到SDRAM(0x58)

(9)设置启动参数,r0、r1等寄存器,关闭MMU、cache等

(10)跳到内核Image的起始处(0x58)执行,此后,bootloader时代一去不复返,进入Linux新时代。

      现在应该知道执行到161行时,PC的值就为0x50~0x58之间的某一个值(假定内存为128MB,s3c6410物理内存的起始地址为0x50),即一物理地址,因此r3的值就为194行的标号3处的物理地址。

     162行,分别将r3、r3+4、r3+8地址上的内容存放到r5、r6、r7寄存器中,即r5存放的是__proc_info_begin的值(是一个链接地址,或者说虚拟地址),r6存放的是__proc_info_end的值(是一个链接地址,或者说虚拟地址),因为 . 表示的是当前的链接地址,所以r7存放的是标号4的链接地址,这跟LD链接脚本里的 . 表示的意思是一样的。

    163行,将r3的值加8,即现在r3的值为196行的标号4的物理地址。

    164行,r3 = r3 – r7,即r3 = 标号4的物理地址 - 标号4的虚拟地址,这样就可以计算出物理地址和虚拟地址的偏移量,显然r3的值为一负数。

    165行,结果为r5 = __proc_info_begin的物理地址。

    166行,结果为r6 = __proc_info_end的物理地址。

    167行,取出struct proc_info_list结构体的前两个成员的值分别放到r3、r4。struct 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;
};

每一种体系结构都有一个这样的结构体变量,对于s3c6410,来说,它属于ARMv6体系结构,它的struct proc_info_list变量在arch/arm/mm/proc-v6.S中定义,在链接的时候所有这些变量都被放在__proc_info_begin和__proc_info_end之间。因此,167行执行后,r3 = cpu_val,r4 = cpu_mask。

    168行,将r4的值与r9的值相与,得到的CPU ID存在r4中。

    169行,比较r4与r3的值。

    170行,如果r4=r3,那么跳到175行处执行,即子程序返回。如果r4不等于r3,那么执行171行,将r5的值加上sizeof(struct proc_info_list),即指向下一个struct proc_info_list变量。

    172行,比较r5和r6。

    173行,如果r5小于r6,则跳转到167行,重复上面的过程。如果所有struct proc_info_list变量都比较后都没有找到对应的CPU ID,那么执行174行,r5 = 0,然后返回。

    至此,__lookup_processor_type分析完毕,回到head.S的83行,把r5的值赋给r10,并影响标志位。

    84行,如果r5=0,那么跳转到__error_p标号。这里假设内核是支持当前CPU的,即r5不为0,因此不分析__error_p的内容。

85行,跳到__lookup_machine_type标号处,同样是在arch/arm/kernel/head-common.S中定义:

00196 4:    .long    .
00197     .long    __arch_info_begin
00198     .long    __arch_info_end

00211 __lookup_machine_type:
00212     adr    r3, 4b
00213     ldmia    r3, {r4, r5, r6}
00214     sub    r3, r3, r4            @ get offset between virt&phys
00215     add    r5, r5, r3            @ convert virt addresses to
00216     add    r6, r6, r3            @ physical address space
00217 1:    ldr    r3, [r5, #MACHINFO_TYPE]    @ get machine type
00218     teq    r3, r1                @ matches loader number?
00219     beq    2f                @ found
00220     add    r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
00221     cmp    r5, r6
00     blo    1b
00223     mov    r5, #0                @ unknown machine
00224 2:    mov    pc, lr
00225 ENDPROC(__lookup_machine_type)

和前面的__lookup_processor_type非常类似,只不过这里查找的是struct machine_desc结构体变量,比较的是struct machine_desc的成员nr的值,因此不再分析。这里需要提一下的是,比如对于mini6410(tiny6410),struct machine_desc变量的定义在arch/arm/mach-s3c64xx/mach-mini6410.c文件中,如下所示:

00512 MACHINE_START(MINI6410, "MINI6410")
00513     /* Maintainer: Ben Dooks  */
00514     .phys_io    = S3C_PA_UART & 0xfff00,
00515     .io_pg_offst    = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
00516     .boot_params    = S3C64XX_PA_SDRAM + 0x100,
00517 
00518     .init_irq    = s3c6410_init_irq,
00519     .map_io        = mini6410_map_io,
00520     .init_machine    = mini6410_machine_init,
00521     .timer        = &s3c24xx_timer,
00522 MACHINE_END

回到head.S,86、87行判断是否支持当前的机器号,不支持就跳到__error_a标号处。

88行,跳到__vet_atags,同样是在arch/arm/kernel/head-common.S中定义:

00250 __vet_atags:
00251     tst    r2, #0x3            @ aligned?
00252     bne    1f
00253 
00254     ldr    r5, [r2, #0]            @ is first tag ATAG_CORE?
00255     cmp    r5, #ATAG_CORE_SIZE
00256     cmpne    r5, #ATAG_CORE_SIZE_EMPTY
00257     bne    1f
00258     ldr    r5, [r2, #4]
00259     ldr    r6, =ATAG_CORE
00260     cmp    r5, r6
00261     bne    1f
00262 
00263     mov    pc, lr                @ atag pointer is ok
00264 
00265 1:    mov    r2, #0
00266     mov    pc, lr
00267 ENDPROC(__vet_atags)

251行,测试r2的低2位是否为0,也即r2的值是否4字节对齐。

252行,如果r2的低2位不为0,则跳转到265行,将r2的值设为0,然后返回。

下面先看一下bootloader传递参数给内核的结构定义,在arch/arm/include/asm/setup.h文件中:

00146 struct tag {
00147     struct tag_header hdr;
00148     union {
00149         struct tag_core        core;
00150         struct tag_mem32    mem;
00151         struct tag_videotext    videotext;
00152         struct tag_ramdisk    ramdisk;
00153         struct tag_initrd    initrd;
00154         struct tag_serialnr    serialnr;
00155         struct tag_revision    revision;
00156         struct tag_videolfb    videolfb;
00157         struct tag_cmdline    cmdline;
00158 
00159         /*
00160          * Acorn specific
00161          */
00162         struct tag_acorn    acorn;
00163 
00164         /*
00165          * DC21285 specific
00166          */
00167         struct tag_memclk    memclk;
00168     } u;
00169 };

147行,struct tag_header的定义:

24 struct tag_header {
25     __u32 size;
26     __u32 tag;
27 };

从struct tag的定义可以知道,bootloader传递的参数有好几种类型的tag,但是内核规定第一个tag必须是ATAG_CORE类型,最后一个必须是ATAG_NONE类型,每一种类型的tag都有一个编号,例如ATAG_CORE为0x54411,ATAG_NONE为0x00。struct tag_header的tag成员就是用来描述tag的类型,而size成员用来描述整个tag的大小。每个tag连续存放。

    那么标号__vet_atags的254行的意思就是获取ATAG_CORE类型tag的size成员的值赋给r5。

    255行,将r5的值与ATAG_CORE_SIZE比较,ATAG_CORE_SIZE的值为((2*4 + 3*4) >> 2),即5。

    256行,如果255行比较的结果不相等,那么将r5与ATAG_CORE_SIZE_EMPTY进行比较,ATAG_CORE_SIZE_EMPTY的值为((2*4) >> 2),即2。

    257行,如果还是不相等,那么跳转到265行执行,同样是将r2设为0,然后返回。

    258行,获取struct tag_header的tag成员,将它的值赋给r5。

      259行,r6 = ATAG_CORE,即0x54411。

    260行,比较r5和r6的值。

    261行,如果r5和r6的值不相等则跳转到265行,如果相等则执行263行直接返回。

    至此,__vet_atags标号的内容分析完毕。

回到head.S的89行,跳转到__create_page_tables标号处,在head.S里定义:

00219 __create_page_tables:
00220     pgtbl    r4                @ page table address
00221 
00     /*
00223      * Clear the 16K level 1 swapper page table
00224      */
00225     mov    r0, r4
00226     mov    r3, #0
00227     add    r6, r0, #0x4
00228 1:    str    r3, [r0], #4
00229     str    r3, [r0], #4
00230     str    r3, [r0], #4
00231     str    r3, [r0], #4
00232     teq    r0, r6
00233     bne    1b
00234 
00235     ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
00236 
00237     /*
00238      * Create identity mapping for first MB of kernel to
00239      * cater for the MMU enable.  This identity mapping
00240      * will be removed by paging_init().  We use our current program
00241      * counter to determine corresponding section base address.
00242      */
00243     mov    r6, pc
00244     mov    r6, r6, lsr #20            @ start of kernel section
00245     orr    r3, r7, r6, lsl #20        @ flags + kernel base
00246     str    r3, [r4, r6, lsl #2]        @ identity mapping
00247 
00248     /*
00249      * Now setup the pagetables for our kernel direct
00250      * mapped region.
00251      */
00252     add    r0, r4,  #(KERNEL_START & 0xff) >> 18
00253     str    r3, [r0, #(KERNEL_START & 0x00f00) >> 18]!
00254     ldr    r6, =(KERNEL_END - 1)
00255     add    r0, r0, #4
00256     add    r6, r4, r6, lsr #18
00257 1:    cmp    r0, r6
00258     add    r3, r3, #1 << 20
00259     strls    r3, [r0], #4
00260     bls    1b
00261 
00262 #ifdef CONFIG_XIP_KERNEL
00263     /*
00264      * Map some ram to cover our .data and .bss areas.
00265      */
00266     orr    r3, r7, #(KERNEL_RAM_PADDR & 0xff)
00267     .if    (KERNEL_RAM_PADDR & 0x00f00)
00268     orr    r3, r3, #(KERNEL_RAM_PADDR & 0x00f00)
00269     .endif
00270     add    r0, r4,  #(KERNEL_RAM_VADDR & 0xff) >> 18
00271     str    r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00) >> 18]!
00272     ldr    r6, =(_end - 1)
00273     add    r0, r0, #4
00274     add    r6, r4, r6, lsr #18
00275 1:    cmp    r0, r6
00276     add    r3, r3, #1 << 20
00277     strls    r3, [r0], #4
00278     bls    1b
00279 #endif
00280 
00281     /*
00282      * Then map first 1MB of ram in case it contains our boot params.
00283      */
00284     add    r0, r4, #PAGE_OFFSET >> 18
00285     orr    r6, r7, #(PHYS_OFFSET & 0xff)
00286     .if    (PHYS_OFFSET & 0x00f00)
00287     orr    r6, r6, #(PHYS_OFFSET & 0x00f00)
00288     .endif
00289     str    r6, [r0]
00290 
00291 #ifdef CONFIG_DEBUG_LL
00292     ldr    r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
00293     /*
00294      * Map in IO space for serial debugging.
00295      * This allows debug messages to be output
00296      * via a serial console before paging_init.
00297      */
00298     ldr    r3, [r8, #MACHINFO_PGOFFIO]
00299     add    r0, r4, r3
00300     rsb    r3, r3, #0x4            @ PTRS_PER_PGD*sizeof(long)
00301     cmp    r3, #0x0800            @ limit to 512MB
00302     movhi    r3, #0x0800
00303     add    r6, r0, r3
00304     ldr    r3, [r8, #MACHINFO_PHYSIO]
00305     orr    r3, r3, r7
00306 1:    str    r3, [r0], #4
00307     add    r3, r3, #1 << 20
00308     teq    r0, r6
00309     bne    1b
00310 #if defined(CONFIG_ARCH_NETWINDER)  defined(CONFIG_ARCH_CATS)
00311     /*
00312      * If we're using the NetWinder or CATS, we also need to map
00313      * in the 16550-type serial port for the debug messages
00314      */
00315     add    r0, r4, #0xff >> 18
00316     orr    r3, r7, #0x7c
00317     str    r3, [r0]
00318 #endif
00319 #ifdef CONFIG_ARCH_RPC
00320     /*
00321      * Map in screen at 0x02 & SCREEN2_BASE
00322      * Similar reasons here - for debug.  This is
00323      * only for Acorn RiscPC architectures.
00324      */
00325     add    r0, r4, #0x02 >> 18
00326     orr    r3, r7, #0x02
00327     str    r3, [r0]
00328     add    r0, r4, #0xd8 >> 18
00329     str    r3, [r0]
00330 #endif
00331 #endif
00332     mov    pc, lr
00 ENDPROC(__create_page_tables)

别看这个定义这么长,其实需要关注的代码并不多。

220行,pgtbl是一个宏,定义如下:

47     .macro    pgtbl, rd
48     ldr    \rd, =(KERNEL_RAM_PADDR - 0x4)
49     .endm

就是将KERNEL_RAM_PADDR - 0x4的值赋给r4,现在关键是KERNEL_RAM_PADDR的定义:

#define KERNEL_RAM_PADDR (PHYS_OFFSET + TEXT_OFFSET)

其中PHYS_OFFSET就是SDRAM的起始地址,对于s3c6410,它的值为0x50,TEXT_OFFSET在arch/arm/Makefile中定义:

00 TEXT_OFFSET := $(textofs-y)
00240 export    TEXT_OFFSET GZFLAGS MMUEXT

而textofs-y的定义为:

00118 textofs-y    := 0x08

因此KERNEL_RAM_PADDR的值就为0x58,而r4的值就为0x54。

    225行,r0 = r4。

    226行,r3 = 0。

    227行,r6 = r0 + 0x4,即0x58。

    228到233行,将0x54开始到0x58这段内存清零。

235行,别忘了r10存的是struct proc_info_list变量的起始地址。这里将其__cpu_mm_mmu_flags成员的值赋给r7。

在分析下面的代码之前,先了解点预备知识。我们知道MMU的主要作用是将虚拟地址转换为物理地址,但是虚拟地址与物理地址的转换关系需要我们预先设置好(就是设置页表项),而转换的过程需要通过页表来完成。对于ARM来说,映射大体分为段映射和二级映射,段映射只需要一级页表,段映射的大小为1MB,二级映射需要两级页表。下面分析的代码都只用到段映射,因此只介绍段映射。

如图1所示(以ARM9为例),根据上面的分析可知,寄存器r4里存放的是一级页表的基地址,当启动MMU后,CPU发出的是虚拟地址(正确来说是修正后的虚拟地址,即MVA),然后MMU利用该地址的最高12位(MVA[31:20])做为索引值,以一级页表基地址作为起始地址索引对应的页表项,当索引到相应的页表项后,根据页表项的内容找到对应的大小为1MB的起始物理地址,然后利用MVA的低20位(MVA[19:0])索引确切的物理地址(精确到1个字节)。

ARM Linux启动代码分析

                                                         图1 段映射

具体过程如图2所示,关键看图中的虚线部分,由于页表项的大小为4字节,因此最低两位为0,也即4字节对齐,根据虚线里的值就可以找到相应页表项的起始地址。从图中也可以知道页表基地址是16KB对齐的(最低14位为0)。

ARM Linux启动代码分析

                                                              图2 获取一级描述符

有了上面的基础知识后就可以继续分析代码了。

    243行,r6 = pc,保存当前PC的值。

    244行,r6 = r6 >> 20。

    245行,r3 = r7 (r6 << 20)。此时,r3的值就是一个页表项的内容,也即段描述符。从这就可以知道244行的作用是清零r6的低20位。

    246行,mem[r4 + r6 << 2] = r3,刚好与图2中的虚线部分对应。将r3的值存到页表相应的位置里,这样就完成了一个页表项的构建,也即完成了内核前1MB的映射。因为这里直接使用物理地址作为索引,所以虚拟地址与物理地址是直接映射关系,比如说虚拟地址0x58对应的物理地址也是0x58。后面会看到,这样做是为了开启MMU之后不用考虑太多的事情。

    252行,r0 = r4 + (KERNEL_START & 0xff) >> 18,KERNEL_START的定义如下:

55 #define KERNEL_START   KERNEL_RAM_VADDR

而KERNEL_RAM_VADDR的定义为:

29 #define KERNEL_RAM_VADDR  (PAGE_OFFSET + TEXT_OFFSET)

PAGE_OFFSET的值板子对应的config文件里定义,这里为0xC0,因此KERNEL_START = 0xC0  + 0x08。

    253行,mem[r0 + (KERNEL_START & 0x00f00) >> 18] = r3和r0 = r0 + (KERNEL_START & 0x00f00) >> 18。其实252行253行的意思就是mem[r4 + (0xC8 & 0xfff00) >> 18] = r3,即将内核的前1MB映射到以0xC8为起始的虚拟内存处。

    254行,r6 = KERNEL_END – 1,KERNEL_END的定义为:

56 #define KERNEL_END _end

而_end在arch/arm/kernel/vmlinux.lds.S中定义,表示的是内核Image的结束链接地址。

    255行,r0 = r0 + 4,即下一个页表项的起始地址。

    256行,r6 = r4 + r6 >> 18。

    257行,比较r0,r6的值,并根据结果影响标志位。

     258行,r3 = r3 + 1 << 20,即将r3的值加1MB。

    259行,如果257行r0 <= r6的值就执行次句,mem[r0] = r3,r0 = r0 + 4。

    260行,如果257行r0 <= r6的值就执行此句,跳转到257行。

    257到260行的作用就是将整个内核Image映射到以0xC8为起始地址的虚拟地址处,如图3所示。

ARM Linux启动代码分析

                                            图3 内核Image映射到虚拟地址

    162行,XIP大概就是说在Flash里执行内核,而不必把内核拷贝到内存里再执行,具体没了解过,在此略过,直接到284行。

    284行,r0 = r4 + PAGE_OFFSET >> 18。

    285行,r6 = r7 ( PHYS_OFFSET & 0xff)。

    289行,mem[r0] = r6,即将物理内存的前1MB映射到0xC0,因为这1MB里存放有bootloader传过来的启动参数,从这可以看到,映射的虚拟地址存在重叠,但并没有关系,一个虚拟地址肯定只对应一个物理地址,但一个物理地址可以对应多个虚拟地址。

    291行,看名字就知道是与调试有关的,因此不分析,直接到332行,子程序返回,至此__create_page_tables分析完毕。

    98行,r13 = __switch_data的地址,等会再分析__switch_data的内容。

    100行,lr = __enable_mmu的物理地址。

    101行,pc = r10 + PROCINFO_INITFUNC,跳到struct proc_info_list变量的__cpu_flush成员处,从arch/arm/mm/proc-v6.S文件中可以知道,那里放的是一条跳转指令:b __v6_setup。__v6_setup也是在proc-v6.S中文件中定义:

00157 __v6_setup:
00158 #ifdef CONFIG_SMP
00159     mrc    p15, 0, r0, c1, c0, 1        @ Enable SMP/nAMP mode
00160     orr    r0, r0, #0x20
00161     mcr    p15, 0, r0, c1, c0, 1
00162 #endif
00163 
00164     mov    r0, #0
00165     mcr    p15, 0, r0, c7, c14, 0        @ clean+invalidate D cache
00166     mcr    p15, 0, r0, c7, c5, 0        @ invalidate I cache
00167     mcr    p15, 0, r0, c7, c15, 0        @ clean+invalidate cache
00168     mcr    p15, 0, r0, c7, c10, 4        @ drain write buffer
00169 #ifdef CONFIG_MMU
00170     mcr    p15, 0, r0, c8, c7, 0        @ invalidate I + D TLBs
00171     mcr    p15, 0, r0, c2, c0, 2        @ TTB control register
00172     orr    r4, r4, #TTB_FLAGS
00173     mcr    p15, 0, r4, c2, c0, 1        @ load TTB1
00174 #endif /* CONFIG_MMU */
00175     adr    r5, v6_crval
00176     ldmia    r5, {r5, r6}
00177 #ifdef CONFIG_CPU_ENDIAN_BE8
00178     orr    r6, r6, #1 << 25        @ big-endian page tables
00179 #endif
00180     mrc    p15, 0, r0, c1, c0, 0        @ read control register
00181     bic    r0, r0, r5            @ clear bits them
00182     orr    r0, r0, r6            @ set them
00183     mov    pc, lr                @ return to head.S:__ret

    158到162行,如果CPU是双核以上的,那么就使能多核模式。

    164到168行,失能数据Cache、指令cache和write buffer。

    169到174行,如果支持MMU,那么失能数据和指令TLB,将r4或上TTB_FLAGS之后写入到TTB1寄存器。

    175行,取得v6_crval标号的物理地址,v6_crval的定义:

00191     .type    v6_crval, #object
00192 v6_crval:
00193     crval    clear=0x01e0fb7f, mmuset=0x00c0387d, ucset=0x00c0187c

其中crval是一个宏,定义如下:

.macro    crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
    .word    \clear
    .word    \mmuset
#else
    .word    \clear
    .word    \ucset
#endif
    .endm

这里假设是支持MMU的,因此v6_crval标号的定义替换为:

v6_crval:
.word 0x01e0fb7f
.word 0x00c0387d

    176行,r5 = 0x01e0fb7f,r6 = 0x00c0387d

    177到179行,大端模式相关,现在大部分CPU都工作在小端模式。

    180行,读控制寄存器的值。

    181行,r0 = r0 & (~r5)。

    182行,r0 = r0 r6。

    183行,返回,注意,这里lr的值为__enable_mmu标号的物理地址,因为返回到__enable_mmu标号处执行,至此__v6_setup分析完毕,下面看__enable_mmu。

00160 __enable_mmu:
00161 #ifdef CONFIG_ALIGNMENT_TRAP
00162     orr    r0, r0, #CR_A
00163 #else
00164     bic    r0, r0, #CR_A
00165 #endif
00166 #ifdef CONFIG_CPU_DCACHE_DISABLE
00167     bic    r0, r0, #CR_C
00168 #endif
00169 #ifdef CONFIG_CPU_BPREDICT_DISABLE
00170     bic    r0, r0, #CR_Z
00171 #endif
00172 #ifdef CONFIG_CPU_ICACHE_DISABLE
00173     bic    r0, r0, #CR_I
00174 #endif
00175     mov    r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER)  \
00176               domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER)  \
00177               domain_val(DOMAIN_TABLE, DOMAIN_MANAGER)  \
00178               domain_val(DOMAIN_IO, DOMAIN_CLIENT))
00179     mcr    p15, 0, r5, c3, c0, 0        @ load domain access register
00180     mcr    p15, 0, r4, c2, c0, 0        @ load page table pointer
00181     b    __turn_mmu_on
00182 ENDPROC(__enable_mmu)

161到174行,根据配置设置相应的位,不说了。

175到179行,设置域存取寄存器。

180行,设置TTB寄存器。

181行,跳到__turn_mmu_on标号处。

00196 __turn_mmu_on:
00197     mov    r0, r0
00198     mcr    p15, 0, r0, c1, c0, 0        @ write control reg
00199     mrc    p15, 0, r3, c0, c0, 0        @ read id reg
00200     mov    r3, r3
00201     mov    r3, r13
00202     mov    pc, r3
00203 ENDPROC(__turn_mmu_on)

需要注意的是在执行200行时,MMU已经开启,CPU以后发出的都是虚拟地址。201行,r3 = r13,而r13的值为__switch_data标号的绝对地址(虚拟地址),因此202行就跳到__switch_data标号处。

19     .type    __switch_data, %object
20 __switch_data:
21     .long    __mmap_switched
22     .long    __data_loc            @ r4
23     .long    _data                @ r5
24     .long    __bss_start            @ r6
25     .long    _end                @ r7
26     .long    processor_id            @ r4
27     .long    __machine_arch_type        @ r5
28     .long    __atags_pointer            @ r6
29     .long    cr_alignment            @ r7
30     .long    init_thread_union + THREAD_START_SP @ sp

取出21行的代码执行,也即跳转到__mmap_switched标号处。

41 __mmap_switched:
42     adr    r3, __switch_data + 4
43 
44     ldmia    r3!, {r4, r5, r6, r7}
45     cmp    r4, r5                @ Copy data segment if needed
46 1:    cmpne    r5, r6
47     ldrne    fp, [r4], #4
48     strne    fp, [r5], #4
49     bne    1b
50 
51     mov    fp, #0                @ Clear BSS (and zero fp)
52 1:    cmp    r6, r7
53     strcc    fp, [r6],#4
54     bcc    1b
55 
56  ARM(    ldmia    r3, {r4, r5, r6, r7, sp})
57  THUMB(    ldmia    r3, {r4, r5, r6, r7}    )
58  THUMB(    ldr    sp, [r3, #16]        )
59     str    r9, [r4]            @ Save processor ID
60     str    r1, [r5]            @ Save machine type
61     str    r2, [r6]            @ Save atags pointer
62     bic    r4, r0, #CR_A            @ Clear 'A' bit
63     stmia    r7, {r0, r4}            @ Save control register values
64     b    start_kernel
65 ENDPROC(__mmap_switched)

    42行,获得__switch_data + 4的地址。

    44行,将__data_loc的地址存到r4,_data的地址存到r5,__bss_start的地址存到r6,_end的地址存到r7。

    45行,比较r4和r5的值,对于XIP,它们是不相等,这里显然是相等的,因此46到49行都不执行。

    51到54行,清BSS段。

    56行,r4 = processor_id,r5 = __machine_arch_type,r6 = __atags_pointer,r7 = cr_alignment,sp = init_thread_union + THREAD_START_SP。

    57、58行,是对于Thumb状态的,这里啥也没做。

    59到61行,将值存到对应的地址上。

    62行,清掉r0的’A’位然后存到r4,该位表示数据存取是否需要对齐。

    63行,保存r0,r4的值。

    64行,start_kernel,欢呼吧……

 

关键字:ARM  Linux  启动代码 引用地址:ARM Linux启动代码分析

上一篇:arm linux 启动之一:汇编启动到start_kernel
下一篇:装载ARM Linux内核启动过程

推荐阅读最新更新时间:2024-03-16 14:57

ARM-S3C2440启动文件init.s解析
简介:Arm上电时处于ARM状态,故无论指令为ARM集或Thumb集,都先强制成ARM集,待init.s初始化完成后 ;再根据用户的编译配置转换成相应的指令模式。 ========================================= ; NAME: 2440INIT.S ; DESC: C start up codes ;Configure memory, ISR ,stacks ; Initialize C-variables ;完全注释 ; HISTORY: ; 2002.02.25:kwtark: ver 0.0 ; 2002.03.20:purnnamu: Add some functions for t
[单片机]
快速学Arm——存储器加速模块(MAM)2
我们再来看一下LPC2300 ARM的MAM工作模式.LPC2300系列ARM允许用户设置MAM的加速级别,使芯片应用于某些对功耗和可预见性有要求的场合.MAM定义了如下的三种工作模式: 从图中可以看出MAM功能会使系统耗费更多的能量,但预见性会提高. MAM部分使能: CPU顺序执行时所需要的代码由缓冲区提供,但是成像跳转后需要对Flash进行读操作.此外,若数据缓冲区中的数据可用,则从其中获取数据.但是,为了保证可预见性,同时也为了不增加功耗,MAM还是会虚拟一次对Flash的读操作.该模式下,CPU具有较好信号,较低的功耗,数据的可预见性也比较好.至于是如虚拟的,我目前还不清楚. MAM完全使能: CPU
[单片机]
快速学<font color='red'>Arm</font>——存储器加速模块(MAM)2
ARM异常中断返回的几种情况
异常中断返回的几种情况: 重要基础知识:R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行” 的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考 点”,称之为当前第一条指令,因此 PC总是指向第三条指令。当 ARM 状态时,每条指令 为 4 字节长,所以 PC 始终指向该指令地址加 8 字节的地址,即:PC 值=当前程序执行 位置+8; 而 ADS 中的 pc,是为了调试看着方便而修改过的,它指向的是正在执行的指令,即 “真正 pc-8”! 1.SWI 和和未定义指令异常中断的返回: 指令地址 A PC-8 当前指令为 SWI 或未定义指令 此时发生中断.PC 的值还没有
[单片机]
DIALOG SEMICONDUCTOR面向ARM®四核应用处理器推出最强大的电源管理芯片
德国Kirchheim/Teck – 2012年11月8日- 电源管理、音频和近距离无线高集成度技术供应商Dialog 半导体有限公司(FWB:DLG)日前宣布:推出其面向ARM®四核和双核应用处理器的最强大、最高集成度的可配置电源管理芯片(PMIC)。新推出的DA9063中的六个DC/DC转换器可提供高达12A的电流,比其最接近的其它竞争产品要高出24%。它可同时为处理器(高达5A的电流输出可满足处理器及协处理器的电源需求)、外部存储器、无线通信(WLAN和蓝牙)、GPS、调频(FM)收音机和数据调制解调器供电。其DC/DC转换器可同时提供3A和5A的电源输出。这意味着该PMIC具有可扩展性,并能适应各种智能手机、平板电脑和
[单片机]
DIALOG SEMICONDUCTOR面向<font color='red'>ARM</font>®四核应用处理器推出最强大的电源管理芯片
快速学Arm(35)--存储器加速模块(1)
LPC2300系列ARM内部有128bit宽度的Flash存储器,为了对该存储器进行管理,在芯片内部集成了存储器加上模块(MAM).MAM为用户调整Flash操作的参数提供了接口. LPC2300系列ARM只是有一组Flash存储器,它包含3个128位的缓冲区: .预取指缓冲区 .分支跟踪缓冲区 .数据缓冲区. CPU对内部Flash取指和读取数据的操作时受到MAM制约的.例如,若MAM打开了,那么在CPU取指之前,MAM会判断要取出的这条指令是否在缓冲区内,若不存在,MAM会从Flash存储器读取一个指令行到缓冲区中. 介绍一下术语: 取 值 :指Arm发出的直接读取Flash的请求. 预取指 :指对当前处
[单片机]
快速学<font color='red'>Arm</font>(35)--存储器加速模块(1)
ARM汇编之寻址方式
ARM7处理器有两个指令集:32位的ARM指令集,16位的Thumb指令集。 1 ARM指令集:效率高,代码密度高 2 Thumb指令集:具有较高的代码密度。 注: 1. 所有的ARM指令集都是有条件执行的,而Thumb指令集仅有一条指令具备条件执行功能。 2. ARM程序和Thumb程序可相互调用,相互之间的状态切换开销几乎为零。 二. ARM处理器寻址方式 寻址方式是指根据指令中给出的地址码字段来实现寻找真实操作数地址的方式。ARM7处理器总共有9中基本的寻址方式。 1. 寄存器寻址 1 操作数的值存放在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器值来操作。 2 MOV R
[单片机]
<font color='red'>ARM</font>汇编之寻址方式
Linux kernel 2.6.36发布支持君正CPU
日前,Linux kernel 2.6.36版本正式发布,值得注意的是,该版本支持君正Jz4740国产CPU,这是Linux内核首次支持国产芯片,标志着国产CPU平台已获得国际开源组织的认可和肯定,为君正CPU跨入国际主流嵌入式处理器行列打下了坚实的基础。 Jz4740芯片是君正推出的一款高性能、低功耗的32位嵌入式处理器,采用君正自主创新XBurst CPU内核,支持RTOS、Linux和WinCE等操作系统,被广泛应用于PMP/MP4、教育电子、电子书、指纹识别等移动设备和嵌入式设备。Jz4740是国内首款支持Rm/Rmvb解码的MP4主控芯片,曾引领国内MP4产业全面进入Real解码时代;在电子书市场,Jz4740
[嵌入式]
31-ARM指令集
[嵌入式]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved