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

函数CreateEventCreateEventEx均用于产生事件,原型如下。其中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, //传递给NotifyFunction函数的参数
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 //SetVirtualAddressMap被调用时触发

对于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) //5次时触发事件
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 //返回触发状态事件在数组内下标 从0开始
);

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 //定时器过期事件 一个单位100ns
);
typedef enum {
TimerCancel, //取消定时器触发事件 设置后不触发定时器
TimerPeriodic, //重复型定时器 触发时间为TriggerTime*100ns
TimerRelative //一次性定时器 触发时间为TriggerTime*100ns
} 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, //Protocol实例
OUT EFI_KEY_DATA *KeyData //返回按键
);
typedef struct {
UINT16 ScanCode; //扫描码
CHAR16 UnicodeChar; //Unicode码
} EFI_INPUT_KEY;
typedef struct _EFI_KEY_STATE {
UINT32 KeyShiftState; //控制键Ctrl、Shift等
EFI_KEY_TOGGLE_STATE KeyToggleState; //切换状态键CapsLock、NumLock等
} EFI_KEY_STATE;
typedef struct {
EFI_INPUT_KEY Key; //按键编码
EFI_KEY_STATE KeyState; //控制键和切换状态键
} EFI_KEY_DATA;
//控制键状态值 KeyShiftState
#define EFI_SHIFT_STATE_VALID 0x80000000 //值有效
#define EFI_RIGHT_SHIFT_PRESSED 0x00000001 //右Shift按下
#define EFI_LEFT_SHIFT_PRESSED 0x00000002 //左Shift按下
#define EFI_RIGHT_CONTROL_PRESSED 0x00000004 //右Ctrl按下
#define EFI_LEFT_CONTROL_PRESSED 0x00000008 //左Ctrl按下
#define EFI_RIGHT_ALT_PRESSED 0x00000010 //右Alt按下
#define EFI_LEFT_ALT_PRESSED 0x00000020 //左Alt按下
#define EFI_RIGHT_LOGO_PRESSED 0x00000040 //右Windows徽标按下
#define EFI_LEFT_LOGO_PRESSED 0x00000080 //左Windows徽标按下
#define EFI_MENU_KEY_PRESSED 0x00000100 //菜单徽标按下
#define EFI_SYS_REQ_PRESSED 0x00000200 //SysRq按下
//状态切换键 EFI_KEY_TOGGLE_STATE KeyToggleState
#define EFI_TOGGLE_STATE_VALID 0x80 //值有效
#define EFI_KEY_STATE_EXPOSED 0x40 //状态切换键信息提供
#define EFI_SCROLL_LOCK_ACTIVE 0x01 //ScrLock打开
#define EFI_NUM_LOCK_ACTIVE 0x02 //NumLock打开
#define EFI_CAPS_LOCK_ACTIVE 0x04 //CapsLock打开

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, //Protocol实例
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, //Protocol实例
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, //Protocol实例
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; //X轴分辨率 0为不支持 单位个/mm
UINT64 ResolutionY; //Y轴分辨率
UINT64 ResolutionZ; //Z轴分辨率
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, //Protocol实例
OUT EFI_SIMPLE_POINTER_STATE *State //鼠标状态
);
typedef struct {
INT32 RelativeMovementX; //X轴位移量
INT32 RelativeMovementY; //Y轴位移量
INT32 RelativeMovementZ; //Z轴位移量
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){
//1 list mouse'mode
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);
//2 get mouse's state
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");