Chapter 25: Graphical Boot Menu
Build a visual boot option selector with keyboard navigation, timeout countdown, and graphical rendering using GOP.
Overview
When to Build a Custom Boot Menu
Build a custom boot menu when you need to:
- Create a branded boot experience for your platform
- Add features not available in standard BDS
- Implement custom boot selection logic
- Support specialized hardware or use cases
| Use Case | Boot Menu Type | Features |
|---|---|---|
| OEM branding | Graphical | Custom colors, logos, themes |
| Multi-boot | Selection menu | OS choice with timeout |
| Kiosk/embedded | Minimal | Single option, no UI |
| Recovery | Special purpose | Boot to recovery partition |
| Security | Authenticated | Password or key required |
Custom Boot Menu vs Standard BDS:
| Factor | Custom Menu | Standard BDS |
|---|---|---|
| Appearance | Full control | Platform default |
| Logic | Custom rules | BootOrder only |
| Maintenance | Your code | Vendor updates |
| Compatibility | Test required | Tested by vendor |
| Best for | OEM/specialized | General use |
Who Builds Custom Boot Menus:
- OEM developers: Branded boot experience
- Embedded developers: Specialized boot flows
- Boot loader developers: Multi-OS support (GRUB, rEFInd)
- Security products: Pre-boot authentication
What We’re Building
A graphical boot menu that:
- Displays boot options in a visual menu
- Supports keyboard navigation (up/down, Enter, Escape)
- Shows countdown timer with auto-boot
- Renders graphics using GOP
- Falls back to text mode if GOP unavailable
┌──────────────────────────────────────────────────────────┐
│ │
│ UEFI Boot Menu v1.0 │
│ │
├──────────────────────────────────────────────────────────┤
│ │
│ ► Windows Boot Manager │
│ Ubuntu │
│ Fedora Linux │
│ UEFI Shell │
│ Enter Setup │
│ │
├──────────────────────────────────────────────────────────┤
│ [↑/↓] Select [Enter] Boot [Esc] Reboot [5s] │
└──────────────────────────────────────────────────────────┘
Architecture
flowchart TB
subgraph "Boot Menu Components"
INIT[Initialize Display]
ENUM[Enumerate Boot Options]
RENDER[Render Menu]
INPUT[Handle Input]
BOOT[Execute Boot]
end
subgraph "Display Modes"
GOP[GOP Graphics]
TEXT[Text Console]
end
subgraph "Data Sources"
BV[Boot Variables]
BO[BootOrder]
end
INIT --> GOP
INIT --> TEXT
ENUM --> BV
ENUM --> BO
RENDER --> INPUT
INPUT --> |Selection| BOOT
INPUT --> |Navigate| RENDER
INPUT --> |Timeout| BOOT
style INIT fill:#3498db,color:#fff
style RENDER fill:#2ecc71,color:#fff
style BOOT fill:#e74c3c,color:#fff
Initialization
Project Structure
BootMenu/
├── BootMenu.c # Main application
├── BootMenu.h # Declarations
├── Graphics.c # GOP rendering
├── Input.c # Keyboard handling
├── BootOptions.c # Boot option enumeration
├── BootMenu.inf # Module definition
└── Colors.h # Color definitions
INF File
# BootMenu.inf
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = BootMenu
FILE_GUID = ABCD1234-5678-9ABC-DEF0-123456789ABC
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
BootMenu.c
BootMenu.h
Graphics.c
Input.c
BootOptions.c
Colors.h
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
UefiBootServicesTableLib
UefiRuntimeServicesTableLib
BaseMemoryLib
MemoryAllocationLib
PrintLib
DevicePathLib
[Protocols]
gEfiGraphicsOutputProtocolGuid
gEfiSimpleTextInputExProtocolGuid
gEfiLoadedImageProtocolGuid
gEfiDevicePathProtocolGuid
[Guids]
gEfiGlobalVariableGuid
Header Files
// BootMenu.h
#ifndef _BOOT_MENU_H_
#define _BOOT_MENU_H_
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/DevicePathLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleTextInEx.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/DevicePath.h>
#include <Guid/GlobalVariable.h>
#define MENU_VERSION L"1.0"
#define MAX_BOOT_OPTIONS 20
#define DEFAULT_TIMEOUT 5
// Boot option structure
typedef struct {
UINT16 BootNum;
CHAR16 *Description;
UINT32 Attributes;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
} BOOT_OPTION;
// Menu state
typedef struct {
BOOT_OPTION Options[MAX_BOOT_OPTIONS];
UINTN OptionCount;
UINTN SelectedIndex;
UINTN Timeout;
BOOLEAN UseGraphics;
} MENU_STATE;
// Function prototypes
EFI_STATUS InitializeDisplay(OUT BOOLEAN *UseGraphics);
EFI_STATUS EnumerateBootOptions(IN OUT MENU_STATE *State);
EFI_STATUS RenderMenu(IN MENU_STATE *State);
EFI_STATUS HandleInput(IN OUT MENU_STATE *State, OUT BOOLEAN *ShouldBoot, OUT BOOLEAN *ShouldExit);
EFI_STATUS ExecuteBoot(IN BOOT_OPTION *Option);
VOID FreeBootOptions(IN MENU_STATE *State);
// Graphics functions
EFI_STATUS GopDrawRect(UINTN X, UINTN Y, UINTN Width, UINTN Height, UINT32 Color);
EFI_STATUS GopDrawText(UINTN X, UINTN Y, CHAR16 *Text, UINT32 FgColor, UINT32 BgColor);
EFI_STATUS GopClearScreen(UINT32 Color);
VOID GetScreenSize(OUT UINTN *Width, OUT UINTN *Height);
#endif // _BOOT_MENU_H_
// Colors.h
#ifndef _COLORS_H_
#define _COLORS_H_
// ARGB color definitions (for GOP)
#define COLOR_BLACK 0x00000000
#define COLOR_WHITE 0x00FFFFFF
#define COLOR_GRAY 0x00808080
#define COLOR_DARK_GRAY 0x00404040
#define COLOR_LIGHT_GRAY 0x00C0C0C0
#define COLOR_BLUE 0x000000FF
#define COLOR_DARK_BLUE 0x00000080
#define COLOR_LIGHT_BLUE 0x008080FF
#define COLOR_GREEN 0x0000FF00
#define COLOR_RED 0x00FF0000
#define COLOR_CYAN 0x0000FFFF
#define COLOR_YELLOW 0x00FFFF00
// Menu theme colors
#define MENU_BG_COLOR COLOR_DARK_BLUE
#define MENU_FG_COLOR COLOR_WHITE
#define MENU_BORDER_COLOR COLOR_LIGHT_BLUE
#define MENU_SELECT_BG COLOR_LIGHT_BLUE
#define MENU_SELECT_FG COLOR_BLACK
#define MENU_TITLE_COLOR COLOR_CYAN
#endif // _COLORS_H_
Configuration
Main Application
// BootMenu.c
#include "BootMenu.h"
#include "Colors.h"
// Global GOP protocol
EFI_GRAPHICS_OUTPUT_PROTOCOL *gGop = NULL;
/**
Main entry point for Boot Menu application.
**/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
MENU_STATE State;
BOOLEAN ShouldBoot = FALSE;
BOOLEAN ShouldExit = FALSE;
EFI_EVENT TimerEvent;
UINT64 TimerPeriod = 10000000; // 1 second in 100ns units
// Initialize state
ZeroMem(&State, sizeof(State));
State.Timeout = DEFAULT_TIMEOUT;
// Initialize display (GOP or text)
Status = InitializeDisplay(&State.UseGraphics);
if (EFI_ERROR(Status)) {
Print(L"Failed to initialize display: %r\n", Status);
return Status;
}
// Enumerate boot options
Status = EnumerateBootOptions(&State);
if (EFI_ERROR(Status) || State.OptionCount == 0) {
Print(L"No boot options found\n");
gBS->Stall(3000000);
return EFI_NOT_FOUND;
}
// Create timer event for countdown
Status = gBS->CreateEvent(
EVT_TIMER,
TPL_CALLBACK,
NULL,
NULL,
&TimerEvent
);
if (!EFI_ERROR(Status)) {
gBS->SetTimer(TimerEvent, TimerPeriodic, TimerPeriod);
}
// Main menu loop
while (!ShouldBoot && !ShouldExit) {
// Render menu
Status = RenderMenu(&State);
// Handle input with timeout
Status = HandleInput(&State, &ShouldBoot, &ShouldExit);
// Check timer for countdown
if (State.Timeout > 0) {
EFI_STATUS TimerStatus;
UINTN Index;
TimerStatus = gBS->CheckEvent(TimerEvent);
if (TimerStatus == EFI_SUCCESS) {
State.Timeout--;
if (State.Timeout == 0) {
ShouldBoot = TRUE;
}
}
}
}
// Clean up timer
gBS->CloseEvent(TimerEvent);
// Execute boot if requested
if (ShouldBoot && State.SelectedIndex < State.OptionCount) {
// Clear screen before boot
if (State.UseGraphics) {
GopClearScreen(COLOR_BLACK);
} else {
gST->ConOut->ClearScreen(gST->ConOut);
}
Status = ExecuteBoot(&State.Options[State.SelectedIndex]);
if (EFI_ERROR(Status)) {
Print(L"Boot failed: %r\n", Status);
gBS->Stall(3000000);
}
}
// Free resources
FreeBootOptions(&State);
return EFI_SUCCESS;
}
Boot Options Enumeration
// BootOptions.c
#include "BootMenu.h"
/**
Enumerate boot options from UEFI variables.
**/
EFI_STATUS
EnumerateBootOptions (
IN OUT MENU_STATE *State
)
{
EFI_STATUS Status;
UINT16 *BootOrder = NULL;
UINTN BootOrderSize = 0;
UINTN Index;
// Get BootOrder variable size
Status = gRT->GetVariable(
L"BootOrder",
&gEfiGlobalVariableGuid,
NULL,
&BootOrderSize,
NULL
);
if (Status != EFI_BUFFER_TOO_SMALL) {
return EFI_NOT_FOUND;
}
// Allocate and get BootOrder
BootOrder = AllocatePool(BootOrderSize);
if (BootOrder == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gRT->GetVariable(
L"BootOrder",
&gEfiGlobalVariableGuid,
NULL,
&BootOrderSize,
BootOrder
);
if (EFI_ERROR(Status)) {
FreePool(BootOrder);
return Status;
}
// Enumerate each boot option
State->OptionCount = 0;
for (Index = 0; Index < BootOrderSize / sizeof(UINT16); Index++) {
if (State->OptionCount >= MAX_BOOT_OPTIONS) {
break;
}
Status = LoadBootOption(BootOrder[Index],
&State->Options[State->OptionCount]);
if (!EFI_ERROR(Status)) {
State->OptionCount++;
}
}
FreePool(BootOrder);
return (State->OptionCount > 0) ? EFI_SUCCESS : EFI_NOT_FOUND;
}
/**
Load a single boot option from variable.
**/
EFI_STATUS
LoadBootOption (
IN UINT16 BootNum,
OUT BOOT_OPTION *Option
)
{
EFI_STATUS Status;
CHAR16 BootName[16];
UINT8 *BootData = NULL;
UINTN BootDataSize = 0;
UINTN DescLen;
UnicodeSPrint(BootName, sizeof(BootName), L"Boot%04X", BootNum);
// Get size
Status = gRT->GetVariable(
BootName,
&gEfiGlobalVariableGuid,
NULL,
&BootDataSize,
NULL
);
if (Status != EFI_BUFFER_TOO_SMALL) {
return EFI_NOT_FOUND;
}
// Allocate and get data
BootData = AllocatePool(BootDataSize);
if (BootData == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gRT->GetVariable(
BootName,
&gEfiGlobalVariableGuid,
NULL,
&BootDataSize,
BootData
);
if (EFI_ERROR(Status)) {
FreePool(BootData);
return Status;
}
// Parse EFI_LOAD_OPTION structure
// Offset 0: Attributes (UINT32)
// Offset 4: FilePathListLength (UINT16)
// Offset 6: Description (null-terminated CHAR16)
// After Description: DevicePath
Option->BootNum = BootNum;
Option->Attributes = *(UINT32 *)BootData;
// Copy description
CHAR16 *SrcDesc = (CHAR16 *)(BootData + 6);
DescLen = StrLen(SrcDesc) + 1;
Option->Description = AllocatePool(DescLen * sizeof(CHAR16));
if (Option->Description != NULL) {
StrCpyS(Option->Description, DescLen, SrcDesc);
}
// Get device path (after description)
UINT16 FilePathLen = *(UINT16 *)(BootData + 4);
UINT8 *DevicePathStart = BootData + 6 + DescLen * sizeof(CHAR16);
Option->DevicePath = AllocateCopyPool(FilePathLen, DevicePathStart);
FreePool(BootData);
return EFI_SUCCESS;
}
/**
Execute the selected boot option.
**/
EFI_STATUS
ExecuteBoot (
IN BOOT_OPTION *Option
)
{
EFI_STATUS Status;
EFI_HANDLE ImageHandle;
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
if (Option == NULL || Option->DevicePath == NULL) {
return EFI_INVALID_PARAMETER;
}
Print(L"Booting: %s\n", Option->Description);
DevicePath = Option->DevicePath;
// Load the image
Status = gBS->LoadImage(
FALSE,
gImageHandle,
DevicePath,
NULL,
0,
&ImageHandle
);
if (EFI_ERROR(Status)) {
Print(L"LoadImage failed: %r\n", Status);
return Status;
}
// Start the image
Status = gBS->StartImage(ImageHandle, NULL, NULL);
// If we return, unload the image
gBS->UnloadImage(ImageHandle);
return Status;
}
/**
Free boot option resources.
**/
VOID
FreeBootOptions (
IN MENU_STATE *State
)
{
for (UINTN i = 0; i < State->OptionCount; i++) {
if (State->Options[i].Description != NULL) {
FreePool(State->Options[i].Description);
}
if (State->Options[i].DevicePath != NULL) {
FreePool(State->Options[i].DevicePath);
}
}
State->OptionCount = 0;
}
Graphics Rendering
// Graphics.c
#include "BootMenu.h"
#include "Colors.h"
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gGop;
// Font dimensions (using built-in 8x16 font approximation)
#define CHAR_WIDTH 8
#define CHAR_HEIGHT 16
/**
Initialize display - try GOP first, fall back to text.
**/
EFI_STATUS
InitializeDisplay (
OUT BOOLEAN *UseGraphics
)
{
EFI_STATUS Status;
*UseGraphics = FALSE;
// Try to get GOP
Status = gBS->LocateProtocol(
&gEfiGraphicsOutputProtocolGuid,
NULL,
(VOID **)&gGop
);
if (!EFI_ERROR(Status) && gGop != NULL) {
*UseGraphics = TRUE;
// Optionally set a preferred video mode
// For now, use current mode
}
// Initialize console regardless
gST->ConOut->SetAttribute(gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLACK);
gST->ConOut->ClearScreen(gST->ConOut);
gST->ConOut->EnableCursor(gST->ConOut, FALSE);
return EFI_SUCCESS;
}
/**
Get screen dimensions.
**/
VOID
GetScreenSize (
OUT UINTN *Width,
OUT UINTN *Height
)
{
if (gGop != NULL) {
*Width = gGop->Mode->Info->HorizontalResolution;
*Height = gGop->Mode->Info->VerticalResolution;
} else {
// Text mode dimensions (approximate)
*Width = 80 * CHAR_WIDTH;
*Height = 25 * CHAR_HEIGHT;
}
}
/**
Clear screen with solid color.
**/
EFI_STATUS
GopClearScreen (
UINT32 Color
)
{
if (gGop == NULL) {
gST->ConOut->ClearScreen(gST->ConOut);
return EFI_SUCCESS;
}
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Pixel;
Pixel.Blue = Color & 0xFF;
Pixel.Green = (Color >> 8) & 0xFF;
Pixel.Red = (Color >> 16) & 0xFF;
Pixel.Reserved = 0;
return gGop->Blt(
gGop,
&Pixel,
EfiBltVideoFill,
0, 0,
0, 0,
gGop->Mode->Info->HorizontalResolution,
gGop->Mode->Info->VerticalResolution,
0
);
}
/**
Draw a filled rectangle.
**/
EFI_STATUS
GopDrawRect (
UINTN X,
UINTN Y,
UINTN Width,
UINTN Height,
UINT32 Color
)
{
if (gGop == NULL) {
return EFI_UNSUPPORTED;
}
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Pixel;
Pixel.Blue = Color & 0xFF;
Pixel.Green = (Color >> 8) & 0xFF;
Pixel.Red = (Color >> 16) & 0xFF;
Pixel.Reserved = 0;
return gGop->Blt(
gGop,
&Pixel,
EfiBltVideoFill,
0, 0,
X, Y,
Width, Height,
0
);
}
/**
Render the boot menu.
**/
EFI_STATUS
RenderMenu (
IN MENU_STATE *State
)
{
UINTN ScreenWidth, ScreenHeight;
UINTN MenuWidth, MenuHeight;
UINTN MenuX, MenuY;
UINTN i;
GetScreenSize(&ScreenWidth, &ScreenHeight);
// Calculate menu dimensions
MenuWidth = 500;
MenuHeight = 100 + (State->OptionCount * 30);
MenuX = (ScreenWidth - MenuWidth) / 2;
MenuY = (ScreenHeight - MenuHeight) / 2;
if (State->UseGraphics && gGop != NULL) {
// Clear screen
GopClearScreen(MENU_BG_COLOR);
// Draw menu background
GopDrawRect(MenuX, MenuY, MenuWidth, MenuHeight, COLOR_DARK_GRAY);
// Draw border
GopDrawRect(MenuX, MenuY, MenuWidth, 3, MENU_BORDER_COLOR);
GopDrawRect(MenuX, MenuY + MenuHeight - 3, MenuWidth, 3, MENU_BORDER_COLOR);
GopDrawRect(MenuX, MenuY, 3, MenuHeight, MENU_BORDER_COLOR);
GopDrawRect(MenuX + MenuWidth - 3, MenuY, 3, MenuHeight, MENU_BORDER_COLOR);
// Title would go here (requires font rendering)
// For now, fall through to text console for strings
}
// Use text console for text rendering
// (GOP text rendering requires a font library)
gST->ConOut->SetCursorPosition(gST->ConOut, 0, 0);
// Print title
gST->ConOut->SetAttribute(gST->ConOut, EFI_CYAN | EFI_BACKGROUND_BLACK);
Print(L"\n\n");
Print(L" UEFI Boot Menu v%s\n", MENU_VERSION);
Print(L" ────────────────────────────────────────────────\n\n");
// Print boot options
for (i = 0; i < State->OptionCount; i++) {
if (i == State->SelectedIndex) {
gST->ConOut->SetAttribute(gST->ConOut,
EFI_BLACK | EFI_BACKGROUND_LIGHTGRAY);
Print(L" ► ");
} else {
gST->ConOut->SetAttribute(gST->ConOut,
EFI_WHITE | EFI_BACKGROUND_BLACK);
Print(L" ");
}
Print(L"%-50s\n", State->Options[i].Description);
}
// Print footer
gST->ConOut->SetAttribute(gST->ConOut, EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK);
Print(L"\n ────────────────────────────────────────────────\n");
Print(L" [↑/↓] Select [Enter] Boot [Esc] Exit");
if (State->Timeout > 0) {
Print(L" [%ds]", State->Timeout);
}
Print(L"\n");
gST->ConOut->SetAttribute(gST->ConOut, EFI_WHITE | EFI_BACKGROUND_BLACK);
return EFI_SUCCESS;
}
Input Handling
// Input.c
#include "BootMenu.h"
/**
Handle keyboard input.
**/
EFI_STATUS
HandleInput (
IN OUT MENU_STATE *State,
OUT BOOLEAN *ShouldBoot,
OUT BOOLEAN *ShouldExit
)
{
EFI_STATUS Status;
EFI_INPUT_KEY Key;
UINTN Index;
*ShouldBoot = FALSE;
*ShouldExit = FALSE;
// Wait for key with timeout (100ms)
Status = gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &Index);
if (Status == EFI_TIMEOUT) {
return EFI_SUCCESS;
}
// Check for key
Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);
if (EFI_ERROR(Status)) {
return EFI_SUCCESS;
}
// Reset timeout on any key
State->Timeout = 0;
switch (Key.ScanCode) {
case SCAN_UP:
if (State->SelectedIndex > 0) {
State->SelectedIndex--;
}
break;
case SCAN_DOWN:
if (State->SelectedIndex < State->OptionCount - 1) {
State->SelectedIndex++;
}
break;
case SCAN_ESC:
*ShouldExit = TRUE;
break;
case SCAN_NULL:
// Check Unicode character
switch (Key.UnicodeChar) {
case CHAR_CARRIAGE_RETURN:
case CHAR_LINEFEED:
*ShouldBoot = TRUE;
break;
case 'q':
case 'Q':
*ShouldExit = TRUE;
break;
}
break;
}
return EFI_SUCCESS;
}
Porting Guide
Platform Considerations
| Aspect | Consideration |
|---|---|
| GOP | Not available on all systems; provide text fallback |
| Resolution | Query GOP for current resolution |
| Color Depth | Use 32-bit BGRA for compatibility |
| Timeout | Read from Boot Manager settings |
| Secure Boot | May require signed binaries |
Text-Only Mode
// Simplified text-only rendering
EFI_STATUS
RenderMenuText (
IN MENU_STATE *State
)
{
gST->ConOut->ClearScreen(gST->ConOut);
gST->ConOut->SetCursorPosition(gST->ConOut, 0, 2);
Print(L" UEFI Boot Menu\n");
Print(L" ══════════════\n\n");
for (UINTN i = 0; i < State->OptionCount; i++) {
if (i == State->SelectedIndex) {
gST->ConOut->SetAttribute(gST->ConOut,
EFI_BLACK | EFI_BACKGROUND_WHITE);
}
Print(L" %s\n", State->Options[i].Description);
gST->ConOut->SetAttribute(gST->ConOut,
EFI_WHITE | EFI_BACKGROUND_BLACK);
}
Print(L"\n [Up/Down] Navigate [Enter] Boot [Esc] Exit\n");
return EFI_SUCCESS;
}
Adding Font Support
For full graphical text rendering, you would need:
// Simple bitmap font structure
typedef struct {
UINT8 Width;
UINT8 Height;
UINT8 Glyphs[256][16]; // 8x16 bitmap font
} BITMAP_FONT;
// Render a character
VOID
RenderChar (
UINTN X,
UINTN Y,
CHAR16 Char,
UINT32 FgColor,
UINT32 BgColor
)
{
// Get glyph bitmap
UINT8 *Glyph = Font.Glyphs[Char & 0xFF];
for (UINTN Row = 0; Row < Font.Height; Row++) {
for (UINTN Col = 0; Col < Font.Width; Col++) {
UINT32 Color = (Glyph[Row] & (0x80 >> Col))
? FgColor : BgColor;
// Set pixel at (X + Col, Y + Row)
}
}
}
Testing
Build and Run
# Build
build -a X64 -t GCC5 -p UefiGuidePkg/UefiGuidePkg.dsc
# Test in QEMU
qemu-system-x86_64 \
-drive if=pflash,format=raw,file=OVMF_CODE.fd,readonly=on \
-drive if=pflash,format=raw,file=OVMF_VARS.fd \
-drive format=raw,file=esp.img \
-serial stdio
Expected Behavior
- Menu displays with available boot options
- Arrow keys navigate selection
- Enter boots selected option
- Escape exits to shell
- Countdown auto-boots after timeout
Extension Ideas
- Themes: Add multiple color schemes
- Background image: Load and display BMP background
- Password protection: Require password for certain options
- Boot once: Add option to boot once without saving
- Edit options: Allow editing boot parameters
Source Code: See
examples/UefiGuidePkg/BootMenu/for the complete buildable example.
References
Next: Chapter 26: Network Application - Build an HTTP client.