中断是硬件与软件打交道的重要方法,因此,大多数驱动程序都涉及到对中断的处理,本文就驱动程序的开发人员以及BSP的开发人员的角度,来谈谈Windows CE中中断的处理过程。
如果一个驱动程序要处理一个中断,那么驱动程序需要首先建立一个事件,可以使用CreateEvent函数,然后调用InterruptInitialize将该事件与中断号绑定,这一步就会使能该中断,OAL中的OEMInerrupteEnable就会被调用,如果该函数不返回true的话,InterruptInitialize就会失败。然后驱动程序中的IST就可以使用WaitForSingleObject函数来等待中断的发生。
当一个硬件中断发生之后,操作系统陷入异常,中断向量指示进入CE的异常处理程序,该异常处理程序然后调用OAL的OEMInterruptHandler函数,该函数检测硬件之后,将硬件中断转换为软件的中断号,返回给系统。该中断号就是上面提到的InterruptInitialize中使用的那个中断号。系统得到该中断号之后,就会找到该中断号对应的事件,并唤醒等待相应事件的线程(IST),然后IST就可以在用户态进行中断处理。处理完成之后,IST需要调用InterruptDone来告诉操作系统中断处理结束,操作系统再次调用OAL中的OEMInterruptDone函数,最后完成中断的处理。
在上面的描述中,驱动程序需要使用的是InterruptInitialize,WaitForSingleObject和InterruptDone三个函数,而BSP开发者需要主要考虑的是OEMInerrupteEnable,OEMInterruptHandler,OEMInterruptDone三个函数。当然,这是上面提到的一些函数,实际上BSP开发者在中断部分还需要考虑Init以及Disable等等函数,这里就不再讨论了。
很好的一篇文章,对驱动开发来说很有帮助!
请问这两个函数KernelIoControl和InterruptInitialize函数的区别是什么呢??
个人理解:先用KernelIoControl申请中断号
然后利用InterruptInitialize把 (从KernelIoControl申请的)中断号与事件相关联
主要作用是使能waitForSingleObject中挂起的事件
KernelIoControl这个函数有很多人都说不能申请成功
那么是否还有另外一种所谓的静态申请中断号的方法
在oalintr.h中添加中断的宏定义
然后…
1 2个函数没有任何关系;KernelIoControl是驱动/应用程序和kernel通讯的一个接口函数,具体的调用意义是看该函数的IOCTRL是什么以及kernel中对应的处理函数(ioctrl table);
2 关于中断,WINCE 5之前都是静态中断,从CE 5开始中断申请分为动态申请和静态分配2种;楼主用的KernelIoControl+IOCTL_HAL_REQUEST_SYSINTR就是动态申请的方法;静态分配其实很简单,在OEMInterruptHandler里直接switch case即可;具体实现很多,各家BSP不一样,但是道理都是如此;
其实,动态申请就是由系统维护一个table,使得OEM允许其他厂商的驱动以安装的形式动态加到这个table里。作为OEM自己,其实维护一个静态的中断分配就可以了,驱动完全不用动态申请。只不过很多参考BSP用了这个形式,导致大家都在动态申请。
S3C2410的内核是ARM920T,所以,这里先介绍一下ARM920T的异常。ARM920T中有一个当前程序状态寄存器(CPSR),其中BIT6和BIT7分别控制FIQ和IRQ的使能与否。大家经常说的开中断和关中断,就是指的设置这两个BIT。
ARM体系的异常中断如下图所示:
可以看到,ARM920T中一共有7中异常模式,如果同一时刻有多个异常发生,系统则通过优先级顺序来决定处理其中的哪一个异常。他们之间的优先级顺序从高到低依次是:
1. Reset复位
2. Data Abort数据访问中止
3. FIQ 快速中断请求
4. IRQ 外部中断请求
5. 指令预取中止
6. 未定义的指令和软件中断
当系统发生异常时,PC指针将跳转到相应的异常中断处理程序处。异常中断和其处理程序之间的对应关系被称为异常向量表,就是通常所说的中断向量表。一般情况下,它存放在低地址(0x0),但在WinCE中,该表存放在高地址(0xffff0000)。
可以看到,S3C2410中跟中断密切相关的寄存器主要有以下几个:
SUBSRCPND(二级源待决寄存器):硬件产生中断后,该寄存器的相应位被置1;
SUBMASK(二级屏蔽寄存器):若该寄存器的对应位为1,则屏蔽该中断,不再往前提交;
SRCPND(源待决寄存器):如果是二级中断源产生了中断,当SUBSRCPND和SUBMASK满足条件时,该寄存器的相应位被置1,或者由一级中断源直接引起该寄存器的对应位置1;
MASK(一级屏蔽寄存器):如果该寄存器的对应位为1,则屏蔽该中断,不再往前提交;
MODE(中断模式寄存器):决定中断是FIQ模式还是IRQ模式,系统中只能有一个FIQ中断。若当前中断为FIQ模式,则产生一个FIQ异常,CPU进入FIQ异常处理程序。
PRIORITY(优先级控制寄存器):控制各中断源的优先级。当有多个中断源同时发出请求时由优先级最高的中断源最终产生IRQ。
INTPND(中断待决寄存器):当SRCPND某一位被置1,且没有被屏蔽,则该寄存器的相应位也被置1,同时产生一个IRQ异常,CPU进入IRQ异常处理程序。
IRQ异常处理程序中,需要清除SRCPND、INTPND。清除SRCPND和INTPND的方法比较特殊,并不是往对应的位写0,相反,应该往对应的位写1。一般是将其值读取出来,再写进去,以完成清除SRCPND和INTPND的工作。
除了以上几个寄存器外,2410还有一个INTOFFSET寄存器,用来表明当前是哪一个中断请求处理。WinCE的OEMInterruptHandler()函数,就是根据其值来判断当前是哪一个中断发出请求。该寄存器在清除SRCPND和INTPND时,被自动复位。所以,代码中不必对其进行处理。
如果中断源是EINT4-23,则还需处理EXTINTn、EINTFLT、EINTMASK和EINTPEND等几个寄存器。另外,由于2410的中断引脚一般与IO复用,所以在使用特定的外部中断之前,需要设置相应的GPIO,使其工作在中断模式下。
WinCE6.0中的中断处理过程如下图所示:
可以看到,整个处理过程分为四层,分别是硬件、内核、OAL和驱动。硬件产生一个IRQ,CPU进入中断服务程序,调用OAL中的OEMInterruptHandler()函数,根据IRQ返回一个SYSINTR,内核根据该SYSINTR,设置一个事件,驱动中捕获到该事件,执行相应的处理程序,完成处理后,调用InterruptDone(),通知CPU中断处理完成。
这里说明一下SYSINTR和IRQ的概念。IRQ一般被认为是物理中断号,由硬件决定。SYSINTR是逻辑中断号,一般跟IRQ一一对应。这种对应关系可以在OAL中静态建立,一般通过函数OALIntrStaticTranslate()实现,静态映射的IRQ一般是MCU内部的中断源,如USB Host。为了提高驱动的可移植性,通常采用动态映射的方式,如网卡驱动。不同的硬件平台,可能使用不同的外部中断供网卡使用,通过动态映射的方式,只需修改注册表中的相应键值就能完成驱动的移植,而无须修改代码。驱动中动态映射IRQ的方法是调用函数KernelIoControl(),第一个参数为IOCTL_HAL_REQUEST_SYSINTR,传入物理中断号IRQ,正确返回后,会产生一个SYSINTR,最终完成动态转换的是函数OALIntrRequestSysIntr()。IRQ和SYSINTR的映射关系在文件C:/WINCE600/PLATFORM/COMMON/SRC/COMMON/INTR/BASE/map.c中实现。
驱动中使用中断的典型过程如下:
1. 初始化GPIO,以及相应中断寄存器,配置中断的工作模式。
2. 建立一个SYSINTR与IRQ对应,调用函数KernelIoControl()。
3. 创建一个事件与SYSINTR关联,调用函数CreateEvent()。
4. 创建一个线程,在线程中等待创建的事件,调用函数WaitForSingleObject()。
5. 完成处理后,通知内核本次中断处理结束,调用函数InterruptDone()。
OAL层跟中断相关的有如下几个函数:
OALIntrInit():初始化中断寄存器及相应的GPIO,可建立静态的IRQ到SYSINTR的映射关系。
OALIntrRequestIrqs():获取设备的IRQ号,如通过IO Address获取该设备对应的IRQ。
OALIntrEnableIrqs():使能中断源,主要完成清除中断屏蔽寄存器和中断待决寄存器。
OALIntrDisableIrqs():关闭中断源,通过设置中断屏蔽寄存器的相应位以屏蔽中断源。
OALIntrDoneIrqs():清除中断屏蔽寄存器和中断待决寄存器使MCU能处理下一次中断。
OEMInterruptHandler():将IRQ号转换为SYSINTR,内核的中断服务程序将根据此值设定特定的事件。
内核中跟中断相关的工作主要有以下几个部分:
1. 定义异常处理函数,其实现文件为C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s。
2. 创建中断向量表,其实现文件为C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/exvector.s
3. 中断向量的初始化,其实现在文件C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/mdarm.c中,其中的代码表明WinCE的中断向量表在高端(0xffff0000)。
http://topic.csdn.net/u/20081224/23/1fc78817-4067-4cac-aad8-c0567c26af1a.html
系统启动:ARMInit->OEMInit->OALIntrInit->OALIntrMapInit->(#ifdef OAL_BSP_CALLBACKS--BSPIntrInit->OALIntrStaticTranslate) ;
中断初始化:InterruptInitialize(驱动程序)->OEMInterruptEnable(微软实现)->OALIntrTranslateSysIntr(数据校验,把逻辑中断号变成IRQ号)->OALIntrEnableIrqs(OEM实现,通过IRQ号使能自定义的中断)->BSPIntrRequestIrqs->BSPIntrEnableIrq;
ISR实现:OEMInterruptHandler->(NKCallIntrChain)/OALIntrTranslateIrq 返回发生中断的IRQ对应的逻辑中断;
中断结束:InterruptDone->OEMInterruptDone->OALIntrDoneIrqs(BSPIntrDoneIrq);
------------------------------------------------
1.在具体中断初始化过程中,IST线程中调用InterruptInitialize(DWORD idInt,,,),进而调用OEMInterruptEnable(DWORD idInt,,):
BOOL OEMInterruptEnable(DWORD sysIntr, LPVOID pvData, DWORD cbData) { BOOL rc = FALSE; const UINT32 * pIrqs; UINT32 count; OALMSG(OAL_INTR&& OAL_VERBOSE, (L"+OEMInterruptEnable(%d, 0x%x, %d)/r/n" , sysIntr, pvData, cbData )); // SYSINTR_VMINI & SYSINTR_TIMING are special cases if (sysIntr == SYSINTR_VMINI || sysIntr == SYSINTR_TIMING) { rc = TRUE; goto cleanUp; } // Obtain the SYSINTR's underlying IRQ number [color=#FF0000] if (!OALIntrTranslateSysIntr(sysIntr, &count, &pIrqs)) {[/color] // Indicate invalid SysIntr OALMSG(OAL_ERROR, ( L"ERROR: OEMInterruptEnable: IRQs are undefined for SysIntr %d/r/n" , sysIntr )); goto cleanUp; } // Enable the interrupt rc = [color=#FF0000]OALIntrEnableIrqs(count, pIrqs);[/ color] cleanUp: OALMSG(OAL_INTR&&OAL_VERBOSE, (L"-OEMInterruptEnable(rc = 1)/r/n" )); return rc; }
再调用OALIntrTranslateSysIntr(DWORD idInt,,)函数:
BOOL OALIntrTranslateSysIntr(UINT32 [color =#FF0000]sysIntr[/color], UINT32 *pCount, const UINT32 **ppIrqs) //第一个参数是输入,后两个是输出参数 { BOOL rc; OALMSG(OAL_INTR&&OAL_VERBOSE, (L"+OALTranslateSysIntr(%d)/r/n" , sysIntr)); // Valid SYSINTR? if (sysIntr >= SYSINTR_MAXIMUM) { rc = FALSE; goto cleanUp; } *pCount = 1 ; *ppIrqs = &g_oalSysIntr2Irq[[color=#FF0000]sysIntr[/color]];//通过g_oalSysIntr2Irq数组转换得到IRQ号 rc = TRUE; cleanUp: OALMSG(OAL_INTR&&OAL_VERBOSE, (L"-OALTranslateSysIntr(rc = %d)/r/n" , rc)); return rc; }
2.OALIntrTranslateSysIntr()函数又调用OALIntrEnableIrqs()函数:
BOOL OALIntrEnableIrqs(UINT32 count, const UINT32 *[color=#FF0000]pIrqs[/ color]) { BOOL rc = TRUE; UINT32 i, mask, irq; OALMSG(OAL_INTR&& OAL_FUNC, ( L"+OALIntrEnableIrqs(%d, 0x%08x)/r/n", count, [color=#FF0000]pIrqs[/ color] )); for (i = 0; i < count; i++ ) { #ifndef OAL_BSP_CALLBACKS [color=#FF0000]irq = pIrqs[i];[/ color] #else // Give BSP chance to enable irq on subordinate interrupt controller [color=#FF0000]irq = BSPIntrEnableIrq(pIrqs[i]);//在未使用到多级中断控制器下,未作实际操作,直接返回pIrqs[i][/color] #endif if (irq == OAL_INTR_IRQ_UNDEFINED) continue ; // Depending on IRQ number use internal or external mask register if (irq <= IRQ_ADC) { // Use interrupt mask register CLRREG32(&g_pIntrRegs->INTMSK, 1 << irq); } else if (irq <= IRQ_EINT7) { // Use external mask register CLRREG32(&g_pIntrRegs->INTMSK, 1 << IRQ_EINT4_7); //IRQ_EINT4_7~4 CLRREG32(&g_pPortRegs->EINTMASK, 1 << (irq - IRQ_EINT4 + 4)); //IRQ_EINT4~32 } else if (irq <= IRQ_EINT23) { // Use external mask register mask = 1 << (irq - IRQ_EINT4 + 4 ); OUTREG32(&g_pPortRegs-> EINTPEND, mask); CLRREG32(&g_pPortRegs-> EINTMASK, mask); mask = 1 << IRQ_EINT8_23; if ((INREG32(&g_pIntrRegs->INTPND) & mask) != 0 ) { OUTREG32(&g_pIntrRegs-> INTPND, mask); } CLRREG32( &g_pIntrRegs->INTMSK, 1 << IRQ_EINT8_23); } else { rc = FALSE; } } OALMSG(OAL_INTR&&OAL_FUNC, (L"-OALIntrEnableIrqs(rc = %d)/r/n" , rc)); return rc; }
这里就有疑问了,通过OALIntrTranslateSysIntr函数转换sysIntr成为IRQ号,但是这个IRQ号只拿来做了OAL调试输出信息的参数,后面的使能中断部分并没有使用到这个IRQ号啊?
而是使用了pIrqs[i]这个数组,OALIntrRequestIrqs()和BSPIntrRequestIrqs()【功能是:获取设备的IRQ号(如通过IO Address获取该设备对应的IRQ)】,但是在中断流程中没有发现对这两个函数的调用啊? 何解啊?
这个静态中断只初始化了网卡的一个中断而已。5.0下其他中断全部用kernelIoControl来动态申请。这样做比较好。你可以随便找个5.0 2440BSP的驱动看看。就一切OK了。应该5.0中有两种中断方式,动态和静态。