/*
 * \brief  Fiasco.oc platform-specific capability allocation
 * \author Stefan Kalkowski
 * \date   2011-01-13
 */

/*
 * Copyright (C) 2011-2012 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/capability.h>
#include <util/misc_math.h>

/* core includes */
#include <cap_session_component.h>
#include <platform.h>

namespace Fiasco {
#include <l4/sys/consts.h>
#include <l4/sys/debugger.h>
#include <l4/sys/factory.h>
#include <l4/sys/types.h>
}

using namespace Genode;


/*****************************
 **  Cap_session_component  **
 *****************************/

Native_capability Cap_session_component::alloc(Cap_session_component *session,
                                               Native_capability ep)
{
	Native_capability cap;

	if (!ep.valid()) {
		PWRN("Invalid cap!");
		return Native_capability();
	}

	/*
	 * maybe someone tries to fool us, proof whether cap exists in cap tree.
	 *
	 * Actually we should proof whether both capability selectors
	 * point to the same object, but this isn't possible in fiasco.oc.
	 */
	Capability_node *n = Capability_tree::tree()->find_by_badge(ep.local_name());
	if (!n) {
		PWRN("Unknown capability!");
		return cap;
	}


	try {
		using namespace Fiasco;

		/*
		 * Allocate new badge, and ipc-gate and set badge as gate-label
		 */
		unsigned long badge = Badge_allocator::allocator()->alloc();
		Native_thread gate  = Capability_allocator::allocator()->alloc();
		l4_msgtag_t   tag   = l4_factory_create_gate(L4_BASE_FACTORY_CAP,
		                                             gate,
		                                             n->pt()->native_thread(),
		                                             badge);
		if (l4_msgtag_has_error(tag)) {
			PERR("l4_factory_create_gate failed!");
			Capability_allocator::allocator()->free(gate);
			Badge_allocator::allocator()->free(badge);
			return cap;
		} else
			/* set debugger-name of ipc-gate to thread's name */
			Fiasco::l4_debugger_set_object_name(gate, n->pt()->name());

		/*
		 * Create new node in capability tree.
		 *
		 * TODO: don't use core_mem_alloc, but a session-specific allocator
		 */
		Capability_node *new_node = new (platform()->core_mem_alloc())
		                            Capability_node(badge, session, n->pt(), gate);
		Capability_tree::tree()->insert(new_node);
		cap = Native_capability(gate, badge);

	} catch (Badge_allocator::Out_of_badges) {}

	return cap;
}


Native_capability Cap_session_component::alloc(Native_capability ep)
{
	return Cap_session_component::alloc(this, ep);
}


void Cap_session_component::free(Native_capability cap)
{
	using namespace Fiasco;

	Capability_node *n = Capability_tree::tree()->find_by_badge(cap.local_name());
	if (!n)
		return;

	/*
	 * check whether this cap_session has created the capability to delete.
	 *
	 * Actually we should proof whether both capability selectors
	 * point to the same object, but this isn't possible in fiasco.oc.
	 */
	if (n->cap_session() != this)
		return;

	Capability_tree::tree()->remove(n);
	Badge_allocator::allocator()->free(n->badge());
	l4_msgtag_t tag = l4_task_unmap(L4_BASE_TASK_CAP,
	                                l4_obj_fpage(cap.dst(), 0, 0),
	                                L4_FP_ALL_SPACES | L4_FP_DELETE_OBJ);
	if (l4_msgtag_has_error(tag))
		PERR("destruction of ipc-gate %lx failed!", (unsigned long) cap.dst());

	/* free explicilty allocated cap-selector */
	Capability_allocator::allocator()->free(n->gate());

	destroy(platform_specific()->core_mem_alloc(), n);
}


/***********************
 **  Badge Allocator  **
 ***********************/

Badge_allocator::Badge_allocator()
: _id_alloc(platform_specific()->core_mem_alloc())
{
	_id_alloc.add_range(BADGE_OFFSET, BADGE_RANGE);
}


unsigned long Badge_allocator::alloc()
{
	Lock::Guard lock_guard(_lock);

	void *badge;
	if (_id_alloc.alloc(BADGE_OFFSET, &badge))
		return (unsigned long) badge;
	throw Out_of_badges();
}


void Badge_allocator::free(unsigned long badge)
{
	Lock::Guard lock_guard(_lock);

	if (badge < BADGE_RANGE)
		_id_alloc.free((void*)(badge & BADGE_MASK), BADGE_OFFSET);
}


Badge_allocator* Badge_allocator::allocator()
{
	static Badge_allocator alloc;
	return &alloc;
}


/***********************
 **  Capability_node  **
 ***********************/

Capability_node::Capability_node(unsigned long          badge,
                                 Cap_session_component *cap_session,
                                 Platform_thread       *pt,
                                 Native_thread          gate)
: _badge(badge), _cap_session(cap_session), _pt(pt), _gate(gate) {}


bool Capability_node::higher(Capability_node *n)
{
	return n->_badge > _badge;
}


Capability_node* Capability_node::find_by_badge(unsigned long badge)
{
	if (_badge == badge) return this;

	Capability_node *n = Avl_node<Capability_node>::child(badge > _badge);
	return n ? n->find_by_badge(badge) : 0;
}


/***********************
 **  Capability_tree  **
 ***********************/

void Capability_tree::insert(Avl_node<Capability_node> *node)
{
	Lock::Guard lock_guard(_lock);

	Avl_tree<Capability_node>::insert(node);
}


void Capability_tree::remove(Avl_node<Capability_node> *node)
{
	Lock::Guard lock_guard(_lock);

	Avl_tree<Capability_node>::remove(node);
}


Capability_node* Capability_tree::find_by_badge(unsigned long badge)
{
	Lock::Guard lock_guard(_lock);

	return first()->find_by_badge(badge);
}


Capability_tree* Capability_tree::tree()
{
	static Capability_tree _tree;
	return &_tree;
}
