嵌入式Linux应用开发完全手册(三)中断

2019-07-12 16:07发布

9 中断体系结构

9.1 ARM中断体系

ARM CPU工作模式和状态

  • 工作模式,7种,1种用户模式,其他6选中特权模式
    • usr 用户模式,ARM处理器正常的工作模式
    • fiq 快速中断模式,高速数据传输或者通道处理
    • irq 中断模式,通用中断处理
    • svc 管理模式,操作系统使用的保护模式
    • abt 数据访问终止模式,数据或指令预取终止时进入该模式,用于虚拟存储及存储保护
    • sys 系统模式,运行具有特权的操作系统任务
    • und 未定义指令终止模式,未定义的指令得到执行的时候进入该模式,可用于支持硬件协处理器的软件仿真
      大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
  • 工作状态,2种
    • ARM状态,处理器执行32位的ARM指令
    • Thumb状态,处理器执行16位的Thumb指令
      CPU一上电就处于ARM状态,无需关心CPU状态。
  • 寄存器
    • 同一时刻可见的寄存器有17个
      • r0 - r12
      • r13 sp
      • r14 lr
      • r15 PC
    • 不同模式下并不会复用所有的寄存器
      • fiq r8 - r14 是独立的
      • svc,abt,irq,und r13, r14 是独立的
      • fiq,svc,abt,irq,und SPSR是独立的
    • CPSR各位含义,详见CPSR
      • N 结果是否为负数
      • Z 运算结果是否为0
      • C 进位/错位/移位溢出
      • V 溢出
      • I,F IRQ中断禁止,FIQ中断禁止
      • T Thumb状态
      • mode CPU当前工作模式

异常进入和返回流程

  • 异常进入
    1. 异常工作模式下的lr保存进入前的下一个指令地址
    2. CPSR复制到SPSR
    3. 设置CPSR到当前异常的工作模式
    4. 设置PC到这个异常的异常向量表入口地址
  • 异常返回
    1. 异常模式下的LR,减去适当的值,设置到PC
    2. SPSR的值恢复到CPSR

中断处理过程

  1. 中断控制器汇集各类外设发出的中断信号,通知CPU
  2. CPU保存当前程序运行环境,调用中断服务程序ISR来处理这些中断
  3. ISR中通过读取中断控制器、外设的相关寄存器识别中断源,进行相应处理
  4. 清除中断,读写中断控制器和外设寄存器
  5. 恢复被打断的程序
    Markdown

2440中断处理流程

Markdown
  • 中断源
    • 含有自子中断的中断源,上图可以看到,子中断先过一遍mask,再设置SRCPND
    • 不含有子中断的中断源,直接设置SRCPND
    • 中断屏蔽
    • 子中断先经过INTSUBMASK判断
    • 不含有子中断和经过判断的子中断,再过一遍INTMASK
    • 优先级
    • 如果是FIQ,INTMOD是1,不用经过优先级选择,直接执行。FIQ只能分配一个。
    • 如果是IRQ,需要经过优先级选择,最高优先级的中断,被设置到INTPND
    • 中断执行
    • ISR(中断处理程序)读INTPND或者INTOFFSET确定中断源

9.2 2440中断控制寄存器

SUBSRCPND

Markdown
  • 2440有15个子中断源,能看到分为5组,分别是这5个中断的子中断
    • INT_WDT_AC97 看门狗
    • INT_CAM 摄像头
    • INT_ADC 触摸板
    • INT_UART0, UART1, UART2 串口
  • 发生中断的时候相应的位被自动设置成1
  • 想要清除的话,需要再写入对应位置的1,请注意不是清零,是写1

INTSUBPND

Markdown
某位被置1的时候,对应中断被屏蔽

SRCPND

Markdown
Markdown
处于pending状态的中断源。
来源有两类,参考上文,带有子中断的,和不带有子中断的。
清除某一位,需要对某一位写1而不是清0.

INTMSK

Markdown
Markdown
屏蔽SRCPND中的中断源。
某位写1即屏蔽该中断,只能屏蔽IRQ,不能屏蔽FIQ。

INTMOD

Markdown
Markdown
某位写1,即设定该中断位FIQ,见上文,同时只能有一个中断设置位FIQ。
一般设置最紧急的中断位FIQ。

PRIORITY

