2010年12月23日 星期四

別以為你不會碰到 (2)

常常看到有人寫了一堆檔,等到要互相做連結的時候,才開始在需要用的 c 檔裡面做 extern,『要用的函數交給 extern,責任全交給編譯器』,這樣其實超危險。

要知道 c 是一個完全依賴 coder 邏輯的語言,除了 syntax 或 casting 這種低級錯誤之外,其它模陵兩可的動作,編譯器的預設立場是『我相信這是你要的結果』。

//aaa.c
int func_a (uint8_t numInput) {
  return ++numInput;
}

//bbb.c
extern int func_a(void);

int func_b (void){
  int numCatch;
  numCatch = func_a();
  printf ("holy shit %d\n", numCatch);
  return 0;
}

範例舉得很傻,但定義如下:

int* GetHash (
    int *workingBuffer, 
    char **hashArray, 
    double potential, 
    uint32_t modulus
)

還敢說不會有問題嗎?

extern 聲明參數和定義不同。這範例結果是 linker 在連結的時候不會報 error,程式執行印出來一堆從暫存器拉出來的鬼數字。

當做一個比較大型的項目,也許會拆到近萬隻源檔,這時候一些基本功就能看得出高下。

需要發佈到所有源檔裡面的函數,我會直接拖個原型在 .h,這邊就是發佈的根源,要用這隻函數,請 include header。不需要發佈的,一律上 static 鎖死在這隻檔裡,或乾脆做 inline。

第一個就是多數 compiler 抓得出錯誤,無須下額外指令,第二個就是非常好整理,畢竟用的是近 30 年的舊語言,它不是很聰明,我們就要多想想。

2010年11月15日 星期一

近期的感悟

最近在工作上發現很多奇妙的東西。OEM 板子 port 的跟 CRB 一模一樣,可是就是點不亮,GPIO 設定說是 62 隻,可是注解就寫 61~63 腳是做啥啥用的。

這次做 Oak Trail 與其說危機,不如說是契機,真的搞出來,我實力會進步得很厲害。

盡信 spec 不如無 spec,但不遵循 spec 做出來的東西就是廢鐵,請密切追蹤本 blog。

2010年10月5日 星期二

別以為你不會碰到 (1)

很多時候,問題都是出在空格上面

result = *x / *y;  //no problem

