↓ Skip to main content

How to Write a UEFI Event DXE Driver in AMI Aptio 4

How to Write a UEFI Event DXE Driver in AMI Aptio 4

UEFI firmware development frequently requires custom code to execute at a specific point in the boot process. Typical examples include emitting a POST code before legacy boot, displaying a diagnostic message when the firmware reaches ReadyToBoot, responding to protocol-related notifications, and performing controlled event-driven tasks during DXE.

The UEFI Event mechanism provides a standardized way to register callback functions that execute when the firmware signals a corresponding event. Instead of continuously polling for a particular boot stage, a DXE driver can register its callback and let the firmware invoke it when the relevant condition occurs.

This tutorial implements an AMI Aptio 4 example named EventSample. The driver registers two callbacks: one for the Legacy Boot event and another for ReadyToBoot. The first emits the POST code 0x77 through I/O port 0x80; the second writes a diagnostic message to the UEFI console.

The implementation covers component registration, SDL configuration, header organization, C source code, compilation rules, BIOS integration, and hardware verification.

Important: UEFI Event services are Boot Services capabilities. They are available after the firmware has entered an appropriate DXE environment, not during the PEI pre-memory phase before those services exist. Also, ReadyToBoot and LegacyBoot represent distinct events with different triggering conditions.

🧩 1. Understanding the UEFI Event Mechanism
#

UEFI Events provide an event-driven notification mechanism for firmware components. A driver creates an event and registers a notification function. When the relevant event is signaled, the firmware schedules the associated callback at its configured task priority level (TPL).

The callback typically receives two parameters:

  • EFI_EVENT Event identifies the event associated with the notification.
  • VOID *Context provides optional caller-supplied context.

The callback uses the EFI_EVENT_NOTIFY calling convention and should perform only the work required for that notification.

1.1 How Event Registration Works
#

The general lifecycle consists of three stages.

Stage Operation Purpose
Registration CreateEvent() or CreateEventEx() Creates an event and registers its callback
Notification Firmware signals the event Schedules the associated callback
Callback execution Registered notification function runs Performs the required firmware action

CreateEvent() creates a general-purpose event. CreateEventEx() additionally associates an event with a GUID-identified event group, which is the appropriate mechanism for standard notifications such as ReadyToBoot and LegacyBoot.

For current UEFI implementations, use CreateEventEx() with the appropriate event-group GUIDs when subscribing to these system events. Some AMI Aptio 4 projects also provide platform-specific event conventions; confirm the API and GUID definitions in the target codebase.

1.2 The Two Events Used in This Tutorial
#

The example registers two event-group notifications.

Event Trigger Condition Demonstration
LegacyBoot The firmware is about to invoke the legacy boot path Writes 0x77 to I/O port 0x80
ReadyToBoot The firmware signals the ReadyToBoot event group before booting the selected OS loader Prints a diagnostic message to the UEFI console

The LegacyBoot event is relevant only to platforms and boot configurations that support the legacy boot path. It should not be confused with a notification that runs during every normal UEFI boot.

ReadyToBoot is useful for final boot-stage notifications, but it is not a substitute for earlier initialization events. Code that must run before ReadyToBoot should register for the appropriate event or use another suitable firmware mechanism.

For a broader overview of the DXE execution environment, see UEFI BIOS Architecture, Boot Flow, and Secure Boot.

📁 2. Understanding the EventSample Module Files
#

The sample uses five files to register, configure, compile, and implement the DXE module.

File Responsibility
EventSample.cif Registers the component with the AMI build system
EventSample.sdl Defines the build switch, module path, and firmware-volume linkage
EventSample.mak Defines the compilation and FFS packaging rules
EventSample.h Contains shared header declarations
EventSample.c Implements the driver entry point and event callbacks

The module directory is organized as follows:

EventSample/
├── EventSample.cif
├── EventSample.sdl
├── EventSample.mak
├── EventSample.h
└── EventSample.c

Unlike a driver that depends on a separate protocol provider, this example does not require a custom .dxs file. Its event callbacks are registered during module initialization and are triggered later by the firmware.

