/*
 * \brief  DDE iPXE wrappers to C++ backend
 * \author Sebastian Sumpf
 * \author Josef Soentgen
 * \date   2010-10-21
 */

/*
 * Copyright (C) 2010-2017 Genode Labs GmbH
 *
 * This file is distributed under the terms of the GNU General Public License
 * version 2.
 */

/*
 * The build system picks up stdarg.h from iPXE instead of the one
 * provided by GCC. This header contains the FILE_LICENCE macro which
 * is defined by iPXE's compiler.h.
 */
#define FILE_LICENCE(x)

/* Genode includes */
#include <base/allocator_avl.h>
#include <base/env.h>
#include <base/log.h>
#include <base/slab.h>
#include <dataspace/client.h>
#include <io_mem_session/connection.h>
#include <io_port_session/connection.h>
#include <irq_session/connection.h>
#include <platform_device/client.h>
#include <platform_session/connection.h>
#include <rm_session/connection.h>
#include <region_map/client.h>
#include <timer_session/connection.h>
#include <util/misc_math.h>
#include <util/retry.h>
#include <util/reconstructible.h>

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

/* DDE support includes */
#include <dde_ipxe/support.h>


static Genode::Entrypoint *_global_ep;
static Genode::Env        *_global_env;
static Genode::Allocator  *_global_alloc;


void dde_support_init(Genode::Env &env, Genode::Allocator &alloc)
{
	_global_env   = &env;
	_global_ep    = &env.ep();
	_global_alloc = &alloc;
}


/**************************
 ** Initialization check **
 **************************/

extern "C" int dde_support_initialized(void)
{
	return !!_global_env;
}


/************
 ** printf **
 ************/

extern "C" void dde_vprintf(const char *format, va_list list)
{
	using namespace Genode;

	char buf[128] { };
	String_console(buf, sizeof(buf)).vprintf(format, list);
	log(Cstring(buf));
}


extern "C" void dde_printf(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	dde_vprintf(fmt, args);
	va_end(args);
}


/***********
 ** Timer **
 ***********/

static Genode::Constructible<Timer::Connection> _timer;


extern "C" void dde_udelay(unsigned long usecs)
{
	if (!_timer.constructed()) {
		_timer.construct(*_global_env);
	}

	/*
	 * This function is called only once during rdtsc calibration (usecs will be
	 * 10000, see dde.c 'udelay'.
	 */
	_timer->usleep(usecs);
}


/***************************
 ** locking/synchronizing **
 ***************************/

/**
 * DDE iPXE mutual exclusion lock
 */
static Genode::Lock _ipxe_lock;

extern "C" void dde_lock_enter(void) { _ipxe_lock.lock(); }
extern "C" void dde_lock_leave(void) { _ipxe_lock.unlock(); }


extern "C" void dde_mdelay(unsigned long msecs)
{
	if (!_timer.constructed()) {
		_timer.construct(*_global_env);
	}

	/*
	 * This function is only called while initializing the device
	 * and only be the same thread.
	 */
	_timer->msleep(msecs);
}


/******************
 ** PCI handling **
 ******************/

struct Pci_driver
{
	enum {
		PCI_BASE_CLASS_NETWORK = 0x02,
		CLASS_MASK             = 0xff0000,
		CLASS_NETWORK          = PCI_BASE_CLASS_NETWORK << 16
	};

	Genode::Region_map &_rm;

	Platform::Connection        _pci;
	Platform::Device_capability _cap;
	Platform::Device_capability _last_cap;

	struct Region
	{
		Genode::addr_t base;
		Genode::addr_t mapped_base;
	} _region;

	template <typename T>
	Platform::Device::Access_size _access_size(T t)
	{
		switch (sizeof(T)) {
		case 1:  return Platform::Device::ACCESS_8BIT;
		case 2:  return Platform::Device::ACCESS_16BIT;
		default: return Platform::Device::ACCESS_32BIT;
		}
	}

	void _bus_address(int *bus, int *dev, int *fun)
	{
		Platform::Device_client client(_cap);
		unsigned char b, d, f;
		client.bus_address(&b, &d, &f);

		*bus = b;
		*dev = d;
		*fun = f;
	}


	Pci_driver(Genode::Env &env, Genode::Region_map &rm)
	: _rm(rm), _pci(env) { }

	template <typename T>
	void config_read(unsigned int devfn, T *val)
	{
		Platform::Device_client client(_cap);
		*val = client.config_read(devfn, _access_size(*val));
	}

	template <typename T>
	void config_write(unsigned int devfn, T val)
	{
		Platform::Device_client client(_cap);

		_pci.with_upgrade([&] () {
			client.config_write(devfn, val, _access_size(val)); });
	}

