UEFI编程入门-GUI开发
入门
UEFI的事件接口函数运行于启动服务环境下,有不同的任务优先级TPL要求。启动服务环境下有4种任务优先级,高优先级任务可以中断低优先级任务。
TPL要求 |
含义 |
TPL_APPLICATION |
最低优先级,应用程序运行在这个级别 |
TPL_CALLBACK |
中等优先级,比较耗时的操作,如磁盘操作 |
TPL_NOTIFY |
高优先级,不允许阻塞,如底层IO操作 |
TPL_HIGH_LEVEL |
最高优先级,不允许被中断,如UEFI内核全局变量修改 |
事件存在两种互斥的状态:等待和触发。当事件被创建后,UEFI系统将其设置为等待状态。事件被触发后,UEFI系统将其转换为触发状态。
对于处于TPL_CALLBACK和TPL_NOTIFY级别的事件,存在处理队列。若队列中同志函数的TPL小于等于目前任务TPL,那它只能等到当前任务TPL降低,一般通过启动服务的RestoreTPL
来改变TPL。
常用函数
CreateEvent/CreateEventEx
函数CreateEvent
和CreateEventEx
均用于产生事件,原型如下。其中CreateEventEx
产生的事件被放入指定事件组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| typedef EFI_STATUS (EFIAPI *EFI_CREATE_EVENT)( IN UINT32 Type, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL, IN VOID *NotifyContext OPTIONAL, OUT EFI_EVENT *Event ); typedef EFI_STATUS (EFIAPI *EFI_CREATE_EVENT_EX)( IN UINT32 Type, IN EFI_TPL NotifyTpl, IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL, IN CONST VOID *NotifyContext OPTIONAL, IN CONST EFI_GUID *EventGroup OPTIONAL, OUT EFI_EVENT *Event ); #define EVT_TIMER 0x80000000 #define EVT_RUNTIME 0x40000000 #define EVT_NOTIFY_WAIT 0x00000100 #define EVT_NOTIFY_SIGNAL 0x00000200 #define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201 #define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202
|
对于EVT_TIMER事件,生成事件后要用SetTimer
设置Timer属性。对于EVT_NOTIFY_WAIT事件,当此类事件通过WaitForEvent
等待或CheckEvent
检查时,其通知函数将被放到待执行队列中。对于EVT_NOTIFY_SIGNAL事件,当此类事件通过SignalEvent
触发时,其通知函数将被放到待执行队列中。通知函数原型为:
1 2 3 4
| typedef VOID (EFIAPI *EFI_EVENT_NOTIFY)( IN EFI_EVENT Event, IN VOID *Context );
|
一个实例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| VOID TestNotifyEvent(VOID) { EFI_EVENT myWaitEvent; EFI_EVENT mySignalEvent; UINTN index = 0; EFI_STATUS Status; Status = gBS->CreateEvent(EVT_NOTIFY_WAIT, TPL_NOTIFY, (EFI_EVENT_NOTIFY)NotifyWaitFunc, (VOID*)L"Hi,UEFI!", &myWaitEvent); Print(L"CreateEvent myWaitEvent=%r\n", Status); Status = gBS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_NOTIFY, (EFI_EVENT_NOTIFY)NotifySignalFunc, (VOID*)L"Hi,me!", &mySignalEvent); Print(L"CreateEvent mySignalEvent=%r\n", Status); Status = gBS->WaitForEvent(1, &myWaitEvent, &index); Print(L"myWaitEvent is signaled! index=%d %r\n", index, Status); gBS->SignalEvent(mySignalEvent); Status = gBS->WaitForEvent(1, &mySignalEvent, &index); Print(L"mySignalEvent is signaled! index=%d %r\n", index, Status); gBS->CloseEvent(myWaitEvent); gBS->CloseEvent(mySignalEvent); } VOID NotifyWaitFunc(IN EFI_EVENT Event, IN VOID* Context) { static UINTN count = 0; Print(L"NotifyWaitFunc: count=%d, Context=%s\n", count, Context); count++; if ((count % 5) == 0) gBS->SignalEvent(Event); } VOID NotifySignalFunc(IN EFI_EVENT Event, IN VOID* Context) { Print(L"NotifySignalFunc: Context=%s\n", Context); }
|
SignalEvent/CloseEvent
SignalEvent
将等待状态的时间转为触发状态,若事件属于一个组,则将组内所有事件设为触发状态。事件使用完后用CloseEvent
关闭事件:
1 2 3 4 5 6
| typedef EFI_STATUS (EFIAPI *EFI_SIGNAL_EVENT)( IN EFI_EVENT Event ); typedef EFI_STATUS (EFIAPI *EFI_CLOSE_EVENT)( IN EFI_EVENT Event );
|
WaitForEvent/CheckEvent
用WaitForEvent
等待事件触发并重置为等待状态,是阻塞操作,必须运行在TPL_APPLICATION级别,否则返回EFI_UNSUPPORTED。若事件为EVT_NOTIFY_SIGNALE则返回EFI_INVALID_PARAMETER。若事件不带通知函数则返回EFI_SUCCESS。
1 2 3 4 5
| typedef EFI_STATUS (EFIAPI *EFI_WAIT_FOR_EVENT)( IN UINTN NumberOfEvents, IN EFI_EVENT *Event, OUT UINTN *Index );
|
用CheckEvent
检查事件是否触发,调用后立即返回。若事件为EVT_NOTIFY_SIGNALE则返回EFI_INVALID_PARAMETER。若事件处于触发状态则清除状态并返回EFI_SUCCESS,事件不处于触发状态且没有通知函数则返回EFI_NOT_READY。事件不处于触发状态且带有通知函数,且通知函数在事件通知函数队列中时,若在通知函数中触发此事件则清除触发状态并返回EFI_SUCCESS,若事件没有触发则返回EFI_NOT_READY。
1 2 3
| typedef EFI_STATUS (EFIAPI *EFI_CHECK_EVENT)( IN EFI_EVENT Event );
|
SetTimer
设置定时器类型的事件:
1 2 3 4 5 6 7 8 9 10
| typedef EFI_STATUS (EFIAPI *EFI_SET_TIMER)( IN EFI_EVENT Event, IN EFI_TIMER_DELAY Type, IN UINT64 TriggerTime ); typedef enum { TimerCancel, TimerPeriodic, TimerRelative } EFI_TIMER_DELAY;
|
RaiseTPL/RestoreTPL
用RaiseTPL
提高任务优先级到指定值,并返回之前优先级值。调用者用完RaiseTPL
后必须返回前用RestoreTPL
将优先级恢复到之前的值。
1 2 3 4 5 6 7 8 9 10
| typedef EFI_TPL (EFIAPI *EFI_RAISE_TPL)( IN EFI_TPL NewTpl ); typedef VOID (EFIAPI *EFI_RESTORE_TPL)( IN EFI_TPL OldTpl ); #define TPL_APPLICATION 4 #define TPL_CALLBACK 8 #define TPL_NOTIFY 16 #define TPL_HIGH_LEVEL 31
|
键盘处理
入门
UEFI提供了两类处理键盘输入的Protocol,EFI_SIMPLE_TEXT_INPUT_PROTOCOL简称ConIn,EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL简称扩展ConIn。ConIn可读取键盘按键的Unicode码和扫描码。此外扩展ConIn还可以获取控制键和切换状态键的状态、设置控制键状态、向系统注册热键等。ConIn和扩展ConIn的实例可用对应GUID,通过OpenProtocol
等函数获取。
扩展ConIn结构如下:
1 2 3 4 5 6 7 8
| struct _EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL { EFI_INPUT_RESET_EX Reset; EFI_INPUT_READ_KEY_EX ReadKeyStrokeEx; EFI_EVENT WaitForKeyEx; EFI_SET_STATE SetState; EFI_REGISTER_KEYSTROKE_NOTIFY RegisterKeyNotify; EFI_UNREGISTER_KEYSTROKE_NOTIFY UnregisterKeyNotify; };
|
ReadKeyStrokeEx
获取按键信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| typedef EFI_STATUS (EFIAPI *EFI_INPUT_READ_KEY_EX)( IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, OUT EFI_KEY_DATA *KeyData ); typedef struct { UINT16 ScanCode; CHAR16 UnicodeChar; } EFI_INPUT_KEY; typedef struct _EFI_KEY_STATE { UINT32 KeyShiftState; EFI_KEY_TOGGLE_STATE KeyToggleState; } EFI_KEY_STATE; typedef struct { EFI_INPUT_KEY Key; EFI_KEY_STATE KeyState; } EFI_KEY_DATA;
#define EFI_SHIFT_STATE_VALID 0x80000000 #define EFI_RIGHT_SHIFT_PRESSED 0x00000001 #define EFI_LEFT_SHIFT_PRESSED 0x00000002 #define EFI_RIGHT_CONTROL_PRESSED 0x00000004 #define EFI_LEFT_CONTROL_PRESSED 0x00000008 #define EFI_RIGHT_ALT_PRESSED 0x00000010 #define EFI_LEFT_ALT_PRESSED 0x00000020 #define EFI_RIGHT_LOGO_PRESSED 0x00000040 #define EFI_LEFT_LOGO_PRESSED 0x00000080 #define EFI_MENU_KEY_PRESSED 0x00000100 #define EFI_SYS_REQ_PRESSED 0x00000200
#define EFI_TOGGLE_STATE_VALID 0x80 #define EFI_KEY_STATE_EXPOSED 0x40 #define EFI_SCROLL_LOCK_ACTIVE 0x01 #define EFI_NUM_LOCK_ACTIVE 0x02 #define EFI_CAPS_LOCK_ACTIVE 0x04
|
UEFI的扫描码与Legacy BIOS下的键盘扫描码不同。UEFI下可打印的字符数字键的扫描码为0,用Unicode码。不可打印的字符数字键,Unicode码为0,用扫描码。
用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| EFI_STATUS GetKeyEx(UINT16 *ScanCode, UINT16 *UniChar, UINT32 *ShiftState, EFI_KEY_TOGGLE_STATE * ToggleState) { EFI_STATUS Status; EFI_KEY_DATA KeyData; UINTN Index; gBS->WaitForEvent(1,&(gSimpleTextInputEx->WaitForKeyEx),&Index); Status = gSimpleTextInputEx->ReadKeyStrokeEx(gSimpleTextInputEx,&KeyData); if(!EFI_ERROR(Status)) { *ScanCode=KeyData.Key.ScanCode; *UniChar=KeyData.Key.UnicodeChar; *ShiftState=KeyData.KeyState.KeyShiftState; *ToggleState=KeyData.KeyState.KeyToggleState; return EFI_SUCCESS; } return Status; }
|
SetState
切换状态键:
1 2 3 4
| typedef EFI_STATUS (EFIAPI *EFI_SET_STATE)( IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, IN EFI_KEY_TOGGLE_STATE *KeyToggleState );
|
RegisterKeyNotify/UnregisterKeyNotify
注册、注销热键。注册后必须注销。
1 2 3 4 5 6 7 8 9 10 11 12 13
| typedef EFI_STATUS (EFIAPI *EFI_REGISTER_KEYSTROKE_NOTIFY)( IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, IN EFI_KEY_DATA *KeyData, IN EFI_KEY_NOTIFY_FUNCTION KeyNotificationFunction, OUT VOID **NotifyHandle ); typedef EFI_STATUS (EFIAPI *EFI_KEY_NOTIFY_FUNCTION)( IN EFI_KEY_DATA *KeyData ); typedef EFI_STATUS (EFIAPI *EFI_UNREGISTER_KEYSTROKE_NOTIFY)( IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This, IN VOID *NotificationHandle );
|
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| EFI_STATUS HotKeyNotifyFunc(IN EFI_KEY_DATA* hotkey){ return EFI_SUCCESS; } extern EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL* gConInEx; EFI_STATUS HotKeySample(IN EFI_KEY_DATA* hotkey){ EFI_STATUS Status; EFI_KEY_DATA key; EFI_HANDLE hotkeyNotifyHandle; hotkey->KeyState.KeyShiftState|=EFI_SHIFT_STATE_VALID; hotkey->KeyState.KeyToggleState|=EFI_TOGGLE_STATE_VALID|EFI_KEY_STATE_EXPOSED; Status=gConInEx->RegisterKeyNotify(gConInEx,hotkey,HotKeyNotifyFunc,(VOID**)&hotkeyNotifyHandle); while(key.Key.ScanCode!=0x17){ UINTN index; gBS->WaitForEvent(1,&(gConInEx->WaitForKeyEx),&index); Status=gConInEx->ReadKeyStrokeEx(gConInEx,&key); } Status=gConInEx->UnregisterKeyNotify(gConInEx,hotkeyNotifyHandle); return EFI_SUCCESS; }; EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABEL* SystemTable){ EFI_KEY_DATA myhotkey={0,0}; myhotkey.Key.UnicodeChar='a'; myhotkey.KeyState.KeyShiftState=EFI_LEFT_CONTROL_PRESSED; HotKeySample(&myhotkey); return EFI_SUCCESS; }
|
鼠标处理
入门
鼠标Protocol结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct _EFI_SIMPLE_POINTER_PROTOCOL { EFI_SIMPLE_POINTER_RESET Reset; EFI_SIMPLE_POINTER_GET_STATE GetState; EFI_EVENT WaitForInput; EFI_SIMPLE_POINTER_MODE *Mode; }; typedef struct { UINT64 ResolutionX; UINT64 ResolutionY; UINT64 ResolutionZ; BOOLEAN LeftButton; BOOLEAN RightButton; } EFI_SIMPLE_POINTER_MODE;
|
GetState
获取鼠标当前状态:
1 2 3 4 5 6 7 8 9 10 11
| typedef EFI_STATUS (EFIAPI *EFI_SIMPLE_POINTER_GET_STATE)( IN EFI_SIMPLE_POINTER_PROTOCOL *This, OUT EFI_SIMPLE_POINTER_STATE *State ); typedef struct { INT32 RelativeMovementX; INT32 RelativeMovementY; INT32 RelativeMovementZ; BOOLEAN LeftButton; BOOLEAN RightButton; } EFI_SIMPLE_POINTER_STATE;
|
实例如下,无法在模拟器中正常工作,需要实机测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| UINT64 flag; flag = InintGloabalProtocols(S_TEXT_INPUT_EX | GRAPHICS_OUTPUT | PCI_ROOTBRIDGE_IO | PCI_IO | FILE_IO | RNG_OUT | SIMPLE_POINTER); if((flag&SIMPLE_POINTER) != SIMPLE_POINTER){ EFI_SIMPLE_POINTER_STATE State; UINTN Index; Print(L"Print Current Mode of Mouse:\n"); Print(L"::ResolutionX=0x%x\n",gMouse->Mode->ResolutionX); Print(L"::ResolutionY=%d\n",gMouse->Mode->ResolutionY); Print(L"::ResolutionZ=%d\n",gMouse->Mode->ResolutionZ); Print(L"::LeftButton=%d\n",gMouse->Mode->LeftButton); Print(L"::RightButton=%d\n",gMouse->Mode->RightButton); while(1){ gMouse->GetState(gMouse,&State); Print(L">>RelativeMovementX=0x%x\n",State.RelativeMovementX); Print(L">>RelativeMovementY=0x%x\n",State.RelativeMovementY); Print(L">>RelativeMovementZ=0x%x\n",State.RelativeMovementZ); Print(L">>LeftButton=0x%x\n",State.LeftButton); Print(L">>RightButton=0x%x\n",State.RightButton); Print(L"--------- ---------- ----------\n"); gBS->WaitForEvent( 1, &gMouse->WaitForInput, &Index ); } } else Print(L"Load Mouse Protocol Error!\n");
|