STM32
直播中

番茄番茄

11年用户 637经验值
私信 关注
[问答]

介绍start.S 文件一些预备知识

start.s文件是什么?
怎样去更好地理解start.s文件呢?

回帖(3)

王帅

2021-11-29 09:55:24
start.s文件是U-Boot启动最先执行的代码,对start.s 文件的正确理解是整个U-Boot源码理解的开端和基础。要理解start.S 文件,需要具备一些预备知识,下面先介绍这些预备知识。
一、start.s预备知识
1.start.s中的汇编指令
1.1 .globl
汇编程序中以.开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。.globl伪指令的用法是:
                                                           .globl xxx
.globl xxx的作用是将xxx符号声明为外部程序可访问的标签,也就是说xxx符号可以被其他.c .s文件使用。
1.2 .word
.word伪指令的用法是:
                                                          .word xxx
.word xxx的作用就是在当前位置放一个word型的值,这个值就是xxx。举例来说:                                                        
                                                               _rWTCON:                                    
                                                                     .word 0x15300000
上面两句的意思就是在当前地址,即_rWTCON处放一个值0x15300000。
1.3 .balignl
.balignl是.balign的变体,.balign的意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为结尾,并且在前面填入一个字节长度的信息,信息内容为第二个参数。举例来说:
                                                      .balign 8, 0xde
上面这条语句的意思是,在以当前地址开始,在地址为8的倍数的位置的前面填入一个字节内容为0xde的内容。如果当前地址正好是8的倍数,则没有东西被写入到内存。
那么以此类推,.balignw则表示第二个参数填入的内容长度为一个字的长度,即16位,所以一般有这样的形式出现:
                                                           .balignw 4,0x368d
以此类推,.balignl这个指令用来填写一个双字,即内容长度为4个字节。举例来说:
                                                   .balignl 16,0xdeadbeef
上面这条语句的意思是,在以当前地址开始,在地址为16的倍数的位置的前面填入4个字节长度的内容,填入的内容为0xdeadbeef。
1.4 .align
.align伪指令的用法是:
                                               .align n