	int first_device(int *bus, int *dev, int *fun)
	{
		_cap = _pci.with_upgrade([&] () {
			return _pci.first_device(CLASS_NETWORK, CLASS_MASK); });

		if (!_cap.valid())
			return -1;

		_bus_address(bus, dev, fun);
		return 0;
	}

	int next_device(int *bus, int *dev, int *fun)
	{
		int result = -1;

		_last_cap = _cap;
		_cap = _pci.with_upgrade([&] () {
			return _pci.next_device(_cap, CLASS_NETWORK, CLASS_MASK); });

		if (_cap.valid()) {
			_bus_address(bus, dev, fun);
			result = 0;
		}

		if (_last_cap.valid())
			_pci.release_device(_last_cap);

		return result;
	}

	Genode::addr_t alloc_dma_memory(Genode::size_t size)
	{
		try {
			using namespace Genode;

			size_t donate = size;

			Ram_dataspace_capability ram_cap =
				retry<Out_of_ram>(
					[&] () {
						return retry<Out_of_caps>(
							[&] () { return _pci.alloc_dma_buffer(size); },
							[&] () { _pci.upgrade_caps(2); });
					},
					[&] () {
						_pci.upgrade_ram(donate);
						donate = donate * 2 > size ? 4096 : donate * 2;
					});

			_region.mapped_base = _rm.attach(ram_cap);
			_region.base = Dataspace_client(ram_cap).phys_addr();

			return _region.mapped_base;
		} catch (...) {
			Genode::error("failed to allocate dma memory");
			return 0;
		}
	}

	Genode::addr_t virt_to_phys(Genode::addr_t virt) {
		return virt - _region.mapped_base + _region.base; }
};


static Pci_driver& pci_drv()
{
	static Pci_driver _pci_drv { *_global_env , _global_env->rm() };
	return _pci_drv;
}


extern "C" int dde_pci_first_device(int *bus, int *dev, int *fun) {
	return pci_drv().first_device(bus, dev, fun); }


extern "C" int dde_pci_next_device(int *bus, int *dev, int *fun) {
	return pci_drv().next_device(bus, dev, fun); }


extern "C" void dde_pci_readb(int pos, dde_uint8_t *val) {
	pci_drv().config_read(pos, val); }


extern "C" void dde_pci_readw(int pos, dde_uint16_t *val) {
	pci_drv().config_read(pos, val); }


extern "C" void dde_pci_readl(int pos, dde_uint32_t *val) {
	pci_drv().config_read(pos, val); }


extern "C" void dde_pci_writeb(int pos, dde_uint8_t val) {
	pci_drv().config_write(pos, val); }


extern "C" void dde_pci_writew(int pos, dde_uint16_t val) {
	pci_drv().config_write(pos, val); }


extern "C" void dde_pci_writel(int pos, dde_uint32_t val) {
	pci_drv().config_write(pos, val); }


/************************
 ** Interrupt handling **
 ************************/

struct Irq_handler
{
	Genode::Irq_session_client           irq;
	Genode::Signal_handler<Irq_handler>  dispatcher;

	typedef void (*irq_handler)(void*);

	irq_handler  handler;
	void        *priv;

	void handle()
	{
		handler(priv);
		irq.ack_irq();
	}

	Irq_handler(Genode::Entrypoint &ep, Genode::Irq_session_capability cap,
	            irq_handler handler, void *priv)
	:
		irq(cap), dispatcher(ep, *this, &Irq_handler::handle),
		handler(handler), priv(priv)
	{
		irq.sigh(dispatcher);

		/* intial ack so that we will receive IRQ signals */
		irq.ack_irq();
	}
};


static Genode::Constructible<Irq_handler> _irq_handler;


extern "C" int dde_interrupt_attach(void(*handler)(void *), void *priv)
{
	if (_irq_handler.constructed()) {
		Genode::error("Irq_handler already registered");
		return -1;
	}

	try {
		Platform::Device_client device(pci_drv()._cap);
		_irq_handler.construct(*_global_ep, device.irq(0), handler, priv);
	} catch (...) { return -1; }

	return 0;
}


/***************************************************
 ** Support for aligned and DMA memory allocation **
 ***************************************************/

enum { BACKING_STORE_SIZE = 1024 * 1024 };

struct Backing_store
{
	Genode::Allocator_avl _avl;

	Backing_store (Genode::Allocator &alloc) : _avl(&alloc)
	{
		Genode::addr_t base = pci_drv().alloc_dma_memory(BACKING_STORE_SIZE);
		/* add to allocator */
		_avl.add_range(base, BACKING_STORE_SIZE);
	}
};


