/*
 * Copyright (c) 2026, Chloe M.
 * Provided under the BSD-3 clause.
 *
 * Description:  Kernel processor control region
 * Author:       Chloe M.
 */

#include <hal/kpcr.h>
#include <hal/intr.h>
#include <ex/trace.h>
#include <ex/pool.h>
#include <ke/knot.h>
#include <machine/cpuid.h>
#include <machine/idt.h>
#include <machine/msr.h>
#include <machine/lapic.h>
#include <stdef.h>

#define DTRACE(Fmt, ...) \
    TRACE("[ CPU ]: " Fmt, ##__VA_ARGS__)

/* Processor types */
#define PROCESSOR_OEM       0
#define PROCESSOR_OVERDRIVE 1
#define PROCESSOR_DUAL      2

#define PROCESSOR_TYPE(TYPE)                \
    ((TYPE) >= NELEM(ProcessorType))        \
        ? "unknown"                         \
        : ProcessorType[(Type)]

/* Processor type lookup table */
static const CHAR *ProcessorType[] = {
    [PROCESSOR_OEM] = "OEM processor",
    [PROCESSOR_OVERDRIVE] = "Intel overdrive processor",
    [PROCESSOR_DUAL] = "Intel P5 dual processor"
};

/* From cpu/vector.S */
extern VOID MdVectorInit(VOID);

/*
 * Called by ProcessorIdentify()
 */
static VOID
ProcessorIdentify1(MCB *Mcb)
{
    UCHAR ManufactId[13];
    ULONG Ebx, Edx, Ecx, Unused;

    if (Mcb == NULL) {
        return;
    }

    /* Grab the manufacturer ID */
    ManufactId[12] = '\0';
    CPUID(0, Unused, Ebx, Ecx, Edx);

    /* DWORD 0 */
    ManufactId[0] = Ebx & 0xFF;
    ManufactId[1] = (Ebx >> 8) & 0xFF;
    ManufactId[2] = (Ebx >> 16) & 0xFF;
    ManufactId[3] = (Ebx >> 24) & 0xFF;

    /* DWORD 1 */
    ManufactId[4] = Edx & 0xFF;
    ManufactId[5] = (Edx >> 8) & 0xFF;
    ManufactId[6] = (Edx >> 16) & 0xFF;
    ManufactId[7] = (Edx >> 24) & 0xFF;

    /* DWORD 2 */
    ManufactId[8] = Ecx & 0xFF;
    ManufactId[9] = (Ecx >> 8) & 0xFF;
    ManufactId[10] = (Ecx >> 16) & 0xFF;
    ManufactId[11] = (Ecx >> 24) & 0xFF;
    DTRACE("vendor : %s\n", ManufactId);
}

static VOID
ProcessorIdentify(MCB *Mcb)
{
    ULONG Eax, Unused;
    UCHAR ModelLow, ModelHigh;
    UCHAR FamilyLow, FamilyHigh;
    UCHAR Type;

    if (Mcb == NULL) {
        return;
    }

    CPUID(1, Eax, Unused, Unused, Unused);

    /* Extarct the model ID */
    ModelLow = (Eax >> 4) & 0xF;
    ModelHigh = (Eax >> 16) & 0xF;
    Mcb->ModelId = (ModelHigh << 4) | ModelLow;

    /* Extract the family ID */
    FamilyLow = (Eax >> 8) & 0xF;
    FamilyHigh = (Eax >> 20) & 0xFF;
    Mcb->FamilyId = (FamilyHigh << 4) | FamilyLow;

    /* Extract the processor type */
    Type = (Eax >> 12) & 3;

    /* Informational logging */
    DTRACE("model  : %X\n", Mcb->ModelId);
    DTRACE("family : %X\n", Mcb->FamilyId);
    DTRACE("type   : %s\n", PROCESSOR_TYPE(Type));
    ProcessorIdentify1(Mcb);
}

VOID
HalKpcrP1Init(KPCR *Kpcr)
{
    MCB *Mcb;

    if (Kpcr == NULL) {
        return;
    }

    Mcb = &Kpcr->Mcb;
    ProcessorIdentify(Mcb);

    /* Load the IDT */
    MdVectorInit();
    MdIdtLoad();

    /* Default to IRQL_PASSIVE */
    ASMV(
        "mov %0, %%cr8"
        :
        : "r" ((UQUAD)IRQL_PASSIVE)
        : "memory"
    );

    /* Save the current KPCR */
    MdWrmsr(IA32_GS_BASE, (UPTR)Kpcr);
}

VOID
HalKpcrP2Init(KPCR *Kpcr)
{
    ST_STATUS Status;

    if (Kpcr == NULL) {
        return;
    }

    Status = ExPoolInit(&Kpcr->AllocPool);
    if (Status != STATUS_SUCCESS) {
        KeKnot(
            KNOT_MISC,
            "failed to initialize pool for cpu%d\n",
            Kpcr->CoreId
        );
    }
}

VOID
HalKpcrP3Init(KPCR *Kpcr)
{
    MdLapicInit(Kpcr);
}

KPCR *
HalKpcrCurrent(VOID)
{
    KPCR *Kpcr;

    Kpcr = (KPCR *)MdRdmsr(IA32_GS_BASE);
    if (Kpcr == NULL) {
        KeKnot(
            KNOT_UNBOUND_RSRC,
            "current kpcr is NULL\n"
        );
    }

    return Kpcr;
}
