日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

BIOS知识枝桠—— Protocol

發布時間:2023/12/16 编程问答 58 豆豆
生活随笔 收集整理的這篇文章主要介紹了 BIOS知识枝桠—— Protocol 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

UEFI的Protocol

  • Protocol的概念
  • Protocol的數據結構
  • Protocol的實現
  • Protocol的使用
  • 其他類別的Protocol
    • Device Path Protocol
    • EFI Driver Binding Protocol

本文為參閱UEFI原理與編程第四章及他人博客后寫的融合怪,由于UEFI spec第八章起都是Protocol的,覺得有必要寫,侵刪

Protocol的概念

在計算機通信中,Protocol是網絡協議的簡稱,網絡協議是通信計算機雙方必須共同遵從的一組約定,是為了使數據在網絡上從源到達目的,網絡通信的參與方必須遵循相同的規則,這套規則稱為協議(protocol),典型的Protocol有HTTP、FTP、TCP、IP等,通過Http完成了文件的傳輸。

在UEFI中Protocol同樣是重要的概念之一,Protocol提供了一種在UEFI應用程序以及UEFI驅動之間的通信方式。通過Protocol,用戶可以使用驅動提供的服務,以及系統提供的其他服務。從本質上說是一種調用者與被調用者之間的“約定”。而這種“約定”在軟件開發領域有另一個更形象化的名字叫接口(Interface)。為了做到二進制間的互操作,那么參與操作的雙方(調用者與被調用者)都必須做出一定的讓步,這個讓步就是雙方必須遵循實現商量好的調用方法(接口),而這種事先約定的接口就是protocol的定義。Protocol引入了C++的面向對象的思想來設計管理,相當于C++的class,用 struct 來模擬 class,用函數指針(Protocol的成員變量)模擬成員函數,此種函數的第一參數必須是指向Protocol的指針,用來模擬this指針。

DXE驅動之間通過Protocol通信,Protocol是一種特殊的結構體,每個Protocol對應一個GUID,利用系統BootService的OpenProtocol,并根據GUID來打開對應的protocol,進而使用這個Protocol提供的服務。Protocol不是UEFI BIOS一開始就可以用的。UEFI BIOS啟動時分為不同的階段,SEC-PEI-DXE-BDS等等。而Protocol需要等到DXE階段才可以使用(不需要特別在意DXE階段的哪個點開始,基本上開發時寫的DXE模塊都可以使用)。UEFI框架下提供了函數來存取Protocol,大部分的設備初始化和其它功能代碼也都被包裝成了一個個的Protocol。Protocol的作用跟普通的結構體沒有區別,如果存放的是數據就作為存儲用,如果存放的是函數指針就用作特定代碼執行。

Protocol的數據結構

Protocol不是很復雜的東西,直觀來說,它就是一個結構體, Protocol的作用跟普通的結構體沒有區別,如果存放的是數據就作為存儲用,如果存放的是函數指針就用作特定代碼執行, 同時 UEFI框架下提供了函數來存取Protocol。UEFI下將大部分的設備初始化和其它功能代碼都包裝成了一個個的Protocol,比如說下面是一個用于存儲設備訪問的Protocol:

// @f ile MdePkg/Include/Protocol/Blocklo .h /// 通過這個Protocol可以控制塊設備 struct _EFI_BLOCK_IO_PROTOCOL {//////Protocol版本號,Protocol必須保證向后兼容///如果沒有向后兼容,必須給未來的版本定義不同的GUID,也就是必須定義一個不同的Protocol///UINT64 Revision;////// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.///EFI_BLOCK_IO_MEDIA *Media; //指針指向這個設備EFI_BLOCK_RESET Reset; //重置復位信號EFI_BLOCK_READ ReadBlocks; //讀Protocol服務EFI_BLOCK_WRITE WriteBlocks; //寫Protocol服務EFI_BLOCK_FLUSH FlushBlocks; //清除緩存服務 }; extern EFI_GUID gEfiBlockloProtocolGuid; //導出該Protocol

每個Protocol必須有一個唯一的GUID,例如在 Blocklo.h 中定義了 Blocklo 的 GUID,如下所示:

