/*
 * Copyright (C) 2018-2022 Intel Corporation.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "dm.h"
#include "pci_core.h"
#include "virtio.h"
#include "mevent.h"
#include <linux/input.h>

static int virtio_input_debug;
#define DPRINTF(params) do { if (virtio_input_debug) pr_dbg params; } while (0)
#define WPRINTF(params) (pr_err params)

/*
 * Queue definitions.
 */
#define VIRTIO_INPUT_EVENT_QUEUE	0
#define VIRTIO_INPUT_STATUS_QUEUE	1
#define VIRTIO_INPUT_MAXQ		2

/*
 * Virtqueue size.
 */
#define VIRTIO_INPUT_RINGSZ		64

/*
 * Default size of the buffer used to hold events between SYN
 */
#define VIRTIO_INPUT_PACKET_SIZE	10

/*
 * Host capabilities
 */
#define VIRTIO_INPUT_S_HOSTCAPS		(1UL << VIRTIO_F_VERSION_1)

enum virtio_input_config_select {
	VIRTIO_INPUT_CFG_UNSET		= 0x00,
	VIRTIO_INPUT_CFG_ID_NAME	= 0x01,
	VIRTIO_INPUT_CFG_ID_SERIAL	= 0x02,
	VIRTIO_INPUT_CFG_ID_DEVIDS	= 0x03,
	VIRTIO_INPUT_CFG_PROP_BITS	= 0x10,
	VIRTIO_INPUT_CFG_EV_BITS	= 0x11,
	VIRTIO_INPUT_CFG_ABS_INFO	= 0x12,
};

struct virtio_input_absinfo {
	uint32_t min;
	uint32_t max;
	uint32_t fuzz;
	uint32_t flat;
	uint32_t res;
};

struct virtio_input_devids {
	uint16_t bustype;
	uint16_t vendor;
	uint16_t product;
	uint16_t version;
};

struct virtio_input_event {
	uint16_t type;
	uint16_t code;
	uint32_t value;
};

/*
 * Device-specific configuration registers
 * To query a specific piece of configuration information FE driver sets
 * "select" and "subsel" accordingly, information size is returned in "size"
 * and information data is returned in union "u"
 */
struct virtio_input_config {
	uint8_t    select;
	uint8_t    subsel;
	uint8_t    size;
	uint8_t    reserved[5];
	union {
		char string[128];
		uint8_t bitmap[128];
		struct virtio_input_absinfo abs;
		struct virtio_input_devids ids;
	} u;
};

struct virtio_input_event_elem {
	struct virtio_input_event		event;
	struct iovec				iov;
	uint16_t				idx;
};

/*
 * Per-device struct
 */
struct virtio_input {
	struct virtio_base			base;
	struct virtio_vq_info			queues[VIRTIO_INPUT_MAXQ];
	pthread_mutex_t				mtx;
	struct mevent				*mevp;
	uint64_t				features;
	struct virtio_input_config		cfg;
	char					*evdev;
	char					*serial;
	int					fd;
	bool					ready;

	struct virtio_input_event_elem		*event_queue;
	uint32_t				event_qsize;
	uint32_t				event_qindex;
};

static void virtio_input_reset(void *);
static void virtio_input_neg_features(void *, uint64_t);
static void virtio_input_set_status(void *, uint64_t);
static int virtio_input_cfgread(void *, int, int, uint32_t *);
static int virtio_input_cfgwrite(void *, int, int, uint32_t);
static bool virtio_input_get_config(struct virtio_input *, uint8_t, uint8_t,
	struct virtio_input_config *);

static struct virtio_ops virtio_input_ops = {
	"virtio_input",			/* our name */
	VIRTIO_INPUT_MAXQ,		/* we support VTCON_MAXQ virtqueues */
	sizeof(struct virtio_input_config),	/* config reg size */
	virtio_input_reset,		/* reset */
	NULL,				/* device-wide qnotify */
	virtio_input_cfgread,		/* read virtio config */
	virtio_input_cfgwrite,		/* write virtio config */
	virtio_input_neg_features,	/* apply negotiated features */
	virtio_input_set_status,	/* called on guest set status */
};

static void
virtio_input_reset(void *vdev)
{
	struct virtio_input *vi;

	vi = vdev;

	DPRINTF(("vtinput: device reset requested!\n"));
	vi->ready = false;
	virtio_reset_dev(&vi->base);
}

static void
virtio_input_neg_features(void *vdev, uint64_t negotiated_features)
{
	struct virtio_input *vi = vdev;

	vi->features = negotiated_features;
}

