Note: Android x86 Bootloader

qummiboot 是一個EFI 的 Bootloader, 當初發展出來是因為Microsoft 主導了Windows 8/UEFI SecureBoot的需要, 透過 UEFI Shim 方法去做自我簽證

而Intel 在力推Android on Intel(ia32/amd64)時期, 就根據原本的gummboot 改寫了一個版本去吻合Android 的需要,它內部除了Shim, 還做了BCB(Boot Control Block) 傳遞替後面的Recovery/Bootloader 交換資料,並且也做了一個BIOS Update 的機制,實際上根據他的內容它可以作到更新Radio Firmware 也可以作到更新EC(embedded controller) 等其他需要更新的內容, 不由得想到之前工作被交代的任務是在Kernel 增加一個interface 跟EC配合的更新版本

不過後來Intel 重新做了一個叫做 KernelFlinger,實作了後來需要的A/B Update, DM-Verify , TEE-OS Access與幫助生產的Blobstore 等新增功能.

gummiboot 目前在Freedesktop 的版本已經被放棄,因為被改稱systemd-boot了, 但是Redhat 仍然有一個自己的版本

不過gummiboot 對我來說仍然是一個極具參考價值得版本, 因為在許多嵌入的x86系統上不見得只有上面提的的OS,  gummiboot 相對簡單的程式碼, 方便用來當做開發其他OS用的Bootloader 基礎

ref.
https://en.altlinux.org/UEFI_SecureBoot_mini-HOWTO
https://github.com/rhboot/shim
https://github.com/android-ia/platform_external_gummiboot
https://github.com/intel/kernelflinger
https://cgit.freedesktop.org/gummiboot/
https://github.com/todorez/tummiboot
https://github.com/android-ia/hardware_intel_efi_kernelflinger
https://www.phoronix.com/scan.php?page=news_item&px=Gummiboot-Is-Dead

 

廣告

Note: Read/Write UEFI variables under Windows 7 and later

簡單講, 用 GetFirmwareEnvironmentVariable 可以讀取 UEFI 的變數, SetFirmwareEnvironmentVariable 可以寫回變數內容! 很簡單的, 但是如果 Google 會發現一堆人求救需要 Example code, 我自己歸納有幾點問題要克服

  • 系統到底是不是用UEFI mode 安裝的, 有的系統實際上安裝因為開了CSM 模式, 最後裝出來的系統是走legacy mode, 如標準UEFI 目前大家應該都只有 FAT32 Filesystem support 但是很多人把install 檔案放到 NTFS system 上面, 根本就是透過 legacy mode 裝起來的. MSDN 有說. 透過下面的dummy variable string & dummy guid 可以透過 ERROR code 判斷到底是不是 UEFI mode 的系統!

