/*
 * \brief  Emulation of Linux kernel interfaces
 * \author Norman Feske
 * \author Sebastian Sumpf
 * \date   2012-01-29
 */

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

/* Genode includes */
#include <base/allocator_avl.h>
#include <dataspace/client.h>
#include <region_map/client.h>
#include <timer_session/connection.h>
#include <util/bit_allocator.h>
#include <util/string.h>
#include <os/backtrace.h>

/* Local includes */
#include "signal.h"
#include "lx_emul.h"

#include <lx_kit/backend_alloc.h>
#include <lx_kit/irq.h>
#include <lx_kit/scheduler.h>
#include <lx_kit/work.h>


#include <lx_emul/impl/slab.h>
#include <lx_emul/impl/mutex.h>

namespace Genode {
	class Slab_backend_alloc;
	class Slab_alloc;
}


unsigned long jiffies;
void lx_backtrace() { Genode::backtrace(); }


void pci_dev_put(struct pci_dev *pci_dev)
{
	Genode::destroy(&Lx_kit::env().heap(), pci_dev);
}


/*************************************
 ** Memory allocation, linux/slab.h **
 *************************************/

void *dma_malloc(size_t size)
{
	return Lx::Malloc::dma().alloc_large(size);
}


void dma_free(void *ptr)
{
	Lx::Malloc::dma().free_large(ptr);
}


void vfree(void *addr)
{
	if (!addr) return;
	Lx::Malloc::mem().free_large(addr);
}


/******************
 ** linux/kref.h **
 ******************/

void kref_init(struct kref *kref)
{ 
	lx_log(DEBUG_KREF,"%s ref: %p", __func__, kref);
	atomic_set(&kref->refcount, 1);
}


void kref_get(struct kref *kref)
{
	atomic_inc(&kref->refcount);
	lx_log(DEBUG_KREF, "%s ref: %p c: %d", __func__, kref, kref->refcount.counter);
}


int  kref_put(struct kref *kref, void (*release) (struct kref *kref))
{
	lx_log(DEBUG_KREF, "%s: ref: %p c: %d", __func__, kref, kref->refcount.counter);

	if(!atomic_dec_return(&kref->refcount)) {
		release(kref);
		return 1;
	}
	return 0;
}


/*********************
 ** linux/uaccess.h **
 *********************/

size_t copy_to_user(void *dst, void const *src, size_t len)
{
	if (dst && src && len)
		memcpy(dst, src, len);
	return 0;
}


bool access_ok(int access, void *addr, size_t size) { return 1; }


int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
	Genode::String_console sc(buf, size);
	sc.vprintf(fmt, args);

	return sc.len();
}


int snprintf(char *buf, size_t size, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	Genode::String_console sc(buf, size);
	sc.vprintf(fmt, args);
	va_end(args);

	return sc.len();
}


int scnprintf(char *buf, size_t size, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	Genode::String_console sc(buf, size);
	sc.vprintf(fmt, args);
	va_end(args);

	return sc.len();
}


size_t strlen(const char *s) { return Genode::strlen(s); }


/*****************
 ** linux/gfp.h **
 *****************/

unsigned long get_zeroed_page(gfp_t gfp_mask)
{
	return (unsigned long)kzalloc(PAGE_SIZE, 0);
}


/******************
 ** linux/log2.h **
 ******************/

int ilog2(u32 n) { return Genode::log2(n); }


/********************
 ** linux/slab.h   **
 ********************/


void *kmem_cache_zalloc(struct kmem_cache *cache, gfp_t flags)
{
	void *ret;
	ret = kmem_cache_alloc(cache, flags);
	memset(ret, 0, cache->size());

	return ret;
}


/********************
 ** linux/device.h **
 ********************/

/**
 * Simple driver management class
 */
class Driver : public Genode::List<Driver>::Element
{
	private:

		struct device_driver *_drv; /* Linux driver */

	public:

		Driver(struct device_driver *drv) : _drv(drv)
		{
			list()->insert(this);
		}

		/**
		 * List of all currently registered drivers
		 */
		static Genode::List<Driver> *list()
		{
			static Genode::List<Driver> _list;
			return &_list;
		}