If the driver also requires a protocol to be installed before dispatch, declare that prerequisite through the dependency-expression mechanism supported by the target project.

🪪 3. Step 1: Register the Component in EventSample.cif
#

The CIF file tells the AMI component-management system that the module exists and identifies the files belonging to it.

Complete CIF Example
#

    <component>
    name = "EventSample"
    category = ModulePart
    LocalRoot = "Sample\EventSample"
    RefName = "EventSample"

    [files]
    "EventSample.sdl"
    "EventSample.mak"
    "EventSample.h"
    "EventSample.c"
    </component>

Key Configuration Fields
#

  • name specifies the component’s display name.
  • category = ModulePart identifies the component type.
  • LocalRoot defines the module’s location relative to the project configuration.
  • RefName provides the internal module reference.
  • [files] lists the source and configuration files associated with the component.

The sample path must match the actual directory layout of the target Aptio project. In particular, verify that the LocalRoot value resolves correctly from the project’s component-management environment.

Copying the module into a source directory is not sufficient if the build system has not registered or discovered its CIF file.

Verification: Confirm that the component manager recognizes EventSample and can locate all four files listed in the CIF.

⚙️ 4. Step 2: Configure EventSample.sdl
#

The SDL file controls the module’s build configuration and integration into the final firmware image.

For this example, the important sections are TOKEN, PATH, MODULE, and ELINK.

4.1 TOKEN: Enable the Module
#

TOKEN
    Name = "EventSample_SUPPORT"
    Value = "1"
    TokenType = Boolean
    TargetEQU = Yes
    TargetMAK = Yes
    Master = Yes
End

The token acts as the main build switch.

Setting EventSample_SUPPORT to 1 enables the feature under the conventions used by this example. A value of 0 disables the module if the surrounding project configuration honors the token as intended.

The exact token semantics and generated macros depend on the AMI SDL implementation. Verify that the switch is exposed to the build system and included in the active project configuration.

4.2 PATH: Define the Module Directory
#

The PATH section associates a build variable with the source location.

PATH
    Name = "EventSample_DIR"
End

In some Aptio 4 projects, the build environment derives the path from the component’s LocalRoot or other project configuration. Other implementations require an explicit path.

If the target project expects one, define it according to that project’s SDL syntax, for example:

PATH
    Name = "EventSample_DIR"
    Path = "Sample\EventSample"
End

Use the form consistent with the surrounding modules. An unresolved path will prevent the MAK rules from finding the source files.

4.3 MODULE: Include the Build Rules
#

MODULE
    Name = "Includes EventSample.mak into project"
    File = "EventSample.mak"
End

This section integrates EventSample.mak into the project build system.

Without this integration, the component may appear in the project configuration without its expected compilation rules being invoked.

4.4 ELINK: Add the FFS File to the Firmware Volume #

    ELINK
        Name = "$(BUILD_DIR)\EventSample.ffs"
        Parent = "FV_MAIN"
        InvokeOrder = AfterParent
    End

The ELINK section associates the generated firmware file with the designated parent firmware volume.

The distinction between compilation and firmware integration is important. A DXE driver can compile and produce an FFS file without being included in the final BIOS image.

Verify the parent volume name, output path, and ELINK behavior against the target Aptio project. Some platforms use different volume names or additional integration rules.

The expected output in this example is:

$(BUILD_DIR)\EventSample.ffs

The presence of that file is only one verification point. The final image must also contain the module in the intended firmware volume.

📄 5. Step 3: Create EventSample.h
#

The header provides a central location for shared declarations and standard includes.

Complete Header Example
#

    #ifndef __EVENT_SAMPLE_HDR__
    #define __EVENT_SAMPLE_HDR__

    #include <Efi.h>
    #include <AmiDxeLib.h>

    #ifdef __cplusplus
    extern "C" {
    #endif

    EFI_STATUS EFIAPI EventSampleInit(
        IN EFI_HANDLE ImageHandle,
        IN EFI_SYSTEM_TABLE *SystemTable
    );

    VOID EFIAPI EventSampleReadyToBoot(
        IN EFI_EVENT Event,
        IN VOID *Context
    );

    VOID EFIAPI EventSampleLegacyBootNotify(
        IN EFI_EVENT Event,
        IN VOID *Context
    );

    #ifdef __cplusplus
    }
    #endif

    #endif

