/*
 * Copyright (c) 2026, Chloe M.
 * Provided under the BSD-3 clause.
 *
 * Description: High-level interrupt management
 * Author:      Chloe M.
 */

#include <hal/intr.h>
#include <ke/bugcheck.h>
#include <machine/intr.h>
#include <machine/idt.h>
#include <ptdef.h>

/* Globals */
static INTR_HANDLER HandlerTable[256];

/*
 * Set a new IRQL
 */
static inline VOID
SetIrql(IRQL Irql)
{
    ASMV(
        "mov %0, %%cr8"
        :
        : "r" ((UQUAD)Irql)
        : "memory"
    );
}

UCHAR
HalIntrRegister(INTR_HANDLER *Handler, BOOLEAN IsUser)
{
    UCHAR VectorBase, Vector;
    INTR_HANDLER *HandlerSlot;

    /*
     * We have 16 priorities at every band as 4-bits makes up
     * the interrupt priority level. Our job is to find a slot
     * that is free for our interrupt handler at its given IRQL.
     */
    VectorBase = MAX(Handler->Irql << IPRI_CLASS_SHIFT, 0x20);
    for (Vector = VectorBase; Vector < Vector + 16; ++Vector) {
        /* Skip system reserved vectors */
        switch (Vector) {
        /* Timer vector */
        case 0x81: continue;
        /* System call vector */
        case 0x2E: continue;
        }

        /* Don't overwrite present entries */
        HandlerSlot = &HandlerTable[Vector];
        if (Handler->Present) {
            continue;
        }

        *HandlerSlot = *Handler;
        HandlerSlot->Present = 1;
        MdIdtSetGate(
            Vector,
            (UPTR)Handler->Handler,
            (IsUser) ? IDT_USER_GATE : IDT_INT_GATE,
            0
        );

        return Vector;
    }

    /* No more vectors */
    return 0;
}

IRQL
HalGetIrql(VOID)
{
    UQUAD CurrentIrql;

    ASMV(
        "mov %%cr8, %0"
        : "=r" (CurrentIrql)
        :
        : "memory"
    );

    return (IRQL)CurrentIrql;
}

IRQL
HalRaiseIrql(IRQL Irql)
{
    IRQL CurrentIrql;

    CurrentIrql = HalGetIrql();
    if (Irql < CurrentIrql) {
        KeBugCheck(BUGCHECK_IRQL_NOT_GTE, "got bad irql\n");
    }

    SetIrql(Irql);
    return CurrentIrql;
}

IRQL
HalLowerIrql(IRQL Irql)
{
    IRQL CurrentIrql;

    CurrentIrql = HalGetIrql();
    if (Irql > CurrentIrql) {
        KeBugCheck(BUGCHECK_IRQL_NOT_LTE, "got bad irql\n");
    }

    return CurrentIrql;
}
