Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15531861
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: LINUX

2007-07-25 18:57:15

浅析armlinux2.4.19启动程序[head-armv.s文件]

文章来源:http://gliethttp.cublog.cn

接续《浅析arm-linux中断vector向量表的建立流程》,分析head-armv.s启动文件
硬件环境描述:[gliethttp 2007-07-25]
    1>自制at91rm9200开发板一块[gliethttp]
    2>32M-sdram总线地址范围0x20000000~0x22000000
    3>u-boot将经过u-boot专用压缩程序mkimage,压缩的vmlinux内核文件
      解压到0x20008000处,并把0x20008000作为内核入口地址,
      跳转到stext第一句mov r12,r0正式执行linux内核程序
      u-boot1.1.5/lib_arm/armlinux.c->do_bootm_linux()->
      theKernel(0, bd->bi_arch_number, bd->bi_boot_params);
1.先来看看编译链接脚本arch/arm/vmlinux-armv.lds.in
OUTPUT_ARCH(arm)        //arm体系
ENTRY(stext)            //stext程序为入口点
SECTIONS
{
//TEXTADDR的定义在arch/arm/Makefile中,部分内容如下:
//ifeq ($(CONFIG_CPU_32),y)
//PROCESSOR    = armv
//TEXTADDR     = 0xC0008000
//LDSCRIPT     = arch/arm/vmlinux-armv.lds.in
//endif
    . = TEXTADDR;  //编译器使用的偏移地址从TEXTADDR=0xC0008000开始
    .init : {
        _stext = .;//编译起始地址为0xC0008000,然后生成的代码按如下的顺序依次存放
        __init_begin = .;
            *(.text.init)
        __proc_info_begin = .;
            *(.proc.info)
        __proc_info_end = .;
        __arch_info_begin = .;
            *(.arch.info)
        __arch_info_end = .;
        __tagtable_begin = .;
            *(.taglist)
        __tagtable_end = .;
            *(.data.init)
        . = ALIGN(16);
        __setup_start = .;
            *(.setup.init)
        __setup_end = .;
        __initcall_start = .;
            *(.initcall.init)
        __initcall_end = .;
        . = ALIGN(4096);
        __init_end = .;
    }
    ......
}
2.head-armv.s文件分析[gliethttp 2007-07-25]
#if (TEXTADDR & 0xffff) != 0x8000//如果定义地址TEXTADDR非0x8000对齐,编译器报错退出

#error TEXTADDR must start at 0xXXXX8000
#endif

.globl    SYMBOL_NAME(swapper_pg_dir)
.equ    SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000

.macro    pgtbl, reg, rambase
adr    \reg, stext //伪指令adr,读取与pc相对的stext物理地址
sub    \reg, \reg, #0x4000//reg存放值为stext物理地址下的16k空间,对于我的板子来说reg=0x20004000
.endm

.macro    krnladr, rd, pgtable, rambase
bic    \rd, \pgtable, #0x000ff000
.endm

        .section ".text.init",#alloc,#execinstr
        .type    stext, #function
ENTRY(stext)
//++++++++++++++
//由u-boot1.1.5传递函数theKernel(0, bd->bi_arch_number, bd->bi_boot_params);
//可知
//r0 = 0;
//r1 = bd->bi_arch_number
//[在uboot-board_init()中初始化得gd->bd->bi_arch_number = MACH_TYPE_AT91RM9200;]
//#define MACH_TYPE_AT91RM9200 251和linux中的一样
//r2 = bd->bi_boot_params
//[在uboot-board_init()中初始化得gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;]
//#define PHYS_SDRAM 0x20000000 所以从0x2000100地址开始存放linux启动参数,
//linux的setup_arch()中mdesc->param_offset指定的0x2000100开始的tag list参数串
//因为固定地址已经相同,所以传不传r2参数已经无所谓了[gliethttp]
//--------------
    mov    r12, r0
    mov    r0, #F_BIT | I_BIT | MODE_SVC
    msr    cpsr_c, r0            //arm9禁止IRQ和FIQ中断,同时进入SVC超级管理模式
    bl    __lookup_processor_type//匹配由cp15协处理器读出的cpu型号,分析见后面[gliethttp]
