/*
 * \brief  Broadwell global graphics translation table
 * \author Josef Soentgen
 * \date   2017-03-15
 */

/*
 * Copyright (C) 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 _GLOBAL_GTT_H_
#define _GLOBAL_GTT_H_

/* Genode includes */
#include <util/bit_array.h>

/* local includes */
#include <mmio.h>


namespace Igd {

	struct Ggtt;
}

/*
 * Global Graphics Translation Table
 */
class Igd::Ggtt
{
	public:

		struct Could_not_find_free    : Genode::Exception { };
		struct Offset_out_of_range    : Genode::Exception { };
		struct Wrong_graphics_address : Genode::Exception { };

		using Offset = Igd::uint32_t;

		struct Mapping
		{
			Genode::Dataspace_capability cap { };
			Offset                       offset;
			Igd::addr_t                  vaddr = 0;

			enum { INVALID_OFFSET = ~0u - 1, };

			Mapping() : offset(INVALID_OFFSET) { }

			Mapping(Genode::Dataspace_capability cap, Offset offset)
			: cap(cap), offset(offset) { }

			virtual ~Mapping() { }
		};

	private:

		/*
		 * IHD-OS-BDW-Vol 5-11.15 p. 44
		 */
		struct Page_table_entry : Genode::Register<64>
		{
			struct Physical_adress : Bitfield<12, 36> { };
			struct Present         : Bitfield< 0,  1> { };
		};

		struct Space_allocator
		{
			Genode::Bit_array<1024*1024> _array { };
			size_t                       _used  { 0 };

			bool allocated(addr_t const index) const
			{
				return _array.get(index, 1);
			}

			void set(addr_t const index)
			{
				_used++;
				_array.set(index, 1);
			}

			void clear(addr_t const index)
			{
				_used--;
				_array.clear(index, 1);
			}

			bool word_free(size_t const word) const
			{
				try {
					size_t const bits = sizeof(size_t) * 8;
					size_t const index = word * sizeof(size_t);
					return !_array.get(index, bits);
				} catch (...) { }

				return false;
			}

			size_t used() const { return _used; }

		} _space { };

		Igd::Mmio &_mmio;

		addr_t const _base;
		size_t const _size;
		size_t const _num_entries;

		uint64_t *_entries;

		addr_t const _scratch_page;
		size_t const _aperture_size;
		size_t const _aperture_entries;

		void _insert_pte(addr_t pa, Offset offset)
		{
			Page_table_entry::access_t pte = 0;
			Page_table_entry::Physical_adress::set(pte, pa >> 12);
			Page_table_entry::Present::set(pte, 1);

			_entries[offset] = pte;
			_mmio.flush_gfx_tlb();
			Igd::wmb();
		}

	public:

		/**
		 * Constructor
		 *
		 * \param mmio           reference to Igd::Mmio object
		 * \param base           virtual base address of GGTT start
		 * \param size           size of GGTT in bytes
		 * \param aperture_size  size of the aperture in bytes
		 * \param scratch_page   physical address of the scratch page
		 * \param fb_size        size of the framebuffer region in the GTT in bytes
		 */
		Ggtt(Igd::Mmio &mmio, addr_t base, size_t size,
		     size_t aperture_size, addr_t scratch_page,
		     size_t fb_size)
		:
			_mmio(mmio),
			_base(base),
			_size(size),
			/* make the last entry/page unavailable */
			_num_entries((_size / 8) - 1),
			_entries((uint64_t*)_base),
			_scratch_page(scratch_page),
			_aperture_size(aperture_size),
			_aperture_entries(_aperture_size / PAGE_SIZE)
		{
			/* reserve GGTT region occupied by the framebuffer */
			size_t const fb_entries = fb_size / PAGE_SIZE;
			for (size_t i = 0; i < fb_entries; i++) {
				_space.set(i);
			}
			for (size_t i = fb_entries; i < _num_entries; i++) {
				_insert_pte(_scratch_page, i);
			}
		}