		/**
		 * Match device and drivers
		 */
		bool match(struct device *dev)
		{
			/*
			 *  Don't try if buses don't match, since drivers often use 'container_of'
			 *  which might cast the device to non-matching type
			 */
			if (_drv->bus != dev->bus)
				return false;

			bool ret = _drv->bus->match ? _drv->bus->match(dev, _drv) : true;
			lx_log(DEBUG_DRIVER, "MATCH: %s ret: %u match: %p %p",
			            _drv->name, ret,  _drv->bus->match, _drv->probe);
			return ret;
		}

		/**
		 * Probe device with driver
		 */
		int probe(struct device *dev)
		{
			dev->driver = _drv;

			if (dev->bus->probe)
				return dev->bus->probe(dev);
			else if (_drv->probe)
				return _drv->probe(dev);

			return 0;
		}
};


int driver_register(struct device_driver *drv)
{
	lx_log(DEBUG_DRIVER, "%s at %p", drv->name, drv);
	new (Lx::Malloc::mem()) Driver(drv);
	return 0;
}


int device_add(struct device *dev)
{
	if (dev->driver)
		return 0;

	/* foreach driver match and probe device */
	for (Driver *driver = Driver::list()->first(); driver; driver = driver->next())
		if (driver->match(dev)) {
			int ret = driver->probe(dev);
			lx_log(DEBUG_DRIVER, "Probe return %d", ret);

			if (!ret)
				return 0;
		}

	return 0;
}


void device_del(struct device *dev)
{
	if (dev->driver && dev->driver->remove)
		dev->driver->remove(dev);

	if (dev->bus && dev->bus->remove)
		dev->bus->remove(dev);
}


int device_register(struct device *dev)
{
	return device_add(dev);
}


void device_unregister(struct device *dev)
{
	device_del(dev);
	put_device(dev);
}


int device_is_registered(struct device *dev)
{
	return 1;
}


void device_release_driver(struct device *dev)
{
	/* is usb_unbind_interface(dev); */
	if (dev->driver->remove)
		dev->driver->remove(dev);

	dev->driver = nullptr;
}


void put_device(struct device *dev)
{
	if (dev->ref) {
		dev->ref--;
		return;
	}

	if (dev->release)
		dev->release(dev);
	else if (dev->type && dev->type->release)
		dev->type->release(dev);
}


struct device *get_device(struct device *dev)
{
	dev->ref++;

	return dev;
}


void *dev_get_drvdata(const struct device *dev)
{
	return dev->driver_data;
}


int dev_set_drvdata(struct device *dev, void *data)
{
	dev->driver_data = data; return 0;
}


const char *dev_name(const struct device *dev) { return dev->name; }


void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)
{
	return kzalloc(size, gfp);
}


void *dev_get_platdata(const struct device *dev)
{
	return (void *)dev->platform_data;
}


void put_unaligned_le16(u16 val, void *p)
{
	struct __una_u16 *ptr = (struct __una_u16 *)p;
	ptr->x = val;
}

u32 get_unaligned_le32(const void *p)
{
	const struct __una_u32 *ptr = (const struct __una_u32 *)p;
	return ptr->x;
}


void put_unaligned_le32(u32 val, void *p)
{
	struct __una_u32 *ptr = (struct __una_u32 *)p;
	ptr->x = val;
}


u64 get_unaligned_le64(const void *p)
{
	const struct __una_u64 *ptr = (const struct __una_u64 *)p;
	return ptr->x;
}


/**********************************
 ** linux/bitops.h, asm/bitops.h **
 **********************************/

int fls(int x)
{
	if (!x)
		return 0;

	for (int i = 31; i >= 0; i--)
		if (x & (1 << i))
			return i + 1;

	return 0;
}


/*******************
 ** linux/delay.h **
 *******************/

#include <lx_emul/impl/delay.h>

void usleep_range(unsigned long min, unsigned long max)
{
	udelay(min);
}


/*********
 ** DMA **
 *********/

struct dma_pool
{
	size_t size;
	int    align;
};