.align n 它的含义就是使得下面的代码按一定规则对齐。.align n 指令的对齐值有两种方案:n2^n 。各种平台最初的汇编器一般都不是gas ,采取方案1 或2 的都很多,gas 的目标是取代原来的汇编器,必然要保持和原来汇编器的兼容,因此在gas 中如何解释.align 指令会显得有些混乱,原因在于保持兼容。arm-linux 是按照 2^n 的方案对齐的,需要说明的是这个对齐和ld-script 里的对齐不同,不是一会事。下面的英文就不同平台的对齐进行了说明:版本2.11.92.0.12 的gas 的info(Mandrake 8.2 上的) 这样说:
The way the required alignment is specified varies from system to system. For the a29k, hppa, m68k, m88k, w65, sparc, and Hitachi SH, and i386 using ELF format, the first expression is the alignment request in bytes. For example .align 8 advances the location counter until it is a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
For other systems, including the i386 using a.out format, and the arm and strongarm, it is the number of low-order zero bits the location counter must have after advancement. For example `.align 3' advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed.
从这段文字来看,ARM.align 5就是 25次方对齐,也就是 4 字节对齐,通过反汇编也可以看出对齐方式。
1.5 .macro ... .endm
.macro ... .endm伪指令的作用相当于c语言的#define,即宏定义。
1.6 b和bl
b指令是ARM分支指令,实现程序的跳转。其语法是:
                      b{}    label
地址label是以一个有符号的相对于pc的偏移量保存在指令中,且必须被限制在分支指令的约32MB范围内。
大多数汇编语言通过使用地址标号来隐藏分支指令编码的细节。地址标号放在一行的开始处,汇编器会记录该行指令的地址,用于计算跳转的偏移量。
bl指令和b指令类似,区别在于bl指令是带返回的跳转,它会把bl后面的的第一条指令地址赋值给lr寄存器。
1.7 ldr和str
ldr用于把存储器里的一个32位数据传入至一个处理器寄存器。其语法是:
                          ldr reg, address
其功能是把存储器地址address处的一个32位数据传入至处理器寄存器reg。
str用于把一个32位数据存储至指定地址。其语法是:
                          str reg, address
其功能是把reg的值存储至address地址处。
1.8 mrs和msr
mrs指令用于把状态寄存器(cpsr或spsr)的值传送到通用寄存器。指令语法:
                         mrs rd, cpsr|spsr
msr指令用于把通用寄存器的值传送到状态寄存器(cpsr或spsr)。指令语法:
                         mrs cpsr|spsr, rm
1.9 bic
bic指令用于实现逻辑位清除。指令语法:
                        bic rd, rn, n
其功能相当于rd = rn & (~n)。
1.10 orr
orr指令用于实现32位逻辑或。指令语法:
                       bic rd, rn, n
其功能相当于rd = rn | n。
1.11 mrc和mcr
mrc和mcr是协处理器传送指令。mrc指令把协处理器寄存器的值传送至通用寄存器。mcr指令把通用寄存器的值传送至协处理器寄存器。指令语法:
                       mrc|mcr cp, opcode1, rd, cn, cm, {, opcode2}
在协处理器指令语法中,cp代表协处理器编号,为p0~p15。opcode描述要在协处理器中执行的操作。cn、cm描述在协处理器中的寄存器(cn一般是主寄存器,cm是辅寄存器)。协处理器的操作和寄存器依赖于具体使用的协处理器。协处理器15(cp15)是为系统控制预留的,如内存管理、写缓冲控制、cache控制及寄存器识别等。
1.12 move
move指令把一个32位数送到一个寄存器。指令语法:
                     move rd, n
n是32为数,rd是寄存器,指令结果相当于rd = n。
1.13 adr
adr是一条伪指令,它把一个相对地址写入寄存器中,它将会使用一个包括pc相对地址的表达式进行编码。指令语法:
                    adr rd, label
rd是寄存器,label是地址标号。adr指令以程序计数器pc的基值与label偏移量相加得到的和赋给rd。比如start.s的第149行:
                   adr r0, _start
该语句的意思是,如果这段代码在 0x02000000 (FLASH起始地址)运行,即此时pc基值=0x02000000,_start偏移量等于0,那么adr r0, _start 得到 r0 = 0x02000000。如果这段代码在地址 0x81008000(Boot在RAM中加载地址)运行,即此时pc基值=0x81008000,_start偏移量等于0,那么r0就是 0x81008000 了。
1.14 ldm和stm指令
ldm指令从目标地址装载多个数据至多个寄存器。stm指令把多个寄存器的值存储至目标地址。指令语法:
                       ldm|stm rd{!}, regs
ldm指令从寄存器rd里的地址装载多个数据至多个寄存器regs(比如r3-r10,共8个寄存器)。
ldm和stm指令有多种不同的寻址模式,分别是ia(执行后增加)、ib(执行前增加)、da(执行后减少)、db(执行前减少)。比如:
                      ldmia r0!, {r3-r10}
                     stmia r1!, {r3-r10}
第一条指令表示从r0地址装载完4个字节数据至r3后,地址自动增加4,然后从新地址装载4个字节数据至r4后,以此类推。
第2条指令表示把r3的值存储至r1地址后,地址自动增加4,然后把r4的值存储至新地址,以此类推。
1.15 cmp
cmp指令是比较指令,指令语法:
           cmp rn, n
cmp指令根据rn减n的值设置cpsr的z标志位。如果rn等于n,则cpsr的z标志位为1。如果rn不等于n,则cpsr的z标志位为0。
在设置cpsr的z标志位后,其他指令可通过条件执行来改变程序的执行流程。比如ble指令,如果cpsr的z标志位为1,则程序跳转。
1.16 add和sub
add指令是32位加法指令,指令语法:
                    add rd, rn, n
其相当于rd = rn + n。
sub指令是32位减法指令,指令语法:
                    sub rd, rn, n
其相当于rd = rn - n。
2.  u-boot.lds
对于u-boot.lds文件,它定义了整个程序编译之后的链接过程,决定了一个可执行程序的各个段的存储位置。
先看一下GNU官方网站上对.lds文件形式的完整描述:

SECTIONS  {
    ...
    secname start BLOCK(align)  (NOLOAD)  :  AT  ( ldadr )
    { contents }  >region :phdr =fill
    ...
}


secname和 contents是必须的,其他的都是可选的。下面介绍几个常用的:
1)secname
secname表示段名
2)contents
决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(bss段、data段、rodata段、text段)。下面简单介绍下
bss段、data段、rodata段、text段:
bss段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。bss是英文Block Started by Symbol的简称。BSS段属于静态内存分配。该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。
data段用于存储初始化的全局变量,初始化为0的全局变量出于编译优化的策略还是被保存在BSS段。
rodata段也叫常量区,用于存放常量数据。
text段是用于存放程序代码的,编译时确定只读。更进一步讲是存放处理器的机器指令,当各个源文件单独编译之后生成目标文件,经连接器链接各个目标文件并解决各个源文件之间函数的引用,与此同时,还得将所有目标文件中的.text段合在一起,但不是简单的将它们“堆”在一起就完事,还需要处理各个段之间的函数引用问题。
3)start
本段链接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4)AT(ldadr)
定义本段存储(加载)的地址。
下面看下freescale的mx28系列的u-boot.lds文件:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
         . = 0x00000000;
         . = ALIGN(4);
         .text        :
         {
           cpu/arm926ejs/start.o (.text)
           *(.text)
         }
         .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 (NOLOAD) : { *(.bss) }
         _end = .;
}
第1行:指定输出可执行文件是elf格式,32位ARM指令,小端。
第2行:指定输出可执行文件的平台为ARM。
第3行:指定输出可执行文件的起始代码段为_start。
第6行:指定整个u-boot程序从地址0x00000000开始链接。
第7行:指定代码以4字节对齐。
第8-12行:表示该段的名字为.text,第10行指定start.s文件中的text段位于地址0x00000000,u-boot程序的其他text段位于其后面。
第13行:表示该段的名字为.rodata,指定u-boot程序的rodata段位于text段后面。
第15行:表示该段的名字为.data,指定u-boot程序的data段位于rodata段后面。
第17行:表示该段的名字为.got,指定u-boot程序的got段位于data段后面。got段是uboot自定义的一个段, 非标准段。
第20行:表示把当前地址值赋给全局变量__u_boot_cmd_start(__u_boot_cmd_start 定义在其他文件里) 。
第21行:表示该段的名字为.u_boot_cmd ,指定u-boot程序的uboot命令位于got段后面。
第22行:表示把当前地址值赋给全局变量__u_boot_cmd_end(__u_boot_cmd_end定义在其他文件里) 。
第25行:表示把当前地址(即bss段的开始位置)赋给全局变量__bss_start (__bss_start 在start.s中被使用) 。
第26行:表示该段的名字为.bss,指定u-boot程序的bss段位于.u_boot_cmd段后面。
第27行:表示把当前地址(即bss段的结束位置)赋给全局变量_end (_end在start.s中被使用) 。
从mx28系列的u-boot.lds文件可以知道生成的u-boot映像的地址分布如下图所示:


举报

刘琬婷

2021-11-29 09:55:41
二、start.s源码分析
***出cpu/arm926ejs/start.s的源码:

/*
*  armboot - Startup Code for ARM926EJS CPU-core
*
*  Copyright (c) 2003  Texas Instruments
*
*  ----- Adapted for OMAP1610 OMAP730 from ARM925t code ------
*
*  Copyright (c) 2001        Marius Gröger
*  Copyright (c) 2002        Alex Züpke
*  Copyright (c) 2002        Gary Jennejohn
*  Copyright (c) 2003        Richard Woodruff
*  Copyright (c) 2003        Kshitij
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/


#include
#include
#include

#if defined(CONFIG_OMAP1610)
#include <./configs/omap1510.h>
#elif defined(CONFIG_OMAP730)
#include <./configs/omap730.h>
#endif

/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/


.globl _start
_start:
        b        reset
        ldr        pc, _undefined_instruction
        ldr        pc, _software_interrupt
        ldr        pc, _prefetch_abort
        ldr        pc, _data_abort
        ldr        pc, _not_used
        ldr        pc, _irq
        ldr        pc, _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


/*
*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram
* setup stack
*
*************************************************************************
*/

_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_START
IRQ_STACK_START:
        .word        0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
        .word 0x0badc0de
#endif


/*
* the actual reset code
*/
.globl reset
reset:
        /*
         * set the cpu to SVC32 mode
         */
        mrs        r0,cpsr
        bic        r0,r0,#0x1f
        orr        r0,r0,#0xd3
        msr        cpsr,r0

        /*
         * we do sys-critical inits only at reboot,
         * not when booting from ram!
         */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
        bl        cpu_init_crit
#endif

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:                                /* relocate U-Boot to RAM            */
        adr        r0, _start                /* r0 <- current position of code   */
        ldr        r1, _TEXT_BASE                /* test if we run from flash or RAM */
        cmp     r0, r1                  /* don't reloc during debug         */
        beq     stack_setup

        ldr        r2, _armboot_start
        ldr        r3, _bss_start
        sub        r2, r3, r2                /* r2 <- size of armboot            */
        add        r2, r0, r2                /* r2 <- source end address         */

copy_loop:
        ldmia        r0!, {r3-r10}                /* copy from source address [r0]    */
        stmia        r1!, {r3-r10}                /* copy to   target address [r1]    */
        cmp        r0, r2                        /* until source end addreee [r2]    */
        ble        copy_loop
#endif        /* CONFIG_SKIP_RELOCATE_UBOOT */

        /* Set up the stack                                                    */
stack_setup:
        ldr        r0, _TEXT_BASE                /* upper 128 KiB: relocated uboot   */
        sub        r0, r0, #CONFIG_SYS_MALLOC_LEN        /* malloc area                      */
        sub        r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo                        */
#ifdef CONFIG_USE_IRQ
        sub        r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
        sub        sp, r0, #12                /* leave 3 words for abort-stack    */

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, #4
        cmp        r0, r1
        ble        clbss_l

        bl coloured_LED_init
        bl red_LED_on

        ldr        pc, _start_armboot

_start_armboot:
        .word start_armboot


/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
        /*
         * flush v4 I/D caches
         */
        mov        r0, #0
        mcr        p15, 0, r0, c7, c7, 0        /* flush v3/v4 cache */
        mcr        p15, 0, r0, c8, c7, 0        /* flush v4 TLB */

        /*
         * disable MMU stuff and caches
         */
        mrc        p15, 0, r0, c1, c0, 0
        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) Align */
        orr        r0, r0, #0x00001000        /* set bit 12 (I) I-Cache */
        mcr        p15, 0, r0, c1, c0, 0

        /*
         * Go setup Memory and board specific bits prior to relocation.
         */
        mov        ip, lr                /* perserve link reg across call */
        bl        lowlevel_init        /* go setup pll,mux,memory */
        mov        lr, ip                /* restore link */
        mov        pc, lr                /* back to my caller */
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

/*
*************************************************************************
*
* Interrupt handling
*
*************************************************************************
*/

@
@ IRQ stack frame.
@
#define S_FRAME_SIZE        72

#define S_OLD_R0        68
#define S_PSR                64
#define S_PC                60
#define S_LR                56
#define S_SP                52

#define S_IP                48
#define S_FP                44
#define S_R10                40
#define S_R9                36
#define S_R8                32
#define S_R7                28
#define S_R6                24
#define S_R5                20
#define S_R4                16
#define S_R3                12
#define S_R2                8
#define S_R1                4
#define S_R0                0

#define MODE_SVC 0x13
#define I_BIT         0x80

/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/

        .macro        bad_save_user_regs
        @ carve out a frame on current user stack
        sub        sp, sp, #S_FRAME_SIZE
        stmia        sp, {r0 - r12}        @ Save user registers (now in svc mode) r0-r12
        ldr        r2, _armboot_start
        sub        r2, r2, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
        sub        r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack
        @ get values for "aborted" pc and cpsr (into parm regs)
        ldmia        r2, {r2 - r3}
        add        r0, sp, #S_FRAME_SIZE                @ grab pointer to old stack
        add        r5, sp, #S_SP
        mov        r1, lr
        stmia        r5, {r0 - r3}        @ save sp_SVC, lr_SVC, pc, cpsr
        mov        r0, sp                @ save current stack into r0 (param register)
        .endm
        .macro        irq_save_user_regs
        sub        sp, sp, #S_FRAME_SIZE
        stmia        sp, {r0 - r12}                        @ Calling r0-r12
        @ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
        add        r8, sp, #S_PC
        stmdb        r8, {sp, lr}^                @ Calling SP, LR
        str        lr, [r8, #0]                @ Save calling PC
        mrs        r6, spsr
        str        r6, [r8, #4]                @ Save CPSR
        str        r0, [r8, #8]                @ Save OLD_R0
        mov        r0, sp
        .endm

        .macro        irq_restore_user_regs
        ldmia        sp, {r0 - lr}^                        @ Calling r0 - lr
        mov        r0, r0
        ldr        lr, [sp, #S_PC]                        @ Get PC
        add        sp, sp, #S_FRAME_SIZE
        subs        pc, lr, #4                @ return & move spsr_svc into cpsr
        .endm

        .macro get_bad_stack
        ldr        r13, _armboot_start                @ setup our mode stack
        sub        r13, r13, #(CONFIG_STACKSIZE+CONFIG_SYS_MALLOC_LEN)
        sub        r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack

        str        lr, [r13]        @ save caller lr in position 0 of saved stack
        mrs        lr, spsr        @ get the spsr
        str        lr, [r13, #4]        @ save spsr in position 1 of saved stack
        mov        r13, #MODE_SVC        @ prepare SVC-Mode
        @ msr        spsr_c, r13
        msr        spsr, r13        @ switch modes, make sure moves will execute
        mov        lr, pc                @ capture return pc
        movs        pc, lr                @ jump to next instruction & switch modes.
        .endm

        .macro get_irq_stack                        @ setup IRQ stack
        ldr        sp, IRQ_STACK_START
        .endm

        .macro get_fiq_stack                        @ setup FIQ stack
        ldr        sp, FIQ_STACK_START
        .endm

/*
* exception handlers
*/
        .align  5
undefined_instruction:
        get_bad_stack
        bad_save_user_regs
        bl        do_undefined_instruction

        .align        5
software_interrupt:
        get_bad_stack
        bad_save_user_regs
        bl        do_software_interrupt

        .align        5
prefetch_abort:
        get_bad_stack
        bad_save_user_regs
        bl        do_prefetch_abort

        .align        5
data_abort:
        get_bad_stack
        bad_save_user_regs
        bl        do_data_abort

        .align        5
not_used:
        get_bad_stack
        bad_save_user_regs
        bl        do_not_used

#ifdef CONFIG_USE_IRQ

        .align        5
irq:
        get_irq_stack
        irq_save_user_regs
        bl        do_irq
        irq_restore_user_regs

        .align        5
fiq:
        get_fiq_stack
        /* someone ought to write a more effiction fiq_save_user_regs */
        irq_save_user_regs
        bl        do_fiq
        irq_restore_user_regs

#else

        .align        5
irq:
        get_bad_stack
        bad_save_user_regs
        bl        do_irq

        .align        5
fiq:
        get_bad_stack
        bad_save_user_regs
        bl        do_fiq

#endif
举报

h1654155275.5651

2021-11-29 09:55:44
第53行:.globl指示告诉汇编器,_start这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号。_start就像C程序的main函数一样特殊,是GNU汇编器默认的整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。如果一个符号没有用.globl声明,就表示这个符号不会被链接器用到。
第54行:定义了一个名为_start的地址标号,这个地址标号代表的地址为第55行指令(b reset)的地址。
第55-62行:arm体系结构的向量表。当一个异常或中断发生时,处理器会把pc设置为一个特定的存储器地址。这个地址放在一个被称为向量表的特定的地址范围内。向量表的入口是一些跳转指令,跳转到专门处理某个异常或中断的子程序(复位、未定义指令、软件中断、预取指中止、数据中止、保留、中断请求、快速中断请求)。
第55行:是一条相对跳转指令,表示跳转到reset(复位异常处理程序)处。
第56行:表示把_undefined_instruction地址处的值装入pc。根据前面.word介绍可以知道_undefined_instruction地址处存储的值是undefined_instruction地址标号代表的地址。所以第56行实现了程序跳转至undefined_instruction(未定义指令异常处理程序)的功能。
第57行:表示程序跳转至software_interrupt(软件中断异常处理程序)。
第58行:表示程序跳转至prefetch_abort(预取指中止异常处理程序)。
第59行:表示程序跳转至data_abort(数据中止异常处理程序)。
第60行:表示程序跳转至not_used(保留异常处理程序)。
第61行:表示程序跳转至irq(irq中断处理程序)。
第62行:表示程序跳转至fiq(fiq中断处理程序)。
第64-65行:定义了名为_undefined_instruction的地址标号,在这个地址标号处存放了值undefined_instruction,即未定义指令异常处理程序首地址。
第66-67行:定义了名为_software_interrupt的地址标号,在这个地址标号处存放了值software_interrupt,即软件中断异常处理程序首地址。
第68-69行:定义了名为_prefetch_abort的地址标号,在这个地址标号处存放了值prefetch_abort,即预取指中止异常处理程序首地址。
第70-71行:定义了名为_data_abort的地址标号,在这个地址标号处存放了值data_abort,即数据中止异常处理程序首地址。
第72-73行:定义了名为_not_used的地址标号,在这个地址标号处存放了值not_used,即保留异常处理程序首地址。
第74-75行:定义了名为_irq的地址标号,在这个地址标号处存放了值irq,即irq中断处理程序首地址。
第76-77行:定义了名为_fiq的地址标号,在这个地址标号处存放了值fiq,即fiq中断处理程序首地址。
第79行:实现了在_start偏移60-63字节地址处(54行-77行的地址范围是_start至_start+59)存放一个值为0xdeadbeef数据的目的。0xdeadbeef的作用大概就是为内存做标记,有点儿像个小旗子,插在那里,表示从这个位置往后,就是干什么的内存,这个位置往前,禁止访问。
       第95行:定义了名为_TEXT_BASE的地址标号,在这个地址标号处存放了值TEXT_BASE。TEXT_BASE定义在board目录下对应开发板的config.mk文件中。TEXT_BASE指定了生成u-boot映像的链接地址,即最终的运行地址(RAM),该地址分布情况可以在u-boot.map文件中查看。特别说明:虽然u-boot.lds文件也定义了u-boot映像的地址分布,但是因为顶层的config.mk中的-Ttext $(TEXT_BASE)命令(LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS))的使用,链接器把UBOOT从地址TEXT_BASE开始连接,至于链接脚本u-boot.lds,其主要作用是用来指明各个*.o文件的顺序,如入口地址标号(_start)等,以及使两个地址标号得到当前的地址,比如:
    __u_boot_cmd_start = .;    *.u_boot_cmd段的起始地址
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;       *.u_boot_cmd段的结束地址
以供C程序使用。 __u_boot_cmd_start和__u_boot_cmd_end可以作为全局的一个常数使用。
第98行:把_armboot_start符号声明为全局符号。因为_armboot_start要被lib_arm/board.c中的start_armboot函数用到。
第99-100行:定义了名为_armboot_start的地址标号,在这个地址标号处存放了值_start,即u-boot映像的基地址。
第105行:把_bss_start符号声明为全局符号。因为_bss_start要被lib_arm/board.c中的start_armboot函数用到。
第106-107行:定义了名为_bss_start的地址标号,在这个地址标号处存放了值__bss_start,__bss_start定义在u-boot.lds中。
第109行:把_bss_end符号声明为全局符号。因为_bss_end要被lib_arm/board.c中的start_armboot函数用到。
第110-111行:定义了名为_bss_end的地址标号,在这个地址标号处存放了值_end,_end定义在u-boot.lds中。
第115行:把IRQ_STACK_START符号声明为全局符号。因为IRQ_STACK_START要被lib_arm/interrupts.c中interrupt_init函数使用。interrupt_init函数计算IRQ_STACK_START值和FIQ_STACK_START值。IRQ_STACK_START表示IRQ中断使用的栈的起始地址,FIR_STACK_START表示FIR中断使用的栈的起始地址。
第116-117行:定义了名为IRQ_STACK_START的地址标号,在这个地址标号处存放了值0x0badc0de。0x0badc0de的作用是内存标记。
第120行:把FIR_STACK_START符号声明为全局符号。因为FIR_STACK_START要被lib_arm/interrupts.c中interrupt_init函数使用。interrupt_init函数计算IRQ_STACK_START值和FIQ_STACK_START值。IRQ_STACK_START表示IRQ中断使用的栈的起始地址,FIR_STACK_START表示FIR中断使用的栈的起始地址。
第121-122行:定义了名为FIQ_STACK_START的地址标号,在这个地址标号处存放了值0x0badc0de。0x0badc0de的作用是内存标记。
第129-192行:复位异常处理程序。
第129行:把reset符号声明为全局符号。
第130行:定义了名为reset的地址标号。
第134-137行:通过设置cpsr寄存器的bit0-bit7(值为二进制11010011)来禁止IRQ和FIQ中断,切换为ARM状态,切换为SVC模式。
第144行:程序跳转至cpu_init_crit。cpu_init_crit位于第205行至第231行。cpu_init_crit主要是做一些处理器关键且必要的初始化工作,比如清除并关闭caches,关闭MMU,调用lowlevel_init函数初始化其他外设(如晶振、存储器控制器等等)。lowlevel_init函数位于board目录下对应开发板的lowlever_init.S文件中或cpu目录下对应处理器的lowlever_init.S文件中。每种处理器或者开发板的lowlevel_init函数要完成的的功能可能会有很大不一样。下面分析下cpu_init_crit:
第210-212行:清除I-Cache和D-Cache。具体要参看对应cpu的协处理器指令。
第217-222行:关闭MMU和Cache。具体要参看对应cpu的协处理器指令。由于在进入操作系统之前,都不需要且不允许虚拟内存,所以必须关闭MMU。
第227行:保存lr至ip寄存器。由于第228行使用了bl指令,该bl指令会把144行的bl指令保存下来的lr的值覆盖掉,所以,这里需要先保存lr的值,以便程序能正确返回。第229行和230行实现了cpu_init_crit的返回。
第228行:程序跳转至lowlevel_init函数。lowlevel_init函数位于board目录下对应开发板的lowlever_init.S文件中或cpu目录下对应处理器的lowlever_init.S文件中。每种处理器或者开发板的lowlevel_init函数要完成的的功能可能会有很大不一样。lowlevel_init函数初始化其他外设(如晶振、存储器控制器等等)。
接下来回到程序执行流程,分析第148-164行。
第148-164行的功能是把u-boot映像从非易失性存储器(比如:NAND Flash、NOR Flash、SD卡等)复制到RAM(比如:SDRAM)。如果u-boot映像本身就存储在RAM中(调试阶段),则不进行拷贝。在这个操作完成之前,u-boot的代码都是在存储u-boot映像的存储器里运行的,比如在NAND Flash、NOR Flash、SD卡等中。这个操作完成之后,将在RAM(比如:SDRAM)中运行start_armboot函数和异常及中断处理的代码。也就是说,除了start_armboot函数和异常及中断处理的代码在RAM中运行外,start.s中的其他代码都在存储u-boot映像的存储器里运行的。为什么是这样?这个主要是由跳转指令b和bl导致的。下面会有具体的分析。
第148行:定义了名为relocate的地址标号。
第149-151行:这三行通过判断u-boot当前运行位置(Flash或者RAM)来决定是否需要把u-boot映像拷贝至RAM。由前面adr指令介绍可知,第149行获得了u-boot当前运行地址的基地址(上电时u-boot的运行地址)并存入r0。第150行获得了u-boot映像的链接基地址,即最终的运行地址的基地址(RAM)并存入r1。如果上电时u-boot运行在Flash中,则r0不等于r1,此时需要把u-boot映像拷贝至RAM。如果上电时u-boot运行在RAM中(调试阶段),则r0等于r1,此时不需要把u-boot映像拷贝至RAM,程序直接跳转至stack_setup,进行堆栈设置。
第154-157行:这4行的作用是计算u-boot映像的结束地址,给copy_loop用。由u-boot.lds文件知道_armboot_start至_bss_start地址范围是u-boot映像的大小。
第159-163行:这4行实现了把u-boot映像拷贝至RAM。第160行从r0中的u-boot映像基地址连续读取8个4字节数据至寄存器r3-r10,然后第161行把寄存器r3-r10的值连续存入r1中的u-boot最终运行地址中(RAM)。第162行判断r0是否到达u-boot映像的结束地址r2,如果未到达u-boot映像的结束地址,则继续拷贝。
第167-174行:这8行的作用是分配堆区(malloc函数使用的区域)、全局数据区、IRQ中断使用的栈区、FIQ中断使用的栈区和用户栈区。用户栈区的设置是通过设置寄存器sp来实现的。根据这8行代码可以知道U-Boot内存使用情况了,如下图所示:





第176-184行:把bss区清零。
第186行:程序跳转至coloured_LED_init。coloured_LED_init函数位于board目录下对应开发板的led.c文件,作用是初始化开发板的指示灯。
第187行:程序跳转至red_LED_on。red_LED_on函数位于board目录下对应开发板的led.c文件,作用是点亮开发板的红色指示灯。
第189-192行:程序跳转至start_armboot。start_armboot函数位于lib_arm目录下的board.c文件中。start_armboot函数是u-boot启动的第2阶段,相当于main函数。
接下来分析向量表的另外6个异常或中断处理程序(未定义指令、软件中断、预取指中止、数据中止、保留、中断请求、快速中断请求),其位于第233-399行。
未定义指令、软件中断、预取指中止、数据中止、保留异常处理程序的行为基本上都一样:先保存异常发生时所有寄存器(r0-r10、fp、ip、sp、lr、pc、cpsr)的值,然后以易以调试的方式把它们的值打印出来,最后复位cpu。
中断请求、快速中断请求中断处理程序的行为基本一样:先保存中断发生时所有寄存器(r0-r10、fp、ip、sp、lr、pc、cpsr)的值,然后跳转至实际的中断处理程序。最后恢复寄存器值。
宏get_bad_stack的作用是保存异常发生时lr、spsr寄存器值至用户栈区。宏bad_save_user_regs的作用是保存异常发生时其他寄存器值至用户栈区。
do_xxx_instruction函数把异常发生时所有寄存器的值,以易以调试的方式把它们的值打印出来,最后复位cpu。do_xxx_instruction函数在lib_arm目录下的interrupts.c文件中。
宏irq_save_user_regs的作用是保存irq或fiq中断发生时所有寄存器(r0-r10、fp、ip、sp、lr、pc、cpsr)的值至用户栈区。
宏irq_restore_user_regs的作用是恢复irq或fiq中断的寄存器值。
do_irq和do_fiq是实际的irq和fiq中断处理程序。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分