#include <string.h>
#include <generated/csr.h>

#include <dyld.h>

#include "kloader.h"
#include "log.h"
#include "clock.h"
#include "flash_storage.h"
#include "mailbox.h"
#include "messages.h"

static void start_kernel_cpu(struct msg_load_request *msg)
{
    // Stop kernel CPU before messing with its code.
    kernel_cpu_reset_write(1);

    // Load kernel support code.
    extern void _binary_ksupport_elf_start, _binary_ksupport_elf_end;
    memcpy((void *)(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE),
           &_binary_ksupport_elf_start,
           &_binary_ksupport_elf_end - &_binary_ksupport_elf_start);

    // Start kernel CPU.
    mailbox_send(msg);
    kernel_cpu_reset_write(0);
}

void kloader_start_bridge()
{
    start_kernel_cpu(NULL);
}

static int load_or_start_kernel(const void *library, int run_kernel)
{
    static struct dyld_info library_info;
    struct msg_load_request request = {
        .library      = library,
        .library_info = &library_info,
        .run_kernel   = run_kernel,
    };
    start_kernel_cpu(&request);

    struct msg_load_reply *reply = mailbox_wait_and_receive();
    mailbox_acknowledge();

    if(reply->type != MESSAGE_TYPE_LOAD_REPLY) {
        log("BUG: unexpected reply to load/run request");
        return 0;
    }

    if(reply->error != NULL) {
        log("cannot load kernel: %s", reply->error);
        return 0;
    }

    return 1;
}

int kloader_load_library(const void *library)
{
    if(!kernel_cpu_reset_read()) {
        log("BUG: attempted to load kernel library while kernel CPU is running");
        return 0;
    }

    return load_or_start_kernel(library, 0);
}

void kloader_filter_backtrace(struct artiq_backtrace_item *backtrace,
                              size_t *backtrace_size) {
    struct artiq_backtrace_item *cursor = backtrace;

    // Remove all backtrace items belonging to ksupport and subtract
    // shared object base from the addresses.
    for(int i = 0; i < *backtrace_size; i++) {
        if(backtrace[i].function > KERNELCPU_PAYLOAD_ADDRESS) {
            backtrace[i].function -= KERNELCPU_PAYLOAD_ADDRESS;
            *cursor++ = backtrace[i];
        }
    }

    *backtrace_size = cursor - backtrace;
}

void kloader_start_kernel()
{
    load_or_start_kernel(NULL, 1);
}

static int kloader_start_flash_kernel(char *key)
{
#if (defined CSR_SPIFLASH_BASE && defined CONFIG_SPIFLASH_PAGE_SIZE)
    char buffer[32*1024];
    unsigned int length, remain;

    length = fs_read(key, buffer, sizeof(buffer), &remain);
    if(length <= 0)
        return 0;

    if(remain) {
        log("ERROR: kernel %s is too large", key);
        return 0;
    }

    return load_or_start_kernel(buffer, 1);
#else
    return 0;
#endif
}

int kloader_start_startup_kernel(void)
{
    return kloader_start_flash_kernel("startup_kernel");
}

int kloader_start_idle_kernel(void)
{
    return kloader_start_flash_kernel("idle_kernel");
}

void kloader_stop(void)
{
    kernel_cpu_reset_write(1);
    mailbox_acknowledge();
}

int kloader_validate_kpointer(void *p)
{
    unsigned int v = (unsigned int)p;
    if((v < KERNELCPU_EXEC_ADDRESS) || (v > KERNELCPU_LAST_ADDRESS)) {
        log("Received invalid pointer from kernel CPU: 0x%08x", v);
        return 0;
    }
    return 1;
}

int kloader_is_essential_kmsg(int msgtype)
{
    switch(msgtype) {
        case MESSAGE_TYPE_NOW_INIT_REQUEST:
        case MESSAGE_TYPE_NOW_SAVE:
        case MESSAGE_TYPE_LOG:
        case MESSAGE_TYPE_WATCHDOG_SET_REQUEST:
        case MESSAGE_TYPE_WATCHDOG_CLEAR:
            return 1;
        default:
            return 0;
    }
}

long long int now;

void kloader_service_essential_kmsg(void)
{
    struct msg_base *umsg;

    umsg = mailbox_receive();
    if(umsg) {
        if(!kloader_validate_kpointer(umsg))
            return;
        switch(umsg->type) {
            case MESSAGE_TYPE_NOW_INIT_REQUEST: {
                struct msg_now_init_reply reply;

                reply.type = MESSAGE_TYPE_NOW_INIT_REPLY;
                reply.now = now;
                mailbox_send_and_wait(&reply);
                break;
            }
            case MESSAGE_TYPE_NOW_SAVE: {
                struct msg_now_save *msg = (struct msg_now_save *)umsg;

                now = msg->now;
                mailbox_acknowledge();
                break;
            }
            case MESSAGE_TYPE_LOG: {
                struct msg_log *msg = (struct msg_log *)umsg;

                if(msg->no_newline) {
                    lognonl_va(msg->fmt, msg->args);
                } else {
                    log_va(msg->fmt, msg->args);
                }
                mailbox_acknowledge();
                break;
            }
            case MESSAGE_TYPE_WATCHDOG_SET_REQUEST: {
                struct msg_watchdog_set_request *msg = (struct msg_watchdog_set_request *)umsg;
                struct msg_watchdog_set_reply reply;

                reply.type = MESSAGE_TYPE_WATCHDOG_SET_REPLY;
                reply.id = watchdog_set(msg->ms);
                mailbox_send_and_wait(&reply);
                break;
            }
            case MESSAGE_TYPE_WATCHDOG_CLEAR: {
                struct msg_watchdog_clear *msg = (struct msg_watchdog_clear *)umsg;

                watchdog_clear(msg->id);
                mailbox_acknowledge();
                break;
            }
            default:
                /* handled elsewhere */
                break;
        }
    }
}
