【转】中断处理

来源:互联网 发布:软件测试技术 同济大学 编辑:程序博客网 时间:2024/05/23 13:03

中断处理
《设备驱动及BSP开发指南》与《工程实践完全解析》笔记。

  转载请注明出处:http://blog.csdn.net/renpine/archive/2009/10/03/4628346.aspx

1、中断处理内部结构流程


中断处理内部结构图

http://p.blog.csdn.net/images/p_blog_csdn_net/renpine/595389/o_clip_image001%5B6%5D_thumb.gif

①、硬件设备向Kernel发送中断异常的代码,如果检测到这个中断异常,就会被Kernel层的异常处理所截获;

②、中断服务调度程序会调用OAL例程中的OEMInterruptDisable函数,这个函数会通知硬件在处理完这一中断前关闭特殊的中断,但其他的中断仍然处于开放状态;

③、中断服务例程ISR被调用以决定如何来处理这一中断;

④、Kernel接收到ISR的返回值(SYSINTR)以得知如何处理这一中断。它的响应结果之一是忽略掉这一中断不作处理(SYSINTR_NOP),另一结果是准备执行IST。

⑤、Kernel引发中断服务调度程序来唤醒中断服务线程去工作。IST是常规的Win32线程,一旦启动后,它会创建必要的EVENT然后等待该EVENT被激发。中断服务调度通过调用PulseEvent函数来激发EVENT,从而唤醒IST线程运行;

⑥、当唤醒以后,IST会对中断进行必要的处理如将数据移动到缓冲区或其他有意义的事;

⑦、如果需要的话,IST会借助于I/O支持例程访问硬件设备;

⑧、当IST处理完成后,它会调用InterruptDone函数通知Kernel;

⑨、Kernel调用OEMInterruptDone函数完成此次中断的处理过程,OAL例程通知硬件设备重新启用中断。

2、对ARM硬件产生中断到ISR之前的分析
对ARM较熟悉,就看看ARM的硬件产生中断到进入ISR之前的流程。一般而言, 硬件的异常产生后,CPU将跳转到0x00000000地址访问中断向量表(normal exception vectors),  但ARM920T / ARM9 / ARM10 系列的CPU支持把中断向量表放到高地址0xFFFF0000(high exception vectors)。该跳转地址的决定因素为协处理器的CP15:BI13。即CP15:BIT13 = 0时, 跳转到低地址; CP15:BIT13 = 1时, 跳转到高地址。此时的PC与MMU的根本没关系,因为PC只要指向了高地址处,那么MMU自身去解释而已,去找到向量表的位置。Wince的中断向量表只支持存放在高地址,从0x00000000~0x00001000这段是reserved的,留作其它用途。

对2410其中断映射到了ArmHigh区域内的虚拟地址0xFFFF0000,因此当硬件IRQ中断产生,迫使PC指向了IRQHandler处即0Xfff0018,在该处存放了一条跳转指令,到达异常向量表0Xffff03F8,如下图所示。

 http://p.blog.csdn.net/images/p_blog_csdn_net/renpine/595389/o_1111_thumb.png

内核中跟中断相关的工作主要有以下几个部分:

定义异常处理函数,其实现文件为:

C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/armtrap.s。

创建中断向量表,其实现文件为:

C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/exvector.s

中断向量的初始化,其实现在文件:

C:/WINCE600/PRIVATE/WINCEOS/COREOS/NK/KERNEL/ARM/mdarm.c

从IRQHandler里面直接就调用了OEMInterruptHandler函数。

3、ISR
由于外部IRQ中断数目,致使需要一个或者更多的ISR来处理,导致处理方式的不同,有了ISR的两种模型: 名称
 描述
 
单ISR硬件平台
 ISR为OEMInterruptHandler,仅此一个
 
多ISR硬件平台
 没有OEMInterruptHandler,必须通过HookInterrupt注册ISR
 


其实也没这么绝对,对X86来说有多个IRQ,使用了多ISR的方式,ARM平台其实也可以看作有两个IRQ的方式(一IRQ一FIQ),并且是混合使用的,里面也调用了NKCallIntChain去调用其它的ISRHandler,但具体的ISR调用的函数在OemGlobal全局变量里面: 处理器类型
  
X86
 OEMNMIHandler(系统非屏蔽中断)
 
ARM
 OEMInterruptHandler

OEMInterruptHandlerFIQ
 


分为静态与动态,静态清楚了再分析动态。

静态ISR:

即固定了物理中断号与逻辑中断号的映射,这在系统初始化时候的OALIntrInit里面实现,就两个数组:g_oalSysIntr2Irq和g_oalIrq2Intr,从字面意思即可知一个是从系统中断到Irq的转换,一个是Irq到系统中断的转换,其实就是将对应的中断号作为下标,得到该元素的值。

动态ISR:

即可安装的ISR,是在运行时候安装的,比较灵活,但必须清楚是一个IRQ可以绑定多个ISRHandler,最多有256个IRQ。查看NKCallIntChain函数的源码,可以看到里面使用了数组pIntChainTable[256],每个指针作为一个链表首指针,其类型为指针数组:

struct _INTCHAIN {

struct _INTCHAIN* pNext; //下一节点

PMODULE pMod;

//NKLoadKernelLibrary (giisr.dll),如果用默认giisr.dll

FARPROC pfnHandler; //ISRHandler,默认

DWORD dwInstData; //InstanceIndex

BYTE bIrq; //中断号

BYTE bPad[3];

};

再看看giisr.dll输出入口:

EXPORTS

ISRHandler

CreateInstance

DestroyInstance

IOControl

