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

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

#ifndef _CORE__INCLUDE__NOVA_UTIL_H_
#define _CORE__INCLUDE__NOVA_UTIL_H_

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

/* core includes */
#include <types.h>
#include <util.h>


/**
 * Return boot CPU number. It is required if threads in core should be placed
 * on the same CPU as the main thread.
 */
inline Genode::addr_t boot_cpu()
{
	/**
	 * Initial value of ax and di register, saved by the crt0 startup code
	 * and SOLELY VALID in 'core' !!!
	 *
	 * For x86_32 - __initial_ax contains the number of the boot CPU.
	 * For x86_64 - __initial_di contains the number of the boot CPU.
	 */
	extern Genode::addr_t __initial_ax;
	extern Genode::addr_t __initial_di;

	return (sizeof(void *) > 4) ? __initial_di : __initial_ax;
}

/**
 * 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
 */
static int map_local(Genode::addr_t const pd, Nova::Utcb &utcb,
                     Nova::Crd const src_crd, Nova::Crd const dst_crd,
                     bool const kern_pd = false, bool const dma_mem = false,
                     bool const write_combined = false)
{
	/* asynchronously map capabilities */
	utcb.set_msg_word(0);

	/* ignore return value as one item always fits into the utcb */
	bool const ok = utcb.append_item(src_crd, 0, kern_pd, false, false,
	                                 dma_mem, write_combined);
	(void)ok;

	Nova::uint8_t res = Nova::delegate(pd, pd, dst_crd);
	if (res != Nova::NOVA_OK) {

		using Hex = Genode::Hex;
		error("map_local failed ",
		      Hex(src_crd.addr()), ":", Hex(src_crd.order()), ":", Hex(src_crd.type()), "->",
		      Hex(dst_crd.addr()), ":", Hex(dst_crd.order()), ":", Hex(dst_crd.type()), " - ",
		      "result=", Hex(res), " "
		      "msg=",    Hex(utcb.msg_items()), ":",
		                 Hex(utcb.msg_words()), ":",
		                 Hex(utcb.msg()[0]), " !!! "
		      "utcb=",   &utcb, " "
		      "kern=",   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 const src,
                                  Nova::Crd const dst, Genode::addr_t const pd)
{
	return map_local(pd, utcb, src, dst, true);
}

inline int map_local_one_to_one(Nova::Utcb &utcb, Nova::Crd const crd,
                                Genode::addr_t const pd)
{
	return map_local(pd, 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 = (unsigned char)__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(Genode::addr_t const pd, 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,
                     bool write_combined = false)
{
	using namespace Nova;
	using namespace Genode;

	using Genode::PAGE_SIZE_LOG2;

	size_t const size = num_pages << 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, 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, 0u);

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

		if (order >= sizeof(void *)*8)
			return 1;

		int const res = map_local(pd, utcb,
		                          Mem_crd((from_curr >> 12), order - PAGE_SIZE_LOG2, permission),
		                          Mem_crd((to_curr   >> 12), order - PAGE_SIZE_LOG2, permission),
		                          kern_pd, dma_mem, write_combined);
		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 &, 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 >> Genode::PAGE_SIZE_LOG2;

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

	while (num_pages) {
		uint8_t const base_bit  = lsb_bit(base);
		uint8_t const order_bit = min(log2(num_pages, 0), uint8_t(31));
		uint8_t const order     = min(order_bit, base_bit);

		Mem_crd const crd(base, order, rwx);

		unmap_local(crd, self);

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


inline Nova::uint8_t syscall_retry(Core::Pager_object &pager, auto const &fn)
{
	Nova::uint8_t res;
	do {
		res = fn();
	} while (res == Nova::NOVA_PD_OOM && Nova::NOVA_OK == pager.handle_oom());

	return res;
}


inline Nova::uint8_t async_map(Core::Pager_object &pager,
                               Genode::addr_t const source_pd,
                               Genode::addr_t const target_pd,
                               Nova::Obj_crd const &source_initial_caps,
                               Nova::Obj_crd const &target_initial_caps,
                               Nova::Utcb &utcb)
{
	/* asynchronously map capabilities */
	utcb.set_msg_word(0);

	/* ignore return value as one item always fits into the utcb */
	bool const ok = utcb.append_item(source_initial_caps, 0);
	(void)ok;

	return syscall_retry(pager,
		[&] {
			return Nova::delegate(source_pd, target_pd, target_initial_caps);
		});
}

inline Nova::uint8_t map_vcpu_portals(Core::Pager_object &pager,
                                      Genode::addr_t const source_exc_base,
                                      Genode::addr_t const target_exc_base,
                                      Nova::Utcb &utcb,
                                      Genode::addr_t const source_pd)
{
	using Nova::Obj_crd;
	using Nova::NUM_INITIAL_VCPU_PT_LOG2;

	Obj_crd const source_initial_caps(source_exc_base, NUM_INITIAL_VCPU_PT_LOG2);
	Obj_crd const target_initial_caps(target_exc_base, NUM_INITIAL_VCPU_PT_LOG2);

	return async_map(pager, source_pd, pager.pd_sel(),
	                 source_initial_caps, target_initial_caps, utcb);
}

inline Nova::uint8_t map_pagefault_portal(Core::Pager_object &pager,
                                          Genode::addr_t const source_exc_base,
                                          Genode::addr_t const target_exc_base,
                                          Genode::addr_t const target_pd,
                                          Nova::Utcb &utcb)
{
	using Nova::Obj_crd;
	using Nova::PT_SEL_PAGE_FAULT;

	Genode::addr_t const source_pd = Core::platform_specific().core_pd_sel();

	Obj_crd const source_initial_caps(source_exc_base + PT_SEL_PAGE_FAULT, 0);
	Obj_crd const target_initial_caps(target_exc_base + PT_SEL_PAGE_FAULT, 0);

	return async_map(pager, source_pd, target_pd,
	                 source_initial_caps, target_initial_caps, utcb);
}

inline Nova::Hip const &kernel_hip()
{
	/**
	 * Initial value of esp register, saved by the crt0 startup code.
	 * This value contains the address of the hypervisor information page.
	 */
	extern Genode::addr_t __initial_sp;
	return *reinterpret_cast<Nova::Hip const *>(__initial_sp);
}

#endif /* _CORE__INCLUDE__NOVA_UTIL_H_ */
