/*
 * Copyright (c) 2026, Chloe M.
 * Provided under the BSD-3 clause.
 *
 * Description: Local APIC driver
 * Author:      Chloe M.
 */

#include <machine/lapic.h>
#include <machine/lapicreg.h>
#include <machine/msr.h>
#include <machine/cpuid.h>
#include <hal/mmio.h>
#include <hal/prim.h>
#include <drivers/acpi/tables.h>
#include <drivers/acpi/acpi.h>
#include <mm/vmm.h>
#include <ex/trace.h>
#include <ke/knot.h>
#include <stdef.h>

/*
 * Trace only on the bootstrap processor but not on any others
 * to avoid log spam...
 */
#define DTRACE_BSP(Fmt, ...) do {                       \
        ULONG ApicBase;                                 \
                                                        \
        ApicBase = MdRdmsr(IA32_APIC_BASE_MSR);         \
        if (ISSET(ApicBase, BIT(8))) {                  \
            TRACE("[ LAPIC ]: " Fmt, ##__VA_ARGS__);    \
        }                                               \
    } while (0);

/*
 * Returns true if the Local APIC unit is supported on the
 * current processor.
 */
static BOOLEAN
LapicIsSupported(VOID)
{
    ULONG Edx, Unused;

    CPUID(1, Unused, Unused, Unused, Edx);
    return ISSET(Edx, BIT(9)) != 0;
}

/*
 * Returns true if the CPU can operate its Local APIC unit
 * in the newer x2APIC mode.
 */
static BOOLEAN
LapicHasX2Apic(VOID)
{
    ULONG Ecx, Unused;

    CPUID(1, Unused, Unused, Ecx, Unused);
    return ISSET(Ecx, BIT(21)) != 0;
}

/*
 * Read a Local APIC register
 *
 * @Mcb:        Machine core block
 * @Register:   Register to read
 */
static UQUAD
LapicRegRead(MCB *Mcb, ULONG Register)
{
    ULONG *Base;
    UQUAD Value;

    if (!Mcb->HasX2Apic) {
        Base = PTR_OFFSET(Mcb->LapicBase, Register);
        Value = MMIORead32(Base);
    } else {
        Register >>= 4;
        Value = MdRdmsr(x2APIC_MSR_BASE + Register);
    }

    return Value;
}

/*
 * Write a value to a Local APIC register space
 *
 * @Mcb:        Machine core block
 * @Register:   Register to read
 * @Value:      Value to write
 */
static VOID
LapicRegWrite(MCB *Mcb, ULONG Register, UQUAD Value)
{
    ULONG *Base;

    if (!Mcb->HasX2Apic) {
        Base = PTR_OFFSET(Mcb->LapicBase, Register);
        MMIOWrite32(Base, (ULONG)Value);
    } else {
        Register >>= 4;
        MdWrmsr(x2APIC_MSR_BASE + Register, Value);
    }
}

/*
 * Enable the Local APIC unit
 *
 * @Mcb:   Machine core block
 */
static VOID
LapicEnable(MCB *Mcb)
{
    UQUAD ApicBase;
    ULONG Svr, Version;
    const CHAR *mode;
    const CHAR *type = "discrete 82489DX";

    /* Hardware enable the Local APIC unit */
    ApicBase = MdRdmsr(IA32_APIC_BASE_MSR);
    ApicBase |= LAPIC_HW_ENABLE;
    ApicBase |= Mcb->HasX2Apic << x2APIC_ENABLE_SHIFT;
    MdWrmsr(IA32_APIC_BASE_MSR, ApicBase);

    /* Software enable the Local APIC unit */
    Svr = LapicRegRead(Mcb, LAPIC_SVR);
    Svr |= LAPIC_SW_ENABLE;
    LapicRegWrite(Mcb, LAPIC_SVR, Svr);

    /* Check the type */
    Version = LapicRegRead(Mcb, LAPIC_VERSION);
    if ((Version & 0xFF) > 0) {
        type = "integrated";
    }

    mode = Mcb->HasX2Apic ? "x2apic" : "xapic";
    DTRACE_BSP("enabled %s %s\n", type, mode);
}

VOID
MdLapicSendIpi(UCHAR Vector, UCHAR DestId, BOOLEAN LogicalDest,
    IPI_SHORTHAND Xnd, IPI_DELMOD DelMod)
{
    KPCR *ThisCore;
    MCB *Mcb;
    ULONG IcrLow, IcrHigh;
    ULONG Icr = 0;

    /*
     * If the shorthand is refering to ourselves and we are x2APIC we
     * can use a better path via its specialized SELF IPI MSR.
     */
    ThisCore = HalKpcrCurrent();
    if (Xnd == IPI_XND_SELF && Mcb->HasX2Apic) {
        MdWrmsr(LAPIC_SELF_IPI, Vector);
        return;
    }

    Mcb = &ThisCore->Mcb;

    /* Clamp these fields */
    DelMod &= 7;
    Xnd &= 3;

    /* Encode the ICR */
    Icr |= Vector & 0xFF;
    Icr |= Xnd << 18;
    Icr |= DelMod << 8;
    Icr |= LogicalDest << 11;

    /* For xAPIC only */
    if (!Mcb->HasX2Apic) {
        IcrHigh = ((UQUAD)Icr >> 32) & (ULONG)-1;
        IcrLow = Icr & (ULONG)-1;
        IcrHigh |= (UQUAD)DestId << 24;

        LapicRegWrite(Mcb, LAPIC_ICRHI, IcrHigh);
        LapicRegWrite(Mcb, LAPIC_ICRLO, IcrLow);

        /* Only on legacy xAPICs do we poll */
        for (;;) {
            Icr = LapicRegRead(Mcb, LAPIC_ICRLO);
            if (ISSET(Icr, IPI_DELSTAT_PENDING))
                HalCpuSpinWait();
        }

        return;
    }

    /* On x2APICs only as it queues */
    Icr |= (UQUAD)DestId << 32;
    LapicRegWrite(Mcb, LAPIC_ICRLO, Icr);
}

VOID
MdLapicInit(KPCR *Kpcr)
{
    INTR_HANDLER Handler;
    INTR_DATA *IntrData;
    ACPI_MADT *Madt;
    MCB *Mcb;

    if (Kpcr == NULL) {
        KeKnot(
            KNOT_MISC,
            "lapic: failed to initialize lapic driver\n"
        );
    }

    if (!LapicIsSupported()) {
        KeKnot(
            KNOT_MISSING_HARDWARE,
            "lapic: local apic not supported\n"
        );
    }

    /* Obtain the ACPI MADT table */
    Madt = AcpiQuery("APIC");
    if (Madt == NULL) {
        KeKnot(KNOT_MISC, "lapic: could not obtain MADT\n");
    }

    Mcb = &Kpcr->Mcb;
    Mcb->LapicBase = PMA_TO_VMA((UPTR)Madt->LapicAddr);
    Mcb->HasX2Apic = LapicHasX2Apic();

    /* Enable the Local APIC */
    LapicEnable(Mcb);
}
