POWERPC中断【转】

2019-04-15 15:43发布

从CPU的e500核的角度,中断源分为自己内核产生的异常和 PIC提供的中断
 
异常,是e500核产生的,它是同步产生的(可以预知的),如非法指令,或访问存储器时出现TLB Miss等情况
中断,是e500核外部引脚产生的中断,由PIC送进来来的,它是异步产生的(无法预知的),
主要有:int#, cint#, mcp#
分别对应一般中断,critical中断和machine check中断。
下面是简图
 


一、中断的开关

 

中断的开关由寄存器MSR来控制的
CE:if set 1,Critical input and watchdog timer interrupts are enabled.
EE:if set 1,External input, decrementer, fixed-interval timer and performance monitor interrupts are enabled
ME:if set 1, Machine check interrupts are enabled.
DE:if set 1,Debug interrputs are enabled if DBCR0[IDM] = 1.

MSR[EE]这个bit很重要,中断的使能要靠它。
注意:当发生中断后,MSR[EE]会自动置0屏蔽所有其他的中。
      如果在MSR[EE]置1前,又来了一个中断
           1.此中断是边沿触发的,那么此中断丢失。此种中断就是电平变化,一次就没了
           2.此中断时水平触发的,此中断不会丢失。此种中断的电平会一直active,直到硬件处理它

二、中断向量

   

每个中断都有自己的中断向量,通过它能计算出中断handler的指令地址。
指令地址的计算方法:
        IVPR[32-47] || IVORn[48-59] || 0b0000
这样就得到一个32个bit地址。
IVOR是一系列寄存器,有下面一张表:    有2个中断向量对程序员来说比较亲切:
    一个是 IVOR4 用来处理外部中断和内部SOC中断
    一个是 IVOR8 用来处理系统调用

随便找一个,比如IVOR10,在uboot下验证。查看uboot-1.1.4/cpu/mpc85xx/start.S
    185 /* Setup interrupt vectors 设置IVPR寄存器*/
    186 lis r1,TEXT_BASE@h
    187 mtspr IVPR, r1

    197 li r1,0x0500
    198 mtspr IVOR4,r1 /* 4: External interrupt */
    。。。省略
    205 li r1,0x0900
    206 mtspr IVOR8,r1 /* 8: System call */
    207 /* 9: Auxiliary processor unavailable(unsupported) */
    208 li r1,0x0a00

    209 mtspr IVOR10,r1 /* 10: Decrementer */
    。。。省略

看board/pq37pc/pq37pc_8560/config.mk 里有 
     TEXT_BASE = 0xFF800000
所以 IVPR   = 0xFFF80000
     IVOR10 = 0x00000a00
计算 IVPR[32-47] || IVPOR10[48-59] || 0b0000  得 0xFFF80a00

检查uboot的符号表文件System.map,验证一下
    14 fff80900 t SystemCall
    15 fff80970 t _back
    16 fff809cc t _end_back

    17 fff80a00 t Decrementer
    18 fff80b00 t IntervalTimer
    19 fff80c00 t WatchdogTimer


至于 Decrementer符号处调用那个函数,也是在uboot-1.1.4/cpu/mpc85xx/start.S文件里定义
    572 STD_EXCEPTION(0x0a00, Decrementer, timer_interrupt)
    573 STD_EXCEPTION(0x0b00, IntervalTimer, UnknownException)
    574 STD_EXCEPTION(0x0c00, WatchdogTimer, WatchdogHandle)


STD_EXCEPTION宏 和 CRIT_EXCEPTION宏 具体咋操作,待看.....

在include/ppc_asm.tmpl 中定义
    #define STD_EXCEPTION(n, label, hdlr)                           \ 277         . = n;                                                  \ 278 label:                                                          \ 279         EXCEPTION_PROLOG(SRR0, SRR1);                           \ 280         addi    r3,r1,STACK_FRAME_OVERHEAD;                     \ 281         EXC_XFER_TEMPLATE(label, hdlr, MSR_KERNEL, NOCOPY)      \