static void
virtio_input_set_status(void *vdev, uint64_t status)
{
	struct virtio_input *vi = vdev;

	if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
		if (!vi->ready)
			vi->ready = true;
	}
}

static int
virtio_input_cfgread(void *vdev, int offset, int size, uint32_t *retval)
{
	struct virtio_input *vi = vdev;
	struct virtio_input_config cfg;
	bool rc;

	rc = virtio_input_get_config(vi, vi->cfg.select,
		vi->cfg.subsel, &cfg);
	if (rc)
		memcpy(retval, (uint8_t *)&cfg + offset, size);
	else
		memset(retval, 0, size);

	return 0;
}

static int
virtio_input_cfgwrite(void *vdev, int offset, int size, uint32_t val)
{
	struct virtio_input *vi = vdev;

	if (offset == offsetof(struct virtio_input_config, select))
		vi->cfg.select = (uint8_t)val;
	else if (offset == offsetof(struct virtio_input_config, subsel))
		vi->cfg.subsel = (uint8_t)val;
	else
		DPRINTF(("vtinput: write to readonly reg %d\n", offset));

	return 0;
}

static bool
virtio_input_ignore_event(struct virtio_input_event *event)
{
	if (!event)
		return true;

	/*
	 * EV_MSC is configured as INPUT_PASS_TO_ALL. In the use case of
	 * virtio-input, there is a loop as follows:
	 * - A mt frame with (EV_MSC,*,*) is passed to FE.
	 * - FE will call virtinput_status to pass (EV_MSC,*,*) back to BE.
	 * - BE writes this event to evdev. Because (EV_MSC,*,*)
	 *   is configured as INPUT_PASS_TO_ALL, it will be written into
	 *   the event buffer of evdev then be read out by BE without
	 *   SYN followed.
	 * - Each mt frame will introduce one (EV_MSC,*,*).
	 *   Later the frame becomes larger and larger...
	 */
	if (event->type == EV_MSC)
		return true;
	return false;
}

static void
virtio_input_notify_event_vq(void *vdev, struct virtio_vq_info *vq)
{
	DPRINTF(("%s\n", __func__));
}

static void
virtio_input_notify_status_vq(void *vdev, struct virtio_vq_info *vq)
{
	struct virtio_input *vi;
	struct virtio_input_event event;
	struct input_event host_event;
	struct iovec iov;
	int n, len;
	uint16_t idx;

	vi = vdev;

	while (vq_has_descs(vq)) {
		n = vq_getchain(vq, &idx, &iov, 1, NULL);
		if (n < 0) {
			WPRINTF(("virtio_input: invalid descriptors\n"));
			return;
		}
		if (n == 0) {
			WPRINTF(("virtio_input: get no available descriptors\n"));
			return;
		}
		if (n != 1) {
			WPRINTF(("virtio_input: get wrong number of available descriptors\n"));
			vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
			return;
		}

		if (vi->fd > 0) {
			memcpy(&event, iov.iov_base, sizeof(event));
			if (!virtio_input_ignore_event(&event)) {
				host_event.type = event.type;
				host_event.code = event.code;
				host_event.value = event.value;
				if (gettimeofday(&host_event.time, NULL)) {
					WPRINTF(("vtinput: gettimeofday failed\n"));
					break;
				}
				len = write(vi->fd, &host_event, sizeof(host_event));
				if (len == -1)
					WPRINTF(("%s: write failed, len = %d, "
						"errno = %d\n",
						__func__, len, errno));
			}
		}
		vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
	}
	vq_endchains(vq, 1);	/* Generate interrupt if appropriate. */
}

static void
virtio_input_send_event(struct virtio_input *vi,
			struct virtio_input_event *event)
{
	struct virtio_vq_info *vq;
	struct iovec iov;
	int n, i;
	uint16_t idx;

	if (!vi->ready)
		return;

	if (vi->event_qindex == vi->event_qsize) {
		vi->event_qsize++;
		vi->event_queue = realloc(vi->event_queue,
			vi->event_qsize *
			sizeof(struct virtio_input_event_elem));
		if (!vi->event_queue) {
			WPRINTF(("virtio_input: realloc memory for vi->event_queue failed!\n"));
			return;
		}
	}
	vi->event_queue[vi->event_qindex].event = *event;
	vi->event_qindex++;

	if (event->type != EV_SYN || event->code != SYN_REPORT)
		return;

