前两天有一个朋友问了我s3c2410 WINCE挂起/唤醒的实现,这两天我研究了一下s3c2410的PowerButton驱动,对WINCE的中断流程又有了进一步的了解,下面我就写一下我的心得和大家交流一下。
首先和PowerButton驱动有关的文件为:
1)$(_WINCEROOT)\PLATFORM\smdk2410\DRIVERS\PWRBTN文件夹下面的三个文件pwrbtn2410.c、 pwrbtn2410.h和pwrbtn2410.def,这个PowerButton驱动是通过流驱动实现的,实现的过程很简单,下面会详细说明。
2)$(_WINCEROOT)\PLATFORM\smdk2410\INC\oalintr.h,定义非内核模式的中断号( non-kernel interrupt identifiers )
3)$(_WINCEROOT)\PLATFORM\smdk2410\KERNEL\HAL\cfw.c,这里面主要实现了 OEMInitInterrupts、OEMInterruptEnable、OEMInterruptDisable、 OEMInterruptDone这几个重要的中断函数。
4)$(_WINCEROOT)\PLATFORM\smdk2410\KERNEL\HAL\ARM\armint.c,这里主要实现了OEMInterruptHandler这个中断处理函数。
下面我简单分析一下中断处理过程。
a) 首先你为自己的硬件(键盘,按键等需要使用的中断)定义一个中断名称,比如这个电源按键就起了一个中断名称SYSINTR_POWER,然后在oalintr.h里面把它定义成SYSINTR_FIRMWARE+n的形式
比如: #define SYSINTR_POWER (SYSINTR_FIRMWARE+13)
n必须小于SYSINTR_MAXUMUM or SYSINTR_FIRMWARE+23
b)在cfw.c的OEMInitInterrupts中进行一些中断初始化工作,主要就是屏蔽所有中断,清除中断挂起等工作,代码我就不详细说明了,比 较简单。在OEMInterruptEnable(这个函数会被InterruptInitialize函数调用)函数中主要进行中断开启工作,当驱动使 用InterruptInitialize的时候(比如InterruptInitialize(SYSINTR_POWER, gPwrButtonIntrEvent, 0, 0))就会把SYSINTR_POWER中断号传入,然后开启EIN0这个中断,并且把SYSINTR_POWER中断和事件 gPwrButtonIntrEvent连接起来,代码如下:
INTERRUPTS_OFF(); //关闭所有中断
switch (idInt)
{......
case SYSINTR_POWER:
s2410INT->rSRCPND = BIT_EINT0;
// S3C2410X Developer Notice (page 4) warns against writing a 1 to a 0 bit in the INTPND register.
if (s2410INT->rINTPND & BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
s2410INT->rINTMSK &= ~BIT_EINT0;
s2410INT->rSRCPND = BIT_EINT2;
// S3C2410X Developer Notice (page 4) warns against writing a 1 to a 0 bit in the INTPND register.
if (s2410INT->rINTPND & BIT_EINT2) s2410INT->rINTPND = BIT_EINT2;
s2410INT->rINTMSK &= ~BIT_EINT2;
break;
.....
}
INTERRUPTS_ON();//开启所有中断
这个文件里面还实现了OEMInterruptDisable函数用来禁止中断,与PowerButton相关的函数如下:
INTERRUPTS_OFF();
switch (idInt)
{....
case SYSINTR_POWER:
s2410INT->rINTMSK |= BIT_EINT0;
s2410INT->rINTMSK |= BIT_EINT2;
break;
.....
}
INTERRUPTS_ON();
在这个文件中,还实现了OEMInterruptDone函数,做一些中断处理结束后的事情,当驱动调用InterruptDone时会把中断号传到这个函数来使用这个函数,与PowerButton相关的函数如下:
INTERRUPTS_OFF();
switch (idInt)
{...
case SYSINTR_POWER:
s2410INT->rSRCPND = BIT_EINT0;
if (s2410INT->rINTPND & BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
s2410INT->rINTMSK &= ~BIT_EINT0;
s2410INT->rSRCPND = BIT_EINT2;
if (s2410INT->rINTPND & BIT_EINT2) s2410INT->rINTPND = BIT_EINT2;
s2410INT->rINTMSK &= ~BIT_EINT2;
break;
}
INTERRUPTS_ON();
以上几个中断函数都相当重要,而且功能我也讲得很清楚了,大家应该理解了吧^_^。
c)在armint.c中主要实现了OEMInterruptHandler这个中断处理函数,当有硬件中断来的时候会进入这个处理函数,我们看看与PowerButton有关的代码:
else if (IntPendVal == INTSRC_EINT0) { // POWER BUTTON中断
s2410INT->rINTMSK |= BIT_EINT0;
s2410INT->rSRCPND = BIT_EINT0; /* Interrupt Clear */
if (s2410INT->rINTPND & BIT_EINT0) s2410INT->rINTPND = BIT_EINT0;
return(SYSINTR_POWER); //返回一个中断号通知系统发生了什么中断,系统通过这个中断产生一个事件//给IST使用。
在这里,我们把PowerButton和EINT0联系起来了,并且如果EINT0来了中断,就会返回系统一个中断号SYSINTR_POWER。
d)我们下面再来看看PowerButton驱动的实现。在pbut2410.c文件里,我们首先看看动态链接库的init实现:
PUBLIC DWORD
DSK_Init(DWORD dwContext)
{
…
do
{
gPwrButtonIntrThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) PBT_IntrThread, 0, 0, &IDThread);//创建了一个PBT_IntrThread线程,这个就是PowerButton的IST
if (gPwrButtonIntrThread == 0)
{
break;
}
} while (0);
…
}
下面我们看看PBT_IntrThread的实现:
DWORD
PBT_IntrThread(PVOID pArg)
{
PBT_InitializeAddresses();//得到EINI0口的虚拟地址
PBT_EnableInterrupt(); //使能EINI0口中断
gPwrButtonIntrEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建一个事件//PowerButton事件
if (!(InterruptInitialize(SYSINTR_POWER, gPwrButtonIntrEvent, 0, 0)))
//通知系统使能SYSINTR_POWER这个中断,并且当这个中断产生时产生一个gPwrButtonIntrEvent事件,//第一个参数为与这个IST连接的中断ID,第二个参数为中断产生是产生的事件
{
RETAILMSG(1, (TEXT("::: SYSINTR_POWER Init Fail\r\n")));
}
while (1)
{
WaitForSingleObject(gPwrButtonIntrEvent, INFINITE);//等待中断发生
if (gOffFlag == FALSE)
{
if (PBT_IsPushed()) /* To Filter Noise *///判断是否是噪声
{
Sleep(200);
if (PBT_IsPushed())
{
}
else
{//如果不是噪声则:
#if (WINCEOSVER >= 400)
if(gpfnSetSystemPowerState != NULL)
{
gpfnSetSystemPowerState(NULL, POWER_STATE_SUSPEND, POWER_FORCE);
}
else
{
PowerOffSystem();//调用PowerOffSystem函数,在这//个函数里面又会调用OEMPowerOff函数,这个函数在power.c里
}
#else
PowerOffSystem();
#endif
DriverSleep(0, FALSE);
}
}
InterruptDone(SYSINTR_POWER);//通知系统调用OEMInterruptDone
}
}
}
在看看一些函数
PRIVATE VOID
PBT_EnableInterrupt(VOID)
{
v_pIOPregs->rGPFCON &= ~(0x3 << 0); /* 设置GPF0) 为 EINT0 */
v_pIOPregs->rGPFCON |= (0x2 << 0);
v_pIOPregs->rEXTINT0 &= ~(0x7 << 0); /* 配置EINT0为下降沿模式 */
v_pIOPregs->rEXTINT0 |= (0x2 << 0);
}
PRIVATE BOOL
PBT_IsPushed(VOID)
{//判断GPF0是否被按下
return ((v_pIOPregs->rGPFDAT & (1 << 0)) ? FALSE : TRUE);
}
PRIVATE BOOL
PBT_InitializeAddresses(VOID)
{//分配EINT0的虚拟地址供驱动使用
BOOL RetValue = TRUE;
/* IO Register Allocation */
v_pIOPregs = (volatile IOPreg *)VirtualAlloc(0, sizeof(IOPreg), MEM_RESERVE, PAGE_NOACCESS);
if (v_pIOPregs == NULL)
{
ERRORMSG(1,(TEXT("For IOPregs : VirtualAlloc failed!\r\n")));
RetValue = FALSE;
}
else
{
if (!VirtualCopy((PVOID)v_pIOPregs, (PVOID)(IOP_BASE), sizeof(IOPreg), PAGE_READWRITE | PAGE_NOCACHE))
{
ERRORMSG(1,(TEXT("For IOPregs: VirtualCopy failed!\r\n")));
RetValue = FALSE;
}
}
if (!RetValue)
{
// RETAILMSG (1, (TEXT("::: PBT_InitializeAddresses - Fail!!\r\n") ));
if (v_pIOPregs)
{
VirtualFree((PVOID) v_pIOPregs, 0, MEM_RELEASE);
}
v_pIOPregs = NULL;
}
else RETAILMSG (1, (TEXT("::: PBT_InitializeAddresses - Success\r\n") ));
return(RetValue);
}
总结:
从上面PowerButton这个驱动我们就能把WINCE的中断处理过程了解清楚,基本的过程是首先在oalinitr.h中把中断ID define成SYSINTR_FIRMWARE+N的形式,然后
OEMInitInterrupts(cfw.c) -> OEMInterruptEnable(cfw.c) -> 硬件中断到达 -> OEMInterruptHandler(armint.c) -> 自己写的中断服务线程(IST)-> OEMInterruptDone(cfw.c)
其中IST的流程一般为:
创建一个事件CreateEvent -> InterruptInitialize(SYSINTR_XXXX, XXXXXEvent, 0, 0) 把中断ID和IST联系起来,并且把中断ID和事件XXXXXEvent联系起来 -> WaitForSingleObject(XXXXXEvent, INFINITE)等待这个事件的产生,由于事件和中断联系起来了,实际就是等待中断的产生 -> 实际的中断处理过程 -> InterruptDone(SYSINTR_ XXXX) 通过这个函数调用OEMInterruptDone