/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/hl/x86/pic.c
 * DESCRIPTION:     Programmable Interrupt Controller (PIC) for x86 (i686/AMD64) support
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtos.h>


/**
 * Clears all errors on the APIC.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
HlClearApicErrors(VOID)
{
    /* Clear APIC errors */
    HlWriteApicRegister(APIC_ESR, 0);
}

/**
 * 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
HlReadApicRegister(IN APIC_REGISTER Register)
{
    if(HlpApicMode == APIC_MODE_X2APIC)
    {
        /* Read from x2APIC MSR */
        return ArReadModelSpecificRegister((ULONG)(APIC_X2APIC_MSR_BASE + Register));
    }
    else
    {
        /* Read from xAPIC */
        return RtlReadRegisterLong((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
HlSendEoi(VOID)
{
    /* Send APIC EOI */
    HlWriteApicRegister(APIC_EOI, 0);
}

/**
 * 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
HlWriteApicRegister(IN APIC_REGISTER Register,
                    IN ULONGLONG Value)
{
    if(HlpApicMode == APIC_MODE_X2APIC)
    {
        /* Write to x2APIC MSR */
        ArWriteModelSpecificRegister((ULONG)(APIC_X2APIC_MSR_BASE + Register), Value);
    }
    else
    {
        /* Write to xAPIC */
        RtlWriteRegisterLong((PULONG)(APIC_BASE + (Register << 4)), Value);
    }
}

/**
 * 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
HlpCheckX2ApicSupport(VOID)
{
    CPUID_REGISTERS CpuRegisters;

    /* Prepare CPUID registers */
    CpuRegisters.Leaf = CPUID_GET_CPU_FEATURES;
    CpuRegisters.SubLeaf = 0;
    CpuRegisters.Eax = 0;
    CpuRegisters.Ebx = 0;
    CpuRegisters.Ecx = 0;
    CpuRegisters.Edx = 0;

    /* Get CPUID */
    ArCpuId(&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;
}

/**
 * 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
HlpGetCpuApicId(VOID)
{
    ULONG ApicId;

    /* Read APIC ID register */
    ApicId = HlReadApicRegister(APIC_ID);

    /* Return logical CPU ID depending on current APIC mode */
    return (HlpApicMode == 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
HlpHandleApicSpuriousService(VOID)
{
}

/**
 * Allows a PIC spurious interrupts to end up.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTCDECL
VOID
HlpHandlePicSpuriousService(VOID)
{
}

/**
 * Initializes the APIC interrupt controller.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
HlpInitializeApic(VOID)
{
    APIC_SPURIOUS_REGISTER SpuriousRegister;
    APIC_COMMAND_REGISTER CommandRegister;
    APIC_BASE_REGISTER BaseRegister;
    APIC_LVT_REGISTER LvtRegister;
    ULONG CpuNumber;

    /* Check if this is an x2APIC compatible machine */
    if(HlpCheckX2ApicSupport())
    {
        /* Enable x2APIC */
        HlpApicMode = APIC_MODE_X2APIC;
    }
    else
    {
        /* Use xAPIC compatibility mode */
        HlpApicMode = APIC_MODE_COMPAT;
    }

    /* Get processor number */
    CpuNumber = KeGetCurrentProcessorNumber();

    /* Enable the APIC */
    BaseRegister.LongLong = ArReadModelSpecificRegister(APIC_LAPIC_MSR_BASE);
    BaseRegister.Enable = 1;
    BaseRegister.ExtendedMode = (HlpApicMode == APIC_MODE_X2APIC);
    BaseRegister.BootStrapProcessor = (CpuNumber == 0) ? 1 : 0;
    ArWriteModelSpecificRegister(APIC_LAPIC_MSR_BASE, BaseRegister.LongLong);

    /* Raise APIC task priority (TPR) to mask off all interrupts */
    HlWriteApicRegister(APIC_TPR, 0xFF);

    /* xAPIC compatibility mode specific initialization */
    if(HlpApicMode == APIC_MODE_COMPAT)
    {
        /* Initialize Destination Format Register with flat model (not supported in x2APIC mode) */
        HlWriteApicRegister(APIC_DFR, APIC_DF_FLAT);

        /* Set the logical APIC ID (read-only in x2APIC mode) */
        HlWriteApicRegister(APIC_LDR, (1UL << CpuNumber) << 24);
    }

    /* Set the spurious interrupt vector */
    SpuriousRegister.Long = HlReadApicRegister(APIC_SIVR);
    SpuriousRegister.Vector = APIC_VECTOR_SPURIOUS;
    SpuriousRegister.SoftwareEnable = 1;
    SpuriousRegister.CoreChecking = 0;
    HlWriteApicRegister(APIC_SIVR, SpuriousRegister.Long);

    /* Mask LVTR_ERROR first, to prevent local APIC error */
    HlWriteApicRegister(APIC_ERRLVTR, APIC_VECTOR_ERROR);

    /* Mask TMRLVTR */
    LvtRegister.Long = 0;
    LvtRegister.Mask = 1;
    LvtRegister.MessageType = APIC_DM_FIXED;
    LvtRegister.TimerMode = 1;
    LvtRegister.TriggerMode = APIC_TGM_EDGE;
    LvtRegister.Vector = APIC_VECTOR_PROFILE;
    HlWriteApicRegister(APIC_TMRLVTR, LvtRegister.Long);

    /* Mask PCLVTR */
    LvtRegister.Long = 0;
    LvtRegister.Mask = 0;
    LvtRegister.MessageType = APIC_DM_FIXED;
    LvtRegister.TimerMode = 0;
    LvtRegister.TriggerMode = APIC_TGM_EDGE;
    LvtRegister.Vector = APIC_VECTOR_PERF;
    HlWriteApicRegister(APIC_PCLVTR, LvtRegister.Long);

    /* Mask LINT0 */
    LvtRegister.Long = 0;
    LvtRegister.Mask = 1;
    LvtRegister.MessageType = APIC_DM_FIXED;
    LvtRegister.TimerMode = 0;
    LvtRegister.TriggerMode = APIC_TGM_EDGE;
    LvtRegister.Vector = APIC_VECTOR_SPURIOUS;
    HlWriteApicRegister(APIC_LINT0, LvtRegister.Long);

    /* Mask LINT1 */
    LvtRegister.Long = 0;
    LvtRegister.Mask = 0;
    LvtRegister.MessageType = APIC_DM_NMI;
    LvtRegister.TimerMode = 0;
    LvtRegister.TriggerMode = APIC_TGM_EDGE;
    LvtRegister.Vector = APIC_VECTOR_NMI;
    HlWriteApicRegister(APIC_LINT1, LvtRegister.Long);

    /* Mask ICR0 */
    CommandRegister.Long0 = 0;
    CommandRegister.DestinationShortHand = APIC_DSH_Destination;
    CommandRegister.MessageType = APIC_MT_INIT;
    CommandRegister.DestinationMode = 1;
    CommandRegister.TriggerMode = APIC_TGM_EDGE;
    CommandRegister.Vector = APIC_VECTOR_ZERO;
    HlWriteApicRegister(APIC_ICR0, CommandRegister.Long0);

    /* Register interrupt handlers once the APIC initialization is done */
    KeSetInterruptHandler(APIC_VECTOR_SPURIOUS, HlpHandleApicSpuriousService);
    KeSetInterruptHandler(PIC1_VECTOR_SPURIOUS, HlpHandlePicSpuriousService);

    /* Clear errors after enabling vectors */
    HlWriteApicRegister(APIC_ESR, 0);

    /* Lower APIC TPR to re-enable interrupts */
    HlWriteApicRegister(APIC_TPR, 0x00);
}

/**
 * Initializes the legacy PIC interrupt controller.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
HlpInitializeLegacyPic(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;
    HlIoPortOutByte(PIC1_CONTROL_PORT, Icw1.Bits);

    /* Initialize ICW2 for PIC1 port */
    Icw2.Bits = 0x00;
    HlIoPortOutByte(PIC1_DATA_PORT, Icw2.Bits);

    /* Initialize ICW3 for PIC1 port */
    Icw3.Bits = 0;
    Icw3.SlaveIrq2 = TRUE;
    HlIoPortOutByte(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;
    HlIoPortOutByte(PIC1_DATA_PORT, Icw4.Bits);

    /* Mask all interrupts on PIC1 port */
    HlIoPortOutByte(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;
    HlIoPortOutByte(PIC2_CONTROL_PORT, Icw1.Bits);

    /* Initialize ICW2 for PIC2 port */
    Icw2.Bits = 0x08;
    HlIoPortOutByte(PIC2_DATA_PORT, Icw2.Bits);

    /* Initialize ICW3 for PIC2 port */
    Icw3.Bits = 0;
    Icw3.SlaveId = 2;
    HlIoPortOutByte(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;
    HlIoPortOutByte(PIC2_DATA_PORT, Icw4.Bits);

    /* Mask all interrupts on PIC2 port */
    HlIoPortOutByte(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
HlpInitializePic(VOID)
{
    /* Initialize APIC */
    HlpInitializeApic();

    /* Initialize legacy PIC */
    HlpInitializeLegacyPic();
}

/**
 * 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
HlpSendIpi(ULONG ApicId,
           ULONG Vector)
{
    /* Check current APIC mode */
    if(HlpApicMode == APIC_MODE_X2APIC)
    {
        /* Send IPI using x2APIC mode */
        HlWriteApicRegister(APIC_ICR0, ((ULONGLONG)ApicId << 32) | Vector);
    }
    else
    {
        /* Send IPI using xAPIC compatibility mode */
        HlWriteApicRegister(APIC_ICR1, ApicId << 24);
        HlWriteApicRegister(APIC_ICR0, Vector);
    }
}
