从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 enabledME: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地址。IVORn 是一系列寄存器,有下面一张表: 有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.cvoid 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#