小華的部落格: BIOS 開發

搜尋此網誌

網頁

顯示具有 BIOS 開發 標籤的文章。 顯示所有文章
顯示具有 BIOS 開發 標籤的文章。 顯示所有文章

星期日, 5月 25, 2025

Dump TCG log

Dump TCG log

    一個簡易的方式在UEFI Shell底下,先找到TPM2 的ACPI Table ,再從Table中找到紀錄TCG log的記憶體位址(一般UEFI PCD目前都是設定0x10000 大小,但進入OS後,OS有可能會加大這塊的內容) ,然後分析TCG log中的每一筆紀錄的Event , 然後重新計算Digest ,這樣子可以呈現出BIOS交給OS前,TCG log中所記錄的TPM PCR可能的值會是甚麼. 底下是部分實作的程式碼,幫助大家了解流程. 


        // Loop through all ACPI tables to find TPM2

        Status = GetAcpiTableBySignature(SIGNATURE_32('T','P','M','2'), &Table);

        Tpm2Table =  (EFI_TPM2_ACPI_TABLE_V4 *) Table;


        if (Tpm2Table == NULL) {

            Print(L"[Fail] TPM2 ACPI Table not found\n");

            return EFI_NOT_FOUND;

        }


        Print(L"TPM2 ACPI Table found\n");

        Print(L"Event Log Start Address: 0x%lx\n", Tpm2Table->Lasa);

        Print(L"Event Log Length: %u bytes\n", Tpm2Table->Laml);

        Print(L"******** TCG Log Dump ******** \n");

        PrintHexAndAscii((UINT8 *) Tpm2Table->Lasa, (UINTN) Tpm2Table->Laml); // Print partial event data  


        Print(L"******** TCG Events ******** \n");


        // 儲存 PCR0 到 PCR23 的最後 Digest

        PCRDigest PcrDigests[PCR_COUNT];

        ZeroMem(PcrDigests, sizeof(PCRDigest) * PCR_COUNT);


        MY_ACPI_TCG2_EVENT *TcgEvent = (MY_ACPI_TCG2_EVENT *)(UINTN)Tpm2Table->Lasa;

        UINT8 *LogEnd = (UINT8 *)(UINTN)(Tpm2Table->Lasa + Tpm2Table->Laml);

        UINTN EventIndex = 0;


        while ((UINT8 *)TcgEvent < LogEnd) {

            UINT8 *EventStart = (UINT8 *)TcgEvent;

               //TODO:  1. 處理EV_NO_ACTION

                               2.CalculateDigest (CombinedData, CombinedDataSize, PcrDigests[PcrIndex].Digest, AlgId);


       } 

...

// 1.找到事件起始位置。
// 2.依序讀出:
// PCRIndex(4 bytes)
// EventType(4 bytes)
// DigestCount(4 bytes)
// 3.每個 Digest 結構:
//        AlgorithmId(2 bytes)
//        Digest(對 SHA128 而言,是 20 bytes, SHA256 而言,是 32 bytes..etc)
// 3.接著是 EventDataSize(4 bytes)
// 4.再來是 EventData
#pragma pack(1)
typedef struct {
    UINT32 PCRIndex;          // 4 bytes
    UINT32 EventType;         // 4 bytes
    UINT32 DigestCount;       // 4 bytes
                              // 之後是 DigestList 和 EventData
    UINT8  Event[];           // 可變長度,儲存 Digest 和 EventData (參考前面說明)
} MY_ACPI_TCG2_EVENT;
#pragma pack()

#define PCR_COUNT 24 // PCR0 到 PCR23,共 24 個 PCR

// 儲存每個 PCR 的最後一筆 Digest
typedef struct {
    UINT8 Digest[64];  // 假設 Digest 最長為 64 字節 (可以根據實際需要調整)
    UINTN DigestSize;  // Digest 大小
} PCRDigest;


UINTN
GetHashSizeFromAlgo(
  IN TPMI_ALG_HASH HashAlgo
  )
{
  switch (HashAlgo) {
    case TPM_ALG_SHA1:   return 20;
    case TPM_ALG_SHA256: return 32;
    case TPM_ALG_SHA384: return 48;
    case TPM_ALG_SHA512: return 64;
    case TPM_ALG_SM3_256:return 32;
    default:             return 0;
  }
}

