目录
-
正文之前
- 1. start.S详解
- 2. start.S的总结
- 3. 相关知识点详解
- 参考书目
插图清单
-
1.1.
LDR指令的语法
1.2.
CPSR/SPSR的位域结构
1.3.
pWTCON
1.4.
INTMOD
1.5.
INTMSK
1.6.
INTSUBMSK
1.7.
CLKDIVN
1.8.
WTCON寄存器的位域
1.9.
INTMSK寄存器的位域
1.10.
INTSUBMSK寄存器的位域
1.11.
INTSUBMSK寄存器的位域
1.12.
macro的语法
1.13.
LDM/STM的语法
1.14.
条件码的含义
2.1.
Uboot中的内存的Layout
3.1.
AMR7三级流水线
3.2.
ARM7三级流水线状态
3.3.
ARM7三级流水线示例
3.4.
ARM7三级流水线 vs ARM9五级流水线
3.5.
ARM7三级流水线到ARM9五级流水线的映射
3.6.
ARM9的五级流水线示例
3.7.
ARM9的五级流水线中为何PC=PC+8
3.8.
ARM Application Procedure Call Standard (AAPCS)
3.9.
数据处理指令的指令格式
表格清单
-
1.1.
global的语法
1.2.
.word的语法
1.3.
balignl的语法
1.4.
CPSR Bitfield
1.5.
CPSR=0xD3的位域及含义
1.6.
控制寄存器1的位域含义
1.7.
时钟模式
1.8.
关于访问控制位在域访问控制寄存器中的含义
1.9.
关于访问允许(AP)位的含义
3.1.
ARM中CPU的模式
3.2.
ARM寄存器的别名
3.3.
mov指令0xe3a00453的位域含义解析
范例清单
-
3.1.
汇编中的ldr加标号实现函数调用 示例
3.2.
3.3.
本文的目标是,希望看完此文的读者,可以达到:
- 微观上,对此start.S的每一行,都有了基本的了解
- 宏观上,对基于ARM核的S3C24X0的CPU的启动过程,有更加清楚的概念
这样的目的,是为了读者看完本文后,再去看其他类似的启动相关的源码,能明白需要做什么事情,然后再看别的系统是如何实现相关的内容的,达到一定程度的触类旁通。
总体说就是,要做哪些,为何要这么做,如何实现的,即英语中常说的:
- do what
- why do
- how do
此三方面都清楚理解了,那么也才能算真正懂了。
所用代码来自TQ2440官网,天嵌的bbs上下载下来的uboot中的源码:
u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\start.S
阅读此文之前,你至少要对TQ2440的板子有个基本的了解,
以及要了解开发板初始化的大概要做的事情,比如设置输入频率,设置堆栈等等。
另外,至少要有一定的C语言的基础,这样更利于理解汇编代码。
摘要
下面将详细解释uboot中的start.S中的每一行代码。详细到,每个指令的语法和含义,都进行详细讲解,使得此文读者可以真正搞懂具体的含义,即what。
以及对于一些相关的问题,深入探究为何要这么做,即why。
对于uboot的start.S,主要做的事情就是系统的各个方面的初始化。
从大的方面分,可以分成这几个部分:
- 设置CPU模式
- 关闭看门狗
- 关闭中断
- 设置堆栈sp指针
- 清除bss段
- 异常中断处理
下面来对start.S进行详细分析,看看每一个部分,是如何实现的。
/* * 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] * ************************************************************************* */ .globl_start
globl是个关键字,对应含义为: http://re-eject.gbadev.org/files/GasARMRef.pdf表 1.1. global的语法
所以,意思很简单,就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问 所以,你可以看到
中,有用到此变量: ENTRY(_start) 即指定入口为_start,而由下面的_start的含义可以得知,_start就是整个start.S的最开始,即整个uboot的代码的开始。 |
_start: b reset
_start后面加上一个冒号’:’,表示其是一个标号Label,类似于C语言goto后面的标号。 而同时,_start的值,也就是这个代码的位置了,此处即为代码的最开始,相对的0的位置。 而此处最开始的相对的0位置,在程序开始运行的时候,如果是从NorFlash启动,那么其地址是0, _stat=0 如果是重新relocate代码之后,就是我们定义的值了,即,在
中的: TEXT_BASE = 0x33D00000 表示是代码段的基地址,即 _start=TEXT_BASE=0x33D00000 关于标号的语法解释:
而_start标号后面的: b reset 就是跳转到对应的标号为reset的位置。 |
ldrpc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq
ldr命令的语法为:
上面那些ldr的作用,以第一个_undefined_instruction为例,就是将地址为_undefined_instruction中的一个word的值,赋值给pc。 |
_undefined_instruction: .wordundefined_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
http://re-eject.gbadev.org/files/GasARMRef.pdf
所以上面的含义,以_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的位置,执行对应的代码。 |
.balignl16,0xdeadbeef
balignl这个标号的语法及含义:
所以意思就是,接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。 其中关于所要填充的内容0xdeadbeef,刚开始没看懂是啥意思,后来终于搞懂了。 经过( 此处0xdeadbeef本身没有真正的意义,但是很明显,字面上的意思是,(坏)死的牛肉。 虽然其本身没有实际意义,但是其是在十六进制下,能表示出来的,为数不多的,可读的单词之一了。 另外一个相对常见的是:0xbadc0de,意思是bad code,坏的代码,注意其中的o是0,因为十六进制中是没有o的。 这些“单词”,相对的作用是,使得读代码的人,以及在查看程序运行结果时,容易看懂,便于引起注意。 而关于自己之前,随意杜撰出来的,希望起到搞笑作用,表示good beef(好的牛肉)的0xgoodbeef,实际上,在十六进制下,会出错的,因为十六进制下没有o和 g这两个字母。 |
/* ************************************************************************* * * 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
关于_bss_start和_bss_end都只是两个标号,对应着此处的地址。 而两个地址里面分别存放的值是__bss_start和_end,这两个的值,根据注释所说,是定义在开发板相关的链接脚本里面的,我们此处的开发板相关的链接脚本是:
其中可以找到__bss_start和_end的定义: __bss_start = .; .bss : { *(.bss) } _end = .; 而关于_bss_start和_bss_end定义为.glogl即全局变量,是因为uboot的其他源码中要用到这两个变量,详情请自己去搜索源码。 |
.globl FREE_RAM_END FREE_RAM_END: .word 0x0badc0de.globl FREE_RAM_SIZE FREE_RAM_SIZE: .word 0x0badc0de
关于FREE_RAM_END和FREE_RAM_SIZE,这里只是两个标号,之所以也是声明为全局变量,是因为uboot的源码中会用到这两个变量。 但是这里有点特别的是,这两个变量,将在本源码start.S中的后面要用到,而在后面用到这两个变量之前,uboot的C源码中,会先去修改这两个值,具体的逻辑是: 本文件start.S中,后面有这两句: ldr pc, _start_armboot _start_armboot: .word start_armboot 意思很明显,就是去调用start_armboot函数。 而start_armboot函数是在:
中: init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */ ...... NULL, }; void start_armboot (void) { init_fnc_t **init_fnc_ptr; ...... for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } ...... } 即在start_armboot去调用了cpu_init。 cpu_init函数是在:
中: int cpu_init (void) { /* * setup up stacks if necessary */ #ifdef CONFIG_USE_IRQ IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #else FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE; FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1; #endif return 0; } 在cpu_init中,根据我们的一些定义,比如堆栈大小等等,去修改了IRQ_STACK_START ,FIQ_STACK_START ,FREE_RAM_END和FREE_RAM_SIZE的值。 至于为何这么修改,后面遇到的时候会具体再解释。 |
/* * the actual reset code */ reset: /* * set the cpu to SVC32 mode */ mrsr0,cpsr
![]()
CPSR 是当前的程序状态寄存器(Current Program Status Register), 而 SPSR 是保存的程序状态寄存器(Saved Program Status Register)。 具体细节,可参考: | |
MRS - Move From Status Register MRS指令的语法为:
所以,上述汇编代码含义为,将CPSR的值赋给R0寄存器。 |
msrcpsr,r0
MSR - Move to Status Register msr的指令格式是:
此行汇编代码含义为,将r0的值赋给CPSR。 |
所以,上面四行汇编代码的含义就很清楚了。
先是把CPSR的值放到r0寄存器中,然后清除bit[4:0],然后再或上
0xd3=11 0 10111b
表 1.5. CPSR=0xD3的位域及含义
CPSR位域 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
位域含义 | I | F | M4 | M3 | M2 | M1 | M0 | |
0xD3 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
对应含义 | 关闭中断IRQ | 关闭快速中断FIQ | 设置CPU为SVC模式,这和上面代码注释中的“set the cpu to SVC32 mode”,也是一致的。 |
关于为何设置CPU为SVC模式,而不是设置为其他模式,请参见本文档后面的章节:第 3.2 节 “uboot初始化中,为何要设置CPU为SVC模式而不是设置为其他模式”
/* 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) || defined(CONFIG_S3C2440)# define pWTCON 0x53000000 # define INTMOD 0X4A000004 # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */ # define INTSUBMSK 0x4A00001C # define CLKDIVN 0x4C000014 /* clock divisor register */ #endif
上面几个宏定义所对应的地址,都可以在对应的datasheet中找到对应的定义: 其中,S3C2410和TQ2440开发板所用的CPU S3C2440,两者在这部分的寄存器定义,都是一样的,所以此处,采用CONFIG_S3C2410所对应的定义。 关于S3C2440相关的软硬件资料,这个网站提供的很全面: http://just4you.springnote.com/pages/1052612 其中有S3C2440的CPU的datasheet: 其中有对应的寄存器定义: 而关于每个寄存器的具体含义,见后面的分析。 |
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440) ldrr0, =pWTCON
这里的ldr和前面介绍的ldr指令不是一个意思。 这里的ldr是伪指令ldr。
而这里的: ldr r0, =pWTCON 意思就很清楚了,就是把宏pWTCON的值赋值给r0寄存器,即 r0=0x53000000 |
movr1, #0x0
mov指令语法:
不过对于MOV指令多说一句,那就是,一般可以用类似于: MOV R0,R0 的指令来实现NOP操作。 上面这句mov指令很简单,就是把0x0赋值给r1,即 r1=0x0 |
所以,上面几行代码意思也很清楚:
先是用r0寄存器存pWTCON的值,然后r1=0,再将r1中的0写入到pWTCON中,其实就是
pWTCON = 0;
而pWTCON寄存器的具体含义是什么呢?下面就来了解其详细含义:
注意到bit[0]是Reset Enable/Disable,而设置为0的话,那就是关闭Watchdog的reset了,所以其他位的配置选项,就更不需要看了。
我们只需要了解,在此处禁止了看门狗WatchDog(的复位功能),即可。
关于看门狗的作用,以及为何要在系统初始化的时候关闭看门狗,请参见本文档后面的章节:第 3.3 节 “什么是watchdog + 为何在要系统初始化的时候关闭watchdog”
/* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0])![]()
上面这几行代码,和前面的很类似,作用很简单,就是将INTMSK寄存器设置为0xffffffff,即,将所有的中端都mask了。 关于每一位的定义,其实可以不看的,反正此处都已mask了,不过还是贴出来,以备后用: 此处,关于mask这个词,解释一下。 mask这个单词,是面具的意思,而中断被mask了,就是中断被掩盖了,即虽然硬件上中断发生了,但是此处被屏蔽了,所以从效果上来说,就相当于中断被禁止了,硬件上即使发生了中断,CPU也不会去执行对应中断服务程序ISR了。 关于中断的内容的详细解释,推荐看这个,解释的很通俗易懂:【转】ARM9 2410移植之ARM中断原理, 中断嵌套的误区,中断号的怎么来的 |
# if defined(CONFIG_S3C2410) ldr r1, =0x3ffldr r0, =INTSUBMSK str r1, [r0] # elif defined(CONFIG_S3C2440) ldr r1, =0x7fff
ldr r0, =INTSUBMSK str r1, [r0] # endif
此处是将2410的INTSUBMSK设置为0x3ff。 后经HateMath的提醒后,去查证,的确此处设置的0x3ff,是不严谨的。 因为,根据2410的datasheet中关于INTSUBMSK的解释,bit[10:0]共11位,虽然默认reset的每一位都是1,但是此处对应的mask值,应该是11位全为1=0x7ff。 即写成0x3ff,虽然是可以正常工作的,但是却不够严谨的。 | |
此处CPU是是S3C2440,所以用到0x7fff这段代码。 其意思也很容易看懂,就是将INTSUBMSK寄存器的值设置为0x7fff。 先贴出对应每一位的含义: 然后我们再来分析对应的0x7fff是啥含义。 其实也很简单,意思就是: 0x7fff = bit[14:0]全是1 = 上表中的全部中断都被屏蔽(mask)。 |
#if 0 /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVNmov r1, #3 str r1, [r0] #endif #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 || CONFIG_S3C2440 */
![]()
此处,关于CLKDIVN的具体含义,参见下表: 而此处代码被#if 0注释掉了。 问:为何要注释掉,难道想要使用其默认的值,即HDIVN和PDIVN上电后,默认值Initial State,都是0,对应的含义为,FCLK:HCLK:PCLK = 1:1:1 ??? 答:不是,是因为我们在其他地方会去初始化时钟,去设置对应的CLKDIVN,详情参考后面的代码第 1.4.3 节 “bl clock_init”的部分。 | |
此处是结束上面的#ifdef |
/* * we do sys-critical inits only at reboot, * not when booting from ram! */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT blcpu_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的语法为:
对于上面的代码来说,意思就很清晰了,就是当没有定义CONFIG_SKIP_LOWLEVEL_INIT的时候,就掉转到cpu_init_crit的位置,而在后面的代码cpu_init_crit中,你可以看到最后一行汇编代码就是 mov pc, lr, 又将PC跳转回来,所以整个的含义就是,调用子程序cpu_init_crit,等cpu_init_crit执行完毕,再返回此处继续执行下面的代码。 于此对应地b指令,就只是单纯的掉转到某处继续执行,而不能再通过mov pc, lr跳转回来了。 |
/* Set up the stack */ 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 */
此句含义是,把地址为_TEXT_BASE的内存中的内容给r0,即,将所有的中断都mask了。 而查看前面的相关部分的代码,即: _TEXT_BASE: .word TEXT_BASE 得知,地址为_TEXT_BASE的内存中的内容,就是
中的: TEXT_BASE = 0x33D00000 所以,此处即: r0 = TEXT_BASE = 0x33D00000 而关于sub指令:
所以对应含义为: r0 = r0 - #CFG_MALLOC_LEN r0 = r0 - #CFG_GBL_DATA_SIZE 其中,对应的两个宏的值是:
中: #define CONFIG_64MB_Nand 0 //添加了对64MB Nand Flash支持 /* * Size of malloc() pool */ #define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024) #define CFG_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data */ #if(CONFIG_64MB_Nand == 1) #define CFG_ENV_SIZE 0xc000 /* Total Size of Environment Sector */ #else #define CFG_ENV_SIZE 0x20000 /* Total Size of Environment Sector */ #endif 所以,从源码中的宏定义中可以看出, CFG_MALLOC_LEN = (CFG_ENV_SIZE + 128*1024) = 0x20000 + 128*1024 = 0x40000 = 256*1024 = 256KB CFG_GBL_DATA_SIZE = 128 所以,此三行的含义就是算出r0的值: r0 = (r0 - #CFG_MALLOC_LEN) - #CFG_GBL_DATA_SIZE = r0 - 0x40000 – 128 = r0 – 0x40080 = 33CBFF80 |
#ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)#endif sub sp, r0, #12
/* leave 3 words for abort-stack */
如果定义了CONFIG_USE_IRQ,即如果使用中断的话,那么再把r0的值减去IRQ和FIQ的堆栈的值, 而对应的宏的值也是在
中: /*------------------------------------------------------------------- * Stack sizes * * The stack sizes are set up in start.S using the settings below */ #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 所以,此时r0的值就是: #ifdef CONFIG_USE_IRQ r0 = r0 - #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) = r0 – (4*1024 + 4*1024) = r0 – 8*1024 = 33CBFF80 – 8*1024 = 33CBDF80 #endif | |
最后,再减去终止异常所用到的堆栈大小,即12个字节。 现在r0的值为: #ifdef CONFIG_USE_IRQ r0 = r0 – 12 = 33CBDF80 - 12 = 33CBDF74 #else r0 = r0 – 12 = 33CBFF80 - 12 = 33CBFF74 #endif 然后将r0的值赋值给sp,即堆栈指针。 关于: sp代表stack pointer,堆栈指针; 和后面要提到的ip寄存器: ip代表instruction pointer,指令指针。 更多详情参见下面的解释。 关于ARM的寄存器的别名和相关的APCS,参见本文后面的内容:第 3.5 节 “AMR寄存器的别名 + APCS” |
bl clock_init![]()
在上面,经过计算,算出了堆栈的地址,然后赋值给了sp,此处,接着才去调用函数clock_init去初始化时钟。 其中此函数是在C文件:
中: void clock_init(void) { ...设置系统时钟clock的相关代码... } 看到这里,让我想起,关于其他人的关于此start.S代码解释中说到的,此处是先去设置好堆栈,即初始化sp指针,然后才去调用C语言的函数clock_init的。 而我们可以看到,前面那行代码: #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif 就不需要先设置好堆栈,再去进行函数调用。 其中cpu_init_crit对应的代码也在start.S中(详见后面对应部分的代码),是用汇编实现的。 而对于C语言,为何就需要堆栈,而汇编却不需要堆栈的原因,请参见本文后面的内容:第 3.6 节 “为何C语言(的函数调用)需要堆栈,而汇编语言却不需要堆栈” |
#ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: /* relocate U-Boot to RAM */ adrr0, _start /* r0 <- current position of code */
adr指令的语法和含义:
所以,上述: adr r0, _start 的意思其实很简单,就是将_start的地址赋值给r0.但是具体实现的方式就有点复杂了,对于用adr指令实现的话,说明_start这个地址,相对当前PC的偏移,应该是很小的,意思就是向前一段后者向后一段去找,肯定能找到_start这个标号地址的,此处,自己通过看代码也可以看到_start,就是在当前指令的前面,距离很近,编译后,对应汇编代码,也可以猜得出,应该是上面所说的,用sub来实现,即当前PC减去某个值,得到_start的值, 参照前面介绍的内容,去: arm-inux-objdump –d u-boot > dump_u-boot.txt 然后打开dump_u-boot.txt,可以找到对应的汇编代码,如下: 33d00000 <_start>: 33d00000: ea000014 b 33d00058 <reset> 。。。 33d000a4 <relocate>: 33d000a4: e24f00ac sub r0, pc, #172 ; 0xac 可以看到,这个相对当前PC的距离是0xac=172,细心的读者可以看到,那条指令的地址减去0xac,却并不等于_start的值,即 33d000a4 - 33d00000 = 0xa4 != 0xac 而0xac – 0xa4 = 8, 那是因为,由于ARM920T的五级流水线的缘故导致指令执行那一时刻的PC的值等于该条指令PC的值加上8,即 sub r0, pc, #172中的PC的值是 sub r0, pc, #172 指令地址:33d000a4,再加上8,即33d000a4+8 = 33d000ac, 所以,33d000ac – 0xac,才等于我们看到的33d00000,才是_start的地址。 这个由于流水线导致的PC的值和当前指令地址不同的现象,就是我们常说的,ARM中,PC=PC+8。 对于为何是PC=PC+8,请参见后面的内容:第 3.4 节 “为何ARM7中PC=PC+8” 对于此处为何不直接用mov指令,却使用adr指令,请参见后面内容:第 3.7 节 “关于为何不直接用mov指令,而非要用adr伪指令” 对于mov指令的操作数的取值范围,请参见后面内容:第 3.8 节 “mov指令的操作数的取值范围到底是多少” |
adr r0, _start
的伪代码,被翻译成实际汇编代码为:
33d000a4: e24f00ac sub r0, pc, #172 ; 0xac
其含义就是,通过计算PC+8-172 ⇒ _start的地址,
而_start的地址,即相对代码段的0地址,是这个地址在运行时刻的值,而当ARM920T加电启动后,,此处是从Nor Flash启动,对应的代码,也是在Nor Flash中,对应的物理地址是0x0,所以,此时_start的值就是0,而不是0x33d00000。
所以,此时:
r0 = 0x0
ldr r1, _TEXT_BASE/* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq clear_bss
这里的_TEXT_BASE的含义,前面已经说过了,那就是: _TEXT_BASE: .word TEXT_BASE 得知,地址为_TEXT_BASE的内存中的内容,就是
中的: TEXT_BASE = 0x33D00000 所以,此处就是 r1 = 0x33D00000 | |
含义很简单,就是比较r0和r1。而 r0 = 0x0 r1 = 0x33D00000 所以不相等 | |
因此beq发现两者不相等,就不会去跳转到clear_bss,不会去执行对应的将bss段清零的动作了。 |
ldr r2, _armboot_start ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
这两行代码意思也很清楚,分别装载_armboot_start和_bss_start地址中的值,赋值给r2和r3 而_armboot_start和_bss_start的值,前面都已经提到过了,就是: .globl _armboot_start _armboot_start: .word _start .globl _bss_start _bss_start: .word __bss_start _TEXT_BASE: .word TEXT_BASE 而其中的_start,是我们uboot的代码的最开始的位置,而__bss_start的值,是在
中的: SECTIONS { . = 0x00000000; . = ALIGN(4); .text : ... . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } ... . = ALIGN(4); __bss_start = .; .bss : { *(.bss) } _end = .; } 所以,可以看出,__bss_start的位置,是bss的start开始位置,同时也是text+rodata+data的结束位置,即代码段,只读数据和已初始化的可写的数据的最末尾的位置。 其实我们也可以通过前面的方法,objdump出来,看到对应的值: 33d00048 <_bss_start>: 33d00048: 33d339d4 .word 0x33d339d4 是0x33d339d4。
| ||||
此处的意思就很清楚了,就是r2 = r3-r2,计算出 text + rodata + data 的大小,即整个需要载入的数据量是多少,用于下面的函数去拷贝这么多的数据到对应的内存的位置。 这里的实际的值是 r2 = r3 –r2 = 0x33d339d4 - 0x33d00000 = 0x000339d4
|
#if 1bl CopyCode2Ram /* r0: source, r1: dest, r2: size */ #else 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 #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
此处,代码很简单,只是注释掉了原先的那些代码,而单纯的只是去调用CopyCode2Ram这个函数。 CopyCode2Ram函数,前面也提到过了,是在:
中: int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size) { unsigned int *pdwDest; unsigned int *pdwSrc; int i; if (bBootFrmNORFlash()) { pdwDest = (unsigned int *)buf; pdwSrc = (unsigned int *)start_addr; /* 从 NOR Flash启动 */ for (i = 0; i < size / 4; i++) { pdwDest[i] = pdwSrc[i]; } return 0; } else { /* 初始化NAND Flash */ nand_init_ll(); /* 从 NAND Flash启动 */ if (NF_ReadID() == 0x76 ) nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK)); else nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP)); return 0; } } 可以看到,其有三个参数,start_addr,*buf和size,这三个参数,分别正好对应着我们刚才所总结的r0,r1和r2. 这些寄存器和参数的对应关系,也是APSC中定义的:
上面说的a1-a4,就是寄存器r0-r3。 而CopyCode2Ram函数的逻辑也很清晰,就是先去判断是从Nor Flash启动还是从Nand Flash启动,然后决定从哪里拷贝所需要的代码到对应的目标地址中。 |
#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
此处忽略已经注释掉的代码 | |
最后的那两行,意思也很简单,那就是将地址为_start_armboot中的内容,即 start_armboot,赋值给PC,即调用start_armboot函数。 至此,汇编语言的start.S的整个工作,就完成了。 而start_armboot函数,在C文件中:
中: void start_armboot (void) { ...... } 这就是传说中的,调用第二层次,即C语言级别的初始化了,去初始化各个设备了。 其中包括了CPU,内存等,以及串口,正常初始化后,就可以从串口看到uboot的打印信息了。 |
/* ************************************************************************* * * 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 mcrp15, 0, r0, c7, c7, 0 /* flush v3/v4
cache */ mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
![]()
关于mcr的来龙去脉:
一些要说明的内容,见下::
CP15有很多个寄存器,分别叫做寄存器0(Register 0),到寄存器15(Register 15), 每个寄存器分别控制不同的功能,而且有的是只读,有的是只写,有的是可读写。 而且这些寄存器的含义,随着版本ARM内核版本变化而不断扩展,详情请参考:Processor setup via co-processor 15 and about co-processors 其中,根据我们此处关心的内容,摘录部分内容如下:
而MCR的详细的语法为:
对照上面的那行代码: mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ 可以看出,其中 rd为r0=0 CRn为C7 CRm为C7 对于这行代码的作用,以此按照语法,来一点点解释如下: 首先,mcr做的事情,其实很简单,就是“ARM处理器的寄存器中的数据传送到协处理器寄存器中”, 此处即是,将ARM的寄存器r0中的数据,此时r0=0,所以就是把0这个数据,传送到协处理器CP15中。 而对应就是写入到“<CRn>”这个“目标寄存器的协处理器寄存器”,此处CRn为C7,即将0写入到寄存器7(Register 7)中去。 而上面关于Register 7的含义中也说了,“Any data written to this location will cause the selected cache to be flushed”,即你往这个寄存器7中写入任何数据,都会导致对应的缓存被清空。而到底那个缓存被清空呢,即我们这行指令 mcr p15, 0, r0, c7, c7, 0 起了什么作用呢 那是由“<CRm>和<opcode_2>两者组合决定”的。 而此处CRm为C7,opcode_2为0,而对于C7和0的组合的作用,参见上面的那个表中Register 7中的Flash I+D那一行, 当opcode_2为0,CRm为0111=7,就是我们要找的,其作用是“Flush I + D”,即清空指令缓存I Cache和数据缓存D Cache。 根据该表,同理,如果是opcode_2=0,而CRm=0101b=5,那么对应的就是去“Flush I”,即只清除指令缓存I Cache了。 而对应的指令也就是 mcr p15, 0, r0, c7, c5, 0 了。 | ||||||||||||||||
此注释说此行代码的作用是,清理v3或v4的缓存 其中v4,我们很好理解,因为我们此处的CPU是ARM920T的核心,是属于ARM V4的,而为何又说,也可以清除v3的cache呢? 那是因为,本身这些寄存器位域的定义,都是向下兼容的,参见上面引用的内容,也写到了:
即,对于ARM7的话,你写同样的这行代码 mcr p15, 0, r0, c7, c7, 0 也还是向register 7中写入了数据0,这也同样满足了其所说的“Any data written to this location”,也会产生同样的效果“cause the IDC (Instruction/Data cache) to be flushed”。 | ||||||||||||||||
同理,可以看出此行是去操作寄存器8,而对应的各个参数为: rd为r0=0 CRn为C8 CRm为C7 opcode_2为0 对照寄存器8的表:
其含义为: 向寄存器8中写入数据,会导致对应的TLB被清空。具体是哪个TLB,由opcode_2和CRm组合决定, 此处opcode_2为0,CRm为7=0111b,所以对应的作用是“Flush I + D”,即清空指令和数据的TLB。
|
/* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0![]()
此处,对应的值为: rd为r0=0 CRn为C1 CRm为C0 opcode_2为0 即,此行代码是将r0的值,即0,写入到CP15的寄存器1中。 寄存器1的相关的定义为:
所以,对应内容就是,向bit[CRm]中写入opcode_2,即向bit[0]写入0,对应的作用为“On-chip MMU turned off”,即关闭MMU。 |
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
![]()
此处几行代码,注释中写的也很清楚了,就是去清楚对应的位和设置对应的位,具体位域的含义见下:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
此行作用是:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
此行作用是:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
此行作用是:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
此行作用是:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
mcr指令,将刚才设置的r0的值,再写入到寄存器1中。 |
/* * 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, ipmov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
![]()
将lr的值给ip,即指令指针r12,此处之所以要保存一下lr是因为此处是在子函数cpu_init_crit中,lr已经保存了待会用于返回主函数的地址,即上次调用时候的pc的值,而此处如果在子函数cpu_init_crit中继续调用其他子函数lowlevel_init,而不保存lr的话,那么调用完lowlevel_init返回来时候,就丢失了cpu_init_crit要返回的位置。 说白了就是,每次你要调用函数之前,你自己要确保是否已经正确保存了lr的值,要保证函数调用完毕后,也能正常返回。当然,如果你此处根本不需要返回,那么就不用去保存lr的值了。 | |
典型的子函数调用,通过将lr的值赋值给pc,实现函数调用完成后而返回的。 | |
这里,其是和前面的代码: #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif 是对应的。 |
摘要
/* ************************************************************************* * * 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 sub sp, sp, #S_FRAME_SIZE
stmia
sp, {r0 - r12} @ Calling r0-r12 ldr r2, _armboot_start
![]()
此处很简单,只是一些宏定义而已。 后面用到的时候再解释。 | |
.macro和后面的.endm相对应,其语法是: 所以,此处就相当于一个无参数的宏bad_save_user_regs,也就相当于一个函数了。 | |
即 sp = sp- S_FRAME_SIZE = sp - 72 | |
stmia的语法为: 其中,条件域的具体含义如下: 更具体的含义:
所以,此行的含义是, 将r0到r12的值,一个个地传送到对应的地址上,基地址是sp的值,传完一个,sp的值加4,一直到传送完为止。 此处,可见,前面那行代码: sp = sp - 72 就是为此处传送r0到r12,共13个寄存器,地址空间需要13*4=72个字节, 即前面sp减去72,就是为了腾出空间,留此处将r0到r12的值,放到对应的位置的。 | |
此处的含义就是,将_armboot_start中的值,参考前面内容,即为_start, 而_start的值: 从 Nor Flash启动时:_stat=0 relocate代码之后为:_start=TEXT_BASE=0x33D00000 此处是已经relocate代码了,所以应该理解为后者,即_start=0x33D00000 所以: r2=0x33D00000 |
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
![]()
此处: r2 = r2 - ( CONFIG_STACKSIZE+CFG_MALLOC_LEN) = r2 – (128*1024 + 256*1024) = 0x33D00000 - 384KB = 0x33CA0000 | |
此处: r2 = r2 - (CFG_GBL_DATA_SIZE + 8) = 0x33CA0000 – (128 + 8) = 0x33C9FF78 | |
分别将地址为r2和r2+4的内容,即地址为0x33C9FF78和0x33C9FF7C中的内容,load载入给r2和r3寄存器。 | |
将sp的值,加上72,送给r0 | |
前面的定义是: #define S_SP 52 所以此处就是将sp的值,加上52,送给r5 | |
将lr给r1 | |
然后将r0到r3中的内容,存储到地址为r5-r5+12中的位置去。 | |
将sp再赋值给r0 | |
结束宏bad_save_user_regs |
此处虽然每行代码基本看懂了,但是到底此bad_save_user_regs函数是做什么的,还是不太清楚,有待以后慢慢深入理解。
.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
![]()
上面两段代码,基本上和前面很类似,虽然每一行都容易懂,但是整个两个函数的意思,除了看其宏的名字irq_save_user_regs和irq_restore_user_regs,分别对应着中断中,保存和恢复用户模式寄存器,之外,其他的,个人目前还是没有太多了解。 | ||||
此处的get_bad_stack被后面undefined_instruction,software_interrupt等处调用,目前能理解的意思是,在出错的时候,获得对应的堆栈的值。 | ||||
此处的含义很好理解,就是把地址为IRQ_STACK_START中的值赋值给sp。 即获得IRQ的堆栈的起始地址。 而对于IRQ_STACK_START,是前面就提到过的cpu_init源码 而此处,就是用到了,前面已经在cpu_init()中重新计算正确的值了。 即算出IRQ堆栈的起始地址,其算法很简单,就是: IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; 即,先减去malloc预留的空间,和global data,即在
中定义的全局变量: DECLARE_GLOBAL_DATA_PTR; 而此宏对应的值在:
中: #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8") 即,用一个固定的寄存器r8来存放此结构体的指针。
此gd_t的结构体,不同体系结构,用的不一样。 而此处arm的平台中,gd_t的定义在同一文件中: 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; 而此全局变量gd_t *gd会被其他很多文件所引用,详情自己去代码中找。 | ||||
此处和上面类似,把地址为FIQ_STACK_START中的内容,给sp。 其中: FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; 即FIQ的堆栈起始地址,是IRQ堆栈起始地址减去IRQ堆栈的大小。 |
/* * 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
![]()
如果发生未定义指令异常,CPU会掉转到start.S开头中对应的位置: ldr pc, _undefined_instruction 即把地址为_undefined_instruction中的内容给pc,即跳转到此处执行对应的代码。 其做的事情依次是: 获得出错时候的堆栈 保存用户模式寄存器 跳转到对应的函数:do_undefined_instruction 而do_undefined_instruction函数是在:
中: void bad_mode (void) { panic ("Resetting CPU ...\n"); reset_cpu (0); } void do_undefined_instruction (struct pt_regs *pt_regs) { printf ("undefined instruction\n"); show_regs (pt_regs); bad_mode (); } 可以看到,此处起始啥事没错,只是打印一下出错时候的寄存器的值,然后跳转到bad_mode中取reset CPU,直接重启系统了。 | |
以上几个宏,和前面的do_undefined_instruction是类似的,就不多说了。 |
@ HJ .globl Launch.align 4 Launch: mov r7, r0 @ diable interrupt @ disable watch dog timer mov r1, #0x53000000 mov r2, #0x0 str r2, [r1] ldr r1,=INTMSK ldr r2,=0xffffffff @ all interrupt disable str r2,[r1] ldr r1,=INTSUBMSK ldr r2,=0x7ff @ all sub interrupt disable str r2,[r1] ldr r1, = INTMOD mov r2, #0x0 @ set all interrupt as IRQ (not FIQ) str r2, [r1] @ mov ip, #0 mcr p15, 0, ip, c13, c0, 0 @ /* zero PID */ mcr p15, 0, ip, c7, c7, 0 @ /* invalidate I,D caches */ mcr p15, 0, ip, c7, c10, 4 @ /* drain write buffer */ mcr p15, 0, ip, c8, c7, 0 @ /* invalidate I,D TLBs */ mrc p15, 0, ip, c1, c0, 0 @ /* get control register */ bic ip, ip, #0x0001 @ /* disable MMU */ mcr p15, 0, ip, c1, c0, 0 @ /* write control register */ @ MMU_EnableICache @mrc p15,0,r1,c1,c0,0 @orr r1,r1,#(1<<12) @mcr p15,0,r1,c1,c0,0 #ifdef CONFIG_SURPORT_WINCE bl Wince_Port_Init #endif @ clear SDRAM: the end of free mem(has wince on it now) to the end of SDRAM ldr r3, FREE_RAM_END ldr r4, =PHYS_SDRAM_1+PHYS_SDRAM_1_SIZE @ must clear all the memory unused to zero mov r5, #0 ldr r1, _armboot_start ldr r2, =On_Steppingstone sub r2, r2, r1 mov pc, r2 On_Steppingstone: 2: stmia r3!, {r5} cmp r3, r4 bne 2b @ set sp = 0 on sys mode mov sp, #0 @ add by HJ, switch to SVC mode msr cpsr_c, #0xdf @ set the I-bit = 1, diable the IRQ interrupt msr cpsr_c, #0xd3 @ set the I-bit = 1, diable the IRQ interrupt ldr sp, =0x31ff5800 nop nop nop nop mov pc, r7 @ Jump to PhysicalAddress nop mov pc, lr
#ifdef CONFIG_USE_IRQ .align 5 irq: /* add by www.embedsky.net to use IRQ for USB and DMA */ sub lr, lr, #4 @ the return address ldr sp, IRQ_STACK_START @ the stack for irq stmdb sp!, { r0-r12,lr } @ save registers ldr lr, =int_return @ set the return addr ldr pc, =IRQ_Handle
@ call the isr int_return: ldmia sp!, { r0-r12,pc }^ @ return from interrupt .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
此处,做的事情,很容易看懂,就是中断发生后,掉转到这里,然后保存对应寄存器,然后跳转到对应irq函数IRQ_Handle中去。 但是前面为何sp为何去减去4,原因不太懂。 | |
关于IRQ_Handle,是在:
中: void IRQ_Handle(void) { unsigned long oft = intregs->INTOFFSET; S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO(); // printk("IRQ_Handle: %d\n", oft); //清中断 if( oft == 4 ) gpio->EINTPEND = 1<<7; intregs->SRCPND = 1<<oft; intregs->INTPND = intregs->INTPND; /* run the isr */ isr_handle_array[oft](); } 此处细节就不多解释了,大体含义是,找到对应的中断源,然后调用对应的之前已经注册的中断服务函数ISR。 | |
此处也很简单,就是发生了快速中断FIQ的时候,保存IRQ的用户模式寄存器,然后调用函数do_fiq,调用完毕后,再恢复IRQ的用户模式寄存器。 | |
do_fiq()是在:
中: void do_fiq (struct pt_regs *pt_regs) { printf ("fast interrupt request\n"); show_regs (pt_regs); bad_mode (); } 和前面提到过的do_undefined_instruction的一样,就是打印寄存器信息,然后跳转到bad_mode()去重启CPU而已。 | |
此处就是,如果没有定义CONFIG_USE_IRQ,那么就用这段代码,可以看到,都只是直接调用do_irq和do_fiq,也没做什么实际工作。 |