2010年6月27日 星期日

EFI SMM Driver Programming

SMI 分兩種,Sw 和 Hw。

作業是以 Dxe  Driver 為主,故討論 Dxe 面。

SwSmi 的主要攻略技巧就是先鎖定幾項重點:

1. SMM Driver 機制
2. 如何將 Dxe Driver 送進 SMRAM
3. 如何 Register SwSmi Notification
4. 如何控制 SwSmi Notification
5. 如何產生 SwSmi

SMM Driver 和一般 Dxe Driver 不同的地方,就是 SMM Driver 會跑兩次

第一次在 Dxe Phase,經用 EFI_SMM_BASE_PROTOCOL.InSmm () 先做驗證是否在 SMM Mode 下。

如果為 FALSE,則使用 Register () 函數執行第二次,這個函數可以看成是 SMM 下的 Driver Entry Point。

接著,在 SMM 下進入 Entry Point,再做 InSmm () 驗證,如果為 TRUE,則執行相關處理,抱括 SwSmiInputValue 的指定,利用 EFI_SMM_SW_DISPATCH_PROTOCOL  的 Register () 做函數綁定。

這部份很重要,因為 EFI_SMM_SW_DISPATCH_PROTOCOL 是由南橋 Bus 產生,所以當這邊有指定的 Value 填進 SMI Command Port 時,南橋偵測到就會觸發 SMI# 或是傳遞訊息。CPU 收到後,會有小小的 Delay (並不像書上寫的 "馬上",因為負責偵測 SMI 的組件並不一定就是接受 SMI 的組件),就會做 Function Callback,工程師就是在這塊做處理。

SmiInputValue 倒是好填,Command Port 指定就很麻煩,每個晶片組都不同,以前在寫 Win32 API 的經驗是 0xB2 for Intel ICH mostly,具體看那張板子什麼晶片組的吧....估計老師是做 AMD Chipset 的才會報 0xB0,這部份如果照單全收很容易『挫街』。

每個 codebase 都有自己的 SMI Cmd Port 定義的檔,ACPI Spec 也有,程式功夫一大抄,對 codebase 搜索強的自然吃香一點。

上面知道了,Trigger 就比較簡單,只要對特定的 SMI Cmd Port 填特定的值就行了。

進入到 SMM 後可用的服務被限制到很低,可用 SMMBASE 提供的 GetSmstLocation,拿到 SMM 的 System Table ,還是有一些東西可以用的,I/O Mem 存取之類。

比較需要注意的地方就是,因為 SMM 跨度十分廣,所以有可能 driver 存活得比自己的 producer 還久,故在使用 protocol 時務必注意,不要使用到跨度後會失效的 (例如 Boot Services 下的東西)。

另外目前了解到的知識是,如果想把 SMM 下的 driver unload,進到 SMM 解掉掛在上面的 dispatcher 就行了,如下:

mSwSmmDispatcher->UnRegister (mSwSmmDispatcher, mSwmSmmHandle);
mPbSmmDispatcher->UnRegister (mPbSmmDispatcher, mPbmSmmHandle);

只是比較奇怪的是,在 DXE 下 register 的 SMM Base 竟然在 DXE 下解不掉,Handle 都對的。。。不緊張,未來搞懂再補充回來。

9/22 補充: 為何 SMM Base 在 DXE 下解不掉,原因是在 Unload 處我把他 Handle 下注冊的函數全 UnRegister 了,根據 EFI Spec,當一 Handle 下沒任何掛載時,它會被 Handle Database 回收,所以造成在 UnRegister SMM Base 時找不到 Handle

EDK service driver or Application INF basic structure

目前寫程式常常遭遇到最麻煩的地方就是在改變 codebase 時,INF 檔的配置,因為還不太明白,所以都常常在 error message 裡邊看自己缺什麼路徑邊補上去。

這邊做個記錄,EDK 底下做 driver 最基礎的 INF 檔結構,如果沒有其它需求,通常這個 INF 可以直接套上去。

在 .h 檔裡:

