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就會被觸發。