AMI Aptio 4 PEI Module Tutorial: Build a Custom PEIM
Developing a PEI module is one of the fundamental exercises in UEFI firmware engineering. It introduces the interaction between PEI Modules (PEIMs), PEIM-to-PEIM Interfaces (PPIs), dependency expressions, firmware volumes, and the build configuration that integrates a component into the final firmware image.
This tutorial uses the AMI Aptio 4 training module EFITrainingPEI as a practical example. The goal is to build a PEIM that locates an EFI_TRAINING_PPI, calls its CheckPoint() method with the diagnostic value 0x88, and invokes its Stall() method to demonstrate that the PPI is accessible.
Although the C implementation is small, integrating it requires several coordinated files. The example covers five core artifacts:
| File | Responsibility |
|---|---|
EFITrainingPEI.c |
Implements the PEIM’s entry point and application logic |
EFITrainingPEI.dxs |
Defines the PEIM’s dispatch dependencies |
EFITrainingPEI.cif |
Registers the component with the AMI build system |
EFITrainingPEI.mak |
Defines the build and PEIM packaging parameters |
EFITrainingPEI.sdl |
Configures build tokens, module inclusion, source paths, and firmware-volume integration |
These files form a typical component-integration pattern in the supplied Aptio 4 project. Exact syntax and build conventions can vary between AMI framework versions and motherboard projects, so the examples should be validated against the target source tree.
🧠 1. Understand the PEI Environment #
1.1 What does PEI do? #
PEI stands for Pre-EFI Initialization. It is an early phase of the UEFI Platform Initialization architecture, responsible for preparing the environment required by subsequent firmware phases.
During PEI, firmware may establish temporary memory using Cache-As-RAM (CAR), initialize essential platform components, discover and initialize permanent DRAM, and prepare Handoff Blocks (HOBs) that communicate information to the DXE phase.
The environment is more constrained than DXE, particularly before permanent memory is available. Consequently, PEI code must use the services and interfaces supported by the PEI environment rather than assuming that DXE or operating-system facilities are available.
Three concepts are especially important when developing a PEIM.
PEIM — PEI Module
A PEIM is a firmware module executed by the PEI Dispatcher. Its type, entry point, dependency information, and firmware-file packaging must be consistent with the target platform’s build conventions.
PPI — PEIM-to-PEIM Interface
PPIs provide interfaces between PEIMs. A PPI is identified by a GUID and exposes an interface structure containing data or function pointers. A consuming PEIM can locate the interface through the PEI Services Table.
DEPEX — Dependency Expression
A dependency expression tells the PEI Dispatcher which conditions must be satisfied before a PEIM can execute. For example, a module that depends on a particular PPI should not be dispatched until that PPI is available.
For the architectural background, see the UEFI Platform Initialization Specification: PEI Services and PEIM-to-PEIM interfaces and firmware volumes.
1.2 The five-file integration model #
The EFITrainingPEI module illustrates how source code becomes an executable PEIM integrated into the firmware image.
The overall workflow is:
- Implement the PEIM’s entry point in
EFITrainingPEI.c. - Declare the required PPI in
EFITrainingPEI.dxs. - Register the component and its files in
EFITrainingPEI.cif. - Configure compilation and PEIM packaging through
EFITrainingPEI.mak. - Connect the component to the project build and firmware-volume configuration through
EFITrainingPEI.sdl.
The files serve different purposes. A correct C implementation cannot compensate for a missing build registration, an unsatisfied dependency, or an incorrect firmware-volume integration setting.
💻 2. Step 1: Implement EFITrainingPEI.c
#
The C source contains the PEIM entry point. Its primary tasks are to log entry, locate the training PPI, call CheckPoint(0x88), and invoke the PPI’s delay method.
Complete source code #
//**********************************************************************
//<AMI_HEADER>
// Copyright (C) 1985-2005, American Megatrends, Inc.
// All Rights Reserved.
// 6145-F Northbelt Pkwy, Norcross, GA 30071
// Phone: (770)-246-8600
//...</AMI_HEADER>
//**********************************************************************
#include "Efi.h"
#include "Pei.h"
#include "token.h"
#include <AmiPeiLib.h>
#include "..\PPI\EFITrainingPPI.h"
EFI_GUID gEFITrainingPpiGuid = EFI_TRAINING_PPI_GUID;
//
// Main Program
//
EFI_STATUS
EFITraining_PEI_Test (
IN EFI_FFS_FILE_HEADER *FfsHeader,
IN EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
EFI_TRAINING_PPI *EFITrainingPpi;
PEI_TRACE((-1, PeiServices,
"EFI Training PEI testing....\n"));
//
// Locate EFI Training PPI
//
Status = (*PeiServices)->LocatePpi (
PeiServices,
&gEFITrainingPpiGuid,
0,
NULL,
&EFITrainingPpi
);
if (EFI_ERROR (Status)) {
PEI_TRACE((-1, PeiServices,
"Cannot locate EFI Training PPI...\n"));
return EFI_SUCCESS;
}
//
// Issue the training checkpoint
//
EFITrainingPpi->CheckPoint (0x88);
//
// Delay for 100 seconds.
// Unit: microseconds, according to this PPI's contract.
//
EFITrainingPpi->Stall (PeiServices, 100000000);
return EFI_SUCCESS;
}This example follows the entry-point style and interface definitions shown in the supplied AMI Aptio 4 material. The actual EFI_TRAINING_PPI structure must be defined in EFITrainingPPI.h, including the function-pointer signatures for CheckPoint() and Stall().
The Stall() example assumes that the training PPI defines its second parameter in microseconds. Confirm this in the PPI declaration and implementation before using the code in a different project.
2.1 Header files and the PPI definition #
The source includes the following headers:
| Header | Purpose |
|---|---|
Efi.h |
Common EFI data types and definitions |
Pei.h |
PEI types, services, and related declarations |
token.h |
Generated build-token definitions used by the project |
AmiPeiLib.h |
AMI PEI library support, including utilities such as PEI_TRACE |
..\PPI\EFITrainingPPI.h |
Training PPI GUID, interface structure, and method declarations |
The relative path to EFITrainingPPI.h is specific to the illustrated project layout. If the header resides elsewhere in the source tree, update the include path and the build configuration accordingly.
The following declaration provides the GUID used to identify the interface:
EFI_GUID gEFITrainingPpiGuid = EFI_TRAINING_PPI_GUID;The name gEFITrainingPpiGuid is the C variable used by this module. EFI_TRAINING_PPI_GUID is the macro that supplies the actual GUID value.
The GUID must match the one used by the PEIM that installs the training PPI. Otherwise, the consumer will search for a different interface and LocatePpi() will not find the intended instance.
2.2 Understand the PEIM entry-point signature #
The sample declares the entry point as:
EFI_STATUS
EFITraining_PEI_Test (
IN EFI_FFS_FILE_HEADER *FfsHeader,
IN EFI_PEI_SERVICES **PeiServices
);FfsHeader identifies the firmware file associated with the module under the entry-point convention used by this project. The function does not use it directly in the shown implementation.
PeiServices provides access to the PEI Services Table. The table contains functions supplied by the PEI Foundation, including PPI discovery and other PEI services.
The exact entry-point typedef and calling-convention requirements should be checked against the target AMI framework. Later UEFI PI implementations may use different type names, such as EFI_PEI_FILE_HANDLE, and a module should follow the ABI expected by its actual build environment.
2.3 Use PEI_TRACE for diagnostic output
#
PEI_TRACE((-1, PeiServices,
"EFI Training PEI testing....\n"));Early PEI code should not assume that a console or standard printf() implementation is available. In the supplied AMI project, PEI_TRACE provides a framework-specific diagnostic mechanism.
The initial log helps establish whether the PEIM’s entry point has been reached. If the message never appears, investigate module dispatch and integration before focusing on the PPI method calls.
The exact routing of PEI_TRACE depends on the platform’s debug configuration and implementation. It may use serial output or another configured diagnostic path.
2.4 Locate the training PPI with LocatePpi()
#
The central operation is the call to the PEI Services Table:
Status = (*PeiServices)->LocatePpi (
PeiServices,
&gEFITrainingPpiGuid,
0,
NULL,
&EFITrainingPpi
);The arguments have the following meanings:
| Argument | Meaning |
|---|---|
PeiServices |
Pointer to the PEI Services Table reference expected by the service |
&gEFITrainingPpiGuid |
GUID identifying the requested PPI |
0 |
Requests the first matching PPI instance |
NULL |
Does not request the PPI descriptor through this optional output parameter |
&EFITrainingPpi |
Receives the pointer to the located PPI interface |
Because the parameter is declared as EFI_PEI_SERVICES **, the expression (*PeiServices) dereferences it to access the services table. The ->LocatePpi expression then selects the function pointer from that table.
The fourth argument is an optional PPI descriptor output parameter, not a registration-notification callback. PEI provides a separate NotifyPpi() service for registering notifications about PPIs becoming available.
If LocatePpi() succeeds, the output pointer refers to the requested interface. The module can then call the methods defined by that PPI.
The code uses the output pointer in its native type. If the target compiler reports pointer-type warnings, verify the prototype provided by the AMI headers and use the casting convention required by that framework.
2.5 Call CheckPoint() and Stall()
#
After locating the interface, the sample performs two operations:
EFITrainingPpi->CheckPoint (0x88);
EFITrainingPpi->Stall (PeiServices, 100000000);CheckPoint(0x88)
The method receives the diagnostic value 0x88. In the training implementation described here, that value is intended to appear on a Port 80 POST diagnostic card.
Seeing the value indicates that execution reached the call, provided the PPI’s CheckPoint() implementation actually routes the supplied code to the POST-code output.
The PPI’s name alone does not guarantee Port 80 behavior. That behavior must be defined by the implementation of CheckPoint().
Stall(PeiServices, 100000000)
The example requests a delay of 100,000,000 microseconds:
\[ 100{,}000{,}000\ \mu s = 100\ \text{seconds} \]This is a deliberately long delay intended to make the call noticeable during testing. Depending on the implementation, boot appears to pause for approximately 100 seconds.
A long stall is useful only in a controlled test environment. It can make the platform appear unresponsive, interact with watchdog timers, or prevent time-sensitive initialization from completing promptly. Reduce the interval when debugging the ordinary boot sequence.
For quick reference:
| Requested delay | Microseconds |
|---|---|
| 1 millisecond | 1000 |
| 10 milliseconds | 10000 |
| 1 second | 1000000 |
| 10 seconds | 10000000 |
| 100 seconds | 100000000 |
These conversions apply only if the PPI’s Stall() contract specifies microseconds.
2.6 Decide how to handle a missing PPI #
The supplied code logs an error when the PPI cannot be located, but returns EFI_SUCCESS:
if (EFI_ERROR (Status)) {
PEI_TRACE((-1, PeiServices,
"Cannot locate EFI Training PPI...\n"));
return EFI_SUCCESS;
}For a nonessential training module, allowing PEI to continue may be the intended behavior. A missing diagnostic interface should not automatically be treated as a fatal platform failure.
However, the behavior should reflect the module’s actual responsibility.
If the PPI is mandatory for a required platform function, returning success can conceal an integration problem. Production firmware should use an error-handling policy appropriate to the dependency and the consequences of continuing without that interface.
A further distinction is important: if the PPI is declared as a DEPEX requirement and is never installed, the dispatcher may never invoke this PEIM at all. In that case, the code’s entry log and its LocatePpi() error message will both be absent.
🧩 3. Step 2: Define Dependencies in EFITrainingPEI.dxs
#
The dependency file tells the PEI Dispatcher that the training PPI must be available before the module can run.
Complete dependency expression #
//**********************************************************************
//<AMI_HEADER>
// Copyright (C) 1985-2005, American Megatrends, Inc.
//...</AMI_HEADER>
#include "..\PPI\EFITrainingPPI.h"
DEPENDENCY_START
EFI_TRAINING_PPI_GUID
DEPENDENCY_ENDThe DEPENDENCY_START and DEPENDENCY_END markers are part of the AMI build syntax used by the supplied example.
The expression names the GUID of the required training PPI. The PEI Dispatcher uses the resulting dependency information when determining whether the PEIM is eligible for execution.
3.1 Why DEPEX matters #
The C code performs a runtime lookup through LocatePpi(), but the dependency expression adds a dispatch-time requirement.
These mechanisms serve different purposes:
- DEPEX determines when the PEIM is eligible to be dispatched.
LocatePpi()obtains the interface pointer when the PEIM executes.
If the PPI is not installed and the PEIM’s dependency is not satisfied, the dispatcher will normally defer execution rather than enter the C function and print the missing-PPI message.
This is one of the main reasons a PEIM can appear to be silently absent even when its source code compiles correctly.
3.2 Key rules for the dependency file #
Include the PPI header. The file must have access to the correct EFI_TRAINING_PPI_GUID definition. Reusing a macro from the interface header reduces the risk of inconsistent GUID values.
Declare the intended dependencies. If a module requires multiple PPIs, the expression must reflect the actual prerequisites using the syntax supported by the build framework.
Verify the generated DEPEX section. The .dxs source is transformed by the build tools into PEI dependency information, which is packaged in the resulting firmware file as an EFI_SECTION_PEI_DEPEX section.
The .dxs file does not behave like a normal C source file. Its purpose is to describe runtime dispatch requirements through build-generated firmware metadata.
🪪 4. Step 3: Register the Component in EFITrainingPEI.cif
#
The CIF file registers the component and its associated files with the AMI build system.
Complete component description #
<Component>
name = "EFITraining - PEI"
category = ModulePart
LocalRoot = "EFITraining\PEI"
RefName = "EFITrainingPEI"
[files]
"EFITrainingPEI.sdl"
"EFITrainingPEI.mak"
"EFITrainingPEI.dxs"
"EFITrainingPEI.c"
<endComponent>The syntax shown is the AMI component-description format used by this example. It should not be treated as generic XML for arbitrary build systems.
4.1 Understand the CIF fields #
| Field | Purpose |
|---|---|
name |
Display name of the component |
category |
Classifies it as a ModulePart |
LocalRoot |
Identifies the component’s root directory within the sample-code structure |
RefName |
Provides the component reference name used by the build configuration |
[files] |
Registers the component’s SDL, MAK, DEPEX, and C source files |
For this component:
nameidentifies it asEFITraining - PEI.LocalRootpoints toEFITraining\PEI.RefNameisEFITrainingPEI.- The files section lists the configuration, build, dependency, and implementation files.
The CIF registration allows the AMI component build tools to discover the module and use its build configuration.
4.2 How CIF relates to MAK generation #
In the supplied workflow, CIF2MAK reads the component description and generates a build-oriented makefile under BUILD_DIR.
This separates component registration from the generated build rules. If the CIF points to the wrong directory or omits a required source artifact, subsequent build stages may fail to locate the module’s inputs.
When troubleshooting a missing build target, verify the component name, LocalRoot, RefName, and file paths before investigating the C code.
🏗️ 5. Step 4: Configure EFITrainingPEI.mak
#
The MAK file connects the component metadata to the compilation and packaging workflow. The supplied file has two main responsibilities: generating the working build file and invoking the build system to produce the PEIM.
Complete build-script example #
all : EFITrainingPEI
EFITrainingPEI : $(BUILD_DIR)\EFITrainingPEI.mak EFITrainingPEIBin
$(BUILD_DIR)\EFITrainingPEI.mak : $(EFITraining_PEI_DIR)\EFITrainingPEI.cif \
$(EFITraining_PEI_DIR)\EFITrainingPEI.mak \
$(BUILD_RULES)
$(CIF2MAK) $(EFITraining_PEI_DIR)\EFITrainingPEI.cif $(CIF2MAK_DEFAULTS)
EFITrainingPEIBin : $(AMIPEILIB) $(AMICSPLib)
$(MAKE) /$(MAKEFLAGS) $(BUILD_DEFAULTS) \
/f $(BUILD_DIR)\EFITrainingPEI.mak all \
NAME=EFITrainingPEI \
MAKEFILE=$(BUILD_DIR)\EFITrainingPEI.mak \
OBJECTS="$(BUILD_DIR)\$(EFITraining_PEI_DIR)\EFITrainingPEI.obj" \
GUID=67FAC09C-B25E-47bc-86DD-ADFD5C884F21 \
ENTRY_POINT=EFITraining_PEI_Test \
TYPE=PEIM \
DEPEX1=$(EFITraining_PEI_DIR)\EFITrainingPEI.dxs \
DEPEX1_TYPE=EFI_SECTION_PEI_DEPEX \
COMPRESS=0
This is an example of the build configuration for the supplied project, rather than a portable makefile for every UEFI toolchain.
5.1 Stage one: Generate the build-directory MAK file #
The following target defines the dependency relationship:
$(BUILD_DIR)\EFITrainingPEI.mak : $(EFITraining_PEI_DIR)\EFITrainingPEI.cif \
$(EFITraining_PEI_DIR)\EFITrainingPEI.mak \
$(BUILD_RULES)
$(CIF2MAK) $(EFITraining_PEI_DIR)\EFITrainingPEI.cif $(CIF2MAK_DEFAULTS)
The generated build-directory makefile depends on the component description, source-tree makefile, and build rules.
When those inputs require regeneration, CIF2MAK produces the working build file according to the defaults supplied by the project.
A missing path variable or incorrectly registered source directory can therefore fail before the C compiler is invoked.
5.2 Stage two: Compile and package the PEIM #
The EFITrainingPEIBin target depends on AMIPEILIB and AMICSPLib, then invokes the configured build process using the generated makefile.
The important parameters are:
| Parameter | Example value | Purpose |
|---|---|---|
NAME |
EFITrainingPEI |
Names the build target |
MAKEFILE |
$(BUILD_DIR)\EFITrainingPEI.mak |
Selects the generated makefile |
OBJECTS |
EFITrainingPEI.obj path |
Identifies the compiled object file |
GUID |
67FAC09C-B25E-47bc-86DD-ADFD5C884F21 |
Identifies the PEIM’s firmware file |
ENTRY_POINT |
EFITraining_PEI_Test |
Specifies the entry-point symbol |
TYPE |
PEIM |
Selects PEIM build and packaging behavior |
DEPEX1 |
EFITrainingPEI.dxs |
Supplies the dependency-expression source |
DEPEX1_TYPE |
EFI_SECTION_PEI_DEPEX |
Identifies the dependency section type |
COMPRESS |
0 |
Requests the configured uncompressed packaging mode |
Use TYPE=PEIM for this component. Selecting another module type, such as a DXE driver, changes the build and packaging conventions. A successfully compiled object is not sufficient to make a module eligible for PEI dispatch.
Choose a suitable file GUID. The example GUID should be replaced when creating a new component, using the project’s GUID-generation and registration conventions. Avoid accidental collisions with another file when a unique firmware-file identity is required.
Keep the entry-point name consistent. The ENTRY_POINT value must correspond to the C symbol built into the object file and must follow any calling-convention requirements imposed by the target framework.
Package the dependency section. The .dxs source must be processed into the dependency section expected by the PEI Dispatcher. If the resulting firmware file lacks the intended DEPEX section, verify the MAK parameters and the generated build output.
🔌 6. Step 5: Integrate the Component with EFITrainingPEI.sdl
#
The SDL file connects the component to the project’s configuration system. In the supplied example, it defines four sections: TOKEN, MODULE, PATH, and ELINK.
Complete SDL example #
TOKEN
Name = "EFITrainingPEI_SUPPORT"
Value = "1"
Help = "Main switch to enable EFITrainingPEI support in Project"
TokenType = Boolean
TargetMAK = Yes
TargetH = Yes
Master = Yes
Token = "EFITrainingPEI_SUPPORT" "=" "1"
End
MODULE
Help = "Includes EFITrainingPEI.mak to Project"
File = "EFITrainingPEI.mak"
End
PATH
Name = "EFITraining_PEI_DIR"
Path = "EFITraining\PEI"
End
ELINK
Name = "$(BUILD_DIR)\EFITrainingPEI.ffs"
Parent = "FV_BB"
InvokeOrder = AfterParent
End6.1 TOKEN: Define the component’s build switch
#
The token EFITrainingPEI_SUPPORT acts as the component’s configuration switch.
TOKEN
Name = "EFITrainingPEI_SUPPORT"
Value = "1"
Help = "Main switch to enable EFITrainingPEI support in Project"
TokenType = Boolean
TargetMAK = Yes
TargetH = Yes
Master = Yes
Token = "EFITrainingPEI_SUPPORT" "=" "1"
EndThe key properties are:
Namedefines the token’s name.Valuesets its initial value to1.TokenType = Booleanmarks the token as a Boolean configuration.TargetMAK = Yesexposes it to the make environment.TargetH = Yesrequests generation into the project’s token-header mechanism.Master = Yesidentifies it as a master token in this framework’s configuration model.
This provides a project-level control for the feature. Whether setting the token to zero completely excludes the module depends on how the project’s SDL and build rules use that token. Verify the actual conditional build logic rather than assuming that a token definition alone removes all module artifacts.
6.2 MODULE: Connect the makefile to the build
#
MODULE
Help = "Includes EFITrainingPEI.mak to Project"
File = "EFITrainingPEI.mak"
EndThe MODULE declaration registers the makefile with the project’s build workflow.
This is a separate step from writing the makefile. A correctly formed build script can remain unused if the component is not connected to the project through the applicable SDL module configuration.
6.3 PATH: Define the source-directory variable
#
PATH
Name = "EFITraining_PEI_DIR"
Path = "EFITraining\PEI"
EndThis declares the path variable referenced throughout EFITrainingPEI.mak.
For example:
$(EFITraining_PEI_DIR)\EFITrainingPEI.cif
$(EFITraining_PEI_DIR)\EFITrainingPEI.dxs
$(EFITraining_PEI_DIR)\EFITrainingPEI.objThe variable name and path must match the actual project layout. If the MAK file references a different variable name, or the source directory is incorrectly specified, the build may fail with missing-file errors.
6.4 ELINK: Add the PEIM to a firmware volume
#
ELINK
Name = "$(BUILD_DIR)\EFITrainingPEI.ffs"
Parent = "FV_BB"
InvokeOrder = AfterParent
EndThis declaration connects the generated EFITrainingPEI.ffs artifact to the FV_BB node in the project’s firmware-volume build configuration.
The fields identify the build artifact, the parent node, and the specified invocation order.
The use of FV_BB is project-specific. Not every PEIM must reside in a firmware volume named FV_BB. The UEFI PI architecture allows PEIMs to reside in multiple firmware volumes, provided the platform’s firmware-volume discovery, dependency handling, and dispatch configuration make them available at the appropriate stage.
Before copying this ELINK into another project, verify that FV_BB is the intended destination and that the PEI environment can discover and dispatch the module from that volume.
For more background on the transition from early firmware initialization to later UEFI phases, see UEFI BIOS Architecture and Boot Flow.
🔍 7. Verification and Debugging #
After building the firmware image, verify each layer separately. This helps distinguish an entry-point problem from a missing PPI, an unsatisfied dependency, or a packaging error.
7.1 Check the serial or PEI debug output #
Connect to the platform’s configured debug-output interface and search for:
EFI Training PEI testing....If this message is present, the entry point has started and PEI_TRACE is working through the configured output path.
If the message is absent, possible causes include:
- The module was not included in the build.
- The FFS file was not integrated into a discoverable firmware volume.
- The module was built with an incorrect type.
- The DEPEX condition was not satisfied.
- The debug-output configuration does not route
PEI_TRACEto the interface being monitored.
A missing log does not, by itself, prove that LocatePpi() failed.
7.2 Verify the POST diagnostic code #
If the training PPI’s CheckPoint() method outputs POST codes to Port 80, observe the diagnostic card for 0x88.
The code indicates that execution reached the checkpoint method, subject to the behavior of the method’s implementation and the diagnostic hardware.
If the PEIM entry message appears but the POST code does not, inspect the PPI definition and implementation, the located interface pointer, and the method’s output mechanism.
7.3 Test the stall duration carefully #
The supplied value requests a delay of 100 seconds if the method’s unit is microseconds.
For a safer initial test, reduce it to a shorter interval, such as one millisecond or one second:
// 1 ms, if the PPI uses microseconds.
EFITrainingPpi->Stall(PeiServices, 1000);
// 1 second, if the PPI uses microseconds.
// EFITrainingPpi->Stall(PeiServices, 1000000);
Only one call should be active when testing. A stall that appears to block boot could be the expected result of a long delay, but it could also indicate an issue in the PPI implementation or surrounding firmware flow.
7.4 Inspect the generated firmware file #
Use the platform’s supported AMI image tools or a compatible firmware-volume analyzer to inspect the built image.
For this example, verify that the intended PEIM is present, that its file GUID matches the build configuration, and that the file contains a PEI DEPEX section.
The sample GUID is:
67FAC09C-B25E-47BC-86DD-ADFD5C884F21A successful C compilation does not guarantee that the PEIM has been incorporated into the final firmware image. Verify the complete chain from source registration through FFS packaging and firmware-volume integration.
Verification checklist #
| Verification method | Action | Expected result |
|---|---|---|
| Serial/debug output | Search for the entry-point trace | EFI Training PEI testing.... appears |
| PPI discovery | Check the code path following LocatePpi() |
A valid interface pointer is obtained |
| POST diagnostic card | Observe output from CheckPoint() |
0x88 appears if the method uses Port 80 output |
| Stall test | Use a controlled, known delay value | The delay matches the PPI’s documented unit and implementation |
| Firmware-volume inspection | Locate the resulting PEIM in the final image | FFS file and PEI DEPEX section are present |
| Build configuration | Confirm module type, GUID, entry point, and paths | Values match the compiled module and project configuration |
⚠️ 8. Common Pitfalls and Their Solutions #
8.1 Reusing a file GUID #
Copying a sample module without assigning an appropriate file GUID can cause collisions or confusion during firmware integration.
Solution: Generate and register a GUID according to the project’s conventions, and confirm that the resulting FFS file contains the expected value.
8.2 Selecting the wrong module type #
Setting TYPE=DXE_DRIVER for a component intended to execute in PEI produces the wrong module configuration for this use case.
Solution: Use the module type expected by the target PEIM build workflow and verify the resulting firmware-file type.
8.3 Leaving the DEPEX requirement unsatisfied #
If the training PPI is never installed, the dependency may never become true. The dispatcher may then leave the module undispatched without executing any of its C logging statements.
Solution: Confirm that the PPI-producing PEIM is built, packaged, and dispatched early enough to install the interface. Verify that the GUID in the producer, consumer, and DEPEX expression is identical.
8.4 Integrating the PEIM into the wrong firmware volume #
An ELINK configuration that points to the wrong firmware-volume node can place the PEIM where the expected PEI execution path will not discover it.
Solution: Confirm the target project’s volume layout, the role of FV_BB, and the applicable PEI firmware-volume discovery configuration.
8.5 Mismatching the SDL path and MAK variable #
If the PATH declaration uses one variable name while the MAK file references another, the build system may fail to locate source files or dependency files.
Solution: Keep EFITraining_PEI_DIR consistent throughout the project and check the generated makefile for the resolved path.
8.6 Using the wrong LocatePpi() argument interpretation
#
The fourth argument is not a PPI registration-notification callback. It is an optional output parameter for the located PPI’s descriptor.
Solution: Use the PEI Services prototype from the actual framework headers. If notification is required, use the relevant NotifyPpi() mechanism.
8.7 Assuming every PEIM belongs in FV_BB
#
FV_BB is the destination specified by this example, not a universal requirement imposed on all PEIMs.
Solution: Determine which firmware volumes are available during the relevant PEI phase and integrate the file into a suitable volume according to the target board’s configuration.
8.8 Miscalculating the delay #
A microsecond-based delay of 100000000 requests 100 seconds, while 1000 requests just one millisecond.
Solution: Calculate the requested delay before running the test, confirm the units in the PPI declaration, and avoid long stalls on production or unattended systems.
✅ 9. Conclusion #
The EFITrainingPEI example demonstrates the complete path from PEIM source code to firmware integration in an AMI Aptio 4 project.
The C implementation locates a PPI, issues checkpoint 0x88, and requests a delay. The .dxs file expresses the dispatch dependency, .cif registers the component, .mak configures compilation and packaging, and .sdl connects the component to the project build and firmware-volume configuration.
The most important lesson is that PEI development is not just about writing C code. Successful execution also depends on consistent GUID definitions, correct entry-point and module-type settings, a satisfied dependency expression, appropriate firmware-volume placement, and a build system that includes the resulting FFS file.
Once these relationships are understood, the same workflow can be adapted to more advanced PEIMs that consume other PPIs, initialize platform hardware, publish interfaces, or participate in the platform’s early initialization sequence.