ARM中断分析之四:WinCE的OAL层的中断分析

    从前面的介绍,我们知道了裸机中断处理的流程、WINCE驱动的中断处理,但是,WINCE底层是怎么处理中断的呢?这里就是介绍WinCE系统的OAL层的中断处理。它和裸机的处理总体一样,只是实现细节方面有点区别,具体流程如下:

一、在OAL层的初始化函数,在系统启动的过程中被调用,如下所示:

BOOL OALIntrInit()
{
BOOL rc = FALSE;
// Initialize interrupt mapping
OALIntrMapInit();
// First get uncached virtual addresses
g_pIntrRegs = (S3C2410X_INTR_REG*)OALPAtoVA(
S3C2410X_BASE_REG_PA_INTR, FALSE
);
g_pPortRegs = (S3C2410X_IOPORT_REG*)OALPAtoVA(
S3C2410X_BASE_REG_PA_IOPORT, FALSE
);
// Mask and clear external interrupts
OUTREG32(&g_pPortRegs->EINTMASK, 0xFFFFFFFF);
OUTREG32(&g_pPortRegs->EINTPEND, 0xFFFFFFFF);
// Mask and clear internal interrupts
OUTREG32(&g_pIntrRegs->INTMSK, 0xFFFFFFFF);
OUTREG32(&g_pIntrRegs->SRCPND, 0xFFFFFFFF);
// S3C2410X developer notice (page 4) warns against writing a 1 to any
// 0 bit field in the INTPND register. Instead we'll write the INTPND
// value itself.
OUTREG32(&g_pIntrRegs->INTPND, INREG32(&g_pIntrRegs->INTPND));
// Unmask the system tick timer interrupt
CLRREG32(&g_pIntrRegs->INTMSK, 1 << IRQ_TIMER4);
// Give BSP change to initialize subordinate controller
rc = BSPIntrInit(); // 配置某些外设的中断配置,比如以太网,但这不是必须的,也可以在以太网驱动内实现。
return rc;
}

实际上,上面这部份的初始化是超简单的,总结为完成的事情如下:
1、外设引脚功能的配置及中断触发方式,图中所示是:GPFCON、EXTINT0
2、中断mask码、优先级、模式的设定,包括子级掩码、主级掩码,图中所示是:EINTMASK、MASK、MODE、Priority。

二、中断服务程序,它会在硬件IRQ产生时被调用
中断服务程序是IRQ中断的入口点,代码如下所示:
ULONG OEMInterruptHandler(ULONG ra)
{
UINT32 sysIntr = SYSINTR_NOP;
UINT32 irq, irq2 = OAL_INTR_IRQ_UNDEFINED, mask;
fInterruptFlag = TRUE; // Signal OemIdle to come out of idle.
// Get pending interrupt(s)
irq = INREG32(&g_pIntrRegs->INTOFFSET);
// System timer interrupt? 系统时间中断?
if (irq == IRQ_TIMER4) {
// Clear the interrupt
OUTREG32(&g_pIntrRegs->SRCPND, 1 << IRQ_TIMER4);
OUTREG32(&g_pIntrRegs->INTPND, 1 << IRQ_TIMER4);
// Rest is on timer interrupt handler
sysIntr = OALTimerIntrHandler();
}
// Profiling timer interrupt?
else if (irq == IRQ_TIMER2)
{
// Mask and clear the interrupt.
mask = 1 << irq;
SETREG32(&g_pIntrRegs->INTMSK, mask);
OUTREG32(&g_pIntrRegs->SRCPND, mask);
OUTREG32(&g_pIntrRegs->INTPND, mask);
// The rest is up to the profiling interrupt handler (if profiling
// is enabled).
//
if (g_pProfilerISR)
{
sysIntr = g_pProfilerISR(ra);
}
}
else
{
if (irq == IRQ_EINT4_7 || irq == IRQ_EINT8_23) {
// Find external interrupt number
mask = INREG32(&g_pPortRegs->EINTPEND);
mask &= ~INREG32(&g_pPortRegs->EINTMASK); // Find the effect interrupt number
/*
// g_i[i], g_i[i] ^(g_i[i]-1), g_i[i]^(g_i[i]-1)) >> 5
1: 1 : 0
2: 3 : 0
4: 7 : 0
8: 15 : 0
16: 31 : 0
32: 63 : 1
64: 127 : 3
128: 255 : 7
256: 511 : 15
512: 1023 : 31
1024: 2047 : 63
*/
mask = (mask ^ (mask - 1)) >> 5;
irq2 = IRQ_EINT4;
while (mask != 0) {
mask >>= 1;
irq2++;
}
// Mask and clear interrupt
mask = 1 << (irq2 - IRQ_EINT4 + 4);
SETREG32(&g_pPortRegs->EINTMASK, mask);
OUTREG32(&g_pPortRegs->EINTPEND, mask);
// calculate mask for primary interrupt
mask = 1 << irq;
// update irq
irq = irq2;
} else {
// Mask the interrupt
mask = 1 << irq;
SETREG32(&g_pIntrRegs->INTMSK, mask);
}
// clear primary interrupt
OUTREG32(&g_pIntrRegs->SRCPND, mask);
OUTREG32(&g_pIntrRegs->INTPND, mask);
// First find if IRQ is claimed by chain
sysIntr = NKCallIntChain((UCHAR)irq);
if (sysIntr == SYSINTR_CHAIN || !NKIsSysIntrValid(sysIntr)) {
// IRQ wasn't claimed, use static mapping
sysIntr = OALIntrTranslateIrq(irq);
}
// unmask interrupts in case it's NOP or invalid
if (SYSINTR_NOP == sysIntr) {
if (OAL_INTR_IRQ_UNDEFINED == irq2) {
// Unmask the primary interrupt
CLRREG32(&g_pIntrRegs->INTMSK, mask);
} else {
// Unmask the external interrupt
mask = 1 << (irq2 - IRQ_EINT4 + 4);
CLRREG32(&g_pPortRegs->EINTMASK, mask);
}
}
}
return sysIntr;
}
从上面代码总结出,中断服务程序主要做的事情是:
1、从INTPND得知主级中断中的中断号。
2、从EINTPEND得知次级中断中的中断号。
3、置位或者清零中断控制器的寄存器
·置位次级的mask
·置位主级的mask
·清理次级中断PND,图中所示为:EINTPEND
·清理主级中断PND,图中所示为:SRCPND
4、最后返回一个中断号sysIntr,这个是逻辑上面的中断号,和WINCE驱动的逻辑中断号相对应。

