/**
* @file Legacy8259.c
* @brief Legacy 8259 Programmable Interrupt Controller (PIC) UEFI Driver
*
* UEFI DXE driver for the Intel 8259A Programmable Interrupt Controller.
* Source: PcAtChipsetPkg/8259InterruptControllerDxe/8259/Legacy8259.c
*
* This driver manages the legacy 8259 PIC hardware by providing an
* EFI_LEGACY_8259_PROTOCOL interface. It handles PIC initialization
* (ICW1-ICW4), interrupt masking via IMR, mode switching (legacy vs.
* virtual wire), IRQ-to-vector translation, and EOI signaling.
*
* Hardware Layout:
* Master PIC (IRQs 0-7): Cmd=0x20, Data=0x21
* Slave PIC (IRQs 8-15): Cmd=0xA0, Data=0xA1
* ELCR1 (Master): 0x4D0
* ELCR2 (Slave): 0x4D1
* CMOS/RTC: Addr=0x70, Data=0x71
*
* Entry point flow:
* 1. Initialize UEFI globals (ImageHandle, SystemTable, BootServices, RuntimeServices)
* 2. Locate HOB list from system configuration table
* 3. Send EOI to both PICs for all IRQs (clear stale ISR bits)
* 4. Program ICW1-ICW4 for both PICs (master=0x58, slave=0x70)
* 5. Write initial IMR and ELCR to hardware
* 6. Install EFI_LEGACY_8259_PROTOCOL via gBS->InstallProtocolInterface()
*/
#include "../uefi_headers/Uefi.h"
#include "Legacy8259.h"
/*=============================================================================
* GUID Definitions
*============================================================================*/
/**
* EFI_LEGACY_8259_PROTOCOL_GUID -- Published protocol GUID.
* Stored at 0xFF0 in the binary (used by InstallProtocolInterface).
*/
EFI_GUID gEfiLegacy8259ProtocolGuid = EFI_LEGACY_8259_PROTOCOL_GUID;
/**
* Debug output protocol GUID (platform-specific).
* Stored at 0xFE0 in the binary (used by LocateProtocol in GetDebugInterface).
*/
EFI_GUID gEfiDebugOutputProtocolGuid = EFI_DEBUG_OUTPUT_PROTOCOL_GUID;
/**
* PCAT compatibility protocol GUID (platform-specific).
* Stored at 0x1000 in the binary (used by HandleProtocol in GetPcatCompatibilityFlag).
*/
EFI_GUID gEfiPcatCompatibilityProtocolGuid = EFI_PCAT_COMPAT_PROTOCOL_GUID;
/**
* HOB List GUID -- Standard UEFI PI specification GUID.
* Stored at 0x1010 in the binary.
*/
EFI_GUID gEfiHobListGuid = EFI_HOB_LIST_GUID;
/*=============================================================================
* Global State Variables
*============================================================================*/
//
// -- UEFI service pointer caches (initialized by ModuleEntryPoint) --
//
/** Cached pointer to EFI_BOOT_SERVICES (qword at 0x1098) */
STATIC EFI_BOOT_SERVICES *gBS = NULL;
/** Cached pointer to EFI_SYSTEM_TABLE (qword at 0x1090) */
STATIC EFI_SYSTEM_TABLE *gST = NULL;
/** Cached ImageHandle (qword at 0x10A0) */
STATIC EFI_HANDLE gImageHandle = NULL;
/** Cached pointer to EFI_RUNTIME_SERVICES (qword at 0x10A8) */
STATIC EFI_RUNTIME_SERVICES *gRT = NULL;
//
// -- PIC register shadow and state --
//
/** Combined master+slave IMR shadow (word at 0x1020: mMasterImr).
* Low byte = master IMR (port 0x21), High byte = slave IMR (port 0xA1).
* Initialized to 0xFFFF (all IRQs masked). */
STATIC UINT16 mMasterImr = 0xFFFF;
/** Combined master+slave ELCR shadow (word at 0x1088: mElcrCombined).
* Low byte = master ELCR (port 0x4D0), High byte = slave ELCR (port 0x4D1). */
STATIC UINT16 mElcrCombined = 0;
/** Master PIC vector base (byte at 0x1022).
* Written to master ICW2 during initialization. */
STATIC UINT8 mMasterVectorBase = 0xFF;
/** Current PIC operating mode (dword at 0x1078).
* 0 = LEGACY_8259_MODE_LEGACY, 1 = LEGACY_8259_MODE_VIRTUAL_WIRE.
* Initialized to 1 (virtual wire). */
STATIC UINT32 mMode = 1;
/** Slave PIC vector base (byte at 0x107C).
* Written to slave ICW2 during initialization. */
STATIC UINT8 mSlaveVectorBase = 0xFF;
/** Protocol handle (qword at 0x1080).
* Set by InstallProtocolInterface during driver initialization. */
STATIC VOID *mHandle = NULL;
/** Slave ELCR shadow (word at 0x10C2: mElcr2).
* Slave PIC ELCR value (port 0x4D1). */
STATIC UINT16 mElcr2 = 0;
/** Cached master IMR for init (word at 0x10C4: mCachedMasterImr).
* Set to 0x0EB8 during initialization.
* 0x0EB8 bit pattern enables timer(0), keyboard(1), cascade(2), IRQ5;
* masks COM1, COM2, floppy, LPT1, RTC. */
STATIC UINT16 mCachedMasterImr = 0x0EB8;
//
// -- Debug and HOB caches --
//
/** Cached debug protocol interface (qword at 0x10B0).
* Located once via LocateProtocol during first assert call. */
STATIC VOID *mDebugInterface = NULL;
/** Cached HOB list pointer (qword at 0x10B8).
* Located once from the system configuration table. */
STATIC VOID *mHobList = NULL;
/*=============================================================================
* Protocol Dispatch Table
*============================================================================*/
/**
* The EFI_LEGACY_8259_PROTOCOL instance published to the system.
* Located at offset 0x1030 in the binary.
*/
STATIC EFI_LEGACY_8259_PROTOCOL m8259Interface;
/*=============================================================================
* I/O Port Access Wrappers
*============================================================================*/
/**
* @brief Read a byte from an I/O port.
*
* @param[in] Port I/O port address.
*
* @return Byte read from the port.
*/
STATIC
UINT8
IoRead8 (
IN UINT16 Port
)
{
UINT8 Value;
__asm {
mov dx, Port
in al, dx
mov Value, al
}
return Value;
}
/**
* @brief Write a byte to an I/O port.
*
* @param[in] Port I/O port address.
* @param[in] Value Byte to write.
*/
STATIC
VOID
IoWrite8 (
IN UINT16 Port,
IN UINT8 Value
)
{
__asm {
mov dx, Port
mov al, Value
out dx, al
}
}
/*=============================================================================
* Forward Declarations
*============================================================================*/
STATIC
VOID
PicReadImr (
OUT UINT16 *MasterPmr OPTIONAL,
OUT UINT16 *ElcrValue OPTIONAL
);
STATIC
VOID *
GetDebugInterface (
VOID
);
/*=============================================================================
* Protocol Function Implementations
*============================================================================*/
/**
* @brief Initialize the 8259 PIC with ICW1-ICW4 programming -- Protocol fn [0].
*
* Programs both master and slave PICs with the initialization command word
* sequence. If the vector base for a PIC has changed since the last
* initialization, that PIC is fully re-initialized. Raises TPL to
* TPL_HIGH_LEVEL during programming to prevent interrupt delivery while
* the PIC is in an inconsistent state.
*
* ICW sequence:
* 1. ICW1 (0x11) to command port: edge-triggered, cascade mode, ICW4 needed
* 2. ICW2 (VectorBase) to data port: interrupt vector base address
* 3. ICW3 to data port: cascade connection map
* 4. ICW4 (0x01) to data port: 8086 mode, normal EOI, non-buffered
* 5. Restore saved IMR
*
* After programming both PICs, sends EOI to both to clear ISR bits.
*
* @param[in] This Protocol instance pointer.
* @param[in] MasterBase ICW2 vector base for master PIC (default 0x58).
* @param[in] SlaveBase ICW2 vector base for slave PIC (default 0x70).
*
* @return EFI_SUCCESS always.
*/
EFI_STATUS
EFIAPI
PicInitialize (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 MasterBase,
IN UINT8 SlaveBase
)
{
EFI_TPL OldTpl;
UINT8 SavedImr;
//
// Raise TPL to prevent interrupts during PIC programming.
//
OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
//
// If slave vector base changed, re-initialize slave PIC.
//
if (SlaveBase != mSlaveVectorBase) {
mSlaveVectorBase = SlaveBase;
//
// Save current slave IMR for restoration after ICW sequence.
//
SavedImr = IoRead8 (LEGACY_8259_SLAVE_PIC_DATA);
//
// ICW1: Write initialization command (0x11 = init + ICW4)
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_CMD, LEGACY_8259_ICW1_INIT_SEQ);
//
// ICW2: Write vector base address
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, mSlaveVectorBase);
//
// ICW3: Slave ID (0x02 = slave on master IRQ2)
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, LEGACY_8259_ICW3_SLAVE_CASCADE);
//
// ICW4: 8086 mode, non-buffered, normal EOI
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, LEGACY_8259_ICW4_8086);
//
// Restore saved slave IMR
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, SavedImr);
}
//
// If master vector base changed, re-initialize master PIC.
//
if (MasterBase != mMasterVectorBase) {
mMasterVectorBase = MasterBase;
//
// Save current master IMR for restoration after ICW sequence.
//
SavedImr = IoRead8 (LEGACY_8259_MASTER_PIC_DATA);
//
// ICW1: Write initialization command (0x11 = init + ICW4)
//
IoWrite8 (LEGACY_8259_MASTER_PIC_CMD, LEGACY_8259_ICW1_INIT_SEQ);
//
// ICW2: Write vector base address
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, mMasterVectorBase);
//
// ICW3: Cascade map (0x04 = slave on IRQ2)
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, LEGACY_8259_ICW3_MASTER_CASCADE);
//
// ICW4: 8086 mode, non-buffered, normal EOI
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, LEGACY_8259_ICW4_8086);
//
// Restore saved master IMR
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, SavedImr);
}
//
// Send non-specific EOI to both PICs to clear any pending ISR bits.
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
IoWrite8 (LEGACY_8259_MASTER_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
//
// Restore TPL.
//
gBS->RestoreTPL (OldTpl);
return EFI_SUCCESS;
}
/**
* @brief Read the current PIC state (cached values) -- Protocol fn [1].
*
* Returns the software-cached values of the PIC registers without
* touching hardware.
*
* @param[in] This Protocol instance pointer.
* @param[out] State Structure to receive:
* MasterImr = combined master+slave IMR shadow
* SlaveImr = combined master+slave ELCR shadow
* Elcr1 = master ELCR portion of shadow
* Elcr2 = slave ELCR portion of shadow
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if State is NULL.
*/
EFI_STATUS
EFIAPI
PicGetState (
IN EFI_LEGACY_8259_PROTOCOL *This,
OUT LEGACY_8259_STATE *State
)
{
if (State == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// Return cached register values from shadow variables.
//
State->MasterImr = mMasterImr;
State->SlaveImr = mElcrCombined;
State->Elcr1 = (UINT16)(mElcrCombined & 0xFF);
State->Elcr2 = mElcr2;
return EFI_SUCCESS;
}
/**
* @brief Write new state to the 8259 PIC -- Protocol fn [2].
*
* Programs the PIC hardware IMR and ELCR registers with the provided
* values. Behavior depends on the current operating mode:
*
* If mMode == LEGACY_8259_MODE_VIRTUAL_WIRE (1):
* Writes the provided MasterImr as master IMR (low byte to 0x21)
* and its high byte as slave IMR (to 0xA1).
* Uses SlaveImr for ELCR values.
*
* Otherwise (mode 0, legacy):
* Uses the shadow mMasterImr for IMR values.
* Uses SlaveImr for ELCR values.
*
* @param[in] This Protocol instance pointer.
* @param[in] State Structure with new IMR and ELCR values.
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if State is NULL.
*/
EFI_STATUS
EFIAPI
PicSetState (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN LEGACY_8259_STATE *State
)
{
UINT16 ImrToWrite;
UINT8 SlaveImrByte;
if (State == NULL) {
return EFI_INVALID_PARAMETER;
}
if (mMode == LEGACY_8259_MODE_VIRTUAL_WIRE) {
//
// Virtual wire mode: write the provided IMR values directly.
//
ImrToWrite = State->MasterImr;
SlaveImrByte = (UINT8)((State->MasterImr >> 8) & 0xFF);
} else {
//
// Legacy mode: write the shadow IMR and caller's ELCR.
//
ImrToWrite = mMasterImr;
SlaveImrByte = (UINT8)((mMasterImr >> 8) & 0xFF);
}
//
// Write IMR to hardware ports.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(ImrToWrite & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, SlaveImrByte);
//
// Write ELCR values from the state SlaveImr field.
//
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(State->SlaveImr & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((State->SlaveImr >> 8) & 0xFF));
//
// Update ELCR shadows.
//
mElcrCombined = (UINT16)(State->SlaveImr & 0xFFFF);
mElcr2 = (UINT16)((State->SlaveImr >> 8) & 0xFF);
return EFI_SUCCESS;
}
/**
* @brief Set the PIC operating mode -- Protocol fn [3].
*
* Switches between legacy PIC mode and virtual wire mode.
*
* LEGACY_8259_MODE_LEGACY (0):
* - Saves current IMR and ELCR from hardware
* - Configures for full legacy PIC operation
* - All IRQs routed through the 8259
*
* LEGACY_8259_MODE_VIRTUAL_WIRE (1):
* - Saves current IMR and ELCR from hardware
* - Clears IRQ0 (timer) mask bit in IMR
* - Only IRQ0 passes through PIC master; rest via I/O APIC
*
* @param[in] This Protocol instance pointer.
* @param[in] Mode LEGACY_8259_MODE_LEGACY (0) or
* LEGACY_8259_MODE_VIRTUAL_WIRE (1).
*
* @return EFI_SUCCESS on success.
* @return EFI_ALREADY_STARTED if mode is already set.
* @return EFI_INVALID_PARAMETER if Mode is not 0 or 1.
*/
EFI_STATUS
EFIAPI
PicSetMode (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 Mode
)
{
UINT16 SavedImr;
UINT16 SavedElcr;
if (Mode == mMode) {
return EFI_ALREADY_STARTED;
}
//
// Save current hardware state before switching.
//
SavedImr = (UINT16)(IoRead8 (LEGACY_8259_MASTER_PIC_DATA) |
(IoRead8 (LEGACY_8259_SLAVE_PIC_DATA) << 8));
SavedElcr = (UINT16)(IoRead8 (LEGACY_8259_ELCR1) |
(IoRead8 (LEGACY_8259_ELCR2) << 8));
if (Mode == LEGACY_8259_MODE_LEGACY) {
//
// Legacy mode: route all IRQs through PIC.
// Ensure timer (IRQ0) is unmasked.
//
mMasterImr = SavedImr & (UINT16)~(1U << 0);
mMode = LEGACY_8259_MODE_LEGACY;
//
// Write IMR to hardware.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(mMasterImr & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, (UINT8)((mMasterImr >> 8) & 0xFF));
//
// Write ELCR to hardware (preserving SavedElcr).
//
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(SavedElcr & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((SavedElcr >> 8) & 0xFF));
} else if (Mode == LEGACY_8259_MODE_VIRTUAL_WIRE) {
//
// Virtual wire mode: only IRQ0 via PIC, rest via I/O APIC.
// Preserve IRQ0 unmasked (clear bit 0).
//
mMasterImr = SavedImr & (UINT16)~(1U << 0);
mMode = LEGACY_8259_MODE_VIRTUAL_WIRE;
//
// Write IMR to hardware.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(mMasterImr & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, (UINT8)((mMasterImr >> 8) & 0xFF));
//
// Write ELCR to hardware.
//
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(SavedElcr & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((SavedElcr >> 8) & 0xFF));
//
// Update ELCR shadows.
//
mElcrCombined = SavedElcr;
mElcr2 = (UINT16)((SavedElcr >> 8) & 0xFF);
} else {
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
/**
* @brief Get the interrupt vector for a given IRQ -- Protocol fn [4].
*
* Converts IRQ number (0-15) to the hardware interrupt vector by
* adding the appropriate PIC vector base.
*
* For master IRQs (0-7): vector = IRQ + mMasterVectorBase.
* For slave IRQs (8-15): vector = IRQ + (mSlaveVectorBase - 8).
*
* @param[in] This Protocol instance pointer.
* @param[in] Irq IRQ number (0-15).
* @param[out] Vector Pointer to receive the vector number.
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if Irq > 15 or Vector is NULL.
*/
EFI_STATUS
EFIAPI
PicGetVector (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 Irq,
OUT UINT8 *Vector
)
{
if (Irq > 15) {
return EFI_INVALID_PARAMETER;
}
if (Vector == NULL) {
return EFI_INVALID_PARAMETER;
}
if (Irq >= 8) {
//
// Slave PIC IRQs: subtract cascade offset.
// IRQ8 -> mSlaveVectorBase (0x70 = 112)
//
*Vector = (UINT8)(Irq + (mSlaveVectorBase - 8));
} else {
//
// Master PIC IRQs: direct offset from master base.
// IRQ0 -> mMasterVectorBase (0x58 = 88)
//
*Vector = (UINT8)(Irq + mMasterVectorBase);
}
return EFI_SUCCESS;
}
/**
* @brief Enable or disable a specific IRQ -- Protocol fn [5].
*
* Updates the IMR bit for the specified IRQ and manages the ELCR:
* Enable = TRUE: clears IMR bit (unmask), sets ELCR bit (level).
* Enable = FALSE: sets IMR bit (mask), clears ELCR bit (edge).
*
* Writes updated values to PIC hardware immediately.
*
* @param[in] This Protocol instance pointer.
* @param[in] Irq IRQ number (0-15).
* @param[in] Enable TRUE = unmask + level, FALSE = mask + edge.
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if Irq > 15.
*/
EFI_STATUS
EFIAPI
PicEnableIrq (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 Irq,
IN BOOLEAN Enable
)
{
UINT16 NewImr;
UINT16 NewElcr;
if (Irq > 15) {
return EFI_INVALID_PARAMETER;
}
if (Enable) {
//
// Unmask: clear IMR bit, set ELCR bit (level-triggered).
//
mMasterImr &= (UINT16)~(1U << Irq);
mElcrCombined |= (UINT16)(1U << Irq);
} else {
//
// Mask: set IMR bit, clear ELCR bit (edge-triggered).
//
mMasterImr |= (UINT16)(1U << Irq);
mElcrCombined &= (UINT16)~(1U << Irq);
}
NewImr = mMasterImr;
NewElcr = mElcrCombined;
//
// Write to hardware.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(NewImr & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, (UINT8)((NewImr >> 8) & 0xFF));
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(NewElcr & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((NewElcr >> 8) & 0xFF));
//
// Update slave ELCR shadow.
//
mElcr2 = (UINT16)((NewElcr >> 8) & 0xFF);
return EFI_SUCCESS;
}
/**
* @brief Disable a specific IRQ in both IMR and ELCR -- Protocol fn [6].
*
* Stronger than masking: sets the IMR bit (masking) AND clears the
* ELCR bit (forcing edge-triggered), providing maximum isolation.
*
* @param[in] This Protocol instance pointer.
* @param[in] Irq IRQ number (0-15).
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if Irq > 15.
*/
EFI_STATUS
EFIAPI
PicDisableIrq (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 Irq
)
{
if (Irq > 15) {
return EFI_INVALID_PARAMETER;
}
//
// Mask in IMR (set bit).
//
mMasterImr |= (UINT16)(1U << Irq);
//
// Clear ELCR bit (force edge-triggered).
//
mElcrCombined &= (UINT16)~(1U << Irq);
//
// Write to hardware.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(mMasterImr & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, (UINT8)((mMasterImr >> 8) & 0xFF));
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(mElcrCombined & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((mElcrCombined >> 8) & 0xFF));
//
* Update slave ELCR shadow.
//
mElcr2 = (UINT16)((mElcrCombined >> 8) & 0xFF);
return EFI_SUCCESS;
}
/**
* @brief Issue End-Of-Interrupt (EOI) to the appropriate PIC(s).
*
* For slave IRQs (>= 8), EOI first goes to slave PIC (0xA0) then
* master PIC (0x20) to deassert the cascade line.
* For master IRQs (< 8), only master PIC receives EOI.
*
* @param[in] This Protocol instance pointer.
* @param[in] Irq IRQ number (0-15) being completed.
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if Irq > 15.
*/
EFI_STATUS
EFIAPI
PicEndOfInterrupt (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN UINT8 Irq
)
{
if (Irq > 15) {
return EFI_INVALID_PARAMETER;
}
//
// Slave IRQ requires EOI to slave PIC first (cascade on IRQ2).
//
if (Irq >= 8) {
IoWrite8 (LEGACY_8259_SLAVE_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
}
//
// Always send EOI to master PIC.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
return EFI_SUCCESS;
}
/**
* @brief Read the PCAT compatibility flag -- Protocol fn [7].
*
* Uses gBS->HandleProtocol() with the provided EFI handle to open
* the PCAT compatibility protocol interface, then reads 1 byte at
* offset 0x3C (60) of the protocol data, which is the PCAT_COMPAT
* flag indicating whether dual 8259 PICs are present.
*
* Bit 0 (PCAT_COMPAT):
* 1 = Dual 8259 PICs present (legacy PC/AT compatible).
* 0 = APIC only; 8259 PICs may not exist.
*
* @param[in] This Protocol instance pointer.
* @param[in] Handle Handle supporting the PCAT compatibility protocol.
* @param[out] PcatFlags Pointer to receive the PCAT_COMPAT flag byte.
*
* @return EFI_SUCCESS on success.
* @return EFI_INVALID_PARAMETER if Handle or PcatFlags is NULL.
* @return EFI_UNSUPPORTED if Handle does not support the protocol.
*/
EFI_STATUS
EFIAPI
PicGetPcatCompatibilityFlag (
IN EFI_LEGACY_8259_PROTOCOL *This,
IN EFI_HANDLE Handle,
OUT UINT8 *PcatFlags
)
{
EFI_STATUS Status;
VOID *Interface;
UINT8 Flags;
if (Handle == NULL || PcatFlags == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// Open the PCAT compatibility protocol on the given handle.
//
Status = gBS->HandleProtocol (
Handle,
&gEfiPcatCompatibilityProtocolGuid,
&Interface
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Read byte at offset 0x3C (60) of the protocol interface data.
// This is the PCAT_COMPAT flag in the MADT (or equivalent table).
//
*PcatFlags = *(UINT8 *)((UINT8 *)Interface + 0x3C);
return EFI_SUCCESS;
}
/*=============================================================================
* Internal Helper Functions
*============================================================================*/
/**
* @brief Read current IMR and ELCR from PIC hardware.
*
* Reads the actual IMR and ELCR register values from hardware ports.
* Used internally during mode transitions to capture hardware state.
*
* @param[out] MasterPmr Optional: receives combined IMR = low(0x21) | high(0xA1<<8).
* @param[out] ElcrValue Optional: receives combined ELCR = low(0x4D0) | high(0x4D1<<8).
*/
STATIC
VOID
PicReadImr (
OUT UINT16 *MasterPmr OPTIONAL,
OUT UINT16 *ElcrValue OPTIONAL
)
{
UINT8 LowByte;
UINT8 HighByte;
if (MasterPmr != NULL) {
LowByte = IoRead8 (LEGACY_8259_MASTER_PIC_DATA);
HighByte = IoRead8 (LEGACY_8259_SLAVE_PIC_DATA);
*MasterPmr = (UINT16)(LowByte | ((UINT16)HighByte << 8));
}
if (ElcrValue != NULL) {
LowByte = IoRead8 (LEGACY_8259_ELCR1);
HighByte = IoRead8 (LEGACY_8259_ELCR2);
*ElcrValue = (UINT16)(LowByte | ((UINT16)HighByte << 8));
}
}
/**
* @brief Locate the debug ASSERT protocol interface.
*
* Locates a platform-specific debug protocol via gBS->LocateProtocol()
* using gEfiDebugOutputProtocolGuid. Caches the interface in mDebugInterface.
*
* To prevent re-entrancy issues, raises TPL to TPL_HIGH_LEVEL to verify
* we are not in interrupt context (TPL > TPL_NOTIFY) before calling
* LocateProtocol.
*
* The returned interface provides assertion output at offset +8:
* Interface[1](FileName, LineNumber, ExpressionText).
*
* @return Pointer to debug protocol interface, or NULL if unavailable.
*/
STATIC
VOID *
GetDebugInterface (
VOID
)
{
EFI_TPL OldTpl;
if (mDebugInterface != NULL) {
return mDebugInterface;
}
//
// Raise to TPL_HIGH_LEVEL, then restore to check current TPL.
// If OldTpl > TPL_NOTIFY, we are in an interrupt context and
// LocateProtocol cannot be safely called.
//
OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
gBS->RestoreTPL (OldTpl);
if (OldTpl <= TPL_NOTIFY) {
//
* Safe to locate the debug protocol.
//
gBS->LocateProtocol (
&gEfiDebugOutputProtocolGuid,
NULL,
&mDebugInterface
);
}
return mDebugInterface;
}
/**
* @brief ASSERT handler -- reports assertion messages.
*
* Called by the ASSERT() macro. Uses the debug protocol interface
* to report the assertion: Interface[1](FileName, Line, AssertText).
*
* If the debug interface is not available, the assertion is silently
* dropped (as per UEFI convention).
*
* @param[in] FileName Source file name string (ASCII).
* @param[in] Line Line number of the assertion.
* @param[in] Text Assertion expression text (ASCII).
*/
VOID
EFIAPI
DebugAssert (
IN CHAR8 *FileName,
IN UINTN Line,
IN CHAR8 *Text
)
{
VOID *Interface;
Interface = GetDebugInterface ();
if (Interface != NULL) {
//
// Call the debug protocol's DebugAssert function at offset +8.
//
((VOID (EFIAPI *)(CHAR8 *, UINTN, CHAR8 *))
((UINTN *)Interface)[1]) (FileName, Line, Text);
}
}
/**
* @brief Read an unaligned 64-bit value.
*
* Performs an unaligned memory read of a 64-bit value by dereferencing
* the buffer as a 64-bit pointer. Used by the GUID comparison routine.
*
* @param[in] Buffer Pointer to the potentially unaligned memory.
*
* @return The 64-bit value at Buffer.
*/
STATIC
UINT64
ReadUnaligned64 (
IN VOID *Buffer
)
{
if (Buffer == NULL) {
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
192,
"Buffer != ((void *) 0)"
);
}
return *(UINT64 *)Buffer;
}
/**
* @brief Compare two GUIDs for equality.
*
* Compares two EFI_GUID values using 64-bit unaligned reads for
* efficiency. Splits each GUID into two 64-bit halves.
*
* @param[in] Guid1 First GUID to compare.
* @param[in] Guid2 Second GUID to compare.
*
* @return TRUE if GUIDs are equal, FALSE otherwise.
*/
STATIC
BOOLEAN
CompareGuid (
IN EFI_GUID *Guid1,
IN EFI_GUID *Guid2
)
{
UINT64 Guid1First;
UINT64 Guid2First;
UINT64 Guid1Second;
UINT64 Guid2Second;
Guid1First = ReadUnaligned64 (Guid1);
Guid2First = ReadUnaligned64 (Guid2);
Guid1Second = ReadUnaligned64 ((EFI_GUID *)((UINT8 *)Guid1 + 8));
Guid2Second = ReadUnaligned64 ((EFI_GUID *)((UINT8 *)Guid2 + 8));
return (Guid1First == Guid2First) && (Guid1Second == Guid2Second);
}
/**
* @brief Locate the HOB list from the system configuration table.
*
* Iterates through gST->ConfigurationTable entries to find the entry
* whose VendorGuid matches gEfiHobListGuid. Caches the HOB list pointer
* in mHobList on success.
*
* @return Pointer to the HOB list, or NULL if not found.
*/
STATIC
VOID *
GetHobList (
VOID
)
{
UINTN Index;
if (mHobList != NULL) {
return mHobList;
}
for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
if (CompareGuid (
&gEfiHobListGuid,
(EFI_GUID *)&gST->ConfigurationTable[Index].VendorGuid
)) {
mHobList = gST->ConfigurationTable[Index].VendorTable;
return mHobList;
}
}
return mHobList;
}
/*=============================================================================
* Driver Entry Point
*============================================================================*/
/**
* @brief Module entry point (_ModuleEntryPoint at 0x384).
*
* Called by the DXE Foundation when the driver is loaded. Validates and
* caches UEFI service table pointers, then calls the main driver
* initialization routine.
*
* @param[in] ImageHandle Handle for the UEFI image.
* @param[in] SystemTable Pointer to the UEFI system table.
*
* @return EFI_STATUS from Legacy8259DriverEntry().
*/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Validate and cache UEFI service table pointers.
//
if (ImageHandle == NULL) {
DebugAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
51,
"gImageHandle != ((void *) 0)"
);
}
if (SystemTable == NULL) {
DebugAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
57,
"gST != ((void *) 0)"
);
}
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
if (gBS == NULL) {
DebugAssert (
"e:\\hs\\MdePkg\\Library\\UefiBootServicesTableLib\\UefiBootServicesTableLib.c",
63,
"gBS != ((void *) 0)"
);
}
if (gRT == NULL) {
DebugAssert (
"e:\\hs\\MdePkg\\Library\\UefiRuntimeServicesTableLib\\UefiRuntimeServicesTableLib.c",
47,
"gRT != ((void *) 0)"
);
}
//
// Initialize HOB list for DXE library.
//
GetHobList ();
//
* Perform full driver initialization.
//
return Legacy8259DriverEntry ();
}
/**
* @brief Main driver initialization (sub_430).
*
* Performs the full 8259 PIC initialization sequence:
*
* 1. Set initial IMR programming value (0x0EB8) enabling timer,
* keyboard, and cascade; masking COM, floppy, LPT, RTC.
*
* 2. Send EOI for all 16 IRQs to clear any stale In-Service bits.
*
* 3. Initialize protocol dispatch table function pointers.
*
* 4. Program both PICs with ICW1-ICW4 via PicInitialize:
* Master base 0x58, Slave base 0x70.
*
* 5. Write initial IMR and ELCR to hardware ports.
*
* 6. Install EFI_LEGACY_8259_PROTOCOL via gBS->InstallProtocolInterface().
*
* @return EFI_SUCCESS on success, or error from InstallProtocolInterface.
*/
EFI_STATUS
Legacy8259DriverEntry (
VOID
)
{
UINTN Index;
//
// Step 1: Set IMR shadow to default programming value (0x0EB8).
// 0x0EB8 bit assignments:
// Bit 0 = 0 (IRQ0 - Timer: unmasked)
// Bit 1 = 0 (IRQ1 - Keyboard: unmasked)
// Bit 2 = 0 (IRQ2 - Slave cascade: unmasked)
// Bit 3 = 1 (IRQ3 - COM2: masked)
// Bit 4 = 1 (IRQ4 - COM1: masked)
// Bit 5 = 0 (IRQ5 - LPT2 or free: unmasked)
// Bit 6 = 1 (IRQ6 - Floppy: masked)
// Bit 7 = 1 (IRQ7 - LPT1: masked)
// Bit 8 = 1 (IRQ8 - RTC: masked on slave)
//
mCachedMasterImr = 0x0EB8;
mElcrCombined = 0;
//
// Step 2: Clear any stale ISR bits by sending EOI to both PICs
// for all possible IRQs (0-15).
//
for (Index = 0; Index <= 15; Index++) {
if (Index >= 8) {
//
// Slave IRQ: EOI to slave PIC first.
//
IoWrite8 (LEGACY_8259_SLAVE_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
}
//
// Send EOI to master PIC.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_CMD, LEGACY_8259_OCW2_NON_SPECIFIC_EOI);
}
//
// Step 3: Initialize protocol dispatch table.
//
m8259Interface.InitPic = PicInitialize;
m8259Interface.GetState = PicGetState;
m8259Interface.SetState = PicSetState;
m8259Interface.SetMode = PicSetMode;
m8259Interface.GetVector = PicGetVector;
m8259Interface.EnableIrq = PicEnableIrq;
m8259Interface.DisableIrq = PicDisableIrq;
m8259Interface.EndOfInterrupt = PicEndOfInterrupt;
m8259Interface.GetPcatCompatibilityFlag = PicGetPcatCompatibilityFlag;
//
// Step 4: Program PICs with ICW1-ICW4.
// Master vector base: 0x58 (IRQ0 -> INT 88).
// Slave vector base: 0x70 (IRQ8 -> INT 112).
//
PicInitialize (
&m8259Interface,
LEGACY_8259_MASTER_VECTOR_BASE,
LEGACY_8259_SLAVE_VECTOR_BASE
);
//
// Step 5: Write initial IMR and ELCR values to hardware.
//
IoWrite8 (LEGACY_8259_MASTER_PIC_DATA, (UINT8)(mCachedMasterImr & 0xFF));
IoWrite8 (LEGACY_8259_SLAVE_PIC_DATA, (UINT8)((mCachedMasterImr >> 8) & 0xFF));
IoWrite8 (LEGACY_8259_ELCR1, (UINT8)(mElcrCombined & 0xFF));
IoWrite8 (LEGACY_8259_ELCR2, (UINT8)((mElcrCombined >> 8) & 0xFF));
//
// Step 6: Install the EFI_LEGACY_8259_PROTOCOL.
//
return gBS->InstallProtocolInterface (
&mHandle,
&gEfiLegacy8259ProtocolGuid,
EFI_NATIVE_INTERFACE,
&m8259Interface
);
}