/*
 * \brief  Implementation of the IPC API for NOVA
 * \author Norman Feske
 * \date   2009-10-02
 */

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

/* Genode includes */
#include <base/ipc.h>
#include <base/thread.h>

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

/* NOVA includes */
#include <nova/cap_map.h>

using namespace Genode;


/****************
 ** IPC client **
 ****************/

Rpc_exception_code Genode::ipc_call(Native_capability dst,
                                    Msgbuf_base &snd_msg, Msgbuf_base &rcv_msg,
                                    size_t rcv_caps)
{
	Receive_window rcv_window;
	rcv_msg.reset();

	/* update receive window for capability selectors if needed */
	if (rcv_caps != ~0UL) {

		/* calculate max order of caps to be received during reply */
		uint8_t log2_max = 0;
		if (rcv_caps) {
			log2_max = log2(rcv_caps, 0u);

			/* if this happens, the call is bogus and invalid */
			if (log2_max >= 8*sizeof(rcv_caps))
				return Rpc_exception_code(Rpc_exception_code::INVALID_OBJECT);

			if ((1UL << log2_max) < rcv_caps)
				log2_max ++;
		}

		rcv_window.rcv_wnd(log2_max);
	}

	Thread * const myself = Thread::myself();
	Nova::Utcb &utcb = *(Nova::Utcb *)myself->utcb();

	/* the protocol value is unused as the badge is delivered by the kernel */
	if (!copy_msgbuf_to_utcb(utcb, snd_msg, 0))
		return Rpc_exception_code(Rpc_exception_code::INVALID_OBJECT);

	/*
	 * Determine manually defined selector for receiving the call result.
	 * See the comment in 'base-nova/include/nova/native_thread.h'.
	 */
	auto manual_rcv_sel_for_myself = [&]
	{
		addr_t sel = Receive_window::INVALID_INDEX;
		if (myself)
			myself->with_native_thread([&] (Native_thread &nt) {
				sel = nt.client_rcv_sel; });
		return sel;
	};
	addr_t const manual_rcv_sel = manual_rcv_sel_for_myself();

	/* if we can't setup receive window, die in order to recognize the issue */
	if (!rcv_window.prepare_rcv_window(utcb, manual_rcv_sel))
		/* printf doesn't work here since for IPC also rcv_prepare* is used */
		return Rpc_exception_code(Rpc_exception_code::INVALID_OBJECT);

	/* establish the mapping via a portal traversal */
	uint8_t res = Nova::call(dst.local_name());

	if (res != Nova::NOVA_OK)
		/* If an error occurred, reset word&item count (not done by kernel). */
		utcb.set_msg_word(0);

	/* track potentially received caps and invalidate unused caps slots */
	rcv_window.post_ipc(utcb, manual_rcv_sel);

	if (res != Nova::NOVA_OK)
		return Rpc_exception_code(Rpc_exception_code::INVALID_OBJECT);

	/* handle malformed reply from a server */
	if (utcb.msg_words() < 1)
		return Rpc_exception_code(Rpc_exception_code::INVALID_OBJECT);

	return Rpc_exception_code((int)copy_utcb_to_msgbuf(utcb, rcv_window, rcv_msg));
}


/********************
 ** Receive_window **
 ********************/

void Receive_window::rcv_pt_sel(Native_capability &cap)
{
	if (_rcv_pt_sel_cnt >= _rcv_pt_sel_max) {
		cap = Native_capability();
		return;
	}

	/* return only received or translated caps */
	cap = Capability_space::import(_rcv_pt_sel[_rcv_pt_sel_cnt++].sel);
}


bool Receive_window::rcv_invalid() const
{
	return _rcv_pt_base == Capability_space::INVALID_INDEX;
}


Receive_window::Receive_cleanup_result Receive_window::rcv_cleanup(bool     const keep,
                                                                   uint16_t const new_max)
{
	/* mark used mapped capabilities as used to prevent freeing */
	bool reinit = false;
	for (unsigned i = 0; i < _rcv_pt_sel_cnt; i++) {
		if (!_rcv_pt_sel[i].del)
			continue;

		/* should never happen */
		if (_rcv_pt_sel[i].sel < _rcv_pt_base ||
			(_rcv_pt_sel[i].sel >= _rcv_pt_base + MAX_CAP_ARGS))
				return Receive_cleanup_result::OUT_OF_BOUNDS;

		_rcv_pt_cap_free [_rcv_pt_sel[i].sel - _rcv_pt_base] = USED_CAP;

		reinit = true;
	}

	/* if old receive window was smaller, we need to re-init */
	for (unsigned i = 0; !reinit && i < new_max; i++)
		if (_rcv_pt_cap_free[i] == FREE_INVALID)
			reinit = true;

	_rcv_pt_sel_cnt = 0;
	_rcv_pt_sel_max = 0;

	/* we can keep the cap selectors if none was used */
	if (keep && !reinit) {
		for (unsigned i = 0; i < MAX_CAP_ARGS; i++) {
			/* revoke received caps which are unused */
			if (_rcv_pt_cap_free[i] == UNUSED_CAP)
				Nova::revoke(Nova::Obj_crd(_rcv_pt_base + i, 0), true);

			/* free rest of indexes if new_max is smaller then last window */
			if (i >= new_max && _rcv_pt_cap_free[i] == FREE_SEL)
				cap_map().remove(_rcv_pt_base + i, 0, false);
		}

		return Receive_cleanup_result::SELECTORS_KEPT;
	}

	/* decrease ref count if valid selector */
	for (unsigned i = 0; i < MAX_CAP_ARGS; i++) {
		if (_rcv_pt_cap_free[i] == FREE_INVALID)
			continue;
		cap_map().remove(_rcv_pt_base + i, 0, _rcv_pt_cap_free[i] != FREE_SEL);
	}

	return Receive_cleanup_result::REINIT_REQUIRED;
}


bool Receive_window::prepare_rcv_window(Nova::Utcb &utcb, addr_t rcv_window)
{
	/* open maximal translate window */
	utcb.crd_xlt = Nova::Obj_crd(0, ~0UL);

	/* use receive window if specified */
	if (rcv_window != INVALID_INDEX) {
		/* cleanup if receive window already used */
		if (!rcv_invalid()) rcv_cleanup(false);

		_rcv_pt_base = rcv_window;

		/* open receive window */
		utcb.crd_rcv = Nova::Obj_crd(_rcv_pt_base, _rcv_wnd_log2);
		return true;
	}

	/* allocate receive window if necessary, otherwise use old one */
	bool reinit_required = rcv_invalid();

	if (!reinit_required) {
		auto const rcv_result = rcv_cleanup(true, 1U << _rcv_wnd_log2);

		if (rcv_result == Receive_cleanup_result::OUT_OF_BOUNDS) {
			_rcv_pt_base = INVALID_INDEX;
			/* no mappings can be received */
			utcb.crd_rcv = Nova::Obj_crd();
			return false;
		}

		reinit_required = rcv_result == Receive_cleanup_result::REINIT_REQUIRED;
	}

	if (reinit_required) {
		_rcv_pt_base = cap_map().insert(_rcv_wnd_log2);

		if (_rcv_pt_base == INVALID_INDEX) {
			/* no mappings can be received */
			utcb.crd_rcv = Nova::Obj_crd();
			return false;
		}
	}

	/* open receive window */
	utcb.crd_rcv = Nova::Obj_crd(_rcv_pt_base, _rcv_wnd_log2);
	return true;
}