//执行__lookup_processor_type处理器匹配函数之后
//r8 = 建立4个1M节MMU页表的标志(0x00000c1e)
//r9 = 处理器ID
//r10 = 处理器结构体的指针
    teq    r10, #0        
    moveq  r0, #'p'
    beq    __error//没有找到与硬件cpu匹配的内核,halt系统
    bl    __lookup_architecture_type//查看与r1型号匹配的体系结构,分析见后面[gliethttp]
//执行__lookup_architecture_type体系结构匹配函数之后
//r5 = 体系结构指针下对应的machine_desc->phys_ram物理内存地址
//r6 = 体系结构指针下对应的machine_desc->phys_io物理寄存器地址
//r7 = 体系结构指针下对应的machine_desc->io_pg_offst物理寄存器地址对应的虚拟地址对应的节基址
    teq    r7, #0
    moveq  r0, #'a'
    beq    __error//没有找到与r1匹配的体系结构,halt系统
    bl    __create_page_tables//创建内核启动临时使用的4M页表,但并不启动MMU,分析见后面[gliethttp]
    adr    lr, __ret//伪指令操作,将__ret的物理地址作为lr返回地址
    add    pc, r10, #12//pc装入b __arm920_setup对应的机器码,执行__arm920_setup函数
//++++++++++++++
//arch/arm/mm/proc-arm9200.S[gliethttp 2007-07-25]
//MCR/MRC{cond} p15, opcode_1, Rd, CRn, CRm, opcode_2
//__arm920_setup://小端模式
//mov r0, #0
//mcr p15, 0, r0, c7, c7     //令ICache、DCache缓冲无效
//mcr p15, 0, r0, c7, c10, 4 //耗写缓冲区
//mcr p15, 0, r0, c8, c7     //使指令与数据TLB
//mcr p15, 0, r4, c2, c0     //将一级页表的指针r4送入,TTB转换表基寄存器
//mov r0, #0x1f              //Domains 0,1为管理域,Domains 2为客户域
//mcr p15, 0, r0, c3, c0     //将r0值置入cp15的c3
//mrc p15, 0, r0, c1, c0     //获取当前c1的控制数据到r0中
//bic r0, r0, #0x0e00        //修改MMU的保护系统:1.系统保护2.ROM 保护
//bic r0, r0, #0x0002        //禁用故障校验
//bic r0, r0, #0x000c        //DCache禁用
//bic r0, r0, #0x1000        //ICache禁用
//orr r0, r0, #0x0031        //MMU使能
//orr r0, r0, #0x2100        //异常寄存器基地址定义为:0xFFFF0000;修改系统保护
//#ifndef CONFIG_CPU_DCACHE_DISABLE
//orr r0, r0, #0x0004        //DCache使能
//#endif
//#ifndef CONFIG_CPU_ICACHE_DISABLE
//orr r0, r0, #0x1000        //ICache使能
//#endif
//mov pc, lr                 //返回到__ret继续执行[gliethttp]
//--------------
        .type    __switch_data, %object
__switch_data:.long    __mmap_switched
    .long    SYMBOL_NAME(__bss_start)
    .long    SYMBOL_NAME(_end)
    .long    SYMBOL_NAME(processor_id)
    .long    SYMBOL_NAME(__machine_arch_type)
    .long    SYMBOL_NAME(cr_alignment)
    .long    SYMBOL_NAME(init_task_union)+8192

        .type    __ret, %function
__ret:ldr  lr, __switch_data//__switch_data为返回地址值,起始是__mmap_switched函数地址
    mcr    p15, 0, r0, c1, c0//将控制值r0置入cp15协处理器的c1控制寄存器,使r0中的控制值生效
    mrc    p15, 0, r0, c1, c0, 0//1.nop
    mov    r0, r0//2.nop
    mov    r0, r0//3.nop
    mov    pc, lr//经过上面的3个nop无用执行指令之后,arm数据指令体系的三级流水线已经填充完毕
                 //跳转到__mmap_switched函数继续执行[gliethttp 2007-07-25]
        .align    5
__mmap_switched:
    adr    r3, __switch_data + 4//adr伪指令获取相对pc的跳转地址
    ldmia  r3, {r4, r5, r6, r7, r8, sp}
