UEFI中的Protocol使用方法
前言
啓動服務提供了豐富的服務供開發者操作Protocol,我們可以使用Protocol也可以開發Protocol。本文主要介紹如何使用Protocol。
使用Protocol一般分爲下面三個步驟:
1. 通過啓動服務找出Protocol對象;
2. 使用這個Protocol提供的服務;
3. 關閉打開的Protocol
可以使用OpenProtocolInformation服務查看打開某個Protocol的所有設備。
啓動服務,有四種,分別爲:OpenProtocol、HandleProtocol、LocateProtocol和LocalHandleBuffer。下面分別介紹它們。
一、OpenProtocol 服務
OpenProtocol 用於查詢指定的Handle中是否支持指定的Protocol,如果支持,則打開該Protocol,否則返回錯誤代碼。
BS(啓動時服務)中的OpenProtocol函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface , OPTIONAL
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
返回值:
返回值 | 描述 |
---|---|
EFI_SUCCESS | The interface information for the specified protocol was returned. |
EFI_UNSUPPORTED | The device does not support the specified protocol. |
EFI_INVALID_PARAMETER | Handle is NULL. |
EFI_INVALID_PARAMETER | Protocol is NULL. |
EFI_INVALID_PARAMETER | Interface is NULL. |
- Handle:查詢次Handle提供的Protocol
- *Protocol:要打開的Protocol(指向次Protocol GUID的指針)
- **Interface:返回打開的Protocol的對象
- AgentHandle:打開此Protocol的Image
- ControllerHandle:打開此Protocol的控制器
-
Attributes:打開此Protocol的方式
Handle是Protocol的提供者,如果Handle的Protocols鏈表中有該Protocol,則Protocol對象的指針寫到*Interface,並返回EFI_SUCCESS,否則返回EFI_UNSUPPORTED。
如果在驅動中調用OpenProtocol,則ControllerHandle是擁有該驅動的控制器,也就是請求使用這個Protocol的控制器;AgentHandle是擁有該EFI_DRIVER_BINDING_PROTOCOL對象的Handle。EFI_DRIVER_BINDING_PROTOCOL是UEFI驅動開發一定會用到的一個Protocol,它負責驅動的安裝與卸載。
如果調用OpenProtocol的是應用程序,那麼AgentHandle是該用用程序的Handle,也就是UefiMain的第一個參數,ControllerHandle此時可以忽略。
Attribute值列表:
注:截圖來源於UEFI Spec 2_6。關於這部分可以查閱UEFI Spec。
OpenHandle示例程序片斷:
/// 打開BlockIo
EFI_HANDLE ImageHandle;
EFI_DRIVER_BINDING_PROTOCOL *This;
IN EFI_HADNLE ControllerHandle;
extern EFI_GUID BlockIoProtocolGuid;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_STATUS Status;
//在應用程序中使用OpenProtocol,一般ControllerHandle會設爲NULL
Status = gBS->OpenProtocol(
ControllerHandle, //Handle
&gEfiBlockIoProtocolGuid, //Protocol
&BlockIo, //Interface
ImageHandle, //AgentHandle
NULL, //ControllerHandle
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL //Attribute
)
//在驅動中使用,OpenProtocol, ControllerHandle是控制器的Handle
Status = gBS->OpenProtocol(
ControllerHandle, //Handle
&gEfiBlockIoProtocolGuid, //Protocol
&BlockIo, //Interface
This->DriverBindingHandle, //AgentHandle
ControllerHandle, //ControllerHandle
EFI_OPEN_PROTOCOL_GET_PROTOCOL //Attribute
)
- 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
gEfiBlockIoProtocolGuid 是全局變量,變量定義在AutoGen.c 中。AutoGen.c 是由build工具根據 .inf 和 .dec 文件生成的,gEfiBlockIoProtocolGuid是EFI_GUID類型的變量,其EFI_GUID值定義在 .dec中。如果要在應用程序或驅動中使用gEfiBlockIoProtocolGuid,那麼必須在 .inf 文件的[Protocols]中聲明以便預處理時將其包含在AutoGen.c中。
二、HandleProtocol 服務
OpenProtocol功能比較強大,但是使用比較複雜,需要提供Handle和Protocol的GUID,還要提供AgentHandle、ControllerHandle和Attributes。但是開發者大多時候只是想通過Protocol的GUID得到Protocol對象,並不關心打開方式細節。爲了方便開發者使用Protocol,啓動服務提供了HandleProtocol以簡化打開Protocol。
HandleProtocol服務的函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
返回值 | 描述 |
---|---|
EFI_SUCCESS | The interface information for the specified protocol was returned. |
EFI_UNSUPPORTED | The device does not support the specified protocol. |
EFI_INVALID_PARAMETER | Handle is NULL. |
EFI_INVALID_PARAMETER | Protocol is NULL. |
EFI_INVALID_PARAMETER | Interface is NULL. |
HandleProtocol 是OpenProtocol的簡化版,在調用HandleProtocol時不必再傳入AgentHandle、ControllerHandle和Attribute。它的內部其實仍是調用了OpenProtocol。
HandleProtocol 的內部實現:
EFI_STATUS
HandleProtocol (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
)
{
return OpenProtocol (
Handle,
Protocol,
Interface,
EfiCoreImageHandle,
NULL,
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL
);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
HandleProtocol使用示例:
Status = gBS->HandleProtocol(
ControllerHandle, //Handle
&gEfiBlockIoProtocolGuid, //Protocol
&BlockIo, //Interface
)
- 1
- 2
- 3
- 4
- 5
三、LocateProtocol 服務
OpenProtocol和HandleProtocol用於打開指定設備上的某個Protocol,要使用這兩個函數,首先要得到這個設備的句柄 。有時開發者並不關心Protocol在哪個設備上,尤其是系統僅有一個該Protocol的實例時。啓動服務提供了LocateProtocol(…)服務,它可以從UEFI內核中找到指定Protocol的第一個實例。
LocateProtocol服務函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL) (
IN EFI_GUID *Protocol,
IN VOID *Registration,OPTIONAL
OUT VOID **Interface
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
返回值 | 描述 |
---|---|
EFI_SUCCESS | A protocol instance matching Protocol was found and returned in Interface. |
EFI_INVALID_PARAMETER | Interface is NULL. |
EFI_NOT_FOUND | No protocol instances were found that match Protocol and Registration. |
UEFI內核中某個Protocol的實例可能不止一個,例如,每個硬盤及每個分區都有一個EFI_DISK_IO_PROTOCOL實例。LocateProtocol順序搜索HANDLE鏈表,返回找到的第一個該Protocol的實例。
下面的例子用於找出系統中第一個SIMPLE_FILE_SYSTEM_PROTOCOL實例。
EFI_SIMPLE_FILE_SYSTE_PROTOCOL* SimpleFs;
gBS->LocateProtocol(
gEfiSimpleFileSystemProtocolGuid,
NULL,
&SimpleFs
);
- 1
- 2
- 3
- 4
- 5
- 6
四、LocateProtocolBuffer服務
前面三種是從設備上找出Protocol的方法。有時候開發者需要找出支持某個Protocol的所有設備,例如找出系統中所有安裝了BlockIo的設備。LocateHandle和LocateProtocolBuffer提供這個服務。
LocateProtocolBuffer函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
IN EFI_LOCATE_SEARCH_TYPE SearchType, //查找方式
IN EFI_GUID *Protocol OPTIONAL, //指定的Protocol
IN VOID *SearchKey OPTIONAL, //PROTOCOL_NOTIFY的類型
IN OUT UINTN *NoHandles, //返回找到的HANDLE數量
OUT EFI_HANDLE **Buffer //分配Handle數組並返回
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
返回值 | 描述 |
---|---|
EFI_SUCCESS | The array of handles was returned. |
EFI_NOT_FOUND | No handles match the search. |
EFI_BUFFER_TOO_SMALL | The BufferSize is too small for the result. BufferSize has been updated with the size needed to complete the request. |
EFI_INVALID_PARAMETER | SearchType is not a member of EFI_LOCATE_SEARCH_TYPE |
EFI_INVALID_PARAMETER | SearchType is ByRegisterNotify and SearchKey is NULL. |
EFI_INVALID_PARAMETER | SearchType is ByProtocol and Protocol is NULL. |
EFI_INVALID_PARAMETER | One or more matches are found and BufferSize is NULL. |
EFI_INVALID_PARAMETER | BufferSize is large enough for the result and Buffer is NULL. |
SearchType有三種:AllHandles用於找出系統中所有的Handle;ByRegisterNotify用於從RegisterProtocolNotify中找出所有匹配SearchKey的Handle;ByProtocol用於從系統Handle數據庫中找到支持指定Protocol的HANDLE。
SearchType的類型定義:
typedef enum{
AllHandles,
ByRegisterNotify,
ByProtocol
} EFI_LOCATE_SEARCH_TYPE;
- 1
- 2
- 3
- 4
- 5
LocateHandle服務函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE) (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
IN OUT UINTN *BufferSize,
OUT EFI_HANDLE *Buffer
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
返回值 | 描述 |
---|---|
EFI_SUCCESS | The array of handles was returned. |
EFI_NOT_FOUND | No handles match the search. |
EFI_BUFFER_TOO_SMALL | The BufferSize is too small for the result. BufferSize has been updated with the size needed to complete the request. |
EFI_INVALID_PARAMETER | SearchType is not a member of EFI_LOCATE_SEARCH_TYPE |
EFI_INVALID_PARAMETER | SearchType is ByRegisterNotify and SearchKey is NULL. |
EFI_INVALID_PARAMETER | SearchType is ByProtocol and Protocol is NULL. |
EFI_INVALID_PARAMETER | One or more matches are found and BufferSize is NULL. |
EFI_INVALID_PARAMETER | BufferSize is large enough for the result and Buffer is NULL. |
LocateHandle服務與LocateHandleBuffer服務最大的不同是需要調用者負責管理Buffer數組佔用的內存。
五、其它Protocol 服務
除了Protocol和根據Protocol找出設備這些常用服務,啓動服務關於使用Protocol的服務還有ProtocolsPerHandle和OpenProtocolInformation。
1. ProtocolsPerHandle
ProtocolsPerHandle用於獲取指定設備所支持的所有Protocol。這些Protocol的GUID通過ProtocolBuffer返回給調用者,UEFI負責分配內存給ProtocolBuffer,調動者負責釋放該內存。
ProtocolsPerHandle函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_PROTOCOLS_PER_HANDLE) (
IN EFI_HANDLE Handle,
OUT EFI_GUID ***ProtocolBuffer,
OUT UINTN *ProtocolBufferCount
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
返回值 | 描述 |
---|---|
EFI_SUCCESS | The list of protocol interface GUIDs installed on Handle was returned in ProtocolBuffer. The number of protocol interface GUIDs was returned in ProtocolBufferCount. |
EFI_INVALID_PARAMETER | Handle is NULL. |
EFI_INVALID_PARAMETER | ProtocolBuffer is NULL. |
EFI_INVALID_PARAMETER | ProtocolBufferCount is NULL. |
EFI_OUT_OF_RESOURCES | There is not enough pool memory to store the results |
2. OpenProtocolInformation
OpenProtocolInformation用於獲得指定設備上指定Protocol的打開信息。
OpenProtocolInformation函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL_INFORMATION) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT EFI_OPEN_PROTOCOL_INFORMATION_ENTRY **EntryBuffer,
OUT UINTN *EntryCount
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
返回值 | 描述 |
---|---|
EFI_SUCCESS | The open protocol information was returned in EntryBuffer,and the number of entries was returned EntryCount. |
EFI_NOT_FOUND | Handle does not support the protocol specified by Protocol. |
EFI_OUT_OF_RESOURCES | There are not enough resources available to allocate EntryBuffer. |
//EFI_OPEN_PROTOCOL_INFORMATION_ENTRY數據結構
typedef struct {
EFI_HANDLE AgentHandle;
EFI_HANDLE ControllerHandle;
UINT32 Attributes;
UINT32 OpenCount;
} EFI_OPEN_PROTOCOL_INFORMATION_ENTRY;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Handle是這個設備的句柄,打開的信息存放在PROTOCOL_INTERDACE的OpenList列表中。設備上的同一Protocol可能被打開和關閉很多次。Protocol每一次被成功打開和關閉後都會更新OpenList列表。成功打開後,都會在設備句柄上添加一項EFI_OPEN_PROTOCOL_INFORMATION_ENTRY,若OpenList列表中已經存在一項與當前的(AgentHandle,ControllerHandle,Attributes)完全相同,則該項OpenCount加一。關閉Protocol則將OpenList對應項的OpenCount減一,OpenCount爲零時刪除對應項。
六、CloseProtocol 服務
Protocol使用完畢之後需要通過CloseProtocol關閉打開的Protocol。
CloseProtocol函數原型:
typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
返回值 | 描述 |
---|---|
EFI_SUCCESS | The protocol instance was closed. |
EFI_INVALID_PARAMETER | Handle is NULL. |
EFI_INVALID_PARAMETER | AgentHandle is NULL. |
EFI_INVALID_PARAMETER | ControllerHandle is not NULL and ControllerHandle is NULL. |
EFI_INVALID_PARAMETER | Protocol is NULL. |
EFI_NOT_FOUND | Handle does not support the protocol specified by Protocol. |
EFI_NOT_FOUND | The protocol interface specified by Handle and Protocol is not currently open by AgentHandle and ControllerHandle. |
通過HandleProtocol和LocateProtocol打開的Protocol因爲沒有指定AgentHandle,所以無法關閉。如果一定要關閉它,則要調用OpenProtocolInformation()獲得AgentHandle和ControllerHandle,然後再關閉它。
七、總結
本部分,不住要講解了Protocol的使用方法。Protocol提供了一種在UEFI應用程序以及UEFI驅動之間的通信方式。通過Protocol,用戶可以使用驅動提供的服務,以及系統提供的其他服務。要熟悉自己使用的Protocol的數據結構。