UEFI 簡介

前言

大多數人接觸 UEFI 都是在 PC 的應用場景上,有在 PC 上安裝過多操作系統的經歷的同學,通常會進入 UEFI 界面設置操作系統引導順序、CPU 虛擬化等設置。UEFI 誕生之初也確實是作爲 BIOS 的替代者,主要應用在 PC 電腦上。隨着手機 / 平板等移動設備的發展,高通從 MSM8998 開始使用 UEFI 替代 LK(Little Kernel)作爲手機的 Bootloader,作爲一個嵌入式開發者勢必比較好奇,UEFI 相比嵌入式常見 Bootloader(u-boot、LK 等)區別在哪?

UEFI(Unified Extensible Firmware Interface,統一可擴展固件接口)定義了操作系統和平臺固件之間的接口,它是 UEFI Forum 發佈的一種標準。它只是一種標準,沒有提供實現。下面讓我們通過 Tianocore 社區提供的 EDK2 源碼一窺 UEFI 的具體實現,相比其他的 Bootloader 有什麼優勢特點。

一、UEFI 入門

1. 啓動過程

UEFI 系統從上電到關機可分爲 7 個階段:SEC-> PEI-> DXE-> BDS-> TSL -> RT-> AL

開源嵌入式的 Bootloader 大多分兩個階段(Stage1、Stage2)。

Stage1:系統資源環境較少情況下部分硬件初始化,爲 Stage2 準備執行環境。

Stage2:系統硬件進一步初始化,定製化功能的實現和操作系統的引導。

進行類比,SEC/PEI 可以看作是 Stage1,DXE/BDS/TSL 可以看作是 Stage2。

SEC 階段 / PEI 階段

SEC:Security Phase

PEI:Pre-EFI Initalization

顧名思義,這兩個階段的主要工作是安全校驗相關和前期硬件初始化,最重要的是 RAM 的初始化,讓後面的階段可以執行在外置 RAM 中。

DXE 階段 / BDS 階段

DXE:Driver Exection Environment

BDS:Boot Device Select

DXE 階段加載各個驅動模塊,驅動模塊通過 Protocol 結構體爲後續的應用功能執行提供服務,DXE 驅動服務在內存中常駐。BDS 實現操作系統的啓動順序的設置。

TSL 階段

TSL:Transient System Load

OSLoader 的執行階段,一般以 UEFI 應用程序的形式存在。以 Android 系統爲例,需要完成 AVB 校驗,加載 Linux Kernel 到內存,解析 DTB 和 Kernel cmdline 的設置等。

高通的 UEFI 實現有所精簡,SEC 和 PEI 作爲 Stage1 放到了一個 Module 中實現。

2. EDK2 目錄

EDK2 根目錄下很多以 * Pkg 命令的文件夾,稱之爲一個 Package,是一組功能的集合。例如 MdePkg 是基礎靜態庫的集合,MdeModulePkg 是 PEI、DXE 啓動過程和常見通用驅動框架服務的集合。Package 下各個功能目錄稱之爲 Module,Module 可編譯成 efi 文件動態加載 / 卸載。

dsc 和 inf 文件相當於是 Makefile,定義了 Module 的編譯,生成哪些 efi 文件,fdf 文件將衆多的 efi 文件組織打包成燒錄鏡像。

二、UEFI 的特點

LK、u-boot 等開源的 Bootloader 都是結構簡單的輕量級系統,開發門檻低。UEFI 系統雖然較爲複雜,但其具有模塊擴展性、多任務實現等類操作系統的特性,更易實現複雜功能,且具有較好的開發靈活性,因而高通使用 UEFI 替換 LK 作爲手機的 Bootloader 也就不奇怪了。

1. 擴展性

UEFI - 統一可擴展固件接口,可擴展性是 UEFI 的突出優勢。這主要體現在 UEFI 可以動態地加載其它編譯好的 efi 鏡像,而 u-boot 在編譯完後並不能動態地加載其它定製化的驅動或者命令。那 UEFI 是如何實現這一特性地呢?

DXE 是 UEFI 的核心階段,這一階段初始化了 Boot Services(後面簡稱 BS),BS 爲後面的引導過程和應用程序的執行提供了幾乎所有基礎服務接口。看一下 BS 的結構:

BS 中的提供了 Image 和 Protocol 的管理接口,Image 相關接口提供了 efi 鏡像加載 & 啓動功能,Protocol 相關接口則可以找到鏡像提供的功能服務。下圖描述了 Image 和 Protocol 加載到內存後的存儲結構,該存儲結構揭示了 Portocol 管理接口如何在 Image 中找到對應的 Protocol 服務:接口(HandleProtocol)傳入了對應的 ImageHandle,則直接通過 GUID 在 IHANDLE 的 Protocols 鏈表中查找,接口(LocateHandle)沒有傳對應的 ImageHandle 則直接在包含所有 Protocol 的鏈表中查找。

efi 鏡像的加載過程:

