/*
 * \brief  Qemu USB controller interface
 * \author Josef Soentgen
 * \author Sebastian Sumpf
 * \date   2015-12-14
 */

/*
 * Copyright (C) 2015-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/env.h>
#include <base/log.h>
#include <util/misc_math.h>

/* local includes */
#include <extern_c_begin.h>
#include <qemu_emul.h>
/* XHCIState is defined in this file */
#include <hcd-xhci.c>
#include <extern_c_end.h>

#include <qemu/usb.h>

/*******************
 ** USB interface **
 *******************/

static bool const verbose_irq  = false;
static bool const verbose_iov  = false;
static bool const verbose_mmio = false;

extern "C" void _type_init_usb_register_types();
extern "C" void _type_init_usb_host_register_types(Genode::Entrypoint*,
                                                   Genode::Allocator*,
                                                   Genode::Env *);
extern "C" void _type_init_xhci_register_types();

extern Genode::Lock _lock;

Qemu::Controller *qemu_controller();


static Qemu::Timer_queue* _timer_queue;
static Qemu::Pci_device*  _pci_device;

static Genode::Allocator *_heap = nullptr;

Qemu::Controller *Qemu::usb_init(Timer_queue &tq, Pci_device &pci,
                                 Genode::Entrypoint &ep,
                                 Genode::Allocator &alloc, Genode::Env &env)
{
	_heap = &alloc;
	_timer_queue = &tq;
	_pci_device  = &pci;

	_type_init_usb_register_types();
	_type_init_xhci_register_types();
	_type_init_usb_host_register_types(&ep, &alloc, &env);

	return qemu_controller();
}

void reset_controller();

void Qemu::usb_reset()
{
	usb_host_destroy();

	reset_controller();
}


void Qemu::usb_update_devices() {
	usb_host_update_devices(); }


void Qemu::usb_timer_callback(void (*cb)(void*), void *data)
{
	Genode::Lock::Guard g(_lock);

	cb(data);
}


/**********
 ** libc **
 **********/

void *g_malloc(size_t size) {
	return _heap->alloc(size); }


void g_free(void *p) {
	if (!p) return;
	_heap->free(p, 0);
}


void *memset(void *s, int c, size_t n) {
	return Genode::memset(s, c, n); }


void q_printf(char const *fmt, ...)
{
	enum { BUF_SIZE = 128 };
	char buf[BUF_SIZE] { };
	va_list args;
	va_start(args, fmt);
	Genode::String_console sc(buf, BUF_SIZE);
	sc.vprintf(fmt, args);
	Genode::log(Genode::Cstring(buf));
	va_end(args);
}


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 strcmp(const char *s1, const char *s2)
{
	return Genode::strcmp(s1, s2);
}


/********************
 ** Qemu emulation **
 ********************/

/**
 * Set properties of objects
 */
template <typename T>
void properties_apply(T *state, DeviceClass *klass)
{
	Property *p = klass->props;

	if (!p)
		return;

	while (p->type != END) {
		char *s = (char *)state;
		s += p->offset;
		uint32_t *member = (uint32_t *)s;
		if (p->type == BIT)
			*member |= p->value;
		if (p->type == UINT32)
			*member = p->value;

		p++;
	}
}


/**
 * Class and objects wrapper
 */
struct Wrapper
{
	unsigned long  _start;
	Object         _object;
	DeviceState    _device_state;
	PCIDevice      _pci_device;
	USBDevice      _usb_device;
	BusState       _bus_state;
	XHCIState     *_xhci_state = nullptr;
	USBHostDevice  _usb_host_device;

	ObjectClass         _object_class;
	DeviceClass         _device_class;
	PCIDeviceClass      _pci_device_class;
	BusClass            _bus_class;
	USBDeviceClass      _usb_device_class;
	HotplugHandlerClass _hotplug_handler_class;
	unsigned long  _end;

	Wrapper() { }