// Hex + ASCII Dump
VOID
PrintHexAndAscii(IN UINT8 *Data, IN UINTN Length)
{
    for (UINTN i = 0; i < Length; i += 16) {
        Print(L"%08x: ", i);

        // Print hex
        for (UINTN j = 0; j < 16; j++) {
            if ((i + j) < Length) {
                Print(L"%02x ", Data[i + j]);
            } else {
                Print(L"   ");
            }
        }

        Print(L" ");

        // Print ASCII
        for (UINTN j = 0; j < 16; j++) {
            if ((i + j) < Length) {
                CHAR8 c = (CHAR8)Data[i + j];
                if (c >= 0x20 && c <= 0x7E) {
                    Print(L"%c", c);
                } else {
                    Print(L".");
                }
            }
        }

        Print(L"\n");
    }
}

// Covert to EventType string

CONST CHAR16* GetEventTypeString(UINT32 EventType)
{
    // Standard event types
    switch (EventType) {
        case EV_PREBOOT_CERT:             return L"EV_PREBOOT_CERT(0x00000000)";
        case EV_POST_CODE:                return L"EV_POST_CODE(0x00000001)";
        case EV_NO_ACTION:                return L"EV_NO_ACTION(0x00000003)";
        case EV_SEPARATOR:                return L"EV_SEPARATOR(0x00000004)";
        case EV_ACTION:                   return L"EV_ACTION(0x00000005)";
        case EV_EVENT_TAG:                return L"EV_EVENT_TAG(0x00000006)";
        case EV_S_CRTM_CONTENTS:          return L"EV_S_CRTM_CONTENTS(0x00000007)";
        case EV_S_CRTM_VERSION:           return L"EV_S_CRTM_VERSION(0x00000008)";
        case EV_CPU_MICROCODE:            return L"EV_CPU_MICROCODE(0x00000009)";
        case EV_PLATFORM_CONFIG_FLAGS:    return L"EV_PLATFORM_CONFIG_FLAGS(0x0000000A)";
        case EV_TABLE_OF_DEVICES:         return L"EV_TABLE_OF_DEVICES(0x0000000B)";
        case EV_COMPACT_HASH:             return L"EV_COMPACT_HASH(0x0000000C)";
        case EV_NONHOST_CODE:             return L"EV_NONHOST_CODE(0x0000000F)";
        case EV_NONHOST_CONFIG:           return L"EV_NONHOST_CONFIG(0x00000010)";
        case EV_NONHOST_INFO:             return L"EV_NONHOST_INFO(0x00000011)";
        case EV_OMIT_BOOT_DEVICE_EVENTS:  return L"EV_OMIT_BOOT_DEVICE_EVENTS(0x00000012)";

        // EFI specific event types (0x80000000 and above)
        case EV_EFI_VARIABLE_DRIVER_CONFIG:     return L"EV_EFI_VARIABLE_DRIVER_CONFIG(0x80000001)";
        case EV_EFI_VARIABLE_BOOT:              return L"EV_EFI_VARIABLE_BOOT(0x80000002)";
        case EV_EFI_BOOT_SERVICES_APPLICATION:  return L"EV_EFI_BOOT_SERVICES_APPLICATION(0x80000003)";
        case EV_EFI_BOOT_SERVICES_DRIVER:       return L"EV_EFI_BOOT_SERVICES_DRIVER(0x80000004)";
        case EV_EFI_RUNTIME_SERVICES_DRIVER:    return L"EV_EFI_RUNTIME_SERVICES_DRIVER(0x80000005)";
        case EV_EFI_GPT_EVENT:                  return L"EV_EFI_GPT_EVENT(0x80000006)";
        case EV_EFI_ACTION:                     return L"EV_EFI_ACTION(0x80000007)";
        case EV_EFI_PLATFORM_FIRMWARE_BLOB:     return L"EV_EFI_PLATFORM_FIRMWARE_BLOB(0x80000008)";
        case EV_EFI_HANDOFF_TABLES:             return L"EV_EFI_HANDOFF_TABLES(0x80000009)";
        case EV_EFI_PLATFORM_FIRMWARE_BLOB2:    return L"EV_EFI_PLATFORM_FIRMWARE_BLOB2(0x8000000A)";
        case EV_EFI_HANDOFF_TABLES2:            return L"EV_EFI_HANDOFF_TABLES2(0x8000000B)";
        case EV_EFI_HCRTM_EVENT:                return L"EV_EFI_HCRTM_EVENT(0x80000010)";
        case EV_EFI_VARIABLE_AUTHORITY:         return L"EV_EFI_VARIABLE_AUTHORITY(0x800000E0)";
        case EV_EFI_SPDM_FIRMWARE_BLOB:         return L"EV_EFI_SPDM_FIRMWARE_BLOB(0x800000E1)";
        case EV_EFI_SPDM_FIRMWARE_CONFIG:       return L"EV_EFI_SPDM_FIRMWARE_CONFIG(0x800000E2)";
        
        // Add 
        //case EV_EFI_SPDM_DEVICE_BLOB:           return L"EV_EFI_SPDM_DEVICE_BLOB(0x800000E1)"; // 和 EV_EFI_SPDM_FIRMWARE_BLOB 相同
        //case EV_EFI_SPDM_DEVICE_CONFIG:         return L"EV_EFI_SPDM_DEVICE_CONFIG(0x800000E2)"; // 和 EV_EFI_SPDM_FIRMWARE_CONFIG 相同

        default:                                return L"UNKNOWN(0x%08x)"; // Unknown event type
    }
}