这里对各个函数大致介绍:

CreateInstance对g_Info[MAX_GIISR_INSTANCES]数组返回一个下标序号,这里数组的宏为32,也即最多每个安装中断有32个实例,DestroyInstance则相反。

IOControl在驱动里面都使用KernelLibIoControl来调用。两个case条件IOCTL_GIISR_PORTVALUE和IOCTL_GIISR_INFO,一般用后者,在外面将信息全部填好过后直接拷贝给g_Info[InstanceIndex]。

看一个info配置使用的的例子:

// Set up ISR handler

Info.SysIntr = dii.dwSysintr;

Info.CheckPort = TRUE;

Info.PortIsIO = (dwIOSpace) ? TRUE : FALSE;

Info.UseMaskReg = TRUE; //使用掩码寄存器

Info.PortAddr = PhysAddr + 0x0C; //判断IO端口地址

Info.PortSize = sizeof(DWORD);

Info.MaskAddr = PhysAddr + 0x10; //掩码地址

再将信息填入g_Info数组中:KernelLibIoControl(pPddObject->IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL),这就是使用注册过程。

ISRHandler通过IO端口或者内存映射端口读取状态,再与Mask与后得到是否是本中断号。例如IO端口类型,ISRHandler从Info.MaskAddr读取屏蔽值,再与Info.PortAddr地址的值进行与即知道是否是合适中断号了。

NKCallIntChain函数知道,这里面会直接调用ISRHandler,如若判定是该中断则将返回逻辑中断号,否则处理后就返回SYSINTR_CHAIN告知结束,OEMInterruptHandler将会继续后面其它处理。下面是OEMInterruptHandler调用到返回得到逻辑中断号的过程,模拟器里面是在这OEMInterruptHandler里面计算出IRQ的值:

 http://p.blog.csdn.net/images/p_blog_csdn_net/renpine/595389/o_22_thumb.png

4、如何使用中断
也分两种,静态与动态ISR的使用。

这里说明几个有点有时让人有点晕的点。逻辑中断号跟物理中断号的挂钩,为了灵活起见,一般做法是:把物理中断号(Irq)放入注册表项里面,在驱动初始化时再行读取,自动获取逻辑中断号,当然这是除开默认系统中断号之外的(SYSINTR_DEVICES+8之下的保留逻辑中断号)。比如模拟器的键盘,注册表里面是如此:

[HKEY_LOCAL_MACHINE/HARDWARE/DEVICEMAP/KEYBD]

    "DriverName"="kbdmouse.dll"

    "Irq"=dword:1

    "IOBase"=dword:B1600000

    "SSPBase"=dword:B1900000

       这里有了Irq,接下来在驱动里面将会读取该值,在IsrThreadProc里面有:

ReadRegDWORD( TEXT("HARDWARE//DEVICEMAP//KEYBD"), _T("Irq"), &dwIrq_Keybd );

       这样取得了Irq值dwIrq_Keybd,后面KernelIoControl就转换得到逻辑中断号g_dwSysIntr_Keybd,后面再用函数InterruptInitialize将g_dwSysIntr_Keybd与事件m_hevInterrupt挂钩,这样键盘驱动就能够响应硬件中断了。


静态ISR:

讲述这个的比较多,就给个别人的实例:

① 驱动初始化

pGPIOInfo->hGPIOEvent1 = CreateEvent(0,FALSE,FALSE,NULL);

其次创建一个处理事件的线程(IST)

pGPIOInfo->hGPIOThread1 = CreateThread(NULL, 0, GPIOFuncThread1, pGPIOInfo, 0, NULL);

然后使用InterruptInitialize让虚拟中断号pGPIOInfo->dwIntID1与创建的事件pGPIOInfo->hGPIOEvent1挂钩。

InterruptInitialize(pGPIOInfo->dwIntID1, pGPIOInfo->hGPIOEvent1, NULL, 0)

那么,当GPIO的中断到来,与GPIO虚拟中断挂钩的事件pGPIOInfo->hGPIOEvent1就会被设为Active。

② ISR到来

OEMInterruptHandler调用sysIntr = OALIntrTranslateIrq(irq),转换为逻辑中断号,再返回给异常处理,发送事件给IST;

③ IST的执行

线程中,WaitForSingleObject(pGPIOInfo->hGPIOEvent1, INFINITE);被唤醒,然后执行下一条指令,当中断处理结束以后,必须使用 InterruptDone(pGPIOInfo->dwIntID1);通知系统已经完成中断处理,那么下一次的中断到来,事件pGPIOInfo->hGPIOEvent1就才会再次被设为Active。

④ 驱动的卸载

驱动卸载时,需要释放申请的事件及线程CloseHandle(pGPIOInfo->hGPIOEvent1);CloseHandle(pGPIOInfo->hGPIOThread1)

动态ISR:

Giisr.dll并没有加入common.bib因此加入platform.bib中,主要步骤:

① 加入platform.bib giisr.dll $(_FLATRELEASEDIR)/giisr.dll NK SHK;

② 驱动初始化时,首先读取该驱动注册表项,并从该项下的"IsrDll"="giisr.dll","IsrHandler"="ISRHandler"与Irq=N等子键得到IRQ等信息,调用LoadIntChainHandler()函数以安装一个ISR;

③ 与静态IST相似,进行事件与逻辑中断号关联,这里初始化结束,IST等待中断触发。

④ IRQ到来,OAL的OEMInterruptHandler调用NKCallIntChain()函数执行ISR来获得逻辑中断号sysIntr;

⑤ 这里跟静态ISR处理完全相同了。

 

原创粉丝点击