The header guard prevents duplicate declarations when the header is included by multiple source files.

The EFIAPI annotation preserves the UEFI calling convention expected by the target toolchain. If the project’s AMI headers or entry-point conventions define these declarations differently, follow the established conventions in compatible modules.

The sample does not require custom packed structures, so the unnecessary #pragma pack directives found in some boilerplate templates have been omitted.

💻 6. Step 4: Implement the Driver in EventSample.c
#

The C source performs event registration and defines the callback functions.

The entry point does not execute both callbacks immediately. It creates the notification events and returns; the corresponding callbacks run only when the firmware signals their event groups.

6.1 Include the Required Headers and Define Event GUIDs
#

    #include "EventSample.h"

    #include <AmiLib.h>
    #include <AmiDxeLib.h>
    #include <AmiCspLib.h>

    /*
     * Standard UEFI PI LegacyBoot event-group GUID:
     * 2A571201-4966-47F6-8B86-F31E41F32F10
     */
    EFI_GUID gEventSampleLegacyBootGuid = {
        0x2a571201,
        0x4966,
        0x47f6,
        { 0x8b, 0x86, 0xf3, 0x1e, 0x41, 0xf3, 0x2f, 0x10 }
    };

    /*
     * Standard UEFI ReadyToBoot event-group GUID:
     * 7CE88FB3-4BD7-4679-87A8-A8D8DEE50D2B
     */
    EFI_GUID gEventSampleReadyToBootGuid = {
        0x7ce88fb3,
        0x4bd7,
        0x4679,
        { 0x87, 0xa8, 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b }
    };

The event-group GUIDs identify the system events to which the callbacks subscribe. If the platform already provides these GUID declarations in its headers, reuse those definitions rather than introducing duplicate symbols.

AmiCspLib.h is included here for the AMI platform-specific port I/O helper used by the LegacyBoot callback. The precise header and function availability should be verified against the target codebase.

6.2 Register Both Events in the DXE Entry Point
#

    EFI_STATUS EFIAPI
    EventSampleInit(
        IN EFI_HANDLE ImageHandle,
        IN EFI_SYSTEM_TABLE *SystemTable
    )
    {
        EFI_STATUS Status;
        EFI_EVENT LegacyBootEvent = NULL;
        EFI_EVENT ReadyToBootEvent = NULL;

        /*
         * Initialize AMI library services before using pBS,
         * pST, or TRACE.
         */
        InitAmiLib(ImageHandle, SystemTable);

        TRACE((TRACE_ALWAYS,
            "EventSample: DXE Driver Init\n"));

        /*
         * Register for the standard LegacyBoot event group.
         */
        Status = pBS->CreateEventEx(
            EVT_NOTIFY_SIGNAL,
            TPL_CALLBACK,
            EventSampleLegacyBootNotify,
            NULL,
            &gEventSampleLegacyBootGuid,
            &LegacyBootEvent
        );

        if (EFI_ERROR(Status)) {
            TRACE((TRACE_ALWAYS,
                "EventSample: LegacyBoot registration failed\n"));
            return Status;
        }

        /*
         * Register for the standard ReadyToBoot event group.
         */
        Status = pBS->CreateEventEx(
            EVT_NOTIFY_SIGNAL,
            TPL_CALLBACK,
            EventSampleReadyToBoot,
            NULL,
            &gEventSampleReadyToBootGuid,
            &ReadyToBootEvent
        );

        if (EFI_ERROR(Status)) {
            TRACE((TRACE_ALWAYS,
                "EventSample: ReadyToBoot registration failed\n"));

            /*
             * Undo the first registration if the second one fails.
             */
            pBS->CloseEvent(LegacyBootEvent);
            return Status;
        }

        TRACE((TRACE_ALWAYS,
            "EventSample: Event registration completed\n"));

        return EFI_SUCCESS;
    }

