/*
 * \brief  seL4 implementation of the IPC API
 * \author Norman Feske
 * \date   2014-10-14
 */

/*
 * Copyright (C) 2014 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode includes */
#include <base/ipc.h>
#include <base/printf.h>
#include <base/blocking.h>
#include <base/thread.h>
#include <util/misc_math.h>

/* base-internal includes */
#include <internal/capability_space_sel4.h>
#include <internal/kernel_debugger.h>

/* seL4 includes */
#include <sel4/interfaces/sel4_client.h>

using namespace Genode;


/**
 * Message-register definitions
 */
enum {
	MR_IDX_NUM_CAPS = 0,
	MR_IDX_CAPS     = 1,
	MR_IDX_DATA     = MR_IDX_CAPS + Msgbuf_base::MAX_CAPS_PER_MSG,
};


/**
 * Return reference to receive selector of the calling thread
 */
static unsigned &rcv_sel()
{
	/*
	 * When the function is called at the very early initialization phase,
	 * we cannot access Thread_base::myself()->tid() because the Thread_base
	 * object of the main thread does not exist yet. During this phase, we
	 * return a reference to the 'main_rcv_sel' variable.
	 */
	if (Thread_base::myself()) {
		return Thread_base::myself()->tid().rcv_sel;
	}

	static unsigned main_rcv_sel = Capability_space::alloc_rcv_sel();
	return main_rcv_sel;
}


/**
 * Convert Genode::Msgbuf_base content into seL4 message
 *
 * \param msg          source message buffer
 * \param data_length  size of message data in bytes
 */
static seL4_MessageInfo_t new_seL4_message(Msgbuf_base &msg,
                                           size_t const data_length)
{
	/*
	 * Supply capabilities to kernel IPC message
	 */
	seL4_SetMR(MR_IDX_NUM_CAPS, msg.used_caps());
	size_t sel4_sel_cnt = 0;
	for (size_t i = 0; i < msg.used_caps(); i++) {

		Native_capability &cap = msg.cap(i);

		if (cap.valid()) {
			Capability_space::Ipc_cap_data const ipc_cap_data =
				Capability_space::ipc_cap_data(cap);

			seL4_SetMR(MR_IDX_CAPS + i, ipc_cap_data.rpc_obj_key.value());
			seL4_SetCap(sel4_sel_cnt++, ipc_cap_data.sel);
		} else {
			seL4_SetMR(MR_IDX_CAPS + i, Rpc_obj_key::INVALID);
		}
	}

	/*
	 * Pad unused capability slots with invalid capabilities to avoid
	 * leakage of any information that happens to be in the IPC buffer.
	 */
	for (size_t i = msg.used_caps(); i < Msgbuf_base::MAX_CAPS_PER_MSG; i++)
		seL4_SetMR(MR_IDX_CAPS + i, Rpc_obj_key::INVALID);

	/*
	 * Allocate and define receive selector
	 */

	if (!rcv_sel())
		rcv_sel() = Capability_space::alloc_rcv_sel();

	/*
	 * Supply data payload
	 */
	size_t const num_data_mwords =
		align_natural(data_length) / sizeof(umword_t);

	umword_t const *src = (umword_t const *)msg.data();
	for (size_t i = 0; i < num_data_mwords; i++)
		seL4_SetMR(MR_IDX_DATA + i, *src++);

	seL4_MessageInfo_t const msg_info =
		seL4_MessageInfo_new(0, 0, sel4_sel_cnt,
		                     MR_IDX_DATA + num_data_mwords);
	return msg_info;
}


/**
 * Convert seL4 message into Genode::Msgbuf_base
 */
