ARM接口技术——中断控制器

ARM中断的介绍

ARM中断

不同的处理器对中断的处理流程大体相同,但是具体的实现细节会差别。

ARM异常源

ARM中断也是异常的一种,ARM处理器有以下几种异常源:

  • FIQ
  • IRQ
  • Reset
  • Software Interrupt
  • Data Abort
  • Prefetch Abort
  • Undefined Instruction

中断虽然是异常,但不完全是错误,异常也不是,而是为了完成某些功能的东西,只有Undefined Instruction异常是真正的系统错误。

这些在ARM异常机制也介绍过,这里主要介绍的是IRQ。

ARM中断执行过程

在ARM处理器中,CPU收到一个中断时,以IRQ为例,大概会执行以下过程:

  • 修改状态寄存器CPSR从User模式切换到到IRQ模式;
  • 把中断IRQ禁止位打开,防止再次接收IRQ中断信号(FIQ不禁止,FIQ中断信号可以打断IRQ中断程序);
  • 跳转到中断向量表IRQ的指令,该指令如果写了中断执行函数,再跳转到该代码段;
  • 执行完之后再跳转回正常程序。
ARM中断产生的问题

在ARM处理器中,有上百个中断源,可能同时产生多个中断信号,会产生问题。

比如:

  • 同样是产生IRQ的中断源有很多个,但是中断向量表的IRQ只有一条跳转指令,那么处理器不知道是谁产生的中断信号,该执行什么中断处理程序;
  • 在处理器正在执行中断程序的时候,CPSR打开了IRQ禁止位,那么其它中断就会被忽略;
  • Exynos4412芯片集成了4个同样的ARM cortex A9处理器,产生的中断应该让哪个处理器处理。

在复杂封装的ARM处理器芯片中还会产生一系列问题,这时候就需要一个中断控制器GIC,去统一管理外部产生的中断信号。

Exynos4412下的中断控制器GIC

中断控制器做了至少四件事:

  1. 接收外部设备产生的中断信号;
  2. 多个中断同时产生时,对中断信号挂起,并按照优先级依次发给CPU;
  3. 为每个中断选择一个CPU进行处理;
  4. CPU处理中断程序时,可以查询中断控制器获取中断信号是由哪个硬件产生的。

这样就解决了前面提到的几个问题,除此之外中断控制器还做了一系列管理中断的事情。

中断实验

中断实验分析

Exynos4412这种SOC要比单片机功能强大很多,相关的寄存器配置也及其复杂,SOC一般会安装一个操作系统,这些事情一般都由操作系统内核去调度。

这里裸机实验就一切从简,只实现基本的中断功能,对于设置中断类型、优先级和选择处理器等不做配置。

原理图:

外围电路原理图:按键Key2连接的引脚是UART_RING。

核心板原理图:所连接的是GPX1_1引脚,同时它是外部中断9的中断源:XEINT9。

中断相关寄存器的配置

Exynos4412中断机制比较复杂,有160个中断,分为三种中断类型:

 

 

我们使用的是SPI,提供的SPI类型中断有128个(0-127)。

1. 首先是中断源的配置,即引脚相关寄存器:

GPX1CON GPIO配置寄存器,GPX1CON[1]设为中断功能,0xF。


 

前面提到我们使用的是外部中断9,但是它使用的中断配置寄存器叫EXT_INT41,不知道三星公司为什么这样设计,但是它正好在寄存器列表的GPX相关寄存器下面。

EXT_INT41_CON 中断配置寄存器,EXT_INT41_CON[1]设为下降沿触发,0x2。


 

EXT_INT41_MASK 中断屏蔽寄存器,默认是打开屏蔽,需要关闭屏蔽使能中断功能,EXT_INT41_MASK[1]设为0。

 

 


EXT_INT41_PEND,挂起状态寄存器

 

 

EXT_INT41_PEND寄存器在该引脚产生中断时,会自动挂起变成1,在CPU处理了这个中断后,这个寄存器的值不会改变,会不断的发出中断信号,所以中断处理程序在处理完这个中断之后需要修改这个寄存器的值。

这个寄存器很特殊,写0的效果是变成1,写1变成0,所以中断处理程序需要写1,它会变成0。

 

至此中断源就配置好了,但是Exynos4412的CPU不直接去接收中断信号,而是通过一个中断控制器去管理中断,所以还需要配置一系列中断控制器的配置。


 

2. 中断控制器相关配置:

首先要找到这个中断源对应的中断号是多少。

中断表的中断号是第二列ID,第一列只是中断类型为SPI的编号。

...

ID: 141-68略

...

 可以看到我们要使用的外部中断9的中断号是57,后面会用到。


 

ICDDCR寄存器