	bool in_object(void * ptr)
	{
		/*
		 * XHCIState is roughly 3 MiB large so we only create on instance
		 * (see below) and have to do this pointer check shenanigans.
		 */
		if (_xhci_state != nullptr
		    && ptr >= _xhci_state
		    && ptr < ((char*)_xhci_state + sizeof(XHCIState)))
			return true;

		using addr_t = Genode::addr_t;

		addr_t p = (addr_t)ptr;

		if (p > (addr_t)&_start && p < (addr_t)&_end)
			return true;

		return false;
	}
};


struct Object_pool
{
	enum {
		XHCI,            /* XHCI controller */
		USB_BUS,         /* bus driver */
		USB_DEVICE,      /* USB device driver */
		USB_HOST_DEVICE, /* USB host device driver */
		MAX = 8          /* host devices (USB_HOST_DEVICE - MAX) */
	};

	bool used[MAX];
	Wrapper obj[MAX];

	Wrapper *create_object()
	{
		for (unsigned i = USB_HOST_DEVICE + 1; i < MAX; i++) {
			if (used[i] == false) {
				used[i] = true;
				return &obj[i];
			}
		}
		return nullptr;
	}

	void free_object(Wrapper *w)
	{
		for (unsigned i = USB_HOST_DEVICE + 1; i < MAX; i++)
			if (w == &obj[i]) {
				used[i] = false;
				break;
			}
	}

	Wrapper * find_object(void *ptr)
	{
		for (Wrapper &w : obj) {
			if(w.in_object(ptr))
				return &w;
		}

		Genode::error("object for pointer not found called from: ",
		              __builtin_return_address(0));
		throw -1;
	}

	XHCIState *xhci_state() {
		return obj[XHCI]._xhci_state; }

	BusState *bus() {
		return &obj[USB_BUS]._bus_state; }

	static Object_pool *p()
	{
		static Object_pool _p;
		return &_p;
	}
};


/***********
 ** casts **
 ***********/

struct PCIDevice *cast_PCIDevice(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_pci_device; }


struct XHCIState *cast_XHCIState(void *ptr) {
	return Object_pool::p()->find_object(ptr)->_xhci_state; }


struct DeviceState *cast_DeviceState(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_device_state; }


struct BusState *cast_BusState(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_bus_state; }


struct USBDevice *cast_USBDevice(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_usb_device; }


struct Object *cast_object(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_object; }


struct PCIDeviceClass *cast_PCIDeviceClass(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_pci_device_class; }


struct DeviceClass *cast_DeviceClass(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_device_class; }


struct USBDeviceClass *cast_USBDeviceClass(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_usb_device_class; }


struct BusClass *cast_BusClass(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_bus_class; }


struct HotplugHandlerClass *cast_HotplugHandlerClass(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_hotplug_handler_class; }


struct USBHostDevice *cast_USBHostDevice(void *ptr) {
	return &Object_pool::p()->find_object(ptr)->_usb_host_device; }


struct USBBus *cast_DeviceStateToUSBBus(void) {
	return &Object_pool::p()->xhci_state()->bus; }


USBHostDevice *create_usbdevice(void *data)
{
	Wrapper *obj = Object_pool::p()->create_object();
	if (!obj) {
		Genode::error("could not create new object");
		return nullptr;
	}

	obj->_usb_device_class = Object_pool::p()->obj[Object_pool::USB_HOST_DEVICE]._usb_device_class;

	/*
	 * Set parent bus object
	 */
	DeviceState *dev_state = &obj->_device_state;
	dev_state->parent_bus  = Object_pool::p()->bus();

	/* set data pointer */
	obj->_usb_host_device.data = data;

	/*
	 * Attach new USB host device to USB device driver
	 */
	Error *e = nullptr;
	DeviceClass *usb_device_class = &Object_pool::p()->obj[Object_pool::USB_DEVICE]._device_class;
	usb_device_class->realize(dev_state, &e);
	if (e) {
		error_free(e);
		e = nullptr;

		usb_device_class->unrealize(dev_state, &e);
		Object_pool::p()->free_object(obj);

		return nullptr;
	}

	return &obj->_usb_host_device;
}