static void decode_seL4_message(umword_t badge,
                                seL4_MessageInfo_t const &msg_info,
                                Msgbuf_base &dst_msg)
{
	/*
	 * Extract Genode capabilities from seL4 IPC message
	 */
	dst_msg.reset_caps();
	size_t const num_caps = seL4_GetMR(MR_IDX_NUM_CAPS);
	size_t curr_sel4_cap_idx = 0;

	for (size_t i = 0; i < num_caps; i++) {

		Rpc_obj_key const rpc_obj_key(seL4_GetMR(MR_IDX_CAPS + i));

		/*
		 * Detect passing of invalid capabilities as arguments
		 *
		 * The second condition of the check handles the case where a non-RPC
		 * object capability as passed as RPC argument as done by the
		 * 'Cap_session::alloc' RPC function. Here, the entrypoint capability
		 * is not an RPC-object capability but a raw seL4 endpoint selector.
		 *
		 * XXX Technically, a message may contain one invalid capability
		 *     followed by a valid one. This check would still wrongly regard
		 *     the first capability as a valid one. A better approach would
		 *     be to introduce another state to Rpc_obj_key, which would
		 *     denote a valid capability that is not an RPC-object capability.
		 *     Hence it is meaningless as a key.
		 */
		if (!rpc_obj_key.valid() && seL4_MessageInfo_get_extraCaps(msg_info) == 0) {
			dst_msg.append_cap(Native_capability());
			continue;
		}

		/*
		 * RPC object key as contained in the message data is valid.
		 */

		unsigned const unwrapped =
			seL4_MessageInfo_get_capsUnwrapped(msg_info) &
			(1 << curr_sel4_cap_idx);

		/* distinguish unwrapped from delegated cap */
		if (unwrapped) {

			/*
			 * Received unwrapped capability
			 *
			 * This means that the capability argument belongs to our endpoint.
			 * So it is already present within the capability space.
			 */

			unsigned const arg_badge =
				seL4_CapData_Badge_get_Badge(seL4_GetBadge(curr_sel4_cap_idx));

			if (arg_badge != rpc_obj_key.value()) {
				PWRN("argument badge (%d) != RPC object key (%d)",
				     arg_badge, rpc_obj_key.value());
			}

			Native_capability arg_cap = Capability_space::lookup(rpc_obj_key);

			dst_msg.append_cap(arg_cap);

		} else {

			/*
			 * Received delegated capability
			 *
			 * We have either received a capability that is foreign to us,
			 * or an alias for a capability that we already posses. The
			 * latter can happen in the following circumstances:
			 *
			 * - We forwarded a selector that was created by another
			 *   component. We cannot re-identify such a capability when
			 *   handed back because seL4's badge mechanism works only for
			 *   capabilities belonging to the IPC destination endpoint.
			 *
			 * - We received a selector on the IPC reply path, where seL4's
			 *   badge mechanism is not in effect.
			 */

			bool const delegated = seL4_MessageInfo_get_extraCaps(msg_info);

			ASSERT(delegated);

			Native_capability arg_cap = Capability_space::lookup(rpc_obj_key);

			if (arg_cap.valid()) {

				/*
				 * Discard the received selector and keep using the already
				 * present one.
				 *
				 * XXX We'd need to find out if both the received and the
				 *     looked-up selector refer to the same endpoint.
				 *      Unfortunaltely, seL4 lacks such a comparison operation.
				 */

				Capability_space::reset_sel(rcv_sel());

				dst_msg.append_cap(arg_cap);

			} else {

				Capability_space::Ipc_cap_data const
					ipc_cap_data(rpc_obj_key, rcv_sel());

				dst_msg.append_cap(Capability_space::import(ipc_cap_data));

				/*
				 * Since we keep using the received selector, we need to
				 * allocate a fresh one for the next incoming delegation.
				 */
				rcv_sel() = Capability_space::alloc_rcv_sel();
			}
		}
		curr_sel4_cap_idx++;
	}

	/*
	 * Extract message data payload
	 */

	umword_t *dst = (umword_t *)dst_msg.data();
	for (size_t i = 0; i < seL4_MessageInfo_get_length(msg_info); i++)
		*dst++ = seL4_GetMR(MR_IDX_DATA + i);

	/*
	 * Store RPC object key of invoked object to be picked up by server.cc
	 */
	*(long *)dst_msg.data() = badge;
}


/*****************************
 ** IPC marshalling support **
 *****************************/

void Ipc_ostream::_marshal_capability(Native_capability const &cap)
{
	_snd_msg->append_cap(cap);
}