#include "Tiano.h"
#include "EfiDriverLib.h"
#include "EfiPrintLib.h" //需要 Debug 或 Print 再用

INF 檔裡:

[defines]
BASE_NAME            = #BaseName
FILE_GUID            = #Guid
COMPONENT_TYPE       = BS_DRIVER

[sources.common]
  DriverName.c

[includes.common]
  .
  $(EDK_SOURCE)\Foundation
  $(EDK_SOURCE)\Foundation\Include
  $(EDK_SOURCE)\Foundation\Efi
  $(EDK_SOURCE)\Foundation\Efi\Include
  $(EDK_SOURCE)\Foundation\Framework
  $(EDK_SOURCE)\Foundation\Framework\Include
  $(EDK_SOURCE)\Foundation\Library\Dxe\Include

[libraries.common]
  EfiDriverLib
  EfiPrintLib   #同 include 檔
[nmake.common]
  IMAGE_ENTRY_POINT=#DriverEntryPoint

接下來就是 Application 的 include file 和 INF 檔配置,這應該用得比較少,不過有用時就方便了。

.h 檔
#include "EfiShellLib.h"

INF 檔
[defines]
BASE_NAME            = #BaseName
FILE_GUID            = #Guid
COMPONENT_TYPE       = APPLICATION

[sources.common]
  ApplicationName.c

[includes.common]
  .
  $(EFI_SOURCE)
  $(EFI_SOURCE)\Include
  $(EDK_SOURCE)\Foundation
  $(EDK_SOURCE)\Foundation\Core\Dxe\
  $(EDK_SOURCE)\Foundation\Efi
  $(EDK_SOURCE)\Foundation\Efi\Include
  $(EDK_SOURCE)\Foundation\Framework\
  $(EDK_SOURCE)\Foundation\Include
  $(EDK_SOURCE)\Foundation\Framework\Include
  $(EDK_SOURCE)\Foundation\Include\IndustryStandard
  $(EFI_SOURCE)\Application\Shell\Library
  $(EFI_SOURCE)\Application\Shell\inc

[libraries.common]
  EfiShellLib
  EdkFrameworkGuidLib

[nmake.common]
  IMAGE_ENTRY_POINT=ApplicationEntry

Component Type 可用 FvVolume.c 內查詢得知:
static const COMP_TYPE_EXTENSION mCompTypeExtension[] = 
{
  {"bs_driver",             ".dxe" },
  {"rt_driver",             ".dxe" },
  {"sal_rt_driver",         ".dxe" },
  {"pic_peim",              ".pei" },
  {"pe32_peim",             ".pei" },
  {"application",           ".app" },
  {"file",                  ".ffs" },
  {"fvimagefile",           ".fvi" },
  {"raw_file",              ".raw" },
  {"apriori",               ".ffs" },
  {"combined_peim_driver",  ".pei" },
  { NULL,                   NULL }
};

2010年6月23日 星期三

第一隻 Brain Fuck 程式

所有程式語言遍歷一次,是我的夢想。

LISP, COBOL, Fortran, Pascal,無聊的時候都有拿來玩過,不求精,有 Hello World 就好。

在軍中有幸見識到迪吉多 1988 年出產的 VAX6000 ,還用它寫過  PL/I

這台高貴不貴的電冰箱…不是,這台性能強大的工作站,可是當資訊兵 7、8 個月天天在碰的東西。

第一次看到兩台跟 UPS 一樣大的硬碟,驚訝程度可想而知...它的腳本語言也是十分怪異,有點像 bash 混合 MSDOS。不過工作需求,我竟然還把它玩精了。

這玩意很 high,處理個 3MB 的 txt 檔,包括解密,比對項目 db 到印出報表要 8 小時,常常整天龜在資訊室就是弄這 shit。




後來一個不爽,用 .NET 改寫整個 flow 才花 15 秒就做完原本三天份的量,剩下 7 小時 59 分鐘就拿來打茫,這又是後話了...