#pragma pack(1)
typedef struct {                             // Refer to Tcg2Acpi.c 
  EFI_ACPI_DESCRIPTION_HEADER    Header;     // "TPM2"
  // Flags field is replaced in version 4 and above
  //    BIT0~15:  PlatformClass      This field is only valid for version 4 and above
  //    BIT16~31: Reserved
  UINT32                         Flags;
  UINT64                         AddressOfControlArea;
  UINT32                         StartMethod;
  UINT8                          PlatformSpecificParameters[12]; // size up to 12
  UINT32                         Laml;                           // Optional
  UINT64                         Lasa;                           // Optional
} EFI_TPM2_ACPI_TABLE_V4;
#pragma pack()


// Signature macro
//#define SIGNATURE_32(A,B,C,D)  ((UINT32)(A) | ((UINT32)(B)<<8) | ((UINT32)(C)<<16) | ((UINT32)(D)<<24))

EFI_STATUS
GetAcpiTableBySignature (
  IN  UINT32 Signature,
  OUT EFI_ACPI_DESCRIPTION_HEADER **OutTable
  )
{
  EFI_CONFIGURATION_TABLE             *ConfigTable = gST->ConfigurationTable;
  EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp = NULL;
  EFI_ACPI_DESCRIPTION_HEADER         *Xsdt;
  UINT64                              *EntryPtr;
  UINTN                               EntryCount;

  // Step 1: Find RSDP
  for (UINTN i = 0; i < gST->NumberOfTableEntries; i++) {
    if (CompareGuid(&ConfigTable[i].VendorGuid, &gEfiAcpi20TableGuid)) {
      Rsdp = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)ConfigTable[i].VendorTable;
      break;
    }
  }
  if (Rsdp == NULL) {
    Print(L"[Error] ACPI 2.0 RSDP not found\n");
    return EFI_NOT_FOUND;
  }

  // Step 2: Get XSDT
  Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)(Rsdp->XsdtAddress);
  if (Xsdt->Signature != EFI_ACPI_6_3_EXTENDED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE) {
    Print(L"[Error] XSDT Signature mismatch (found: %08X)\n", Xsdt->Signature);
    return EFI_NOT_FOUND;
  }

  // Step 3: Traverse XSDT entries
  EntryCount = (Xsdt->Length - sizeof(EFI_ACPI_DESCRIPTION_HEADER)) / sizeof(UINT64);
  EntryPtr = (UINT64 *)(Xsdt + 1); // entries come right after the header

  for (UINTN i = 0; i < EntryCount; i++) {
    EFI_ACPI_DESCRIPTION_HEADER *Hdr = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)EntryPtr[i];
    if (Hdr->Signature == Signature) {
      *OutTable = Hdr;
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

EFI_STATUS
EFIAPI
CalculateDigest (
  IN UINT8  *Data,
  IN UINTN  DataSize,
  OUT UINT8  *Digest,
  IN UINT16  AlgId
  )
{
    BOOLEAN  Status;

    if (AlgId == TPM_ALG_SHA256) {
        Status = Sha256HashAll (Data, DataSize, Digest);
    } else {
        // 支援其他演算法
        return EFI_UNSUPPORTED;
    }

    if (!Status) {
        return EFI_DEVICE_ERROR;
    }

    return EFI_SUCCESS;
}

星期二, 9月 16, 2014

EDK2/EDKII Python Build Tool 兩三事 (Build.py)

EDK2 Python Build Tool 兩三事

最近因為工作需要,所以不得已逼自己去把EDK2 Python Build source code看完,看完後順便整理一下一些小重點,以免過陣子自己又忘記了。

     1)EDK_GLOBAL 
     這種定義的Macro只能給EDK1 driver使用,因為他會去檢查.inf 內的
     INF_VERSION  = 0x00010005 ,如果大於或是等於這個值就是當成EDK2,如果找不到這個值就會當成是EDK1。