void remove_usbdevice(USBHostDevice *device)
{
	DeviceClass *usb_device_class = &Object_pool::p()->obj[Object_pool::USB_DEVICE]._device_class;
	DeviceState *usb_device_state = cast_DeviceState(device);

	if (usb_device_class == nullptr)
		Genode::error("usb_device_class null");

	if (usb_device_state == nullptr)
		Genode::error("usb_device_class null");

	Error *e = nullptr;
	usb_device_class->unrealize(usb_device_state, &e);

	Wrapper *obj = Object_pool::p()->find_object(device);
	Object_pool::p()->free_object(obj);
}


void reset_controller()
{
	Wrapper *w = &Object_pool::p()->obj[Object_pool::XHCI];
	DeviceClass *dc = &w->_device_class;

	dc->reset(&w->_device_state);
}


/***********************************
 ** Qemu interface implementation **
 ***********************************/

Type type_register_static(TypeInfo const *t)
{
	if (!Genode::strcmp(t->name, TYPE_XHCI)) {
		Wrapper *w = &Object_pool::p()->obj[Object_pool::XHCI];
		w->_xhci_state = (XHCIState*) g_malloc(sizeof(XHCIState));
		Genode::memset(w->_xhci_state, 0, sizeof(XHCIState));

		t->class_init(&w->_object_class, 0);

		properties_apply(w->_xhci_state, &w->_device_class);

		PCIDevice      *pci       = &w->_pci_device;
		PCIDeviceClass *pci_class = &w->_pci_device_class;
		Error *e;
		pci_class->realize(pci, &e);
	}

	if (!Genode::strcmp(t->name, TYPE_USB_DEVICE)) {
		Wrapper *w = &Object_pool::p()->obj[Object_pool::USB_DEVICE];
		t->class_init(&w->_object_class, 0);
	}

	if (!Genode::strcmp(t->name, TYPE_USB_HOST_DEVICE)) {
		Wrapper *w = &Object_pool::p()->obj[Object_pool::USB_HOST_DEVICE];
		t->class_init(&w->_object_class, 0);
	}

	if (!Genode::strcmp(t->name, TYPE_USB_BUS)) {
		Wrapper *w = &Object_pool::p()->obj[Object_pool::USB_BUS];
		ObjectClass *c = &w->_object_class;
		t->class_init(c, 0);
	}

	return nullptr;
}


void qbus_create_inplace(void* bus, size_t size , const char* type,
                         DeviceState *parent, const char *name)
{
	Wrapper    *w =  &Object_pool::p()->obj[Object_pool::USB_BUS];
	BusState   *b = &w->_bus_state;
	char const *n = "xhci.0";
	b->name = (char *)g_malloc(Genode::strlen(n) + 1);
	Genode::strncpy(b->name, n, Genode::strlen(n) + 1);
}


void timer_del(QEMUTimer *t)
{
	_timer_queue->deactivate_timer(t);
}


void timer_free(QEMUTimer *t)
{
	_timer_queue->delete_timer(t);
	g_free(t);
}


void timer_mod(QEMUTimer *t, int64_t expire)
{
	_timer_queue->activate_timer(t, expire);
}


QEMUTimer* timer_new_ns(QEMUClockType, void (*cb)(void*), void *opaque)
{
	QEMUTimer *t = (QEMUTimer*)g_malloc(sizeof(QEMUTimer));
	if (t == nullptr) {
		Genode::error("could not create QEMUTimer");
		return nullptr;
	}

	_timer_queue->register_timer(t, cb, opaque);

	return t;
}


int64_t qemu_clock_get_ns(QEMUClockType) {
	return _timer_queue->get_ns(); }


struct Controller : public Qemu::Controller
{
	struct Mmio
	{
		Genode::addr_t id;
		Genode::size_t size;
		Genode::off_t  offset;