Markdown
Markdown
- 优先级寄存器控制优先级仲裁器的行为
Markdown
  • 2440的优先级仲裁,分为2个阶段
    • 1级仲裁,即仲裁器0到仲裁器5,小组赛,选择出优胜者
    • 2级仲裁,即仲裁器6,决赛,各 小组选出的中断再经过决赛选择总冠军
  • 每组的优先级顺序可以配置,通过ARB_SLE
  • 每组的优先级模式可以配置,是固定不变的,还是轮转的
    • 固定不变的,按照ARB_SLE的配置顺序
    • 轮转的,已经被服务的中断,其优先级降到倒数第二,仅高于REQ5
    • 即使轮转,最高和最低,REQ0和REQ5都不会变化

INTPND

Markdown
Markdown
经过优先级筛选之后,从SRCPND中选出的中断,在INTPND中设置相应的位为1.
同一时间,只能有一位是1.
要清除这个中断,需要将相应的位置1,而不是清零,通过INTPND = INTPND 操作即可。

INTOFFSET

Markdown
跟INTPND是联动的,INTPND对应位置1表示有中断等待处理,INTOFFSET这时设置成一个数字,跟INTPND表示的是同一个意思,编号为多少的中断正在等待处理。
清除SRCPND,INTPND寄存器的时候,INTOFFSET寄存器被自动清除。

9.3 实例