因為不好用,所以就隨手改了一下CODE,讓他通吃EDK1 + EDK2 + FDF + DSC + DEC + INF

   2) Build Rule and Package rule
   在EDK2 中,他把EDK1原本的Build Rule & Package Rule 分別描述成:
        Tools_def.txt 
        Build_Rule.txt
        [Rule.$(Arch).$(ModuleType] 

好不好用看個人,不過如果你要支援原本EDK1 Package Rule時那就對不起啦,你要修改Python source code,不然你做不到。  例如說底下的EDK1 的COMPONENT_TYPE = FILE原本對應到某個Package Rule,但是在EDK2內不支援就是不支援,除非自己改python source code.

[Defines]
BASE_NAME               = xxxxx
FILE_GUID                   = 126A4D6A-C404-4200-8779-F327A4A79087
COMPONENT_TYPE  = FILE
BUILD_TYPE               = MAKEFILE

   3) Python v.s SQL
   Python build tool 只是拿SQL當成是存放他收集到的資料的一個地方,然後在另一個地方就可以去撈資料庫內的資訊然後寫對應的CODE去分析要幹嘛,所以rebuild的時候最好把這個資料庫暫存檔案刪除。 EX: Conf\.cache\build.db

   4) GenSection & GenFFS
   以前EDK1的這兩個工具也整合進去Python Build tool內了,所以想了解FDF是怎麼產生FV或是FD的人就請自行研究,不過他是另一隻Tool叫做GenFds.py。

   5) AutoGen.c v.s AutoGen.h
   偷偷產生的檔案用來存放一些PCD操作/有用到的PCD Value,GUID 定義跟一些偷塞的include... 如果是給PCD database使用的,他還會塞Dynamic PCD給PCD 資料庫當成初始化值。

    另外,Library 只會產生AutoGen.h 不會產生AutoGen.c,因為Library通常會跟某個Driver Link,所以等到真正要去產生Module make時,才會去檢查Library 內用到的GUID/PCD ,然後產生在Module's AutoGen.c內。

    6) Hard code
    恩,很多東西都寫死在Python build tool內,像是nmake command 要增加修改時就要改一改他,不過也是個好處,就是我可以偷加很多東西在裡面,然後不給你source code...哈!!!

   7) 產生執行檔
    .py  轉成 .exe 方式我自己再用的有兩種,一種是py2Exe不過版本過舊已經不能支援了,所以我採用EDK2 所建議的cx_Freeze 去轉換,轉換時還要搭配你的Python 版本,目前我看到的codebase內使用的是Python 2.7.3,所以應該以這個為主吧! 底下是EDK2 建議的方式:

Using cx_Freeze 4.2.3 with Python 2.7.2
Using cx_Freeze 3.0.3 with Python 2.5.4

  8) 嗯...有想到再說~


     
   




星期三, 12月 19, 2007

