1 基本概念及函數
最近在幫公司弄一個ALPHA架構的EFI BIOS,在調DXE_CORE的時候,發現必須要裝幾個CPU架構相關的協議。如果這幾個協議不去安裝,代碼就不會執行BDS階段。代碼偵測協議有沒有安裝,是通過CoreRegisterProtocolNotify()函數實現的,這個函數在未安裝協議之前,便先建立PROTOCOL_ENTRY變量,然後將其鏈接到mProtocolDatabase鏈表中,並將已經有的Event鏈接到自己的Notify鏈表中。等到代碼真正安裝Protocol實例後,這個Event便會被調用,代碼有Event NotifyFunction中便知道某個CPU架構類協議有沒有建立起來。開始時代碼中沒加裝載這幾個協議的驅動,所以代碼跑不到BDS階段,只是打印出架構協議未安裝的調試信息。這個設計很巧妙,也展現了EFIEvent強大的一角。
新聞聯播上總是說,北京時間XX點,發生了XX事件。這句話有兩個重點,時間和事情。EFI的Event同樣也有這兩個特性:一,Event是個可發生(執行)的函數;二,Event需要在某個時間點發生。在EFI裏,實現第一個特性是用CreateEvent()或CreateEventEx(),實現第二個特性需要使用SignalEvent()。Event除了這幾個函數外,也包括關閉事件函數CloseEvent(),等待事件函數WaitForEvent(),檢查事件函數CheckEvent()。下面分別介紹一下他們的基本用法。
1.1 創建事件
EFI_STATUS
EFIAPI
CoreCreateEvent (
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
INEFI_EVENT_NOTIFY NotifyFunction,OPTIONAL
IN VOID *NotifyContext, OPTIONAL
OUTEFI_EVENT *Event
)
這個函數創建了一個Type類別的,NotifyTpl級別的,擁有NotifyFunction函數的Event事件。NotifyFunction是可選的,如果NotifyTpl擁有EVT_NOTIFY_SIGNAL或EVT_NOTIFY_WAIT級別,則其作爲有效參數被使用。
EFI_STATUS
EFIAPI
CoreCreateEventEx (
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
INEFI_EVENT_NOTIFY NotifyFunction,OPTIONAL
IN CONSTVOID *NotifyContext,OPTIONAL
IN CONSTEFI_GUID *EventGroup, OPTIONAL
OUTEFI_EVENT *Event
)
此函數比CreateEvent()多了一個EventGroup函數,與CreateEvent創造的事件不同的是,BootServcies函數無法單獨觸發。
1.2 銷燬事件
EFI_STATUS
EFIAPI
CoreCloseEvent (
IN EFI_EVENT UserEvent
)
此函數將UserEvent銷燬,從鏈表中摘除,並釋放佔有的內存空間。
1.3 觸發事件
EFI_STATUS
EFIAPI
CoreSignalEvent (
IN EFI_EVENT UserEvent
)
觸發UserEvent事件。
1.4 等待事件
EFI_STATUS
EFIAPI
CoreWaitForEvent (
IN UINTN NumberOfEvents,
IN EFI_EVENT *UserEvents,
OUT UINTN *UserIndex
)
此函數停止當前的任務,等待NumberOfEvents個事件發生,若其中一個事件發生,則退出並反饋給用戶這個信息:第userIndex個事件發生了。
1.5 檢查事件
EFI_STATUS
EFIAPI
CoreCheckEvent (
IN EFI_EVENT UserEvent
)
此函數檢查UserEvent的現在狀態。
1.6 其它事件相關函數
1.6.1 CoreSetTimer()
爲Timer事件設置類型和觸發時間,Timer那一章介紹過了,此處略過。
1.6.2 CoreRaiseTpl ()
爲當前任務升權。
1.6.3 CoreRestoreTpl ()
恢復任務權限級別,所有Event級別比新的任務權限級別高的,都會得到執行。
2 Event相關結構
2.1 Event真身
typedef struct {
UINTN Signature;
UINT32 Type;
UINT32 SignalCount;
LIST_ENTRY SignalLink;
EFI_TPL NotifyTpl;
EFI_EVENT_NOTIFY NotifyFunction;
VOID *NotifyContext;
EFI_GUID EventGroup;
LIST_ENTRY NotifyLink;
BOOLEAN ExFlag;
EFI_RUNTIME_EVENT_ENTRY RuntimeData;
TIMER_EVENT_INFO Timer;
} IEVENT;
CreateEvent()函數最後一個參數是輸出參數EVENT,它的真身就是IEVENT結構體。其它輸入參數,都被賦於了這個結構體的成員。
2.2 gEfiCurrentTpl
當前任務級別。可以將其看爲Legacy BIOS中的當前中斷優先級。
2.3 gEventQueue[]
一個專門鏈接處於觸發態的事件的雙向鏈表,其有32個元素,當前只用了4個。分別是: TPL_APPLICATION,TPL_CALLBACK,TPL_NOTIFY,TPL_HIGH_LEVEL。
Event事件被SignalEvent()後,便會將其塞入到gEventQueue[Event-> NotifyTpl]鏈表裏。當CoreRestoreTpl()函數執行時,如果Event-> NotifyTpl大於恢復後的任務權限級別,gEventQueue [Event-> NotifyTpl]所鏈接的所有Event都會被執行,執行完後,Event從gEventQueue [Event-> NotifyTpl]中移除。
2.4 gEventPending
該全局變量爲一指示型變量。其每一位指示gEventQueue[]的每一個元素是否鏈接有相應的Event,若有則爲1,沒有值爲0。因其類型爲UINTN,但是gEvent[]的元素有32個。所以如果某一類型CPU的數據寬度小於32位,我們要強制將其類型設爲大於或等於32位的。
2.5 gEventSignalQueue
這是一個雙向鏈表。當我們創造一個EVENT時,要將EVENT鏈入該鏈表中。當CloseEvent()時,從該鏈表中移除。
2.6 mEventTable[]
有效的Event類型。EVENT類型不在此數組元素內,創建EVENT時不會成功。
(完,附上一張EVENT相關結構圖,是2014年,小明剛學BIOS時,畫的一張圖。RuntimeData的直接忽略吧,畢竟代碼還未支持,而且也沒有支持的必要)