/*
 * \brief  NOVA-specific convenience functions
 * \author Norman Feske
 * \author Sebastian Sumpf
 * \author Alexander Boettcher
 * \date   2010-01-19
 */

/*
 * Copyright (C) 2010-2013 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.
 */

#ifndef _NOVA_UTIL_H_
#define _NOVA_UTIL_H_

/* Genode includes */
#include <base/printf.h>

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

/* local includes */
#include <echo.h>
#include <util.h>

enum { verbose_local_map = false };


/**
 * Establish a mapping
 *
 * \param utcb       UTCB of the calling EC
 * \param src_crd    capability range descriptor of source
 *                   resource to map locally
 * \param dst_crd    capability range descriptor of mapping
 *                   target
 * \param kern_pd    Whether to map the items from the kernel or from core
 * \param dma_mem    Whether the memory is usable for DMA or not
 *
 * This functions sends a message from the calling EC to the echo EC.
 * The calling EC opens a receive window and the echo EC creates a transfer
 * item of the message and replies. The kernel will map during the reply
 * from the echo EC to the calling EC.
 */
static int map_local(Nova::Utcb *utcb, Nova::Crd src_crd, Nova::Crd dst_crd,
                     bool kern_pd = false, bool dma_mem = false)
{
	/* open receive window at current EC */
	utcb->crd_rcv = dst_crd;

	/* tell echo thread what to map */
	utcb->msg[0] = src_crd.value();
	utcb->msg[1] = 0;
	utcb->msg[2] = kern_pd;
	utcb->msg[3] = dma_mem;
	utcb->set_msg_word(4);

	/* establish the mapping via a portal traversal during reply phase */
	Nova::uint8_t res = Nova::call(echo()->pt_sel());
	if (res != Nova::NOVA_OK || utcb->msg_words() != 1 || !utcb->msg[0] ||
	    utcb->msg_items() != 1) {
		PERR("Failure - map_local 0x%lx:%lu:%u->0x%lx:%lu:%u - call result=%x"
		     " utcb=%x:%x:%lx !!! utcb=%p kern=%u",
		     src_crd.addr(), src_crd.order(), src_crd.type(),
		     dst_crd.addr(), dst_crd.order(), dst_crd.type(), res,
		     utcb->msg_items(), utcb->msg_words(), utcb->msg[0], utcb, kern_pd);
		return res > 0 ? res : -1;
	}
	/* clear receive window */
	utcb->crd_rcv = 0;

	return 0;
}


static inline int unmap_local(Nova::Crd crd, bool self = true) {
	return Nova::revoke(crd, self); }

inline int map_local_phys_to_virt(Nova::Utcb *utcb, Nova::Crd src,
                                  Nova::Crd dst) {
	return map_local(utcb, src, dst, true); }

inline int map_local_one_to_one(Nova::Utcb *utcb, Nova::Crd crd) {
	return map_local(utcb, crd, crd, true); }


/**
 * Find least significant set bit in value
 */
inline unsigned char
lsb_bit(unsigned long const &value, unsigned char const shift = 0)
{
	unsigned long const scan  = value >> shift;
	if (scan == 0) return 0;

	unsigned char pos = __builtin_ctzl(scan);
	unsigned char res = shift ? pos + shift : pos;
	return res;
}

/**
 * Remap pages in the local address space
 *
 * \param utcb        UTCB of the main thread
 * \param from_start  physical source address
 * \param to_start    local virtual destination address
 * \param num_pages   number of pages to map
 */
inline int map_local(Nova::Utcb *utcb,
                     Genode::addr_t from_start, Genode::addr_t to_start,
                     Genode::size_t num_pages,
                     Nova::Rights const &permission,
                     bool kern_pd = false, bool dma_mem = false)
{
	if (verbose_local_map)
		Genode::printf("::map_local: from %lx to %lx, %zd pages from kernel %u\n",
		               from_start, to_start, num_pages, kern_pd);

	using namespace Nova;
	using namespace Genode;

	size_t const size = num_pages << get_page_size_log2();

	addr_t const from_end = from_start + size;
	addr_t const to_end   = to_start   + size;

	for (addr_t offset = 0; offset < size; ) {

		addr_t const from_curr = from_start + offset;
		addr_t const to_curr   = to_start   + offset;

		/*
		 * The common alignment corresponds to the number of least significant
		 * zero bits in both addresses.
		 */
		addr_t const common_bits = from_curr | to_curr;

		/* find least set bit in common bits */
		size_t order = lsb_bit(common_bits, get_page_size_log2());

		/* look if flexpage fits into both 'from' and 'to' address range */
		if ((from_end - from_curr) < (1UL << order))
			order = log2(from_end - from_curr);

		if ((to_end - to_curr) < (1UL << order))
			order = log2(to_end - to_curr);

		if (verbose_local_map)
			Genode::printf("::map_local: order %zx %lx:%lx %lx:%lx\n",
			               order, from_curr, from_end, to_curr, to_end);

		int const res = map_local(utcb,
		                          Mem_crd((from_curr >> 12), order - get_page_size_log2(), permission),
		                          Mem_crd((to_curr   >> 12), order - get_page_size_log2(), permission),
		                          kern_pd, dma_mem);
		if (res) return res;

		/* advance offset by current flexpage size */
		offset += (1UL << order);
	}
	return 0;
}


/**
 * Unmap pages from the local address space
 *
 * \param utcb        UTCB of the main thread
 * \param start       local virtual address
 * \param num_pages   number of pages to unmap
 * \param self        unmap from this pd or solely from other pds
 * \param self        map from this pd or solely from other pds
 * \param rights      rights to be revoked, default: all rwx
 */
inline void unmap_local(Nova::Utcb *utcb, Genode::addr_t start,
                        Genode::size_t num_pages,
                        bool const self = true,
                        Nova::Rights const rwx = Nova::Rights(true, true, true))
{
	using namespace Nova;
	using namespace Genode;

	Genode::addr_t base = start >> get_page_size_log2();

	if (start & (get_page_size() - 1)) {
		PERR("unmap failed - unaligned address specified");
		return;
	}

	if (verbose_local_map)
		PINF("Unmapping local: range 0x%lx+0x%zx", base, num_pages);

	while (num_pages) {
		unsigned char const base_bit  = lsb_bit(base);
		unsigned char const order_bit = min(log2(num_pages), 31U);
		unsigned char const order     = min(order_bit, base_bit);

		Mem_crd const crd(base, order, rwx);

		if (verbose_local_map)
			PINF("Unmapping local:       0x%lx+0x%lx", crd.base(),
			     1UL << crd.order());

		unmap_local(crd, self);

		num_pages -= 1UL << order;
		base      += 1UL << order;
	}
}


#endif /* _NOVA_UTIL_H_ */
