现在的位置: 首页 > 综合 > 正文

2.6.18-2内核中对S3C2440的引导启动分析

2013年12月17日 ⁄ 综合 ⁄ 共 15459字 ⁄ 字号 评论关闭
这是以前玩Arm的时候写的~
 
 
主要参考了xpl的arm linux kernel 从入口到start_kernel 的代码分析
http://linux.chinaunix.net/bbs/thread-1021226-1-1.html
 
板子:朗成AT2440EVB
内核:2.6.18-2
BootLoader在引导启动内核的时候需要设置3个寄存器
R0 – 0
R1 – 板子的ID号
R2 – 内核的参数链表地址,也就是TAG链表
 
内核在编译之后会进行再连接,连接的脚本在/arch/arm/kernel/vmlinux.lds.S中
 

SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
    . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
    . = PAGE_OFFSET + TEXT_OFFSET;
#endif
    .init : {            /* Init code and data        */
        _stext = .;
            _sinittext = .;
            *(.init.text)
            _einittext = .;
.................................................
}

PAGE_OFFSET为0xC000 0000   是内核空间的虚拟地址起始处
TEXT_OFFSET 为0x8000       是相对于内核空间的代码段起始处偏移值

这里PAGE_OFFSET + TEXT_OFFSET也就是内核代码段起始处的虚拟地址,为0xC000 8000
而在这个地址的代码为_stext
_stext在/arch/arm/kernel/head.S中

    __INIT
    .type    stext, %function
ENTRY(stext)
    //SVC模式,禁止中断和快速中断
    msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE // ensure svc mode
                        // and irqs disabled               
    //MRC p15,0,Rd,c0,c0,0 ; returns ID register
    //用R9保存处理器的ID号
    mrc    p15, 0, r9, c0, c0        // get processor id
    //跳转到__lookup_processor_type
    //并将下一条指令的地址赋给LR寄存器
    bl    __lookup_processor_type        // r5=procinfo r9=cpuid
    //将R5的值赋给R10,同时检测R5的值是否为0
    movs    r10, r5                // invalid processor (r5=0)?
    //为0则跳转到出错处理
    beq    __error_p            // yes, error 'p'
    //不为0则跳转到__lookup_machine_type
    //并将下一条指令的地址赋给LR寄存器
    bl    __lookup_machine_type        // r5=machinfo
    //将R5的值赋给R8,同时检测R5的值是否为0
    movs    r8, r5                // invalid machine (r5=0)?
    //为0则跳转到出错处理
    beq    __error_a            // yes, error 'a'
    //不为0则跳转到__create_page_tables
    //并将下一条指令的地址赋给LR寄存器
    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.
     */

    //将__switch_data处的地址赋给R13
    ldr    r13, __switch_data        // address to jump to after
                        // mmu has been enabled
    //将__enable_mmu处的地址赋给LR寄存器
    adr    lr, __enable_mmu        // return (PIC) address
    //将proc_info_list结构中的__cpu_flush成员的值赋给pc
    //也就是跳转到__cpu_flush中执行
    add    pc, r10, #PROCINFO_INITFUNC

首先是__lookup_processor_type,它负责寻找处理器ID号对应的proc_info_list结构
__lookup_processor_type在arch/arm/kernel/head-common.S中

    .type    __lookup_processor_type, %function
__lookup_processor_type:
    //读取下面标号3处的地址到R3中
    adr    r3, 3f
    //将标号3处地址的内容装载到R5-R7中
    //R7 - .
    //R6 - __proc_info_end
    //R5 - __proc_info_begin
    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_list结构中的内容到R3和R4
    //R3 -cpu_val 
    //R4 -cpu_mask
1:    ldmia    r5, {r3, r4}            // value, mask
    //用R4与上R9,只关注需要的位
    and    r4, r4, r9            // mask wanted bits
    //比较R3和R4是否相等
    teq    r3, r4
    //相等则跳转到下面标号2处
    beq    2f
    //不等则取得下一个proc_info_list结构
    add    r5, r5, #PROC_INFO_SZ        // sizeof(proc_info_list)
    //测试R5和R6是否相等,相等则说明proc_info_list结构历遍完毕
    cmp    r5, r6
    //R5和R6不等则跳转到上面的标号1处
    blo    1b
    //R5和R6相等则将R5设置为0
    mov    r5, #0                // unknown processor
    //将LR寄存器中的值赋给PC