call the function with a dummy firmware environment name such as an empty string (“") for the lpName parameter and a dummy GUID such as “{00000000-0000-0000-0000-000000000000}" for the lpGuid parameter. On a legacy BIOS-based system, or on a system that supports both legacy BIOS and UEFI where Windows was installed using legacy BIOS, the function will fail with ERROR_INVALID_FUNCTION. On a UEFI-based system, the function will fail with an error specific to the firmware, such as ERROR_NOACCESS, to indicate that the dummy GUID namespace does not exist.

  • 執行權限的問題實際上他需要比較高的權限才能執行, 執行帳號要有 SE_SYSTEM_ENVIRONMENT_NAME 的權限, 或是程式內要求提權
  • 然後就是 UEFI Variable 的 Name String & GUID, 這個可以透過 DXE App DumpVar(Phoenix)/DumpVariable(Intel) or uefidump(Ubuntu Linux) 等工具, 但是這些只能觀察到變數的 Name String & GUID, 變數到底是怎樣組成的結構無法得知! 比較好的方式是跟BIOS Engineer 合作 取得 Header File 才能理解哪些變數有啥功能, 跟位於struct 中的offset

下面就是簡單的範例, 範例中的 Name String & GUID 是針對某特定版本的機種, 所以實際上無法完全直接換到不同型號的主機板上測試

 

#include "stdafx.h"


void RasiePrivileges(void)
{
	HANDLE hToken;
	TOKEN_PRIVILEGES tkp;

	if (!OpenProcessToken(GetCurrentProcess(), 
			TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
			&hToken)) {
				printf("Failed OpenProcessToken\r\n");
				return;
	}

	LookupPrivilegeValue(NULL, SE_SYSTEM_ENVIRONMENT_NAME,
		&tkp.Privileges[0].Luid);
	tkp.PrivilegeCount = 1;
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	
	DWORD len;
	AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, &len);

	if (GetLastError() != ERROR_SUCCESS) {
		printf("Failed RasiePrivileges()\r\n");
		return;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	BYTE Val[4096];
	DWORD dwLen = 0;
	const TCHAR name[] = TEXT("BootOrder");
	const TCHAR guid[] = TEXT("{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}");
	const TCHAR system[] = TEXT("System");
	const TCHAR sys_guid[] = TEXT("{E947FCf9-DD01-4965-B808-32A7B6815657}");
	const unsigned int offset_TPM = 11; // TPM On/Off offset

#if 0
	const TCHAR name[] = TEXT("");
	const TCHAR guid[] = TEXT("{00000000-0000-0000-0000-000000000000}");
#endif

	printf("Change Boot Order\r\n");
	RasiePrivileges();

	dwLen = GetFirmwareEnvironmentVariable(
		name, guid, Val, 4096);

	if (dwLen == 0)
	{
		DWORD dwErr = GetLastError();
		printf("Failed, GetFirmwareEnvironmentVariable(), GetLastError return %d(0x%08x)\r\n",
			dwErr, dwErr);
		return 1;
	}

	printf("Dump BootOrder variable, Len:%d(0x%02x)\r\n", dwLen, dwLen);
	for(UINT32 i = 0; i < dwLen; i++) {
		printf("%02x", Val[i]);
		if (i%16 == 0 && i != 0) {
			printf("\r\n");
		} else {
			putchar(' ');
		}
	}

	printf("\r\nExchange BootOrder Item1 & Item2\r\n");
	BYTE Tmp[2];
	Tmp[0] = Val[0];
	Tmp[1] = Val[1];
	
	Val[0] = Val[2];
	Val[1] = Val[3];
	
	Val[2] = Tmp[0];
	Val[3] = Tmp[1];

	if (SetFirmwareEnvironmentVariable(name, guid, Val, dwLen) == TRUE) {
		printf("Success!! SetFirmwareEnvironmentVariable\r\n");
	} else {
		printf("Failed!! SetFirmwareEnvironmentVariable\r\n");
	}

	printf("Read TPM Support state\r\n");
	dwLen = GetFirmwareEnvironmentVariable(
		system, sys_guid, Val, 4096);

	if (dwLen == 0)
	{
		DWORD dwErr = GetLastError();
		printf("Failed, GetFirmwareEnvironmentVariable(), GetLastError return %d(0x%08x)\r\n",
			dwErr, dwErr);
		return 2;
	}
	
	if (Val[offset_TPM]) {
		printf("TPM Support is Enabled, will change to Disable\r\n");
	} else {
		printf("TPM Support is Disabled, will change to Enable\r\n");
	}
	Val[offset_TPM] = (Val[offset_TPM]) ? 0 : 1;

	if (SetFirmwareEnvironmentVariable(system, sys_guid, Val, dwLen) == TRUE) {
		printf("Success!! Change TPM Support Setting\r\n");
	} else {
		printf("Failed!! Can't change TPM Support Setting\r\n");
	}
	return 0;
}

Note: UEFI 定義的四種VFR 的 VARSTORE的型別

根據 28.2.5.6 Storage in UEFI 2.3 spec. 寫到UEFI目前可用在VFR Question的有 4種型別

1. Buffer Storage, 它是一個Buffer Storage Type, 用 EFI_IFR_VARSTORE OpCode表示, VFR 語法則是 varstore,  在Driver內要建立 EFI_HII_CONFIG_ACCESS_PROTOCOL 中的 ExtraceConfig() 與 RouteConfig(), 讓Framwork去存取 Var 的Buffer

2.  EFI Storage, 它是一種特定的 Buffer Storage, 用 EFI_IFR_EFIVARSTORE OpCode表示, VFR語法是 efivarstore, 在Driver內它是透過 EFI runtimer Service 中的 GetVariable()/SetVariable()存取變數, 因此這個會回存 NVRAM(Flash)

3. Name/Value Storage, 主要是一個 String 型別, 用 EFI_IFR_VARSTORE_NAME_VALUE Opcode表示, VFR語法是 namevaluevarstore,  在Driver內一樣要提供EFI_HII_CONFIG_ACCESS_PROTOCOL 來存取與儲存變數.

4. Date/Time Storage, 這就是Date/Time的專屬型別, 用 EFI_HII_DATE/EFI_HII_TIME OpCode表示,  VFR則直接就是data/time, 在Driver內就透過 GetTime()/SetTime(), GetWakeupTime()/SetWakeupTime() 存取之

當FormBrowse DXE使用 Buffer Storage 時, 會透過 EFI_HII_CONFIG_ACCESS_PROTOCOL,呼叫 ExtraceConfig(), 會透過Request 的EFI_STRING 將需要的 Var information 傳進來, 底下是該 Request String的一些 Example. 如果是一個Varstore的集合, 會有Byte offset, Width 的值是以Byte為單位, width為1時代表是一個byte. 而NameValue則直接傳進 Var Name

  • GUID=f4274aa000df424db55239511302113d&NAME=004d0079004900660072004e00560044006100740061&PATH=010414008db653c1fceb8e48b110662867745b877fff0400&OFFSET=DE&WIDTH=10
  • GUID=f4274aa000df424db55239511302113d&NAME=004d0079004900660072004e00560044006100740061&PATH=010414008db653c1fceb8e48b110662867745b877fff0400&OFFSET=EF&WIDTH=1
  • GUID=f4274aa000df424db55239511302113d&NAME=004d0079004900660072004e00560044006100740061&PATH=010414008db653c1fceb8e48b110662867745b877fff0400&OFFSET=D5&WIDTH=1&O
  • GUID=f4274aa000df424db55239511302113d&NAME=&PATH=010414008db653c1fceb8e48b110662867745b877fff0400&NameValueVar0&NameValueVar1&NameValueVar2

Buffer Storage 可以是Struct.Member 的變數形式, 在Form的 DXE Driver被載入時要自己將Struct 給初始化, 並透過 NewPackageList 去加入HII 的Database

Note: VFR with Dynamic Refresh

在 EDKII 的 DriverSampleDxe 中可以看到 VFR.vfr中有 下面一個Form3的宣告, 重點的地方用Color標示

form formid = 3, title = STRING_TOKEN(STR_FORM3_TITLE);  // note formid is a variable (for readability) (UINT16) – also added Form to the line to signify the Op-Code

  suppressif  ideqval MyEfiVar == 111;
    text
      help = STRING_TOKEN(STR_TEXT_HELP),
      text = STRING_TOKEN(STR_TEXT_TEXT_1);
  endif;

  goto 1,
    prompt = STRING_TOKEN(STR_GOTO_FORM1), //MainSetupPage
    help   = STRING_TOKEN(STR_GOTO_HELP);

  numeric varid   = MyIfrNVData.DynamicRefresh,
          prompt  = STRING_TOKEN(STR_NUMERIC_MANUAL_PROMPT),
          help    = STRING_TOKEN(STR_NUMERIC_HELP0),
         flags   = INTERACTIVE,
          key     = 0x5678,
          minimum = 0,
          maximum = 0xff,
          step    = 0,
          default = 0,
          refresh interval = 3             // Refresh interval in seconds
  endnumeric;

然後在 DriverSample.c 中可以找到對應的Callback function “DriverCallBack()”, 在Dxe Entry Point“DriverSampleInit()”時, 透過 EFI_HII_CONFIG_ACCESS_PROTOCOL 來建立 CallBack link.

在CallBack 中是透過 EFI_BROWSER_ACTION_CHANGING 通知要 Refersh 了,  如果是原本 EDKII 的code, 會發現畫面一直閃, 原因是它會動態插入 Exit Item, 所以整個Form 會重畫! 只要簡單移除該段插入的code就可以, 如下

    case 0x5678:
      //
      // We will reach here once the Question is refreshed
      //
    #if 0 // By KunYi , remove dynamic add Exit Item
      //
      // Initialize the container for dynamic opcodes
      //
      StartOpCodeHandle = HiiAllocateOpCodeHandle ();
      ASSERT (StartOpCodeHandle != NULL);

      //
      // Create Hii Extend Label OpCode as the start opcode
      //
      StartLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (StartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL));
      StartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;
      StartLabel->Number       = LABEL_UPDATE2;

      HiiCreateActionOpCode (
        StartOpCodeHandle,                // Container for dynamic created opcodes
        0x1237,                           // Question ID
        STRING_TOKEN(STR_EXIT_TEXT),      // Prompt text
        STRING_TOKEN(STR_EXIT_TEXT),      // Help text
        EFI_IFR_FLAG_CALLBACK,            // Question flag
        0                                 // Action String ID
      );

      HiiUpdateForm (
        PrivateData->HiiHandle[0],  // HII handle
        &mFormSetGuid,              // Formset GUID
        0x3,                        // Form ID
        StartOpCodeHandle,          // Label for where to insert opcodes
        NULL                        // Insert data
        );

      HiiFreeOpCodeHandle (StartOpCodeHandle);
    #endif
      //
      // Refresh the Question value
      //
      PrivateData->Configuration.DynamicRefresh++;
      Status = gRT->SetVariable(
                      VariableName,
                      &mFormSetGuid,
                      EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                      sizeof (DRIVER_SAMPLE_CONFIGURATION),
                      &PrivateData->Configuration
                      );

      //
      // Change an EFI Variable storage (MyEfiVar) asynchronous, this will cause
      // the first statement in Form 3 be suppressed
      //
      MyVarSize = 1;
      MyVar = 111;
      Status = gRT->SetVariable(
                      L"MyVar",
                      &mFormSetGuid,
                      EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                      MyVarSize,
                      &MyVar
                      );
      break;

Note: UEFI & PI are independent Interfaces.

The below picture get form http://nchc.dl.sourceforge.net/project/efidevkit/Member%20Documents/2009%20Beijing%20UEFI%20Training%20Stuff/102%20The%20framework%20Overview%20with%20UEFI-PI1-2.pdf , to see page 9

image

So UEFI Forum provide both PI and UEFI spec.

Normally an OEM BIOS enginee focus on to modify PI part, to meet the HW specificaiton and collection platform infomation. such as GPIO setting/special device initilization sequence/DMI(SMBIOS)/ACPI Tables …

Addition: PI SCT : Platform Initialization Self Certification Test