博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于S3C2440的U-BOOT的start.S分析
阅读量:7259 次
发布时间:2019-06-29

本文共 13035 字,大约阅读时间需要 43 分钟。

基于S3C2440的U-BOOT的start.S分析 在了解了ARM相关的汇编指令后,同时结合网上各位大虾的提点开始阅读u-boot的启动代码,现将分析过程记录如下可执行文件及内存映射我们可以把可执行文件分为2种情况:存放态和运行态1.存放态:可执行文件经过烧到存储介质上(flash或磁盘)的分布,此时可执行文件通常有2部分组成,代码段和数据段,代码段又分为可执行代码段 (.text)和只读数据段(.rodata),数据段可以分为初始化数据段(.data)和未初始化代码段(.bss),如下:+-------------+-----------| .bss        | (ZI)+-------------+-- 数据段| .data    | (RW)+-------------+-----------| .rodata     ||_____________| 代码段(RO)| .text    |+-------------+-----------2.运行态:可执行文件经过装载后就变成为运行态,当可执行文件装载后, 在RAM中的分布如下:| ...       |+-------------+-- ZI段结束地址| ZI 段    |+-------------+-- ZI段起始地址| 保留区2     |+-------------+-- RW段结束地址| RW 段    |+-------------+-- RW段起始地址| 保留区1     |+-------------+-- RO段结束地址| RO 段    |+-------------+-- RO段起始地址所以装载过程必须完成把可执行文件的各个段搬移到RAM的指定位置,这个装载过程则是由启动程序来完成的。而可执行代码在RAM中的地址则是由链接脚本来指定的。一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。这个是有链接脚本来实现的,由此我们可以找到程序 的入口点是在 /board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu /arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们 将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。ARM在CPU加电复位后是从0x0000地址开始取指,因此在零地址需要放置第一条启动代码。默认情况下,程序的链接器是把0x8000作为映像的入口 点(取指的第一条指令的位置),因此 需要对映像链接定位,即重定位映像段的存放,包括代码段、数据段、零区等,对整个系统的代码做正确的定位,这些规则通常写成链接脚本。链接脚本就是提供了 一种把代码段和数据段放在不同存储器定位。我们的只读代码和数据是固化在ROM中(通常在0x0000),但是在执行的时候想在RAM区运行(优化系统,使性能发挥最大),就需要链接定位。链接器告诉了随机存储器从哪里开始。Load View:代码编译链接的一个组织情况Execute View:代码正确执行的空间组织启动过程的C部分1. 初始化MMU2.初始化外部端口3. 中断处理程序表初始化4. 串口初始化5. 其它部分初始化(可选)6. 主程序循环于是我们可以在链接脚本中找到映像的加载地址,也即程序的入口点。/board/s3c2410/U-boot.ldsOUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{. = 0x00000000;                 /*映像的入口点,通常链接器将此地址定位到ROM的0x0地址,必须使编译器知道这个地址*/. = ALIGN(4);.text    :{cpu/arm920t/start.o (.text)*(.text)}. = ALIGN(4);.rodata : { *(.rodata) }. = ALIGN(4);.data : { *(.data) }. = ALIGN(4);.got : { *(.got) }. = .;__u_boot_cmd_start = .;.u_boot_cmd : { *(.u_boot_cmd) }__u_boot_cmd_end = .;. = ALIGN(4);__bss_start = .;.bss : { *(.bss) }_end = .;}从上面可以看出,链接脚本指定了代码段从0x00000000开始,而代码段最开始链接的就是cpu/arm920t/start.o。于是可以知道在CPU加电复位后程序首先是从cpu/arm920t/start.S开始的。1.Stage1:cpu/arm920t/start.S当系统启动时, ARM CPU会跳到0x00000000去执行。一般BootLoader都包括如下几个部分:1. 建立中断向量异常表2. 显示的切换到SVC且32指令模式3. 关闭S3C2410的内部看门狗4. 禁止所有的中断5. 配置系统时钟频率和总线频率6. 设置内存区的控制寄存器7. 初始化中断8. 安装中断向表量9. 把可执行文件的各个段搬到运行态的各个位置10. 跳到C代码部分执行具体分析如下:/*复位时0地址是ROM区,从0x0到0x20分配了ARM的中断向量表*/.globl _start_start: b    reset                /*0x0,正常情况下,系统reset后进入的入口,驻留于0x0地址,机器码为EA0000XX*/ldr pc, _undefined_instruction     /*0x4,未定义指令,系统出错处理的入口*/ldr pc, _software_interrupt       /*0x8,软中断,monitor程序的入口*/ldr pc, _prefetch_abort          /*0x0c,预取失败错误*/ldr pc, _data_abort                /*0x10,取数据失败错误(通常是保护现场,然后do nothing)*/ldr pc, _not_used                 /*0x14保留*/ldr pc, _irq                       /*0x18,快速中断请求 */ldr pc, _fiq                       /*0x1c,处理原理与irq相同,所有的硬件中断源共用一个通道来进行IRQ或FIQ */_undefined_instruction: .word undefined_instruction_software_interrupt:     .word software_interrupt_prefetch_abort:         .word prefetch_abort_data_abort:            .word data_abort_not_used:                .word not_used_irq:                      .word irq_fiq:                      .word fiq.balignl 16,0xdeadbeef/*.将地址对其到16的倍数,如果PC跳过4字节才是16的倍数,则用0xdeadbeef填充,如果只跳过了1,2,3个字节则填充不确定,如果PC是16的倍数,则什么也不做*/**************************************************************** 当一个异常出现以后,ARM会自动执行以下几个步骤:* (1) 把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。* (2) 将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR。* (3) 根据异常类型,强制设置CPSR的运行模式位。* (4) PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中。*** 当异常处理完毕后,ARM会执行以下几步操作从异常返回:* (1) 将连接寄存器LR的值减去相应的偏移量后送到PC中* (2) 将SPSR复制回CPSR中* (3) 若在进入异常处理时设置了中断禁止位,要在此清除上述代码即碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序继续执行。******************************************************************/**************************************************************************** Startup Code (reset vector)** do important init only if we don't start from memory!* relocate armboot to ram* setup stack* jump to second stage***************************************************************************//*保存变量的数据区*/_TEXT_BASE: .word TEXT_BASE.globl _armboot_start_armboot_start: .word _start/** These are defined in the board-specific linker script.*/.globl _bss_start_bss_start:    .word __bss_start.globl _bss_end_bss_end:    .word _end#ifdef CONFIG_USE_IRQ/* IRQ stack memory (calculated at run-time) */.globl IRQ_STACK_STARTIRQ_STACK_START:    .word 0x0badc0de/* IRQ stack memory (calculated at run-time) */.globl FIQ_STACK_STARTFIQ_STACK_START:    .word 0x0badc0de#endif/*****************************************************/上述代码主要是用于保存一些全局变量,用于启动程序将代码从flash拷贝到RAM或其他使用。有一些变量的值是通过链接脚本得到的,如TEXT_BASE位于/u-boot-1.1.6/board/xxx(开发板目录名称)/config.mk* 文件里。__bss_start、_end位于/u-boot-1.1.6/board/xxx(开发板目录名称)/u-boot.lds文件里,具体值是由编译器算出来的。/********************************************************//** the actual reset code*/reset:/** set the cpu to SVC32 mode ,在进入时将CPSR设置为监控模式,退出后改为用户模式* 运行模式位为:10011(svc mode)*/mrs r0,cpsrbic r0,r0,#0x1f     //r0=r0 AND (!0x1f),屏蔽所有中断,为中断提供服务通常是OS的设备驱动的责任,在bootloader执行中不需要中断orr r0,r0,#0xd3     //逻辑或msr cpsr,r0         //svc mode/**************************************************************************/*设置cpu运行在SVC32模式。ARM共有7种模式:* 用户模式(usr):             arm处理器正常的程序执行状态* 快速中断模式(fiq):         用于高速数据传输或通道处理* 外部中断模式(irq):         用于通用的中断处理* 超级保护模式(svc):         操作系统使用的保护模式* 数据访问终止模式(abt):        当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护* 系统模式(sys):             运行具有特权的操作系统任务* 未定义指令中止模式(und):     当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真** 通过设置ARM的CPSR寄存器,让CPU运行在操作系统保护模式,为后面进行其它操作作好准备了。*************************************************************************//* turn off the watchdog */#if defined(CONFIG_S3C2400)# define pWTCON        0x15300000# define INTMSK        0x14400008         /* Interupt-Controller base addresses */# define CLKDIVN 0x14800014             /* clock divisor register */#elif defined(CONFIG_S3C2410)# define pWTCON        0x53000000# define INTMSK        0x4A000008         /* Interupt-Controller base addresses */# define INTSUBMSK 0x4A00001C# define CLKDIVN 0x4C000014             /* clock divisor register */#endif#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)ldr     r0, =pWTCONmov     r1, #0x0str     r1, [r0]                         //各个硬件还未就绪,关闭看门狗/** mask all IRQs by setting all bits in the INTMR - default*/mov r1, #0xffffffffldr r0, =INTMSKstr r1, [r0]# if defined(CONFIG_S3C2410)ldr r1, =0x3ffldr r0, =INTSUBMSKstr r1, [r0]# endif/* FCLK:HCLK:PCLK = 1:2:4 */ //FCLK用于CPU,HCLK用于AHB,PCLK用于APB/* default FCLK is 120 MHz ! */ldr r0, =CLKDIVNmov r1, #3str r1, [r0]#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 *//*初始化代码在系统重启的时候调用,运行时热复位从RAM中启动不执行* we do sys-critical inits only at reboot,* not when booting from ram!*/#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_crit //初始化CPU#endif#ifndef CONFIG_SKIP_RELOCATE_UBOOTrelocate:             /* 重定位 U-Boot 到 RAM */adr r0, _start        /* r0/* 初始化堆栈        */stack_setup:ldr r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot */sub r0, r0, #CFG_MALLOC_LEN /* malloc area                   */sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                      */#ifdef CONFIG_USE_IRQsub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)#endif                  sub sp, r0, #12        /* leave 3 words for abort-stack *//*得到最终sp的值*/clear_bss:ldr r0, _bss_start        /* find start of bss segment        */ldr r1, _bss_end        /* stop here                      */mov     r2, #0x00000000        /* clear                         */clbss_l:str r2, [r0]        /* clear loop...                    */add r0, r0, #4cmp r0, r1ble clbss_l               /**********************************************************************/* 已经准备好了堆栈,就可跳到C写的代码里了,也就是* 跳到内存中的/u-boot-1.1.4/board.c --> start_armboot中运行了* 把_start_armboot地址处的值也就是start_armboot绝对地址值移到pc* 于是跳到C代码。/*********************************************************************/ldr pc, _start_armboot_start_armboot: .word start_armboot/**************************************************************************** CPU_init_critical registers** setup important registers* setup memory timing***************************************************************************/cpu_init_crit:/** flush v4 I/D caches*/mov r0, #0mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache *//*使I/D cache失效: 协处理寄存器操作,将r0中的数据写入到协处理器p15的c7中,c7对应cp15的cache控制寄存器*/mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB *//*使TLB操作寄存器失效:将r0数据送到cp15的c8、c7中。C8对应TLB操作寄存器*//******************************************************************************************************* MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:* MCR 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。* 其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,* 源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。******************************************************************************************************//** disable MMU stuff and caches,禁止MMU和caches*/mrc p15, 0, r0, c1, c0, 0       //将c1、c0的值写入到r0中bic r0, r0, #0x00002300         @ clear bits 13, 9:8 (--V- --RS)bic r0, r0, #0x00000087         @ clear bits 7, 2:0 (B--- -CAM)orr r0, r0, #0x00000002         @ set bit 2 (A) Alignorr r0, r0, #0x00001000         @ set bit 12 (I) I-Cachemcr p15, 0, r0, c1, c0, 0       //将设置好的r0值写入到协处理器p15的c1、c0中/** before relocating, we have to setup RAM timing* because memory timing is board-dependend, you will* find a lowlevel_init.S in your board directory.*/mov ip, lr                         //保存前一个跳转地址,防止下一个跳转将前一个lr地址覆盖bl lowlevel_init                 //board/smdk2410/lowlevel_init.S:用于完成芯片存储器的初始化mov lr, ipmov pc, lr                         //返回cpu_init_crit函数2.Stage2:lib_arm/board.c此文件是u-boot Stage2部分,入口为Stage1最后调用的start_armboot函数。注意上面最后ldr到pc的是_start_armboot这个地址,而非start_armboot变量。start_armboot是U-Boot执行的第一个C语言函数,完成如下工作:1. 初始化MMU2.初始化外部端口3. 中断处理程序表初始化4. 串口初始化5. 其它部分初始化(可选)6. 主程序循环void start_armboot (void){    DECLARE_GLOBAL_DATA_PTR;    //此宏定义了一个gd_t类型的指针 *gd,并指名用r8寄存器来存储:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")    ulong size;    init_fnc_t **init_fnc_ptr;    char *s;    /* Pointer is writable since we allocated a register for it     上面那个宏的作用*/    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));    //此C语句引用的是start.S中的地址标号_armboot_start,但是得到的却是其中所指的变量_start的值(在RAM中,_start= 0x33F80000)。    // Ps: _armboot_start: .word _start    //gd是全局变量,位置在堆栈区以下(低地址):    typedef struct global_data {        bd_t *bd;        unsigned long flags;        unsigned long baudrate;        unsigned long have_console;     /* serial_init() was called */        unsigned long reloc_off;         /* Relocation Offset */        unsigned long env_addr;         /* Address of Environment struct */        unsigned long env_valid;         /* Checksum of Environment valid? */        unsigned long fb_base;             /* base address of frame buffer */#ifdef CONFIG_VFD        unsigned char vfd_type;         /* display type */#endif#if 0        unsigned long cpu_clk;             /* CPU clock in Hz! */        unsigned long bus_clk;        unsigned long ram_size;         /* RAM size */        unsigned long reset_status;     /* reset status register at boot */#endif        void **jt; /* jump table */    }gd_t;    /* compiler optimization barrier needed for GCC >= 3.4 */    __asm__ __volatile__("": : :"memory");    memset ((void*)gd, 0, sizeof (gd_t));    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //得到bd的起点    memset (gd->bd, 0, sizeof (bd_t));    monitor_flash_len = _bss_start - _armboot_start;    /* 顺序执行init_sequence数组中的初始化函数 */    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {        if ((*init_fnc_ptr)() != 0) {            hang ();        }    }    /*配置可用的Flash */    size = flash_init (); //初始化Nor flash的函数,函数实现在下面    display_flash_config (size);//打印到控制台:Flash: 512 kB    /* _armboot_start 在u-boot.lds链接脚本中定义 */    mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //将CFG_MALLOC_LEN区域用memset函数清零(直接往目的地址写0)    /* 配置环境变量,重新定位 */    env_relocate (); //刚才的初始化函数中有一个是env_init(),根据CRC校验来初始化gd->env_addr变量(自己设定的还是初始值),此函 数是作用是将环境变量值从某个flash和RAM之间的拷贝。下图描述了ENV的初始化过程:    /* 从环境变量中获取IP地址,放到全局变量gd中 */    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");    /* 以太网接口MAC 地址,放到全局变量gd中*/    {        int i;        ulong reg;        char *s, *e;        uchar tmp[64];        i = getenv_r ("ethaddr", tmp, sizeof (tmp));        s = (i > 0) ? tmp : NULL;        for (reg = 0; reg 6; ++reg) {            gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;            if (s)            s = (*e) ? e + 1 : e;        }    }    devices_init (); /* 获取列表中的设备 */    jumptable_init ();    console_init_r (); /* 完整地初始化控制台设备 */    enable_interrupts (); /* 使能例外处理 */    /* 通过环境变量初始化 */    if ((s = getenv ("loadaddr")) != NULL) {        load_addr = simple_strtoul (s, NULL, 16);    }    /* main_loop()总是试图自动启动,循环不断执行 */    for (;;) {        main_loop (); /* 主循环函数处理执行用户命令 -- common/main.c */    }    /* NOTREACHED - no way out of command loop except booting */} http://www.cnblogs.com/hnrainll/archive/2011/06/14/2080257.html

 

你可能感兴趣的文章
软件设计方案
查看>>
重新安装系统之前备份
查看>>
linux定时任务的设置 crontab 配置指南
查看>>
mysql 一些基础的语法和命令
查看>>
imx6dl i2c4 support
查看>>
介绍几种SSIS部署方式
查看>>
C#中的委托解析
查看>>
SqlSugar ORM已经支持读写分离
查看>>
5V系统和3.3V系统电平转换
查看>>
基于jquery的tips悬浮消息提示插件tipso
查看>>
PHPUnit笔记
查看>>
【VBA编程】14.操作工作簿对象
查看>>
Java Queue的使用
查看>>
Using Text_IO To Read Files in Oracle D2k
查看>>
关于解决Missing Number之类的算法问题
查看>>
117、Android应用程序退至后台,再次启动数据丢失的问题(转载)
查看>>
无论你如何努力,总会有人讨厌你
查看>>
Json解析工具Jackson(使用注解)
查看>>
第二十篇:在子进程中装载新的程序
查看>>
MVC日期和其它字符串格式化
查看>>