2:    mov    pc, lr
    .long    __proc_info_begin
    .long    __proc_info_end
3:    .long    .
    .long    __arch_info_begin
    .long    __arch_info_end

由于刚进入引导程序,这个时候MMU还有没开启,所以需要手工计算虚拟地址和物理地址之间的差值
__proc_info_begin和__proc_info_end在/arch/arm/kernel/vmlinux.lds.S的连接脚本中,用于标注proc_info_list结构的起始和结束地址
这里处理器为Arm920T,所以对应的proc_info_list结构在/arch/arm/mm/proc-arm920.S中

执行完毕后回到stext,来到__lookup_machine_type,它负责寻找板子ID号对应的machine_desc结构
__lookup_machine_type在arch/arm/kernel/head-common.S中

    .long    __proc_info_begin
    .long    __proc_info_end
3:    .long    .
    .long    __arch_info_begin
    .long    __arch_info_end
    .type    __lookup_machine_type, %function
__lookup_machine_type:
    //将上面标号3处的地址赋给R3
    adr    r3, 3b
    //读取R3中的内容到R4-R6
    //R4 - .
    //R5 - __arch_info_begin
    //R6 - __arch_info_end
    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
    //读取R5所指的machine_desc结构中的machinfo_type成员到R3中
1:    ldr    r3, [r5, #MACHINFO_TYPE]    // get machine type
    //比较R3和R1是否相等
    teq    r3, r1                // matches loader number?
    //相等则跳转到下面的标号2处
    beq    2f                // found
    //不等则将R5指向下一个machine_desc结构
    add    r5, r5, #SIZEOF_MACHINE_DESC    // next machine_desc
    //检测R5和R6是否相等
    cmp    r5, r6
    //不等则跳转到上面的标号1处
    blo    1b
    //相等则将R5赋为0
    mov    r5, #0                // unknown machine
    //将LR寄存器中的值赋给PC
2:    mov    pc, lr

__arch_info_begin和__arch_info_end在/arch/arm/kernel/vmlinux.lds.S的连接脚本中,用于标注machine_desc结构的起始和结束地址
这里板子ID号对应的machine_desc结构在/arch/arm/mach-s3c2410/mach-sbz2440.c中

MACHINE_START(SBZ2440, "SBZ2440")
    .phys_io    = S3C2410_PA_UART,
    .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    .boot_params    = S3C2410_SDRAM_PA + 0x100,

    .init_irq    = s3c24xx_init_irq,
    .map_io        = smdk2440_map_io,
    .init_machine    = smdk2440_machine_init,
    .timer        = &s3c24xx_timer,
MACHINE_END

MACHINE_START和MACHINE_END都是宏,在/include/asm/mach/arch.h中

#define MACHINE_START(_type,_name)            /
static const struct machine_desc __mach_desc_##_type    /
 __attribute_used__                    /
 __attribute__((__section__(".arch.info.init"))) = {    /
    .nr        = MACH_TYPE_##_type,        /
    .name        = _name,

#define MACHINE_END                /
};

这里machine_desc结构所在的文件是由用户自己编写的,朗成自己改了一个mach-sbz2440.c给AT2440EVB使用

执行完毕后就回到stext,来到__create_page_tables,它负责执行第一阶段,也就是内核引导阶段所要使用的分页初始化
__create_page_tables在/arch/arm/kernel/head.S中

    .type    __create_page_tables, %function
__create_page_tables:
    //    .macro    pgtbl, rd
    //    ldr    /rd, =(__virt_to_phys(KERNEL_RAM_ADDR - 0x4000))
    //    .endm
    //#define __virt_to_phys(x)    ((x) - PAGE_OFFSET + PHYS_OFFSET)
    //PAGE_OFFSET = 0xC000 0000
    //PHYS_OFFSET = 0x3000 0000
    //#define KERNEL_RAM_ADDR    (PAGE_OFFSET + TEXT_OFFSET)
    //PAGE_OFFSET = 0xC000 0000
    //TEXT_OFFSET = 0x8000
    //R4 = 0x3000 4000
    pgtbl    r4                // page table address
    /*
     * Clear the 16K level 1 swapper page table
     */

     //将R4的值赋给R0
    mov    r0, r4
    //将R3设为0
    mov    r3, #0
    //R6 = R0 + 0x4000
    //R6 = 0x3000 8000
    add    r6, r0, #0x4000
    //将0x30004000 - 0x30008000区域的值清零
    //将R3的值赋给R0所指的地址,并且R0的值自加4
1:    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    //当R0 = 0x30008000时初始化完毕
    teq    r0, r6
    //R0未到达0x30008000时则返回上面的标号1处继续初始化
    bne    1b
    //读取proc_info_list结构中的__cpu_mm_mmu_flags成员到R7中
    //这个值为0xC1D
    ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] // mm_mmuflags
    /*
     * Create identity mapping for first MB of kernel to
     * cater for the MMU enable. This identity mapping
     * will be removed by paging_init(). We use our current program
     * counter to determine corresponding section base address.
     */

    //将PC寄存器的值向右移20位,取得高12位赋给R6
    //这里R6为0x300,因为将内核解压到了物理地址0x3000 8000,则PC的最高12位为0x300
    mov    r6, pc, lsr #20            // start of kernel section
    //将R6的值向左移20位后或上R7保存在R3中
    orr    r3, r7, r6, lsl #20        // flags + kernel base
    //[0x3000 4000] = 0x3000 0C1D
    str    r3, [r4, r6, lsl #2]        // identity mapping
    /*
     * Now setup the pagetables for our kernel direct
     * mapped region. We round TEXTADDR down to the
     * nearest megabyte boundary. It is assumed that
     * the kernel fits within 4 contigous 1MB sections.
     */

    //PAGE_OFFSET = 0xC000 0000
    //TEXT_OFFSET = 0x8000
    //#define KERNEL_RAM_ADDR    (PAGE_OFFSET + TEXT_OFFSET)
    //#define TEXTADDR KERNEL_RAM_ADDR
    add    r0, r4, #(TEXTADDR & 0xff000000) >> 18    // start of kernel
    //[0x3000 7000] = 0x3000 0C1D
    str    r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
    add    r3, r3, #1 << 20
    //[0x3000 7004] = 0x3010 0C1D
    str    r3, [r0, #4]!            // KERNEL + 1MB
    add    r3, r3, #1 << 20
    //[0x3000 7008] = 0x3020 0C1D
    str    r3, [r0, #4]!            // KERNEL + 2MB
    add    r3, r3, #1 << 20
    //[0x3000 700C] = 0x3030 0C1D
    str    r3, [r0, #4]            // KERNEL + 3MB
    /*
     * Then map first 1MB of ram in case it contains our boot params.
     */

    //R0 = 0x3000 4000 + 0x3000
    add    r0, r4, #PAGE_OFFSET >> 18
    //R6 = R7 | 0x3000 0000
    orr    r6, r7, #PHYS_OFFSET
    //[0x3000 7000] = 0x3000 0C1D
    str    r6, [r0]
    mov    pc, lr

上面代码中还有一部分宏判断语句,因为这里不会执行,我就不贴出来了

PAGE_OFFSET为0xC000 0000   是内核空间的虚拟地址起始处
TEXT_OFFSET 为0x8000       是相对于内核空间的代码段起始处偏移值
PHYS_OFFSET 为0x3000 0000   是RAM所在的BANK物理地址的起始处
这是我的板子上的设置,因为RAM是接在了BANK6上,而BANK6的起始地址为0x3000 0000,所以PHYS_OFFSET 为0x3000 0000

小结一下,这里将物理地址0x3000 4000 – 0x3000 8000处的内容全部清0
然后设置了以下地址的描述符
[0x3000 4000] = 0x3000 0C1D
[0x3000 7000] = 0x3000 0C1D
[0x3000 7004] = 0x3010 0C1D
[0x3000 7008] = 0x3020 0C1D
[0x3000 700C] = 0x3030 0C1D

__create_page_tables执行完后回到stext中,接下来是以下3步
 //将__switch_data处的地址赋给R13
 ldr r13, __switch_data       
 //将__enable_mmu处的地址赋给LR寄存器
 adr lr, __enable_mmu  
 //将proc_info_list结构中的__cpu_flush成员的值赋给pc
 //也就是跳转到__cpu_flush中执行
 add pc, r10, #PROCINFO_INITFUNC

__enable_mmu和__switch_data等用到的时候再说
现在先来看看add pc, r10, #PROCINFO_INITFUNC
R10在之前指向了ARM920所对应的proc_info_list结构
这个结构在/arch/arm/mm/proc-arm920.S中
结构如下:

__arm920_proc_info:
//cpu_val
    .long    0x41009200
//cpu_mask
    .long    0xff00fff0
//__cpu_mm_mmu_flags
    .long PMD_TYPE_SECT | /
        PMD_SECT_BUFFERABLE | /
        PMD_SECT_CACHEABLE | /
        PMD_BIT4 | /
        PMD_SECT_AP_WRITE | /
        PMD_SECT_AP_READ
//__cpu_io_mmu_flags
    .long PMD_TYPE_SECT | /
        PMD_BIT4 | /
        PMD_SECT_AP_WRITE | /
        PMD_SECT_AP_READ
//__cpu_flush
    b    __arm920_setup
//arch_name
    .long    cpu_arch_name
//elf_name
    .long    cpu_elf_name
//elf_hwcap
    .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
//cpu_name
    .long    cpu_arm920_name
//proc
    .long    arm920_processor_functions
//tlb
    .long    v4wbi_tlb_fns
//user
    .long    v4wb_user_fns
//cache
#ifndef CONFIG_CPU_DCACHE_WRITETHROUGH
    .long    arm920_cache_fns
#else
    .long    v4wt_cache_fns

对应的结构声明在/include/asm-arm/procinfo.h中

这里PROCINFO_INITFUNC取的是__cpu_flush,也就是__arm920_setup

__arm920_setup在/arch/arm/mm/proc-arm920.S中,如下:

    __INIT
    .type    __arm920_setup, #function
__arm920_setup:
    mov    r0, #0
    //Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0
    mcr    p15, 0, r0, c7, c7        // invalidate I,D caches on v4
    //Drain write buffer SBZ MCR p15,0,Rd,c7,c10,4
    //Stops execution until the write buffer has drained.
    mcr    p15, 0, r0, c7, c10, 4        // drain write buffer on v4
#ifdef CONFIG_MMU
    //Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0
    mcr    p15, 0, r0, c8, c7        // invalidate I,D TLBs on v4
#endif
    //加载下面标号为arm920_crval的地址
    adr    r5, arm920_crval
    //加载R5所指的地址内容到R5和R6中
    //R5 - clear
    //当CONFIG_MMU为真时
    //R6 - mmuset
    //当CONFIG_MMU为假时
    //R6 - ucset
    ldmia    r5, {r5, r6}
    //MRC p15, 0, Rd, c1, c0, 0 ; read control register
    //读取控制寄存器信息到R0中
    mrc    p15, 0, r0, c1, c0        // get control register v4
    //清除不需要的位
    bic    r0, r0, r5
    //置需要的位为真
    orr    r0, r0, r6
    mov    pc, lr
    .size    __arm920_setup, . - __arm920_setup
    /*
     * R
     * .RVI ZFRS BLDP WCAM
     * ..11 0001 ..11 0101
     *
     */

    .type    arm920_crval, #object
arm920_crval:
//    .macro    crval, clear, mmuset, ucset
//#ifdef CONFIG_MMU
//    .word    /clear
//    .word    /mmuset
//#else
//    .word    /clear
//    .word    /ucset
//#endif
//    .endm
    crval    clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130

SBZ的意思为0,这里也就是需要的参数为0,所以需要先把R0置0
crval是一个宏
 .macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
 .word /clear
 .word /mmuset
#else
 .word /clear
 .word /ucset
#endif
 .endm

当CONFIG_MMU为真时则
arm920_crval:
.word 0x00003f3f
.word 0x00003135
为假时则
arm920_crval:
.word 0x00003f3f
.word 0x00001130

最后执行mov pc, lr
在之前内核将LR设为了__enable_mmu
__enable_mmu在/arch/arm/kernel/head.S中,如下

    .type    __enable_mmu, %function
__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
//#define domain_val(dom,type)    ((type) << (2*(dom)))
// #define DOMAIN_KERNEL    2
//#define DOMAIN_TABLE    2
//#define DOMAIN_USER    1
//#define DOMAIN_IO    0
//#define DOMAIN_MANAGER 3
//#define DOMAIN_CLIENT 1
    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, Rd, c3, c0, 0 ; write domain 15:0 access permissions
    mcr    p15, 0, r5, c3, c0, 0        // load domain access register
    //MCR p15, 0, Rd, c2, c0, 0 ; write TTB register
    //填写基地址
    //R4在之前设置为了0x3000 4000
    mcr    p15, 0, r4, c2, c0, 0        // load page table pointer
    b    __turn_mmu_on

 

主要就是填写了节基地址寄存器,设置基地址为0x3000 4000
然后转到__turn_mmu_on

__turn_mmu_on也在/arch/arm/kernel/head.S中,如下

__turn_mmu_on:
    mov    r0, r0
    //MCR p15, 0, Rd, c1, c0, 0 ; write control register
    mcr    p15, 0, r0, c1, c0, 0        // write control reg
    //MRC p15,0,Rd,c0,c0,0 ; returns ID register
    mrc    p15, 0, r3, c0, c0, 0        // read id reg
    mov    r3, r3
    mov    r3, r3
    mov    pc, r13

ARM9是5级流水线,分别为
1. 取指
2. 译码
3. 执行
4. 缓冲
5. 回写

第一步mov r0, r0和之前的b __turn_mmu_on一起考虑
在之前的mcr p15, 0, r4, c2, c0, 0 的指令中会装载节基地址,但是这个时候只是取指,到执行还需要2个指令周期, b __turn_mmu_on是第一个指令周期,所以还需要mov  r0, r0做第二个指令周期来让mcr p15, 0, r4, c2, c0, 0得以真正的执行

下面的mov r3, r3同理

最后mov pc, r13
R13在之前设置为__switch_data

__switch_data在/arch/arm/kernel/head-common.S中,如下:

    .type    __switch_data, %object
__switch_data:
    .long    __mmap_switched
    .long    __data_loc            // r4
    .long    __data_start            // r5
    .long    __bss_start            // r6
    .long    _end                // r7
    .long    processor_id            // r4
    .long    __machine_arch_type        // r5
    .long    cr_alignment            // r6
    .long    init_thread_union + THREAD_START_SP // sp

R13中就是__mmap_switched的地址, mov pc, r13等于去执行__mmap_switched所指的指令

__mmap_switched在/arch/arm/kernel/head-common.S中,如下:

    .type    __mmap_switched, %function
__mmap_switched:
    //加载__switch_data+4处的地址给R3
    //也就是__data_loc的地址
    adr    r3, __switch_data + 4
    //加载R3处的内容给R4-R7
    //并且将地址回写到R3,最后R3指向processor_id
    //R4 - __data_loc 数据存放的位置
    //R5 - __data_start 数据开始的位置
    //R6 - __bss_start BSS段开始的位置
    //R7 - _end BSS段结束位位置,也是内核结束的位置
    ldmia     {r4, r5, r6, r7}
    //检测__data_loc和__data_start是否相等
    cmp    r4, r5                // Copy data segment if needed
    //不等则执行拷贝
    //将__data_loc开始处的内容拷贝到__data_start开始的位置
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b
    //将FP指针置0
    mov    fp, #0                // Clear BSS (and zero fp)
    //将__bss_start到_end中的内容清0
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b
    //加载R3处的内容给R4-R6,SP
    //R4 - processor_id
    //R5 - __machine_arch_type
    //R6 - cr_alignment
    //SP - init_thread_union + THREAD_START_SP
    ldmia    r3, {r4, r5, r6, sp}
    //将R9中的值保存到processor_id
    //也就是保存处理器ID号
    str    r9, [r4]            // Save processor ID
    //将R1中的值保存到__machine_arch_type
    //也就是保存板子的ID号
    str    r1, [r5]            // Save machine type
    //清除R0中的A位后保存到R4中
    bic    r4, r0, #CR_A            // Clear 'A' bit
    //将R0和R4中的值保存到R6所指的地址
    //R6所指的地址在arch/arm/kernel/entry-armv.S
    //    .globl    cr_alignment
    //    .globl    cr_no_alignment
    //cr_alignment:
    //    .space    4
    //cr_no_alignment:
    //    .space    4
    //cr_alignment <-R0
    //cr_no_alignment <-R4
    stmia    r6, {r0, r4}            // Save control register values
    //进入到start_kernel
    b    start_kernel

注释都有了~ 最后就是跳转到start_kernel,进行第二阶段,也就是内核的初始化

下面对ARM的分页进行一下介绍
ARM的分页分为两层,第一层为必选,称为分节,将内存分为每个1MB的区域,第二层为可选,是将第一层中的1MB区域再进行划分成1KB,4KB或者64KB大小的页

引导启动中只使用了第一层分节,未使用第二层分页,下图描述了分节的取址
 

抱歉!评论已关闭.