struct dma_pool *dma_pool_create(const char *name, struct device *d, size_t size,
                                 size_t align, size_t alloc)
{
	lx_log(DEBUG_DMA, "size: %zx align:%zx %p", size, align, __builtin_return_address((0)));

	if (align & (align - 1))
		return 0;

	dma_pool *pool = new(Lx::Malloc::mem()) dma_pool;
	pool->align    = Genode::log2((int)align);
	pool->size     = size;
	return pool;
}


void  dma_pool_destroy(struct dma_pool *d)
{
	lx_log(DEBUG_DMA, "close");
	destroy(Lx::Malloc::mem(), d);
}


void *dma_pool_alloc(struct dma_pool *d, gfp_t f, dma_addr_t *dma)
{
	void *addr;
	addr = dma_alloc_coherent(0, d->size, dma, 0);

	lx_log(DEBUG_DMA, "addr: %p size %zx align %x phys: %lx pool %p",
	            addr, d->size, d->align, *dma, d);
	return addr;
}


void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle)
{
	void * ret = dma_pool_alloc(pool, mem_flags, handle);
	if (ret) Genode::memset(ret, 0, pool->size);
	return ret;
}


void *dma_zalloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)
{
	void * ret = dma_alloc_coherent(dev, size, dma_handle, flag);
	if (ret) Genode::memset(ret, 0, size);
	return ret;
}


void  dma_pool_free(struct dma_pool *d, void *vaddr, dma_addr_t a)
{
	lx_log(DEBUG_DMA, "free: addr %p, size: %zx", vaddr, d->size);
	Lx::Malloc::dma().free(vaddr);
}


void *dma_alloc_coherent(struct device *, size_t size, dma_addr_t *dma, gfp_t)
{
	void *addr = Lx::Malloc::dma().alloc(size, PAGE_SHIFT, dma);

	if (!addr)
		return 0;

	lx_log(DEBUG_DMA, "DMA pool alloc addr: %p size %zx align: %d, phys: %lx",
	            addr, size, PAGE_SHIFT, *dma);
	return addr;
}


void dma_free_coherent(struct device *, size_t size, void *vaddr, dma_addr_t)
{
	lx_log(DEBUG_DMA, "free: addr %p, size: %zx", vaddr, size);
	Lx::Malloc::dma().free(vaddr);
}


/*************************
 ** linux/dma-mapping.h **
 *************************/

dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
                                size_t size,
                                enum dma_data_direction dir,
                                struct dma_attrs *attrs)
{
	dma_addr_t phys = (dma_addr_t)Lx::Malloc::dma().phys_addr(ptr);

	if (phys == ~0UL)
		Genode::error("translation virt->phys ", ptr, "->", Genode::Hex(phys), "failed, return ip ",
		     __builtin_return_address(0));

	lx_log(DEBUG_DMA, "virt: %p phys: %lx", ptr, phys);
	return phys;
}


dma_addr_t dma_map_page(struct device *dev, struct page *page,
                        size_t offset, size_t size,
                        enum dma_data_direction dir)
{
	lx_log(DEBUG_DMA, "virt: %p phys: %lx offs: %zx", page->virt, page->phys, offset);
	return page->phys + offset;
}


int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg,
                     int nents, enum dma_data_direction dir,
                     struct dma_attrs *attrs) { return nents; }


/*************************
 ** linux/scatterlist.h **
 *************************/

struct scatterlist *sg_next(struct scatterlist *sg)
{
	if (sg->last)
		return 0;

	return sg++;
}


struct page *sg_page(struct scatterlist *sg)
{
	if (!sg)
		return 0;
	
	return (page *)sg->page_link;
}


/*****************
 ** linux/smp.h **
 *****************/

int smp_call_function_single(int cpu, smp_call_func_t func, void *info,
                             int wait) { func(info); return 0; }


/******************
 ** linux/log2.h **
 ******************/


int rounddown_pow_of_two(u32 n)
{
	return 1U << Genode::log2(n);
}


/*****************
 ** linux/nls.h **
 *****************/

