7.11 Runtime Drivers

UEFI Runtime Drivers are not common. If a UEFI Driver does not need to provide services after ExitBootServices(), the UEFI Driver should not use the techniques described in this section. The best example of a runtime driver following the UEFI driver model is an UNDI driver providing services for a network interface controller (NIC).

A UEFI Runtime Driver provides services that are available after ExitBootServices() has been called. UEFI Drivers of this category are much more difficult to implement and validate because they are required to execute in both the pre-boot environment, where the system firmware owns the platform, and in the post-boot environment, where an operating system owns the platform.

An OS may choose to execute in a virtual addressing mode and, as a result, may prefer to call firmware services provided by UEFI Runtime Drivers in a virtual addressing mode. A UEFI Runtime Driver must not make any assumptions about the type of operating system to be booted, so the driver must always be able to switch from using physical addresses to using virtual addresses if the operating system calls SetVirtualAddressMap().

In addition, because all memory regions marked as boot services memory in the UEFI memory map are converted to available memory when the OS boots, a UEFI Runtime Driver must allocate memory buffers required by the services provided after ExitBootServices() in order to be allocated from runtime memory.

A UEFI Runtime Driver typically creates the following two events so the driver is notified when these important transitions occur:

  • Exit Boot Services event

  • Set Virtual Address Map event

The Exit Boot Services event is signaled when the OS loader or OS kernel calls ExitBootServices(). After this point, the UEFI driver is not allowed to use any of the UEFI boot services. The UEFI runtime services and services from other runtime drivers are still available.

The Set Virtual Address Map event is signaled when the OS loader or OS kernel calls SetVirtualAddressMap(). If this event is signaled, the OS loader or OS kernel requests that all runtime components be converted from their physical address mapping to the virtual address mappings that are then passed to SetVirtualAddressMap().

The UEFI firmware below the UEFI Driver performs most of the work here by relocating all the UEFI images from their physically addressed code and data segments to their virtually addressed code and data segments. However, the UEFI firmware below the UEFI Driver is not aware of runtime memory buffers have been allocated by a UEFI Runtime Driver. UEFI firmware below the UEFI Driver is also not aware if there are any pointer values within those allocated buffers that must be converted from physical addresses to virtual addresses.

Caution: The notification function for the Set Virtual Address Map event is required to use the

UEFI Runtime Service ConvertPointer() to convert all pointers in global variables and allocated runtime buffers from physical address to virtual addresses. This code may be complex and difficult to get correct because, at this time, no tools are available to help know when all the pointers have been converted. When not done correctly, the only symptom noticed may be that the OS crashes or hangs due to a condition in the middle of a call to a service produced by a runtime driver.

Note: The algorithm to convert pointers can be especially complex if the UEFI

_Runtime Driver manages linked lists or nested structures. The SetVirtualAddressMap() event executes in physical mode, so all linked list and structure traversals must be performed with the physical versions of the pointer values. Once a pointer value is converted from a physical address to a virtual address, that pointer value cannot be used again within the SetVirtualAddressMap() event. The typical approach is to convert the pointers to the leaf structures first and work towards the root.

The following example shows the driver entry point for a UEFI Runtime Driver that creates an Exit Boot Services event and a Set Virtual Address Map event. These events are typically declared as global variables. The notification function for the Exit Boot Services event sets a global variable gAtRuntime to TRUE, allowing the code in other functions to know if the UEFI boot services are available or not. This global variable is initialized to FALSE in its declaration. The notification function for the Set Virtual Address Map event converts one global pointer from a physical address to a virtual address as an example using a the EfiConvertPointer() function from the EDK II library UefiRuntimeLib. A real driver might have many more pointers to convert. In general, a UEFI Runtime Driver should be designed to reduce or eliminate pointers that need to be converted to minimize the likelihood of missing a pointer conversion.

Example 108-UEFI Runtime Driver entry point

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
#include <Library/DebugLib.h>
//
// Global variable for Exit Boot Services event.
//
EFI_EVENT mExitBootServicesEvent = NULL;
//
// Global variable for Set Virtual Address Map event.
//
EFI_EVENT mSetVirtualAddressMapEvent = NULL;
//
// Global variable updated when Exit Boot Services is signaled.
//
BOOLEAN gAtRuntime = FALSE;
//
// Global pointer that is converted to a virtual address when
// Set Virtual Address Map is signaled.
//
VOID *gGlobalPointer;
VOID
EFIAPI
AbcNotifyExitBootServices (
IN EFI_EVENT Event,
IN VOID *Context
)
{
gAtRuntime = TRUE;
}
VOID
EFIAPI
AbcNotifySetVirtualAddressMap (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
Status = EfiConvertPointer (
EFI_OPTIONAL_PTR,
(VOID **)&gGlobalPointer
);
}
EFI_STATUS
EFIAPI
AbcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Create an Exit Boot Services event.
//
Status = gBS->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type
TPL_NOTIFY, // NotifyTpl
AbcNotifyExitBootServices, // NotifyFunction
NULL, // NotifyContext
&mExitBootServicesEvent // Event
);
ASSERT_EFI_ERROR (Status);
//
// Create a Set Virtual Address Map event.
//
Status = gBS->CreateEvent (
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, // Type
TPL_NOTIFY, // NotifyTpl
AbcNotifySetVirtualAddressMap, // NotifyFunction
NULL, // NotifyContext
&mSetVirtualAddressMapEvent // Event
);
ASSERT_EFI_ERROR (Status);
//
// Perform additional driver initialization here
//
return EFI_SUCCESS;
}