純手工打造你自己的x86 BIOS(4)

上ㄧ篇文章我已經針對我的實驗做了敘述,這裡我就針對實際上我的程式碼撰寫的內容做一個介紹。

在程式碼的撰寫中,其實我只有使用了簡單的C語言跟組合語言語法,重點是要讓大家知道,其實BIOS跟一般的Boot Loader寫法沒什麼不同,只是PC上面的BIOS需要考慮的事情比一般的Boot Loader還多很多,因此程式碼 size可以大 到1MB甚至是2MB (ㄧ般Boot Loader不可能這麼大),所以我有機會玩一個這麼大的Boot Loader也真是很榮幸的啦!

廢話不多說,我就先針對我前面提到的Build.exe 內的程式碼說明;

底下是我的Build.c 內的程式碼片段,其實我就只有使用到fopen() 、fputc() ...等基本的函數去讀寫一個檔案,所以可以很容易的把我組譯好的MyBIOS.bin 跟EC.bin 塞進去同一個檔案內,做法其實很簡單,就是像我下面做法一樣,先利用fopen()開啟檔案,然後在把你要的資料寫進去檔案,只是寫的時候你要考慮file offset 位置的問題,因為當你燒錄到BIOS part中的時候,CPU是會固定重FFFF_FFF0h的位址讀取第一條指令,因此你要像我前面說的一樣,把MyBios.bin放在固定的位址中。

//建立一個空白的MyBIOS.ROM , 裡面資料都是00h
void show_help(void)
{
printf("Build.exe v1.0.0 by Harrison Hsieh \n");
printf("===========================================\n");
printf("/C Init MyBIOS.ROM \n");
printf("/B [EC] [BIOS] Add Rom \n");
printf("Output : MyBios.ROM \n");
}
void InitBiosROM(char *argv[])

{
FILE *fo;
long i;
if ((fo = fopen (BiosRom, "wb")) == (FILE *) NULL)
{
exit(1);
}

for(i=0 ; i<= BIOSSIZE ; i++) //1MB
{
fputc(0x00,fo);
}
/* All done, close the file */
fclose (fo);
}

在說明完Build.c內的做法後,接著說明MyBIOS.bin 內的程式碼撰寫;
其實在MyBios.asm 中,我只有做4 件事情:

1. 設定好FFFF_FFF0h的第一條指令
2.開啟BigReal Mode (因為我要設定ICH9的RCRB內的暫存器,所以要開啟)
3.設定ICH9內的暫存器,把所有Port 80h的訊號轉送到LPC介面(我的Post card走LPC界面,所以要設定)
4.輸出99h 到Port 80h(所以LPC介面上面的Post card就會顯示99h)

底下是我的MyBIOS.asm 內的程式碼片段:

COLDBOOT:
CLI
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 1. Enable big real mode
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
JMPREG di,Make4GBSegmentDI

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 2. Set RCRB base address
;; 3. Config ICH9 Register
;; 4. Out 99h to Port 80h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
....
mov dx, 0cfch
mov eax,RCRB_BaseAddr
out dx, eax
....
and BYTE PTR es:[esi], NOT (04h) ; RCRB+xxxxh bit 2=0 Output to LPC
....
fPostCode:
mov al,099h
mov dx,80h
out dx,al
jmp fPostCode ;無窮回圈ㄧ直顯示99h
...
...
wbinvd ; ...begins here on power up
PUBLIC POWER
POWER:
JMP COLDBOOT ; first jump
DB '11/14/07',00,00,00 ; My release marker

以上就是我撰寫的程式碼內容的說明,其實沒有用到什麼特別的東西,如果說比較難的部份大概就是如何把程式碼塞到正確的位址吧。

總結:
純手工打造你自己的x86 BIOS 文章(1)~(4) 在這邊就做一個結束,在這幾篇文章中的實驗我主要是要幫助剛入門的BIOS新手去了解整個BIOS vendor提供的BIOS環境架構以及實際上BIOS code撰寫的第一步,因為很多東西都是入門的第一步比較難。

