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

#include <hal/intr.h>
#include <machine/idt.h>
#include <ke/knot.h>
#include <stdef.h>

/* Interrupt priority level shift */
#define IPL_SHIFT 4

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

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

ST_STATUS
HalRegisterIntr(const INTR_HANDLER *Handler, BOOLEAN IsUser)
{
    UCHAR Vector, Idx;
    INTR_HANDLER *HandlerSlot;

    if (Handler == NULL) {
        return STATUS_INVALID_PARAM;
    }

    /* Must have a valid handler */
    if (Handler->Handler == NULL) {
        return STATUS_INVALID_PARAM;
    }

    /* Ensure that the priority is valid */
    switch (Handler->Irql) {
    case IRQL_PASSIVE:
    case IRQL_APC:
    case IRQL_DEVICE:
    case IRQL_HIGH:
        break;
    default:
        return STATUS_INVALID_PARAM;
    }

    /*
     * 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.
     */
    Vector = MAX(0x20, Handler->Irql << IPL_SHIFT);
    for (Idx = Vector; Idx < Vector + 16; ++Idx) {
        HandlerSlot = &Handlers[Idx];

        /* This entry is present, don't overwrite */
        if (HandlerSlot->Present) {
            continue;
        }

        /* Fill the slot */
        *HandlerSlot = *(INTR_HANDLER *)Handler;
        HandlerSlot->Present = 1;

        /* Register the interrupt */
        MdIdtSetEntry(
            Idx,
            (UPTR)Handler->Handler,
            (IsUser) ? IDT_USER_GATE : IDT_INT_GATE,
            0
        );

        return STATUS_SUCCESS;
    }

    /* No entry found */
    return STATUS_NOT_FOUND;
}

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) {
        KeKnot(KNOT_IRQL_NOT_GTE, "got bad irql\n");
    }

    SetIrql(Irql);
    return CurrentIrql;
}

IRQL
HalLowerIrql(IRQL Irql)
{
    IRQL CurrentIrql;

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

    return CurrentIrql;
}
