/*
 * \brief   Platform implementation specific for hw
 * \author  Martin Stein
 * \author  Stefan Kalkowski
 * \date    2011-12-21
 */

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

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

/* core includes */
#include <boot_modules.h>
#include <core_log.h>
#include <hw/memory_region.h>
#include <map_local.h>
#include <platform.h>
#include <platform_pd.h>
#include <hw/page_flags.h>
#include <hw/util.h>
#include <kernel/kernel.h>
#include <translation_table.h>
#include <kernel/cpu.h>

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

using namespace Genode;


/**************
 ** Platform **
 **************/

Hw::Boot_info<Board::Boot_info> const & Platform::_boot_info() {
	return *reinterpret_cast<Hw::Boot_info<Board::Boot_info>*>(Hw::Mm::boot_info().base); }

addr_t Platform::mmio_to_virt(addr_t mmio) {
	return _boot_info().mmio_space.virt_addr(mmio); }

addr_t Platform::core_page_table() {
	return (addr_t)_boot_info().table; }

Hw::Page_table::Allocator & Platform::core_page_table_allocator()
{
	using Allocator  = Hw::Page_table::Allocator;
	using Array      = Allocator::Array<Hw::Page_table::CORE_TRANS_TABLE_COUNT>;
	addr_t virt_addr = Hw::Mm::core_page_tables().base + sizeof(Hw::Page_table);
	return *unmanaged_singleton<Array::Allocator>(_boot_info().table_allocator,
	                                              virt_addr);
}

void Platform::_init_io_mem_alloc()
{
	/* add entire adress space minus the RAM memory regions */
	_io_mem_alloc.add_range(0, ~0x0UL);
	_boot_info().ram_regions.for_each([this] (Hw::Memory_region const &r) {
		_io_mem_alloc.remove_range(r.base, r.size); });
};


Hw::Memory_region_array const & Platform::_core_virt_regions()
{
	return *unmanaged_singleton<Hw::Memory_region_array>(
	Hw::Memory_region(stack_area_virtual_base(), stack_area_virtual_size()));
}


addr_t Platform::core_phys_addr(addr_t virt)
{
	addr_t ret = 0;
	_boot_info().elf_mappings.for_each([&] (Hw::Mapping const & m)
	{
		if (virt >= m.virt() && virt < (m.virt() + m.size()))
			ret = (virt - m.virt()) + m.phys();
	});
	return ret;
}


addr_t Platform::_rom_module_phys(addr_t virt)
{
	Hw::Mapping m = _boot_info().boot_modules;
	if (virt >= m.virt() && virt < (m.virt() + m.size()))
		return (virt - m.virt()) + m.phys();
	return 0;
}


void Platform::_init_platform_info()
{
	unsigned const  pages    = 1;
	size_t   const  rom_size = pages << get_page_size_log2();
	void           *phys_ptr = nullptr;
	void           *virt_ptr = nullptr;
	const char     *rom_name = "platform_info";

	if (!ram_alloc().alloc(get_page_size(), &phys_ptr)) {
		error("could not setup platform_info ROM - ram allocation error");
		return;
	}

	if (!region_alloc().alloc(rom_size, &virt_ptr)) {
		error("could not setup platform_info ROM - region allocation error");
		ram_alloc().free(phys_ptr);
		return;
	}

	addr_t const phys_addr = reinterpret_cast<addr_t>(phys_ptr);
	addr_t const virt_addr = reinterpret_cast<addr_t>(virt_ptr);

	if (!map_local(phys_addr, virt_addr, pages, Hw::PAGE_FLAGS_KERN_DATA)) {
		error("could not setup platform_info ROM - map error");
		region_alloc().free(virt_ptr);
		ram_alloc().free(phys_ptr);
		return;
	}

	Genode::Xml_generator xml(reinterpret_cast<char *>(virt_addr),
	                          rom_size, rom_name, [&] ()
	{
		xml.node("kernel", [&] () { xml.attribute("name", "hw"); });
		_init_additional_platform_info(xml);
	});

	if (!unmap_local(virt_addr, pages)) {
		error("could not setup platform_info ROM - unmap error");
		return;
	}

	region_alloc().free(virt_ptr);

	_rom_fs.insert(
		new (core_mem_alloc()) Rom_module(phys_addr, rom_size, rom_name));
}