ICDDCR,整个中断控制器的总开关,让中断控制器能够接收外部的中断信号,必须要打开。ICDDCR = 1。


 

ICDISER_CPU

 

 

这组寄存器的作用是使能某个中断与CPU的接口(它的表述上是打开中断与CPU的接口,实际上只是打开中断与中断控制器的接口,真正与CPU交互的是中断控制器)。

Exynos4412有160个中断,每个寄存器是32位,所以可以控制32个中断,一共需要5个寄存器。

(但是它提供了8个寄存器,后面3个是与PPI和SGI相关寄存器,与SPI中断无关)

它的对应关系如下:

 

 

57号中断所在的寄存器地址偏移量为0x0104,结合上一张图0x0104对应的寄存器名为ICDISER1_CPU0,在三星提供的头文件中寄存器名是ICDISER1。

所以打开中断57与CPU0的接口就是设置 ICDISER1_CPU0[25]=1。


 

ICDIPTR_CPU

ICDIPTR_CPU寄存器设置用于告诉中断控制器这个中断该转发给哪个CPU。

它的数量很多对应关系比较复杂,直接给出结果:ICDIPTR14[15:8] = 0x01。


 

ICCICR_CPU

至此只是告诉中断控制器这个中断转发给哪个CPU,但是中断控制器与这个CPU之间的接口还没打开。

刚刚设置了要转发给CPU0,那么需要打开GIC与CPU0的接口:ICCICR_CPU0 = 1。


 

ICCEOIR

 

 

这个寄存器的作用是当中断处理程序执行完以后,CPU告诉GIC当前处理的中断号程序已经处理完,GIC如果还有挂起的中断,就可以发给CPU了。

在中断处理程序结束之前:ICCEOIR_CPU0 = 57(告诉GIC处理完57号中断)。


 

配置完以上寄存器就配置了以下功能:

  • 打开中断控制器总开关
  • 中断控制器对某个中断号的使能
  • 配置某个中断发生后该发往哪个处理器
  • 打开中断控制器与某个处理器直接的接口

3. 除此之外,还有其它一系列寄存器设置,前面也提到只实现基本的中断功能,所以其它寄存器就不做设置。

中断实验代码

C语言执行环境的修改:

中断实验需要修改中断向量表,另外一篇会介绍裸机实验的工程模板,还没写好,这里不作介绍。

启动汇编程序start.S(这里只需要关注7-14行和93-98行):

  1 .text   2 .global _start   3 _start:   4     /*   5      * Vector table   6      */    7     b reset   8     b .   9     b .  10     b .  11     b .  12     b .  13     b irq_handler  14     b .  15   16 reset:  17     /*  18      * Set vector address in CP15 VBAR register  19      */   20     ldr    r0, =_start  21     mcr    p15, 0, r0, c12, c0, 0    @Set VBAR  22   23     /*  24      * Set the cpu to SVC32 mode, Disable FIQ/IRQ  25      */    26     mrs r0, cpsr  27     bic r0, r0, #0x1f  28     orr    r0, r0, #0xd3  29     msr    cpsr ,r0  30   31     /*  32      * Defines access permissions for each coprocessor  33      */    34     mov    r0, #0xfffffff  35     mcr    p15, 0, r0, c1, c0, 2        36   37     /*  38      * Invalidate L1 I/D                                                                                                                     39      */  40     mov    r0, #0                    @Set up for MCR  41     mcr    p15, 0, r0, c8, c7, 0    @Invalidate TLBs  42     mcr    p15, 0, r0, c7, c5, 0    @Invalidate icache  43       44     /*  45      * Set the FPEXC EN bit to enable the FPU  46      */   47     mov r3, #0x40000000  48     fmxr FPEXC, r3  49       50     /*  51      * Disable MMU stuff and caches  52      */  53     mrc    p15, 0, r0, c1, c0, 0  54     bic    r0, r0, #0x00002000        @Clear bits 13 (--V-)  55     bic    r0, r0, #0x00000007        @Clear bits 2:0 (-CAM)  56     orr    r0, r0, #0x00001000        @Set bit 12 (---I) Icache  57     orr    r0, r0, #0x00000002        @Set bit 1 (--A-) Align  58     orr    r0, r0, #0x00000800        @Set bit 11 (Z---) BTB  59     mcr    p15, 0, r0, c1, c0, 0  60   61     /*  62      * Initialize stacks                                                                                                                    63      */  64 init_stack:       65     /*svc mode stack*/  66     msr cpsr, #0xd3  67     ldr sp, _stack_svc_end  68   69     /*undef mode stack*/  70     msr cpsr, #0xdb  71     ldr sp, _stack_und_end  72   73     /*abort mode stack*/      74     msr cpsr,#0xd7  75     ldr sp,_stack_abt_end  76   77     /*irq mode stack*/      78     msr cpsr,#0xd2  79     ldr sp, _stack_irq_end  80       81     /*fiq mode stack*/  82     msr cpsr,#0xd1  83     ldr sp, _stack_fiq_end  84       85     /*user mode stack, enable FIQ/IRQ*/  86     msr cpsr,#0x10  87     ldr sp, _stack_usr_end  88   89     /*Call main*/  90     b main  91   92 /* IRQ的异常处理程序 */      93 irq_handler:  94     /* 因为产生IRQ异常时LR自动保存的是当前执行指令的下下条指令 */  95     sub lr,lr,#4     /* 修正LR位为中断前执行指令的下条指令 */  96     stmfd sp!,{r0-r12,lr}    /* 压栈保护现场 */  97     bl do_irq    /* 跳转到中断处理程序 */  98     ldmfd sp!,{r0-r12,pc}^    /* 出栈恢复现场,同时恢复中断之前的模式 */  99  100  101 _stack_svc_end:       102     .word stack_svc + 512 103 _stack_und_end:       104     .word stack_und + 512 105 _stack_abt_end:       106     .word stack_abt + 512 107 _stack_irq_end:       108     .word stack_irq + 512 109 _stack_fiq_end: 110     .word stack_fiq + 512 111 _stack_usr_end:       112     .word stack_usr + 512 113  114 .data 115 stack_svc:       116     .space 512 117 stack_und: 118     .space 512 119 stack_abt:       120     .space 512 121 stack_irq:       122     .space 512 123 stack_fiq:       124     .space 512 125 stack_usr:       126     .space 512