result = *x/*y;  //compile error

查了好久都不知道為什麼,後來發現… y 被當成多行注解,抹掉了

result = (*x) / (*y); //for your safety

其實有時候不是在寫公司的 code 很懶得去注意 style,上述的方法基本上是沒在用,只能說永遠記得,C 是 maximal munch strategy,你不把運算符切開,它一次就咬住最大口來分析代碼。

經典問題:

a = x+++y;

答案為下列何者?
1.
a = x+       ++y;

2.
a = x++     +y;

思考一下 :)

2010年9月16日 星期四

前進

下單位一個多月了,感覺 leader 和前輩都很好相處,而且大家知識很深,相較之下自己對於相關的東西還嫌太淺,以下簡述。

對硬體的架構和整個 UEFI 的 Bootflow 了解程度仍需加強,一個平台到手上後要先了解全局構造,有什麼部件,基本上就是要把他們推起來,這自然是廢話,要不然焊在板子上做啥?

EFI 的 flow,怎麼走,pei 怎麼去 init,dxemain 怎麼去推 dispatcher,順序、滿足條件,思考了一下,都需要經驗的累積,這部份其實還算好,common knowledge 上網就能查到,畢竟 tiano 在這一塊仍然是公開的。

難是難在不公開的這一段,就需要多問,多學,多筆記。把學習的東西轉成養份,好好去培。

目前 bring up 不是很大的問題,卡是卡在 windows 下要正常 function,這些東西就很機密了,但不違反商機的學習流路未來可以和大家分享一下。

2010年9月6日 星期一

什麼是將才之風?

今天跟朋友聊到 cache as ram 和 ram only 的工程模式,在 google code 上做了一些試驗,發現差異不是一個數量級的。朋友就來了個很妙的問題:大將之風…是什麼?

老實說,我不知道,因為從來沒當過大將。一輩子庸庸碌碌的寫 code,看 code,看算法,寫算法,肚子裡的東西不少,搬得上檯面的不多 (肚子本身除外)。

今天在公司好挫折,一個 20 分鐘就解出來的問題,卻遲遲不敢發,到晚上 8 點才不情願的弄了個 solution。為什麼會這樣,工程思維及經驗的不足導致行動遲滯,這一點再不改進,會是非常恐怖的致命傷。

這輩子錯過不少好機會,這次不會再讓它從指間流走。

關於 cache as ram 的問題可能週末搬過來這邊跟大家討論一下,這期間就做一些 common 的討論,順便寫一些紀錄。

2010年8月18日 星期三

Assembly

下單位之後,整天都在忙 power on,上 feature,自己寫 code 的空間是零。所以最近 leader 放了兩個 assembly 作業,真的超高興,寫得很用心。

組語是本行,寫得真是順手…寫完三個月的 UEFI code,換成組語也是滿恐怖的,沒有語言的保護,直接跟硬體面對面硬幹,有錯誤就毫不留情的噴給你看,這種特性也是蠻值得懷念。

今天又領了經理裝備 (羅西塔小姐的 "來領鋼盔水壺" 的梗不錯笑),20" 的 LCD,不免俗的普通牌鍵鼠組,8G dongle。螢幕接上去後,coding 果然是爽歪歪,字好大。

這幾天不忙再把 asm code 貼上來分析分享一下。

2010年7月29日 星期四

下部隊

經過三個月的努力,終於完成了階段性的任務,從 NTC 畢業了。

雖然說期末考被我玩炸了,不過看了一下期中考和作業成績,要過關應該還算是容易。

大家紛紛整理裝備往各層移動,一邊和同學說再見,一邊深知這種團體日子應該在今天就會做個總結。

大家都撤了,和 Ian 兩個人在空蕩蕩的會議室等別人來接,心情是很忐忑不安,但潛意識更希望能趕快與工作接軌,畢竟收了錢不是來讀書的,NTC 只是個小門檻。

Vincent 過來把兩人叫出去,分配了位置,結果真的如 mail 所說:暫時座位:x 樓 xx 號。

真囧,仔細想想,我的工作之路好像一路充滿笨點:識別證名字打錯,丟給 HR 重做,到現在還沒來。結果小三梯的學弟都有照片了,我識別證還是白牌,現在連座位都是暫時配置,搞什麼呢。。。

安頓好了之後,跑去問兩位學長接下來該準備什麼。請購單、SVN 申請,去找指導學長,一步步來,穩穩地走。

拷貝 codebase,porting guide,相關 spec。東西都齊了,剩下的就是這三個月磨出來的工程思維,不懂的,搜;不會的,查,心情不能游移,我要變強,我要成為首屈一指的 BIOS 工程師。

不過在達到目標之前,說的話都是屁就是了。

開 Slickedit,導入 codebase 做 tag,開 referrence,比照 code,追函數,查電氣特性,一切都是那麼熟悉,NTC 紮下來的底子果然有用。

接下來就是等 MIS 來新筆電、19" 螢幕、鍵鼠組、Dongle,林林總總的雞絲。
希望接下來的日子大家都能順利,成功就在不遠的地方,加油吧,1004。

2010年7月7日 星期三

PEIM & PPI

入口函式原型 (PeiApi.h @ EDK):

typedef
EFI_STATUS
(EFIAPI *EFI_PEIM_ENTRY_POINT)(
  IN EFI_FFS_FILE_HEADER       * FfsHeader,
  IN EFI_PEI_SERVICES          **PeiServices
  );

比較常用到的幾個函數:

(**PeiServices).LocatePpi
這個函數要注意的就是 PEI Phase 下 PPI 的存放和 DXE 下存放 PROTOCOL 略有不同,因為 PEI FOUNDATION 會用來存放 PPI DB,所以很有可能一個 DB 裡 N 個 INSTANCE,第三個參數就是讓你選第幾個 INSTANCE 用的,第一個為 0,以此類推。

Status = (**PeiServices).CreateHob (
    PeiServices,
    EFI_HOB_TYPE_XXX,
    (UINT16) (sizeof (EFI_HOB_GUID_TYPE) + PrivateDataSize),
    (VOID**) &HobBuffer
    );

這個函數要注意的地方就是 CreateHOB 他只幫你弄個 HEADER 出來,要塞 HOB 資料,要自己拉空間出來,算好 OFFSET 是要件。
函數成功的話,HobBuffer 就會指一塊空間出來給你。

(**PeiServices).CopyMem (
    (VOID*)(((UINT8*) HobBuffer) + sizeof (EFI_HOB_GUID_TYPE)),
    (VOID*) PrivateData,
    (UINTN) PrivateDataSize
    );

再來也是算 OFFSET 把資料 COPY 進去,就行了。

DXE Phase 下:

EFI_HOB_HANDOFF_INFORMATION_TABLE  *HobList;
LibGetSystemConfigurationTable (&gEfiHobListGuid, (VOID**) &HobList);

藉此取得 HOBLIST,然後再用想對應的函數把你要的 HOB 拉出來就行了,如果我上面 HOB 的 TYPE 是 EFI_HOB_TYPE_GUID_EXTENSION,同時這個 HOB.Name 有加以指定一個 GUID,那我在 DXE Phase 下就能使用下列函數把 HOB 倒出來:

GetNextGuidHob ((VOID**) &HobList, &HobNameGuid, (VOID**) &HobContent, &BufferSize);

關於為什麼 PEI Service Table 要做成雙指標,在 BIOSREN 論壇 srcore 兄做了很漂亮的解釋,這邊借轉載並改成台灣用語:

『這個問題細究起來有點意思。這裡頭有歷史原因,其實依現在看來,定義成直接指標就可以了。』

『PEI 階段中,在 Main Memory 可用之前,PEI Core 和 PEIM 都必須在 flash 上 XIP。XIP 就意味著全域變數只能讀,不能寫。所以PEI Core在 memory 可用並且把自己 Shadow 到 memory 之前,是不可以用全域變數保存自己在 Stack 中分配的 PrivateData 的位址的。』

『這就是說我們必須有一種方法能讓 PEI CORE 在 PEIM 調用 PEI Service 的時候,能從傳進來的指向 PEI Serivces Table 的間接指標得到 PEI CORE 自己私有資料的位址。』

『當初,規範定義 EFI_PEI_SERVICES ** 是期望如下的 PEI CORE 的實現:舉例說明』

typedef struct {    ...
  EFI_PEI_SERVICES *PS,
  ...
  } PEI_CORE_DATA;

『PEI_CORE_DATA 在 Stack 上分配,PS 域被初始化為 PEI Core 資料段中 PEI Service Table 的指標。』

『&PEI_CORE_DATA.PS 不就是EFI_PEI_SERVICES ** 嗎?通過 CR 就可能得到 Stack 上 PEI_CORE_DATA 的地址。』

『注意,這裡有個前提,就是 PEI Service Table 中所有域的值都是固定的,作為一個 PEI Core 的全域變數存放在資料段中。』

『對,這就是定義 EFI_PEI_SERVICES ** 的由來。』

『有人會問,如果PEI CORE按照如下實現:』

typedef struct {
  ...
  EFI_PEI_SERVICES PSTable,
  ...
} PEI_CORE_DATA;


『就是說在 PEI Core 的 PrivateData 中定義一份 PEI Service Table 而不是指向它的指標,PEI Core 在初始化的時候將自己資料段中 PEI Service Table 作為範本拷到 PEI_CORE_DATA.PSTable 中。』

『這樣,就只用定義 EFI_PEI_SERVICES * (即 &PEI_CORE_DATA.PSTable)就可以了。確實如此,但規範仍然定義 EFI_PEI_SERVICES **,是考慮到 PEI Service Table 是固定的,可以直接放在 flash 上 PEI Core image 的資料段裡,PEI CORE 私有資料裡有一個指標指向它就可以了,這樣可以省掉一些對 temporary memory 的佔用,畢竟 temporary memory 的資源還是比較寶貴的。』

『瞭解了它的最初的設計意圖,那為什麼說回過頭來看,其實定義成直接指標就可以了?』

『因為 PEI CIS 規範的修訂使得 PEI Service Table 裡面的值不再是 build time 決定之後就不變的了。PEI CIS 0.91 在 PEI Service Table 加入了 CPU IO PPI 和 PCI CFG PPI 的指標。這兩個指標必須在執行時填入,所以 PEI Service Table 不能再放在 flash 上了,它必須被放到 memory 裡,這樣它才能被修改。』

『所以現在PEI CORE的實現一般是這樣:』

typedef struct {
  ...
  EFI_PEI_SERVICES *PS,
  ...
  ...
  EFI_PEI_SERVICES PSTable,
  ...
} PEI_CORE_DATA;

『PEI Core 在初始化的時候將自己資料段中 PEI Service Table 作為模組拷到 PEI_CORE_DATA.PSTable 中,然後 PEI_CORE_DATA.PS = &PEI_CORE_DATA.PSTable。』

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 開通

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