Platform::Platform()
:
	_io_mem_alloc(&core_mem_alloc()),
	_io_port_alloc(&core_mem_alloc()),
	_irq_alloc(&core_mem_alloc())
{
	struct Kernel_resource : Exception { };

	_core_mem_alloc.virt_alloc().add_range(Hw::Mm::core_heap().base,
	                                       Hw::Mm::core_heap().size);
	_core_virt_regions().for_each([this] (Hw::Memory_region const & r) {
		_core_mem_alloc.virt_alloc().remove_range(r.base, r.size); });
	_boot_info().elf_mappings.for_each([this] (Hw::Mapping const & m) {
		_core_mem_alloc.virt_alloc().remove_range(m.virt(), m.size()); });
	_boot_info().ram_regions.for_each([this] (Hw::Memory_region const & region) {
		_core_mem_alloc.phys_alloc().add_range(region.base, region.size); });

	_init_io_port_alloc();

	/* make all non-kernel interrupts available to the interrupt allocator */
	for (unsigned i = 0; i < Board::Pic::NR_OF_IRQ; i++) {
		bool kernel_resource = false;
		Kernel::cpu_pool().for_each_cpu([&] (Kernel::Cpu & cpu) {
			if (i == cpu.timer().interrupt_id()) {
				kernel_resource = true;
			}
		});
		if (i == Board::Pic::IPI) {
			kernel_resource = true;
		}
		if (kernel_resource) {
			continue;
		}
		_irq_alloc.add_range(i, 1);
	}

	_init_io_mem_alloc();
	_init_rom_modules();
	_init_platform_info();

	/* core log as ROM module */
	{
		void * core_local_ptr = nullptr;
		void * phys_ptr       = nullptr;
		unsigned const pages  = 1;
		size_t const log_size = pages << get_page_size_log2();

		ram_alloc().alloc_aligned(log_size, &phys_ptr, get_page_size_log2());
		addr_t const phys_addr = reinterpret_cast<addr_t>(phys_ptr);

		/* let one page free after the log buffer */
		region_alloc().alloc_aligned(log_size, &core_local_ptr, get_page_size_log2());
		addr_t const core_local_addr = reinterpret_cast<addr_t>(core_local_ptr);

		map_local(phys_addr, core_local_addr, pages);
		memset(core_local_ptr, 0, log_size);

		_rom_fs.insert(new (core_mem_alloc()) Rom_module(phys_addr, log_size,
		                                                 "core_log"));

		init_core_log(Core_log_range { core_local_addr, log_size } );
	}

	log(_rom_fs);
}


/****************************************
 ** Support for core memory management **
 ****************************************/

bool Genode::map_local(addr_t from_phys, addr_t to_virt, size_t num_pages,
                       Page_flags flags)
{
	Platform_pd &pd = Kernel::core_pd().platform_pd();
	return pd.insert_translation(to_virt, from_phys,
	                             num_pages * get_page_size(), flags);
}


bool Genode::unmap_local(addr_t virt_addr, size_t num_pages)
{
	Platform_pd &pd = Kernel::core_pd().platform_pd();
	pd.flush(virt_addr, num_pages * get_page_size());
	return true;
}


bool Mapped_mem_allocator::_map_local(addr_t virt_addr, addr_t phys_addr,
                                      unsigned size) {
	return ::map_local(phys_addr, virt_addr, size / get_page_size()); }


bool Mapped_mem_allocator::_unmap_local(addr_t virt_addr, addr_t,
                                        unsigned size) {
	return ::unmap_local(virt_addr, size / get_page_size()); }
