#include <stdarg.h>
#include <crc.h>
#include <uart.h>
#include <generated/csr.h>

#include "corecom.h"

enum {
    MSGTYPE_REQUEST_IDENT = 0x01,
    MSGTYPE_LOAD_KERNEL = 0x02,
    MSGTYPE_KERNEL_FINISHED = 0x03,
    MSGTYPE_RPC_REQUEST = 0x04,
};

static int receive_int(void)
{
    unsigned int r;
    int i;

    r = 0;
    for(i=0;i<4;i++) {
        r <<= 8;
        r |= (unsigned char)uart_read();
    }
    return r;
}

static char receive_char(void)
{
    return uart_read();
}

static void send_int(int x)
{
    int i;

    for(i=0;i<4;i++) {
        uart_write((x & 0xff000000) >> 24);
        x <<= 8;
    }
}

static void send_sint(short int i)
{
    uart_write((i >> 8) & 0xff);
    uart_write(i & 0xff);
}

static void send_char(char c)
{
    uart_write(c);
}

static void receive_sync(void)
{
    char c;
    int recognized;

    recognized = 0;
    while(recognized < 4) {
        c = uart_read();
        if(c == 0x5a)
            recognized++;
        else
            recognized = 0;
    }
}

static void send_sync(void)
{
    send_int(0x5a5a5a5a);
}

int ident_and_download_kernel(void *buffer, int maxlength)
{
    int length;
    unsigned int crc;
    int i;
    char msgtype;
    unsigned char *_buffer = buffer;

    while(1) {
        receive_sync();
        msgtype = receive_char();
        if(msgtype == MSGTYPE_REQUEST_IDENT) {
            send_int(0x41524f52); /* "AROR" - ARTIQ runtime on OpenRISC */
            send_int(1000000000000LL/identifier_frequency_read()); /* RTIO clock period in picoseconds */
        } else if(msgtype == MSGTYPE_LOAD_KERNEL) {
            length = receive_int();
            if(length > maxlength) {
                send_char(0x4c); /* Incorrect length */
                return -1;
            }
            crc = receive_int();
            for(i=0;i<length;i++)
                _buffer[i] = receive_char();
            if(crc32(buffer, length) != crc) {
                send_char(0x43); /* CRC failed */
                return -1;
            }
            send_char(0x4f); /* kernel reception OK */
            return length;
        } else
            return -1;
    }
}

int rpc(int rpc_num, int n_args, ...)
{
    send_sync();
    send_char(MSGTYPE_RPC_REQUEST);
    send_sint(rpc_num);
    send_char(n_args);

    va_list args;
    va_start(args, n_args);
    while(n_args--)
        send_int(va_arg(args, int));
    va_end(args);

    return receive_int();
}

void kernel_finished(void)
{
    send_sync();
    send_char(MSGTYPE_KERNEL_FINISHED);
}