	vq = &vi->queues[VIRTIO_INPUT_EVENT_QUEUE];
	for (i = 0; i < vi->event_qindex; i++) {
		if (!vq_has_descs(vq)) {
			while (i-- > 0)
				vq_retchain(vq);
			WPRINTF(("%s: not enough avail descs, dropped:%d\n",
				__func__, vi->event_qindex));
			goto out;
		}
		n = vq_getchain(vq, &idx, &iov, 1, NULL);
		if (n < 0) {
			WPRINTF(("virtio-input: invalid descriptors\n"));
			return;
		}
		if (n == 0) {
			WPRINTF(("virtio-input: get no available desciptors\n"));
			return;
		}
		if (n != 1) {
			WPRINTF(("virtio_input: get wrong number of available descriptors\n"));
			vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
			return;
		}
		vi->event_queue[i].iov = iov;
		vi->event_queue[i].idx = idx;
	}

	for (i = 0; i < vi->event_qindex; i++) {
		memcpy(vi->event_queue[i].iov.iov_base,
			&vi->event_queue[i].event,
			sizeof(struct virtio_input_event));
		vq_relchain(vq, vi->event_queue[i].idx,
			sizeof(struct virtio_input_event));
	}

out:
	vi->event_qindex = 0;
	vq_endchains(vq, 1);
}

static void
virtio_input_read_event(int fd __attribute__((unused)),
			enum ev_type t __attribute__((unused)),
			void *arg)
{
	struct virtio_input *vi = arg;
	struct virtio_input_event event;
	struct input_event host_event;
	int len;

	while (1) {
		len = read(vi->fd, &host_event, sizeof(host_event));
		if (len != sizeof(host_event)) {
			if (len == -1 && errno != EAGAIN)
				WPRINTF(("vtinput: host read failed! "
					"len = %d, errno = %d\n",
					len, errno));
			break;
		}

		event.type = host_event.type;
		event.code = host_event.code;
		event.value = host_event.value;
		virtio_input_send_event(vi, &event);
	}
}

static int
virtio_input_get_bitmap(struct virtio_input *vi, unsigned int cmd, int count,
			struct virtio_input_config *cfg)
{
	int i, size = -1;
	int rc;

	if (count <= 0)
		return -1;

	if (!cfg)
		return -1;

	memset(cfg, 0, sizeof(*cfg));
	rc = ioctl(vi->fd, cmd, cfg->u.bitmap);
	if (rc < 0)
		return -1;

	count = count / 8;
	for (i = count - 1; i >= 0; i--) {
		if (cfg->u.bitmap[i]) {
			size = i + 1;
			break;
		}
	}

	return size;
}

static bool
virtio_input_get_propbits(struct virtio_input *vi,
			  struct virtio_input_config *cfg)
{
	unsigned int cmd;
	int size;

	if (!cfg)
		return false;

	cmd = EVIOCGPROP(INPUT_PROP_CNT / 8);
	size = virtio_input_get_bitmap(vi, cmd, INPUT_PROP_CNT, cfg);
	if (size > 0) {
		cfg->select = VIRTIO_INPUT_CFG_PROP_BITS;
		cfg->subsel = 0;
		cfg->size = size;
		return true;
	}

	return false;
}

static bool
virtio_input_get_evbits(struct virtio_input *vi, int type,
			struct virtio_input_config *cfg)
{
	unsigned int cmd;
	int count, size;

	if (!cfg)
		return false;

	switch (type) {
	case EV_KEY:
		count = KEY_CNT;
		break;
	case EV_REL:
		count = REL_CNT;
		break;
	case EV_ABS:
		count = ABS_CNT;
		break;
	case EV_MSC:
		count = MSC_CNT;
		break;
	case EV_SW:
		count = SW_CNT;
		break;
	case EV_LED:
		count = LED_CNT;
		break;
	default:
		return false;
	}

	cmd = EVIOCGBIT(type, count / 8);
	size = virtio_input_get_bitmap(vi, cmd, count, cfg);
	if (size > 0) {
		cfg->select = VIRTIO_INPUT_CFG_EV_BITS;
		cfg->subsel = type;
		cfg->size = size;
		return true;
	}

	return false;
}

static bool
virtio_input_get_absinfo(struct virtio_input *vi, int axis,
			 struct virtio_input_config *cfg)
{
	struct virtio_input_config ev_cfg;
	struct input_absinfo abs;
	bool has_ev_abs;
	int rc;

	if (!cfg)
		return false;

	has_ev_abs = virtio_input_get_evbits(vi, EV_ABS, &ev_cfg);
	if (!has_ev_abs)
		return false;