The example uses the standard CreateEventEx() event-group model. Some legacy AMI projects may expose different wrappers or naming conventions for Boot Services, so verify support for CreateEventEx() and the relevant event-group definitions in your Aptio 4 environment.

The entry point initializes the AMI library, registers both callbacks, and returns a status reflecting whether registration succeeded.

If the second registration fails, it closes the first event to avoid leaving a partially initialized registration behind.

The event handles remain registered after the entry point returns. The driver must remain resident while callbacks can execute; a driver that supports unloading must arrange to close its events before its code is removed.

6.3 Understand the Event Registration Parameters
#

Each CreateEventEx() invocation contains six parameters.

Parameter Purpose
EVT_NOTIFY_SIGNAL Requests notification when the event is signaled
TPL_CALLBACK Sets the callback notification priority
Callback function Identifies the function to execute
NULL context Supplies no additional callback context
Event-group GUID Identifies LegacyBoot or ReadyToBoot
Event handle output Receives the newly created event handle

TPL_CALLBACK is appropriate for many ordinary event-notification tasks. High task-priority levels should not be selected arbitrarily because they can restrict which firmware services are safe to invoke.

Registration is not equivalent to immediate execution. The callback runs when the associated event is signaled, so the output for each event depends on the actual boot path.

6.4 Implement the LegacyBoot Callback
#

    VOID EFIAPI
    EventSampleLegacyBootNotify(
        IN EFI_EVENT Event,
        IN VOID *Context
    )
    {
        TRACE((TRACE_ALWAYS,
            "EventSample: LegacyBoot Notify Triggered\n"));

        /*
         * Emit a POST/debug checkpoint for a hardware
         * diagnostic card or platform debug interface.
         */
        IoWrite8(0x80, 0x77);

        /*
         * Do not introduce a long Stall() here in production.
         * Event callbacks should complete promptly.
         */
    }

The callback writes 0x77 to I/O port 0x80, a conventional POST-code output port on many compatible platforms.

The actual observation depends on the motherboard and debugging hardware. A POST-code display can show the value, but another firmware write may overwrite it shortly afterward.

This callback runs only if the firmware signals the LegacyBoot event group. Systems using only native UEFI boot, or platforms without a supported legacy boot path, may never trigger it.

For demonstrations, avoid inserting a long Stall() merely to keep the code visible. A multi-second delay inside a boot callback can slow boot progress and interfere with other firmware work.

6.5 Implement the ReadyToBoot Callback
#

    VOID EFIAPI
    EventSampleReadyToBoot(
        IN EFI_EVENT Event,
        IN VOID *Context
    )
    {
        EFI_STATUS Status;
        CHAR16 MsgBuf[] =
            L"CreateReadyToBootEvent AOTC EFI Sample";

        TRACE((TRACE_ALWAYS,
            "EventSample: ReadyToBoot Notify Triggered\n"));

        /*
         * The console may not be usable on every platform.
         * Validate the protocol pointers before accessing them.
         */
        if (pST == NULL ||
            pST->ConOut == NULL ||
            pST->ConOut->SetCursorPosition == NULL ||
            pST->ConOut->OutputString == NULL) {
            TRACE((TRACE_ALWAYS,
                "EventSample: Console output unavailable\n"));
            return;
        }

        /*
         * SetCursorPosition uses (Column, Row).
         */
        Status = pST->ConOut->SetCursorPosition(
            pST->ConOut,
            29,
            0
        );

        if (EFI_ERROR(Status)) {
            TRACE((TRACE_ALWAYS,
                "EventSample: SetCursorPosition failed\n"));
            return;
        }

        Status = pST->ConOut->OutputString(
            pST->ConOut,
            MsgBuf
        );

        if (EFI_ERROR(Status)) {
            TRACE((TRACE_ALWAYS,
                "EventSample: OutputString failed\n"));
        }
    }

The callback prints a diagnostic message through the UEFI Simple Text Output protocol.

The sample checks the system table, console interface, and required function pointers before using them. It also checks the return values from the console functions.

