Chapter 12: Boot and Runtime Services
Explore the two service tables that define everything UEFI firmware can do – and understand the critical transition between them.
12.1 The Two Service Tables
UEFI defines exactly two service tables, both accessible through the System Table:
// These global pointers are set up by UefiBootServicesTableLib
extern EFI_BOOT_SERVICES *gBS; // Boot Services
extern EFI_RUNTIME_SERVICES *gRT; // Runtime Services
extern EFI_SYSTEM_TABLE *gST; // System Table (contains both)
graph TD
ST["EFI_SYSTEM_TABLE (gST)"]
ST --> BS["EFI_BOOT_SERVICES (gBS)\n--- Available during boot only ---"]
ST --> RT["EFI_RUNTIME_SERVICES (gRT)\n--- Available always ---"]
ST --> CI["ConIn / ConOut / StdErr\nConsole I/O Protocols"]
ST --> CT["Configuration Tables\n(ACPI, SMBIOS, etc.)"]
BS --> BS1["Memory Services"]
BS --> BS2["Protocol Services"]
BS --> BS3["Event/Timer Services"]
BS --> BS4["Image Services"]
BS --> BS5["Driver Services"]
RT --> RT1["Variable Services"]
RT --> RT2["Time Services"]
RT --> RT3["Virtual Memory Services"]
RT --> RT4["Reset Services"]
RT --> RT5["Capsule Services"]
style ST fill:#1a1a2e,stroke:#e94560,color:#fff
style BS fill:#16213e,stroke:#0f3460,color:#fff
style RT fill:#533483,stroke:#e94560,color:#fff
12.2 Boot Services Reference
Boot Services are available from the moment the DXE phase begins until ExitBootServices() is called. After that, every function pointer in gBS becomes invalid.
12.2.1 Memory Services
| Function | Purpose |
|---|---|
AllocatePages() |
Allocate page-aligned memory |
FreePages() |
Free page-aligned memory |
AllocatePool() |
Allocate arbitrary-size memory |
FreePool() |
Free pool memory |
GetMemoryMap() |
Get the current memory map |
These were covered in depth in Chapter 11.
12.2.2 Protocol Handler Services
| Function | Purpose |
|---|---|
InstallProtocolInterface() |
Add a protocol to a handle |
UninstallProtocolInterface() |
Remove a protocol from a handle |
ReinstallProtocolInterface() |
Replace a protocol interface |
RegisterProtocolNotify() |
Get notified when a protocol is installed |
LocateHandle() |
Find handles with a protocol |
LocateHandleBuffer() |
Find handles (allocates buffer for you) |
HandleProtocol() |
Get a protocol from a handle (legacy) |
OpenProtocol() |
Get a protocol with access tracking |
CloseProtocol() |
Release a protocol |
OpenProtocolInformation() |
Query who has a protocol open |
LocateProtocol() |
Find the first instance of a protocol |
InstallMultipleProtocolInterfaces() |
Atomically install several protocols |
UninstallMultipleProtocolInterfaces() |
Atomically remove several protocols |
ProtocolsPerHandle() |
List all protocols on a handle |
LocateDevicePath() |
Find a handle by device path |
These were covered in Chapter 10.
12.2.3 Event Services
| Function | Purpose |
|---|---|
CreateEvent() |
Create a new event |
CreateEventEx() |
Create an event in an event group |
SetTimer() |
Arm a timer event |
WaitForEvent() |
Block until one of several events signals |
SignalEvent() |
Manually signal an event |
CloseEvent() |
Destroy an event |
CheckEvent() |
Non-blocking check if an event is signaled |
12.2.4 Image Services
| Function | Purpose |
|---|---|
LoadImage() |
Load a PE/COFF image into memory |
StartImage() |
Call the entry point of a loaded image |
UnloadImage() |
Unload an image from memory |
Exit() |
Return from an image (used by applications) |
ExitBootServices() |
Transition to runtime – the point of no return |
12.2.5 Driver Support Services
| Function | Purpose |
|---|---|
ConnectController() |
Connect drivers to a controller |
DisconnectController() |
Disconnect drivers from a controller |
12.2.6 Miscellaneous Services
| Function | Purpose |
|---|---|
SetWatchdogTimer() |
Set/disable the watchdog timer |
Stall() |
Busy-wait for a specified number of microseconds |
CopyMem() |
Memory copy (handles overlapping regions) |
SetMem() |
Fill memory with a byte value |
GetNextMonotonicCount() |
Get a monotonically increasing counter |
InstallConfigurationTable() |
Add/remove a configuration table entry |
CalculateCrc32() |
Calculate a CRC32 checksum |
12.3 Events and Timers
Events are UEFI’s mechanism for asynchronous notification, periodic callbacks, and synchronization. They are simpler than OS-level threading – UEFI is single-threaded and events fire only at safe points (TPL transitions).
12.3.1 Creating Events
EFI_STATUS
EFIAPI
gBS->CreateEvent (
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL,
IN VOID *NotifyContext OPTIONAL,
OUT EFI_EVENT *Event
);
Event types:
| Type flag | Meaning |
|---|---|
EVT_TIMER |
Can be armed with SetTimer() |
EVT_NOTIFY_SIGNAL |
Notification function called when signaled |
EVT_NOTIFY_WAIT |
Notification function called when checked/waited |
EVT_SIGNAL_EXIT_BOOT_SERVICES |
Signaled when ExitBootServices() is called |
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE |
Signaled when SetVirtualAddressMap() is called |
12.3.2 Timer Events
EFI_STATUS
EFIAPI
gBS->SetTimer (
IN EFI_EVENT Event,
IN EFI_TIMER_DELAY Type,
IN UINT64 TriggerTime // In 100-nanosecond units
);
| Timer type | Behavior |
|---|---|
TimerCancel |
Cancel a pending timer |
TimerPeriodic |
Signal repeatedly at the specified interval |
TimerRelative |
Signal once after the specified delay |
12.3.3 Complete Timer Example
This example creates a periodic timer that fires every second and prints a message:
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
STATIC UINTN mTickCount = 0;
VOID
EFIAPI
TimerCallback (
IN EFI_EVENT Event,
IN VOID *Context
)
{
mTickCount++;
//
// NOTE: Print() is not safe at elevated TPL in all implementations.
// Use DEBUG() in production code. This works for demonstration.
//
}
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_EVENT TimerEvent;
EFI_EVENT WaitEvents[2];
UINTN EventIndex;
EFI_EVENT KeyEvent;
//
// Create a periodic timer event (fires every 1 second)
// 10,000,000 * 100ns = 1 second
//
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
TimerCallback,
NULL,
&TimerEvent
);
if (EFI_ERROR (Status)) {
Print (L"Failed to create timer event: %r\n", Status);
return Status;
}
Status = gBS->SetTimer (
TimerEvent,
TimerPeriodic,
10000000 // 1 second in 100ns units
);
if (EFI_ERROR (Status)) {
gBS->CloseEvent (TimerEvent);
return Status;
}
Print (L"Timer running. Press any key to stop.\n\n");
//
// Wait for a keypress while the timer runs in the background
//
KeyEvent = gST->ConIn->WaitForKey;
while (TRUE) {
WaitEvents[0] = KeyEvent;
WaitEvents[1] = TimerEvent;
Status = gBS->WaitForEvent (2, WaitEvents, &EventIndex);
Print (L"\rTicks: %u ", mTickCount);
if (EventIndex == 0) {
//
// Key was pressed -- consume the keystroke and exit
//
EFI_INPUT_KEY Key;
gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
break;
}
}
//
// Clean up
//
gBS->SetTimer (TimerEvent, TimerCancel, 0);
gBS->CloseEvent (TimerEvent);
Print (L"\nDone. Total ticks: %u\n", mTickCount);
return EFI_SUCCESS;
}
12.3.4 Task Priority Levels (TPL)
UEFI uses Task Priority Levels to prevent reentrancy:
| TPL | Value | Purpose |
|---|---|---|
TPL_APPLICATION |
4 | Normal execution level |
TPL_CALLBACK |
8 | Timer callbacks and protocol notifications |
TPL_NOTIFY |
16 | Higher-priority notifications |
TPL_HIGH_LEVEL |
31 | Interrupts disabled – never use for event notifications |
// Raise TPL to prevent timer callbacks during a critical section
EFI_TPL OldTpl;
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
// Critical section -- no callbacks can fire here
SharedData->Counter++;
gBS->RestoreTPL (OldTpl);
Never call blocking functions (
WaitForEvent,Stallfor long periods) at elevated TPL. This will deadlock the system because the events that would unblock you cannot fire.
12.3.5 Event Groups with CreateEventEx
CreateEventEx allows multiple events to be signaled together as a group:
EFI_EVENT ReadyToBootEvent;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
ReadyToBootCallback,
NULL,
&gEfiEventReadyToBootGuid,
&ReadyToBootEvent
);
Important event groups defined by the specification:
| Event Group GUID | When signaled |
|---|---|
gEfiEventReadyToBootGuid |
Just before the boot option is launched |
gEfiEventAfterReadyToBootGuid |
After ReadyToBoot processing |
gEfiEventExitBootServicesGuid |
When ExitBootServices() is called |
gEfiEventVirtualAddressChangeGuid |
When SetVirtualAddressMap() is called |
gEfiEventMemoryMapChangeGuid |
When the memory map changes |
12.4 WaitForEvent – Blocking Synchronization
WaitForEvent blocks the caller until one of the specified events is signaled:
EFI_STATUS
EFIAPI
gBS->WaitForEvent (
IN UINTN NumberOfEvents,
IN EFI_EVENT *Event,
OUT UINTN *Index
);
WaitForEventcan only be called atTPL_APPLICATION. It will returnEFI_UNSUPPORTEDif the current TPL is higher. This prevents deadlocks.
Waiting for multiple sources
EFI_EVENT Events[3];
UINTN EventIndex;
Events[0] = gST->ConIn->WaitForKey; // Keyboard input
Events[1] = NetworkReadyEvent; // Network data available
Events[2] = TimeoutEvent; // 30-second timeout
Status = gBS->WaitForEvent (3, Events, &EventIndex);
switch (EventIndex) {
case 0:
HandleKeyInput ();
break;
case 1:
HandleNetworkData ();
break;
case 2:
Print (L"Timed out waiting for input.\n");
break;
}
12.5 Runtime Services Reference
Runtime Services survive ExitBootServices() and are available to the operating system for the lifetime of the platform. The OS calls them through the Runtime Services table, which is part of the System Table passed to ExitBootServices().
12.5.1 Variable Services
| Function | Purpose |
|---|---|
GetVariable() |
Read a named variable |
SetVariable() |
Write/create/delete a named variable |
GetNextVariableName() |
Enumerate all variables |
QueryVariableInfo() |
Get storage space information |
12.5.2 Time Services
| Function | Purpose |
|---|---|
GetTime() |
Read the real-time clock |
SetTime() |
Set the real-time clock |
GetWakeupTime() |
Read the RTC alarm |
SetWakeupTime() |
Set/disable the RTC alarm |
12.5.3 Virtual Memory Services
| Function | Purpose |
|---|---|
SetVirtualAddressMap() |
Provide the runtime virtual mapping (called once by OS) |
ConvertPointer() |
Convert a physical pointer to its virtual equivalent |
12.5.4 Reset Services
| Function | Purpose |
|---|---|
ResetSystem() |
Reset or shut down the platform |
12.5.5 Capsule Services
| Function | Purpose |
|---|---|
UpdateCapsule() |
Pass a capsule (firmware update, etc.) to the platform |
QueryCapsuleCapabilities() |
Query what capsule types are supported |
12.6 Variable Services in Detail
Variables are UEFI’s persistent key-value store. They survive reboots and are the primary mechanism for storing configuration (boot order, Secure Boot keys, OEM settings).
Reading a variable
UINTN DataSize = 0;
VOID *Data = NULL;
UINT32 Attributes;
//
// First call: get the required buffer size
//
Status = gRT->GetVariable (
L"BootOrder",
&gEfiGlobalVariableGuid,
&Attributes,
&DataSize,
NULL
);
if (Status == EFI_BUFFER_TOO_SMALL) {
Data = AllocatePool (DataSize);
if (Data == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = gRT->GetVariable (
L"BootOrder",
&gEfiGlobalVariableGuid,
&Attributes,
&DataSize,
Data
);
}
if (!EFI_ERROR (Status)) {
UINT16 *BootOrder = (UINT16 *)Data;
UINTN Count = DataSize / sizeof (UINT16);
Print (L"Boot order (%u entries):", Count);
for (UINTN i = 0; i < Count; i++) {
Print (L" %04x", BootOrder[i]);
}
Print (L"\n");
}
if (Data != NULL) {
FreePool (Data);
}
Writing a variable
UINT32 MyConfig = 0x12345678;
Status = gRT->SetVariable (
L"MyConfigValue",
&gMyVendorGuid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
sizeof (UINT32),
&MyConfig
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Failed to set variable: %r\n", Status));
}
Variable attributes
| Attribute | Value | Meaning |
|---|---|---|
EFI_VARIABLE_NON_VOLATILE |
0x01 | Persists across reboots |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
0x02 | Accessible during boot |
EFI_VARIABLE_RUNTIME_ACCESS |
0x04 | Accessible after ExitBootServices |
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS |
0x10 | Deprecated – do not use |
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS |
0x20 | Requires signed updates |
EFI_VARIABLE_APPEND_WRITE |
0x40 | Append data instead of overwrite |
Deleting a variable
Set it with zero DataSize and NULL data:
Status = gRT->SetVariable (
L"MyConfigValue",
&gMyVendorGuid,
0, // Attributes
0, // DataSize = 0
NULL // Data = NULL
);
Enumerating all variables
CHAR16 VariableName[256];
EFI_GUID VendorGuid;
UINTN NameSize;
VariableName[0] = L'\0';
while (TRUE) {
NameSize = sizeof (VariableName);
Status = gRT->GetNextVariableName (&NameSize, VariableName, &VendorGuid);
if (Status == EFI_NOT_FOUND) {
break; // End of variable list
}
if (EFI_ERROR (Status)) {
break; // Error
}
Print (
L"%g : %s\n",
&VendorGuid,
VariableName
);
}
12.7 The ExitBootServices Transition
ExitBootServices() is the single most critical moment in the boot process. It is the point where the operating system takes full ownership of the platform.
EFI_STATUS
EFIAPI
gBS->ExitBootServices (
IN EFI_HANDLE ImageHandle,
IN UINTN MapKey
);
What happens during ExitBootServices
sequenceDiagram
participant OS as OS Loader
participant BS as Boot Services
participant RT as Runtime Services
participant HW as Hardware
OS->>BS: GetMemoryMap() → MapKey
OS->>BS: ExitBootServices(ImageHandle, MapKey)
Note over BS: Signal EVT_SIGNAL_EXIT_BOOT_SERVICES events
Note over BS: Stop all timer events
Note over BS: Disable Boot Services watchdog
Note over BS: Set gBS to NULL in System Table
Note over BS: Reclaim EfiBootServicesCode/Data memory
BS-->>OS: EFI_SUCCESS
Note over OS: Boot Services are now DEAD
Note over OS: Only Runtime Services remain
OS->>RT: SetVirtualAddressMap()
Note over RT: Convert all internal pointers
Note over RT: Signal EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
OS->>RT: GetTime(), GetVariable(), etc.
Note over RT: Runtime services work normally
The ExitBootServices contract
After a successful ExitBootServices():
- All Boot Services are gone. Calling any function through
gBSis undefined behavior (typically a crash). - All timer events are stopped. No more callbacks.
- All
EfiBootServicesCodeandEfiBootServicesDatamemory may be reclaimed. Do not touch it. - Console I/O protocols are gone. No more
Print(). - Runtime Services remain.
gRT->GetVariable(),gRT->GetTime(),gRT->ResetSystem(), etc. still work. - Interrupts are disabled. The OS must set up its own IDT before re-enabling them.
Correct ExitBootServices pattern
The correct implementation must handle the case where the memory map changes between GetMemoryMap and ExitBootServices:
EFI_STATUS
DoExitBootServices (
IN EFI_HANDLE ImageHandle
)
{
EFI_STATUS Status;
UINTN MemMapSize = 0;
EFI_MEMORY_DESCRIPTOR *MemMap = NULL;
UINTN MapKey;
UINTN DescSize;
UINT32 DescVer;
//
// Step 1: Get the memory map size
//
Status = gBS->GetMemoryMap (&MemMapSize, NULL, &MapKey, &DescSize, &DescVer);
ASSERT (Status == EFI_BUFFER_TOO_SMALL);
//
// Step 2: Allocate buffer with extra room
//
MemMapSize += 2 * DescSize;
Status = gBS->AllocatePool (EfiLoaderData, MemMapSize, (VOID **)&MemMap);
ASSERT_EFI_ERROR (Status);
//
// Step 3: Get the memory map
//
Status = gBS->GetMemoryMap (&MemMapSize, MemMap, &MapKey, &DescSize, &DescVer);
ASSERT_EFI_ERROR (Status);
//
// Step 4: Call ExitBootServices with the current MapKey
//
Status = gBS->ExitBootServices (ImageHandle, MapKey);
if (Status == EFI_INVALID_PARAMETER) {
//
// MapKey mismatch -- memory map changed.
// Get the map again WITHOUT allocating (reuse the buffer).
//
Status = gBS->GetMemoryMap (&MemMapSize, MemMap, &MapKey, &DescSize, &DescVer);
ASSERT_EFI_ERROR (Status);
//
// Retry ExitBootServices
//
Status = gBS->ExitBootServices (ImageHandle, MapKey);
}
//
// If we get here with success, Boot Services are dead.
// Do NOT call FreePool, Print, or any Boot Service.
//
return Status;
}
After a failed
ExitBootServices(), Boot Services are still available but in a degraded state. You can callGetMemoryMapand retry, but you must not perform any allocations between the secondGetMemoryMapand the retry. This is why the retry reuses the existing buffer.
12.8 Service Availability Matrix
This table shows which services are available in each phase of the UEFI boot process:
| Service | SEC | PEI | DXE (Boot) | After ExitBootServices | After SetVirtualAddressMap |
|---|---|---|---|---|---|
| Boot Services (gBS) | – | – | Yes | No | No |
AllocatePool / FreePool |
– | – | Yes | No | No |
LocateProtocol |
– | – | Yes | No | No |
InstallProtocolInterface |
– | – | Yes | No | No |
CreateEvent / SetTimer |
– | – | Yes | No | No |
LoadImage / StartImage |
– | – | Yes | No | No |
ExitBootServices |
– | – | Yes (once) | No | No |
| Runtime Services (gRT) | – | – | Yes | Yes | Yes |
GetVariable / SetVariable |
– | – | Yes | Yes | Yes |
GetTime / SetTime |
– | – | Yes | Yes | Yes |
ResetSystem |
– | – | Yes | Yes | Yes |
UpdateCapsule |
– | – | Yes | Yes | Yes |
SetVirtualAddressMap |
– | – | No | Yes (once) | No |
ConvertPointer |
– | – | No | During SVAM only | No |
| PEI Services | – | Yes | No | No | No |
InstallPpi / LocatePpi |
– | Yes | No | No | No |
GetHobList |
– | Yes | No | No | No |
PEI Services are not part of the UEFI specification proper – they come from the PI (Platform Initialization) specification. They exist only during the PEI phase and are replaced by DXE/Boot Services when the DXE Core takes over.
12.9 ResetSystem – Resetting the Platform
ResetSystem is a Runtime Service that can be called at any time (boot or runtime):
VOID
EFIAPI
gRT->ResetSystem (
IN EFI_RESET_TYPE ResetType,
IN EFI_STATUS ResetStatus,
IN UINTN DataSize,
IN VOID *ResetData OPTIONAL
);
| Reset type | Behavior |
|---|---|
EfiResetCold |
Full power cycle (equivalent to unplugging and re-plugging) |
EfiResetWarm |
CPU reset without power cycle (memory may be preserved) |
EfiResetShutdown |
Power off the system |
EfiResetPlatformSpecific |
Platform-defined reset with GUID-tagged data |
// Reboot the system
gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL);
// Shutdown
gRT->ResetSystem (EfiResetShutdown, EFI_SUCCESS, 0, NULL);
// Warm reset with a reason string
gRT->ResetSystem (
EfiResetWarm,
EFI_SUCCESS,
StrSize (L"Firmware update applied"),
L"Firmware update applied"
);
ResetSystemdoes not return. If it does, the platform firmware is broken. Always treat the call as a termination point.
12.10 Time Services
The UEFI time services provide access to the real-time clock (RTC):
EFI_TIME Time;
EFI_TIME_CAPABILITIES Capabilities;
Status = gRT->GetTime (&Time, &Capabilities);
if (!EFI_ERROR (Status)) {
Print (
L"Current time: %04u-%02u-%02u %02u:%02u:%02u\n",
Time.Year, Time.Month, Time.Day,
Time.Hour, Time.Minute, Time.Second
);
Print (
L"RTC resolution: %u (1=1s, 10=100ms, etc.)\n",
Capabilities.Resolution
);
Print (
L"RTC accuracy: %u ppm\n",
Capabilities.Accuracy
);
}
The EFI_TIME structure:
typedef struct {
UINT16 Year; // 1900 - 9999
UINT8 Month; // 1 - 12
UINT8 Day; // 1 - 31
UINT8 Hour; // 0 - 23
UINT8 Minute; // 0 - 59
UINT8 Second; // 0 - 59
UINT8 Pad1;
UINT32 Nanosecond; // 0 - 999,999,999
INT16 TimeZone; // -1440 to 1440, or 2047 = unspecified
UINT8 Daylight; // Daylight saving flags
UINT8 Pad2;
} EFI_TIME;
Setting a wake alarm
EFI_TIME WakeTime;
ZeroMem (&WakeTime, sizeof (WakeTime));
WakeTime.Year = 2026;
WakeTime.Month = 3;
WakeTime.Day = 23;
WakeTime.Hour = 6;
WakeTime.Minute = 0;
WakeTime.Second = 0;
Status = gRT->SetWakeupTime (TRUE, &WakeTime);
if (!EFI_ERROR (Status)) {
Print (L"Wake alarm set for 2026-03-23 06:00:00\n");
}
12.11 Capsule Services
Capsules provide a mechanism for passing data across a system reset. The primary use case is firmware updates:
EFI_CAPSULE_HEADER *CapsuleHeaderArray[1];
UINT64 MaxCapsuleSize;
EFI_RESET_TYPE ResetType;
//
// Query capabilities first
//
Status = gRT->QueryCapsuleCapabilities (
CapsuleHeaderArray,
1,
&MaxCapsuleSize,
&ResetType
);
if (!EFI_ERROR (Status)) {
Print (L"Max capsule size: %lu bytes\n", MaxCapsuleSize);
Print (L"Required reset type: %u\n", ResetType);
}
//
// To deliver a capsule (e.g., firmware update):
//
Status = gRT->UpdateCapsule (
CapsuleHeaderArray,
1, // CapsuleCount
(EFI_PHYSICAL_ADDRESS)(UINTN)ScatterGatherList
);
// UpdateCapsule may trigger a reset or defer to next boot
12.12 Putting It All Together: Boot Flow with Services
flowchart TD
A[Power On] --> B[SEC Phase]
B --> C[PEI Phase\n--- PEI Services ---]
C --> D[DXE Phase\n--- Boot Services + Runtime Services ---]
D --> E[BDS Phase\n--- Boot Services + Runtime Services ---]
E --> F{User selects boot option}
F --> G[OS Loader runs\n--- Still has Boot Services ---]
G --> H[OS Loader: GetMemoryMap]
H --> I[OS Loader: ExitBootServices]
I --> J{Success?}
J -->|Yes| K[Boot Services DEAD\n--- Only Runtime Services ---]
J -->|No| H
K --> L[OS Loader: SetVirtualAddressMap]
L --> M[OS Running\n--- Runtime Services via virtual addresses ---]
M --> N[OS calls gRT->GetVariable, GetTime, etc.]
M --> O[OS calls gRT->ResetSystem to reboot]
style D fill:#16213e,stroke:#0f3460,color:#fff
style K fill:#533483,stroke:#e94560,color:#fff
style M fill:#0f3460,stroke:#e94560,color:#fff
12.13 Common Patterns and Pitfalls
Pattern: ReadyToBoot event handler
Many drivers need to perform last-minute setup before the OS boots (lock security registers, finalize configuration):
VOID
EFIAPI
OnReadyToBoot (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Lock down flash write protection
//
LockSpiFlash ();
//
// Final SMBIOS table update
//
UpdateSmbiosEntries ();
//
// Close the event so we're not called again
//
gBS->CloseEvent (Event);
}
// During initialization:
EFI_EVENT Event;
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
OnReadyToBoot,
NULL,
&gEfiEventReadyToBootGuid,
&Event
);
Pattern: ExitBootServices notification in a runtime driver
VOID
EFIAPI
OnExitBootServices (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Boot Services are about to die. Save any state we need.
// After this, we cannot allocate memory or use protocols.
//
mAtRuntime = TRUE;
}
// Register during entry point:
EFI_EVENT ExitBsEvent;
gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
OnExitBootServices,
NULL,
&gEfiEventExitBootServicesGuid,
&ExitBsEvent
);
Pitfall: Calling Print() after ExitBootServices
// FATAL: Print() uses Boot Services (LocateProtocol, ConOut, etc.)
gBS->ExitBootServices (ImageHandle, MapKey);
Print (L"Boot services exited!\n"); // CRASH!
// Use serial port or direct framebuffer writes after ExitBootServices
Pitfall: Forgetting SetVirtualAddressMap in runtime driver
If a runtime driver stores pointers to its data and the OS calls SetVirtualAddressMap(), those physical pointers become invalid. The driver must register for the virtual address change event and convert all its internal pointers.
Complete source code: The full working example for this chapter is available at
examples/UefiMuGuidePkg/ServicesExample/.
Summary
Boot Services and Runtime Services are the two pillars of the UEFI service architecture. Boot Services provide the rich environment needed during firmware initialization – memory management, protocol handling, event systems, and image loading. Runtime Services provide the minimal set of services that persist into the operating system – variables, time, reset, and capsules. The ExitBootServices() transition is the bright line between these two worlds, and understanding it correctly is essential for writing reliable firmware and OS loaders.
Together with the driver model (Chapter 9), protocols and handles (Chapter 10), and memory services (Chapter 11), this chapter completes the foundation of UEFI core concepts. You now have the knowledge to understand, write, and debug any UEFI firmware module.
Hands-on exercise: Write a UEFI application that (1) creates a periodic timer counting up every 100ms, (2) waits for a keypress, (3) reads the current time from
GetTime()at start and at key press, (4) calculates elapsed wall-clock time, and (5) compares it against the tick count to verify timer accuracy. Print the results. This exercise combines event services, timer services, and runtime time services.