還記得ㄧ年前我剛入行的時候,我們學長跟我說寫BIOS最簡單的方式就是自己把一個BIOS寫到能開機你大概就已經學會了,雖然我離能自己寫到開機還有一段距離,不過在學習的過程中也學到了很多東西,因此當初會想自己純手工寫一個能讓x86 CPU執行一段BIOS code的環境也是希望能幫助更多BIOS入門時遇到挫折的朋友 ^^Y。

星期三, 11月 28, 2007

純手工打造你自己的x86 BIOS(3)

在前面兩篇文章中的描述中其實大家就應該可以知道我的實驗環境由幾個部份所組成,所以我這邊假設 "如果我是ㄧ個BIOS Vendor",我將會如何描述我前面所說的那些部分。

在我的實驗中,整個BIOS Build Environment 我們可以得知如同下圖的架構,我在後面將分別對這些部份做ㄧ個簡單的說明。



1.Source code : 這就是我的MyBios.asm,只有一個檔案,ㄧ般我會放在某個目錄內,大家可以想一下如果擴充成7000多個檔案的時候,你會放同一個目錄嗎? 如果分類你要如何分? 如果修改,你要直接改嗎? 還是採用什麼方式去覆蓋?

2. Build Settings : 我使用的是MASM,而他在我的C:\MASM,假如你是利用makefile產生結果,那麼你就會需要設定一些工具的路徑,組譯或是編譯的程式是哪ㄧ個,參數為何...等。

3.Build Tools : 像我提到的Build.exe就是我自己寫的,用來輔助建立BIOS Image時所使用,所以當你的環境越來越大的時候,所使用的Tools可能就不只一個。

4.Build : 當上面的部分都結合在一起後,就可以產生出結果,ㄧ般我們可以利用makefile 方式把上面步驟都結合在一起,然後就可以方便的產生出結果。

5.BIOS Image : 在我的實驗中,產生的結果就是MyBIOS.ROM。

結論:
實驗中大家可以發現,其實BIOS vendor所提供的環境基本的本質很簡單,只是當你在實做的時候你會遇到一些問題,而你在解決這些問題的時候不知不覺整個架構就會越來越複雜,因此當我們接觸到一個成熟的BIOS Build environment時,就會需要了解更多的東西以便我們更能夠駕馭BIOS vendor所提供的環境。

前面這幾篇文章大致上描述出BIOS Build Environment的基本架構,所以當你想要寫一個BIOS然後提供給別人一個環境去撰寫BIOS時,其基本本質大概就是這樣,後面的文章中我會繼續介紹實際上MyBIOS.asm 中我們該撰寫什麼後我們才能夠在Port 80h 的7段顯示器上顯示99h。

星期二, 11月 27, 2007

純手工打造你自己的x86 BIOS(2)

電腦發展至今已經經過了很長的時間,許多遇到的問題也都被ㄧ些前輩解決了,因此目前學校或是市面上的書籍幾乎都是講解如何在一個"現成且成熟"的平台上發展。

例如很多書會教你寫VC/.net/Java ,但是,說到如何去寫編譯器、作業系統及開發BIOS的書就不多了,也因此大家比較專注於如何在成熟的平台上能夠快速/有效率/有系統性的開發以及提出解決問題的辦法,而像我因為興趣而去探討BIOS的本質的人就應該比較少吧,畢竟這些問題在之前的前輩都已經遭遇過,也提出了很好的解決方式,所以才會有目前一些實力堅強的BIOS 供應商的存在,因此也沒必要像我這樣純手工打造。

在前ㄧ篇的文章中我已經大致上描述了一下我的實驗方式,這邊就針對整個流程作ㄧ些詳細的介紹。

在純手工打造你自己的x86 BIOS(1) 中有提到,你可以學習到的東西是比較基本的概念,所以我並不會把完整的Sample code貼上來,畢竟教釣魚比給魚吃還重要,因此請大家輕鬆看待我的拙作(小弟也只入行1年多 ,還請前輩還多多指導)。

前一篇文章中所提到的核心部份在於我撰寫了64K 的BIOS程式碼 (MyBIOS.asm),實際不到64k ,只是我利用了填00h的方式填滿到64k。

