/*
*
*Purpose: the document is used to learn detailed information aboutimx51 cpu start.S, *referring to some documents on websites.
*file address: U-boot-2009.08/Cpu/Arm_cortexa8/start.S
*
* writer: xfhai 2011.7.22
*
*Instruction:
*1.@xxxx : indicates annotation
*2./*****
***
*****/ : stand for code in my files
*3.instructions refers to code not included in my file
*
*/
Section 1: uboot overview
大多数bootloader都分为stage1和stage2两部分,u-boot也不例外。依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
1、Stage1 start.S代码结构
u-boot的stage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下:==> (1)定义入口。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
==>(2)设置异常向量(Exception Vector)。
==>(3)设置CPU的速度、时钟频率及终端控制寄存器。
==>(4)初始化内存控制器。
==>(5)将ROM中的程序复制到RAM中。
==>(6)初始化堆栈。
==>(7)转到RAM中执行,该工作可使用指令ldr pc来完成。
2、Stage2 C语言代码部分
lib_arm/board.c中的start arm boot是C语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,该函数只要完成如下操作:
==>(1)调用一系列的初始化函数。
==>(2)初始化Flash设备。
==>(3)初始化系统内存分配函数。
==>(4)如果目标系统拥有NAND设备,则初始化NAND设备。
==>(5)如果目标系统有显示设备,则初始化该类设备。
==>(6)初始化相关网络设备,填写IP、MAC地址等。
==>(7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
Section 2: demos
3、U-Boot的启动顺序(示例,其他u-boot版本类似)
cpu/arm920t/start.S
(my file is U-boot-2009.08/Cpu/Arm_cortexa8/start.S )
@文件包含处理
#include
@由顶层的mkconfig生成,其中只包含了一个文件:configs/<顶层makefile中6个参数的第1个参数>.h
#include
#include
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
注:ARM微处理器支持字节(8位)、半字(16位)、字(32位)3种数据类型向量跳转表,每条占四个字节(一个字),地址范围为0x0000 0000~@0x0000 0020,ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,通过硬件实现。他们就是异常向量表。ARM在上电复位后,是从0x00000000开始启动的,其实如果bootloader存在,在执行下面第一条指令后,就无条件跳转到start_code,下面一部分并没执行。设置异常向量表的作用是识别bootloader。以后系统每当有异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表做相应的处理
/******************************************************
;当一个异常出现以后,ARM会自动执行以下几个步骤:
;1.把下一条指令的地址放到连接寄存器LR(通常是R14).---保存位置
;2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中---保存CPSR
;3.根据异常类型,强制设置CPSR的运行模式位
;4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中
*********************************************************/
.globl _start /*系统复位位置,整个程序入口*/
@_start是GNU汇编器的默认入口标签,.globl将_start声明为外部程序可访问的标签,.globl是GNU汇编的保留关键字,前面加点是GNU汇编的语法
_start: b start_code @0x00
//diff here: _start: b reset
@ARM上电后执行的第一条指令,也即复位向量,跳转到start_code
@reset用b,就是因为reset在MMU建立前后都有可能发生
@其他的异常只有在MMU建立之后才会发生
ldr pc, _undefined_instruction /*未定义指令异常,0x04*/
ldr pc, _software_interrupt /*软中断异常,0x08*/
ldr pc, _prefetch_abort /*内存操作异常,0x0c*/
ldr pc, _data_abort /*数据异常,0x10*/
ldr pc, _not_used /*未适用,0x14*/
ldr pc, _irq /*慢速中断异常,0x18*/
ldr pc, _fiq /*快速中断异常,0x1c*/
@对于ARM数据从内存到CPU之间的移动只能通过L/S(load && storage)指令,如:ldr r0,0x12345678为把0x12345678内存中的数据写到r0中,还有一个就是ldr伪指令,如:ldr r0,=0x12345678为把0x12345678地址写到r0中,mov只能完成寄存器间数据的移动,而且立即数长度限制在8位
_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
//three more lines here:
//.pad: .word 0x12345678 /*now 16*4=64*/
//.global _end_vector
//_end_vect:
//don’t make sense
@.word为GNU ARM汇编特有的伪操作,为分配一段字内存单元(分配的单元为字对齐的),可以使用.word把标志符作为常量使用。如_fiq:.word fiq即把fiq存入内存变量_fiq中,也即是把fiq放到地址_fiq中。
.balignl 16,0xdeadbeef
.balign是意思是:以当前地址为开始开始,找到第一次出现的以第一个参数为整数倍的地址,并将其作为结束地址,在这个结束地址前面存储一个字节长度的数据,存储内容正是第二个参数。如果当前地址正好是第一个参数的倍数,则没有数据被写入到内存。
.balign 8, 0xde这条指令的含义可以用下图表示:
图解:以当前地址为开始开始,找到第一次出现的以8为整数倍的地址,并将其作为结束地址,在这个结束地址前面存储一个字节长度的数据0xde。如果当前地址正好是8的倍数,则没有数据被写入到内存。
以此类推,.balignw则表示第二个参数存入的内容长度为2字节:
.balignw 4, 0x368d
因为现在填入的内容为2个字节,那就存在以下几种情况:
当前地址没有偏移就满足了以4为倍数的地址
当前地址偏移了1个字节就满足了以4为倍数的地址
当前地址偏移了2个字节就满足了以4为倍数的地址
当前地址编移了3个字节就满足了以4为倍数的地址
分析一下这四种情况:
当没有偏移的时候,地址中间肯定没有办法填上信息
当偏移1个字节的时候,地址中间空隙不够,所以填入的数值,是末定义,也就是说,填入什么值,不清楚
当偏移2个字节的时候,地址中间的空隙正好填入0x368d两个字节的内容
当偏移3个字节的时候,地址中间的空隙大于所要填的内容。此时填入的数值,是末定义,填入什么值,不清楚
以此类推,.balignl,这个指令用来填与一个字,即4个字节的长度
仔细分析一下就知道,对于.balignl 16, 0xdeadbeef,如果想要0xdeadbeef一定填到当前地址后面某个部分,当前地址偏移量就必须为4字节,这样才能保证在任何情况下,偏移的地址所留的空隙刚好填入所要填的内容。
//伪操作指机器码里没有对应的汇编指令,由编译器实现其功能
/*
*************************************************************************
*
* Startup Code (called from the ARM reset exception vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
@保存变量的数据区,保存一些全局变量,用于BOOT程序从FLASH拷贝@到RAM,或者其它的使用。还有一些变量的长度是通过连接脚本里得到,实际上由编译器算出@来的
_TEXT_BASE:
@因为linux开始地址是0x30000000
//the reason for linux start address is 0x30000000?
.word TEXT_BASE /*uboot映像在SDRAM中的重定位地址*/
@TEXT_BASE在开发板相关的目录中的config.mk文档中定义, 他定义了代码在运行时所在的地址, 那么_TEXT_BASE中保存了这个地址(这个TEXT_BASE怎么来的还不清楚)
//the file address is U-boot-2009.08/Board/freescale/Mx51_bbg/Config.mk
//& the TEXT_BASE is 0x978000000, has any special reasons?
.globl _armboot_start
_armboot_start:
.word _start
@用_start来初始化_armboot_start。
/*
* These are defined in the board-specific linker script.
*/
@下面这些是定义在开发板目录链接脚本中的
.globl _bss_start
_bss_start:
.word __bss_start
@__bss_start定义在和开发板相关的u-boot.lds中,_bss_start保存的是__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 start code
*/
@复位后执行程序
@真正的初始化从这里开始了。其实在CPU一上电以后就是跳到这里执行的
reset:
/*
* set the cpu to SVC32 mode
*/
@更改处理器模式为管理模式
@对状态寄存器的修改要按照:读出-修改-写回的顺序来执行
@ 31 30 29 28 --- 7 6 - 4 3 2 1 0
N Z C V I F M4 M3 M2 M1 M0
0 0 0 0 0 User26 模式
0 0 0 0 1 FIQ26 模式
0 0 0 1 0 IRQ26 模式
0 0 0 1 1 SVC26 模式
1 0 0 0 0 User 模式
1 0 0 0 1 FIQ 模式
1 0 0 1 0 IRQ 模式
1 0 0 1 1 SVC 模式
1 0 1 1 1 ABT 模式
1 1 0 1 1 UND 模式
1 1 1 1 1 SYS 模式