以前一直有个疑问,在U-boot下到底能不能使用中断,为了验证这个问题,于是乎,昨天晚上我在自己的 TQ2440开发板上进行了uboot环境下的按键中断实验,这次使用的我刚移植的最新版Uboot,版本是 u-boot-2014-04,验证的结论是:
U-boot完全能够支持中断
下面就以u-boot-2014-04为例,介绍一下按键中断的实现。
这里分为几部分介绍:
1、异常向量表 ------ 由u-boot完成
2、通用中断处理函数 ------ 由u-boot完成
3、u-boot自己完成的中断初始化部分 ----- 由u-boot完成
4、用户按键中断中断初始化 ------ 由用户完成
5、用户自定义中断处理函数 ------ 由用户完成
这里有两篇我在网络上搜集的关于S3C2440中断的文章:
https://files.cnblogs.com/pengdonglin137/S3C2440%E7%B3%BB%E7%BB%9F%E4%B8%AD%E6%96%AD.rar
https://files.cnblogs.com/pengdonglin137/S3C2440%E5%A4%96%E9%83%A8%E4%B8%AD%E6%96%AD%E6%93%8D%E4%BD%9C.pdf
其中介绍了如何使用S3C2440的中断功能以及ARM处理器异常处理。
先简单介绍一下几个知识点:
ARM状态下的寄存器组织
在系统上电时,也就是RESET后,处于SVC特权模式
ARM状态寄存器
关于状态寄存器的介绍可以参考:
http://www.cnblogs.com/pengdonglin137/p/3819546.html
Control Bits的含义:
Mode bits的含义:
异常向量表
异常向量表是一段特定内存地址空间,每种ARM异常对应一个字长空间(4Bytes),正好是一条32位指令长度,当异常发生时,CPU强制将PC的值设置为当前异常对应的固定内存地址。如表3-4所示是S3C2440的异常向量表。
我们一般都是用的是IRQ异常。下面的按键产生IRQ异常。
异常发生的硬件操作
在异常发生后,ARM内核会自动做以下工作:
保存执行状态:将CPSR复制到发生的异常模式下SPSR中;
(以按键中断为例,uboot环境下处于SVC模式,中断后,处于irq模式,所以这步完成的动作是:CPSR ----> SPSR_irq)
模式切换:将CPSR模式位强制设置为与异常类型相对应的值,同时处理器进入到ARM执行模式,禁止所有IRQ中断,当进入FIQ快速中断模式时禁止FIQ中断;
(以按键中断为例,将CPSR的mode bits设置为0x12,将I位置为1,屏蔽IRQ中断,将T位置为1,进入ARM状态)
保存返回地址:将下一条指令的地址(被打断程序)保存在LR(异常模式下LR_excep)中。
(一条指令的执行分为:取指,译码,执行三个主要阶段, CPU由于使用流水线技术,造成当前执行指令的地址应该是PC – 8(32位机一条指令四个字节),那么执行指令的下条指令应该是PC – 4。在异常发生时,CPU自动会将将PC – 4 的值保存到LR里,但是该值是否正确还要看异常类型才能决定。
快速中断请求和一般中断请求返回处理是一样的。通常处理器执行完当前指令后,查询FIQ/IRQ中断引脚,并查看是否允许FIQ/IRQ中断,如果 某个中断引脚有效,并且系统允许该中断产生,处理器将产生FIQ/IRQ异常中断,当FIQ/IRQ异常中断产生时,程序计数器pc的值已经更新,它指向 当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当 FIQ/IRQ异常中断产生时,处理器将值(pc-4)保存到FIQ/IRQ异常模式下的寄存器lr_irq/lr_irq中,它指向当前指令之后的第2 条指令,因此正确返回地址可以通过下面指令算出:
SUBS PC,LR_irq,#4 ; 一般中断
SUBS PC,LR_fiq,#4 ; 快速中断
注:LR_irq/LR_fiq分别为一般中断和快速中断异常模式下LR,并不存在LR_xxx寄存器,为方便读者理解加上_xxx)
跳入异常向量表:强制设置PC的值为相应异常向量地址,跳转到异常处理程序中。
(以按键中断为例,将PC强制设置为0x18)
保存执行现场
异常处理程序最开始,要保存被打断程序的执行现场,程序的执行现场无非就是保存当前操作寄存器里的数据,可以通过下面的栈操作指令实现保存现场:
STMFD SP_excep!, {R0 – R12, LR_excep}
注:LR_abt,SP_excep分别为对应异常模式下LR和SP,为方便读者理解加上_abt
需要注意的是,在跳转到异常处理程序入口时,已经切换到对应异常模式下了,因此这里的SP是异常模式下的SP_excep了,所以被打断程序现场 (寄存器数据)是保存在异常模式下的栈里,上述指令将R0~R12全部都保存到了异常模式栈,最后将修改完的被打断程序返回地址入栈保存,之所以保存该返 回地址就是将来可以通过类似:MOV PC, LR的指令,返回用户程序继续执行。
异常发生后,要针对异常类型进行处理,因此,每种异常都有自己的异常处理程序,异常处理过程通过下节的系统中断处理来进行分析。
异常处理的返回
异常处理完成之后,返回被打断程序继续执行,具体操作如下:
恢复被打断程序运行时寄存器数据
恢复程序运行时状态CPSR
通过进入异常时保存的返回地址,返回到被打断程序继续执行
异常发生后,进入异常处理程序时,将用户程序寄存器R0~R12里的数据保存在了异常模式下栈里面,异常处理完返回时,要将栈里保存的的数据再恢复 回原先R0~R12里,毫无疑问在异常处理过程中必须要保证异常处理入口和出口时栈指针SP_excep要一样,否则恢复到R0~R12里的数据不正确, 返回被打断程序时执行现场不一致,出现问题,虽然将执行现场恢复了,但是此时还是在异常模式下,CPSR里的状态是异常模式下状态,因此要恢复 SPSR_excep里的保存状态到CPSR里,SPSR_excep是被打断程序执行时的状态,在恢复SPSR_excep到CPSR的同时,CPU的 模式和状态从异常模式切换回了被打断程序执行时的模式和状态。此刻程序现场恢复了,状态也恢复了,但PC里的值仍然指向异常模式下的地址空间,我们要让 CPU继续执行被打断程序,因此要再手动改变PC的值为进入异常时的返回地址,该地址在异常处理入口时已经计算好,直接将PC = LR_excep即可。
上述操作可以一步一步实现,但是通常我们可以通过一条指令实现上述全部操作:
LDMFD SP_excp!, {r0-r12, pc}^
注:SP_excep为对应异常模式下SP,^符号表示恢复SPSR_excep到CPSR
以上操作可以用下图来描述
接下来分析u-boot代码。
让u-boot支持中断,首先需要在配置文件中定义几个宏,我在我的板子的配置文件include/configs/smdk2440.h中定义了如下几个宏(少定义了在编译时会报错,可以根据出错信息判断少定义了那些宏):
#define CONFIG_USE_IRQ
#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ的栈大小*/
#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ的栈大小*/
异常向量表
首先分析一下arch/arm/cpu/arm920t/start.S
1: .globl _start 指令链接地址 指令的运行地址
2: _start: b start_code 0x33f00000 0x00000000
3: ldr pc, _undefined_instruction
0x33f00004 0x00000004
4: ldr pc, _software_interrupt
0x33f00008 0x00000008
5: ldr pc, _prefetch_abort
0x33f0000c 0x0000000c
6: ldr pc, _data_abort
0x33f00010 0x00000010
7: ldr pc, _not_used
0x33f00014 0x00000014
8: ldr pc, _irq
0x33f00018 0x00000018
9: ldr pc, _fiq
0x33f0001c 0x0000001c
10:
11: _undefined_instruction: .word undefined_instruction
0x33f00020 0x00000020
12: _software_interrupt: .word software_interrupt
0x33f00024 0x00000024
13: _prefetch_abort: .word prefetch_abort
0x33f00028 0x00000028
14: _data_abort: .word data_abort
0x33f0002c 0x0000002c
15: _not_used: .word not_used
0x33f00030 0x00000030
16: _irq: .word irq
0x33f00034 0x00000034
17: _fiq: .word fiq
0x33f00038 0x00000038
18:
19: .balignl 16,0xdeadbeef
上面就是建立异常向量表,其中b start_code指令的地址对应的就是复位异常发生时要赋给PC的值,b 是一条相对跳转指令。其中,我们要关注的是IRQ异常: ldr pc, _irq ,这条语句的作用是将_irq中存放的数据放入pc中,可以将_irq看做变量名或者一个*p,而其中存放的是内容就是irq,即中断处理的入口地址(链接地址)。
即当发生按键动作是,pc会指向“ldr pc, _irq”所在的地址,执行这条指令(会被解释成ldr pc, [pc, #offset]),这条指令完成了将irq的地址(链接地址)赋给了pc,从而从异常向量表中直接跳入了中断处理程序(链接时确定的地址处)。
这里需要解释一下,指令的运行地址和链接地址。链接地址是在编译连接时编译器确定的地址,运行地址是实际运行这条指令时,去哪个物理地址去取这条指令,这两个地址一般相同。如果设备支持程序在Flash中运行,那么这两个地址相同,但是对于从NandFlash启动时,他们就不同了,以S3C2440为例,系统会先把NandFlash的前4KB的内容读到SRAM(sram会被映射到物理地址0开始的地方),然后运行这4KB的程序,这段4KB的程序负责把整个程序从NandFlash读到他们的链接地址处(一般在物理内存的末端,S3C2440的物理内存起始地址是0x30000000)。那么对于刚才运行在SRAM中的那4KB程序来说,他们的运行地址(sram中,起始地址0)跟链接地址(内存中,起始地址0x30000000)就不相同了。ARM架构下的异常向量表默认应该存放在0地址处,即要想使用异常,物理地址0处应该存放正确完整的异常向量表。对于从NorFlash启动,自然不是问题,此时NorFlash会被映射到物理地址0开始的地方,NorFlash的中存放的uboot开头便是异常向量表。对于从NandFlash启动时,SRAM被映射到了物理地址0开始的地方,并且前面已经说过,SRAM中的代码来自NandFlash的前4KB,这4KB也就是uboot的前4KB,自然含有异常向量表,也不会出问题,如果你故意在u-boot中通过使用命令mw破坏SRAM中的异常向量表,当发生异常时,u-boot就跑飞了。这里还要提一下被重定向到内存中的u-boot,其中也含有异常向量表,但是异常产生时系统用不到。
通用中断处理函数
通用中断处理函数在u-boot中的实现,还是在start.S中(我做了修改):
1: .align 5
2: irq:
3: sub lr, lr, #4 @ the return address
4: ldr sp, IRQ_STACK_START @ the stack for irq
5: stmdb sp!, { r0-r12,lr } @ save registers
6:
7: ldr lr, =int_return @ set the return addr
8: ldr pc, =do_irq @ call the isr
9: int_return:
10: ldmia sp!, { r0-r12,pc }^ @ return from interrupt
11:
解释:
“sub lr, lr, #4”的原因在上面已经解释过了。
“ldr sp, IRQ_STACK_START”
这条指令中的sp已经是irq模式下的sp,即r13_irq,意思是将IRQ_STACK_START中存放的数据放入sp,即初始化irq模式下的栈指针。IRQ_STACK_START在什么地方赋值呢?一会儿分析。
“stmdb sp!, { r0-r12,lr }”