How Option ROMs Register Interrupts in UEFI

Third-party Option ROMs (OpROMs) operating in a Unified Extensible Firmware Interface (UEFI) environment handle interrupts differently than traditional legacy BIOS implementations. This article examines the architectural mechanisms UEFI provides for interrupt management, detailing how native UEFI drivers register hardware interrupt handlers, use event-driven timer callbacks as non-preemptive alternatives, replace legacy software interrupts with protocols, and manage legacy OpROM execution via the Compatibility Support Module (CSM).

The Architectural Shift from Legacy BIOS to UEFI

In legacy BIOS systems, an Option ROM loaded from a PCI expansion card hooked directly into the real-mode Interrupt Vector Table (IVT) located at memory address 0x0000:0x0000 or modified the protected-mode Interrupt Descriptor Table (IDT). A legacy OpROM provided hardware and software services by intercepting real-mode interrupt calls such as INT 10h for video or INT 13h for storage.

Native UEFI eliminates raw interrupt hooking for boot-time device drivers. UEFI runs in protected or long mode with a flat memory model and operates predominantly as a single-threaded, polling-based, cooperative multitasking environment. Consequently, native UEFI Option ROMs are structured as standard UEFI drivers (PE32+ binaries) rather than raw machine code blobs.

Registering Hardware Interrupts via Protocols

When a hardware device requires servicing via a physical interrupt line (such as PCI INTx or MSI/MSI-X) during the pre-boot phase, the UEFI driver cannot manipulate the CPU's IDT directly. Instead, it must rely on architectural protocols provided by the DXE (Driver Execution Environment) core.

To register a hardware interrupt service routine, a driver uses the EFI_HARDWARE_INTERRUPT_PROTOCOL (or platform-specific variations such as EFI_HARDWARE_INTERRUPT2_PROTOCOL). The process proceeds through the following steps:

  1. Locate the Protocol: The third-party driver calls the Boot Service gBS->LocateProtocol() to obtain a pointer to the platform's active EFI_HARDWARE_INTERRUPT_PROTOCOL interface.

  2. Define the Handler: The driver defines an interrupt handler function adhering to the EFI_CPU_INTERRUPT_HANDLER prototype:

    typedef VOID (EFIAPI *EFI_CPU_INTERRUPT_HANDLER)(
        IN EFI_EXCEPTION_TYPE InterruptType,
        IN EFI_SYSTEM_CONTEXT SystemContext
    );
  3. Register the Routine: The driver invokes the RegisterInterruptHandler member function of the protocol, passing the target interrupt vector source assigned to the PCI device along with the pointer to the handler function.

  4. Enable the Vector: Once registered, the driver calls EnableInterruptSource to unmask the interrupt vector in the platform's interrupt controller (e.g., APIC).

Because UEFI core services are generally not re-entrant, code running within a hardware interrupt handler must be minimal, avoiding calls to most UEFI Boot Services to prevent deadlocks or state corruption.

Event-Driven Asynchronous Polling as the Standard Approach

Due to the constraints and platform dependencies of hardware interrupts in pre-boot environments, the standard UEFI design pattern discourages raw hardware ISRs for third-party Option ROMs. Instead, UEFI provides an asynchronous event model that abstracts interrupt-like behavior without preemptive context switching:

  1. Creating a Timer Event: The driver uses gBS->CreateEvent() with the type EVT_TIMER | EVT_NOTIFY_SIGNAL and assigns a notification callback function.
  2. Arming the Timer: The driver calls gBS->SetTimer(), setting the timer to TimerPeriodic with a trigger interval (often ranging from 10 to 100 milliseconds).
  3. Execution at TPL: The UEFI core's scheduler calls the driver’s callback function whenever the timer expires, executing at a specific Task Priority Level (TPL), typically TPL_CALLBACK or TPL_NOTIFY.

Inside this callback, the driver queries its hardware status registers to determine if an operation has finished or if an error has occurred, mimicking an interrupt-driven state machine safely within the UEFI cooperative model.

Replacing Software Interrupts with Protocols

In legacy environments, third-party Option ROMs registered themselves as software interrupt handlers so operating system loaders or other utilities could invoke their services. In UEFI, software interrupts are entirely replaced by the UEFI Protocol model:

  1. Protocol Implementation: The OpROM implements a defined interface, such as EFI_BLOCK_IO_PROTOCOL for mass storage controllers or EFI_GRAPHICS_OUTPUT_PROTOCOL (GOP) for display adapters.
  2. Publishing the Interface: The driver calls gBS->InstallMultipleProtocolInterfaces() to bind its function pointers and device path to the handle representing the underlying hardware controller.
  3. Consumer Discovery: Other drivers and the OS boot manager locate and call these services directly through function pointers, bypassing the need for software interrupt dispatching.

Legacy Option ROMs and the Compatibility Support Module (CSM)

If a third-party Option ROM is compiled strictly as a legacy binary rather than a native UEFI driver, it cannot natively interact with the UEFI driver model. In systems where legacy boot support is enabled:

  1. The UEFI firmware loads the Compatibility Support Module (CSM), which provides a partial real-mode BIOS execution environment.
  2. The CSM driver (EFI_LEGACY_BIOS_PROTOCOL) copies the legacy OpROM into the Compatibility16 execution space (typically the C0000h–EFFFFh region).
  3. The CSM transitions the processor to real or virtual-8086 mode and executes the OpROM's entry point.
  4. The legacy OpROM then directly writes to the 16-bit Interrupt Vector Table (IVT) in the first kilobyte of physical RAM, registering traditional software and hardware interrupt handlers (such as INT 13h or hardware IRQs routed through the legacy 8259 PIC) exactly as it would on native BIOS firmware.