#define EFI_BLOCK_IO_PROTOCOL_GUID\{\0x964e5b21, 0x6459, 0xlld2, {0x8e, 0x39,0x0, OxaO, 0xc9, 0x69, 0x72, 0x3b }\} typedef struct _EFI_BLOCK_IO_PROTOCOL EFI_BLOCK_IO_PROTOCOL;

結構體EFI_BLOCK_IO_PROTOCOL有兩個成員變量和4個成員函數(當然,從C語言的角度來看,“成員函數”這樣的叫法不準確,它實際上也是一個成員變量,只是這個變量是函數指針而已)。gEfiBlockIoProtocolGuid ({ 0x964e5b21, 0x6459,0x11d2,{ 0x8e, 0x39, 0x0, 0xa0, 0xc9, 0x69, 0x72, 0x3b} })是一種標示符,標示了 EFI_BLOCK_IO_PROTOCOL。

下面是 EFI_BLOCK_IO_PROTOCOL的ReadBlocks服務的函數原型:

/**從地址Lba開始的塊讀取Buffersize字節到緩沖區@retval EFI_SUCCESS 數據從設備正確讀出@retval EFI_DEVICE_ERROR 設備出現錯誤@retval EFI_NO_MEDIA 設備中沒有介質@retval EFI_MEDIA_CHANGED Mediald與當前設備不符@retval EFI_BAD_BUFFER_SIZE 緩沖區大小不是塊的整數倍 @retval EFI_INVALID_PARAMETER 要讀取的塊中包含無效塊;或緩沖區未對齊 **/ typedef EFI_STATUS(EFIAPI *EFI_BLOCK_READ)(IN EFI_BLOCK_IO_PROTOCOL *This, //This 指針,指向調用上下文IN UINT32 Mediald, // media IdIN EFI_LBA Lba, //要讀取的啟始塊邏輯地址IN UINTN BufferSize, //要讀取的字節數,必須是塊大小的整數倍OUT VOID *Buffer //目的緩沖區,調用者負責該緩沖區的創建與刪除}

它的第一個參數,指向EFI_BLOCK_IO_PROTOCOL對象自己的This指針,這是成員函數區別于一般函數的重要特征。通常,計算機中有許多不同的塊設備,每個塊設備都有一個EFI_BLOCK_IO_PROTOCOL的實例,This指針就是指向這個實例,用于告訴成員函數我們正在操作哪個設備。This指針是Protocol成員函數的一個重要特征,與C++成員函數this指針的區別是,C++的this指針由編譯器自動加人,而Protocol成員函數的This指針需手工添加。

Protocol的實現


這里需要關注的圖中紅框部分的內容。
這里實際上是兩種鏈表,一種是Handle的鏈表,一種是Protocol的鏈表。Handle其實就是一個不會重復的整型數字,而Protocol在之前已經說過就是一個結構體。各個Handle連接在一起構成一個鏈表,每個Handle上可以附著若干個不會重復的Protocol。
上述的兩種鏈表交織成了一張網,這張網被稱為“Handle Database”。
這個Handle Database會在DXE最開始的地方初始化起來,之后通過接口可以擴展,搜尋等等操作。

下面認識一下EFI_HANDLE。typedef VOID *EFI_HANDLE;
EFI_HANDLE是指向某種對象的指針,UEFI用它來表示某個對象。UEFI掃描總線后,會為每個設備建立一個Controller對象,用于控制設備,所有該設備的驅動以Protocol的形式安裝到這個Controller中,這個Controller就是一個EFI_HANDLE對象。當我們將一個.efi文件加載到內存中時,UEFI也會為該文件建立一個Image對象(此Image非圖像的意思),這個Image對象也是一個EFI_HANDLE對象。在UEFI內部,EFI_HANDLE被理解為IHANDLE, IHANDLE的數據結構如代碼所示。

/// IHANDLE -包含了 Protocols 鏈表 typedef struct { UINTN Signature; //表明Handle的類別 LIST_ENTRY AllHandles; //所有IHANDLE組成的鏈表 LIST_ENTRY Protocols; //此 Handle 的 Protocols 鏈表 UINTN LocateRequest; UINT64 Key; } IHANDLE;

每 個 IHANDLE中 都 有 一 個Protocol鏈 表 , 存 放 屬 于 自 己 的 Protocol。所 有 的IHANDLE通過AllHandles鏈接起來。上圖展示了 IHANDLE內的Protocol是如何被組織起來的。IHANDLE的Protocols是一個雙向鏈表,鏈表中每一個元素是PROTOCOL_INTERFACE ,通過 PROTOCOL_INTERFACE 的 Protocol 指針可以得到這個 Protocol 的GUID,通過Interface指針可以得到這個Protocol的實例。

Protocol的使用

在UEFI Boot Service中提供了如下的函數用來操作Protocol:

NameTypeDescription
InstallProtocolInterfaceBoot在設備句柄上安裝protocol接口
UninstallProtocolInterfaceBoot從設備句柄中移除protocol接口
ReinstallProtocolInterfaceBoot在設備句柄上重新安裝protocol接口。
RegisterProtocolNotifyBoot注冊一個事件,該事件在為指定protocol安裝接口時發出信號。
LocateHandleBoot返回支持指定protocol的句柄數組。
HandleProtocolBoot查詢句柄以確定它是否支持指定的protocol。
LocateDevicePathBoot在支持指定protocol的設備路徑上定位所有設備,并返回最接近該路徑的設備的句柄。
OpenProtocolBoot向使用protocol接口的代理列表中添加元素
CloseProtocolBoot從使用protocol接口的代理列表中刪除元素。
OpenProtocolInformationBoot檢索當前正在使用protocol接口的代理的列表。
ConnectControllerBoot使用一組優先規則來找到管理控制器的最佳驅動程序集。
DisconnectControllerBoot通知一組驅動程序停止管理控制器。
ProtocolsPerHandleBoot檢索安裝在句柄上的protocol列表。返回緩沖區被自動分配
LocateHandleBufferBoot從符合搜索條件的句柄數據庫中檢索句柄列表。返回緩沖區被自動分配。
LocateProtocolBoot找到句柄數據庫中支持請求protocol的第一個句柄。
InstallMultipleProtocolInterfacesBoot將一個或多個protocol接口安裝到句柄上。
UninstallMultipleProtocolInterfacesBoot從句柄中卸載一個或多個protocol接口

具體可以查看UEFI Spec 6.3節
它們可以分為幾種不同的類型:

  • 安裝和卸載接口,就是這里的IntallXXX,ReinstallXXX,UninstallXXX,IntallMultipleXXX,UninstallMultipleXXX等。

  • 獲取和關閉接口,比如HandleProtocol,LocateHandle等等。

  • 其它輔助接口,比如OpenProtocolInformation,RegisterProtocolNotify等,其中RegisterProtocolNotify注冊了一個回調函數,當指定的Protocol被安裝時,這個回調函數就會被執行

  • 其他類別的Protocol

    參考博客:https://blog.csdn.net/jiangwei0512/article/details/86996846
    UEFI中的Protocol有一些比較特殊的類型,本節將介紹這些Protocol。
    Architectural Protocol
    UEFI規定了一些Protocol,這些Protocol在UEFI BIOS運行的過程中會安裝,且一定需要被安裝,如果沒有被安裝的話,系統就會報錯。

    這些Protocol如下所示:

    // // DXE Core Global Variables for all of the Architectural Protocols. // If a protocol is installed mArchProtocols[].Present will be TRUE. // // CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event // and mArchProtocols[].Registration as it creates events for every array // entry. // EFI_CORE_PROTOCOL_NOTIFY_ENTRY mArchProtocols[] = {{ &gEfiSecurityArchProtocolGuid, (VOID **)&gSecurity, NULL, NULL, FALSE },{ &gEfiCpuArchProtocolGuid, (VOID **)&gCpu, NULL, NULL, FALSE },{ &gEfiMetronomeArchProtocolGuid, (VOID **)&gMetronome, NULL, NULL, FALSE },{ &gEfiTimerArchProtocolGuid, (VOID **)&gTimer, NULL, NULL, FALSE },{ &gEfiBdsArchProtocolGuid, (VOID **)&gBds, NULL, NULL, FALSE },{ &gEfiWatchdogTimerArchProtocolGuid, (VOID **)&gWatchdogTimer, NULL, NULL, FALSE },{ &gEfiRuntimeArchProtocolGuid, (VOID **)&gRuntime, NULL, NULL, FALSE },{ &gEfiVariableArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiVariableWriteArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiCapsuleArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiMonotonicCounterArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiResetArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ &gEfiRealTimeClockArchProtocolGuid, (VOID **)NULL, NULL, NULL, FALSE },{ NULL, (VOID **)NULL, NULL, NULL, FALSE } };

    這些Protocol都是UEFI或者系統必須的最基礎的Protocol,比如說這里的gEfiBdsArchProtocolGuid對應的Protocol,它是BDS階段的如果,在DXEMain.c中有如下的代碼:

    //// Transfer control to the BDS Architectural Protocol//gBds->Entry (gBds);

    使DXE階段過渡到BDS階段。

    Device Path Protocol

    Device Path Protocol是一種純數據的結構體,它表示的是一個設備的可編程路徑,可以簡稱就是Device Path(后面就直接省略掉Protocol)。這種說法比較抽象,而且這里說的“設備”也并不一定需要是真實的設備,它可以是虛擬設備,甚至可以是一個文件。

    Device Path的具體說明有在其它的文章中介紹,這里不做具體的說明。
    簡單介紹一下它的結構體:

    /**此協議可用于任何設備句柄,以獲取有關物理設備或邏輯設備的通用路徑/位置信息。如果句柄在邏輯上沒有映射到物理設備,則句柄可能不一定支持設備路徑協議。設備路徑描述了句柄所對應的設備的位置。設備路徑的大小可以從構成設備路徑的結構中確定。 **/ typedef struct {UINT8 Type; ///< 0x01 Hardware Device Path.///< 0x02 ACPI Device Path.///< 0x03 Messaging Device Path.///< 0x04 Media Device Path.///< 0x05 BIOS Boot Specification Device Path.///< 0x7F End of Hardware Device Path.UINT8 SubType; ///< Varies by Type///< 0xFF End Entire Device Path, or///< 0x01 End This Instance of a Device Path and start a new///< Device Path.UINT8 Length[2]; ///< Specific Device Path data. Type and Sub-Type define///< type of data. Size of data is included in Length.} EFI_DEVICE_PATH_PROTOCOL;

    它的結構非常的簡單,是一個可變長的結構體。成員包括了一個基本的頭部(分為類型,子類型和長度三部分),以及之后的具體類型所需要包含的成員。Device Path有一個非常重要的作用就是標記對應Handle的屬性。

    舉一個簡單的例子,現在有兩個硬盤,那么它們都有一個_EFI_BLOCK_IO_PROTOCOL(見開頭),然而我們想訪問其中一個特定的硬盤,如何找到這個硬件,就可以依賴于Device Path。

    以硬盤的Device Path舉例,它的類型是HARDWARE_DEVICE_PATH,子類型是HW_CONTROLLER_DP,因此它的Device Path中包含如下的部分:

    /// /// Controller Device Path. /// typedef struct {EFI_DEVICE_PATH_PROTOCOL Header;////// Controller number.///UINT32 ControllerNumber; } CONTROLLER_DEVICE_PATH;

    而兩個不同的硬盤,其中的ControllerNumber可能是不同的(根據不同的硬件配置),因此就可以確定到底使用哪個Device Path,最終獲取到正確的_EFI_BLOCK_IO_PROTOCOL,大致流程如下:

  • 調用LocateHandleBuffer獲取到所有安裝了_EFI_BLOCK_IO_PROTOCOL的Handle;
  • 遍歷所有的Handle;
  • 根據上述Handle獲取到Device Path Protocol,根據這個Device Path Protocol就能夠確定該Handle是否是我們要找的那個Handle;
  • 通過找到的Handle,調用HandleProtocol來到對應的_EFI_BLOCK_IO_PROTOCOL,然后就可以使用這個Protocol來訪問硬盤。
  • 以上是使用Device Path Protocol的一個示例,當然Device Path Protocol的用法還有很多,可以參考UEFI Spec第九章。

    EFI Driver Binding Protocol

    總結

    以上是生活随笔為你收集整理的BIOS知识枝桠—— Protocol的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。