啊啊!! 離題太遠,回到 Brain Fuck。這玩意超級恐怖,果然不負其名,光是寫個小小的 Hi! Matt 就快把我的 Brain 給 Fuck 了。不過它的優點是很原始,很野性,指標和位址的移動,有種 asm 的直觀爽感,而且符合圖靈完全思維…閒話不多說,來看 code:
+++++ +++++
[
  > +++
  > +++++ ++
  > +++++ ++++
  > +++++ +++++
  > +++++ +++++ +
  <<<<< -
]
>> ++.
>> +++++.
<<< +++.
>+. 
< +++++ +.
>>> ++++.
<<< ----- --.
> ++++.
> +++++ ++.
>> +++++ +.
  .
<<<< +.

連成一條龍也不是不行,不過那個太刻意 fuck 了,這樣就夠 fuck 。
換成 c code:

unsigned char arr[30000] = {0};
*arr = 10; 

while (*arr) {
  *(arr + 1) += 3;
  *(arr + 2) += 7;
  *(arr + 3) += 9;
  *(arr + 4) += 10;
  *(arr + 5) += 11;
  *arr-=1;
} 

putchar (*(arr + 2) += 2);
putchar ((*(arr + 4) += 5));
putchar ((*(arr + 1) += 3));
putchar (++*(arr + 2));
putchar ((*(arr + 1) += 6));
putchar ((*(arr + 4) += 4));
putchar ((*(arr + 1) -= 7));
putchar ((*(arr + 2) += 4));
putchar ((*(arr + 3) += 7));
putchar ((*(arr + 5) += 6));
putchar (*(arr + 5));
putchar ((*(arr + 1) += 1));

或許你會問:有 compiler 嗎?
別說 compiler,連 IDE 都有!!

可在這邊下載 4mhz.de/bfdev.html

最後還是想說一句…雖然忙,該玩的東西還是要玩一下的...

EFI Image 初探

左邊就是 EFI Image 分類和依存關係表,spec 上有,可是很多人選擇跳過,這邊就自己了解,獻一下醜。

Image 分兩大類:applicationdriver









Application 就沒什麼懸念,只要 return 個值或是去呼叫  
BS->Exit()
即自動 unload。 Application 中的異類是  OS Loader,它的運作機制比較奇特,不 return 也不 exit,它會去叫
gBS->ExitBootServices()
將控制權從 EFI 轉到 OS,從此 EFI 任務即算完成,接下來 EFI 就從事潛伏等待 OS 呼叫的功能。不過基本上它們兩個執行完後會從記憶體中載出。

Driver 大體上分為 Non Driver-Model Driver Driver-Model Driver 兩種。如果就工作階段的不同,分為 Boot Service Driver 和 Runtime Driver 兩種,這兩者的差異請自己看 spec,最大的差別就是存活期限。

Driver 和 Application 有個最大的差異,就是 Application 只要回傳 EFI_SUCCESS 就能 unload,而 Driver 剛好相反,後面會有利用這種特性的 Driver。

Non Driver-Model Driver 以下介紹:

Service Driver: 故名思義,提供服務 (一個或以上的 protocol 或是 handle),實作完回傳 EFI_SUCCESS 常駐進記憶體等待呼叫。

Initializing Driver: 它不出 protocol 也不出 handle,單純做初始化動作,做完故意 return Error Code 強迫自己退出記憶體。

Root Bridge Driver: 這類型的 driver 專門建立實體 controller handle,每個 controller handle 又有包 Device Path Protocol,Chipset 產生的 root bus I/O 硬體抽象化的 protocol 也是包在這 driver 裡面。較常見的為 PCI_ROOT_BRIDGE_IO_PROTOCOL。

EFI 1.02 Driver:  其餘非 Driver-Model Driver 的統稱 EFI 1.02 Driver。這類型 Driver 和 Driver-Model Driver 最大的不同處,就是在它 Entry Point 直接就做 Start 了。這種行為代表著它一載入,所有硬體所有 protocol 都必須處理好,挫著等它下手做另外的事,沒有延後呼叫的可能性



Driver-Model Driver 以下介紹:

EFI Driver-Model Driver: 任何遵循 EFI 1.10 spec 製作的 driver 都是 Driver-Model Driver。它和上述 Non Driver-Model Driver 最決定性的差別,就是在它的進入點,絕不會去碰硬體或是produce 跟硬體有關的任何服務。它會去包一個 Driver Binding Protocol,在進入點只做 Support、Start、Stop 這三種動作,而不像 Non Driver-Model Driver 一進入就直接 Start。這樣有個好處,可以等到條件符合再執行。

Device Driver: 這類型的 driver 就是藉由 Driver Binding Protocol produce 一個或以上的  driver handle (或 driver image handle) 進 handle database。它的特性是在
EFI_DRIVER_BINDING_PROTOCOL.Start ()
呼叫的時候,不會去生成 child handle,只會在目前系統裡有的 controller handle 上裝 I/O protocol。

Bus Driver: 它和 Device Driver 行為模式一樣,兩者差別只在
EFI_DRIVER_BINDING_PROTOCOL.Start () 
呼叫的時候,它會生成 child handle 然後把 I/O protocol 附加在這些新創的 handle 上面。

Hybrid Driver: 和上述兩者行為模式一模一樣,一樣是在
EFI_DRIVER_BINDING_PROTOCOL.Start ()
呼叫的時候,它不但會生成 child handle,同時會把 I/O protocol 裝在 child handle 和既有的 handle 上。

2010年6月22日 星期二

亂寫

今天聽到前輩在詮述問題,句句到位,很是佩服。

一個門檻思考了半天,看了好久的 spec,無法理解。一句話直接中的, 功力從言談就能得知,想必經驗亦是十分豐富。

雖然不知道何時才能到達他的高度,不過至少知道這高度是人類可以達到的。

反之有些人不知是藏私、瞧不起還是裝懂,你問啥就不回啥,從他嘴裡說出來的東西不知所以然,都是些不著邊際的東西。

乍聽不懂,本位面食之無味,回想起來也沒什麼營養,跟他鬥嘴也只是兩敗俱傷,打高空的打高空,不會的仍然不會。讓你覺得向他求教是種浪費口水的決定。

做人嘛,何必把潛在對手搞得如此不堪,兩邊都沒好處的事,少做吧。自然,個性不強求交好,畢竟一樣米百種人,不可能誰都合得來。

人還是要往前看的,這種不好的經驗記取,希望以後自己不要用這種心態對人。

2010年6月21日 星期一

EFI 基礎知識

這邊看情況再回來修改,主要是目前自己學的也不是很熟悉,只把確定的知識拉出來喇迪賽。

個重要的 EFI object type:
  • EFI System Table
  • Memory
  • Handles
  • Images
  • Events
一般在寫 EFI driver or app,應該最常用到就是 System Table (ST)。裡面有太多的資料結構可供使用,基本可謂之 EFI 的脊骨。 

SystemTable->BootServices
SystemTable->RuntimeServices


這個常寫 EFI driver (app) 應該很熟著用了,反正基礎思想就是以 code 佐 spec 慢慢去試。
再來容易讓別人混淆的就是 Handle database、Handle、Protocol 和 Driver 的相互關係。

Handle database 故名思義,不多說;Handle 可看成  protocol 的 repository,它本身沒什麼屬性,性質也是看存放的 protocol 而訂。這段不廢話,多做些引用 handle 和 protocol 的練習自然就懂了。

Driver 和 Protocol 的關係也是使初接觸的人難以理解。EFI Driver 就是擁有一個或多個函數的 executable EFI image,Protocol 就是 Driver 的函數指標。

所以常常看到 spec 裡面什麼 

"某某 driver consumes protocol 同時又 produces protocol"  

之類的,就不要懷疑了,函數互相調用而已。 單個 protocol 要發佈成 global 都得有個 GUID 識別。其實上面那句有點語法問題,基本上不需要 global 的 function 就不要做成 protocol 了…反正喇喇賽。

下面貼個 protocol interface:

#define EFI_COMPONENT_NAME_PROTOCOL_GUID \ 
  { 0x107a772c,0xd5e1,0x11d4,\
    0x9a,0x46,0x0,0x90,0x27,0x3f,0xc1,0x4d } //GUID

