/**
 * \file
 * \brief inter core messaging
 */

/*
 * Copyright (c) 2016, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

#include <aos/aos_urpc.h>
#include <aos/kernel_cap_invocations.h>
#include <aos/waitset_chan.h>
#include <aos/aos_rpc.h>
#include <spawn/rpc_server.h>
#include <aos/deferred.h>

extern coreid_t my_core_id;
extern rpc_handler_t rpc_handlers[RPC_MTYPE_COUNT];

errval_t do_aos_urpc(
    struct aos_urpc *rpc, uintptr_t msg_type,
    struct capref arg_cap, size_t arg_size, uintptr_t arg0, uintptr_t arg1,
    struct capref *ret_cap, size_t *ret_size, uintptr_t *ret0, uintptr_t *ret1
) {
    errval_t err;

    // allocate the return capability before performing the rpc call
    if(ret_cap != NULL) {
        err = slot_alloc(ret_cap);
        if (err_is_fail(err)) return err_push(err, LIB_ERR_SLOT_ALLOC);
    }

    assert(rpc->meta->call_in_progress == 0);

    assert(capref_is_null(arg_cap));
    rpc->meta->cap.type = ObjType_Null;

    rpc->meta->a0 = msg_type;
    rpc->meta->a1 = arg_size;
    rpc->meta->a2 = arg0;
    rpc->meta->a3 = arg1;

    // memory barrier
    __asm volatile (
        "dmb sy\n"
    );

    rpc->meta->call_in_progress = 1;

    // wait until the rpc call completes
    while(rpc->meta->call_in_progress) {
        // yield the thread because there might be useful stuff to do...
        thread_yield();
    }

    // memory barrier
    __asm volatile (
        "dmb sy\n"
    );

    // read the response
    if (ret_size != NULL) *ret_size = rpc->meta->a1;
    if (ret0 != NULL) *ret0 = rpc->meta->a2;
    if (ret1 != NULL) *ret1 = rpc->meta->a3;

    if(ret_cap != NULL) {
        assert(rpc->meta->cap.type == ObjType_RAM);
        err = ram_forge(*ret_cap, rpc->meta->cap.u.ram.base, rpc->meta->cap.u.ram.bytes, my_core_id);
        if (err_is_fail(err)) return err;
    }

    return rpc->meta->a0;
}

// URPC calls on the BSP core should only be done on this thread.
int urpc_client_loop(void *arg) {
    errval_t err;
    struct waitset *ws = arg;
    while (true) {
        err = event_dispatch(ws);
        if (err_is_fail(err)) {
            DEBUG_ERR(err, "in event_dispatch");
            abort();
        }
    }
}


static void urpc_server_handler(void *arg) {
    struct aos_urpc_server *urpc = arg;
    errval_t err;

    uintptr_t msg_type = urpc->meta->a0;
    uintptr_t arg_size = urpc->meta->a1;
    uintptr_t arg0 = urpc->meta->a2;
    uintptr_t arg1 = urpc->meta->a3;

    struct capref ret_cap = NULL_CAP;
    urpc->meta->a1 = 0;
    urpc->meta->a2 = 0;
    urpc->meta->a3 = 0;
    if (msg_type >= RPC_MTYPE_COUNT) {
        err = AOS_ERR_RPC_UNKNOWN_MSG_TYPE;
    } else if (arg_size > RPC_SHARED_SIZE) {
        err = AOS_ERR_RPC_ARG_TOO_BIG;
    } else {
        rpc_handler_t handler = rpc_handlers[msg_type];
        if (handler == NULL) {
            err = AOS_ERR_RPC_UNKNOWN_MSG_TYPE;
        } else {
            err = handler(
                &urpc->g,
                NULL_CAP, arg_size, arg0, arg1,
                &ret_cap, &urpc->meta->a1, &urpc->meta->a2, &urpc->meta->a3
            );
        }
    }
    urpc->meta->a0 = err;
    if (!capref_is_null(ret_cap)) {
        err = cap_direct_identify(ret_cap, &urpc->meta->cap);
        if (err_is_fail(err)) {
            DEBUG_ERR(err, "in cap_direct_identify while handling URPC");
            abort();
        }
        // only allow ram capabilities or frame capabilities to be sent to other cores
        assert(urpc->meta->cap.type == ObjType_RAM || urpc->meta->cap.type == ObjType_Frame);
    }

    // memory barrier
    __asm volatile (
        "dmb sy\n"
    );

    // tell the other side that we are done
    urpc->meta->call_in_progress = false;

    // Unblock the URPC thread
    thread_sem_post(&urpc->sem);
}

errval_t aos_urpc_get_bootinfo(struct aos_urpc * rpc, struct bootinfo_serialized ** ret)
{
    size_t ret_len;
    errval_t err;
    err = do_aos_urpc(
        rpc, RPC_MTYPE_GET_BOOTINFO,
        NULL_CAP, 0, 0, 0,
        NULL, &ret_len, NULL, NULL
    );
    if (err_is_fail(err)) {
        return err;
    }

    *ret = malloc(ret_len);
    if (*ret == NULL) return LIB_ERR_MALLOC_FAIL;

    memcpy(*ret, rpc->shared_mem, ret_len);
    return SYS_ERR_OK;
}


int urpc_server(void *arg) {
    struct aos_urpc_server *urpc = arg;
    struct waitset *default_ws = get_default_waitset();
    struct waitset_chanstate chan;
    waitset_chanstate_init(&chan, CHANTYPE_OTHER);

    thread_sem_init(&urpc->sem, 0);

    while (true) {
        // wait until an rpc call arrives
        while(urpc->meta->call_in_progress == false) {
            // sleep for a bit while there is nothing to do
            barrelfish_usleep(100);
        }

        // memory barrier
        __asm volatile (
            "dmb sy\n"
        );

        // handle the URPC on the same thread that handles regular RPCs, to avoid concurrency for the handlers
        waitset_chan_trigger_closure(default_ws, &chan, MKCLOSURE(urpc_server_handler, urpc));

        // wait until the rpc is handled
        thread_sem_wait(&urpc->sem);
    }
}
