Posted on

Edk2

Edk2 is the official UEFI implementation. One problem I encountered is that it doesn't provide a way to specify the default boot entry.

If you already know what the default boot device or application should be, then it should be possible to set it in the 'to be flashed' file.

Here I made a patch that add a PCD option to choose which entry will be Boot0000, hence it will be booted first if no other value has been set in the NVStore.

Compile EDK2

See Getting Started with EDK II for complete compilation steps.

It boils down to the following:

# Download edk2 source
git clone \
   --filter=tree:0 --single-branch --depth 1 \
   --recurse-submodules --shallow-submodules \
   --branch "${EDK2_VERSION:-edk2-stable202308}" \
   "https://github.com/tianocore/edk2.git" \
   "edk2"

cd "edk2"
make -j 4 -C BaseTools Source/C
make -j 4 -C BaseTools Source/Python
mkdir -p edk2build
export WORKSPACE="$PWD/edk2build"
export PACKAGES_PATH="$PWD"
source edksetup.sh

Now our shell is setup with edk2 environment and we can build UEFI firmwares. But first, we'll add the patch.

Apply the patch

It can also be found in the form of a pull request
---
diff --git MdeModulePkg/MdeModulePkg.dec MdeModulePkg/MdeModulePkg.dec
index 0ff058b0..6ee074c3 100644
--- MdeModulePkg/MdeModulePkg.dec
+++ MdeModulePkg/MdeModulePkg.dec
@@ -2151,6 +2151,10 @@
   # @Prompt The value is use for Usb Network rate limiting supported.
   gEfiMdeModulePkgTokenSpaceGuid.PcdUsbNetworkRateLimitingFactor|100|UINT32|0x10000028

+  ## Indicate the first boot entry, does not set bootorder
+  # @Prompt First boot entry.
+  gEfiMdeModulePkgTokenSpaceGuid.PcdPlatformBootBoot0000|L""|VOID*|0x0000012e
+
 [PcdsPatchableInModule]
   ## Specify memory size with page number for PEI code when
   #  Loading Module at Fixed Address feature is enabled.
diff --git MdeModulePkg/Universal/BdsDxe/BdsDxe.inf MdeModulePkg/Universal/BdsDxe/BdsDxe.inf
index 5bac635d..557c2361 100644
--- MdeModulePkg/Universal/BdsDxe/BdsDxe.inf
+++ MdeModulePkg/Universal/BdsDxe/BdsDxe.inf
@@ -98,6 +98,7 @@
   gEfiMdeModulePkgTokenSpaceGuid.PcdTestKeyUsed                       ## CONSUMES
   gEfiMdeModulePkgTokenSpaceGuid.PcdCapsuleOnDiskSupport              ## CONSUMES
   gEfiMdeModulePkgTokenSpaceGuid.PcdPlatformRecoverySupport           ## CONSUMES
+  gEfiMdeModulePkgTokenSpaceGuid.PcdPlatformBootBoot0000              ## CONSUMES

 [Depex]
   TRUE
diff --git MdeModulePkg/Universal/BdsDxe/BdsEntry.c MdeModulePkg/Universal/BdsDxe/BdsEntry.c
index 72de8d32..dc773513 100644
--- MdeModulePkg/Universal/BdsDxe/BdsEntry.c
+++ MdeModulePkg/Universal/BdsDxe/BdsEntry.c
@@ -692,6 +692,9 @@ BdsEntry (
   EFI_STATUS                      BootManagerMenuStatus;
   EFI_BOOT_MANAGER_LOAD_OPTION    PlatformDefaultBootOption;
   BOOLEAN                         PlatformDefaultBootOptionValid;
+  EFI_BOOT_MANAGER_LOAD_OPTION    PlatformDefaultBoot0000;
+  BOOLEAN                         PlatformDefaultBoot0000Valid;
+  CONST CHAR16                    *Boot0000;

   HotkeyTriggered = NULL;
   Status          = EFI_SUCCESS;
@@ -799,6 +802,35 @@ BdsEntry (
     BootNext = NULL;
   }

+  Boot0000 = ((CONST CHAR16 *)PcdGetPtr (PcdPlatformBootBoot0000));
+  if (Boot0000 && (*Boot0000 != L'\0')) {
+    FilePath = ConvertTextToDevicePath (Boot0000);
+    if (FilePath == NULL) {
+      DEBUG ((DEBUG_ERROR, "Fail to allocate memory for default boot file path. Unable to boot.\n"));
+      CpuDeadLoop ();
+    }
+
+    PlatformDefaultBoot0000Valid = EfiBootManagerInitializeLoadOption (
+                                     &PlatformDefaultBoot0000,
+                                     0,
+                                     LoadOptionTypeBoot,
+                                     LOAD_OPTION_ACTIVE,
+                                     L"Default PlatformBoot",
+                                     FilePath,
+                                     NULL,
+                                     0
+                                     ) == EFI_SUCCESS;
+
+    DEBUG ((DEBUG_ERROR, "%d\n", PlatformDefaultBoot0000Valid));
+    ASSERT (PlatformDefaultBoot0000Valid == TRUE);
+    if (PlatformDefaultBoot0000Valid) {
+      EfiBootManagerLoadOptionToVariable (&PlatformDefaultBoot0000);
+      EfiBootManagerFreeLoadOption (&PlatformDefaultBoot0000);
+    }
+
+    FreePool (FilePath);
+  }
+
   //
   // Initialize the platform language variables
   //
--
2.43.0

Edk2 source code has CRLF line endings and has whitespace-only lines, which can confuse the patch command. However, git apply works fine.

In edk2 source code: git apply --ignore-whitespace -p0 Pcd-boot0000.patch

Build a firmware

With the patch applied edk2 accepts a new configuration key gEfiMdeModulePkgTokenSpaceGuid.PcdPlatformBootBoot0000, which takes an UTF-16 string.

To enter the correct value, we need first to find our firmware GUID and the application GUID:

find -name 'OvmfPkgX64.fdf' -exec grep FvNameGuid {} \; # Fv
grep FILE_GUID ShellPkg/Application/Shell/Shell.inf     # FvFile

It is then possible to choose the default boot:

  • Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4f1c-AD65-E05268D0B4D1) boots into shell
  • Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331) boots into uefi manager

It uses EfiDevicePathProtocol under the hood, you can guess more boot options from these files:

Hint: Anything that is recognized as a filesystem by edk2 can be booted directly with its path (/EFI/BOOT/grub.efi) Firmware memory can be mapped as a filesystem with LoadFileOnFv2.

Anyway, let's build:

export BOOT0000="Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)"
build \
    -t GCC5 \
    --buildtarget="RELEASE" \
    --arch=X64 \
    --platform=OvmfPkg/OvmfPkgX64.dsc \
    --pcd=gEfiMdeModulePkgTokenSpaceGuid.PcdPlatformBootBoot0000="L${BOOT0000}" # `L` prefix to convert to UTF-16

Test with QEMU

qemu-system-x86_64 -m 64M -enable-kvm -nographic -drive if=pflash,file="$(find -name OVMF.fd)",format=raw,readonly=on

It's good to know that QEMU will automatically create an NVStore for the firmware to use and will automatically set a BootOrder. This can be configured using fw_cfg.

Alternative

If you intend to flash the target device's NVRAM, you can achieve the same result with virt-fw-vars, see this stackoverflow post for more details.

Conclusion

Now you can choose the default boot at the compilation step and before the first boot without touching the NVStore.

That's all, good day!