//r4 = __bss_start
//r5 = _end
//r6 = processor_id
//r7 = __machine_arch_type
//r8 = cr_alignment
//sp = (init_task_union)+8192//对于sp的使用,可以参见《浅析armlinux-sp进程切换栈结构和切换函数__switch_to()》
//以及《 浅析armlinux-sp的孵化流程,1号内核线程init的创建》[http://gliethttp.cublog.cn]
    mov    fp, #0
1:  cmp    r4, r5//清0未初始化变量单元等
    strcc  fp, [r4],#4
    bcc    1b//cc表示如果r4
    str    r9, [r6]//将r9处理器ID值转存入变量processor_id中,供c程序调用
    str    r1, [r7]//将r7体系结构指针的machine_desc转存入变量__machine_arch_type中,供c程序调用
#ifdef CONFIG_ALIGNMENT_TRAP
    orr    r0, r0, #2//r0为cp15协处理器c1中控制值
#endif
    bic    r2, r0, #2//取消掉故障校验后的数值存入r2
    stmia  r8, {r0, r2}//cr_alignment = r0;其后存储(init_task_union)+8192数值的空间存入r2
    b    SYMBOL_NAME(start_kernel)
//好了,我的分析到此结束,由u-boot1.1.5完成解压的启动部分,转入c代码start_kernel,作更复杂的启动[gliethttp 2007-07-25]
__create_page_tables:
//通过前面的分析已知r5 = 体系结构指针下对应的machine_desc->phys_ram物理内存地址
//但pgtbl宏并没有使用到r5,而是直接使用stext,
    pgtbl  r4, r5
//所以此时r4=物理地址=0x2000
    mov    r0, r4
    mov    r3, #0
    add    r2, r0, #0x4000//r2=0x20008000
1:  str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4//将0x20004000~0x20008000这16k空间清0
    teq    r0, r2
    bne    1b
//ok,清0结束[gliethttp]
    krnladr r2, r4, r5//bic r2,r4,#0x000ff000;将ff处对应的1数据清0
//所以r2 = 0x20000000
//通过前面的分析r8=0x00000c1e
//在进行下面的分析之前,最好先看看另一篇文章
//《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》[http://gliethttp.cublog.cn]
//熟悉一下MMU的表结构
//MMU对4种大小进行映射管理,
//1)微页:构成1k的存储区
//2)小页:构成4k的存储区
//3)大页:构成64k的存储区
//4)节:构成1M的存储区
    add    r3, r8, r2