int utf16s_to_utf8s(const wchar_t *pwcs, int len,
                    enum utf16_endian endian, u8 *s, int maxlen)
{
	/*
	 * We do not convert to char, we simply copy the UTF16 plane 0 values
	 */
	u16 *out = (u16 *)s;
	u16 *in  = (u16 *)pwcs;
	int length = min(len, maxlen / 2);
	for (int i = 0; i < length; i++)
		out[i] = in[i];

	return 2 * length;
}

/**********************
 ** linux/notifier.h **
 **********************/

int raw_notifier_chain_register(struct raw_notifier_head *nh,
                                struct notifier_block *n)
{
	struct notifier_block *nl = nh->head;
	struct notifier_block *pr = 0;
	while (nl) {
		if (n->priority > nl->priority)
			break;
		pr = nl;
		nl = nl->next;
	}

	n->next = nl;
	if (pr)
		pr->next = n;
	else
		nh->head = n;

	return 0;
}


int raw_notifier_call_chain(struct raw_notifier_head *nh,
                            unsigned long val, void *v)
{
	int ret = NOTIFY_DONE;
	struct notifier_block *nb = nh->head;

	while (nb) {

		ret = nb->notifier_call(nb, val, v);
		if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
			break;

		nb = nb->next;
	}

	return ret;
}


int blocking_notifier_chain_register(struct blocking_notifier_head *nh,
                                     struct notifier_block *n)
{
	return raw_notifier_chain_register((struct raw_notifier_head *)nh, n);
}


int blocking_notifier_call_chain(struct blocking_notifier_head *nh,
                                 unsigned long val, void *v)
{
	return raw_notifier_call_chain((struct raw_notifier_head *)nh, val, v);
}


/*******************
 ** linux/timer.h **
 *******************/

#include <lx_emul/impl/timer.h>
#include <lx_emul/impl/sched.h>

signed long schedule_timeout_uninterruptible(signed long timeout)
{
	lx_log(DEBUG_COMPLETION, "%ld\n", timeout);
	schedule_timeout(timeout);
	return 0;
}


/************************
 ** linux/completion.h **
 ************************/

#include <lx_emul/impl/completion.h>


long __wait_completion(struct completion *work, unsigned long timeout)
{
	Lx::timer_update_jiffies();
	struct process_timer timer { *Lx::scheduler().current() };
	unsigned long        expire = timeout + jiffies;

	if (timeout) {
		timer_setup(&timer.timer, process_timeout, 0);
		mod_timer(&timer.timer, expire);
	}

	while (!work->done) {

		if (timeout && expire <= jiffies) return 0;

		Lx::Task * task = Lx::scheduler().current();
		work->task = (void *)task;
		task->block_and_schedule();
	}

	if (timeout) del_timer(&timer.timer);

	work->done = 0;
	return (expire > jiffies) ? (expire - jiffies) : 1;
}


/***********************
 ** linux/workqueue.h **
 ***********************/

#include <lx_emul/impl/work.h>


void tasklet_init(struct tasklet_struct *t, void (*f)(unsigned long), unsigned long d)
{
	t->func    = f;
	t->data    = d;
}


void tasklet_schedule(struct tasklet_struct *tasklet)
{
	Lx::Work *lx_work = (Lx::Work *)tasklet_wq->task;
	lx_work->schedule_tasklet(tasklet);
	lx_work->unblock();
}


void tasklet_hi_schedule(struct tasklet_struct *tasklet)
{
	tasklet_schedule(tasklet);
}


struct workqueue_struct *create_singlethread_workqueue(char const *name)
{
	workqueue_struct *wq = (workqueue_struct *)kzalloc(sizeof(workqueue_struct), 0);
	Lx::Work *work = Lx::Work::alloc_work_queue(&Lx::Malloc::mem(), name);
	wq->task       = (void *)work;

	return wq;
}


struct workqueue_struct *alloc_workqueue(const char *fmt, unsigned int flags,
                                         int max_active, ...)
{
	return create_singlethread_workqueue(fmt);
}


/******************
 ** linux/wait.h **
 ******************/

#include <lx_emul/impl/wait.h>


static Genode::Bit_allocator<1024> id_allocator;

