/* SPDX-License-Identifier: MIT */

#include "cpu_regs.h"
#include "exception.h"
#include "gxf.h"
#include "malloc.h"
#include "memory.h"
#include "smp.h"
#include "uart.h"
#include "utils.h"

uint64_t gxf_enter(void *func, uint64_t a, uint64_t b, uint64_t c, uint64_t d);

void _gxf_init(void *gl2_stack, void *gl1_stack);

u8 *gl1_stack[MAX_CPUS];
u8 *gl2_stack[MAX_CPUS];

void gxf_init(void)
{
    int cpu = smp_id();

    if (!gl2_stack[cpu])
        gl2_stack[cpu] = memalign(0x4000, GL_STACK_SIZE);
    if (in_el2() && !gl1_stack[cpu])
        gl1_stack[cpu] = memalign(0x4000, GL_STACK_SIZE);

    _gxf_init(gl2_stack[cpu], gl1_stack[cpu]);
}

bool gxf_enabled(void)
{
    if (!supports_gxf())
        return false;

    if (!(mrs(SYS_IMP_APL_SPRR_CONFIG_EL1) & SPRR_CONFIG_EN))
        return false;

    return (mrs(SYS_IMP_APL_GXF_CONFIG_EL1) & GXF_CONFIG_EN);
}

bool in_gl12(void)
{
    if (!gxf_enabled())
        return false;

    return (mrs(SYS_IMP_APL_GXF_STATUS_EL1) & GXF_STATUS_GUARDED);
}

static uint64_t gl_call(void *func, uint64_t a, uint64_t b, uint64_t c, uint64_t d)
{
    // disable the MMU first since enabling SPRR will change the meaning of all
    // pagetable permission bits and also prevent us from having rwx pages
    u64 sprr_state = mrs(SYS_IMP_APL_SPRR_CONFIG_EL1);
    if (!(sprr_state & SPRR_CONFIG_EN))
        reg_set_sync(SYS_IMP_APL_SPRR_CONFIG_EL1, sprr_state | SPRR_CONFIG_EN);

    u64 gxf_state = mrs(SYS_IMP_APL_GXF_CONFIG_EL1);
    if (!(gxf_state & GXF_CONFIG_EN))
        reg_set_sync(SYS_IMP_APL_GXF_CONFIG_EL1, gxf_state | GXF_CONFIG_EN);

    uint64_t ret = gxf_enter(func, a, b, c, d);

    if (!(gxf_state & GXF_CONFIG_EN))
        msr_sync(SYS_IMP_APL_GXF_CONFIG_EL1, gxf_state);
    if (!(sprr_state & SPRR_CONFIG_EN))
        msr_sync(SYS_IMP_APL_SPRR_CONFIG_EL1, sprr_state);

    return ret;
}

uint64_t gl2_call(void *func, uint64_t a, uint64_t b, uint64_t c, uint64_t d)
{
    if (mrs(CurrentEL) != 0x8)
        return -1;
    return gl_call(func, a, b, c, d);
}

struct gl_call_argv {
    void *func;
    uint64_t a, b, c, d;
};

static uint64_t gl_call_wrapper(struct gl_call_argv *args)
{
    return gl_call(args->func, args->a, args->b, args->c, args->d);
}

uint64_t gl1_call(void *func, uint64_t a, uint64_t b, uint64_t c, uint64_t d)
{
    if (mrs(CurrentEL) == 0x4)
        return gl_call(func, a, b, c, d);

    struct gl_call_argv args;
    args.func = func;
    args.a = a;
    args.b = b;
    args.c = c;
    args.d = d;

    // enable EL1 here since once GXF has been enabled HCR_EL2 writes are only possible from GL2
    if (mrs(HCR_EL2) & HCR_TGE)
        reg_clr(HCR_EL2, HCR_TGE);

    u64 sprr_state = mrs(SYS_IMP_APL_SPRR_CONFIG_EL1) & SPRR_CONFIG_EN;
    reg_set_sync(SYS_IMP_APL_SPRR_CONFIG_EL1, SPRR_CONFIG_EN);

    u64 gxf_state = mrs(SYS_IMP_APL_GXF_CONFIG_EL1) & GXF_CONFIG_EN;
    reg_set_sync(SYS_IMP_APL_GXF_CONFIG_EL1, GXF_CONFIG_EN);

    uint64_t ret = el1_call(gl_call_wrapper, (uint64_t)&args, 0, 0, 0);

    msr_sync(SYS_IMP_APL_GXF_CONFIG_EL1, gxf_state);
    msr_sync(SYS_IMP_APL_SPRR_CONFIG_EL1, sprr_state);

    return ret;
}
