u-boot-2009.08在mini2440上的移植(一)-建立mini2440工程环境(2)
在真正开始移植Uboot之前,这里还是先分析一下uboot的启动流程吧!很有利于之后对移植的理解,这里分析的是未经修改的u-boot源码
根据cpu/arm920t/u-boot.lds中指定的连接方式:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .text : { cpu/arm920t/start.o (.text) *(.text) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.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) . = ALIGN(4); } _end = .; }第一个链接的是cpu/arm920t/start.o,因此u-boot.bin的入口代码在cpu/arm920t/start.o中,其源代码在cpu/arm920t/start.S中。下面我们来分析cpu/arm920t/start.S的执行,下面就开始分段分析start.S这个文件
.globl _start _start: b start_code //复位 ldr pc, _undefined_instruction //未定义指令响向量 ldr pc, _software_interrupt //软件中断向量 ldr pc, _prefetch_abort //读取指令异常向量 ldr pc, _data_abort //数据操作异常向量 ldr pc, _not_used //未使用 ldr pc, _irq //irq中断向量 ldr pc, _fiq //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
globl是个关键字,所以,意思很简单,就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问,所以,你可以看到u-boot.lds中,有用到此变量:
ENTRY(_start)
即指定入口为_start,而由下面的_start的含义可以得知,_start就是整个start.S的最开始,即整个uboot的代码的开始。
_start后面加上一个冒号’:’,表示其是一个标号Label,类似于C语言goto后面的标号。
而同时,_start的值,也就是这个代码的位置了,此处即为代码的最开始,相对的0的位置。而此处最开始的相对的0位置,在程序开始运行的时候,如果是从NorFlash启动,那么其地址是0,_stat=0,如果是重新relocate代码之后,就是我们定义的值了,即在u-boot-2009.08\board\samsung\config.mk中的:
TEXT_BASE = 0x33F80000
表示是代码段的基地址,即_start=TEXT_BASE=0x33F80000
而_start标号后面的:
b reset
就是跳转到对应的标号为reset的位置。
上面那些ldr的作用,以第一个_undefined_instruction为例,就是将地址为_undefined_instruction中的一个word的值,赋值给pc。
所以上面的含义,以_undefined_instruction为例,就是,此处分配了一个word=32bit=4字节的地址空间,里面存放的值是undefined_instruction。
而此处_undefined_instruction也就是该地址空间的地址了。用C语言来表达就是:
_undefined_instruction = &undefined_instruction
或
*_undefined_instruction = undefined_instruction
在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。(其他几个对应的“软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”,也是同样的做法,跳转到对应的位置执行对应的代码。)
所以:
ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
......(具体要执行的代码)
的意思就是,将地址为标号1中内容载入到pc,而地址为标号1中的内容,正好装的是标号2。
用C语言表达其实很简单:
PC = *(标号1) = 标号2
对PC赋值,即是实现代码跳转,所以整个这段汇编代码的意思就是:
跳转到标号2的位置,执行对应的代码。
接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。觉得这样解释会更加合理些:
此处0xdeadbeef本身没有真正的意义,但是很明显,字面上的意思是,(坏)死的牛肉。虽然其本身没有实际意义,但是其是在十六进制下,能表示出来的,为数不多的,可读的单词之一了。另外一个相对常见的是:0xbadc0de,意思是bad code,坏的代码,注意其中的o是0,因为十六进制中是没有o的。这些“单词”,相对的作用是,使得读代码的人,以及在查看程序运行结果时,容易看懂,便于引起注意。而关于自己之前,随意杜撰出来的,希望起到搞笑作用,表示good beef(好的牛肉)的0xgoodbeef,实际上,在十六进制下,会出错的,因为十六进制下没有o和 g这两个字母。
在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。
其中复位异常向量的指令“b start_code”决定了U-Boot启动后将自动跳转到标号“start_code”处执行。在真正开始运行之前,我们还是很有必要再分析一下之后的这一小段代码:
_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此处和上面的类似,_TEXT_BASE是一个标号地址,此地址中是一个word类型的变量,变量名是TEXT_BASE,此值见名知意,是text的base,即代码的基地址,可以在 u-boot-2009.08\board\samsung\config.mk中找到他的定义:
1._start的值经过上面的分析已经得出,可用c语言的形式表示为*(_armboot_start) = _start
2.关于_bss_start和_bss_end都只是两个标号,对应着此处的地址。而两个地址里面分别存放的值是__bss_start和_end,这两个的值,根据注释所说,是定义在开发板相关的链接脚本里面的,我们此处的开发板相关的链接脚本也就是最开始提到的u-boot.lds了,看上面的代码可以知道这两个变量的定义,而关于_bss_start和_bss_end定义为.glogl即全局变量,是因为uboot的其他源码中要用到这两个变量,详情请自己去搜索源码。
3.后面就是直接的赋值语句了,用c语言的形式表示如下:*(IRQ_STACK_START)= 0x0badc0de和*(FIQ_STACK_START)= 0x0badc0de,IRQ_STACK_START和FIQ_STACK_START,将会在cpu_init中用到。不过此处,是只有当定义了宏CONFIG_USE_IRQ的时候,才用到这两个变量,其含义也很明显,只有用到了中断IRQ,才会用到中断的堆栈,才有中端堆栈的起始地址。快速中断FIQ,同理。
第一步:
start_code: /* * set the cpu to SVC32 mode */ mrs r0,cpsr /*将CPSR寄存器地址交给RO*/ bic r0,r0,#0x1f /*工作模式位清零,bic将r0的后五位清理并保存在r0中*/ orr r0,r0,#0xd3 /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1*/ msr cpsr,r0 bl coloured_LED_init /*在编译的时候产生错误,已经注释掉了*/ bl red_LED_on /*在编译的时候产生错误,已经注释掉了*/
上面方法设置cpu进入SVC模式,其实要做动作很简单,获取到CPSR寄存器的地址,保存到ro中,然后清除这个寄存器中值的后五位,然后将这五位填充为“10011”,然后再将保存到r0中的计算结果保存到CSPR寄存器中去,下面是s3c2440的官方手册中的说明,后面的五位M4,M3,M2,M1,M0就是我们要修改的地方,这五位决定的模式状态的选择,具体如何选择下面也有贴出:
CPSR 是当前的程序状态寄存器(Current Program Status Register),而 SPSR 是保存的程序状态寄存器(Saved Program Status Register)。
MRS指令的格式为:
MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR)
MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:
Ⅰ.当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
Ⅱ.当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。
指令示例:
MRS R0,CPSR ;传送CPSR的内容到R0
MRS R0,SPSR ;传送SPSR的内容到R0”
所以,上述汇编代码含义为,将CPSR的值赋给R0寄存器。
BIC指令的格式为:
BIC{条件}{S} 目的寄存器,操作数1,操作数2
BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,
操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。
0x1f=11111b,所以,此行代码的含义就是,清除r0的bit[4:0]位。
ORR指令的格式为:
ORR{条件}{S} 目的寄存器,操作数1,操作数2
ORR指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位。
指令示例:
ORR R0,R0,#3 ; 该指令设置R0的0、1位,其余位保持不变。
所以此行汇编代码的含义为:
而0xd3=1101 0111[4:0]位。
将r0与0xd3算数或运算,然后将结果给r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置为1。
MSR指令的格式为:
MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数
MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为4个域:
位[31:24]为条件标志位域,用f表示;
位[23:16]为状态位域,用s表示;
位[15:8]为扩展位域,用x表示;
位[7:0]为控制位域,用c表示;
该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。
指令示例:
MSR CPSR,R0 ;传送R0的内容到CPSR
MSR SPSR,R0 ;传送R0的内容到SPSR
MSR CPSR_c,R0 ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域
此行汇编代码含义为,将r0的值赋给CPSR。
第二步:
/*这里是为AT91RM9200写的代码,暂时先不关注*/ #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) /* * relocate exception table */ ldr r0, =_start ldr r1, =0x0 mov r2, #16 copyex: subs r2, r2, #1 ldr r3, [r0], #4 str r3, [r1], #4 bne copyex #endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) /* turn off the watchdog */ /*设置控制寄存器地址*/ # if defined(CONFIG_S3C2400) # define pWTCON 0x15300000 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ # define CLKDIVN 0x14800014 /* clock divisor register */ #else # define pWTCON 0x53000000 /* 看门狗timer寄存器地址*/ # define INTMSK 0x4A000008 /* 中断控制寄存器 */ # define INTSUBMSK 0x4A00001C /* SUB中断控制寄存器*/ # define CLKDIVN 0x4C000014 /* 时钟分频寄存器 */ # endif /* 关闭看门狗*/ ldr r0, =pWTCON /* 将WTCON寄存器地址保存到r0中*/ mov r1, #0x0 /* R1中保存的值为0*/ str r1, [r0] /* 用str将r1中的值写入到r0所保存的地址中*/ /* * mask all IRQs by setting all bits in the INTMR - default */ /* 屏蔽所有中断*/ mov r1, #0xffffffff /* R1中保存的值为0xffffffff*/ ldr r0, =INTMSK /* 将INTMSK寄存器地址保存到r0中*/ str r1, [r0] /* 用str将r1中的值写入到r0所保存的地址中*/ # if defined(CONFIG_S3C2410) ldr r1, =0x3ff /* R1中保存的值为0x3ff*/ ldr r0, =INTSUBMSK /* 将INTSUBMSK寄存器地址保存到r0中*/ str r1, [r0] /* 用str将r1中的值写入到r0所保存的地址中*/ # endif /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN /* 将CLKDIVN寄存器地址保存到r0中*/ mov r1, #3 /* R1中保存的值为3*/ str r1, [r0] /* 用str将r1中的值写入到r0所保存的地址中*/ #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
上面几个宏定义所对应的地址,都可以在对应的datasheet中找到对应的定义:
其中,S3C2410和TQ2440开发板所用的CPU S3C2440,两者在这部分的寄存器定义,都是一样的,所以此处,采用CONFIG_S3C2410所对应的定义。
关于S3C2440相关的软硬件资料,这个网站提供的很全面:http://just4you.springnote.com/pages/1052612
1.先是用r0寄存器存pWTCON的值,然后r1=0,再将r1中的0写入到pWTCON中,其实就是pWTCON = 0;
看门狗控制器的最低位为0时,看门狗不输出复位信号,以上代码向看门狗控制寄存器写入0,关闭看门狗。否则在U-Boot启动过程中,CPU将不断重启。
伪指令,就是“伪”的指令,是针对“真”的指令而言的。
真的指令就是那些常见的指令,比如上面说的arm的ldr,bic,msr等等指令,是arm体系架构中真正存在的指令,你在arm汇编指令集中找得到对应的含义。
而伪指令是写出来给汇编程序看的,汇编程序能看的伪指令具体表示的是啥意思,然后将其翻译成真正的指令或者进行相应的处理。
伪指令ldr语法和含义:http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx
另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。ldr伪指令可以在立即数前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。
只不过mov指令后面的立即数是有限制的,这个立即数,能够必须由一个8位的二进制数,即属于0x00-0xFF内的某个值,经过偶数次右移后得到,这样才是合法数据,而ldr伪指令没有这个限制。
那为何ldr伪指令的操作数没有限制呢,那是因为其是伪指令,写出来的伪指令,最终会被编译器解释成为真正的,合法的指令的,一般都是对应的mov指令。
这样的话,写汇编程序的时候,使用MOV指令是比较麻烦的,因为有些简单的数据比较容易看出来,有些数据即不容易看出来是否是合法数据。所以,对此,ldr伪指令的出现,就是为了解决这个问题的,你只管放心用ldr伪指令,不用关心操作数,而写出的ldr伪指令,编译器会帮你翻译成对应的真正的汇编指令的。
而关于编译器是如何将这些ldr伪指令翻译成为真正的汇编指令的,我的理解是,其自动会去算出来对应的操作数,是否是合法的mov 的操作数,如果是,就将该ldr伪指令翻译成mov指令,否则就用别的方式处理,我所观察到的,其中一种方式就是,单独申请一个4字节的空间用于存放操作数,然后用ldr指令实现。
在uboot中,最后make完毕之后,会生产u-boot,
通过:
arm-linux-objdump –d u-boot > dump_u-boot.txt
就可以把对应的汇编代码输出到该txt文件了,其中就能找到伪指令:
ldr r0, =0x53000000
所对应的,真正的汇编代码:
33d00068: e3a00453 mov r0, #1392508928 ; 0x53000000
所以被翻译成了mov指令。
而经过我的尝试,故意将0x53000000改为0x53000010,对应的生产的汇编代码为:
33d00068: e59f0408 ldr r0, [pc, #1032] ; 33d00478 <fiq+0x58>
......
33d00478: 53000010 .word 0x53000010
其中可以看到,由于0x53000010不是有效的mov的操作数,没法找到合适的0x00-0Xff去通过偶数次循环右移而得到,所以只能换成此处这种方式,即在另外申请一个word的空间用于存放这个值:
33d00478: 53000010 .word 0x53000010
然后通过计算出相对当前PC的偏移,得到的地址,用ldr指令去除该地址中的值,即0x53000010,送给r0,比起mov指令,要复杂的多,也多消耗了一个word的空间。
对应地,其他的方式,个人理解,好像也可以通过MVN指令来实现,具体细节,有待进一步探索。
而这里的:ldr r0, =pWTCON
意思就很清楚了,就是把宏pWTCON的值赋值给r0寄存器,即
r0=0x53000000
MOV指令的格式为:
MOV{条件}{S} 目的寄存器,源操作数
MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。
指令示例:
MOV R1,R0 ;将寄存器R0的值传送到寄存器R1
MOV PC,R14 ;将寄存器R14的值传送到PC,常用于子程序返回
MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1
不过对于MOV指令多说一句,那就是,一般可以用类似于:
MOV R0,R0
的指令来实现NOP操作。
上面这句mov指令很简单,就是把0x0赋值给r1,即r1=0x0
STR指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
指令示例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并
将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
所以这句str的作用也很简单,那就是将r1寄存器的值,传送到地址值为r0的(存储器)内存中。
用C语言表示就是:*r0 = r1
2.INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理。根据手册,INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK寄存器全部位置一,从而屏蔽所有的中断。
此处,关于mask这个词,解释一下。mask这个单词,是面具的意思,而中断被mask了,就是中断被掩盖了,即虽然硬件上中断发生了,但是此处被屏蔽了,所以从效果上来说,就相当于中断被禁止了,硬件上即使发生了中断,CPU也不会去执行对应中断服务程序ISR了。
各位所控制的中断,下面只贴出来一部分,可以自己查看手册:
3.INTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。根据手册,INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,从而屏蔽对应的中断。
此处是将2410的INTSUBMSK设置为0x3ff。开始时很是不能理解,为什么要屏蔽掉所有中断,而这里只屏蔽掉了后面十位,从手册上看是低15位啊,的确此处设置的0x3ff,是不严谨的。因为,根据2410的datasheet中关于INTSUBMSK的解释,bit[10:0]共11位,虽然默认reset的每一位都是1,但是此处对应的mask值,应该是11位全为1=0x7ff。即写成0x3ff,虽然是可以正常工作的,但是却不够严谨的。此处CPU是是S3C2440,所以用到0x7fff这段代码。其意思也很容易看懂,就是将INTSUBMSK寄存器的值设置为0x7fff。
4.设置时钟分频寄存器
这里只是设置相应的位,不做过多说明:
第四步:
/* * we do sys-critical inits only at reboot, * not when booting from ram! */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif
关于bl指令的含义:
b指令,是单纯的跳转指令,即CPU直接跳转到某地址继续执行。
而BL是Branch with Link,带分支的跳转,而Link指的是Link Register,链接寄存器R14,即lr,所以,bl的含义是,除了包含b指令的单纯的跳转功能,在跳转之前,还把r15寄存器=PC=cpu地址,赋值给r14=lr,然后跳转到对应位置,等要做的事情执行完毕之后,再用
mov pc, lr
使得cpu再跳转回来,所以整个逻辑就是调用子程序的意思。
BL指令的格式为:
BL{条件} 目标地址
BL 是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14 的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。以下指令:
BL Label ;当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14中
对于上面的代码来说,意思就很清晰了,就是当没有定义CONFIG_SKIP_LOWLEVEL_INIT的时候,就掉转到cpu_init_crit的位置,而在后面的代码cpu_init_crit中,你可以看到最后一行汇编代码就是
mov pc, lr,
又将PC跳转回来,所以整个的含义就是,调用子程序cpu_init_crit,等cpu_init_crit执行完毕,再返回此处继续执行下面的代码。
于此对应地b指令,就只是单纯的掉转到某处继续执行,而不能再通过mov pc, lr跳转回来了。
cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。跳入 cpu_init_crit ,这是一个系统初始化函数,他还会调用 board/*/lowlevel_init.S 中的lowlevel_init 函数。 主要是对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。下面的代码重定向就依赖它。下面分析一下cpu_init_crit到底做了什么:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT cpu_init_crit: /* * flush v4 I/D caches */ /* 使数据cache与指令cache无效 */ mov r0, #0 /* 向c7写入0将使ICache与DCache无效*/ mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ /* 向c8写入0将使TLB失效 */ mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 /*读出控制寄存器到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) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0 /*保存r0到控制寄存器*/ /* * 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 bl lowlevel_init mov lr, ip mov pc, lr #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
调用 board/*/lowlevel_init.S 中的 lowlevel_init函数,对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。 代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。将0写入c7、c8,使Cache,TLB内容无效。关闭了MMU。这里还有很多的不理解,接下来还是要重点研究一下啊
先看CP15的c1寄存器的格式(仅列出代码中用到的位):
各个位的意义如下:
V : 表示异常向量表所在的位置,0:异常向量在0x00000000;1:异常向量在 0xFFFF0000
I : 对ICaches的配置, 0 :关闭ICaches;1 :开启ICaches
R,S : 用来与页表中的描述符一起确定内存的访问权限
B : 0 :CPU为小字节序;1 : CPU为大字节序
C : 0:关闭DCaches;1:开启DCaches
A : 0:数据访问时不进行地址对齐检查;1:数据访问时进行地址对齐检查
M : 0:关闭MMU;1:开启MMU
之后跳转到lowlevel_init中运行,其中的lowlevel_init就完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于mini2410,lowlevel_init在board/samsung/smdk2410/lowlevel_init.S中定义如下:
_TEXT_BASE: .word TEXT_BASE .globl lowlevel_init lowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA ldr r1, _TEXT_BASE sub r0, r0, r1 ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4 0: ldr r3, [r0], #4 str r3, [r1], #4 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr .ltorg /* the literal pools origin */ SMRDATA: .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0x32 .word 0x30 .word 0x30这里上面针对ldr的说明还有遗漏的地方,加在这里了
指令 | 说明 |
LDR R0,[R1] | 将地址R1处字资料读入R0 |
LDR R0, [R1, R2] | 将地址R1+R2处字资料读入R0 |
LDR R0,[R1, #8] | 将地址R1+8处字资料读入R0 |
LDR R0, [R1, R2]! | 将地址R1+R2处字资料读入R0,并将新地址R1+R2写入R1 |
LDR R0, [R1, #8]! | 将地址R1+8处字资料读入R0,并将新地址R1+8写入R1 |
LDR R0, [R1], R2 | 将地址R1的字资料读入R0,并将新地址R1+R2写入R1 |
LDR R0, [R1, R2,LSL #2]! | 将地址R1+R2x4处字资料读入R0,新址R1+R2x4写入R1 |
LDR R0,[R1],R2,LSL #2 | 将地址R1处字资料写入R0,新址R1+R2x4写入R1 |
STR r0,[r1,#-12] | 将r0写入到地址r1-12处 |
然后cmp r2, r0对r2和r0进行比较,不等则跳转到上面标号为0处的地址继续执行bne 0b 跳回到返回地址中继续,实现了数据的搬移,知道吧定义的十三个寄存器的内容都搬移完成返回调用的地方继承往下执行
第五步:
#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 */
代码重定向,它首先检测自己是否已经在内存中: 如果是直接跳到下面的堆栈初始化代码 stack_setup。 如果不是就将自己从 Nor Flash 中拷贝到内存中 ,进行自循环拷贝
ADR是小范围的地址读取伪指令.ADR 指令将基于PC 相对偏移的地址值读取到寄存器中.在汇编编译源程序时,ADR 伪指令被编译器替换成一条合适的指令.通常,编译器用一条ADD 指令或SUB 指令来实现该ADR 伪指令的功能,若不能用一条指令实现,则产生错误,编译失败.上边的u-boot代码中的"adr r0,_start",就是把PC的当前值赋给r0;先是比较当前地址和_TEXT_BASE这两个符号的地址是否相等,_TEXT_BASE这个符号取值为TEXT_BASE,它的值为0x33F80000。其实就是判断u-boot是否已经在SDRAM中,如果u-boot已经在SDRAM中,那么也就不必拷贝了,直接跳到堆栈设置。如果u-boot没有在SDRAM中,那么就将u-boot拷贝到SDRAM中,CPU是可以从nor flash中取指执行的,但是在SDRAM中执行速度更快,所以将代码拷贝到SDRAM中。那么到底这里是怎样实现代码拷贝的呢??由上面知道,当前地址也就是u-boot代码段起始地址已经存放在寄存器r0了,然后计算代码段的结束地址,通过(_bss_start - _armboot_start)相减得到,结束地址放在寄存器r2中,然后使用多寄存器加载指令将代码复制到SDRAM中,代码放在SDRAM中的什么地方的呢,就是放在r1指向的地址,也就是TEXT_BASE这个地方的,也就是0x33F80000这个地方。
下面还是很有比较说明一下ldmia和stmia这两个多寄存器加载指令,一开始很是不理解啊
批量数据加载/存储指令ARM微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:
LDM(或STM)指令
LDM(或STM)指令的格式为:
LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。
其中,{类型}为以下几种情况:
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;
FA 满递增堆栈;
EA 空递增堆栈;
所以上面ldmia的意思就是每次传送地址加一,并且是从r0开始的一片连续的存储器的数据拷贝到后面寄存器列表所指示的寄存器中,也就是r3-r10这八个寄存器中,所以就是把r0---r0+4*7这段内存中的数据依次拷贝到r3-r10中
另外上面stmia的意思也是每次传送地址加一,并且是把后面寄存器列表所指示的寄存器,也就是r3-r10这八个寄存器中的数据拷贝到r1开始的一片连续的存储器中,所以就是把r3-r10中的数据依次拷贝到r1---r1+4*7这段内存中
所以r3-r10就是中转站,另外中间还有一个“!”呢,所以接着分析
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。所以这里ldmia数据传送完成之后相当于会将做下面这个动作r0=r0+4*7,所以这里才可以实现循环,原因就在这里了
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
第六步:
/* 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 ldr pc, _start_armboot _start_armboot: .word start_armboot前三条语句的含义是,把地址为_TEXT_BASE的内存中的内容给r0,即,而查看前面的相关部分的代码,即:
/* * Size of malloc() pool */ #define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + 128*1024) #define CONFIG_SYS_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data */
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */所以,此三行的含义就是算出r0的值:
如果定义了CONFIG_USE_IRQ,即如果使用中断的话,那么再把r0的值减去IRQ和FIQ的堆栈的值,而对应的宏的值也是在刚刚的路径下
#define CONFIG_STACKSIZE (128*1024) /* regular stack */ #ifdef CONFIG_USE_IRQ #define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */ #define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */ #endif最后,再减去终止异常所用到的堆栈大小,即12个字节。这里计算方法我就不再说了
首先将_bss_start地址信息保存到r0,_bss_end地址信息保存到r1,赋值r2为0,第一次进入clbss_l这个循环时,将r0地址的寄存器的值赋值为0,然后r0的地址增加4个字节,然后与r1保存的地址比较,如果不不相等则返回到clbss_l循环标示处继续循环,知道这个区间内所有内容都别清空,结束循环,接着往下运行。
上面代码的最后两条语句,跳入第二阶段的 C 语言代码入口_start_armboot (已经被重定向到内存) 这里c语言的代码分析单独做一部分进行讲解
第七步:
从这里开始异常中断处理程序,刚开始看到就蒙了,不知道从哪里下手了,其实此处很简单,只是一些宏定义而已。
.macro和后面的.endm相对应,所以,此处就相当于一个无参数的宏bad_save_user_regs,也就相当于一个函数了。所以我们一个个去分析这几个函数就可以了
.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) sub r2, r2, #(CONFIG_SYS_MALLOC_LEN) sub r2, r2, #(CONFIG_SYS_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这个函数bad_save_user_regs,先将sp-72再保存到sp中,然后将r0-r12这13个寄存器中的值批量拷贝到sp处,所以S_FRAME_SIZE一定要大于13*4=52,这个就就是为了提供足够的空间来保存这些寄存器的值,然后将_armboot_start的地址保存到r2中,让后r2-128*1024 - 256*1024-(128 + 8) = 0x33C9FF78,这里我没有去计算,直接拿来用的,然后分别将地址为r2和r2+4的内容,即地址为0x33C9FF78和0x33C9FF7C中的内容,load载入给r2和r3寄存器,然后将r0的值再指向sp最初的位置,这里要知道定义的S_SP=52,那么sp+52,刚好就在刚刚空间赋值之后的地址,刚刚提供了72个字节,用掉52个,还有20个,r5就指向紧跟那第52个字节的后面,将lr传给r1,让后把r0-r3这四个寄存器中的值保持到热r5所指向地址的连续四个寄存器中,其实保存的这四个寄存器分别是sp_SVC,lr_SVC,pc,spsr,最后将sp的值再付给r0,结束这个函数,我一直不是很明白,这个函数是做什么用的,实现什么样的功能??
接着看下一个函数:
.macro irq_save_user_regs sub sp, sp, #S_FRAME_SIZE stmia sp, {r0 - r12} @ Calling r0-r12 add r7, sp, #S_PC stmdb r7, {sp, lr}^ @ Calling SP, LR str lr, [r7, #0] @ Save calling PC mrs r6, spsr str r6, [r7, #4] @ Save CPSR str r0, [r7, #8] @ Save OLD_R0 mov r0, sp .endm
分析一下这段代码:方法其实跟上面的方法很是类似,首先sp自减S_FRAME_SIZE,首先先提供72字节的空间用于之后存储数据,然后将r0-r12这13个寄存器的数据拷贝到sp指向的新位置,然后sp自加60,移动到刚刚拷贝数据的末尾,除了刚刚使用了52=13*4个字节,中间也就是说还有8个字节,放心,这个8个字节没有被浪费掉,这个stmdb sp, {sp, lr}^,就是r7的地址自减1后再把sp的数据先存放下来,然后再次减一,再把lr的数据存放到这个地址,所以sp存放到相当于52的位置,lr存放到相当于56的位置.stm这个命令不再多说了,上面已经说过他的用法了,
待续。。。