韦东山嵌入式Linux学习笔记08--中断体系结构
中断是什么?
举个栗子, 系统怎么知道你什么时候插入鼠标这个设备? 可以有两种处理方式:
1. 查询方式:
轮询去检测是否有设备插入;
2. 中断的方式
当鼠标插入这个事件发生时, 置位某个寄存器,告诉CPU去处理这个事件.
对于查询方式, 我们需要一直去监控想要知道的状态, 而中断的处理方式, 使得CPU有空去处理其他的事情, 当插入鼠标这个时间发生时才来处理这个事件, 这样下来,
处理效率会高很多,中断的作用也可见一斑.
下面介绍s3c2440的中断控制器
s3c2440有60个中断源, 这么多中断源, 怎么处理? 下面是中断处理的一个框图:
上面框图中寄存器的介绍:
SUBSRCPND: (0x4A00 0018)
它包含的中断源如下, 当这些中断发生时,对应的位会被自动置1.我们可以往某位写入1来令此位为0, 清中断.写入0无效果,数据保持不变.
SUBMASK: (0x4A00 001C)
SUBSRCPND寄存器后面是SUBMASK寄存器, 这个寄存器用作屏蔽中断, 这个寄存器的对应的位与SUBSRCPND对应. 当这里相应的
位被置一之后,即使相应中断发生了,也会被屏蔽掉. 这就是这个寄存器的作用.
SRCPND: (0x4A00 0000)
SRCPND寄存器, 这里包含了SUBSRCPND中没有的一些中断源, 同样地,当中断被相应之后,相应的位会被置一,如果需要清除中断,往相应的位写入1就可以实现清0的效果.
INTMOD: (0x4A00 0004)
这个寄存器同样由32位组成,每一位对应于一种中断源,如果某一位被设置为一,那么这个中断源就会被当做是快速中断来处理.且只有一个位可以被设置成快速中断,其他都为普通中断.
INTMASK: (0x4A00 0008)
INTMASK寄存器, 这个寄存器也是用于屏蔽某一种中断源.与SRCPND对应.
PRIORITY: (0x4A00 000C)
出现多个普通中断时有可能同时出现的,这时候就需要告诉CPU那种中断源优先级更高,CPU根据优先级高低先后处理中断.
优先级仲裁:
上图中的ARB_SELN(n:0-6)时什么来的?原来CPU有一个仲裁器,用来对中断源进行仲裁,.
如下图, 有6个一级仲裁器和1个二级仲裁器
1.REQ0总是有最高的优先级, REQ5总是有最低的优先级.
2.REQ1-REQ4可以根据上面的寄存器进行设置.
当ARB_MODEn被设置为0时,当这个中断被处理之后,REQn的优先级不会发生变化.
当ARB_MODEn被设置为1时,当这个中断被处理之后,REQn优先级会被置为最低,
INTPND: (0x4A00 0010)
INTPND寄存器, 经过中断优先级仲裁器选出优先级最高的中断后,这个中断的相应的位会被置1, 如果想要清除中断,往这个位写1
插播以下知识点, 因为下面代码会用到:
下面的代码多了一些陌生的知识,比如系统的模式:
ARM体系CPU有7中工作模式:
1.用户模式: ARM处理器正常的程序执行状态. (usr)
2.快速中断模式: 用于高速数据传输或通道处理; (fiq)
3.中断模式: 用于通用的中断处理; (irq)
4.管理模式: 操作系统使用的保护模式; (svc)
5.数据访问终止模式: 当数据或指令预取终止时进入该模式,用于虚拟存储及存储保护; (abt)
6.系统模式:运行具有特权的操作系统任务; (sys)
7.未定义指令终止模式: 当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真. (und)
这些模式的切换可以通过软件设置来进行切换, 或者当CPU发生中断或者异常时自动进入相应的模式. 除用户模式外,其他6种模式都属于特权模式.大多数程序运行与用户模式,进入特权模式是为了处理中断, 异常或者访问被保护的系统资源.
比如, CPU复位上电也属于一种异常, 会自动跳转到0x00地址执行指令, 当CPU发生通用中断时, 会自动进入中断模式并且跳转到0x18地址处执行指令.
如上图, ARM中有16个寄存器, 每种模式下都有这些寄存器, 但是每种模式都有一些"备份寄存器", 这些寄存器是每种模式下与其他模式不同的寄存器,是独立的. 其中
R13被称为栈指针寄存器,又叫SP
R14被称为程序链接寄存器
R15被称为程序计数器
比如你在fiq模式下设置了SP的地址, 又可以切换到irq模式下设置SP的地址,这两个SP地址是互相独立的,在两种模式下设置后都是不同的.
关于切换CPU模式,如果通过软件方式设置, 我们可以更改寄存器CPSR(Current Program Status Register)的值, 这个寄存器的每个位的含义如下, 你可以初步看到一些mode设置位, 快速中断去使能位和普通中断去使能位.
CPSR各个bit的详细解析如下:
进入异常模式时应该按照以下步骤执行:(CPU核自动执行)
1. 在异常工作模式的R14中保存前一个工作模式的下一条指令. 对于ARM状态,这个值是当前PC值加4或者加8,参考下表
2. 将CPSR的值复制到异常模式的SPSR(Saved Program Status Register)
3. 将CPSR设置为相应的异常对应的工作模式.
4.将PC值等于这个异常在异常向量表中的地址(这里的异常向量表指的上面代码0x00-0x1c地址的指令
退出异常模式的操作步骤:(软件完成)
1.将前面保存在R14中的值,将它减去一个值,参考上表
2.将SPSR的值复制回CPSR
下面来看下如何实现代码测试中断:
文件如下:
Makefile:(关于编译选项可以参考: https://www.cnblogs.com/cheyihaosky/p/11562330.html)
objs := head.o init.o interrupt.o main.o int.bin: $(objs) #编译的目标文件是int.bin, 要生成这个文件需要依赖${objs} ->> head.o init.o interrupt.o main.o这几个OBJ文件 arm-linux-ld -Ttext 0x00000000 -o int_elf $^ #$^表示所有依赖文件 arm-linux-objcopy -O binary -S int_elf $@ #$@目标文件的名称, 表示int.bin arm-linux-objdump -D -m arm int_elf > int.dis #生成反汇编文件 %.o:%.c arm-linux-gcc -Wall -O2 -c -o $@ $< #$<表示第一个.c文件 %.o:%.S arm-linux-gcc -Wall -O2 -c -o $@ $< clean: rm -f int.bin int_elf int.dis *.o
测试代码: head.S
.extern main .text @表明这里为代码段 .global _start _start: @系统一上电先从这里开始跑 b Reset @这条语句的地址为0x00,上电也算是一种异常,跳到这个地址执行第一条指令 HandleUndef: b HandleUndef @ 0x04: 这条语句在地址0x04处, 标号就在它上方,实际这里什么都不干 HandleSWI: b HandleSWI @ 0x08:同0x04 HandlePrefetchAbort: b HandlePrefetchAbort @ 0x0c:同0x04 HandleDataAbort: b HandleDataAbort @ 0x10:同0x04 HandleNotUsed: b HandleNotUsed @ 0x14:同0x04 b HandleIRQ @ 0x18: 这里实际是有相关的代码实现的 HandleFIQ: b HandleFIQ @ 0x1c:同0x04 Reset: @系统复位,会自动执行0x00地址的b reset跳转到这里 ldr sp, =4096 @下面是调用c函数,所以要先设置栈指针,由于CPU的内部RAM只有4k,所以是指成4096 bl disable_watch_dog @这里只是往控制看门够的一个寄存器写值关闭看门狗, 跳转到init.c执行这个函数 msr cpsr_c, #0xd2 @ 进入中断模式 ldr sp, =3072 @ 然后设置中断模式的栈指针 msr cpsr_c, #0xd5 @进入系统模式 ldr sp, =4096 @设置系统模式栈指针 @ 其实系统复位就处于系统模式 bl init_led @初始化LED相关的GPIO引脚 bl init_irq @ 中断初始化函数 msr cpsr_c, #0x5f @ 开IRQ中断 ldr lr, =halt_loop @ 设置返回地址 ldr pc, =main @ 调用main函数 halt_loop: b halt_loop HandleIRQ: sub lr, lr, #4 @计算返回地址? stmdb sp!, { r0-r12,lr } @保存使用到的寄存器 @ @ ldr lr, =int_return @ 设置函数的返回地址 ldr pc, =EINT_Handle @ 调用EINT_Handle函数 int_return: ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将SPSR的值复制到CPSR中
init.c
#include "s3c24xx.h" #define GPF4_out (1<<(4*2)) #define GPF5_out (1<<(5*2)) #define GPF6_out (1<<(6*2)) #define GPF4_msk (3<<(4*2)) #define GPF5_msk (3<<(5*2)) #define GPF6_msk (3<<(6*2)) #define GPF0_eint (0x2<<(0*2)) #define GPF2_eint (0x2<<(2*2)) #define GPG3_eint (0x2<<(3*2)) #define GPF0_msk (3<<(0*2)) #define GPF2_msk (3<<(2*2)) #define GPG3_msk (3<<(3*2)) void disable_watch_dog(void) { WTCON = 0; } void init_led(void) { GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk); GPFCON |= GPF4_out | GPF5_out | GPF6_out; } void init_irq( ) { GPFCON &= ~(GPF0_msk | GPF2_msk); GPFCON |= GPF0_eint | GPF2_eint; GPGCON &= ~GPG3_msk; GPGCON |= GPG3_eint; EINTMASK &= ~(1<<11); PRIORITY = (PRIORITY & ((~0x01) | (0x3<<7))) | (0x0 << 7) ; INTMSK &= (~(1<<0)) & (~(1<<2)) & (~(1<<5));
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)
interrupt.c
#include "s3c24xx.h" void EINT_Handle() { unsigned long oft = INTOFFSET; unsigned long val; switch( oft ) { case 0: { GPFDAT |= (0x7<<4); GPFDAT &= ~(1<<4); break; } // S3±»°ŽÏÂ case 2: { GPFDAT |= (0x7<<4); GPFDAT &= ~(1<<5); break; } // K4±»°ŽÏÂ case 5: { GPFDAT |= (0x7<<4); GPFDAT &= ~(1<<6); break; } default: break; } if( oft == 5 ) EINTPEND = (1<<11); SRCPND = 1<<oft; INTPND = 1<<oft; }
main.c
int main() { while(1); return 0; }