UEFI Setup Callback Tutorial: Build an Interactive AMI Aptio 4 Module
A UEFI Setup menu is more than a collection of options displayed on a screen. In a production BIOS, changing one setting may affect another option, trigger validation, request a system reset, or initiate a hardware-related operation.
For example, selecting a fan-control mode might automatically adjust the permitted PWM range. Disabling fan detection could make other fan-related options unavailable. Entering an invalid numeric value should produce an error rather than allowing an unsupported configuration to be saved.
This is where the UEFI Setup Callback mechanism becomes important.
In AMI Aptio 4, Setup customization commonly involves VFR form definitions, UNI language resources, SDL integration, and C-based callback implementations. The standardized UEFI interface connecting the Forms Browser to the driver is the EFI_HII_CONFIG_ACCESS_PROTOCOL.
This tutorial builds a sample fan-control Setup module using three interactive controls:
- A
oneofcontrol for fan operating mode. - A
checkboxfor fan detection. - A
numericcontrol for the minimum PWM value.
The implementation covers form definitions, callback registration, configuration extraction, configuration persistence, and callback-driven validation.
One important distinction: the examples combine standard UEFI HII interfaces with common AMI Aptio integration conventions. Exact SDL tokens, eLink names, TSE callback tables, and HII helper functions vary between Aptio releases and OEM projects. The sample should therefore be adapted to the target firmware source tree rather than treated as a universally drop-in module.
🧩 0. Background: How UEFI Setup Connects the Frontend and Backend #
0.1 VFR, IFR, HII, and the Forms Browser #
UEFI Setup uses the Human Interface Infrastructure (HII) to organize configuration forms, strings, and other user-interface resources.
The architecture can be understood through four main components:
| Component | Responsibility |
|---|---|
| VFR | Describes the Setup forms, controls, questions, and configuration bindings |
| IFR | Binary representation generated from VFR source |
| HII Database | Stores form and string package lists |
| Forms Browser | Displays forms, processes user input, and communicates with configuration drivers |
VFR is a declarative language. It describes what the Setup interface contains, but it does not independently implement the hardware-related behavior behind each control.
For example, VFR can describe a dropdown containing Auto, Full, and Silent fan modes. It does not automatically know how to validate those selections against the current hardware state.
The Forms Browser processes the compiled IFR representation and communicates with the driver through the relevant HII interfaces.
A useful analogy is that VFR describes the structure of a user interface, while the C implementation supplies the configuration and event-handling logic.
0.2 EFI_HII_CONFIG_ACCESS_PROTOCOL: Three Core Functions #
The standard UEFI configuration-access interface contains three methods:
| Function | Typical caller | Responsibility |
|---|---|---|
ExtractConfig() |
HII Configuration Routing | Retrieves current configuration in HII configuration-string format |
RouteConfig() |
HII Configuration Routing | Applies configuration changes to the driver’s storage |
Callback() |
Forms Browser | Handles supported user interactions with interactive questions |
These functions have different responsibilities.
ExtractConfig() supplies configuration data that can be displayed by the Setup interface.
RouteConfig() receives configuration changes and applies them to the appropriate storage, such as an NVRAM variable.
Callback() receives browser events and can validate or process an interactive question.
The three functions work together, but they are not interchangeable. In particular, Callback() should not replace the normal configuration-extraction and persistence mechanisms.
For the formal definitions, see the UEFI 2.11 HII Configuration Processing and Browser Protocol specification.
0.3 The Callback Flag and Question Key #
A common beginner mistake is implementing the C callback function but forgetting to mark the corresponding VFR question as interactive.
A question must use the appropriate interaction flag supported by the target VFR compiler.
For example:
oneof varid = SETUP_DATA.FanControl,
key = FAN_CONTROL_KEY,
prompt = STRING_TOKEN(STR_FAN_CONTROL),
help = STRING_TOKEN(STR_FAN_CONTROL_HELP),
flags = INTERACTIVE | RESET_REQUIRED;
option text = STRING_TOKEN(STR_AUTO),
value = 0,
flags = DEFAULT;
option text = STRING_TOKEN(STR_FULL),
value = 1,
flags = 0;
option text = STRING_TOKEN(STR_SILENT),
value = 2,
flags = 0;
endoneof;There are two important elements here:
INTERACTIVEenables the question’s callback behavior by setting the corresponding IFR callback flag.keyidentifies the question. The Forms Browser passes this identifier to the C callback asQuestionId.
Use key in standard VFR syntax, rather than assuming that questionid is a valid VFR attribute. Some platform-specific source-generation mechanisms may use different terminology, so check the VFR compiler and existing Aptio examples.
The key should be unique within the relevant form set and centrally defined in a header file.
Also note that INTERACTIVE and RESET_REQUIRED are different flags. The former enables callback interaction; the latter indicates that a reset is required for the setting to take effect, subject to browser and platform support.
0.4 CHANGING versus CHANGED #
The Forms Browser uses several action values when communicating with a callback.
Two are especially important:
| Action | Timing | Typical purpose |
|---|---|---|
EFI_BROWSER_ACTION_CHANGING |
Before the question value changes | Validate or modify the proposed value |
EFI_BROWSER_ACTION_CHANGED |
After the browser updates the question value | React to the accepted change |
The distinction affects how validation should be implemented.
For CHANGING, returning EFI_SUCCESS tells the browser to use the value returned by the callback. Returning an error other than EFI_UNSUPPORTED prevents the current question from being updated. Returning EFI_UNSUPPORTED tells the browser to use the original input value.
For CHANGED, the browser has already updated its internal copy of the question value. Errors returned by the callback are ignored, so this is not the appropriate stage for rejecting an invalid selection.
The callback can also receive actions such as RETRIEVE, FORM_OPEN, FORM_CLOSE, and SUBMITTED. A production implementation should handle the actions it actually supports and return EFI_UNSUPPORTED for unsupported actions.
One more important rule: modifying the current question’s Value parameter is supported, but changing other questions requires notifying the Forms Browser through the appropriate browser interface, such as EFI_FORM_BROWSER2_PROTOCOL.BrowserCallback(). Directly changing an unrelated variable in C does not automatically update the Browser’s uncommitted form data.
0.5 Aptio 4’s VFR, SD, UNI, and C Organization #
Aptio projects commonly divide Setup customization into several layers.
| File | Role | Responsibility |
|---|---|---|
.vfr |
Form structure | Defines forms, questions, controls, and variable bindings |
.sd |
AMI customization | Supplies project-specific Setup insertion and configuration definitions |
.uni |
Localization | Defines the displayed text for supported languages |
.c |
Callback implementation | Implements HII configuration access and interaction logic |
.h |
Shared declarations | Defines structures, GUIDs, and question keys |
The exact division depends on the Aptio release and OEM build system. Some projects compile standalone VFR modules, while others generate or extend core Setup forms through SD and eLink integration.
For a broader explanation of these relationships, see KAD’s AMI TSE Setup Menu guide covering VFR, SD, UNI, and HII callbacks.
📁 1. Project Structure: The Setup Module Files #
The example uses eight source and configuration files:
| File | Role | Responsibility |
|---|---|---|
EFITrainingSetup.cif |
Component registration | Registers the module |
EFITrainingSetup.sdl |
Build configuration | Defines support switches, GUIDs, and eLinks |
EFITrainingSetup.mak |
Build rules | Defines compilation and FFS packaging |
EFITrainingSetup.dxs |
Dependency expression | Waits for required HII and variable services |
EFITrainingSetup.vfr |
Frontend | Defines the Setup form and interactive questions |
EFITrainingSetup.uni |
Localization | Provides English and Chinese strings |
EFITrainingSetup.c |
Backend | Implements HII ConfigAccess and callback functions |
EFITrainingSetup.h |
Shared declarations | Defines configuration structures and question IDs |
The directory layout is:
EFITraining/
└── Setup/
├── EFITrainingSetup.cif
├── EFITrainingSetup.sdl
├── EFITrainingSetup.mak
├── EFITrainingSetup.dxs
├── EFITrainingSetup.vfr
├── EFITrainingSetup.uni
├── EFITrainingSetup.c
└── EFITrainingSetup.h📝 2. Step 1 — CIF: Register the Component #
The CIF file registers the module and declares the files that belong to it.
EFITrainingSetup.cif #
<component>
name = "EFITraining-Setup"
category = ModulePart
LocalRoot = "EFITraining\Setup"
RefName = "EFITrainingSetup"
[files]
"EFITrainingSetup.sdl"
"EFITrainingSetup.mak"
"EFITrainingSetup.dxs"
"EFITrainingSetup.vfr"
"EFITrainingSetup.uni"
"EFITrainingSetup.c"
"EFITrainingSetup.h"
<endComponent>The important fields are:
name: Human-readable component name.category: Identifies the module category.LocalRoot: Defines the module directory.RefName: Provides the component reference.[files]: Lists the source and build files.
The VFR and UNI files must be included in the relevant build pipeline. Merely placing them in the source directory does not guarantee that their HII packages will be compiled and included in the firmware image.
⚙️ 3. Step 2 — SDL: Define Tokens and Setup Integration #
The SDL file controls module inclusion and defines the identifiers used by the Setup implementation.
EFITrainingSetup.sdl #
TOKEN
Name = "EFITrainingSetup_SUPPORT"
Value = "1"
TokenType = Boolean
TargetMAK = Yes
TargetH = Yes
Master = Yes
End
TOKEN
Name = "EFITrainingSetup_VAR_GUID"
Value = "0x7E374E2A, 0x8A29, 0x4B12, 0x9C, 0x04, 0xD3, 0x51, 0x88, 0x22, 0x10, 0x55"
TargetH = Yes
End
TOKEN
Name = "EFITrainingSetup_FORMSET_GUID"
Value = "0xA374E2A1, 0x8A29, 0x4B12, 0x9C, 0x04, 0xD3, 0x51, 0x88, 0x22, 0x10, 0x56"
TargetH = Yes
End
MODULE
File = "EFITrainingSetup.mak"
End
PATH
Name = "EFITraining_SETUP_DIR"
Path = "EFITraining\Setup"
End
ELINK
Name = "$(BUILD_DIR)\EFITrainingSetup.ffs"
Parent = "FV_MAIN"
InvokeOrder = AfterParent
End
ELINK
Name = "$(EFITraining_SETUP_DIR)\EFITrainingSetup.vfr"
Parent = "SETUP_DEFINITIONS"
InvokeOrder = BeforeParent
EndThe GUID values above are illustrative. Generate unique GUIDs for the actual project and ensure that the SDL token-generation rules produce the C definitions in the expected format.
3.1 Registering Aptio-specific callbacks #
Some Aptio projects use an eLink-based callback table to register individual Setup question handlers.
A project-specific example may look like this:
ELINK
Name = "ITEM_CALLBACK(ADVANCED_FORM_SET_CLASS,0,FAN_DETECT_KEY,SetupCallback),"
Parent = "SetupItemCallbacks"
InvokeOrder = AfterParent
End
ELINK
Name = "ITEM_CALLBACK(ADVANCED_FORM_SET_CLASS,0,FAN_CONTROL_KEY,SetupCallback),"
Parent = "SetupItemCallbacks"
InvokeOrder = AfterParent
End
ELINK
Name = "ITEM_CALLBACK(ADVANCED_FORM_SET_CLASS,0,FAN_PWM_MIN_KEY,SetupCallback),"
Parent = "SetupItemCallbacks"
InvokeOrder = AfterParent
EndThis is an AMI-specific integration pattern, not a universal UEFI requirement.
Standard UEFI HII drivers can expose their callback through the Callback member of EFI_HII_CONFIG_ACCESS_PROTOCOL. Legacy Aptio TSE implementations may also provide their own callback registration mechanisms.
Do not blindly register the same handler through multiple dispatch paths. Inspect the target project to determine whether the Setup callback table, standard ConfigAccess callback, or a platform-specific wrapper is responsible for invoking the function.
3.2 Why use GUID tokens? #
The variable GUID and form-set GUID identify different HII resources:
- The variable GUID identifies the NVRAM namespace containing the configuration data.
- The form-set GUID identifies the group of Setup forms.
They can be related by project convention, but they should not be treated as interchangeable.
🔗 4. Step 3 — DXS: Declare the Required Dependencies #
The Setup module needs the HII services required to register its form packages and process configuration requests.
A dependency expression can include the HII Database protocol, HII Configuration Routing protocol, and the appropriate variable-service dependency.
EFITrainingSetup.dxs #
#include <Protocol\HiiDatabase.h>
#include <Protocol\HiiConfigRouting.h>
#include <Protocol\Variable.h>
DEPENDENCY_START
EFI_HII_DATABASE_PROTOCOL_GUID AND
EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID AND
EFI_VARIABLE_ARCH_PROTOCOL_GUID
DEPENDENCY_ENDThe exact variable-service dependency depends on the AMI firmware implementation. Some Aptio projects provide their own variable-services protocol, in which case the project’s existing module conventions should be followed.
Do not add EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID to this dependency expression if the same module installs that protocol. That would create a self-dependency: the module would wait for a protocol that it cannot install until it has been dispatched.
A DEPEX should express dependencies on services provided by other modules, not on protocols that the current module itself is responsible for publishing.
🖥️ 5. Step 4 — VFR: Define the Interactive Setup Form #
VFR describes the frontend controls and binds them to a configuration variable.
The sample provides three questions:
- Fan Control Mode: Auto, Full, or Silent.
- Fan Detect: A Boolean checkbox.
- Fan PWM Minimum: A numeric value between 0 and 100.
A key part of the implementation is the varstore declaration. It defines the backing configuration structure, its variable name, and its GUID.
EFITrainingSetup.vfr #
formset
guid = EFITrainingSetup_FORMSET_GUID,
title = STRING_TOKEN(STR_TITLE),
help = STRING_TOKEN(STR_TITLE_HELP),
class = EFI_ADVANCED_CLASS,
subclass = 0,
varstore EFITRAINING_SETUP_DATA,
varid = SETUP_DATA,
name = EFITrainingSetup,
guid = EFITrainingSetup_VAR_GUID;
form formid = 1,
title = STRING_TOKEN(STR_TITLE);
oneof varid = SETUP_DATA.FanControl,
key = FAN_CONTROL_KEY,
prompt = STRING_TOKEN(STR_FAN_CONTROL),
help = STRING_TOKEN(STR_FAN_CONTROL_HELP),
flags = INTERACTIVE | RESET_REQUIRED;
option text = STRING_TOKEN(STR_AUTO),
value = 0,
flags = DEFAULT;
option text = STRING_TOKEN(STR_FULL),
value = 1,
flags = 0;
option text = STRING_TOKEN(STR_SILENT),
value = 2,
flags = 0;
endoneof;
checkbox varid = SETUP_DATA.FanDetect,
key = FAN_DETECT_KEY,
prompt = STRING_TOKEN(STR_FAN_DETECT),
help = STRING_TOKEN(STR_FAN_DETECT_HELP),
flags = INTERACTIVE;
endcheckbox;
numeric varid = SETUP_DATA.FanPwmMin,
key = FAN_PWM_MIN_KEY,
prompt = STRING_TOKEN(STR_FAN_PWM_MIN),
help = STRING_TOKEN(STR_FAN_PWM_MIN_HELP),
minimum = 0,
maximum = 100,
step = 5,
default = 30,
flags = NUMERIC_SIZE_1 | DISPLAY_UINT_DEC | INTERACTIVE;
endnumeric;
endform;
endformset;5.1 Why every interactive question needs a key #
The key identifies the question when the Forms Browser calls the driver.
For example:
#define FAN_CONTROL_KEY 0x1234
#define FAN_DETECT_KEY 0x1235
#define FAN_PWM_MIN_KEY 0x1236The callback receives the corresponding identifier through the QuestionId parameter.
This allows a single callback function to process multiple controls using a switch statement.
5.2 Dynamic visibility and grayout behavior #
A callback is not the only mechanism for controlling the presentation of Setup questions.
For straightforward dependencies, VFR expressions such as suppressif and grayoutif are often more appropriate.
For example, a PWM option could be suppressed or grayed out when fan detection is disabled, depending on the desired behavior and the VFR compiler’s supported expression syntax.
The callback can update uncommitted question values using the Form Browser’s supported interfaces. However, directly changing a C structure or NVRAM variable does not automatically refresh another question in the active form.
For complex dynamic form updates, use the appropriate EFI_FORM_BROWSER2_PROTOCOL.BrowserCallback() functionality or the target Aptio implementation’s supported form-update mechanism.
🌐 6. Step 5 — UNI: Define Multi-Language Strings #
The UNI file separates displayed wording from the VFR structure.
This makes it easier to maintain different language versions without changing the callback implementation.
EFITrainingSetup.uni #
#string STR_TITLE
#language eng "EFITraining Setup"
#language chi "EFI 训练 Setup 配置"
#string STR_TITLE_HELP
#language eng "Fan control training page"
#language chi "风扇控制训练页面"
#string STR_FAN_CONTROL
#language eng "Fan Control Mode"
#language chi "风扇控制模式"
#string STR_FAN_CONTROL_HELP
#language eng "Select fan control policy"
#language chi "选择风扇控制策略"
#string STR_AUTO
#language eng "Auto"
#language chi "自动"
#string STR_FULL
#language eng "Full"
#language chi "全速"
#string STR_SILENT
#language eng "Silent"
#language chi "静音"
#string STR_FAN_DETECT
#language eng "Fan Detect"
#language chi "风扇检测"
#string STR_FAN_DETECT_HELP
#language eng "Enable fan presence detection"
#language chi "启用风扇在位检测"
#string STR_FAN_PWM_MIN
#language eng "Fan PWM Minimum (%)"
#language chi "风扇最低 PWM 占空比 (%)"
#string STR_FAN_PWM_MIN_HELP
#language eng "Minimum PWM duty cycle from 0 to 100"
#language chi "最低 PWM 占空比,范围为 0 到 100"For Aptio 4, language identifiers and character rendering depend on the project configuration.
Some legacy builds use chi, while others expect a different language-code convention. Confirm the supported languages, CJK_CHARACTERS_SUPPORT setting, font coverage, and UNI compiler rules in the target build environment.
Enabling a Chinese language token alone does not guarantee that every character can be displayed correctly.
💻 7. Step 6 — C Implementation: Configuration Access and Callback Logic #
The C implementation contains the configuration structures, protocol GUIDs, three ConfigAccess functions, callback handler, and HII registration code.
7.1 Header File: Define the Configuration Structure #
The C structure must match the VFR variable layout.
EFITrainingSetup.h #
#ifndef _EFI_TRAINING_SETUP_H_
#define _EFI_TRAINING_SETUP_H_
#include <Efi.h>
#include <Token.h>
#include <Protocol\HiiConfigAccess.h>
#include <Protocol\HiiDatabase.h>
#include <Protocol\HiiConfigRouting.h>
#include <Protocol\FormBrowser2.h>
extern EFI_GUID gEfiTrainingSetupVarGuid;
extern EFI_GUID gEfiTrainingSetupFormSetGuid;
#define FAN_CONTROL_KEY 0x1234
#define FAN_DETECT_KEY 0x1235
#define FAN_PWM_MIN_KEY 0x1236
#define SETUP_VARIABLE_NAME L"EFITrainingSetup"
typedef struct {
UINT8 FanControl; /* 0=Auto, 1=Full, 2=Silent */
UINT8 FanDetect; /* 0=Disabled, 1=Enabled */
UINT8 FanPwmMin; /* 0-100 */
UINT8 Reserved;
} EFITRAINING_SETUP_DATA;
/* HII Config Access functions. */
EFI_STATUS
EFIAPI
EFITrainingSetupExtractConfig(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN CONST EFI_STRING Request,
OUT EFI_STRING *Progress,
OUT EFI_STRING *Results
);
EFI_STATUS
EFIAPI
EFITrainingSetupRouteConfig(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN CONST EFI_STRING Configuration,
OUT EFI_STRING *Progress
);
EFI_STATUS
EFIAPI
EFITrainingSetupCallback(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN EFI_BROWSER_ACTION Action,
IN EFI_QUESTION_ID QuestionId,
IN UINT8 Type,
IN OUT EFI_IFR_TYPE_VALUE *Value,
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
);
#endifThe four-byte example structure is deliberately simple.
A production Setup variable may contain many more fields, and its layout must match the platform’s existing structure definition. Do not create a second incompatible structure for a variable already used by the core Setup implementation.
Check field offsets, data types, alignment, and variable size before integrating a new form into an existing Setup variable.
7.2 ExtractConfig(): Read Configuration for the Browser #
ExtractConfig() provides the current configuration in HII configuration-string format.
The original form of this example cannot simply return Results = NULL with EFI_SUCCESS. A successful extraction must provide a valid configuration response.
For a buffer-backed variable store, the standard approach is to use the HII Configuration Routing protocol’s BlockToConfig() function.
The general workflow is:
- Validate
ProgressandResults. - Load the current Setup variable.
- Construct or validate the HII configuration request.
- Convert the requested binary configuration block to a configuration-response string.
- Return the allocated result to the caller.
EFITrainingSetupExtractConfig() #
EFI_STATUS
EFIAPI
EFITrainingSetupExtractConfig(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN CONST EFI_STRING Request,
OUT EFI_STRING *Progress,
OUT EFI_STRING *Results
)
{
EFI_STATUS Status;
UINTN DataSize;
EFITRAINING_SETUP_DATA Data;
EFI_HII_CONFIG_ROUTING_PROTOCOL *ConfigRouting;
EFI_STRING ConfigRequestHdr;
EFI_STRING ConfigRequest;
UINTN RequestSize;
if ((Progress == NULL) || (Results == NULL)) {
return EFI_INVALID_PARAMETER;
}
*Progress = (EFI_STRING)Request;
*Results = NULL;
/*
* 1. Locate HII Configuration Routing.
*/
Status = gBS->LocateProtocol(
&gEfiHiiConfigRoutingProtocolGuid,
NULL,
(VOID **)&ConfigRouting
);
if (EFI_ERROR(Status)) {
return Status;
}
/*
* 2. Load the configuration variable.
*
* LoadTrainingSetupData() is a project helper that should
* retrieve the variable safely and initialize defaults
* if the variable does not exist.
*/
Status = LoadTrainingSetupData(&Data);
if (EFI_ERROR(Status)) {
return Status;
}
DataSize = sizeof(Data);
/*
* 3. Construct the request header using the variable GUID,
* variable name, and HII driver/package-list handle.
*/
ConfigRequestHdr = HiiConstructConfigHdr(
&gEfiTrainingSetupVarGuid,
SETUP_VARIABLE_NAME,
mDriverHandle
);
if (ConfigRequestHdr == NULL) {
return EFI_OUT_OF_RESOURCES;
}
/*
* 4. Construct a full-block request when Request is NULL.
*/
if (Request == NULL) {
RequestSize =
(StrLen(ConfigRequestHdr) + 32 + 1) * sizeof(CHAR16);
ConfigRequest = AllocateZeroPool(RequestSize);
if (ConfigRequest == NULL) {
FreePool(ConfigRequestHdr);
return EFI_OUT_OF_RESOURCES;
}
UnicodeSPrint(
ConfigRequest,
RequestSize,
L"%s&OFFSET=0&WIDTH=%016LX",
ConfigRequestHdr,
(UINT64)DataSize
);
} else {
/*
* 5. Verify that the incoming request belongs to this
* configuration variable.
*/
if (!HiiIsConfigHdrMatch(
Request,
&gEfiTrainingSetupVarGuid,
SETUP_VARIABLE_NAME)) {
FreePool(ConfigRequestHdr);
return EFI_NOT_FOUND;
}
ConfigRequest = (EFI_STRING)Request;
}
/*
* 6. Convert the binary configuration block into a
* configuration-response string.
*/
Status = ConfigRouting->BlockToConfig(
ConfigRouting,
ConfigRequest,
(UINT8 *)&Data,
DataSize,
Results,
Progress
);
/*
* 7. Release temporary resources.
*/
if (Request == NULL) {
FreePool(ConfigRequest);
}
FreePool(ConfigRequestHdr);
return Status;
}LoadTrainingSetupData() is a project-specific helper and must be implemented using the correct variable name, GUID, expected structure size, and initialization policy.
The example uses the standard HII helper functions commonly available through EDK II HII libraries. An Aptio 4 project may provide equivalent AMI helpers with different names or function signatures.
For a complete discussion of ExtractConfig() and BlockToConfig(), see the TianoCore EDK II Driver Writer’s Guide.
7.3 RouteConfig(): Persist Configuration Changes #
RouteConfig() processes the configuration-response string returned by the browser.
For a buffer-backed variable store, the normal sequence is:
- Validate the configuration header.
- Load the existing variable.
- Convert the incoming configuration string to the binary structure using
ConfigToBlock(). - Write the updated data with
SetVariable().
EFITrainingSetupRouteConfig() #
EFI_STATUS
EFIAPI
EFITrainingSetupRouteConfig(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN CONST EFI_STRING Configuration,
OUT EFI_STRING *Progress
)
{
EFI_STATUS Status;
UINTN DataSize;
EFITRAINING_SETUP_DATA Data;
EFI_HII_CONFIG_ROUTING_PROTOCOL *ConfigRouting;
if ((Configuration == NULL) || (Progress == NULL)) {
return EFI_INVALID_PARAMETER;
}
*Progress = (EFI_STRING)Configuration;
/*
* 1. Validate the routing header.
*/
if (!HiiIsConfigHdrMatch(
Configuration,
&gEfiTrainingSetupVarGuid,
SETUP_VARIABLE_NAME)) {
return EFI_NOT_FOUND;
}
/*
* 2. Locate HII Configuration Routing.
*/
Status = gBS->LocateProtocol(
&gEfiHiiConfigRoutingProtocolGuid,
NULL,
(VOID **)&ConfigRouting
);
if (EFI_ERROR(Status)) {
return Status;
}
/*
* 3. Retrieve the current configuration.
*/
Status = LoadTrainingSetupData(&Data);
if (EFI_ERROR(Status)) {
return Status;
}
DataSize = sizeof(Data);
/*
* 4. Convert browser configuration strings into the
* binary variable structure.
*/
Status = ConfigRouting->ConfigToBlock(
ConfigRouting,
Configuration,
(UINT8 *)&Data,
&DataSize,
Progress
);
if (EFI_ERROR(Status)) {
return Status;
}
if (DataSize != sizeof(Data)) {
return EFI_BAD_BUFFER_SIZE;
}
/*
* 5. Validate the final structure before committing it.
*/
if ((Data.FanControl > 2) ||
(Data.FanDetect > 1) ||
(Data.FanPwmMin > 100)) {
return EFI_INVALID_PARAMETER;
}
/*
* 6. Persist the updated variable.
*
* SaveTrainingSetupData() should preserve or use the
* exact variable attributes required by the platform.
*/
Status = SaveTrainingSetupData(&Data);
if (EFI_ERROR(Status)) {
return Status;
}
TRACE((
TRACE_ALWAYS,
"EFITrainingSetup: RouteConfig saved\n"
));
return EFI_SUCCESS;
}The two helpers, LoadTrainingSetupData() and SaveTrainingSetupData(), are intentionally separated from the HII conversion logic.
They should:
- Use the correct variable name and vendor GUID.
- Check the variable size before reading.
- Handle first-boot initialization.
- Preserve the existing variable attributes when appropriate.
- Validate the configuration before writing.
- Return storage and policy errors to the caller.
Do not assume every Setup variable uses the same attributes. The combination of EFI_VARIABLE_NON_VOLATILE, EFI_VARIABLE_BOOTSERVICE_ACCESS, and EFI_VARIABLE_RUNTIME_ACCESS must match the target platform’s variable definition and policy.
7.4 Callback(): Process Interactive Questions #
This is the central part of the module.
The callback receives the browser action, question ID, value type, and question value.
For the fan-control example, it can:
- Reject a minimum PWM value that is below the permitted threshold.
- Validate the fan-control mode.
- Log changes for troubleshooting.
- Trigger supported browser actions when required.
EFITrainingSetupCallback() #
EFI_STATUS
EFIAPI
EFITrainingSetupCallback(
IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This,
IN EFI_BROWSER_ACTION Action,
IN EFI_QUESTION_ID QuestionId,
IN UINT8 Type,
IN OUT EFI_IFR_TYPE_VALUE *Value,
OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest
)
{
if (ActionRequest == NULL) {
return EFI_INVALID_PARAMETER;
}
*ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
/*
* Questions with value-bearing actions need a valid Value.
*/
if ((Action == EFI_BROWSER_ACTION_CHANGING) ||
(Action == EFI_BROWSER_ACTION_CHANGED)) {
if (Value == NULL) {
return EFI_INVALID_PARAMETER;
}
}
switch (Action) {
case EFI_BROWSER_ACTION_CHANGING:
switch (QuestionId) {
case FAN_PWM_MIN_KEY:
/*
* The PWM field is a one-byte numeric question.
*/
if (Type != EFI_IFR_TYPE_NUM_SIZE_8) {
return EFI_INVALID_PARAMETER;
}
/*
* Reject a proposed value below 10%.
*
* Returning an error prevents the Browser from
* accepting this proposed value.
*/
if (Value->u8 < 10) {
TRACE((
TRACE_ALWAYS,
"FanPwmMin %d is below the minimum\n",
Value->u8
));
return EFI_INVALID_PARAMETER;
}
if (Value->u8 > 100) {
return EFI_INVALID_PARAMETER;
}
break;
case FAN_CONTROL_KEY:
if (Value->u8 > 2) {
return EFI_INVALID_PARAMETER;
}
TRACE((
TRACE_ALWAYS,
"FanControl changing -> %d\n",
Value->u8
));
break;
case FAN_DETECT_KEY:
if (Type != EFI_IFR_TYPE_BOOLEAN) {
return EFI_INVALID_PARAMETER;
}
break;
default:
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
case EFI_BROWSER_ACTION_CHANGED:
/*
* The Browser has already updated its internal
* copy of the selected question value.
*
* Use this stage for observation and follow-up logic.
* Do not rely on an error here to reject the change.
*/
switch (QuestionId) {
case FAN_CONTROL_KEY:
TRACE((
TRACE_ALWAYS,
"FanControl changed -> %d\n",
Value->u8
));
/*
* If another question must change, use the
* Form Browser's supported BrowserCallback()
* interface or the platform's form-update API.
*/
break;
case FAN_DETECT_KEY:
TRACE((
TRACE_ALWAYS,
"FanDetect changed -> %d\n",
Value->u8
));
break;
case FAN_PWM_MIN_KEY:
TRACE((
TRACE_ALWAYS,
"FanPwmMin changed -> %d\n",
Value->u8
));
break;
default:
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
default:
/*
* This sample handles CHANGING and CHANGED only.
* Other actions must be implemented explicitly
* if the form requires them.
*/
return EFI_UNSUPPORTED;
}
}Why validation belongs in CHANGING #
Suppose the user enters 5 in the Fan PWM Minimum field.
The callback receives:
Action = EFI_BROWSER_ACTION_CHANGING;
QuestionId = FAN_PWM_MIN_KEY;
Value->u8 = 5;Returning EFI_INVALID_PARAMETER prevents the browser from accepting the proposed value.
By contrast, EFI_BROWSER_ACTION_CHANGED is invoked after the browser has already updated its internal question value. Errors returned from that action are ignored, so it should not be used as the final validation checkpoint.
Why the callback should not directly write hardware #
The original example uses:
IoWrite8(0xCF8, 0x00);This should not be treated as a generic fan-control operation.
Port 0xCF8 is associated with legacy PCI configuration-address access on compatible x86 platforms. Writing an arbitrary value there does not constitute a valid fan-controller command.
More importantly, a Setup interaction may still represent an uncommitted change. If the user later cancels the form, an immediate hardware write could leave the hardware in a state that does not match the saved configuration.
For ordinary configuration changes, a safer approach is:
- Validate the proposed value in
CHANGING. - Keep the new value in the browser’s uncommitted configuration.
- Persist it through the normal
RouteConfig()path when the user saves. - Apply the hardware setting at the appropriate point in the platform’s configuration lifecycle.
For a deliberate immediate hardware action, use a dedicated Action question, an explicit confirmation flow where appropriate, and the correct platform-specific device interface.
🧰 8. Step 7 — Install ConfigAccess and Register HII Packages #
The module must publish its ConfigAccess interface and register the VFR and UNI resources with the HII Database.
The package list and the ConfigAccess protocol must be associated with the same HII handle.
For a standalone Setup configuration provider, this is commonly done during the driver’s entry point. A device-specific driver following the UEFI Driver Model may instead install ConfigAccess during its Start() callback for each managed controller.
EFITrainingSetup.c #
#include <Efi.h>
#include <Token.h>
#include <AmiLib.h>
#include <AmiDxeLib.h>
#include <Protocol\HiiDatabase.h>
#include <Protocol\HiiConfigAccess.h>
#include "EFITrainingSetup.h"
EFI_GUID gEfiTrainingSetupVarGuid =
EFITrainingSetup_VAR_GUID;
EFI_GUID gEfiTrainingSetupFormSetGuid =
EFITrainingSetup_FORMSET_GUID;
/*
* HII Config Access protocol instance.
*/
EFI_HII_CONFIG_ACCESS_PROTOCOL mConfigAccess = {
EFITrainingSetupExtractConfig,
EFITrainingSetupRouteConfig,
EFITrainingSetupCallback
};
EFI_HANDLE mDriverHandle = NULL;
EFI_HII_HANDLE mHiiHandle = NULL;
EFI_STATUS
EFIAPI
EFITrainingSetupInstall(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
InitAmiLib(ImageHandle, SystemTable);
TRACE((
TRACE_ALWAYS,
"EFITraining Setup install...\n"
));
/*
* 1. Install ConfigAccess on a handle.
*/
Status = gBS->InstallProtocolInterface(
&mDriverHandle,
&gEfiHiiConfigAccessProtocolGuid,
EFI_NATIVE_INTERFACE,
&mConfigAccess
);
if (EFI_ERROR(Status)) {
return Status;
}
/*
* 2. Register the VFR and UNI packages.
*
* The AMI helper shown here is project-specific.
* Verify its prototype and return type in the target
* Aptio source tree.
*/
mHiiHandle = AmiHiiAddPackages(
&gEfiTrainingSetupFormSetGuid,
mDriverHandle,
EFITrainingSetupVfrBin,
EFITrainingSetupStrings,
NULL
);
if (mHiiHandle == NULL) {
gBS->UninstallProtocolInterface(
mDriverHandle,
&gEfiHiiConfigAccessProtocolGuid,
&mConfigAccess
);
mDriverHandle = NULL;
return EFI_OUT_OF_RESOURCES;
}
TRACE((
TRACE_ALWAYS,
"EFITraining Setup HII packages registered\n"
));
return EFI_SUCCESS;
}AmiHiiAddPackages() is an AMI helper rather than a standard UEFI Boot Service. Its actual signature and return type vary by Aptio project.
In EDK II, the commonly used HiiAddPackages() helper returns an EFI_HII_HANDLE, not an EFI_STATUS. Use the helper provided by the target environment and perform error handling according to its actual interface.
A production module should also implement an appropriate unload or cleanup path that removes its HII package list and uninstalls the ConfigAccess protocol when the module is no longer referenced.
Important: The HII package handle matters #
The Forms Browser uses the form package list and its associated configuration-access interface to locate the correct driver.
If the HII packages are registered against one handle but the ConfigAccess protocol is installed on an unrelated handle, the browser may not be able to route configuration requests to the intended callback implementation.
Always verify that the form package registration and ConfigAccess installation use the intended matching handle.
🏗️ 9. Step 8 — MAK: Define the Build Parameters #
The MAK file connects the C implementation, VFR source, UNI resources, dependency expression, and final FFS module.
EFITrainingSetup.mak #
all : EFITrainingSetup
EFITrainingSetup : \
$(BUILD_DIR)\EFITrainingSetup.mak \
EFITrainingSetupBin
$(BUILD_DIR)\EFITrainingSetup.mak : \
$(EFITraining_SETUP_DIR)\EFITrainingSetup.cif \
$(EFITraining_SETUP_DIR)\EFITrainingSetup.mak \
$(BUILD_RULES)
$(CIF2MAK) \
$(EFITraining_SETUP_DIR)\EFITrainingSetup.cif \
$(CIF2MAK_DEFAULTS)
EFITrainingSetupBin : $(AMIDXELIB) $(AMIHIILIB) $(AMICSPLIB)
$(MAKE) /$(MAKEFLAGS) $(BUILD_DEFAULTS) \
/f $(BUILD_DIR)\EFITrainingSetup.mak all \
NAME=EFITrainingSetup \
MAKEFILE=$(BUILD_DIR)\EFITrainingSetup.mak \
OBJECTS="$(BUILD_DIR)\$(EFITraining_SETUP_DIR)\EFITrainingSetup.obj" \
GUID=7E374E2A-8A29-4B12-9C04-D35188221055 \
ENTRY_POINT=EFITrainingSetupInstall \
TYPE=BS_DRIVER \
DEPEX1=$(EFITraining_SETUP_DIR)\EFITrainingSetup.dxs \
DEPEXITYPE=EFI_SECTION_DXE_DEPEX \
VFR=$(EFITraining_SETUP_DIR)\EFITrainingSetup.vfr \
UNI=$(EFITraining_SETUP_DIR)\EFITrainingSetup.uni \
COMPRESS=1
The important parameters are:
| Parameter | Purpose |
|---|---|
NAME |
Module name |
OBJECTS |
Compiled C object files |
GUID |
FFS file GUID |
ENTRY_POINT |
Driver initialization function |
TYPE |
DXE driver image type |
DEPEX1 |
Dependency-expression source |
DEPEXITYPE |
DXE dependency section type |
VFR |
Form source file |
UNI |
Language resource file |
COMPRESS |
Configured firmware compression behavior |
The sample build rules are illustrative. Some Aptio projects generate VFR and UNI objects through separate tools or include them through a central Setup build. Follow the existing module rules rather than assuming that every project accepts the same variables.
🧪 10. Compilation, Flashing, and Verification #
After creating the files, integrate the module into the BIOS project and verify the generated HII resources before flashing.
10.1 Build integration #
A typical workflow is:
- Copy the
EFITrainingSetupdirectory into the project, such asSamplecode\EFITraining\Setup\. - Register the CIF component in the VEB or equivalent module-management environment.
- Confirm that
EFITrainingSetup_SUPPORTis set to1. - Verify that both GUID tokens are generated in the expected C format.
- Confirm that the VFR compiler accepts the form set, varstore, controls, and question keys.
- Confirm that UNI compilation includes the expected strings and languages.
- Build the DXE module and verify that
EFITrainingSetup.ffsis generated. - Verify that the FFS file is included in the intended firmware volume.
- Inspect the build output for VFR, IFR, UNI, or HII package-registration warnings.
Before flashing, inspect the generated IFR to confirm that the interactive questions contain the expected callback flags and question identifiers.
10.2 Boot and Setup verification #
After flashing a test BIOS, boot the platform and inspect the serial log.
A successful initialization should produce a message similar to:
EFITraining Setup install...
EFITraining Setup HII packages registeredThen enter the Setup interface and navigate to the configured page.
The expected path might be:
Power On
|
v
SEC / PEI
|
v
DXE Dispatcher
|
v
EFITrainingSetupInstall()
|
+-- Install ConfigAccess
|
+-- Register HII Packages
|
v
Setup Form Browser
|
v
EFITraining Setup
|
+-- ExtractConfig()
|
+-- User Changes a Question
| |
| v
| Callback()
|
+-- Save Configuration
|
v
RouteConfig()
|
v
NVRAM Variable10.3 Three interactive tests #
Test 1: Numeric validation
Navigate to Fan PWM Minimum and enter 5.
Expected behavior:
FanPwmMin 5 is below the minimumThe Forms Browser should reject the proposed value because the callback returns EFI_INVALID_PARAMETER during CHANGING.
Test 2: Fan-control selection
Change Fan Control Mode from Auto to Full.
Expected callback output:
FanControl changing -> 1
FanControl changed -> 1The selected value should appear in the form’s uncommitted Browser data. The actual hardware configuration should be applied through the platform’s intended configuration-commit path.
Test 3: Save and persistence
Press the Setup interface’s Save & Exit command, commonly F10 on PC platforms.
Expected output:
EFITrainingSetup: RouteConfig savedAfter rebooting, re-enter Setup and verify that the stored configuration is still present.
This final check is important. A callback message alone does not prove that the NVRAM variable was successfully updated.
⚠️ 11. Common Setup Callback Development Pitfalls #
| Pitfall | Consequence | Recommended solution |
|---|---|---|
Missing INTERACTIVE |
Browser does not invoke the expected callback for the question | Add the interaction flag supported by the VFR compiler |
Using questionid instead of key |
Standard VFR compilation may fail or use an unsupported attribute | Use key to assign the question identifier |
| Duplicate question keys | Multiple questions may enter the same callback branch | Maintain unique identifiers in a central header |
| Incorrect variable structure layout | Browser reads or writes the wrong field offsets | Keep VFR varstore and C structure definitions aligned |
Returning Results = NULL from successful ExtractConfig |
Browser receives no valid configuration response | Use HII Config Routing and BlockToConfig() |
| RouteConfig does not persist the variable | Settings disappear after leaving Setup or rebooting | Convert with ConfigToBlock() and save through the correct variable service |
| Missing HII Database dependency | Package registration may fail before HII services are available | Add the appropriate external service dependencies |
| Self-dependency on ConfigAccess | DXE dispatch may be blocked by a protocol the module must install itself | Do not include the module’s own output protocol in its DEPEX |
| Writing unrelated question values directly in C | Browser display and uncommitted data may become inconsistent | Use BrowserCallback() or supported form-update functionality |
| Validating in CHANGED | Browser has already updated the value; errors are ignored | Perform rejection checks in CHANGING |
| Direct hardware writes during an uncommitted interaction | Hardware may change even if the user later cancels the form | Defer ordinary configuration changes until commit |
| Using arbitrary I/O ports | May corrupt unrelated hardware or produce undefined behavior | Use the proper device-specific hardware interface |
| Incorrect variable GUID | GetVariable() or SetVariable() may fail |
Verify the exact variable name and GUID |
| Overwriting variable attributes | May create persistence or policy problems | Preserve and validate the platform’s variable attributes |
Assuming RESET_REQUIRED enables a callback |
Callback may never execute | Use INTERACTIVE separately |
| Assuming string updates always refresh a visible form | Displayed text may remain stale | Use the platform-supported HII refresh mechanism |
| Excessive work inside Callback | Setup can become unresponsive | Keep callback execution short and defer lengthy operations |
11.1 Callback is not a replacement for VFR expressions #
If the only requirement is to gray out an option when another setting is disabled, a VFR conditional expression may be simpler than writing additional callback code.
Callbacks are most useful when the firmware must validate user input, retrieve dynamic information, update uncommitted values, or perform a platform-specific operation.
Keeping display logic in VFR and configuration logic in C makes the Setup implementation easier to maintain.
11.2 Do not confuse browser state with NVRAM state #
When a user changes a Setup question, the Forms Browser can hold an updated value that has not yet been committed.
That value is not necessarily stored in NVRAM immediately.
The distinction is essential:
Callback()processes interaction events.- Browser data contains the current editing state.
RouteConfig()applies the submitted configuration to persistent storage.
Writing directly to NVRAM inside every callback can undermine the normal Save/Discard workflow.
11.3 Do not assume the same callback integration works across every Aptio release #
Aptio TSE includes project-specific integration mechanisms, and older platforms may expose legacy HII interfaces or vendor-specific form-update functions.
The standard UEFI HII ConfigAccess interface provides a defined callback contract, but the surrounding Setup integration can differ between firmware trees.
Before implementing a new module, inspect an existing working Setup callback in the target platform and compare its VFR syntax, eLink registration, HII package handling, and callback prototype.
🏁 12. Summary: Making UEFI Setup Interactive #
The UEFI Setup callback mechanism provides a standardized way for the Forms Browser to communicate with firmware configuration logic.
In an AMI Aptio 4 project, the development process brings together VFR, UNI, SDL, HII package registration, and C-based ConfigAccess functions.
The complete configuration flow can be summarized as follows:
VFR defines the questions, UNI supplies the text, SDL integrates the module, ExtractConfig retrieves the current configuration, Callback handles interaction, and RouteConfig persists submitted changes.
The most important engineering rules are:
- Use
INTERACTIVEfor questions that need callback behavior. - Assign each question a unique
key. - Validate proposed values during
EFI_BROWSER_ACTION_CHANGING. - Use the appropriate browser interface when changing other questions.
- Keep uncommitted browser state separate from NVRAM persistence.
- Implement real
ExtractConfig()andRouteConfig()conversion logic. - Avoid direct hardware writes for ordinary uncommitted configuration changes.
- Match the ConfigAccess handle with the corresponding HII package registration.
- Verify AMI-specific callback and build integration against the target firmware tree.
Once these relationships are clear, more advanced Setup features become much easier to implement.
You can extend this example with CPU power controls, memory timing settings, PCIe configuration, fan curves, security options, and hardware-monitoring pages.
For further reading, see the UEFI 2.11 HII Configuration Processing specification, the TianoCore Driver Writer’s Guide to HII ConfigAccess, and KAD’s AMI TSE Setup customization guide.
Master the relationship between VFR, the Forms Browser, ConfigAccess, and NVRAM, and you can turn a static Setup page into a reliable, interactive firmware configuration interface.