三、中断源寄存器

 中断向量只能确定类型,里面保存了interrupt handler的地址。
 在interrupt handler里,需要根据中断源寄存器来进一步确定到底是哪里发生了中断。

 E500内部中断源有32个,来自TSEC,LBC,DMA,CPM等
 寄存器组 IIVPRn 与 MPC8560 内部SOC中断一一对应(n取值 0~31)
      E500外部中断源有12个,来自外部引脚 IRQ[0:11]
  寄存器组 EIVPRn 与 MPC8560 外部中断一一对应(n取值 0~11)
    

其实,内外中断源寄存器格式差不多,下面关注外部中断源寄存器:

MSK:  若置1,此中断源的中断被无视.
A:    若置1,此中断源有中断发生
P:    若置1, active-high; 若置0, active-low.
S:    若置1,此中断是水平触发。若置0,此中断是边界触发
PRIORITY: 优先级0-15。15是最高优先级,0时相当于无视此中断。
VECTOR: 硬件中断号。中断发生后处于pengding时,此字段被写在寄存器IACK

 

VECTOR:当前处于pending状态的最高优先级的硬件中断号
处理内、外中断的interrupt handler都是 ExternalInput(看上面第三节)
在ExternalInput里读 寄存器IACK 就知道是哪个中断源触发了中断


---------------------- 华丽的分割线 --------------------------------

知道了 中断向量 和 中断源,系统处理中断的大致流程就能理出来了,以外部中断举例:
1. 硬件上,外部中断管脚8上传来一个中断
   CPU 的 PIC 将寄存器 EIVPR8 的VECTOR 字段拷贝到寄存器 IACK 中 
   这个VECTOR字段就是所谓的“硬中断号”,它是可编程的。不同OS,硬中断号分配方法不一样。
2. 然后PIC 通知 CPU 的 core 跳转到下列地址,执行interrupt handler
               IVPR[32-47] || IVOR4[48-59] || 0b0000

   IVOR4 就是中断对应的中断向量寄存器了,它在uboot中是这样初始化的
               SET_IVOR(4, ExternalInput)  
   注意,外部中断、内部中断的 interrupt handler 都是 ExternalInput 
   
3.软件上 ExternalInput 做下列事情:
  读寄存器IACK,获取VECTOR字段,获得“硬中断号” 
  接着保存中断现场,然后再接着处理,因OS而异。
  
   Linux中是根据“硬中断号”获取“逻辑中断号”,然后调用逻辑中断号上所挂的中断处理例程
        EXCEPTION(0x0500, ExternalInput, do_IRQ, EXC_XFER_LITE)


---------------------- 华丽的分割线 --------------------------------


四、Linux硬中断机制

    

前面说的都E500中断架构,下面讲Linux在此架构下的中断机制的实现
先简单列下中断处理流程:
    第一步:
        中断来了根据中断向量表(在head_fs_booke.S中初始化)来执行handler
        如IVOR4,就执行ExternalInput() --> do_IRQ()
    第二步:
        do_IRQ()通过读IACK获取“硬件中断号”
    第三步:
        硬件中断号m --> 逻辑中断号n
        从而找到irq_desc[n]
    第四步:
        遍历执行irq_desc[n]上action链表(这些action是通过request_irq注册的)

   上面基本上就是一个硬中断的处理过程,由上可知的,它需要三个初始化:
   1. 中断向量的初始化
   2. 中断源的初始化 (即硬件中断号的分配)
   3. ISR中断处理例程的注册(也就是上面的action)
 
4.1 初始化中断向量

查看文件 arch/ppc/kernel/head_fsl_booke.S
    318 /* Establish the interrupt vector offsets 建立异常向量表*/
    319  SET_IVOR(0,  CriticalInput);
    320  SET_IVOR(1,  MachineCheck);
    321  SET_IVOR(2,  DataStorage);
    322  SET_IVOR(3,  InstructionStorage);
    323  SET_IVOR(4,  ExternalInput);
    324  SET_IVOR(5,  Alignment);
    325  SET_IVOR(6,  Program);



