#include "libcflat.h"
#include "processor.h"
#include "msr.h"
#include "isr.h"
#include "vm.h"
#include "apic.h"
#include "desc.h"
#include "smp.h"
#include "atomic.h"
#include "hyperv.h"
#include "alloc_page.h"

#define MAX_CPUS 4

static atomic_t isr_enter_count[MAX_CPUS];

static void synic_sint_auto_eoi_isr(isr_regs_t *regs)
{
    atomic_inc(&isr_enter_count[smp_id()]);
}

static void synic_sint_isr(isr_regs_t *regs)
{
    atomic_inc(&isr_enter_count[smp_id()]);
    eoi();
}

struct sint_vec_entry {
    int vec;
    bool auto_eoi;
};

struct sint_vec_entry sint_vecs[HV_SYNIC_SINT_COUNT] = {
    {0xB0, false},
    {0xB1, false},
    {0xB2, false},
    {0xB3, true},
    {0xB4, false},
    {0xB5, false},
    {0xB6, false},
    {0xB7, false},
    {0xB8, true},
    {0xB9, false},
    {0xBA, true},
    {0xBB, false},
    {0xBC, false},
    {0xBD, false},
    {0xBE, true},
    {0xBF, false},
};

static void synic_prepare_sint_vecs(void)
{
    bool auto_eoi;
    int i, vec;

    for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) {
        vec = sint_vecs[i].vec;
        auto_eoi = sint_vecs[i].auto_eoi;
        handle_irq(vec, (auto_eoi) ? synic_sint_auto_eoi_isr : synic_sint_isr);
    }
}

static void synic_sints_prepare(int vcpu)
{
    bool auto_eoi;
    int i, vec;

    for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) {
        vec = sint_vecs[i].vec;
        auto_eoi = sint_vecs[i].auto_eoi;
        synic_sint_create(i, vec, auto_eoi);
    }
}

static void synic_test_prepare(void *ctx)
{
    u64 r;
    int i = 0;

    write_cr3((ulong)ctx);
    irq_enable();

    rdmsr(HV_X64_MSR_SVERSION);
    rdmsr(HV_X64_MSR_SIMP);
    rdmsr(HV_X64_MSR_SIEFP);
    rdmsr(HV_X64_MSR_SCONTROL);
    for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) {
        rdmsr(HV_X64_MSR_SINT0 + i);
    }
    r = rdmsr(HV_X64_MSR_EOM);
    if (r != 0) {
        report("Hyper-V SynIC test, EOM read %#" PRIx64, false, r);
        return;
    }

    wrmsr(HV_X64_MSR_SIMP, (u64)virt_to_phys(alloc_page()) |
            HV_SYNIC_SIMP_ENABLE);
    wrmsr(HV_X64_MSR_SIEFP, (u64)virt_to_phys(alloc_page())|
            HV_SYNIC_SIEFP_ENABLE);
    wrmsr(HV_X64_MSR_SCONTROL, HV_SYNIC_CONTROL_ENABLE);

    synic_sints_prepare(smp_id());
}

static void synic_sints_test(int dst_vcpu)
{
    int i;

    atomic_set(&isr_enter_count[dst_vcpu], 0);
    for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) {
        synic_sint_set(dst_vcpu, i);
    }

    while (atomic_read(&isr_enter_count[dst_vcpu]) != HV_SYNIC_SINT_COUNT) {
        pause();
    }
}

static void synic_test(void *ctx)
{
    int dst_vcpu = (ulong)ctx;

    irq_enable();
    synic_sints_test(dst_vcpu);
}

static void synic_test_cleanup(void *ctx)
{
    int i;

    irq_enable();
    for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) {
        synic_sint_destroy(i);
    }

    wrmsr(HV_X64_MSR_SCONTROL, 0);
    wrmsr(HV_X64_MSR_SIMP, 0);
    wrmsr(HV_X64_MSR_SIEFP, 0);
}

int main(int ac, char **av)
{

    if (synic_supported()) {
        int ncpus, i;
        bool ok;

        setup_vm();
        smp_init();
        enable_apic();

        ncpus = cpu_count();
        if (ncpus > MAX_CPUS)
            report_abort("number cpus exceeds %d", MAX_CPUS);
        printf("ncpus = %d\n", ncpus);

        synic_prepare_sint_vecs();

        printf("prepare\n");
        on_cpus(synic_test_prepare, (void *)read_cr3());

        for (i = 0; i < ncpus; i++) {
            printf("test %d -> %d\n", i, ncpus - 1 - i);
            on_cpu_async(i, synic_test, (void *)(ulong)(ncpus - 1 - i));
        }
        while (cpus_active() > 1)
            pause();

        printf("cleanup\n");
        on_cpus(synic_test_cleanup, NULL);

        ok = true;
        for (i = 0; i < ncpus; ++i) {
            printf("isr_enter_count[%d] = %d\n",
                   i, atomic_read(&isr_enter_count[i]));
            ok &= atomic_read(&isr_enter_count[i]) == 16;
        }

        report("Hyper-V SynIC test", ok);
    } else {
        printf("Hyper-V SynIC is not supported");
    }

    return report_summary();
}
