2011年6月28日 星期二

Device Path and Image Information of the OS Loader

下列的程式片段會秀出OS Loader自己取得Device Path以及File Path的步驟. 首先會呼叫到HandleProtocol()去取得LOADED_IMAGE_PROTOCOL的介面. 其中ImageHandle會透過Dispater傳給OS Loader. 再來會呼叫HandleProtocol()去取得DEVICE_PATH_PROTOCOL的介面給OS Loader使用. 這兩個呼叫是用來將OS Loader的Device Path, File Path以及其他Image的資訊傳給OS Loader自己.

BS->HandleProtocol(
             ImageHandle,
             &LoadedImageProtocol,
             LoadedImage);

BS->HandleProtocol(
             LoadedImage->DeviceHandle,
             &DevicePathProtocol,
             &DevicePath);

Print (
   L"Image device : %s\n",
   DevicePathToStr (DevicePath));

Print (
   L"Image file : %s\n",
   DevicePathToStr (LoadedImage->FilePath));

Print (
   L"Image Base : %X\n",
   LoadedImage->ImageBase);

Print (
   L"Image Size : %X\n",
   LoadedImage->ImageSize);

2011年6月20日 星期一

SMM Mode

SMM是CPU一個獨有的運作模式, 當CPU SMI Pin被觸發時, 就會進入到SMM Mode, 以程式而言, 它會取得Vector去看位址是多少, 而這個位址所指到的記憶體就稱為SMM RAM, 所有有關SMM的Driver都會放在這塊RAM裡面, 當產生中斷進入SMM時, 會去判斷發生中斷的人是誰, 然後去執行相關的Function, 當POST結束之後, 這塊SMM RAM就會被Lock起來, 避免Run Time時有人會去破壞.

在UEFI的架構中, SMM Driver是一個個獨立的Driver, EFI會有Dispatcher來負責執行Driver, 當執行過程中發現Driver是SMM Driver時, 這時會呼叫Kernel的Function, 將此Driver搬到SMM RAM裡面.

SMM Driver可以從Entry Point發現與DXE Driver的不同, SMM的Entry Point會先去Locate一個叫EFI_SMM_BASE_PROTOCOL Protocol, 透過此Protocol來知道目前是否在SMM Mode下, 如果不是在SMM Mode下的話, 程式會先去註冊(Register), 將這支SMM Driver的FV位址以及大小Register起來, 如此一來系統就會依照這些資訊將此SMM Driver搬到SMM RAM裡面去, 搬完之後系統會產生一次SMI, 此時會再進入一次SMM Driver的Entry Point, 第二次進入Entry Point透過EFI_SMM_BASE_PROTOCOL.InSmm ()就會知道目前是在SMM Mode下, 第二次就要透過EFI_SMM_BASE_PROTOCOL.RegisterCallback ()註冊Callback Function. 等下一次SMI觸發時, 就可以進入到Callback Function去執行.

ACPI Enable/Disable

ACPI在OS起來之後, 會去Locate DSDT Table, 在Locate之前, 會先找到FACS Table (Firmware ACPI Control Structure), FACE Table裡面有一個欄位, 會記載Software SMI的Port是多少, 以及ACPI Enable/Disable的Software SMI Number是多少, 如此一來就可以透過Out SMI_Port, ACPI_Number的方式去發SMI來執行ACPI Enable/Disable的Callback Function.

上述的動作可以參考Platform\PlatformSmm的Driver.

S3 Save

在系統進入S3之前, 有一些Chipset的內容需要儲存到記憶體內, 所以需要有一支Driver來處理這些儲存的動作, 在系統進入S3之前, 這些動作會在SMM中完成.

上述的動作可以參考Platform\PlatformS3的Driver.

要做這些儲存的動作, 會透過一種稱為"Boot Script"的命令去完成, Boot Script會在你S3回來後, 把一些已經初始化好的值回填到暫存器內, 如此一來系統才能夠S3回來之後還能夠繼續正常工作. "Boot Script"是EFI的Protocol - EFI_BOOT_SCRIPT_SAVE_PROTOCOL, 而回填的動作是在PEI完成的, 所以會透過EFI_PEI_BOOT_SCRIPT_EXECUTER_PPI.Execute來完成.
當從S3回到OS, POST過程並不會經過DXE Driver, 但會經過PEI Driver, 所以會在PEI Driver上偵測目前的Boot Mode是否為S3, 如果是S3的話, 就透過Boot Script將值填回暫存器內.

EFI OS Loaders

這個段落會討論一些特別的考慮有關OS Loader. OS Loader是一種特別的EFI程式, 負責轉換系統, 從firmware的環境轉到OS的環境. 為了要達成這樣的任務, 有一些重要的步驟需要掌握:

1. OS Loader必須要決定它會從哪裡載入. 這個決定可以允許OS Loader可以從相同的地方取得一些額外的檔案.

2. OS Loader必須要知道OS存在哪裡. 一般而言, OS會在HDD的分割區裡. OS存在的分割區可能無法符合EFI環境能夠使用的檔案系統.  在這個情況下, OS Loader只能夠像Block Device一樣只使用Block I/O來存取分割區. OS Loader接著會需要實做或載入File System Driver去存取在OS分割區上的檔案.

3. OS Loader必須要建立實體記憶體的記憶體對映(memory map of physical memory), 使OS Kernel可以知道記憶體要管理的東西. 系統中一些實體記憶體必須要保留一些區段讓OS Kernel無法存取, 所以OS loader必須使用EFI API去取得系統目前的記憶體對映.

4. OS會有一些儲存Boot Path的選項以及Boot Option, 用環境變數的形式存在非揮發的儲存裝置(nonvolatile storage)裡. OS Loader可能會需要使用到這些儲存在非揮發儲存裝置的環境變數, 此外, OS Loader可能會需要將這些環境變數傳給OS Kernel.

5. 下一個步驟是去呼叫ExitBootServices(), 這個呼叫可以由OS Loader或試OS Kernel來完成, 要特別注意的是必須要在呼叫這個Function之前, 要取得目前記憶體對映(Current Memory Map). 一旦呼叫了ExitBootServices(), 就再也沒有EFI Boot Service可以使用了. 在某些點, 呼叫ExitBootServices()之前或之後, OS Loader就會將控制權交給OS Kernel.

6. 最後, 呼叫ExitBootServices()之後, EFI Boot Service就無法再使用, 這表示一旦OS Kernel取得系統的控制權之後, OS Kernel就只會呼叫EFI Runtime Service. 下列可以找到OS Loader程式的完整列表. 在下列的程式片段中並沒有完成任何的錯誤檢查. OS Loader的範例程式呼叫了幾種EFI Library來做一些簡單的實作. 下列會秀出OS Loader自己的Device Path以及File Path. 它也秀出了OS Loader存在的記憶體位址, 以及使用了多少Bytes. 接著它載入了OSKERNEL.BIN到記憶體, 而OSKERNEL.BIN是從OS Loader相同的目錄中取得的.
接著會秀出數個Block Device的第一個Block. 第一個Block是Floppy Drive的FAT12 File System. 第二個是HDD的MBR(Master Boot Record). 第三個則是相同HDD的大FAT32分割區. 第四個是相同HDD的小FAT16分割區.
最後的步驟是秀出所有System Configuration Table, 系統目前記憶體對映的指標以及列出所有系統的環境變數.  然後最後OS Loader呼叫ExitBootServices().

2011年6月17日 星期五

Protocols You Should Know

這個章節描述的是EFI會經常使用到的Protocol. 不管是要建立Device Driver, EFI pre-OS應用程式, 或者是Platform Firmware都應該要知道. 在這裡會有一些例子說明Protocol, 從最耳熟能響的"Hello world"開始. 這個測試會列在這裡是因為它最簡單. 它並不會與其他EFI Library的function有任何相依性. 所以在執行時並不會產生連結到EFI Library. 這個測試程式會使用Entry Point傳進來的SystemTable, 去取得EFI console device的控制權. 要在Console Output Device(通常是螢幕)上顯示一段訊息, 可以透過SIMPLE_TEXT_OUTPUT_INTERFACE Protocol的OutputString()來顯示訊息. 而Console Input Device(通常是鍵盤)可以等待使用者按下鍵盤, 可以透過SIMPLE_INPUT_INTERFACE Protocol的WaitForEvent()以及WaitForKey Event來達成. 一旦按鍵按下, 程式就會離開.

#include "efi.h"
EFI_STATUS
InitializeHelloApplication (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
  UINTN Index;


  //
  // Send a message to the ConsoleOut device.
  //


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,
  L"Hello application started\n\r");


  //
  // Wait for the user to press a key.
  //


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,
  L"\n\r\n\r\n\rHit any key to exit\n\r");


  SystemTable->BootServices->WaitForEvent (
  1,
  &(SystemTable->ConIn->WaitForKey),
  &Index);


  SystemTable->ConOut->OutputString (
  SystemTable->ConOut,L"\n\r\n\r");


  //
  // Exit the application.
  //


  return EFI_SUCCESS;
}


要執行EFI的程式, 需要在EFI Shell的command line打入程式的名稱, 下面的例子可以知道如何在EFI Shell中執行程式.
Example:
Shell> hello
Hello application started
Hit any key to exit this image