BDS 跳轉至 TSL 階段時 BS 調用 CoreLoadImage() 函數加載 TSL 的 efi 鏡像,調用過程如下:

CoreLoadImage->CoreLoadImageCommon->CoreInstallProtocolInterfaceNotify

可以看到 CoreLoadImage 最終申請了一個 IHADNLE 結構體,並將節點掛到 gHandleList 列表中。

而後調用 CoreStartImage 函數最終執行 Image 的 EntryPoint(定義在 inf 文件中)。

驅動服務模塊一般在 ModuleEntry 中將自身的 Protocol 註冊到 ImageHandle 中。Protocol 的管理接口如何使用就不在贅述。

2. 設備驅動模型

UEFI 驅動實現了一種類 Linux 系統的驅動模型,實現了 Host Bus、Driver 和 Device 的架構解耦。(DXE_DRIVER 可以看做是一種驅動類型的功能服務,不一定需要硬件支持)

以 SCSI 驅動爲例,在 UEFI 中的驅動架構大致如下圖:

UEFI 驅動一般分爲兩部分,一部分是架構部分的 Driver Binding Protocol,一部分是和硬件 IO 相關的 Protocol。Driver Binding Protocol 用於在 UEFI 框架中匹配到 Device Handle 後,觸發 Start() 接口將對應的 IO Protocol 協議綁定到 Device Handle 上。

先看 SCSI 總線驅動的實現,Start() 接口掃描總線上的設備,爲設備上的 LUN 創建 Controller Handle(即 Device),並安裝 SCSI 通信協議的 IOProtocol:

SCSIBusDriverBindingStart()->ScsiScanCreateDevice()

再看 ScsiDisk 驅動,安裝後匹配到 ScsiBus 驅動創建的 LUN 設備,觸發 Driver Binding Protocol 的 Start 接口,Start 接口將 BlockIo 驅動安裝到 Controller Handle(即設備)上,這樣我們就能在應用或服務中使用 BlockIoProtocol 的接口讀寫設備了。

3. 多任務機制

目前接觸的嵌入式 Bootloader 都是單線程系統,但 UEFI 實現了 Linux 系統中類似 eloop 的單線程多任務機制,UE,eloop 機制是通過 Linux 系統的 select/epoll 系統調用實現了多任務、定時器等機制,那 UEFI 中是如何實現這一機制的呢?

UEFI 中的應用和 Event 的 Notifier 可以看作是任務,DXE 中初始化的 BS 提供了事件相關接口實現了異步操作。

Event 接口可類比 Linux 系統中的條件變量,對應的存在 create、wait、signal 等函數。TPL 接口用於提升任務的優先級,當優先級大於 31 時,中斷被關閉,這被用於 UEFI 鎖的實現,因爲 UEFI 中任務的調度依賴時鐘中斷,關中斷後其它任務不會再被調度。

先講講任務如何調度的,再來看看如何觸發調度。DXE 中調用 CoreInitializeEventServices 函數初始化 Event 模塊,申請了一個事件隊列存儲調用任務鏈表,同優先級的在一個按時間排在一個鏈表中。

Event 的調度過程大致如下:

CoreRestoreTpl 函數依據 gEventPending 標誌位優先級從高到低遍歷任務依次執行,清空後標誌位置 0。

下面再來看看調度的時機,每一次釋放鎖的操作,都會觸發高於當前任務等級的任務執行,前面說到任務調度依賴時鐘中斷,中斷處理程序進入 CoreTimerTick 函數有釋放鎖的操作,高於當前優先級(TPL_APPLICATION)的任務在此時被調度,即每次 TimerTick 會導致高優先級任務搶佔 CPU。

時鐘中斷到 CoreTimerTick 的過程,DXE 中調用 CoreNotifyOnProtocolInstallation 函數註冊了 CPU 架構相關的 Protocol,其中包含 Timer 的 Protocol。

DxeMain->CoreNotifyOnProtocolInstallation->GenericProtocolNotify:

在註冊 gTimer 之後,GenericProtocolNotify 被觸發,CoreTimerTick 註冊到 gTimer 中斷處理函數 TimerInterruptHandler 中。

以 ArmPkg 的 gTimer 爲例:

最後看看 CoreTimerTick 的實現:

先檢查 Timer 的時間,將 Timer 任務加入調度列表,最後調用 CoreReleaseLock(即 RestoreTpl)函數觸發了任務調度。

三、小結

u-boot、LK 和 UEFI 等 Bootloader 各有優劣,無所謂哪一種更好,只有合不合適的情況。目前在嵌入式領域應用最廣的 BL 還是 u-boot,結構簡單,驅動移植和裁剪的門檻低,可以滿足絕大部分嵌入式系統的需求,而 UEFI 在手機、平板等較複雜的嵌入式硬件更具優勢,滿足手機設備的多樣化需求。

參考文獻

1.   EDK2 源碼:https://github.com/tianocore/edk2

2.  《UEFI 原理與編程》-- 戴正華

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tgW9-FDo2hgxm8Uwne8ySw