		MemoryRegionOps const *ops;
	} mmio_regions [16];

	uint64_t _mmio_size;

	typedef Genode::Hex Hex;

	Controller()
	{
		Genode::memset(mmio_regions, 0, sizeof(mmio_regions));
	}

	void   mmio_add_region(uint64_t size) { _mmio_size = size; }

	void   mmio_add_region_io(Genode::addr_t id, uint64_t size, MemoryRegionOps const *ops)
	{
		for (Mmio &mmio : mmio_regions) {
			if (mmio.id != 0) continue;

			mmio.id   = id;
			mmio.size = size;
			mmio.ops  = ops;
			return;
		}
	}

	Mmio &find_region(Genode::off_t offset)
	{
		for (Mmio &mmio : mmio_regions) {
			if (offset >= mmio.offset
			    && (offset < mmio.offset + (Genode::off_t)mmio.size))
				return mmio;
		}

		Genode::error("could not find MMIO region for offset: ",
		              Genode::Hex(offset));
		throw -1;
	}

	void   mmio_add_sub_region(Genode::addr_t id, Genode::off_t offset)
	{
		for (Mmio &mmio : mmio_regions) {
			if (mmio.id != id) continue;

			mmio.offset = offset;
			return;
		}
	}

	Genode::size_t mmio_size() const { return _mmio_size; }

	int    mmio_read(Genode::off_t offset, void *buf, Genode::size_t size)
	{
		Genode::Lock::Guard g(_lock);
		Mmio &mmio        = find_region(offset);
		Genode::off_t reg = offset - mmio.offset;

		void *ptr = Object_pool::p()->xhci_state();

		/*
		 * Handle port access
		 */
		if (offset >= (OFF_OPER + 0x400) && offset < OFF_RUNTIME) {
			uint32_t port = (offset - 0x440) / 0x10;
			ptr = (void*)&Object_pool::p()->xhci_state()->ports[port];
		}

		uint64_t v = mmio.ops->read(ptr, reg, size);
		memcpy(buf, &v, Genode::min(sizeof(v), size));

		if (verbose_mmio)
			Genode::log(__func__, ": ", Hex(mmio.id), " offset: ", Hex(offset), " "
			            "reg: ", Hex(reg), " v: ", Hex(v));

		return 0;
	}

	int    mmio_write(Genode::off_t offset, void const *buf, Genode::size_t size)
	{
		Genode::Lock::Guard g(_lock);
		Mmio &mmio        = find_region(offset);
		Genode::off_t reg = offset - mmio.offset;
		void *ptr         = Object_pool::p()->xhci_state();
		uint64_t v        = 0ULL;

		memcpy(&v, buf, Genode::min(sizeof(v), size));

		/*
		 * Handle port access
		 */
		if (offset >= (OFF_OPER + 0x400) && offset < OFF_RUNTIME) {
			uint32_t port = (offset - 0x440) / 0x10;
			ptr = (void*)&Object_pool::p()->xhci_state()->ports[port];
		}

		mmio.ops->write(ptr, reg, v, size);

		if (verbose_mmio)
			Genode::log(__func__, ": ", Hex(mmio.id), " offset: ", Hex(offset), " "
			            "reg: ", Hex(reg), " v: ", Hex(v));

		return 0;
	}
};


static Controller *controller()
{
	static Controller _inst;
	return &_inst;
}

Qemu::Controller *qemu_controller()
{
	return controller();
}


/**********
 ** MMIO **
 **********/

void memory_region_init(MemoryRegion *mr, Object *obj, const char *name, uint64_t size) {
	controller()->mmio_add_region(size); }


void memory_region_init_io(MemoryRegion* mr, Object* obj, const MemoryRegionOps* ops,
                           void *, const char * name, uint64_t size) {
	controller()->mmio_add_region_io((Genode::addr_t)mr, size, ops); }