三、OALIntrDoneIrqs函数
WinCE驱动在处理中断完毕后,会调用InterruptDone函数,此函数会调用OALIntrDoneIrqs函数,OALIntrDoneIrqs函数内容如下:
VOID OALIntrDoneIrqs(UINT32 count, const UINT32 *pIrqs)
{
UINT32 i, mask, irq;
for (i = 0; i < count; i++) {
// Depending on IRQ number use internal or external mask register
if (irq <= IRQ_ADC) {
// Use internal interrupt mask register
mask = 1 << irq;
CLRREG32(&g_pIntrRegs->INTMSK, mask);
} else if (irq <= IRQ_EINT23) {
// Use external mask register
mask = 1 << (irq - IRQ_EINT4 + 4);
CLRREG32(&g_pPortRegs->EINTMASK, mask);
}
}
}
从上面的代码可知,OALIntrDoneIrqs所做的事情是在中断处理完毕之后的清零主级的mask或者清零次级的mask。

现在回顾一下整理,在OEMInterruptHandler函数内清零PND,这样做是有好处的。不然,在驱动内实现的话,就要严格按照步骤来清零,即:先清零PND,然后再清零外设PND。为什么要先清零PND,然后再清零外设PND,可以参考我写的另一篇文章“应该怎么样清理中断的PND位?”。

结论是,WinCE在默认情况下,适合边缘触发的中断,如果需要处理电平触发的话,需要同时修改OEMInterruptHandler函数和OALIntrDoneIrqs函数,在OEMInterruptHandler函数内不要清零PND而在OALIntrDoneIrqs内清零PND,这样才能避免重复处理同一个中断。

总之,WINCE的OAL层和中断相关的函数是:
ULONG OEMInterruptHandler(ULONG ra); //中断服务函数

void OEMInterruptDone( DWORD idInt ); //中断处理完毕的函数
void OEMInterruptDisable( DWORD idInt ); //禁用中断的函数
BOOL OEMInterruptEnable( DWORD idInt, LPVOID pvData, DWORD cbData ); //启用中断的函数
当然,还有中断初始化函数,但那是非OEM函数,即自己定义。以上几个函数的源码,可以参考相应的BSP包。

仅管OEMInterruptDone和OEMInterruptEnable,在功能上都是把指定IRQ的MASK给清零,但是OEMInterruptEnable需要多做一件事情,它需要把前一级的相应的PND寄存器给清零,意思是:清除以前发生的中断,现在开始处理新的中断。

OEMInterruptDone会调用OALIntrDoneIrqs,OALIntrDoneIrqs调用BSPIntrDoneIrq,主要的功能处理都在OALIntrDoneIrqs函数内实现。 

 

 

 

 

 

 

posted @ 2012-05-12 12:30  yfm1202  阅读(1677)  评论(0编辑  收藏  举报