static Genode::Allocator_avl& allocator()
{
	static Backing_store _instance { *_global_alloc };
	return _instance._avl;

}


extern "C" void *dde_dma_alloc(dde_size_t size, dde_size_t align,
                                    dde_size_t offset)
{
	void *ptr;
	if (allocator().alloc_aligned(size, &ptr, Genode::log2(align)).error()) {
		Genode::error("memory allocation failed in alloc_memblock ("
		              "size=",   size, " "
		              "align=",  Genode::Hex(align), " "
		              "offset=", Genode::Hex(offset), ")");
		return 0;
	}
	return ptr;
}


extern "C" void dde_dma_free(void *p, dde_size_t size) {
	allocator().free(p, size); }


extern "C" dde_addr_t dde_dma_get_physaddr(void *virt) {
	return pci_drv().virt_to_phys((Genode::addr_t)virt); }


/**************
 ** I/O port **
 **************/

static Genode::Constructible<Genode::Io_port_session_client> _io_port;


extern "C" void dde_request_io(dde_uint8_t virt_bar_ioport)
{
	using namespace Genode;

	if (_io_port.constructed()) { _io_port.destruct(); }

	Platform::Device_client device(pci_drv()._cap);
	Io_port_session_capability cap = device.io_port(virt_bar_ioport);

	_io_port.construct(cap);
}


extern "C" dde_uint8_t dde_inb(dde_addr_t port) {
	return _io_port->inb(port); }


extern "C" dde_uint16_t dde_inw(dde_addr_t port) {
	return _io_port->inw(port); }


extern "C" dde_uint32_t dde_inl(dde_addr_t port) {
	return _io_port->inl(port); }


extern "C" void dde_outb(dde_addr_t port, dde_uint8_t data) {
	_io_port->outb(port, data); }


extern "C" void dde_outw(dde_addr_t port, dde_uint16_t data) {
	_io_port->outw(port, data); }


extern "C" void dde_outl(dde_addr_t port, dde_uint32_t data) {
	_io_port->outl(port, data); }


/**********************
 ** Slab memory pool **
 **********************/

struct Slab_backend_alloc : public Genode::Allocator,
                            public Genode::Rm_connection,
                            public Genode::Region_map_client
{
	enum {
		VM_SIZE    = 2 * 1024 * 1024,
		BLOCK_SIZE =       64 * 1024,
		ELEMENTS   = VM_SIZE / BLOCK_SIZE,
	};

	Genode::addr_t                    _base;
	Genode::Ram_dataspace_capability  _ds_cap[ELEMENTS];
	int                               _index;
	Genode::Allocator_avl             _range;
	Genode::Ram_allocator            &_ram;

	bool _alloc_block()
	{
		using namespace Genode;

		if (_index == ELEMENTS) {
			error("slab backend exhausted!");
			return false;
		}

		try {
			_ds_cap[_index] = _ram.alloc(BLOCK_SIZE);
			Region_map_client::attach_at(_ds_cap[_index], _index * BLOCK_SIZE, BLOCK_SIZE, 0);
		} catch (...) { return false; }

		/* return base + offset in VM area */
		Genode::addr_t block_base = _base + (_index * BLOCK_SIZE);
		++_index;

		_range.add_range(block_base, BLOCK_SIZE);
		return true;
	}

	Slab_backend_alloc(Genode::Env &env, Genode::Region_map &rm,
	                   Genode::Ram_allocator &ram,
	                   Genode::Allocator &md_alloc)
	:
		Rm_connection(env),
		Region_map_client(Rm_connection::create(VM_SIZE)),
		_index(0), _range(&md_alloc), _ram(ram)
	{
		/* reserver attach us, anywere */
		_base = rm.attach(dataspace());
	}

	Genode::addr_t start() const { return _base; }
	Genode::addr_t end()   const { return _base + VM_SIZE - 1; }

	/*************************
	 ** Allocator interface **
	 *************************/

	bool alloc(Genode::size_t size, void **out_addr)
	{
		bool done = _range.alloc(size, out_addr);

		if (done)
			return done;

		done = _alloc_block();
		if (!done) {
			Genode::error("backend allocator exhausted");
			return false;
		}

		return _range.alloc(size, out_addr);
	}

	void           free(void *addr, Genode::size_t size) { _range.free(addr, size); }
	Genode::size_t overhead(Genode::size_t size) const   { return 0; }
	bool           need_size_for_free() const            { return false; }
};


class Slab_alloc : public Genode::Slab
{
	private:

		Genode::size_t const _object_size;

		static Genode::size_t _calculate_block_size(Genode::size_t object_size)
		{
			Genode::size_t const block_size = 8*object_size;
			return Genode::align_addr(block_size, 12);
		}

	public:

		Slab_alloc(Genode::size_t object_size, Slab_backend_alloc &allocator)
		:
			Slab(object_size, _calculate_block_size(object_size), 0, &allocator),
			_object_size(object_size)
		{ }

		Genode::addr_t alloc()
		{
			Genode::addr_t result;
			return (Slab::alloc(_object_size, (void **)&result) ? result : 0);
		}

		void free(void *ptr) { Slab::free(ptr, _object_size); }
};


struct Slab
{
	enum {
		SLAB_START_LOG2 = 5,  /* 32 B */
		SLAB_STOP_LOG2  = 13, /* 8 KiB */
		NUM_SLABS = (SLAB_STOP_LOG2 - SLAB_START_LOG2) + 1,
	};

	Genode::Constructible<Slab_alloc> _allocator[NUM_SLABS];

	Genode::addr_t _start;
	Genode::addr_t _end;

	Slab(Slab_backend_alloc &alloc)
	: _start(alloc.start()), _end(alloc.end())
	{
		for (unsigned i = 0; i < NUM_SLABS; i++) {
			_allocator[i].construct(1U << (SLAB_START_LOG2 + i), alloc);
		}
	}

	void *alloc(Genode::size_t size)
	{
		using namespace Genode;
		size += sizeof(Genode::addr_t);

		int msb = Genode::log2(size);

		if (size > (1U << msb))
			msb++;

		if (size < (1U << SLAB_START_LOG2))
			msb = SLAB_STOP_LOG2;

		if (msb > SLAB_STOP_LOG2)
			return 0;

		Genode::addr_t addr =  _allocator[msb - SLAB_START_LOG2]->alloc();
		if (!addr)
			return 0;

		*(Genode::addr_t*)addr = msb - SLAB_START_LOG2;
		addr += sizeof(Genode::addr_t);

		return (void*)addr;
	}

	void free(void *p)
	{
		Genode::addr_t *addr = ((Genode::addr_t *)p)-1;
		unsigned index = *(unsigned*)(addr);
		_allocator[index]->free((void*)(addr));
	}
};


static ::Slab& slab()
{
	static ::Slab_backend_alloc sb(*_global_env, _global_env->rm(),
	                               _global_env->ram(), *_global_alloc);
	static ::Slab s(sb);
	return s;
}


extern "C" void *dde_slab_alloc(dde_size_t size) {
	return slab().alloc(size); }


extern "C" void dde_slab_free(void *p) {
	slab().free(p); }


/****************
 ** I/O memory **
 ****************/

struct Io_memory
{
	Genode::Io_mem_session_client       _mem;
	Genode::Io_mem_dataspace_capability _mem_ds;

	Genode::addr_t                      _vaddr;

	Io_memory(Genode::Region_map &rm,
	          Genode::addr_t base, Genode::Io_mem_session_capability cap)
	:
		_mem(cap),
		_mem_ds(_mem.dataspace())
	{
		if (!_mem_ds.valid())
			throw Genode::Exception();

		_vaddr = rm.attach(_mem_ds);
		_vaddr |= base & 0xfff;
	}

	Genode::addr_t vaddr() const { return _vaddr; }
};


static Genode::Constructible<Io_memory> _io_mem;


extern "C" int dde_request_iomem(dde_addr_t start, dde_addr_t *vaddr)
{
	if (_io_mem.constructed()) {
		Genode::error("Io_memory already requested");
		return -1;
	}

	Platform::Device_client device(pci_drv()._cap);
	Genode::Io_mem_session_capability cap;

	Genode::uint8_t virt_iomem_bar = 0;
	for (unsigned i = 0; i < Platform::Device::NUM_RESOURCES; i++) {
		Platform::Device::Resource res = device.resource(i);
		if (res.type() == Platform::Device::Resource::MEMORY) {
			if (res.base() == start) {
				cap = device.io_mem(virt_iomem_bar);
				break;
			}
			virt_iomem_bar ++;
		}
	}

	if (!cap.valid()) { return -1; }

	try {
		_io_mem.construct(_global_env->rm(), start, cap);
	} catch (...) { return -1; }

	*vaddr = _io_mem->vaddr();
	return 0;
}


extern "C" int dde_release_iomem(dde_addr_t start, dde_size_t size)
{
	try {
		_io_mem.destruct();
		return 0;
	} catch (...) { return -1; }
}
