zoukankan      html  css  js  c++  java
  • INTERRUPT CONTROLLER

     1,中断的基本概念

         CPU与外设之间传输数据的控制方式通常有3种:查询方式,中断方式和DMA方式。查询方式的优点是硬件开销小不需要额外的硬件支持只是通过软件不断的轮询,使用起来也就比较简单,但在此方式下,CPU要不断地查询外设的状态,当外设未准备好时,CPU就只能循环的等待,不能执行其他程序,这样就浪费了CPU的大量时间,降低了CPU的利用率,为了解决这个矛盾,通常采用中断传送方式,即当CPU进行主程序的操作时,外设的数据已经存入输入端口的数据寄存器或者输出端口的数据输出寄存器已为空,则由外设通过接口电路向CPU发出中断请求信号,CPU在满足一定条件下,会暂停执行当前正在执行的主程序,然后转入执行相应能够进行输入/输出操作的中断子程序,待输入/输出操作执行完毕之后,CPU再返回并继续执行原来被中断的主程序。这样CPU就避免了把大量时间耗费在等待查询状态信号的操作上,使其工作效率得以大大提高。能够向CPU发出中断请求的设备或事件称为中断源,系统引入中断机制后,CPU与外设(甚至多个外设)处于“并行”工作状态,便于实现信息的实时处理和系统的故障处理。

         (1)中断响应

          中断源向CPU发出中断请求,若优先级别是最高的,则CPU在满足一定的条件时,可中断当前程序的运行,保护好被中断的主程序的断点及现场信息,然后根据中断源提供的信息,找到特定中断服务子程序的入口地址,转去执行新的程序段。

          CPU响应中断是有条件的,如内部要允许中断,中断未被屏蔽,关键一点CPU当前指令要执行完才回去响应中断(只有在CPU执行完一条指令后才会停下当前的程序去响应中断)等

          (2)中断服务子程序

          CPU响应中断以后,就会中止当前的程序,转去执行一个中断服务子程序,以完成为相应的设备服务,中断服务子程序的一般结构为:

          保护现场:由一系列压栈指令完成,目的是为了保护那些与主程序中有冲突的寄存器也就是在中断子程序中也需要用到的寄存器(如r0,r1,r2等),若中断服务子程序中所使用的寄存器与主程序中所使用的寄存器没有冲突,则这一步可省略。

          中断处理:中断处理程序在检查到相应的中断源后,调用对应的中断处理程序来执行。

          恢复现场并返回:由一系列的出栈指令完成,与现场保护对应的,但要注意数据恢复的次序。

          由于中断服务子程序需要打断主程序的执行,所以其处理应该及时完成,较长时间的延时会导致系统性能下降。

          在进入正题之前,我想先把ARM920T的异常向量表(Exception Vectors)做一个简短的介绍。ARM920T的异常向量表有两种存放方式,一种是低端存放(从0x00000000处开始存放),另一种是高端存放(从0xfff000000处开始存放)。关于为什么要分两种方式进行存放这点我将在介绍MMU的文章中进行说明,本文采用低端模式。ARM920T能处理有8个异常,他们分别是:
    Reset,Undefined instruction,Software Interrupt,Abort (prefetch),Abort (data),Reserved,IRQ,FIQ
    下面是某个采用低端模式的系统源码片段:

    _start:
    b Handle_Reset
    b HandleUndef
    b HandleSWI
    b HandlePrefetchAbort
    b HandleDataAbort
    b HandleNotUsed
    b HandleIRQ
    b HandleFIQ
    …..

    ..
    other codes

        上面这部分片段一般出现在一个名叫“head.s”的汇编文件里,“b Handle_Reset”这条语句就是系统上电之后运行的第一条语句。也就是说这部分代码的二进制码必须位于内存的最开始部分(这正是低端存放模式),因为上电后CPU会从SDRAM的0x00000000处取第一条指令并执行

    Address     Instruct
    0x00000000: b Handle_Reset
    0x00000004: b HandleUndef
    0x00000008: b HandleSWI
    0x0000000C: b HandlePrefetchAbort
    0x00000010: b HandleDataAbort
    0x00000014: b HandleNotUsed
    0x00000018: b HandleIRQ
    0x0000001C: b HandleFIQ

        上面是该程序段在系统上电后加载到内存后的分布情况,我们可以看到每条指令占用了4个字节。
        上电后,PC指针会跳转到Handle_Reset处开始运行。以后系统每当有异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表做相应的处理,比如系统触发了一个IRQ异常,IRQ为第6号异常,则CPU将把PC指向0x00000018地址(4*6=24=0x00000018)处运行,该地址的指令是跳转到“中断异常服务例程”(HandleIRQ)处运行。以上就是我对异常向量表的一个简单介绍。现在可以进入我们文章的主题 “中断异常处理”,s3c2410的中断分快中断(FIQ)和普通中断(IRQ),我们讨论的重点是普通中断(IRQ)。
    s3c2410的中断异常处理模块总共由以下寄存器构成
      SRCPND(SOURCE PENDING REGISTER)
      INTMOD(INTERRUPT MODE REGISTER)
      INTMSK(INTERRUPT MASK REGISTER)
      PRIORITY( PRIORITY REGISTER)
      INTPND(INTERRUPT PENDING REGISTER)
      INTOFFSET(INTERRUPT OFFSET REGISTER)
      SUBSRCPND (INTERRUPT SUB SOURCE PENDING)
      INTSUBMSK  (INTERRUPT SUB MASK REGISTER)

    下面我将讲解每个寄存器在一个中断处理流程中所扮演的角色:
        SRCPND/ SUBSRCPND这两个寄存器在功能上是相同的,它们是中断源引脚寄存器,在一个中断异常处理流程中,中断信号传进中断异常处理模块后首先遇到的就是SRCPND/ SUBSRCPND,这两个寄存器的作用是用于标示出哪个中断请求被触发SRCPND的有效位为32,SUBSRCPND 的有效位为11,它们中的每一位分别代表一个中断源。SRCPND为主中断源引脚寄存器,SUBSRCPND为副中断源引脚寄存器。
    这里列举出SRCPND的各个位信息:

       每个位的初始值皆为0。假设现在系统触发了TIMER0中断,则第10bit将被置1,代表TIMER0中断被触发,该中断请求即将被处理(若该中断没有被屏蔽的话)。SUBSRCPND情况与SRCPND相同,这里就不多讲了。
        INTMOD寄存器有效位为32位,每一位与SRCPND中各位相对应,它的作用是指定该位相应的中断源处理模式(IRQ还是FIQ)。若某位为0,则该位相对应的中断按IRQ模式处理,为1则以FIQ模式进行处理,该寄存器初始化值为0x00000000,即所有中断皆以IRQ模式进行处理。(详细请参考s3c2410操作手册)。
    INTMSK/ INTSUBMSK 寄存器为中断屏蔽寄存器 ,INTMSK为主中断屏蔽寄存器,INTSUBMSK为副中断屏蔽寄存器。INTMSK有效位为32,INTSUBMSK有效位为11,这两个寄存器各个位与SRCPNDSUBSRCPND分别对应。它们的作用是决定该位相应的中断请求是否被处理。若某位被设置为1,则该位相对应的中断产生后将被忽略(CPU不处理该中断请求),设置为0则对其进行处理。这两个寄存器初始化后的值是0xFFFFFFFF和0x7FF,既默认情况下所有的中断都是被屏蔽的。
    到目前为止我们总共讲解了SRCPND,INTMOD,INTMSK,SUBSRCPND,INTSUBMSK
    五个寄存器,在继续讲解PRIORITY寄存器之前我们先来看一张图。
       先弄清楚一点,现在要讨论的是一个中断优先级的判断问题。为什么会有中断有先级的问题呢?我们知道CPU某个时刻只能对一个中断源进行中断处理,如果现在有3个中断同时发生了,那CPU要按什么顺序处理这个3个中断呢?这正是引入优先级判断的原因所在,通过优先级判断,CPU可以按某种顺序逐个处理中断请求。3sc2410的优先级判断分为两级。
        如上图所示,SRCPND寄存器对应的32个中断源总共被分为6个组,每个组由一个ARBITER(0~5)寄存器对其进行管理。中断必须先由所属组的ARBITER(0~5)进行第一次优先级判断(第一级判断)后再发往ARBITER6进行最终的判断(第二级判断)。ARBITER(0~5)这六个组的优先级已经固定,我们无法改变,也就是说由ARBITER0控制的该组中断优先级最高(该组产生的中断进行第一级判断后永远会以REQ0向ARBITER6传递过去)其次是ARBITER1, ARBITER2, ARBITER4, ARBITER4, ARBITER5.我们能够控制的是某个组里面各个中断的优先级顺序。怎么控制?通过PRIORITY寄存器进行控制:]
    以下是PRIORITY寄存器各个位的参数表
       从表上我们可以知道PRIORITY寄存器内部各个位被分为两种类型,一种是ARB_MODE,另一种为ARB_SEL, ARB_MODE类型有7组对应ARBITER(0~6)ARB_SEL类型有7组对应ARBITER(0~6)。现在我将以ARBITER2为例,讲解中断组与PRIORITY寄存器中ARB_SEL, ARB_MODE之间的相互关系。
     首先我们看到ARBITER2寄存器管理的该组中断里包括了6个中断,分别是INT_TIMER0,INT_TIMER1,INT_TIMER2,INT_TIMER3,INT_TIMER4,INT_UART2,她们的默认中断请求号分别为REQ0,REQ1,REQ2,REQ3,REQ4,REQ5。我们先看PRIORITY寄存器中的ARB_SEL2该参数由两个位组成,初始值为00。从该表可以看出00定义了一个顺序 0-1-2-3-4-5 ,这个顺序就是这组中断组的优先级排列,这个顺序指明了以中断请求号为0(REQ0)的INT_TIMER0具有最高的中断优先级,其次是INT_TIMER1,INT_TIMER2…。假设现在ARB_SEL2的值被我们设置为01。则一个新的优先级次序将被使用,01对应的优先级次序为0-2-3-4-1-5,从中可以看出优先级最高和最低的中断请求和之前没有变化,但本来处于第2优先级的INT_TIMER1中断现在变成了第5优先级。从ARB_SEL2被设置为00,01,10,11各个值所出现的情况我们可以看出,除最高和最低的优先级不变以外,其他各个中断的优先级其实是在做一个旋转排列(rotate)。为了达到对各个中断平等对待这一目标,我们可以让优先级次序在每个中断请求被处理完之后自动进行一次旋转,如何自动让它旋转呢?我们可以通过ARB_MODE2达到这个目的,该参数只有1个 bit,置1代表开启对应中断组的优先级次序旋转,0则为关闭。事实上当该位置为1之后,每处里完某个组的一个中断后,该组的ARB_SEL便递增在1(达到11后恢复为00)。
        现在我们另ARB_MODE2=1,ARB_SEL2=00,则当前ARBITER2的优先级顺序为0-1-2-3-4-5,假设现在该组的1号中断请求INT_TIMER1和2号中断请求INT_TIMER2被同时触发,CPU根据优先级判断后决定先把INT_TIMER1中断向ARBITER6进行发送(在ARBITER6做第最终优先级判断),接着再向ARBITER6发送INT_TIMER2中断。请注意,在INT_TIMER1被处理完毕后,该组中段的优先级次序被自动做了一次旋转,旋转后ARBITER2的优先级顺序变为0-2-3-4-1-5。假设之后某个时刻该组的INT_TIMER1和INT_TIMER2又被同时触发,则此时CPU优先处理的会是INT_TIMER2。若我们另ARB_MODE2=0,则改组的中断优先级次序在任何情况下都不做任何改变,除非我们人为地重新设置了ARB_SEL2的值。
    INTPND 寄存器可能是整个中断处理过程中我们要特别注意的一个寄存器了,他的操作比较特别,先看一下该寄存器各位详细功能列表
        正如你所见的,INTPND寄存器与SRCPND长得一模一样,但他们在中断异常处理中却扮演着不同的角色,如果说SRCPND是中断信号进入中断处理模块后所经过的第一个场所的话,那么INTPND则是中断信号在中断处理模块里经历的最后一个寄存器。它的每个位对应一个中断请求,若该位被置1,则表示相应的中断请求被触发,描述到这里你可能会发现它不仅和SRCPND长得一模一样,就连功能都一样,其实不然,他们在功能上有着重大的区别。SRCPND是中断源引脚寄存器,某个位被置1表示相应的中断被触发,但我们知道在同一时刻内系统可以触发若干个中断,只要中断被触发了,SRCPND的相应位便被置1,也就是说SRCPND在同一时刻可以有若干位同时被置1,然而INTPND则不同,他在某一时刻只能有1个位被置1INTPND 某个位被置1(该位对应的中断在所有已触发的中断里具有最高优先级且该中断没有被屏蔽),则表示CPU即将或已经在对该位相应的中断进行处理。于是我们可以有一个总结:SRCPND说明了有什么中断被触发了,INTPND说明了CPU即将或已经在对某一个中断进行处理。
    特别注意:每当某一个中断被处理完之后,我们必须手动地把SRCPND/SUBSRCPND , INTPND三个寄存器中与该中断相应的位由1设置为0,刚才我说INTPND的操作很特别,它的特别之处就在于对当我们要把该寄存器中某个值为1的位设置为0时,我们不是往该位置0,而是往该位置1。假设SRCPND=0x00000003,INTPND=0x00000001,该值说明当前0号中断和1号中断被触发,但当前正在被处理的是0号中断,处理完毕后我们应该这样设置INTPND和SRCPND
      SRCPND=0x00000002              //位0被置为0
      INTPND =0x00000001             //位0被置为0(方法是往该位写入1)
    INTOFFSET寄存器的功能则很简单,它的作用只是用于表明哪个中断正在被处理。下面是该寄存器各位详细功能列表
        若当前INT_TIMER0被触发了,则该寄存器的值为10,以此类推。
        现在我把整个中断流程用一个图加以说明
    以上这个图清楚地说明了一个中断异常处理流程。
        下面我用INT_TIMER0, INT_TIMER2和INT_UART0三个中断完整地介绍一次中断异常处理。首先我们得做几个假设:
    假设1:这三个中断的屏蔽被取消。
    假设2:PRIORITY寄存器中ARB_MODE2,ARB_MODE5皆为0,既不进行优先级的自动旋转排序,任何时候
          ARBITER2,ARBITER5控制的中断组优先级次序分别为0-1-2-3-4-5和1-2-3-4。
    假设3:这三个中断皆为IRQ类型。
    假设4:这三个中断同时被触发。
    INT_TIMER0,INT_TIMER2和INT_UART0三个中断被同时触发,此时三个中断信号流向SRCPND寄存器,使该寄存器中的第10位,12位,28位被置为1,中断信号继续向前流经INTMASK寄存器,这三个中断都没有被屏蔽,于是信号进一步流经INTMODE寄存器,这三个中断皆为IRQ类型,故中断信号继续向前流向PRIORITY寄存器,经过优先级判断,INT_TIMER0中断信号使INTPND寄存器的第10位置1(INT_TIMER0优先级最高),此时INTOFFSET寄存器的值为10,CPU转向相应的中断服务例程进行处理。处理完毕后,我们的程序将INTPNDSRCPND的第10置为0,至此INT_TIMER0中断处理完毕。此时SRCPND的第12位,28位仍为1(这两个中断请求未被处理),故他们会继续被CPU已刚才描述的方式进行处理。
        S3C2410X处理器的中断处理与其他CPU的处理模式基本上是一致的,只是由于它引入了几种不同的处理器模式,使中断处理变得更加的容易,步骤如下:
           (1) 保存现场,当系统出现中断时,处理器会首先保存现场,这一过程包括将主程序当前的PC值存入到LR中,保存当前的程序运行状态也就是CPSR的值到SPSR中。
           (2) 模式切换,当处理器完成现场保护后,就要进入到中断模式,并将PC值设置为一个固定的值,也就是我们之前提到的通过从0x00000000开始查表查到,中断处理是0x00000018,这也就是IRQ模式的中断入口地址,在中断模式下,有两个独立的寄存器r13,r14,
    可以便于中断程序使用自己特有的堆栈,但这样就会产生一个问题,也就是中断处理时堆栈溢出保护的问题,这需要认真的估计堆栈的大小,通常在中断处理时也要尽量减少函数的调用层次,否则会有一些不可预知的错误。
           (3)  获取中断源,所有的IRQ中断都是从0x00000018开始执行,通常在该地址处放一条跳转指令,进一步跳到中断程序中,一般是如: b HandleIRQ,或 LDR pc,=0x30000018,也就是完成中断三级跳的第一跳,从NAND FLASH跳到SDRAM的中断入口。我们可以看到中断入口的一部分汇编代码:
         0x00000018:       LDR  pc, =0x30000018
         ......

         0x30000018:       b  HandlerIRQ

         HandlerIRQ:

         sub    sp,sp,#4  //将堆栈指针向上移动4个字节用来为中断分发例程入口地址预留栈空间

         stmfd  sp!,{r0}  //将r0保存到堆栈中sp自加4个字节

         ldr    r0, =HandleIRQ //将中断分发例程入口地址的指针保存到r0中

         ldr    r0,[r0] //将中断分发例程入口地址保存到r0中

         str    r0,[sp,#4] //将中断分发入口地址保存到预留的堆栈空间中

         ldmfd  sp!,{r0,pc} //这个涉及到赋值的一个顺序,堆栈中现在已经保存了两个寄存器的值,最开始r0的初始值以及中断分发例程入口地址值,这句代码的意思也就是,从堆栈中取出中断分发例程入口地址赋值给PC,sp自加4个字节指向第一次存放r0的位置,然后将该值取出来赋值给r0,所以sp的值最终不变,ldmfd的赋值顺序是从右到左。sp完成自加运算。这条指令也实现了一个跳转,因为将中断分发入口地址赋值给了PC,所以会转去执行中断分发例程。这也就实现了中断三级跳的第二跳。

             在此有个前提条件,即必须在HandleIRQ地址处保存正确的分发例程入口地址,如使用下面代码后,IsrIRQ就是中断分发例程。

              ldr  r0, =HandleIRQ

              ldr  r1, =IsrIRQ

              str  r1, [r0]

    IsrIRQ通常采用汇编语言编写,通过读取INIOFFSET寄存器获取产生中断的中断源的偏移,然后根据该偏移值获取该中断对应向量表中的偏移,最后从中断向量表中获取到中断服务函数入口地址,并跳转到该地址处执行。
             IsrIRQ:
                  sub    sp,sp,#4    //为保存PC预留栈区域
                  stmfd sp!,{r8-r9}  //将r8,r9保存到堆栈中,sp对应自加8
                  ldr     r9, =INTOFFSET  //在某一时刻INTOFFSET寄存器中只存放在INTPND中的那个中断的偏移值,将INTOFFSET地址保存到r9
                  ldr     r9,[r9]    //将该寄存器中存放的对应中断的偏移地址取出来存放到r9中
                  ldr     r8, =HandleEINT0  //HandleEINT0是中断向量表的入口地址,每四个字节保存一个终端服务函数的入口地址。
                  add   r8,r8,r9,lsl #2 //将r9获取到的值左移2位也就是乘以4,然后加上中断向量表的入口地址,结果保存到r8,此时r8也就是该中断对应中断函数入口地址的指针
                  ldr     r8,[r8]   //将该中断函数入口地址保存到r8中
                  str     r8,[sp,#8]  //将r8保存到堆栈中
                  ldmfd sp!,{r8-r9,pc}  //主要是将r8取出来赋值给pc,然后去执行对应的中断服务函数。
    IsrIR跳转到相应的ISR执行后,必须由ISR在执行完毕后推出IRQ模式,因此必须需将该函数定义为中断服务函数,若采用GNU编译器,使用如下关键字:__attribute__((interrupt("IRQ")))
                (4)处理中断,在中断程序中需要进一步获取中断源,即谁引发了中断,然后通过查表获取到相应的中断的处理程序入口,并调用对应的函数。这一步在上面已经讲叙过了。
                 (5) 中断返回,恢复现场, 在返回时需要恢复处理器的模式,包括恢复中断处理用到的所有寄存器,恢复被中断的程序运行状态CPSR,并跳转到被中断的主程序。
  • 相关阅读:
    北漂开始
    iOS沙盒简单介绍
    iOS多线程技术
    使用Redis实现分布式锁
    Spring Cloud构建微服务架构(六)高可用服务注册中心
    springboot学习之maven多环境打包的几种方式
    数据库中in和exists关键字的区别
    Java 中的悲观锁和乐观锁的实现
    springboot学习笔记-5 springboot整合shiro
    spring 整合 redis,以及spring的RedisTemplate如何使用
  • 原文地址:https://www.cnblogs.com/lidabo/p/5291942.html
Copyright © 2011-2022 走看看