	rc = ioctl(vi->fd, EVIOCGABS(axis), &abs);
	if (rc < 0)
		return false;

	cfg->u.abs.min = abs.minimum;
	cfg->u.abs.max = abs.maximum;
	cfg->u.abs.fuzz = abs.fuzz;
	cfg->u.abs.flat = abs.flat;
	cfg->u.abs.res = abs.resolution;

	cfg->select = VIRTIO_INPUT_CFG_ABS_INFO;
	cfg->subsel = axis;
	cfg->size = sizeof(struct virtio_input_absinfo);
	return true;
}

static bool
virtio_input_get_config(struct virtio_input *vi, uint8_t select,
			uint8_t subsel, struct virtio_input_config *cfg)
{
	struct input_id dev_ids;
	bool found = false;
	int rc;

	if (!cfg)
		return false;

	memset(cfg, 0, sizeof(*cfg));

	switch (select) {
	case VIRTIO_INPUT_CFG_ID_NAME:
		rc = ioctl(vi->fd, EVIOCGNAME(sizeof(cfg->u.string) - 1),
			cfg->u.string);
		if (rc >= 0) {
			cfg->select = VIRTIO_INPUT_CFG_ID_NAME;
			cfg->size = strnlen(cfg->u.string,
				sizeof(cfg->u.string));
			found = true;
		}
		break;
	case VIRTIO_INPUT_CFG_ID_SERIAL:
		if (vi->serial) {
			cfg->select = VIRTIO_INPUT_CFG_ID_SERIAL;
			cfg->size = snprintf(cfg->u.string,
				sizeof(cfg->u.string), "%s", vi->serial);
			found = true;
		}
		break;
	case VIRTIO_INPUT_CFG_ID_DEVIDS:
		rc = ioctl(vi->fd, EVIOCGID, &dev_ids);
		if (!rc) {
			cfg->u.ids.bustype = dev_ids.bustype;
			cfg->u.ids.vendor  = dev_ids.vendor;
			cfg->u.ids.product = dev_ids.product;
			cfg->u.ids.version = dev_ids.version;
			cfg->select = VIRTIO_INPUT_CFG_ID_DEVIDS;
			cfg->size = sizeof(struct virtio_input_devids);
			found = true;
		}
		break;
	case VIRTIO_INPUT_CFG_PROP_BITS:
		found = virtio_input_get_propbits(vi, cfg);
		break;
	case VIRTIO_INPUT_CFG_EV_BITS:
		found = virtio_input_get_evbits(vi, subsel, cfg);
		break;
	case VIRTIO_INPUT_CFG_ABS_INFO:
		found = virtio_input_get_absinfo(vi, subsel, cfg);
		break;
	default:
		break;
	}

	return found;
}

static void
virtio_input_teardown(void *param)
{
	struct virtio_input *vi;

	vi = (struct virtio_input *)param;
	if (vi) {
		pthread_mutex_destroy(&vi->mtx);
		if (vi->event_queue)
			free(vi->event_queue);
		if (vi->fd > 0)
			close(vi->fd);
		if (vi->evdev)
			free(vi->evdev);
		if (vi->serial)
			free(vi->serial);

		virtio_input_reset(vi);
		free(vi);
		vi = NULL;
	}
}