//r2=0x20000000
//r3=0x20000000+0x00000c1e=0x20000c1e
//r4=0x20004000
//r2>>18剩下的高14位,即16K地址,4k个4字节组地址单元,用于一级表映射bit31~bit20节基址匹配
    str    r3, [r4, r2, lsr #18]//r4为转换表基
//>>18位,剩余14位,而不是12位,是高12位节基址*4字节存储地址的结果,是bit31~bit20节基址索引×4字节的结果
//表示虚拟地址节0x20000000~0x200fffff对应物理地址节0x20000000~0x200fffff
    add    r0, r4, #(TEXTADDR & 0xff000000) >> 18
    bic    r2, r3, #0x00f00000
//r2 = 0x20000c1e
    str    r2, [r0]
//表示虚拟地址节0xc0000000~0xc00fffff对应物理地址节0x20000000~0x200fffff
    add    r0, r0, #(TEXTADDR & 0x00f00000) >> 18
//接下来开始依次线性节映射[gliethttp 2007-07-25]
    str    r3, [r0], #4
//表示虚拟地址节0xc0000000~0xc00fffff对应物理地址节0x20000000~0x200fffff
//r3=0x20000c1e表项值,r0=r0+4,r0指向下一个连续的虚拟地址1M节基址
    add    r3, r3, #1 << 20
//r3=r3+1M指向下一物理地址节基址
    str    r3, [r0], #4
//表示虚拟地址节0xc0100000~0xc01fffff对应物理地址节0x20100000~0x201fffff
//r0指向下一个连续的虚拟地址1M节基址
    add    r3, r3, #1 << 20
//r3=r3+1M指向下一物理地址节基址
    str    r3, [r0], #4
//表示虚拟地址节0xc0200000~0xc02fffff对应物理地址节0x20200000~0x202fffff
//r0指向下一个连续的虚拟地址1M节基址
    add    r3, r3, #1 << 20
//r3=r3+1M指向下一物理地址节基址
    str    r3, [r0], #4
//表示虚拟地址节0xc0300000~0xc03fffff对应物理地址节0x20300000~0x203fffff
//r0指向下一个连续的虚拟地址1M节基址
//这样0xc0000000~0xc03fffff 4M虚拟地址空间和0x20000000~0x200fffff 1M虚拟地址空间
//都线性的映射到了0x20000000~0x203fffff 4M物理地址空间
    bic    r0, r0, #0x01f00000 >> 18//0x1f=32M空间对齐
    and    r2, r5, #0xfe000000//对于传递的参数r5-物理内存地址空间
    add    r3, r8, r2
    str    r3, [r0]//最后还是0x20000000~0x200fffff虚拟地址到0x20000000~0x200fffff物理地址映射
//如果r5的值和内核拷贝到的内存中的位置值不符,真不知会是什么想象,不过还好,他们相等.
    bic    r8, r8, #0x0c
#ifdef CONFIG_DEBUG_LL
    add    r0, r4, r7//r7为debug串口显示物理寄存器对应的虚拟地址对应的节基址
    rsb    r3, r7, #0x4000//r3=0x4000-r3=距离0x3fff还有多少个地址空间
    cmp    r3, #0x0800//0xE0000000空间
    addge  r2, r0, #0x0800//r7对应的虚拟空间<=0xE0000000,加上0x20000000,
                           //调整到0xE0000000~0xFFFFFFFF虚拟空间
    addlt    r2, r0, r3//r7对应的虚拟空间>0xE0000000,r2=计算出的io虚拟地址映射的大小
    orr    r3, r6, r8//r6对应串口物理寄存器的物理地址|| r8这个节标志
1:  str    r3, [r0], #4//将虚拟地址r0所在的1M空间映射到物理地址r3所在的1M空间中,之后r0虚拟地址1M节索引r0+4指向下一节
    add    r3, r3, #1 << 20//进入物理地址空间的下一个1M节
    teq    r0, r2//是否映射完毕
    bne    1b//
#if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)
    teq    r1, #MACH_TYPE_NETWINDER//非该类型,是MACH_TYPE_AT91RM9200类型
    teqne    r1, #MACH_TYPE_CATS//也非该类型
    bne    1f//跳到下一个标号1:处
    add    r0, r4, #0x3fc0
    mov    r3, #0x7c000000
    orr    r3, r3, r8
    str    r3, [r0], #4
    add    r3, r3, #1 << 20
    str    r3, [r0], #4
1:
#endif
#endif
#ifdef CONFIG_ARCH_RPC//未定义
    add    r0, r4, #0x80
    mov    r3, #0x02000000
    orr    r3, r3, r8
    str    r3, [r0]
    add    r0, r4, #0x3600
    str    r3, [r0]
#endif
    mov    pc, lr//返回
__error:
#ifdef CONFIG_DEBUG_LL//实现了串口输出,那么将错误信息打印到串口终端
    mov    r8, r0
    adr    r0, err_str
    bl    printascii//打印"\nError: "字符串
    mov    r0, r8
    bl    printch//打印
#endif
#ifdef CONFIG_ARCH_RPC
    mov    r0, #0x02000000
    mov    r3, #0x11
    orr    r3, r3, r3, lsl #8
    orr    r3, r3, r3, lsl #16
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
#endif
1:  mov  r0, r0//系统因为错误halt在此处
    b    1b
#ifdef CONFIG_DEBUG_LL
err_str:    .asciz    "\nError: "
        .align
#endif
__lookup_processor_type:
    adr    r5, 2f//伪指令adr,读取下面第一个2:标号处的和pc相对的物理地址
    ldmia  r5, {r7, r9, r10}
//r7=虚拟地址__proc_info_end
//r9=虚拟地址__proc_info_begin
//r10=虚拟地址2b=标号2处的地址值[虚拟地址-反汇编得0xc00081b8]
//所以r5=0x200081b8
    sub    r5, r5, r10//计算标号2物理地址和虚拟地址的间距r5 = 0x200081b8-0xc00081b8;
    add    r7, r7, r5 //将r7中的线性虚拟地址转换成与之线性对应的物理地址,结果存入r7
    add    r10, r9, r5//同样计算r9中存放的虚拟地址,对应的物理地址,结果存入r10
    mrc    p15, 0, r9, c0, c0//通过协处理器cp15,获取cpu的型号,对于at91rm9200,r9=0x41129200
//r7 = __proc_info_end结束的物理地址
//r9 = 处理器ID
//r10 = 处理器结构体的指针
1:  ldmia  r10, {r5, r6, r8}//读取编译进r10为起始物理地址的__proc_info_begin结构体信息
//++++++++++++++
//arch/arm/mm/proc-arm9200.S中定义了该结构体信息
//    .section ".proc.info", #alloc, #execinstr
//    .type    __arm920_proc_info,#object
//__arm920_proc_info:
//    .long    0x41009200
//    .long    0xff00fff0
//    .long    0x00000c1e            @ mmuflags
//    b    __arm920_setup
//    .long    cpu_arch_name
//    .long    cpu_elf_name
//    .long    HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB
//    .long    cpu_arm920_info
//    .long    arm920_processor_functions
//    .size    __arm920_proc_info, . - __arm920_proc_info
//--------------
//所以r5 = 0x41009200
//r6 = 0xff00fff0
//r8 = 0x00000c1e
    and    r6, r6, r9//r6 = r6 & r9 = 0x41009200
    teq    r5, r6
    moveq  pc, lr//因为处理器类型匹配,返回
    add    r10, r10, #36//如果多处理器,那sizeof(__arm920_proc_info)=0x24=36,r10指向下一个处理器单元
    cmp    r10, r7
    blt    1b//如果没有遍历所有处理器类型,那么跳到标号1:处,继续匹配
    mov    r10, #0//没有找到匹配的处理器
    mov    pc, lr
2:  .long    __proc_info_end
    .long    __proc_info_begin
    .long    2b
    .long    __arch_info_begin
    .long    __arch_info_end
__lookup_architecture_type:
    adr    r4, 2b//伪指令,读取上面第一个2:标号处的和pc相对的物理地址
    ldmia  r4, {r2, r3, r5, r6, r7}
//r2 = 虚拟地址__proc_info_end
//r3 = 虚拟地址__proc_info_begin
//r5 = 虚拟地址标号2:
//r6 = 虚拟地址__arch_info_begin
//r7 = 虚拟地址__arch_info_end
    sub    r5, r4, r5//和__lookup_processor_type一样,计算虚拟地址和物理地址的线性偏移量
    add    r4, r6, r5//r4=__arch_info_begin的物理地址
    add    r7, r7, r5//r7=__arch_info_end的物理地址
//++++++++++++++
//arch/arm/mach-at91rm9200/Core.c
//MACHINE_START(AT91RM9200, "ATMEL AT91RM9200")//.nr = MACH_TYPE_##_type=MACH_TYPE_AT91RM9200=251
//与上面从u-boot传到r1中的一样[gliethttp]
//MAINTAINER("SAN People / ATMEL")
//BOOT_MEM(AT91_SDRAM_BASE, AT91C_BASE_SYS, AT91C_VA_BASE_SYS)
//BOOT_PARAMS(AT91_SDRAM_BASE + 0x100)//tag list存放的物理地址0x20000100[gliethttp]
//FIXUP(at91rm9200_fixup)
//MAPIO(at91rm9200_map_io)
//INITIRQ(at91rm9200_init_irq)
//MACHINE_END
//--------------
1:  ldr    r5, [r4]
    teq    r5, r1//与r1传入的MACH_TYPE_AT91RM9200=251数值匹配
    beq    2f//匹配上,则跳到下一个标号2:处,退出
    add    r4, r4, #SIZEOF_MACHINE_DESC//r4+sizeof(machine_desc),指向下一个体系结构体的物理地址
    cmp    r4, r7
    blt    1b//.arch.info体系结构是否已经遍历结束
    mov    r7, #0//没有找到与r1类型匹配的体系结构
    mov    pc, lr
2:  ldmib  r4, {r5, r6, r7}//先加后装载
//所以r5 = machine_desc->phys_ram
//r6 = machine_desc->phys_io
//r7 = machine_desc->io_pg_offst
    mov    pc, lr

阅读(3675) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~