A UEFI Runtime Driver must have the required subsystem type in the PE/COFF image for the UEFI Boot Service LoadImage() to allocate memory for the code and data sections from runtime memory. In the EDK II this is done by setting MODULE_TYPE in the [Defines] section of the INF file to DXE_RUNTIME_DRIVER. In addition, a MODULE_TYPE of DXE_RUNTIME_DRIVER is required to have a [Depex] section in the INF file. UEFI Runtime Driver must use the same [Depex] section contents. The example below shows the INF file for a UEFI Runtime Driver with a MODULE_TYPE of DXE_RUNTIME_DRIVER and the required [Depex] section.

Example 109-UEFI Runtime Driver INF File

[Defines]
INF_VERSION = 0x00010005
BASE_NAME = AbcRuntimeDriver
FILE_GUID = D3A3F14B-8ED4-438c-B7B7-FAF3F639B160
MODULE_TYPE = DXE_RUNTIME_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = AbcDriverEntryPoint
[Sources]
Abc.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiBootServicesTableLib
UefiRuntimeLib
DebugLib
[Depex]
gEfiBdsArchProtocolGuid AND
gEfiCpuArchProtocolGuid AND
gEfiMetronomeArchProtocolGuid AND
gEfiMonotonicCounterArchProtocolGuid AND
gEfiRealTimeClockArchProtocolGuid AND
gEfiResetArchProtocolGuid AND
gEfiRuntimeArchProtocolGuid AND
gEfiSecurityArchProtocolGuid AND
gEfiTimerArchProtocolGuid AND
gEfiVariableWriteArchProtocolGuid AND
gEfiVariableArchProtocolGuid AND
gEfiWatchdogTimerArchProtocolGuid

If a UEFI Runtime Driver also supports the unload feature, the Unload() function must close the Exit Boot Services and Set Virtual Address Map events by calling the UEFI Boot Service CloseEvent(). These events are typically declared as global variables so they can be easily accessed from the Unload() function. The example below shows an unloadable runtime driver. It is the same as the previous example, except the entry point looks up the EFI_LOADED_IMAGE_PROTOCOL associated with ImageHandle and registers the Unload() function called AbcUnload(). AbcUnload() closes the events that were created in the driver entry point using the UEFI Boot Service CloseEvent().

Example 110-UEFI Runtime Driver entry point with Unload feature

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
#include <Library/DebugLib.h>
//
// Global variable for Exit Boot Services event.
//
EFI_EVENT mExitBootServicesEvent = NULL;
//
// Global variable for Set Virtual Address Map event.
//
EFI_EVENT mSetVirtualAddressMapEvent = NULL;
//
// Global variable updated when Exit Boot Services is signaled.
//
BOOLEAN gAtRuntime = FALSE;
//
// Global pointer that is converted to a virtual address when
// Set Virtual Address Map is signaled.
//
VOID *gGlobalPointer;
VOID
EFIAPI
AbcNotifyExitBootServices (
IN EFI_EVENT Event,
IN VOID *Context
)
{
gAtRuntime = TRUE;
}
VOID
EFIAPI
AbcNotifySetVirtualAddressMap (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
Status = EfiConvertPointer (
EFI_OPTIONAL_PTR,
(VOID **)&gGlobalPointer
);
}
EFI_STATUS EFIAPI
AbcUnload (
IN EFI_HANDLE ImageHandle
)
{
gBS->CloseEvent (mExitBootServicesEvent);
gBS->CloseEvent (mSetVirtualAddressMapEvent);
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
AbcDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Create an Exit Boot Services event.
//
Status = gBS->CreateEvent (
EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type
TPL_NOTIFY, // NotifyTpl
AbcNotifyExitBootServices, // NotifyFunction
NULL, // NotifyContext
&mExitBootServicesEvent // Event
);
ASSERT_EFI_ERROR (Status);
//
// Create a Set Virtual Address Map event.
//
Status = gBS->CreateEvent (
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, // Type
TPL_NOTIFY, // NotifyTpl
AbcNotifySetVirtualAddressMap, // NotifyFunction
NULL, // NotifyContext
&mSetVirtualAddressMapEvent // Event
);
ASSERT_EFI_ERROR (Status);
//
// Perform additional driver initialization here
//
return EFI_SUCCESS;
}