Files
exectos/xtoskrnl/hl/x86/pic.cc
Aiken Harris b2c8fa3e62
All checks were successful
Builds / ExectOS (amd64, debug) (push) Successful in 29s
Builds / ExectOS (amd64, release) (push) Successful in 27s
Builds / ExectOS (i686, debug) (push) Successful in 30s
Builds / ExectOS (i686, release) (push) Successful in 28s
Use new C++ API
2025-09-19 10:49:07 +02:00

416 lines
10 KiB
C++

/**
* PROJECT: ExectOS
* COPYRIGHT: See COPYING.md in the top level directory
* FILE: xtoskrnl/hl/x86/pic.cc
* DESCRIPTION: Programmable Interrupt Controller (PIC) for x86 (i686/AMD64) support
* DEVELOPERS: Rafal Kupiec <belliash@codingworkshop.eu.org>
*/
#include <xtos.hh>
/**
* Checks whether the x2APIC extension is supported by the processor.
*
* @return This routine returns TRUE if x2APIC is supported, or FALSE otherwise.
*
* @since XT 1.0
*
* @todo Check if bits 0 and 1 of DMAR ACPI table flags are set after implementing ACPI support.
* Intel VT-d spec says x2apic should not be enabled if they are.
*/
XTAPI
BOOLEAN
HL::Pic::CheckX2ApicSupport(VOID)
{
CPUID_REGISTERS CpuRegisters;
/* Prepare CPUID registers */
CpuRegisters.Leaf = CPUID_GET_STANDARD1_FEATURES;
CpuRegisters.SubLeaf = 0;
CpuRegisters.Eax = 0;
CpuRegisters.Ebx = 0;
CpuRegisters.Ecx = 0;
CpuRegisters.Edx = 0;
/* Get CPUID */
AR::CpuFunc::CpuId(&CpuRegisters);
/* Check x2APIC status from the CPUID results */
if(!(CpuRegisters.Ecx & CPUID_FEATURES_ECX_X2APIC))
{
/* x2APIC is not supported */
return FALSE;
}
/* x2APIC is supported */
return TRUE;
}
/**
* Clears all errors on the APIC.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTAPI
VOID
HL::Pic::ClearApicErrors(VOID)
{
/* Clear APIC errors */
WriteApicRegister(APIC_ESR, 0);
}
/**
* Gets the local APIC ID of the current processor.
*
* @return This routine returns the current processor's local APIC ID.
*
* @since XT 1.0
*/
XTAPI
ULONG
HL::Pic::GetCpuApicId(VOID)
{
ULONG ApicId;
/* Read APIC ID register */
ApicId = ReadApicRegister(APIC_ID);
/* Return logical CPU ID depending on current APIC mode */
return (ApicMode == APIC_MODE_COMPAT) ? ((ApicId & 0xFFFFFFFF) >> APIC_XAPIC_LDR_SHIFT) : ApicId;
}
/**
* Allows an APIC spurious interrupts to end up.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTCDECL
VOID
HL::Pic::HandleApicSpuriousService(VOID)
{
}
/**
* Allows a PIC spurious interrupts to end up.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTCDECL
VOID
HL::Pic::HandlePicSpuriousService(VOID)
{
}
/**
* Initializes the APIC interrupt controller.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTAPI
VOID
HL::Pic::InitializeApic(VOID)
{
APIC_SPURIOUS_REGISTER SpuriousRegister;
APIC_BASE_REGISTER BaseRegister;
APIC_LVT_REGISTER LvtRegister;
ULONG CpuNumber;
/* Determine APIC mode (xAPIC compatibility or x2APIC) */
if(CheckX2ApicSupport())
{
/* Enable x2APIC mode */
ApicMode = APIC_MODE_X2APIC;
}
else
{
/* Fall back to xAPIC compatibility mode */
ApicMode = APIC_MODE_COMPAT;
}
/* Get current processor number */
CpuNumber = KE::Processor::GetCurrentProcessorNumber();
/* Enable the APIC */
BaseRegister.LongLong = AR::CpuFunc::ReadModelSpecificRegister(APIC_LAPIC_MSR_BASE);
BaseRegister.Enable = 1;
BaseRegister.ExtendedMode = (ApicMode == APIC_MODE_X2APIC);
BaseRegister.BootStrapProcessor = (CpuNumber == 0) ? 1 : 0;
AR::CpuFunc::WriteModelSpecificRegister(APIC_LAPIC_MSR_BASE, BaseRegister.LongLong);
/* Mask all interrupts by raising Task Priority Register (TPR) */
WriteApicRegister(APIC_TPR, 0xFF);
/* Perform initialization specific to xAPIC compatibility mode */
if(ApicMode == APIC_MODE_COMPAT)
{
/* Use Flat Model for destination format (not supported in x2APIC) */
WriteApicRegister(APIC_DFR, APIC_DF_FLAT);
/* Set the logical APIC ID for this processor (read-only in x2APIC) */
WriteApicRegister(APIC_LDR, (1UL << CpuNumber) << 24);
}
/* Configure the spurious interrupt vector */
SpuriousRegister.Long = ReadApicRegister(APIC_SIVR);
SpuriousRegister.Vector = APIC_VECTOR_SPURIOUS;
SpuriousRegister.SoftwareEnable = 1;
SpuriousRegister.CoreChecking = 0;
WriteApicRegister(APIC_SIVR, SpuriousRegister.Long);
/* Setup the LVT Error entry to deliver APIC errors on a dedicated vector */
WriteApicRegister(APIC_ERRLVTR, APIC_VECTOR_ERROR);
/* Program the APIC timer for periodic mode */
LvtRegister.Long = 0;
LvtRegister.Mask = 1;
LvtRegister.DeliveryMode = APIC_DM_FIXED;
LvtRegister.TimerMode = 1;
LvtRegister.TriggerMode = APIC_TGM_EDGE;
LvtRegister.Vector = APIC_VECTOR_PROFILE;
WriteApicRegister(APIC_TMRLVTR, LvtRegister.Long);
/* Configure the performance counter overflow */
LvtRegister.Long = 0;
LvtRegister.Mask = 0;
LvtRegister.DeliveryMode = APIC_DM_FIXED;
LvtRegister.TimerMode = 0;
LvtRegister.TriggerMode = APIC_TGM_EDGE;
LvtRegister.Vector = APIC_VECTOR_PERF;
WriteApicRegister(APIC_PCLVTR, LvtRegister.Long);
/* Configure the LINT0 pin */
LvtRegister.Long = 0;
LvtRegister.Mask = 1;
LvtRegister.DeliveryMode = APIC_DM_FIXED;
LvtRegister.TimerMode = 0;
LvtRegister.TriggerMode = APIC_TGM_EDGE;
LvtRegister.Vector = APIC_VECTOR_SPURIOUS;
WriteApicRegister(APIC_LINT0, LvtRegister.Long);
/* Configure the LINT1 pin */
LvtRegister.Long = 0;
LvtRegister.Mask = 0;
LvtRegister.DeliveryMode = APIC_DM_NMI;
LvtRegister.TimerMode = 0;
LvtRegister.TriggerMode = APIC_TGM_EDGE;
LvtRegister.Vector = APIC_VECTOR_NMI;
WriteApicRegister(APIC_LINT1, LvtRegister.Long);
/* Register interrupt handlers */
KE::Irq::SetInterruptHandler(APIC_VECTOR_SPURIOUS, (PVOID)HandleApicSpuriousService);
KE::Irq::SetInterruptHandler(PIC1_VECTOR_SPURIOUS, (PVOID)HandlePicSpuriousService);
/* Clear any pre-existing errors */
WriteApicRegister(APIC_ESR, 0);
/* Re-enable all interrupts by lowering the Task Priority Register */
WriteApicRegister(APIC_TPR, 0x00);
}
/**
* Initializes the legacy PIC interrupt controller.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTAPI
VOID
HL::Pic::InitializeLegacyPic(VOID)
{
PIC_I8259_ICW1 Icw1;
PIC_I8259_ICW2 Icw2;
PIC_I8259_ICW3 Icw3;
PIC_I8259_ICW4 Icw4;
/* Initialize ICW1 for PIC1 port */
Icw1.Init = TRUE;
Icw1.InterruptMode = EdgeTriggered;
Icw1.InterruptVectorAddress = 0;
Icw1.Interval = Interval8;
Icw1.NeedIcw4 = TRUE;
Icw1.OperatingMode = Cascade;
HL::IoPort::WritePort8(PIC1_CONTROL_PORT, Icw1.Bits);
/* Initialize ICW2 for PIC1 port */
Icw2.Bits = 0x00;
HL::IoPort::WritePort8(PIC1_DATA_PORT, Icw2.Bits);
/* Initialize ICW3 for PIC1 port */
Icw3.Bits = 0;
Icw3.SlaveIrq2 = TRUE;
HL::IoPort::WritePort8(PIC1_DATA_PORT, Icw3.Bits);
/* Initialize ICW4 for PIC1 port */
Icw4.BufferedMode = NonBuffered;
Icw4.EoiMode = NormalEoi;
Icw4.Reserved = 0;
Icw4.SpecialFullyNestedMode = FALSE;
Icw4.SystemMode = New8086Mode;
HL::IoPort::WritePort8(PIC1_DATA_PORT, Icw4.Bits);
/* Mask all interrupts on PIC1 port */
HL::IoPort::WritePort8(PIC1_DATA_PORT, 0xFF);
/* Initialize ICW1 for PIC2 port */
Icw1.Init = TRUE;
Icw1.InterruptMode = EdgeTriggered;
Icw1.InterruptVectorAddress = 0;
Icw1.Interval = Interval8;
Icw1.NeedIcw4 = TRUE;
Icw1.OperatingMode = Cascade;
HL::IoPort::WritePort8(PIC2_CONTROL_PORT, Icw1.Bits);
/* Initialize ICW2 for PIC2 port */
Icw2.Bits = 0x08;
HL::IoPort::WritePort8(PIC2_DATA_PORT, Icw2.Bits);
/* Initialize ICW3 for PIC2 port */
Icw3.Bits = 0;
Icw3.SlaveId = 2;
HL::IoPort::WritePort8(PIC2_DATA_PORT, Icw3.Bits);
/* Initialize ICW4 for PIC2 port */
Icw4.BufferedMode = NonBuffered;
Icw4.EoiMode = NormalEoi;
Icw4.Reserved = 0;
Icw4.SpecialFullyNestedMode = FALSE;
Icw4.SystemMode = New8086Mode;
HL::IoPort::WritePort8(PIC2_DATA_PORT, Icw4.Bits);
/* Mask all interrupts on PIC2 port */
HL::IoPort::WritePort8(PIC2_DATA_PORT, 0xFF);
}
/**
* Initializes the (A)PIC interrupt controller.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*
* @todo Initialize APIC only when supported, otherwise fall back to legacy PIC.
*/
XTAPI
VOID
HL::Pic::InitializePic(VOID)
{
/* Initialize APIC */
InitializeApic();
/* Initialize legacy PIC */
InitializeLegacyPic();
}
/**
* Reads from the APIC register.
*
* @param Register
* Supplies the APIC register to read from.
*
* @return This routine returns the value read from the APIC register.
*
* @since XT 1.0
*/
XTFASTCALL
ULONGLONG
HL::Pic::ReadApicRegister(IN APIC_REGISTER Register)
{
if(ApicMode == APIC_MODE_X2APIC)
{
/* Read from x2APIC MSR */
return AR::CpuFunc::ReadModelSpecificRegister((ULONG)(APIC_X2APIC_MSR_BASE + Register));
}
else
{
/* Read from xAPIC */
return IoRegister::ReadRegister32((PULONG)(APIC_BASE + (Register << 4)));
}
}
/**
* Signals to the APIC that handling an interrupt is complete.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTAPI
VOID
HL::Pic::SendEoi(VOID)
{
/* Send APIC EOI */
WriteApicRegister(APIC_EOI, 0);
}
/**
* Sends an IPI (Inter-Processor Interrupt) to the specified CPU.
*
* @param ApicId
* Supplies a CPU APIC ID to send an IPI to.
*
* @param Vector
* Supplies the IPI vector to send.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTAPI
VOID
HL::Pic::SendIpi(ULONG ApicId,
ULONG Vector)
{
/* Check current APIC mode */
if(ApicMode == APIC_MODE_X2APIC)
{
/* Send IPI using x2APIC mode */
WriteApicRegister(APIC_ICR0, ((ULONGLONG)ApicId << 32) | Vector);
}
else
{
/* Send IPI using xAPIC compatibility mode */
WriteApicRegister(APIC_ICR1, ApicId << 24);
WriteApicRegister(APIC_ICR0, Vector);
}
}
/**
* Writes to the APIC register.
*
* @param Register
* Supplies the APIC register to write to.
*
* @param Value
* Supplies the value to write to the APIC register.
*
* @return This routine does not return any value.
*
* @since XT 1.0
*/
XTFASTCALL
VOID
HL::Pic::WriteApicRegister(IN APIC_REGISTER Register,
IN ULONGLONG Value)
{
if(ApicMode == APIC_MODE_X2APIC)
{
/* Write to x2APIC MSR */
AR::CpuFunc::WriteModelSpecificRegister((ULONG)(APIC_X2APIC_MSR_BASE + Register), Value);
}
else
{
/* Write to xAPIC */
IoRegister::WriteRegister32((PULONG)(APIC_BASE + (Register << 4)), Value);
}
}