/*
* armboot - Startup Code for ARM920 CPU-core
*
* Copyright (c) 2001 Marius Gr鰃er <mag@sysgo.de>
* Copyright (c) 2002 Alex Z黳ke <azu@sysgo.de>
* Copyright (c) 2002 Gary Jennejohn <gj@denx.de>
*
* 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 <config.h>
#include <version.h>
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
start.s代码,是u-boot的stage1阶段所要运行的代码
由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(flash)的0x地址。因此,必须通知编译器以使其知道这个入口,该工作可以通过修改连接器的脚本来完成。
- board/smdk2410/uboot.lds: ENTRY(_start)==>cpu/arm920t/start.o(.text)
- Uboot在ram的代码区(TEXT_BASE=0X33F80000)定义在board/amdk2410/config.mk
***********************************************************************
*/
/*
.globl symbol
定义一个全局符号,让这个符号对链接器可见,通常为连接器(ld)使用。symbol是全局可见的,可以供其他链接对象模块使用。
.global _start 让 _start 符号成为可见的标识符,这样链接器就知道跳转到程序中的什么地方并开始执行。linux寻找这个 _start 标签作为程序的默认进入点。标号_start是GNU链接器用来指定第一个要执行指令所必须的,同样的是全局可见的(并且只能出现在一个模块中)。
标号是后边紧跟一个冒号的符号,此时该标号代表活动位置计数器的当前值,并且可作为指令的操作数使用。
*/
.globl _start
_start: b reset /*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
/*
.globl _start 、 _start: 这些伪指令,标号在编译后的代码中并不占用内存。
Ldr 加载指定存储器地址中的内容到寄存器,区分:ldr伪指令
当发生异常时,执行cpu/arm920t/interrupts.c中的中断处理函数
.word expressions :定义一个字,并为之分配空间,4bytes。同理有:
.short expressions/.int expressions/.byte expressions/.long expressions/.ascii "string"
原理分析:在这里先预留好空间,空间里边的内容就是后边的expression,这里expression是标号,则代表的是某个地址,该地址就是异常处理函数的首地址。
当发生某异常时,处理器自动跳到存储器起始的对应异常处理地址(由硬件确定地址,即地址的前几个字,见上代码),执行里边的代码,里边一般都放置一个跳转指令。如:ldr pc, _undefined_instruction ;将此标号地址处的内容取出来给pc指针,而这内容恰好是异常处理函数的首地址(预留的空间就是为了放异常处理地址用的,这里_software_interrupt: .word software_interrupt指令中software_interrupt,是一个标号,标号代表的是处理地址,也是跳转的目标地址,该标号在本文件最下边有定义,其他标号类似),这样完成了一次异常处理。
见图示更易理解:
地址 地址中内容 指令内容
*/
_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
/*
这是一个数据对齐指令,在这个.balignl 16,0xdeadbeef指令之前,一共占了4x15=60个字节的内存,15个字。所以本代码的作者当时就简单的在15这个数上,加了个1,即16,把当前指针往后移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值。见图示中0x0000003c位置的内容。详细解释见《于关u-boot中的.balignl 16,0xdeadbeef的理解》一文。
*/
/*
*************************************************************************
*
* 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_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
*/
reset:
/*
* set the cpu to SVC32 mode
刚进入系统时,设置为管理模式
CPSR_c或SPSR_c的位示意图(控制位:低8位):
7 6 5 4 3 2 1 0
I F T M4 M3 M2 M1 M0
I:IRQ禁止,1禁止,0允许
F:FIQ禁止,1禁止,0允许
T:状态位,0-ARM,1-Thumb
M4-M0:模式位
10000-用户,10001-快速中断,10010-中断,10011-管理,10111-未定义,11111-系统
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* 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) /*这是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, =pWTCON /*这是个ldr伪指令,将pWTCON所代表的32值给r0寄存器*/
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
ldr r0, =INTMSK
mov r1, #0xffffffff
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif /*CONFIG_S3C2410*/
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
/*
cpu_init_crit代码的主要工作是:禁止MMU,以及指令cache和数据cache,因为uboot在开始时,需要用到物理地址,不能是虚拟地址。故禁止MMU.关闭cache,是为了uboot在写入数据的时候,一定要实实在在的写到物理地址上,而不是写到缓存中。顺便再跳转到lowlevel_init.s中,进行sdram的初始化,为以后拷贝代码做好准备
*/
#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
/*
bss段的开始地址,就是代码段的结束地址:_bss_start-_armboot_start=size of data
关于_start 和_TEXT_BASE之间的关系,还有虚拟地址,物理地址在此处的分配,还有待以后研究。
注意:此处的源代码,并没有涉及从nand flash 拷贝数据到ram中,如果需要移植的话,要自己写nand flash的驱动函数,并且拷贝数据到ram中。
*/
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
/*
这个循环拷贝,是从nor flash 中拷贝数据到ram中,并不是从nand flash拷贝数据,nor flash的操作类似于ram,直接操作地址总线,数据总线即可。Nand flash却不可以,需要一定的操作时序和驱动。直白一点:nor flash类似于ram,可以直接跟cpu的数据、控制、地址总线连接,进行控制;nand flash 不可以,有时序限制,需要驱动。
*/
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
分析:建立栈空间的过程
Ram中的地址分布(高->低):...,bss区,u-boot镜像,CFG_MALLOC_LEN,GBL_DATA_SIZE,
IRQ&FIQ栈区(可选,视具体情况),用户栈区(向下生长,sp指针初始位置),SDRAM_BASE。_TEXT_BASE并不是ram中的起始地址,只是u-boot放置的首地址,源码中是:0x33f80000。
*/
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_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
/*
借此详细分析:编译器跟代码的交互原理
在ads编译程序代码的时候,会要求我们输入程序的入口代码地址,开始执行地址,各个段的存放首地址(基本由编译器自己根据代码大小情况决定,但我们可以自己指定,虽然一般不这么做)等信息。
类似,在用交叉编译器,编译我们的bootloader代码时,它们也会用到这些地址。不过一般由编译器自己指定一部分(如:__bss_start),我们指定一部分必要的(如:TEXT_BASE,一般放在某些配置文件里边,供编译器查看)。
我们看看由编译器自己指定的情况:
.globl _bss_start
_bss_start:
.word __bss_start
定义一个全局标号_bss_start,标号代表该地址,在该地址处定义了一个字大小的变量,变量内容是__bss_start,而这个__bss_start是由编译器在编译代码时确定的,由编译器把该变量的值填入此位置,供其他代码使用。
__bss_start:应该是编译器内部定义好的变量,专用的。
*/
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
/*这一段代码默认是不编译的,也就是寄存器设置,和时钟设置,参考datasheet就可以*/
#if 0
/* try doing this stuff after the relocation */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMR
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
/* END stuff after relocation */
#endif
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:
/*
以下指令时对协处理器进行操作:cp15
* flush v4 I/D caches
mcr p15, 0, r0, c7, c7, 0;这一句的意思是:使指令cache和数据cache失效,即:关闭
这一句的具体含义见《arm920t technical reference manual》手册,里边也并未给出具体含义,只是写出了某些操作时需要用到的命令。
mcr p15, 0, r0, c8, c7, 0;关闭指令tlb和数据tlb,文档也只是列出了五个相关指令。
*/
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
协处理器中register 1是control register
13:V bit function:base location of exception registers(异常寄存器的基地址)
0=low address=0x00000000;1=high address=0xffff0000
12: I bit Icache enable ,0=Icache disabled,1=Icache enabled
9:R bit ROM protection,this bit modifies the MMU protection system
8: S bit system protection ,this bit modifies the MMU protection system
7: B bit endianness ,0=little-endian operation 1=big-endian operation
2: C bit Dcache enable ,0=Dcache disabled,1=Dcache enabled
1: A bit Alignment fault enable,Data address alignment fault checking
0=fault checking disabled, 1=fault checking enabled
0: M bit MMU enable 0=MMU disabled,1=MMU enabled
*/
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 1 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* 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.
*/
/*
r12 是内部调用暂时寄存器 ip。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 r12。
ip就是r12寄存器的别名,arm和thumb子程序调用使用规则ATPCS,定义了这些内容。
Reg APCS 意义
R0 a1 工作寄存器
R1 a2 "
R2 a3 "
R3 a4 "
R4 v1 必须保护
R5 v2 "
R6 v3 "
R7 v4 "
R8 v5 "
R9 v6 "
R10 sl 栈限制
R11 fp 桢指针
R12 ip
R13 sp 栈指针
R14 lr 连接寄存器
R15 pc 程序计数器
译注:ip 是指令指针的简写。
注意:千万不要把ip和pc两个寄存器混淆了,ip=r12,pc=r15
*/
mov ip, lr //保存子程序返回地址,放到r12中。
bl lowlevel_init //初始化内存配置
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/*
*************************************************************************
*
* Interrupt handling
*从这里往下,是中断处理函数的调用,一般在uboot中,不会运行到此,uboot中禁用了irq和fiq中断。其他中断应该可以,但一般不会进入。
在此,因为还对编译器中组织的代码不太熟悉,能看懂基本功能即可,不深入追究。等以后再来深入分析原理。
*************************************************************************
*/
@
@ 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
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
ldmia r2, {r2 - r3} @ get pc, cpsr
add r0, sp, #S_FRAME_SIZE @ restore sp_SVC
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
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+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
str lr, [r13] @ save caller lr / spsr
mrs lr, spsr
str lr, [r13, #4]
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13
mov lr, pc
movs pc, lr
.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