/* SPDX-License-Identifier: MIT */

#include <assert.h>
#include <stdarg.h>

#include "utils.h"
#include "cpu_regs.h"
#include "iodev.h"
#include "smp.h"
#include "types.h"
#include "utils.h"
#include "vsprintf.h"
#include "xnuboot.h"

bool is_mac;

static char ascii(char s)
{
    if (s < 0x20)
        return '.';
    if (s > 0x7E)
        return '.';
    return s;
}

void hexdump(const void *d, size_t len)
{
    u8 *data;
    size_t i, off;
    data = (u8 *)d;
    for (off = 0; off < len; off += 16) {
        printf("%08lx  ", off);
        for (i = 0; i < 16; i++) {
            if ((i + off) >= len)
                printf("   ");
            else
                printf("%02x ", data[off + i]);
        }

        printf(" ");
        for (i = 0; i < 16; i++) {
            if ((i + off) >= len)
                printf(" ");
            else
                printf("%c", ascii(data[off + i]));
        }
        printf("\n");
    }
}

void regdump(u64 addr, size_t len)
{
    u64 i, off;
    for (off = 0; off < len; off += 32) {
        printf("%016lx  ", addr + off);
        for (i = 0; i < 32; i += 4) {
            printf("%08x ", read32(addr + off + i));
        }
        printf("\n");
    }
}

int snprintf(char *buffer, size_t size, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsnprintf(buffer, size, fmt, args);
    va_end(args);
    return i;
}

int debug_printf(const char *fmt, ...)
{
    va_list args;
    char buffer[512];
    int i;

    va_start(args, fmt);
    i = vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);

    iodev_console_write(buffer, min(i, (int)(sizeof(buffer) - 1)));

    return i;
}

void __assert_fail(const char *assertion, const char *file, unsigned int line, const char *function)
{
    printf("Assertion failed: '%s' on %s:%d:%s\n", assertion, file, line, function);
    flush_and_reboot();
}

void udelay(u32 d)
{
    u64 delay = ((u64)d) * mrs(CNTFRQ_EL0) / 1000000;
    u64 val = mrs(CNTPCT_EL0);
    while ((mrs(CNTPCT_EL0) - val) < delay)
        ;
    sysop("isb");
}

u64 ticks_to_msecs(u64 ticks)
{
    // NOTE: only accurate if freq is even kHz
    return ticks / (mrs(CNTFRQ_EL0) / 1000);
}

u64 ticks_to_usecs(u64 ticks)
{
    // NOTE: only accurate if freq is even MHz
    return ticks / (mrs(CNTFRQ_EL0) / 1000000);
}

u64 timeout_calculate(u32 usec)
{
    u64 delay = ((u64)usec) * mrs(CNTFRQ_EL0) / 1000000;
    return mrs(CNTPCT_EL0) + delay;
}

bool timeout_expired(u64 timeout)
{
    bool expired = mrs(CNTPCT_EL0) > timeout;
    sysop("isb");
    return expired;
}

void flush_and_reboot(void)
{
    iodev_console_flush();
    reboot();
}

void spin_init(spinlock_t *lock)
{
    lock->lock = -1;
    lock->count = 0;
}

void spin_lock(spinlock_t *lock)
{
    s64 tmp;
    s64 me = smp_id();
    if (__atomic_load_n(&lock->lock, __ATOMIC_ACQUIRE) == me) {
        lock->count++;
        return;
    }

    __asm__ volatile("1:\n"
                     "mov\t%0, -1\n"
                     "2:\n"
                     "\tldaxr\t%0, %1\n"
                     "\tcmn\t%0, 1\n"
                     "\tbeq\t3f\n"
                     "\tldxr\t%0, %1\n"
                     "\tcmn\t%0, 1\n"
                     "\tbeq\t2b\n"
                     "\twfe\n"
                     "\tb\t1b\n"
                     "3:"
                     "\tstxr\t%w0, %2, %1\n"
                     "\tcbnz\t%w0, 2b\n"
                     : "=&r"(tmp), "+m"(lock->lock)
                     : "r"(me)
                     : "cc", "memory");

    assert(__atomic_load_n(&lock->lock, __ATOMIC_RELAXED) == me);
    lock->count++;
}

void spin_unlock(spinlock_t *lock)
{
    s64 me = smp_id();
    assert(__atomic_load_n(&lock->lock, __ATOMIC_RELAXED) == me);
    assert(lock->count > 0);
    if (!--lock->count)
        __atomic_store_n(&lock->lock, -1L, __ATOMIC_RELEASE);
}

bool is_heap(void *addr)
{
    u64 p = (u64)addr;
    u64 top_of_kernel_data = (u64)cur_boot_args.top_of_kernel_data;
    u64 top_of_ram = cur_boot_args.mem_size + cur_boot_args.phys_base;

    return p > top_of_kernel_data && p < top_of_ram;
}

bool supports_arch_retention(void)
{
    return mrs(AIDR_EL1) & AIDR_EL1_ARCH_RETENTION;
}

bool supports_gxf(void)
{
    return mrs(AIDR_EL1) & AIDR_EL1_GXF;
}

bool supports_pan(void)
{
    return (mrs(ID_AA64MMFR1_EL1) >> 20) & 0xf;
}

// TODO: update mapping?
u64 top_of_memory_alloc(size_t size)
{
    static bool guard_page_inserted = false;
    cur_boot_args.mem_size -= ALIGN_UP(size, SZ_16K);
    u64 ret = cur_boot_args.phys_base + cur_boot_args.mem_size;

    if (!guard_page_inserted) {
        cur_boot_args.mem_size -= SZ_16K;
        guard_page_inserted = true;
    } else {
        // If the guard page was already there, move it down and allocate
        // above it -- this is accomplished by simply shifting the allocated
        // region by one page up.
        ret += SZ_16K;
    }

    return ret;
}