		/**
		 * Insert page into GGTT
		 *
		 * \param pa      physical address
		 * \param offset  offset of GTT entry
		 *
		 * \throw Offset_out_of_range
		 */
		void insert_pte(addr_t pa, Offset offset)
		{
			if (offset > _num_entries) { throw Offset_out_of_range(); }

			if (_space.allocated(offset)) {
				Genode::warning(__func__, " offset:", offset, " already used");
			}

			_space.set(offset);
			_insert_pte(pa, offset);
		}

		/**
		 * Remove page from GTT
		 *
		 * \param offset  offset of GTT entry
		 */
		void remove_pte(Offset offset)
		{
			if (!_space.allocated(offset)) {
				Genode::warning(__func__, " offset:", offset, " was not used");
				return;
			}

			_space.clear(offset);
			_insert_pte(_scratch_page, offset);
		}

		/**
		 * Remove range of pages from GTT
		 *
		 * \param start  offset of the first page in the GGTT
		 * \param num    number of pages
		 */
		void remove_pte_range(Offset const start, Offset const num)
		{
			Offset const end = start + num;
			for (Offset offset = start; offset < end; offset++) {
				remove_pte(offset);
			}
		}

		/**
		 * Find free GGTT entries
		 *
		 * \param num       number of continuous entries
		 * \param aperture  request entries mappable by CPU
		 *
		 * \return start offset of free entry range
		 *
		 * \throw Could_not_find_free
		 */
		Offset find_free(size_t /* num */, bool aperture = false) const
		{
			size_t const start_index = aperture ?                 0 : _aperture_entries;
			size_t const end_index   = aperture ? _aperture_entries : _num_entries;

			for (size_t i = start_index; i < end_index; i++) {
				bool const f = _space.word_free(i);
				if (f) { return i * (sizeof(size_t)); }
			}

			throw Could_not_find_free();
		}

		/**
		 * Get GGTT entry offset from GMADDR
		 *
		 * \param gmaddr  graphics memory address
		 *
		 * \return GGTT offset of GMADDR
		 *
		 * \throw Wrong_graphics_address
		 */
		Offset offset(addr_t const gmaddr) const
		{
			if (gmaddr & 0xfffu)      { throw Wrong_graphics_address(); }
			if (gmaddr > 0xffffffffu) { throw Wrong_graphics_address(); }
			return gmaddr / PAGE_SIZE;
		}

		/**
		 * Get GMADDR for GGTT entry offset
		 *
		 * \param offset  offset of GGTT entry
		 *
		 * \return graphics memory address
		 *
		 * \throw Offset_out_of_range
		 */
		addr_t addr(Offset offset) const
		{
			if (offset > _num_entries) { throw Offset_out_of_range(); }
			return offset * PAGE_SIZE;
		}

		/**
		 * Get total number of entries in GGTT
		 *
		 * \return number of entries
		 */
		size_t entries() const { return _num_entries; }

		/**
		 * Get scratch page address
		 *
		 * \return physical address of the scratch page
		 */
		addr_t scratch_page() const { return _scratch_page; }

		/*********************
		 ** Debug interface **
		 *********************/

		void dump(bool const dump_entries = false,
		          size_t const limit = 0u,
		          size_t const start = 0u) const
		{
			using namespace Genode;

			log("GGTT");
			log("  vaddr:", Hex(_base), " size:", Hex(_size), " entries:", _num_entries,
			    " used:", _space.used(), " aperture_size:", Hex(_aperture_size));
			log("  scratch_page:", Hex(_scratch_page), " (PA)");

			if (!dump_entries) { return; }

			log("  entries:");
			uint64_t *ggtt = (uint64_t*)_base;
			size_t const max = !limit ? _num_entries : limit > _num_entries ? _num_entries : limit;
			for (size_t i = start; i < start + max; i += 8) {
				log("  ", Hex(i, Hex::PREFIX, Hex::PAD), " ",
				    Hex(ggtt[i]),     " ", Hex(ggtt[i + 1]), " ",
				    Hex(ggtt[i + 2]), " ", Hex(ggtt[i + 3]), " ",
				    Hex(ggtt[i + 4]), " ", Hex(ggtt[i + 5]), " ",
				    Hex(ggtt[i + 6]), " ", Hex(ggtt[i + 7]));
			}
		}
};

#endif /* _GLOBAL_GTT_H_ */