void memory_region_add_subregion(MemoryRegion *mr, hwaddr offset, MemoryRegion *sr) {
	controller()->mmio_add_sub_region((Genode::addr_t)sr, offset); }


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

int pci_dma_read(PCIDevice*, dma_addr_t addr, void *buf, dma_addr_t size)
{
	return _pci_device->read_dma(addr, buf, size);
}


int pci_dma_write(PCIDevice*, dma_addr_t addr, const void *buf, dma_addr_t size)
{
	return _pci_device->write_dma(addr, buf, size);
}


int dma_memory_read(AddressSpace*, dma_addr_t addr, void *buf, dma_addr_t size)
{
	return _pci_device->read_dma(addr, buf, size);
}


/****************
 ** Interrupts **
 ****************/

void pci_set_irq(PCIDevice*, int level)
{
	if (verbose_irq)
		Genode::log(__func__, ": IRQ level: ", level);
	_pci_device->raise_interrupt(level);
}


void pci_irq_assert(PCIDevice*)
{
	pci_set_irq(nullptr, 1);
}


int msi_init(PCIDevice *pdev, uint8_t offset, unsigned int nr_vectors, bool msi64bit,
             bool msi_per_vector_mask)
{
	return 0;
}


int msix_init(PCIDevice*, short unsigned int, MemoryRegion*, uint8_t, unsigned int, MemoryRegion*,
              uint8_t, unsigned int, uint8_t)
{
	return 0;
}


bool msi_enabled(const PCIDevice *pdev)
{
	return false;
}


int msix_enabled(PCIDevice *pdev)
{
	return 0;
}


void msi_notify(PCIDevice *pdev, unsigned int level) { }


void msix_notify(PCIDevice *pdev , unsigned int level) { }


/*************************************
 ** IO vector / scatter-gatter list **
 *************************************/

void qemu_iovec_add(QEMUIOVector *qiov, void *base, size_t len)
{
	int niov = qiov->niov;

	if (qiov->alloc_hint <= niov) {
		if (verbose_iov)
			Genode::log(__func__, ": alloc_hint ", qiov->alloc_hint,
			            " <= niov: ", niov);

		qiov->alloc_hint += 64;
		iovec *new_iov = (iovec*) g_malloc(sizeof(iovec) * qiov->alloc_hint);
		if (new_iov == nullptr) {
			Genode::error("could not reallocate iov");
			throw -1;
		}

		for (int i = 0; i < niov; i++) {
			new_iov[i].iov_base = qiov->iov[i].iov_base;
			new_iov[i].iov_len  = qiov->iov[i].iov_len;
		}

		g_free(qiov->iov);
		qiov->iov = new_iov;
	}

	if (verbose_iov)
		Genode::log(__func__, ": niov: ", niov, " iov_base: ",
		            &qiov->iov[niov].iov_base, " base: ", base, " len: ", len);

	qiov->iov[niov].iov_base = base;
	qiov->iov[niov].iov_len  = len;
	qiov->size              += len;
	qiov->niov++;
}


void qemu_iovec_destroy(QEMUIOVector *qiov)
{
	qemu_iovec_reset(qiov);

	g_free(qiov->iov);
	qiov->iov = nullptr;
}


void qemu_iovec_reset(QEMUIOVector *qiov)
{
	qiov->size = 0;
	qiov->niov = 0;
}


void qemu_iovec_init(QEMUIOVector *qiov, int alloc_hint)
{
	if (verbose_iov)
		Genode::log(__func__, " iov: ", qiov->iov, " alloc_hint: ", alloc_hint);

	iovec *iov = qiov->iov;
	if (iov != nullptr) {
		if (alloc_hint > qiov->alloc_hint)
			Genode::error("iov already initialized: ", iov, " and alloc_hint smaller");

		qemu_iovec_reset(qiov);
		return;
	}

	if (alloc_hint <= 0) alloc_hint = 1;

	qiov->alloc_hint = alloc_hint;

	qiov->iov = (iovec*) g_malloc(sizeof(iovec) * alloc_hint);
	if (qiov->iov == nullptr) {
		Genode::error("could not allocate iov");
		throw -1;
	}

	Genode::memset(qiov->iov, 0, sizeof(iovec) * alloc_hint);

	qemu_iovec_reset(qiov);
}