int idr_alloc(struct idr *idp, void *ptr, int start, int end, gfp_t gfp_mask)
{
	int max = end > 0 ? end - 1 : ((int)(~0U>>1));  /* inclusive upper limit */
	int id;

	/* sanity checks */
	if (start < 0)   return -EINVAL;
	if (max < start) return -ENOSPC;

	/* allocate id */
	id = id_allocator.alloc();
	if (id == 0) id = id_allocator.alloc(); /* do not use id zero */
	if (id > max) return -ENOSPC;

	if (!(id >= start)) BUG();
	return id;
}


void idr_remove(struct idr *idp, int id)
{
	if (id > 0) id_allocator.free(id);
}


int object_is_on_stack(const void *obj)
{
	Genode::Thread::Stack_info info = Genode::Thread::mystack();
	if ((Genode::addr_t)obj <= info.top &&
	    (Genode::addr_t)obj >= info.base) return 1;
	return 0;
}


int pci_irq_vector(struct pci_dev *dev, unsigned int nr)
{
	return dev->irq;
}


static int platform_match(struct device *dev, struct device_driver *drv)
{
	if (!dev->name)
		return 0;


	printk("MATCH %s %s\n", dev->name, drv->name);
	return (Genode::strcmp(dev->name, drv->name) == 0);
}


static int platform_drv_probe(struct device *_dev)
{
	struct platform_driver *drv = to_platform_driver(_dev->driver);
	struct platform_device *dev = to_platform_device(_dev);

	return drv->probe(dev);
}


struct bus_type platform_bus_type = {
	.name  = "platform",
};


int platform_driver_register(struct platform_driver *drv)
{
	/* init plarform_bus_type */
	platform_bus_type.match = platform_match;
	platform_bus_type.probe = platform_drv_probe;

	drv->driver.bus = &platform_bus_type;
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;

	printk("Register: %s\n", drv->driver.name);
	return driver_register(&drv->driver);
}


struct resource *platform_get_resource(struct platform_device *dev,
                                       unsigned int type, unsigned int num)
{
	unsigned i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

		if ((type & r->flags) && num-- == 0)
			return r;
	}

	return NULL;
}


struct resource *platform_get_resource_byname(struct platform_device *dev,
                                              unsigned int type,
                                              const char *name)
{
	unsigned i;

	for (i = 0; i < dev->num_resources; i++) {
		struct resource *r = &dev->resource[i];

	if (type == r->flags && !Genode::strcmp(r->name, name))
		return r;
	}

	return NULL;
}


int platform_get_irq_byname(struct platform_device *dev, const char *name)
{
	struct resource *r = platform_get_resource_byname(dev, IORESOURCE_IRQ, name);
	return r ? r->start : -1;
}


int platform_get_irq(struct platform_device *dev, unsigned int num)
{
	struct resource *r = platform_get_resource(dev, IORESOURCE_IRQ, 0);
	return r ? r->start : -1;
}


int platform_device_register(struct platform_device *pdev)
{
	pdev->dev.bus  = &platform_bus_type;
	pdev->dev.name = pdev->name;
	/*Set parent to ourselfs */
	if (!pdev->dev.parent)
		pdev->dev.parent = &pdev->dev;
	device_add(&pdev->dev);
	return 0;
}


struct platform_device *platform_device_alloc(const char *name, int id)
{
	platform_device *pdev = (platform_device *)kzalloc(sizeof(struct platform_device), GFP_KERNEL);

	if (!pdev)
		return 0;

	int len    = strlen(name);
	pdev->name = (char *)kzalloc(len + 1, GFP_KERNEL);

	if (!pdev->name) {
		kfree(pdev);
		return 0;
	}

	memcpy(pdev->name, name, len);
	pdev->name[len] = 0;
	pdev->id = id;
	pdev->dev.dma_mask = (u64*)kzalloc(sizeof(u64),  GFP_KERNEL);

	return pdev;
}