static int
virtio_input_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	struct virtio_input *vi;
	pthread_mutexattr_t attr;
	char *opt;
	int flags, ver;
	int rc;

	/* get evdev path from opts
	 * -s n,virtio-input,/dev/input/eventX[,serial]
	 */
	if (!opts) {
		WPRINTF(("%s: evdev path is NULL\n", __func__));
		return -1;
	}

	vi = calloc(1, sizeof(struct virtio_input));
	if (!vi) {
		WPRINTF(("%s: out of memory\n", __func__));
		return -1;
	}

	opt = strsep(&opts, ",");
	if (!opt) {
		WPRINTF(("%s: evdev path is NULL\n", __func__));
		goto opt_fail;
	}

	vi->evdev = strdup(opt);
	if (!vi->evdev) {
		WPRINTF(("%s: strdup failed\n", __func__));
		goto opt_fail;
	}

	if (opts) {
		vi->serial = strdup(opts);
		if (!vi->serial) {
			WPRINTF(("%s: strdup serial failed\n", __func__));
			goto serial_fail;
		}
	}

	vi->fd = open(vi->evdev, O_RDWR);
	if (vi->fd < 0) {
		WPRINTF(("open %s failed %d\n", vi->evdev, errno));
		goto open_fail;
	}
	flags = fcntl(vi->fd, F_GETFL);
	fcntl(vi->fd, F_SETFL, flags | O_NONBLOCK);

	rc = ioctl(vi->fd, EVIOCGVERSION, &ver); /* is it a evdev device? */
	if (rc < 0) {
		WPRINTF(("%s: get version failed\n", vi->evdev));
		goto evdev_fail;
	}

	rc = ioctl(vi->fd, EVIOCGRAB, 1); /* exclusive access */
	if (rc < 0) {
		WPRINTF(("%s: grab device failed %d\n", vi->evdev, errno));
		goto evdev_fail;
	}

	/* init mutex attribute properly to avoid deadlock */
	rc = pthread_mutexattr_init(&attr);
	if (rc)
		DPRINTF(("mutexattr init failed with erro %d!\n", rc));
	rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
	if (rc)
		DPRINTF(("vtinput: mutexattr_settype failed with "
			"error %d!\n", rc));
	rc = pthread_mutex_init(&vi->mtx, &attr);
	if (rc)
		DPRINTF(("vtinput: pthread_mutex_init failed with "
			"error %d!\n", rc));

	vi->event_qsize = VIRTIO_INPUT_PACKET_SIZE;
	vi->event_qindex = 0;
	vi->event_queue = calloc(vi->event_qsize,
		sizeof(struct virtio_input_event_elem));
	if (!vi->event_queue) {
		WPRINTF(("vtinput: could not alloc event queue buf\n"));
		goto evqueue_fail;
	}

	vi->mevp = mevent_add(vi->fd, EVF_READ, virtio_input_read_event, vi,
		virtio_input_teardown, vi);
	if (vi->mevp == NULL) {
		WPRINTF(("vtinput: could not register event\n"));
		goto mevent_fail;
	}

	virtio_linkup(&vi->base, &virtio_input_ops, vi, dev, vi->queues, BACKEND_VBSU);
	vi->base.mtx = &vi->mtx;
	vi->base.device_caps = VIRTIO_INPUT_S_HOSTCAPS;

	vi->queues[VIRTIO_INPUT_EVENT_QUEUE].qsize = VIRTIO_INPUT_RINGSZ;
	vi->queues[VIRTIO_INPUT_EVENT_QUEUE].notify =
		virtio_input_notify_event_vq;

	vi->queues[VIRTIO_INPUT_STATUS_QUEUE].qsize = VIRTIO_INPUT_RINGSZ;
	vi->queues[VIRTIO_INPUT_STATUS_QUEUE].notify =
		virtio_input_notify_status_vq;

	/* initialize config space */
	pci_set_cfgdata16(dev, PCIR_DEVICE, 0x1040 + VIRTIO_TYPE_INPUT);
	pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
	pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_INPUTDEV);
	pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_INPUTDEV_OTHER);
	pci_set_cfgdata16(dev, PCIR_SUBDEV_0, 0x1100);
	if (is_winvm == true)
		pci_set_cfgdata16(dev, PCIR_SUBVEND_0, ORACLE_VENDOR_ID);
	else
		pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
	pci_set_cfgdata16(dev, PCIR_REVID, 1);

	if (virtio_interrupt_init(&vi->base, virtio_uses_msix())) {
		DPRINTF(("%s, interrupt_init failed!\n", __func__));
		goto fail;
	}
	rc = virtio_set_modern_bar(&vi->base, true);

	return rc;

fail:
	/* all resources will be freed in the teardown callback */
	mevent_delete(vi->mevp);
	return -1;

mevent_fail:
	free(vi->event_queue);
	vi->event_queue = NULL;
evqueue_fail:
	pthread_mutex_destroy(&vi->mtx);
evdev_fail:
	close(vi->fd);
	vi->fd = -1;
open_fail:
	if (vi->serial) {
		free(vi->serial);
		vi->serial = NULL;
	}
serial_fail:
	free(vi->evdev);
	vi->evdev = NULL;
opt_fail:
	free(vi);
	vi = NULL;
	return -1;
}

static void
virtio_input_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	struct virtio_input *vi;

	vi = (struct virtio_input *)dev->arg;
	if (vi && vi->mevp)
		mevent_delete(vi->mevp);
}

struct pci_vdev_ops pci_ops_virtio_input = {
	.class_name	= "virtio-input",
	.vdev_init	= virtio_input_init,
	.vdev_deinit	= virtio_input_deinit,
	.vdev_barwrite	= virtio_pci_write,
	.vdev_barread	= virtio_pci_read
};
DEFINE_PCI_DEVTYPE(pci_ops_virtio_input);