而組譯與連結是透過ML.EXE ,輸出的是一個MyBIOS.exe ,而這個是一個DOS下的執行檔,所以裡面有MZ Header ,因為被多加了這個Header 因此MyBIOS.exe約65k ,而我會再利用Build tools取出裡面的64k ,然後變成MyBios.bin,當然這只是最簡單的方法而已,但不是唯一。

[註] EXE2BIN 只能轉換小於64k 的檔案,所以這邊不能使用它,所以我才自己轉換。



當取出了MyBios.bin 之後連同EC.bin 經由Build.exe 產生一個1MB 大小的BIOS ROM Image file,然後把位置固定住。

固定位址是因為:
1. 我的範例中的Platform 上面的EC Controller是採用Share ROM方式,也就是把EC BIOS包在System BIOS中,因此我們需要固定住位址,這樣子EC Controller 才能去System BIOS中讀取EC BIOS的程式碼並且執行。

2.由於x86 CPU讀取第一條指令是在 FFFF_FFF0h,所以我們必須要把BIOS code固定在尾端往下算的64k 範圍內,如下圖所示:

圖中可以看到整個BIOS ROM Image file是1MB ,其中64k是EC code另外64k是BIOS code,然後擺放在1MB 檔案中的位置就如上圖所示,其餘空白的地方我都是填00h/ffh (須看BIOS ROM Spec中說明空白是00h/ffh)

總結:

●MyBios.asm 負責CPU 第一條指令以及組態CPU 模式還有設定Port 80h的輸出並且輸出一個99h 到Port 80h

●EC.bin EC的BIOS Code,由EC BIOS工程師撰寫,我只是拿來用而已

●Build.exe 會先產生一個1MB 空白的BIOS ROM Image,然後把上面兩個bin file塞到先前產生的1MB 空白BIOS ROM Image,並固定其擺放位址,而擺放時並沒有考慮任何File System的架構問題,而是直接塞。

●MyBIOS.ROM 產生出的MyBIOS.ROM就是要用來燒入到BIOS part中的檔案,也就是類似一般大家在Flash BIOS時的那個檔案。

C:\> Flash.exe /all MyBIOS.ROM

上面是一般大家使用某個Flash Utiltity 時會打的一些指令,因為工具不同所以參數也不同,不過相同的是都會有一個BIOS ROM Image file(例如MyBios.ROM)

另外這邊有點不ㄧ樣的地方在於我沒有自己寫Flash Utility(我們BIOS裝在EC Controller下,而我又懶的看EC Spec),所以沒辦法像上面方式使用某個工具去更新 BIOS ROM,況且你們如果要實驗相同的東西,Flash Utiltiy也不能共用,所以這部份有興趣的人就自己研究一下你們公司內是怎樣撰寫這部份的工具。

而我的燒錄方式是採用EC Controller提供的燒入器,所以直接點選我的MyBios.ROM就可以燒進去BIOS Part了,而這部份也不多做說明。

由於整個實驗我才花了1.5天時間(0.5天寫Build.exe + 1天寫MyBios.asm),所以很多地方沒考慮進去,希望各位有其他意見請告訴我,謝謝!

~未完待續~

星期日, 11月 18, 2007

純手工打造你自己的x86 BIOS(1)

存放BIOS的設備從早期都放在EEPROM到現在的Flash ROM,一路上的演變已經可以寫成一部BIOS歷史課本。

在早期的BIOS中,BIOS本身程式碼就是用來當成一個Boot Loader,但是由於後來的晶片功能越來越強大且BIOS除了初始化硬體設備之外還要協助OS去支援一些功能,所以整個BIOS程式碼就已經變成了一個龐然大物,而維護整個BIOS程式碼也非一個人的能力所及。

因此後來的BIOS程式碼都是由一些BIOS供應商來負責維護,各家BIOS供應商會有自己的撰寫方式與架構。正因為如此,開發BIOS的程式碼目前也都是使用各家廠商所提供的開發環境來建構。