void Ipc_istream::_unmarshal_capability(Native_capability &cap)
{
	cap = _rcv_msg->extract_cap();
}


/*****************
 ** Ipc_ostream **
 *****************/

void Ipc_ostream::_send()
{
	ASSERT(false);

	_write_offset = sizeof(umword_t);
}


Ipc_ostream::Ipc_ostream(Native_capability dst, Msgbuf_base *snd_msg)
:
	Ipc_marshaller((char *)snd_msg->data(), snd_msg->size()),
	_snd_msg(snd_msg), _dst(dst)
{
	_write_offset = sizeof(umword_t);
}


/*****************
 ** Ipc_istream **
 *****************/

void Ipc_istream::_wait()
{
	ASSERT(false);
}


Ipc_istream::Ipc_istream(Msgbuf_base *rcv_msg)
:
	Ipc_unmarshaller((char *)rcv_msg->data(), rcv_msg->size()),
	_rcv_msg(rcv_msg)
{
	_read_offset = sizeof(umword_t);
}


Ipc_istream::~Ipc_istream() { }


/****************
 ** Ipc_client **
 ****************/

void Ipc_client::_call()
{
	if (!Ipc_ostream::_dst.valid()) {
		PERR("Trying to invoke an invalid capability, stop.");
		kernel_debugger_panic("IPC destination is invalid");
	}

	if (!rcv_sel())
		rcv_sel() = Capability_space::alloc_rcv_sel();

	seL4_MessageInfo_t const request_msg_info =
		new_seL4_message(*_snd_msg, _write_offset);

	unsigned const dst_sel = Capability_space::ipc_cap_data(_dst).sel;

	seL4_MessageInfo_t const reply_msg_info =
		seL4_Call(dst_sel, request_msg_info);

	decode_seL4_message(0, reply_msg_info, *_rcv_msg);

	_write_offset = _read_offset = sizeof(umword_t);
}


Ipc_client::Ipc_client(Native_capability const &srv, Msgbuf_base *snd_msg,
                       Msgbuf_base *rcv_msg, unsigned short)
: Ipc_istream(rcv_msg), Ipc_ostream(srv, snd_msg), _result(0)
{ }


/****************
 ** Ipc_server **
 ****************/

void Ipc_server::_prepare_next_reply_wait()
{
	/* now we have a request to reply */
	_reply_needed = true;

	/* leave space for return value at the beginning of the msgbuf */
	_write_offset = 2*sizeof(umword_t);

	/* receive buffer offset */
	_read_offset = sizeof(umword_t);

	_rcv_msg->reset_read_cap_index();
	_snd_msg->reset_caps();
}


void Ipc_server::_wait()
{
	seL4_Word badge = Rpc_obj_key::INVALID;
	seL4_MessageInfo_t const msg_info =
		seL4_Wait(Thread_base::myself()->tid().ep_sel, &badge);

	decode_seL4_message(badge, msg_info, *_rcv_msg);

	_prepare_next_reply_wait();
}


void Ipc_server::_reply()
{
	ASSERT(false);
}


void Ipc_server::_reply_wait()
{
	if (!_reply_needed) {

		_wait();

	} else {

		seL4_Word badge = Rpc_obj_key::INVALID;
		seL4_MessageInfo_t const reply_msg_info =
			new_seL4_message(*_snd_msg, _write_offset);

		seL4_MessageInfo_t const request_msg_info =
			seL4_ReplyWait(Thread_base::myself()->tid().ep_sel,
			               reply_msg_info, &badge);

		decode_seL4_message(badge, request_msg_info, *_rcv_msg);
	}

	_prepare_next_reply_wait();
}


Ipc_server::Ipc_server(Msgbuf_base *snd_msg,
                       Msgbuf_base *rcv_msg)
:
	Ipc_istream(rcv_msg), Ipc_ostream(Native_capability(), snd_msg),
	_reply_needed(false)
{
	*static_cast<Native_capability *>(this) =
		Native_capability(Capability_space::create_ep_cap(*Thread_base::myself()));
}