typedef struct _EFI_COMPONENT_NAME_PROTOCOL { 
  EFI_COMPONENT_NAME_GET_DRIVER_NAME       GetDriverName; 
  EFI_COMPONENT_NAME_GET_CONTROLLER_NAME   GetControllerName; 
  CHAR8                                    *SupportedLanguages; 
} EFI_COMPONENT_NAME_PROTOCOL;

一個 GUID,兩個 function pointer,一個 string,這個 protocol 型體就長這樣,其它的也就差不多,使用頻繁自然會懂。

EFI 下的 Image,無論是 application 還是 driver,都擁有 PE / COFF header 描述,Microsoft Windows 和 Unix 下皆可分析其結構,具體情況 wiki 一下,不贄述。

EFI 接觸現在兩個多月,感覺就是個 protocol 和 driver 的世界,這部份搞得清楚來,以後會比較好作事,先寫到這邊,未來有想法再補充。

2010年6月20日 星期日

EFI File System

do {
  Status = FileRoot->Read (
    FileRoot,
    &FileInfoSize,
    FileInfo
  );
  if (Status == EFI_BUFFER_TOO_SMALL) {
    FreePool (FileInfo);
    FileInfo = AllocateZeroPool (FileInfoSize);
  }
} while (FileInfoSize != 0);

比較需要注意的地方就是只有 Read 動作才能讓 FileRoot 往下移位,GetInfo 沒辦法。Spec 是有講,不過很輕鬆的帶過了 (它輕鬆,累工程師)。

Status = FileRoot->Open (
  FileRoot,
  &FileCreated,
  FileName,
  (EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ),
  Attribute
);

建檔的時候比較麻煩就是這個,單弄個 EFI_FILE_MODE_CREATE 上到某些機器會爆炸,具體情況目前還不知道,未來了解了再過來修改文章。

CopyMem (FileContentMerge, FileContentTemp1, (UINTN)FileInfo1->Size);
CopyMem (
  (CHAR16*)FileContentMerge + ((UINTN)FileInfo1->Size / sizeof (CHAR16)),
  FileContentTemp2,
  (UINTN)FileInfo2->Size
  );

Merge 的時候用 CopyMem 其實都行,了解自己的資料結構最重要,要不然自爆真的就自找的。還是比較推崇強制 typecast 然後再具體解 bug… 比較差的辦法啦,不過出來的東西至少有錯還是有根據反追…

追加一點,如果要把整個 file structure 包含 attribute、filename、content 諸元都截出來,直接隨 Read 隨 Allocate 就行。但如果只想截 content,要先用 GetInfo 把 FileSize 抓出來,用那個 Size 去 Allocate 及截取。最重要的差異點應該在這。

※ 追加,一般來說用 LocateProtocol 如 code 所示:

EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystemProtocol;

Status = BS->LocateProtocol (       
  &gEfiSimpleFileSystemProtocolGuid,
  NULL,                             
  &FileSystemProtocol                
  );

這樣是只會搜到 "第一個 FileSystem Protocol (FS0)" ,更甚者是沒有,非常賭運氣的作法。

如果比較喜歡指定的 Storage,剛剛聽說了一個方法:

BS->HandleProtocol (
  ImageHandle,
  &gEfiLoadedImageProtocolGuid,
  &LoadImage
  );
DeviceHandle = LoadImage->DeviceHandle;

這樣就會抓到 Executable Image 所在的 storage device,也是比較  bulletprove 的做法。

還聽說有人做出在 Shell 下面先找到 current command root,對那個 root 單獨運作的,這就不知道方法了,還望各位大德賜教。

記得 FreePool,記得 Close File,其實這個還滿簡單的。

目前還有一些東西沒解決,例如 SetPosition 還不知道怎麼樣搞才能進到根目錄的各資料夾下面做掃描動作,不過相信時間演進,技術會更進步一些。

Blog 開通

先留個底,以後工作上有什麼狗屁知識就貼這邊了