而這篇文章的目的在於如何在目前的PC架構下純手工打造一個屬於自己的BIOS環境以及撰寫一個簡單的BIOS程式碼可以讓系統輸出一個值到Port 80h,我同事問我為什麼不寫一個mini BIOS可以開到DOS下去,因為如同我上述所說,寫是可以寫啦,要花很多的精力跟體力,這邊只是拋磚引玉說一個大概,然後描述一下如果自己真的要撰寫一個BIOS 要如何做? 或許有些人有興趣可以找幾個朋友一起寫BIOS,或許哪一天就可以開ㄧ家台灣BIOS供應商...(呵呵,我自己在幻想啦!)

需要用到的工具以及相關知識:
1.MASM 6.15
2.Turbo C++ 3.0
3.基本組合語言撰寫能力
4.基本C語言撰寫能力
5.IA32 Spec vol 1~3
6.EC BIOS ROM(可以請EC BIOS Eng協助)

實驗目的與方法:
1. 建立一個BIOS 開發環境
2. 建立一個1MB BIOS ROM file
3. 利用組合語言撰寫一個64kb 大小的BIOS程式碼的 binary file
4. 利用C語言撰寫一個Build Tools,並將EC與64k BIOS塞進去1MB BIOS ROM
5.利用燒入器將1MB BIOS ROM燒入到MLB中,並且上電後檢查Port 80h是否有正確的輸出我們程式碼中撰寫的值。

上面的程式撰寫部分不需要很強的能力,只要基本的C或是組合語言語法就可以了,所以算是基本入門,重點還是在我ㄧ直強調的地方 "懂架構才是重點,程式語言只是工具而已"...。

~未完待續~

星期三, 11月 14, 2007

BIOS 開發環境

這篇文章主要是概述 BIOS 開發環境下的一些基礎知識,有助於了解如何自己去撰寫一個屬於你自己的 BIOS。

大家都知道Legacy BIOS是使用Assembly 所撰寫,目前的UEFI則是使用C語言,但是不論是哪一種BIOS,其開發環境下的基礎知識都還是相同。

ㄧ般BIOS工程師開始學習BIOS Vendor所提供的BIOS Code時,最主要會學習下列幾個項目:

1.Build process : BIOS ROM是如何產生,這部份要去了解的是整個流程為何?
例如: (ㄧ堆Source code) --> 經過哪些Build process --> (產生BIOS.ROM)

2.Directory : BIOS Code的目錄架構為何? 哪些是屬於Framework/Kernel/Oem ?

3.Build Config : BIOS 相關參數的設定,每家Vendor設定方式都不同!

4.Build Tools : BIOS 建立時使用了哪些工具,ㄧ般都是MASM+VisualStudio+DDK+Bios Vendor自己開發的一些Tools。

5.Build your BIOS ROM : 如何建立你的第一個BIOS ROM,ㄧ般都是使用nmake/直接在IDE(整合開發環境下)下設定好,就可以直接去Buildㄧ個完整的BIOS ROM。

有了上述的整理分類,其實不難發現,實際上我們撰寫的BIOS code只是一個BIOS Vendor做出來的環境下(或稱為框架下)去"填寫"一些屬於我們的程式碼,就像是C語言的開發環境下你只需要知道在main(){.....} 的括號內去撰寫程式碼就好了,後面的事情BIOS Vendor都幫你做好了。

所以OEM/ODM端的BIOS能夠處理的東西都是比較偏向客戶端的需求而去撰寫一些程式碼,我們稱這些程式碼為"Features"。而這些部份就像你買Asus /Acer/...etc 不同家的電腦,裡面所能提供的功能會不同。

雖然好像看起來只是"填入"ㄧ些程式碼,但是這部分又與整個硬體運作以及BIOS Vendor所提供的程式碼的"穩定度"有很大的關係,所以如果只是單純修改這些程式碼約1年可以上手,但是如果要能處理bug,那麼可能就需要多年的經驗累積了。因此在這種OEM/ODM BIOS與BIOS Vendor分工合作的情況之下,OEM/ODM端的BIOS有自己負責解決問題的地方,而BIOS Vendor也有自己負責要處理的地方,大家相互合作。

說了這麼多,對於BIOS開發環境應該有所了解,但是最近案子開始在忙了,所以會寫Blog時間會比較少了,等過陣子等案子不忙的時候,我再告訴大家如何如果要實做一個類似BIOS vendor開發環境,你需要哪些工具,還有怎樣子做。