The CHAR16 array is initialized directly from the string literal, allowing the compiler to allocate enough storage for the message and its terminating null character.

The cursor-position parameters are passed as (Column, Row), so the example places the output at column 29, row 0.

Console output at ReadyToBoot is useful for controlled firmware diagnostics, but it is not guaranteed to appear on every platform. Console protocols may be unavailable, redirected, or configured differently.

The callback intentionally does not introduce a multi-second stall or reset the system. Those operations would unnecessarily delay the boot process or risk disrupting normal boot behavior.

6.6 Why ResetSystem Should Not Be Part of This Demonstration
#

A sample may occasionally include a call such as:

    /*
     * Demonstration only — do not enable during normal boot.
     *
     * pRT->ResetSystem(
     *     EfiResetCold,
     *     EFI_SUCCESS,
     *     0,
     *     NULL
     * );
     */

Calling ResetSystem() from the ReadyToBoot callback initiates a system reset rather than allowing the normal boot path to continue.

Because the event may be triggered during each subsequent boot, an unconditional reset can create a repeated reboot loop.

For a diagnostic module, the recommended behavior is to record the event and return promptly. Reset logic should be used only when explicitly required by a controlled test and when a reliable recovery procedure is available.

🛠️ 7. Step 5: Configure EventSample.mak
#

The MAK file connects the sample’s build configuration to the AMI compilation and firmware packaging process.

Complete MAK Example
#

    all : $(BUILD_DIR)\EventSample.mak EventSampleBin

    $(BUILD_DIR)\EventSample.mak : $(EventSample_DIR)\$(@B).mak \
                                    $(@B).mak \
                                    $(BUILD_RULES)
        $(CIF2MAK) $(EventSample_DIR)\$(@B).cif $(CIF2MAK_DEFAULTS)

    EventSampleBin : $(AMIDXELIB) $(AMICSPLIB)
        $(MAKE) /$(MAKEFLAGS) $(BUILD_DEFAULTS) \
        /f $(BUILD_DIR)\EventSample.mak all \
        GUID=2C2AA475-F939-48EB-1D74-7FED629CB999 \
        ENTRY_POINT=EventSampleInit \
        TYPE=BS_DRIVER \
        COMPRESS=1

This example retains the AMI build conventions supplied for EventSample. It should be checked against the actual MAK rules and generated makefiles in the target project, as build dependencies and macros can differ among Aptio 4 branches.

7.1 Important Build Parameters
#

Parameter Meaning
GUID Identifies the firmware file
ENTRY_POINT Names the DXE driver’s entry function
TYPE=BS_DRIVER Selects the project-specific boot-services driver type
COMPRESS=1 Requests the configured FFS compression behavior
CIF2MAK Converts CIF configuration into build-system input
BUILD_RULES Supplies the project’s applicable build rules

The sample GUID is provided for illustration only. Generate a unique GUID for a production module and check the repository for collisions before integrating the driver.

The ENTRY_POINT value must match the function implemented in EventSample.c and the conventions expected by the target AMI build rules.

Depending on the project’s compilation macros, the MAK configuration may need explicit object-file declarations or additional library dependencies. Use a known-working DXE module in the same codebase as the reference.

For additional background on the relationship between DXE modules, firmware packaging, and build integration, see How to Write a UEFI DXE Driver in AMI Aptio 4.

🔨 8. Build, Flash, and Verify the Event Driver
#

Once the CIF, SDL, header, C source, and MAK files are prepared, verify their integration into the target Aptio 4 project.

8.1 Register and Enable the Component
#

Place the EventSample directory in the appropriate project location. For the supplied example, the parent location is:

Training\SOEMporting\Samplecode\EFITraining\

Register the component through the project’s normal component-management process.

Open the SDL editor and confirm that EventSample_SUPPORT is set to 1. Ensure that the build environment resolves EventSample_DIR correctly.

8.2 Compile and Inspect the Output
#

Run the normal build for the target firmware configuration.

Check the build log for successful compilation, linking, and generation of the expected FFS file:

$(BUILD_DIR)\EventSample.ffs

Then inspect the final firmware image to verify that the module is actually present in the intended FV_MAIN volume.

A successful source compilation does not prove that the FFS file was generated or included in the image. Validate each stage separately.

8.3 Flash and Verify on the Target Platform
#

Use the approved flashing and recovery procedure for the target hardware. Validate the image before deploying it to a production system.

The expected observations are:

Verification Method Expected Result
Serial trace log EventSample: DXE Driver Init
Event registration log EventSample: Event registration completed
LegacyBoot callback EventSample: LegacyBoot Notify Triggered
POST-code diagnostic card 0x77 when the LegacyBoot event is triggered
ReadyToBoot callback EventSample: ReadyToBoot Notify Triggered
UEFI console CreateReadyToBootEvent AOTC EFI Sample when console output is available

The LegacyBoot callback should be tested using a configuration that actually enters the supported legacy boot path. The ReadyToBoot callback can be validated separately through the corresponding trace message and console output.

If the DXE initialization trace is present but neither event callback runs, examine whether the relevant event groups are signaled on the platform and whether the target boot path reaches the associated stage.

If the initialization trace is absent, check the module’s build switch, entry-point linkage, firmware-volume inclusion, and driver dispatch behavior before investigating the callback code itself.

⚠️ 9. Common UEFI Event Development Pitfalls
#

Pitfall Potential Consequence Recommended Solution
EventSample_SUPPORT is disabled Module is excluded from the build Verify the token in the active SDL configuration
Module GUID conflicts with another FFS file Packaging or firmware-image integration problems Generate a unique GUID and check the repository
InitAmiLib() is omitted AMI library services may be uninitialized Initialize the library before using pBS, pST, or TRACE
Wrong event API or event-group GUID Callback never runs or registration fails Use the correct CreateEventEx() registration and platform-supported GUID definitions
Incorrect assumptions about LegacyBoot Callback never triggers on UEFI-only systems Verify that the platform actually signals the legacy boot event
Cursor-position arguments are reversed Diagnostic output appears in the wrong location Pass (Column, Row) in that order
Console protocol is unavailable Screen output fails or the callback accesses an invalid pointer Validate pST, ConOut, and required function pointers
Long operations occur in callbacks Boot latency or interference with firmware processing Keep callbacks short and avoid unnecessary stalls
Events remain registered after a driver is unloaded Callback may reference invalid code Close registered events before unloading the driver
ResetSystem runs during every ReadyToBoot event Repeated reboot loop Keep reset calls disabled unless performing a controlled recovery test

Additional Engineering Recommendations
#

Use event-driven notifications for work that genuinely depends on a particular firmware milestone. For periodic tasks, use the appropriate timer-event mechanism instead of repeatedly registering one-shot system notifications.

Keep callback execution short, validate all interfaces, and record sufficient trace information to distinguish registration failures from missing event notifications.

Most importantly, treat system event groups as part of the firmware execution model. An event registration does not guarantee that a callback will run on every boot; the platform must actually signal the corresponding event.

📝 10. Conclusion: Master the UEFI Event Development Pattern
#

An AMI Aptio 4 Event DXE module combines component registration, build configuration, source implementation, event registration, and firmware integration.

The essential workflow is:

  1. CIF: Register the component.
  2. SDL: Configure the module switch, path, and firmware-volume linkage.
  3. H: Define the module’s shared declarations.
  4. C: Register the events and implement the callback functions.
  5. MAK: Compile, link, and package the driver.

At runtime, the key sequence is Initialize → Register → Wait for notification → Execute callback → Verify the result.

For standard ReadyToBoot and LegacyBoot notifications, CreateEventEx() provides the event-group mechanism. The callbacks then perform the relevant diagnostic action, such as emitting a POST code or writing a message to the console.

Once this pattern is understood, the same approach can be extended to other firmware notifications and timing-dependent tasks. The critical requirements remain consistent: register the appropriate event, use the correct GUID and callback signature, keep callback work bounded, and validate the resulting behavior on the target platform.

Related