在讲解中断时有必要理清一些关系: 中断和异常
1.中断:
a.可屏蔽中断(maskable): I/O设备发出的中断请求(irq)都属于.可处于两种状态:屏蔽的/非屏蔽的.
b非屏蔽中断(nonmaskable): 只有几个危急事件才引起.总是由CPU辨认.
2.异常:
a.处理器探测异常:当CPU执行指令时探测到一个反常条件所产生的异常. 根据保存在eip寄存器中的值,分为3种; 1)故障(fault):通常可以被纠正.eip中保存的是引起故障的指令地址.纠正后会重新执行该条指令. ; 2)陷阱(trap):在陷阱指令执行后立刻报告.内核把控制器返回给程序后可以继续他的执行而不失连贯性. eip保存的是随后要执行的指令地址.只有当没有必要重新执行已终止的指令时(通常为了调试程序)时才触发陷阱. ; 3)异常中止(abort):不能在eip中保存引起异常的指令所在的确切位置.用于报告严重的错误.异常中止处理程序会强制受影响的进程终止.
b.编程异常: 在编程者发出请求时发生.将其作为陷阱来处理.也叫软中断.用途:1)执行系统调用.2)给调试程序通报一个特定的事件.
c.每个中断和异常由0~255之间的一个数来标示.称为向量(vector).只有可屏蔽中断的向量可以通过编程改变.其余都是固定的.
d.IRQ和中断:能发出中断的设备都有一个IRQ的输出线.所有IRQ线都与一个可编程中断控制器(PIC)的硬件电路的输入引脚相连.可以有选择地禁止每条IRQ线.可对PIC编程从而禁止IRQ.禁止的中断不丢失.一旦激活,PIC就把他们发送到CPU.该特性运行中断处理程序逐次处理同一类型的IRQ.
e.为了发挥SMP体系的并行性,能够把中断传递给每个CPU很重要.所以引入了I/O高级可编程控制器(I/O APIC)的组件.所有CPU都含有一个本地APIC,通过APIC总线(在系统总线上)连接到外部的I/O APIC.还支持CPU产生处理器间中断(IPI),可以利用它来在CPU之间交换消息.
f.中断描述符表:IDT是一个系统表,它与每一个中断或异常向量相联系.每一个向量在表中有相应的中断或异常处理程序的入口地址.最多需要256*8=2048字节来存放IDT. idtr寄存器指定IDT的线性基地址及其最大长度,从而使IDT可以位于内存中的任何地方.分为3种类型: 1)任务门:信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中. ;2)中断门:包含段选择符和处理程序的段内偏移量.当CPU控制权转移到一个适当的断后,清除IF标志来关闭将来会发生的可屏蔽中断 ;3)陷阱门:与中断门相似,但控制权传递到一个适合的CPU时不修改IF标志. Linux利用中断门处理中断,利用陷阱门处理异常.注意: “Double fault”异常是唯一由任务们处理的异常.表示一种内核错误.
ARM体系CPU的7中工作模式:
中断或异常导致的CPU会自动完成工作模式的转换,举个例子,当上电时会导致复位异常,PC会指向复位异常地址,在ARM中复位地址是0x0,这就是为何上电会从0x0跑程序的原因,其他处理器可能不是0x0,但没什么差别,接着会设置其工作模式,复位异常对应的是system mode,这是CPU自动完成的,你只需要当PC跳转到0x0时设置处理指令,但由于0x0只能放置一条指令,做不了啥事所以设置了一条跳转指令,指向具体处理函数,既然复位异常会导致PC指向地址0x0处,依次还有未定义指令异常地址0x4,软件异常地址0x8,指令预取异常0xc,数据访问异常0x10,保留异常地址0x14,中断地址0x18,快速中断地址0x1c,可以看出这些异常或者中断都占用4个字节也就是一条指令,所以常规做法就是放置一条跳转指令,指向具体处理的函数。这些地址异常或中断地址称为向量!之所以不同的异常或中断需要设置不同的工作模式是为了访问处理器的资源和一些必要的操作权限,比如用户模式下无法copy spsr 到 cpsr,程序运行在用户模式限制访问敏感的系统资源等。
一个异常发生时会自动切换相应的工作模式,期间会自动完成一些事:
a. 在异常工作模式下R14(lr)保存前一个工作模式的下一条指令
b. 将CPSR 复制到该异常模式的SPSR,这样该异常模式就可以使用CPSR
c. 将CPSR设置成改异常工作模式
d. 另PC等于该异常的向量地址
相反的,退出该异常模式需要手动退出
a.lr减去适当值赋值PC
b.SPSR 赋值 CPSR
现在讲解s342440的中断体系结构
从图可以看出中断源分两种,一种是独立中断源,发生中断后直接在SRCPND置位,这样当读取SRCPND哪bit置1就知道是什么中断源了,另一种是共用一个SRCPND的中断源,即使SRCPND置位了只能表示某一类中断源至少一个发生中断了。需要读取SUBSRCPND才知道具体是哪一个发生中断了,当然,前提是这个能够通过SUBMASK,当在SRCPND置位后会通过MASK,如果是FIQ则不需要MASK审核直接置位INTPND,若是IRQ则需要MASK审核,只有通过MASK审核通过了才能置位INTPND,CPU每预取下一条指令时都会判断INTPND是否有被置位,若有则执行完当前指令后跳转到向量中断地址处。当然还有相关的其他register没有介绍,请看DS(datasheet),后续code也会提到。
现讲解code,有如下6个文件:
head.S init.c interrupt.c main.c Makefile s3c24xx.h
head.S
.extern main
.text
.global _start
_start:
b Reset
Handle_Undef:
b Handle_Undef
Handle_SWI:
b Handle_SWI
Handle_CMD_Abort:
b Handle_CMD_Abort
Handle_DATA_Abort:
b Handle_DATA_Abort
Handle_Unuse :
b Handle_Unuse
b Handle_IRQ
Handle_FIQ:
b Handle_FIQ
Reset:
ldr sp, =4096
bl disable_watch_dog
bl init_led
bl init_irq
msr cpsr_c, #0xd2 @set interrupt mode,but disable IRQ FIQ
ldr sp, =3072
msr cpsr_c, #0x5f @ enable IRQ FIQ and return system mode
ldr lr, =halt_loop
ldr pc, =main
halt_loop:
b halt_loop
Handle_IRQ:
sub lr, lr, #4 @ lr sp is interrupt mode register
stmdb sp!, {r0-r12,lr}
ldr lr, =int_return
ldr pc, =EINT_Handle
int_return:
ldmia sp!, {r0-r12,pc}^
需要注意的是发生中断后PC指向Handle_IRQ后CPU已经处于中断工作模式了,此时的lr sp都是IRQ工作模式下的register,需要注意的是模式切换后R0-R12还是前个工作模式(系统模式)得值,我们得保存现场环境,以备到时返回工作模式,最简单就是保留到中断指向的栈空间(ldr sp, =3072)同时需要注意的是lr保留的是前个工作模式(系统模式)的下一条指令,我们需要将其保留起来,最简单就是也一同保留到中断指向的栈空间,这样当返回时直接赋值给PC就ok了,不过记得要减去4,为何呢,因为当CPU处理一条指令时PC指向的是下两条指令,即当前指令+8字节(也有人表示 PC+8),lr保存的是这个PC+8减4 最后就是PC+4 (CPU自动的)也即刚好偏移到当前指令的下一条指令,但是有些指令是不一样的,在中断发生时,CPU并不会直接去处理,而是执行完当前的指令,可是当处理完当前指令的时候PC又偏移一条指令即PC+12 所以CPU自动减4是不够的,还需要手动的再减4 就有了sub lr, lr, #4 最后PC+12-4-4 =PC+4.
init.c
/*************************************************************************
> File Name: init.c
> Author: Vedic.Fu
> Mail: [email protected]
> Created Time: 2015骞?4鏈?6鏃?鏄熸湡鏃?12:25:24
************************************************************************/
#include "s3c24xx.h"
#define GPB5_OUT (1<<(5*2)) // LED1
#define GPB6_OUT (1<<(6*2)) // LED2
#define GPB7_OUT (1<<(7*2)) // LED3
#define GPB8_OUT (1<<(8*2)) // LED4
#define GPF1_EINT (2<<(1*2)) // K1,EINT1
#define GPF4_EINT (2<<(4*2)) // K2,EINT4
#define GPF2_EINT (2<<(2*2)) // K3,EINT2
#define GPF0_EINT (2<<(0*2)) // K4,EINT0
void disable_watch_dog(void)
{
WTCON = 0;
}
void init_led(void)
{
GPBCON = GPB5_OUT | GPB6_OUT | GPB7_OUT | GPB8_OUT ;
GPBDAT = ~0;
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
GPFCON = GPF0_EINT | GPF2_EINT | GPF1_EINT | GPF4_EINT;
// 对于EINT4,需要在EINTMASK寄存器中使能它们
EINTMASK &= ~(1<<4);
// enable EINT0 1 2 4~7
INTMSK &= ~((1<<0) | (1<<1) | (1<<2) | (1<<4));
}
interrupt.c
#include "s3c24xx.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
unsigned long val;
switch( oft )
{
// K4
case 0:
{
GPBDAT |= (0x0f<<5); // set all leds off
GPBDAT &= ~(1<<8); // LED4 on
break;
}
// key1
case 1:
{
GPBDAT |= (0x0f<<5);
GPBDAT &= ~(1<<5);
break;
}
// K3
case 2:
{
GPBDAT |= (0x0f<<5);
GPBDAT &= ~(1<<7);
break;
}
// K2
case 4:
{
GPBDAT |= (0x0f<<5);
GPBDAT &= ~(1<<6);
break;
}
default:
GPBDAT |= (0x0f<<5);
GPBDAT &= ~(3<<6);
break;
}
//清中断
if( oft == 4 )
EINTPEND = (1<<4);
SRCPND = 1<1<
main.c
int main()
{
while(1);
return 0;
}
s3c24xx.h
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
/* SDRAM regisers */
#define MEM_CTL_BASE 0x48000000
#define SDRAM_BASE 0x30000000
/* NAND Flash registers */
#define NFCONF (*(volatile unsigned int *)0x4e000000)
#define NFCMD (*(volatile unsigned char *)0x4e000004)
#define NFADDR (*(volatile unsigned char *)0x4e000008)
#define NFDATA (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT (*(volatile unsigned char *)0x4e000010)
/*GPIO registers*/
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPFUP (*(volatile unsigned long *)0x56000058)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPGUP (*(volatile unsigned long *)0x56000068)
#define GPHCON (*(volatile unsigned long *)0x56000070)
#define GPHDAT (*(volatile unsigned long *)0x56000074)
#define GPHUP (*(volatile unsigned long *)0x56000078)
/*UART registers*/
#define ULCON0 (*(volatile unsigned long *)0x50000000)
#define UCON0 (*(volatile unsigned long *)0x50000004)
#define UFCON0 (*(volatile unsigned long *)0x50000008)
#define UMCON0 (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
#define UTXH0 (*(volatile unsigned char *)0x50000020)
#define URXH0 (*(volatile unsigned char *)0x50000024)
#define UBRDIV0 (*(volatile unsigned long *)0x50000028)
/*interrupt registes*/
#define SRCPND (*(volatile unsigned long *)0x4A000000)
#define INTMOD (*(volatile unsigned long *)0x4A000004)
#define INTMSK (*(volatile unsigned long *)0x4A000008)
#define PRIORITY (*(volatile unsigned long *)0x4A00000c)
#define INTPND (*(volatile unsigned long *)0x4A000010)
#define INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define SUBSRCPND (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned long *)0x4A00001c)
/*external interrupt registers*/
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
Makefile
objs := head.o init.o interrupt.o main.o
int.bin: $(objs)
arm-linux-ld -Ttext 0x00000000 -o int_elf $^
arm-linux-objcopy -O binary -S int_elf $@
arm-linux-objdump -D -m arm int_elf > int.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f int.bin int_elf int.dis *.o int.dis