interface.c:

/*  * 按下K2,串口打印信息  * 按下K3,LED点亮,再按熄灭  * */  #include exynos_4412.h  void do_irq() {     /* 从中断控制器中获取当前中断的中断号 */     unsigned int IrqNum = CPU0.ICCIAR & 0x3FF;     switch(IrqNum)     {         case 57:             printf(Key2 pressed\n);  /* 另一个文件实现的重定向到UART的printf */             EXT_INT41_PEND = (1<<1);    /* 把中断控制器的挂起清除 */             /* 告诉中断控制器当前中断已经处理完成 */             /* 把当前中断的中断号写回中断控制器 */             CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 57;             break;         case 58:             LED2_Turn();             EXT_INT41_PEND = (1<<2);    /* 把中断控制器的挂起清除 */             /* 告诉中断控制器当前中断已经处理完成 */             /* 把当前中断的中断号写回中断控制器 */             CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 58;              break;         default:             break;     } }  void KEY2_INT_Init() {     /* 外围层次 */     /* GPX1_1设为中断功能 */     GPX1.CON = GPX1.CON | (0xF<<4);     /* 下降沿触发 */     EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<4)) | (0x2<<4);     /* 打开中断,写 0 Enable */     EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1<<1));       /* SOC内层 中断控制器 */     /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */     ICDDCR = ICDDCR | 1;     /* 中断控制器中使能57号中断 */     ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<25);     /* 设置57号中断发给CPU0处理 */     ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<8)) | (0x01<<8);     /* 打开中断控制器与CPU0的接口 */     CPU0.ICCICR = CPU0.ICCICR | 1;  }  void KEY3_INT_Init() {     /* 外围层次 */     /* GPX1_1 设为中断功能 */     GPX1.CON = GPX1.CON | (0xF<<8);     /* EXT_INT41_CON[2] 设为下降沿触发 */     EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<8)) | (0x2<<8);     /* EXT_INT41_MASK[2] 打开中断,写 0 Enable */     EXT_INT41_MASK = EXT_INT41_MASK & ~(0x1<<2);       /* SOC内层 中断控制器 */     /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */     ICDDCR = ICDDCR | 1;     /* 中断控制器中使能58号中断 */     ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<26);     /* 设置58号中断发给CPU0处理 */     ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<16)) | (0x1<<16);     /* 打开中断控制器与CPU0的接口 */     CPU0.ICCICR = CPU0.ICCICR | 1;  }   void LED2_Init() {     GPX2.CON = GPX2.CON & (~(0xF<<28)) | (0x1<<28); }  void LED2_On() {     GPX2.DAT |= (1<<7); }  void LED2_Off() {     GPX2.DAT &= (~(1<<7)); }  void LED2_Turn() {     if(GPX2.DAT & (1<<7))/* 条件为真,则关掉LED */         LED2_Off();     else         LED2_On(); }  int main() {     KEY2_INT_Init();     KEY3_INT_Init();     LED2_Init();     while(1);     return 0; }

实验现象

按下K2,终端会打印信息: Key2 pressed。

按下K3,LED2会打开,再次按下LED2会熄灭。