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 cause 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 set
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()这个函数,这个函数中又会调用pSmmBase->RegisterCallback(pSmmBase, 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就会被触发。