4.2 初始化中断源

 
  
Linux内核有2个重要的数据结构来描述外部中断.
   全局变量ISR[]中记录了中断源寄存器的地址,内核通过它可以初始化中断源寄存器,完成硬件中断号的分配,中断优先级,触发方式的初始化。
   全局变量irq_desc[]则记录了逻辑中断号,以及此中断上挂的钩子函数ISR

文件arch/ppc/platforms/85xx/mpc8560_ads.c
    start_kernel-->init_IRQ --> mpc8560_ads_init_IRQ
    -->mpc85xx_ads_init_IRQ()
        -->openpic_set_sources(0, 32, OpenPIC_Addr + 0x10200)
        -->openpic_set_sources(48, 12, OpenPIC_Addr + 0x10000)
          //ISR[0~31]对应着内部中断源
 IIVPRn 的物理地址 (n取值0~31)
          //ISR[48~59]对应着外部中断源 EIVPRn 的物理地址
 (n取值0~11)
        -->openpic_init(MPC85xx_OPENPIC_IRQ_OFFSET) //初始化中断源寄存器
           for(i=0; i              if (ISR[i] == 0) continue;
              openpic_disable_irq(); //操作ISR[i],关闭中断,EIVPR[MSK] IIVPR[MASK]都被置1
              openpic_initirq();
                //初始化ISR[i],分配中断号,优先级OPENPIC_PRIORITY_DEFAULT
                //触发方式(水平、边沿)由全局数组mpc85xx_ads_openpic_initsenses[]决定

           }


硬件中断号的分配方案,取决于OS和PIC,上图是 Linxppc、OpenPIC的例子。
知道一点就够了:当外设连到MPC8560某一外部中断管脚后,其实它的硬件中断号就已经由 EIVPRn 确定了

4.3 注册ISR


就是函数request_irq()  驱动中常用。

    request_irq(逻辑中断号,中断处理函数,IRQF_DISABLED等标记)
        action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
        setup_irq(irq,action); //用形参来初始化一个irqaction
            struct irq_desc *desc = irq_desc + irq; //irq_desc全局变量数组
            //一些代码,将新建的irqaction放到 irq_desc[irq] 的action链表中去   
            if(desc->status允许使能中断源,即IRQ_NOAUTOEN位为0)
               des->chip->enable(irq) //使能此中断
            register_irq_proc(irq);   //将中断信息注册到proc
            register_handler_proc(irq, new);


4.4 中断嵌套

描述硬中断处理流程(即中断上半部)之前,先说说中断嵌套。
所谓中断嵌套,就是在执行中断处理流程ISR时,有另一个中断发生,将当前流程打断。
这种中断“加塞”的过程,就是中断嵌套。
Linux支持“硬中断嵌套”,但有个限制:
    1.不同中断的ISR可以自由嵌套,同种中断的ISR不可嵌套。(这里通过硬中断号来区分相同与否)

    2.不同中断ISR的嵌套,是有层数限制的

对于同种中断,它是通过数据结构 irq_desc[n].status 来防止中断嵌套,如下图所示:

 

另外,不同中断ISR嵌套时,为了防止中断堆栈的溢出,嵌套层数是有限制的,
OS一般有两种方法来处理ISR中断栈:
   1.为中断建立独立的堆栈,与应用程序、系统程序的栈段独立。
   2.将中断堆栈依附在OS的核心堆栈中。(LinuxPPC采用此种)

Linux如何限制中断嵌套层数?待看


4.4 硬中断处理流程