/* taken from util/iov.c */
size_t iov_from_buf(const struct iovec *iov, unsigned int iov_cnt,
                    size_t offset, const void *buf, size_t bytes)
{
	size_t done;
	unsigned int i;
	for (i = 0, done = 0; (offset || done < bytes) && i < iov_cnt; i++) {
		if (offset < iov[i].iov_len) {
			size_t len = Genode::min(iov[i].iov_len - offset, bytes - done);
			memcpy((char*)iov[i].iov_base + offset, (char*)buf + done, len);
			done += len;
			offset = 0;
		} else {
			offset -= iov[i].iov_len;
		}
	}

	assert(offset == 0);
	return done;
}


/* taken from util/iov.c */
size_t iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt,
                  size_t offset, void *buf, size_t bytes)
{
	size_t done;
	unsigned int i;
	for (i = 0, done = 0; (offset || done < bytes) && i < iov_cnt; i++) {
		if (offset < iov[i].iov_len) {
			size_t len = Genode::min(iov[i].iov_len - offset, bytes - done);
			memcpy((char*)buf + done, (char*)iov[i].iov_base + offset, len);
			done += len;
			offset = 0;
		} else {
			offset -= iov[i].iov_len;
		}
	}

	assert(offset == 0);
	return done;
}


void pci_dma_sglist_init(QEMUSGList *sgl, PCIDevice*, int alloc_hint) {
	qemu_iovec_init(sgl, alloc_hint); }


void qemu_sglist_add(QEMUSGList *sgl, dma_addr_t base, dma_addr_t len) {
	qemu_iovec_add(sgl, (void*)base, len); }


void qemu_sglist_destroy(QEMUSGList *sgl) {
	qemu_iovec_destroy(sgl); }


int usb_packet_map(USBPacket *p, QEMUSGList *sgl)
{
	void *mem;

	for (int i = 0; i < sgl->niov; i++) {
		dma_addr_t base = (dma_addr_t) sgl->iov[i].iov_base;
		dma_addr_t len = sgl->iov[i].iov_len;

		while (len) {
			dma_addr_t xlen = len;
			mem = _pci_device->map_dma(base, xlen);
			if (verbose_iov)
				Genode::log("mem: ", mem, " base: ", (void *)base, " len: ",
				            Genode::Hex(len));

			if (!mem) {
				goto err;
			}
			if (xlen > len) {
				xlen = len;
			}
			qemu_iovec_add(&p->iov, mem, xlen);
			len -= xlen;
			base += xlen;
		}
	}
	return 0;

err:
	Genode::error("could not map dma");
	usb_packet_unmap(p, sgl);
	return -1;
}


void usb_packet_unmap(USBPacket *p, QEMUSGList *sgl)
{
	for (int i = 0; i < p->iov.niov; i++) {
		_pci_device->unmap_dma(p->iov.iov[i].iov_base,
		                       p->iov.iov[i].iov_len);
	}
}


/******************
 ** qapi/error.h **
 ******************/

void error_setg(Error **errp, const char *fmt, ...)
{
	assert(*errp == nullptr);

	*errp = (Error*) g_malloc(sizeof(Error));
	if (*errp == nullptr) {
		Genode::error("could not allocate Error");
		return;
	}

	Error *e = *errp;

	va_list args;

	va_start(args, fmt);
	Genode::String_console sc(e->string, sizeof(e->string));
	sc.vprintf(fmt, args);
	va_end(args);
}


void error_propagate(Error **dst_errp, Error *local_err) {
	*dst_errp = local_err; }


void error_free(Error *err) { g_free(err); }