int platform_device_add_data(struct platform_device *pdev, const void *data,
                             size_t size)
{
	void *d = NULL;

	if (data && !(d = kmemdup(data, size, GFP_KERNEL)))
		return -ENOMEM;

	kfree(pdev->dev.platform_data);
	pdev->dev.platform_data = d;

	return 0;
}


int platform_device_add(struct platform_device *pdev)
{
	return platform_device_register(pdev);
}

int platform_device_add_resources(struct platform_device *pdev,
                                  const struct resource *res, unsigned int num)
{
	struct resource *r = NULL;
	
	if (res) {
		r = (resource *)kmemdup(res, sizeof(struct resource) * num, GFP_KERNEL);
		if (!r)
			return -ENOMEM;
	}

	kfree(pdev->resource);
	pdev->resource = r;
	pdev->num_resources = num;
	return 0;
}


void *platform_get_drvdata(const struct platform_device *pdev)
{
	return dev_get_drvdata(&pdev->dev);
}


void platform_set_drvdata(struct platform_device *pdev, void *data)
{
	dev_set_drvdata(&pdev->dev, data);
}


/**********************
 ** asm-generic/io.h **
 **********************/

void *devm_ioremap_resource(struct device *dev, struct resource *res)
{
	return ioremap(res->start, res->end - res->start);
}


/****************
  ** **
  *****/
int device_property_read_string(struct device *dev, const char *propname, const char **val)
{
	if (Genode::strcmp("dr_mode", propname) == 0) {
		*val = "host";
		return 0;
	}

	if (DEBUG_DRIVER) Genode::warning("property ", propname, " not found");
	*val = 0;
	return -EINVAL;
}


/****************
 ** linux/of.h **
 ****************/

const void *of_get_property(const struct device_node *node, const char *name, int *lenp)
{
	for (property * p = node ? node->properties : nullptr; p; p = p->next)
		if (Genode::strcmp(name, p->name) == 0) return p->value;

	if (DEBUG_DRIVER) Genode::warning("OF property ", name, " not found");
	return nullptr;
}


struct property *of_find_property(const struct device_node *np, const char *name, int *lenp)
{
	if (Genode::strcmp("non-zero-ttctrl-ttha", name) == 0) return (property*) 0xdeadbeef;

	if (DEBUG_DRIVER) Genode::warning("Could not find property ", name);
	return nullptr;
}


struct platform_device *of_find_device_by_node(struct device_node *np)
{
	return container_of(np->dev, struct platform_device, dev);
}


const struct of_device_id *of_match_device(const struct of_device_id *matches,
                                           const struct device *dev)
{
	const char * compatible = (const char*) of_get_property(dev->of_node, "compatible", 0);
	for (; matches && matches->compatible; matches++)
		if (Genode::strcmp(matches->compatible, compatible) == 0)
			return matches;
	return nullptr;
}


int of_parse_phandle_with_args(struct device_node *np,
                               const char *list_name,
                               const char *cells_name,
                               int index, struct of_phandle_args *out_args)
{
	out_args->np      = (device_node*) of_get_property(np, "fsl,usbmisc", 0);
	out_args->args[0] = 1;

	return 0;
}


int match_string(const char * const *array, size_t n, const char *string)
{
	for (size_t i = 0; i < n; i++)
		if (Genode::strcmp(string, array[i]) == 0) return i;
	return -1;
}


int strcmp(const char *a,const char *b)
{
	return Genode::strcmp(a, b);
}

struct regmap *syscon_regmap_lookup_by_phandle(struct device_node *np, const char *property) {
	return (regmap*)of_get_property(np, property, 0); }


bool of_property_read_bool(const struct device_node *np, const char *propname)
{
	if (DEBUG_DRIVER) Genode::warning("Could not find bool property ", propname);
	return false;
}


static usb_phy * __devm_usb_phy = nullptr;

struct usb_phy *devm_usb_get_phy_by_phandle(struct device *dev,
                                            const char *phandle, u8 index) {
	return __devm_usb_phy; }


int usb_add_phy_dev(struct usb_phy *phy) { __devm_usb_phy = phy; return 0; }


int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
{
	if (DEBUG_DRIVER) Genode::warning("Could not find property ", propname);
	return -EINVAL;
}