比如来了一个外部中断,根据中断向量,它的handler是函数ExternalInput
    322  SET_IVOR(3,  InstructionStorage);
    323  SET_IVOR(4,  ExternalInput);
    324  SET_IVOR(5,  Alignment);


 /* External Input Interrupt */
 EXCEPTION(0x0500, ExternalInput, do_IRQ, EXC_XFER_LITE)

 
 上述宏展开后得
 .align 5;
 ExternalInput:           //注意,发生中断后MSR[EE]会自动置0,屏蔽所有其他的中断

 NORMAL_EXCEPTION_PROLOG; //确立切入异常用的核心堆栈(用户进程 or 核心进程的核心栈)
                          //保存现场
 addi r3,r1,STACK_FRAME_OVERHEAD;//使r3存形参给do_IRQ(struct pt_regs *regs)
 EXC_XFER_LITE(0x0500, do_IRQ); //调用do_IRQ 然后调用ret_from_except返回


详细看do_IRQ的实现,文件arch/powerpc/kernel/irq.c

void do_IRQ(struct pt_regs *regs) // 这个pt_regs就是中断前的现场
        irq = ppc_md.get_irq();   // 读取IACK,获取硬件中断号,通过映射,获取逻辑中断号irq
        generic_handle_irq(irq);  // 这里的形参已经是逻辑中断号
        {   struct irq_desc *desc = irq_desc + irq;
            //下面将通过 desc->status 来防止同种中断的ISR嵌套执行
            status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
               status |= IRQ_PENDING; //来了中断就置IRQ_PENDING标识,很重要
            action = NULL;
            if (likely(!(status & (IRQ_DISABLED | IRQ_INPROGRESS)))) {
                action = desc->action;
                status &= ~IRQ_PENDING;  //我们要处理中断的话,就去掉IRQ_PENDING标记
                status |= IRQ_INPROGRESS;
            }
            desc->status = status;
            if (unlikely(!action))       //同种中断ISR不能嵌套执行,直接中断下半部
                goto out;
            for (;;) {
        I        rqreturn_t action_ret;
                spin_unlock(&desc->lock);
                action_ret = handle_IRQ_event(irq, action); //执行中断ISR
                spin_lock(&desc->lock);
                if (likely(!(desc->status & IRQ_PENDING)))
                    break;
                //代码到这里,状态中有IRQ_PENDING标记,则同种中断又来了,再次调用ISR
                desc->status &= ~IRQ_PENDING;
            }
            desc->status &= ~IRQ_INPROGRESS;
        }

        irq_exit(); //进入中断下半部,即软中断
,无视



查看handle_IRQ_event的实现,文件kernel/irq/handle.c中

handle_IRQ_event(int irq, struct irqaction *action)
    if (!(action->flags & IRQF_DISABLED))
       local_irq_enable_in_hardirq();  //执行ISR前,打开硬中断,使能中断嵌套

    do{
        action->handler(irq, action->dev_id) //执行我们注册的ISR
        action = action->next;
    }while(action);          //一个中断可以挂多个ISR,循环处理之

    local_irq_disable(); //执行ISR后,关闭硬中断,
后面判断irq_desc[].status需原子操作
                            //在中断下半部的do_softirq中,会把硬中断打开



    发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。
    同种中断的ISR不能嵌套执行。在一次do_IRQ中,某个中断ISR可能会再次被调用。Linux是通过一个IRQ_PENDING标记来判断ISR执行期间,又有中断发生。但同种中断发生了几次,是无法知道的(所谓的丢中断??),这就需要ISR来处理了。

    中断线程化,待看。。。


五、
中断统计

可以通过/proc/interrupts来查看系统中断的相关信息

-bash-3.2# cat /proc/interrupts
           CPU0
 41:         19   CPM2 SIU  Level     cpm_uart
 77:          0   OpenPIC   Level     enet_tx
 78:          0   OpenPIC   Level     enet_rx
 82:          0   OpenPIC   Level     enet_error
 91:         73   OpenPIC   Level     i2c-mpc
 94:         19   OpenPIC   Level     cpm2_cascade
118:   54450509   OpenPIC   Level     mcc
121:     209573   OpenPIC   Edge      eth2
BAD:        335
-bash-3.2#