jz2440开发板上有4个按键,分别对应外部中断EINT0,EINT2,EINT11,EINT19,通过这4个按键操作LED灯。
  • 程序结构
    • 启动文件 head.s
    • C程序
      • 中断初始化 init.c
      • 中断例程 interrrupt.c
      • 主程序 main.c
  • 中断向量
    • 发生异常的时候,ARM核按照一个向量表决定如何跳转到响应的例程
    • 当前的CPU处理的方式是在向量表的对应入口存放跳转指令
    • 较新的CPU,或者第三方定制的CPU,也有可能存放的是例程地址,而不是指令
    • 如果是指令,可以接受的指令如下
      • b [address]
      • ldr pc,[pc,#offset]
      • ldr pc,[pc,#-ff0]
      • mov pc,#immediate
    • 向量位置
      • reset svc 0x00
      • undef und 0x04
      • SWI svc 0x08
      • I-abt abt 0x0C
      • D-abt abt 0x10
      • 未定义 未定义 0x14
      • IRQ IRQ 0x18
      • FIQ FIQ 0x1C

head.s

[head.s] .extern main .text .global _start _start: @ 这个例子里边,因为使用了IRQ,所以开始的部分按照IRQ的要求设置中断向量,从地址0开始 b Reset @ 0x00, reset异常,上电首先执行 HandleUndef: b HandleUndef @ 0x04, 未定义异常,一下发生的异常,如果没有处理例程,就直接在对应位置死循环 HandleSWI: b HandleSWI @ 0x08, SWI软中断异常 HandlePreFetchAbort: b HandlePreFetchAbort @ 0x0C, 预取指异常 HandleDataAbort: b HandleDataAbort @ 0x10, 数据访问异常 HandleReserved: b HandleReserved @ 0x14,未定义 b HandleIRQ @ 0x18, 中断处理 HandleFIQ: b HandleFIQ @ 0x1c,快速中断 Reset: ldr r0,=0x53000000 @ 关闭看门狗 mov r1,#0 str r1,[r0] ldr sp,=4096 @ 设置栈指针,因为下面都是C函数,所以需要设置栈指针 msr cpsr_c,#0xd2 @ 进入中断模式,CPSR 11010010 @ 为什么不是cpsr,而是cpsr_c,因为cpsr_c表示cpsr的低8位 @ IF都设置上了,IRQ和FIQ屏蔽,模式10010,IRQ模式 ldr sp,3072 @ 设置IRQ模式的栈指针,见上文,寄存器的拷贝 msr cpsr_c,#0xdf @ 进入系统模式,模式11111 ldr dp,=4096 @ 设置系统模式的栈指针,CPU复位之后,处于系统模式 bl init_led bl init_irq msr cpsr_c,#5f @ 打开I,IRQ去掉屏蔽 ldr lr,=halt_loop ldr pc,=main halt_loop: b halt_loop HandleIRQ: sub lr,lr,#4 @ 返回地址 stmdb sp!,{r0-r12,lr} @ 保存使用到的寄存器,!的含义是sp随着数据传送的改变而改变 @ 现在sp,已经是IRQ模式的sp @ IRQ模式的sp,比系统模式少1000字节,啥意思,是怕覆盖了系统模式下的栈数据? ldr lr,=int_return ldr pc,=EINT_Handle @ ISR例程,在interrupt.c中实现 int_return: ldmia sp!,{r0-r12,lr}^ @ 中断返回,^的意思是将SPSR恢复到CPSR

init.c

init.c程序中设置中断寄存器的状态。
用到的定义,放在头文件int_key_led.h中。 [int_key_led.h] #ifndef _INT_KEY_LED_H #define _INT_KEY_LED_H /* * LED1,LED2,LED4对应GPF4、GPF5、GPF6 */ #define GPF_OUT(x) (1 << ((x) * 2)) #define GPF_MSK(x) (3 << ((x) * 2)) #define GPG_MSK(x) GPF_MSK(x) /* * S2,S3,S4对应GPF0、GPF2、GPG3 */ #define GPF_EINT(x) (2 << ((x) * 2)) #define GPG_EINT(x) GPF_EINT(x) #define GPFCON (*(volatile unsigned long *)0x56000050) #define GPFDAT (*(volatile unsigned long *)0x56000054) #define GPGCON (*(volatile unsigned long *)0x56000060) #define INTOFFSET (*(volatile unsigned long *)0x4a000014) #define EINTMASK (*(volatile unsigned long *)0x560000a4) #define PRIORITY (*(volatile unsigned long *)0x4a00000c) #define INTMSK (*(volatile unsigned long *)0x4a000008) #define EINTPEND (*(volatile unsigned long *)0x560000a8) #define SRCPND (*(volatile unsigned long *)0x4a000000) #define INTPND (*(volatile unsigned long *)0x4a000010) /* LED灯对应的二进制数字,可以表示0到7 */ #define LED_NUM(x) (~(((x) & ~((~0) << 3)) << 4)) #define LED_MSK(x) (1 << ((x) + 4)) #endif /* * init.c: 进行一些初始化 */ #include "int_key_led.h" void init_led(void) { // LED1,LED2,LED4对应的3根引脚设为输出 GPFCON &= ~(GPF_MSK(4) | GPF_MSK(5) | GPF_MSK(6)); GPFCON |= GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6); } /* * 初始化GPIO引脚为外部中断 * GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD) */ void init_irq( ) { // S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2 GPFCON &= ~(GPF_MSK(0) | GPF_MSK(2)); GPFCON |= GPF_EINT(0) | GPF_EINT(2); // S4对应的引脚设为中断引脚EINT11 GPGCON &= ~GPF_MSK(3); GPGCON |= GPF_EINT(3); // 对于EINT11,需要在EINTMASK寄存器中使能它 EINTMASK &= ~(1 << 11); /* * 设定优先级: * ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2 * 仲裁器1、6无需设置 * 最终: * EINT0 > EINT2 > EINT11即K2 > K3 > K4 */ PRIORITY = (PRIORITY & ((~1) | (3 << 7))) | (0 << 7) ; // EINT0、EINT2、EINT8_23使能 INTMSK &= (~(1 << 0)) & (~(1 << 2)) & (~(1 << 5)); }

interrupt.c

函数EINT_Handle的实现。 #include "int_key_led.h" void EINT_Handle() { unsigned long oft = INTOFFSET; unsigned long val; GPFDAT &= ~(LED_MSK(0) | LED_MSK(1) | LED_MSK(2)); GPFDAT |= LED_NUM(oft); //清中断 if( oft == 5 ) EINTPEND = (1<<11); // EINT8_23合用IRQ5 SRCPND = 1 << oft; INTPND = 1 << oft; }

main.c

死循环。 int main() { while(1); return 0; }

Makefile

src := $(shell ls *.c *.s) obj := $(patsubst %.s, %.o, $(src)) obj := $(patsubst %.c, %.o, $(obj)) int_key_led.bin : $(obj) arm-linux-ld -Ttext 0x00000000 $^ -o int_key_led_elf arm-linux-objcopy -O binary -S int_key_led_elf int_key_led.bin %.o : %.c arm-linux-gcc -c -o $@ $< %.o : %.s arm-linux-gcc -c -o $@ $< clean : rm *.o *_elf *.bin