NonMaskableInterrupt(NMI)不可屏蔽中断学习总结CRBNMI.C⾥⾯有function
VOID CRBGpi8SmiHandler (
IN EFI_HANDLE DispatchHandle,
IN EFI_SMM_GPI_DISPATCH_CONTEXT *DispatchContext )
{
// Porting if needed
}
为什么通过相应的GPIO会产⽣⼀个SMI信号?
l The corresponding GPI must be routed in the GPI_ROUT register to cau an SMI.
l MmPci32(LPC_BUS, LPC_DEVICE, LPC_FUNC, ICH_REG_LPC_GPI_ROUT) |= 1 << (2 * GpiNo);
将配对的GPI在GPI_POUT中设置,SMI信号就可以rout到相应的GPIO
软件触发NMI的流程:
在SMI的Handle处理程序中设置相应的寄存器来触发NMI,流程如下(参考SBSMI.C⾥的SBGpi14SmiHandler() ):
TCO_BASE_ADDRESS=0x0460
ICH_IOREG_TCO1_CNT=0x08
ICH_IOREG_TCO1_STS=0x04
ALIASED_NMI_EN_PORT=0x74
NMI_EN_PORT=0x70
1、 Read the NMI2SMI_EN bit, save it for future restore:
Save_Nmi2Smi_En = IoRead8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1);
2、 Set the NMI2SMI_EN bit to 0
Data8 = (UINT8)(Save_Nmi2Smi_En & 0xFD);
IoWrite8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1, Data8);
3、 Enable NMI_EN
Save_Port70 = IoRead8(ALIASED_NMI_EN_PORT);
Data8 = (UINT8)(Save_Port70 & 0x7F);
IoWrite8(NMI_EN_PORT, Data8);
4、 Set NMI_NOW = 1
Data8 = IoRead8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1);
Data8 = (UINT8) (Data8 | 0x01);
IoWrite8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1, Data8);
5、 Clear NMI_NOW = 0 by writing 1 to NMI_NOW bit
Data8 = IoRead8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1);
Data8 = (UINT8) (Data8 | 0x01);
IoWrite8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1, Data8);
6、 Restore NMI2SMI_EN
IoWrite8(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_CNT + 1, Save_Nmi2Smi_En);
7、 Clear the MCHSERR_STS bit, bit 12
Data16 = IoRead16(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_STS);
Data16 = (UINT8) (Data16 | 0x1000);
IoWrite16(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_STS, Data16);
8、 Clear the NMI2SMI_STS bit if t
Data16 = IoRead16(TCO_BASE_ADDRESS + ICH_IOREG_TCO1_STS);
if (Data16 & 0x0001) {
/
/ check port 0x61
Data8 = IoRead8(NMI_SC_PORT);
if (Data8 & 0x80) {
Data8 = IoRead8(NMI_SC_PORT);
Data8 = (UINT8) (Data8 | 0x04);
Data8 = (UINT8) (Data8 & 0x0F);
IoWrite8 (NMI_SC_PORT, Data8);
Data8 = (UINT8) (Data8 & 0x0B);
IoWrite8 (NMI_SC_PORT, Data8);
}
}
9、 Restore NMI_EN
IoWrite8(NMI_EN_PORT, Save_Port70);
NMI_EN是从0x74端⼝读,从0x70端⼝写
对怎样执⾏NMI中断处理程序的理解
当NMI被触发以后,interrupt controller(8259)会将NMI的中断向量(vector=02h)发送给CPU,CPU就会根据vector在中断向量表(interrupt table)中去寻找对应的NMI处理程序。在source code中找到这么⼀段代码:
PUBLIC CsmOemInterrupts
PUBLIC CsmOemInterruptsEnd
CsmOemInterrupts LABEL WORD
mBODY_ID_AND_TBL_CSM_ENTRY_NEAR 00002h, OEMINT2
mEND_TBL_CSM CsmOemInterrupts
mBODY_ID_AND_TBL_CSM_ENTRY_NEAR和mEND_TBL_CSM都是宏定义,它们表⽰在memory中分配了⼀段interrupt table的地址空间,范围是0000h——FFFFh。OEMINT2是处理NMI中断的proc,被定义在interrupt table中的02号位置处。这段code就表明了NMI的中断处理程序是OEMINT2。OEMINT2通过ELINK在CsmOemInterrupts执⾏后被调⽤,CsmOemInterrupts本⾝也是通过ELINK被调⽤,但不清楚何时被调⽤,source code ⾥⾯定义的调⽤顺序是InvokeOrder = TableFunction,找不到相关的解释,不知是否可理解成在CPU接到中断请求,准备查找中断向量表时调⽤。
硬件触发NMI的流程:
1、将对应的GPIO(GPIO9)设置成GPI
2、将GPIO9的寄存器GPI_INV设置成0(上升沿触发)或1(下降沿触发)
3、将GPI_ROUT寄存器routed到可触发GPIO9的NMI的功能
4、将GPI_NMI_EN寄存器中GPIO9匹配的位置1
5、将GPI_NMI_STS寄存器中GPIO9匹配的位写1清零
CPU怎样知道SMI被触发?
以产⽣GPIO14上的SMI为例。在DXE阶段,会为SMI做好准备,主要是在InSmmFunction()做两个动作,⼀是pBS->LocateProtocol(),⼆是pGpiDispatch->Register(),它的函数原形是EfiSmmSwRegister(),其作⽤就是注册⼀个SMI,具体过程是通过增加⼀个链表的节点来实现。注册好的SMI节点会存放在SMRAM这段地址空间中(30000h+8000h),SMRAM这段空间在⾮SMI模式下时当做常规内存使⽤,在SMI模式下,CPU就会在SMRAM这段空间中取得SMI的资源。注册了以后,CPU是怎样知道是GPIO14产⽣的SMI呢?当GPIO14的SMI被触发后,系统会进⼊SMM模式。在SMM模式中,CPU就会去寻找时哪个GPI产⽣的SMI。InstallChildDispatchHandler这个Driver的⼊⼝函数中,会调⽤InitSmmHandler()这个函数,这个函数中⼜会调⽤pSmmBa->RegisterCallback(pSmmBa, ImageHandle, ChildDispatcher, FALSE, FALSE),其中参数ChildDispatcher也是函数,CPU会通过ChildDispatcher()⾥的GetContextGpi()来判断是否有GPI产⽣了SMI中断,如有SMI中断的话,就将其中的gSmiContext.GpiSource 设置为TRUE,在ChildDispatcher()的另外⼀个地⽅调⽤if (gSmiContext.GpiSource)作判断,如为真,则执⾏EfiSmmGpiDispatch(&gSmiContext.GpiContext),在这个函数中有这么两条语句
if (Link->Context.GpiNum & Context->GpiNum)
Link->Function(Link, &Link->Context);
Link->Function()在注册的时候EfiSmmSwRegister()中赋值,其值就是设置触发NMI相关寄存器的SBGpi14SmiHandler()函数,经过这个过程以后,NMI就会被触发。