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

#include <sys/param.h>
#include <sys/uio.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <openssl/md5.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/evp.h>
#endif
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <stdbool.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>

#include "dm.h"
#include "pci_core.h"
#include "virtio.h"
#include "acpi.h"

/* I2c adapter virtualization architecture
 *
 *                +-----------------------------+
 *                | ACRN DM                     |
 *                |  +----------------------+   |  virtqueue
 *                |  |                      |<--+-----------+
 *                |  | virtio i2c mediator  |   |           |
 *                |  |                      |   |           |
 *                |  +--+-----+-----+-------+   |           |
 *                +-----+-----+-----+-----------+           |
 * User space   +-------+     |     +-----------+           |
 *              v             v                 v           |
 *    +---------+----+  +-----+--------+  +-----+------+    |  +-----------+
 * ---+ /dev/i2c-0   +--+ /dev/i2c-1   +--+ /dev/i2c-n +----+--+User VM:   |
 *    |              |                 |  |            |    |  |/dev/i2c-n |
 *    +----------+---+  +-------+------+  +-----+------+    |  +-----+-----+
 * Kernel space  v              v               v           |        v
 *         +-----+-------+ +----+--------+ +----+--------+  |  +-----+------------+
 *         |i2c adapter 0| |i2c adapter 1| |i2c adapter n|  +->|User VM:          |
 *         |             | |             |               |     |virtio i2c adapter|
 *         +-----+-------+ +-------------+ +-------------+     +------------------+
 * --------------+-----------------------------------------
 * Hardware      +----------+
 *               |          |
 *          bus 0v          v      ....
 *         +-----+----+ +----+-----+
 *         |i2c client| |i2c client| ....
 *         +----------+ +----------+
 */

static int virtio_i2c_debug=0;
#define VIRTIO_I2C_PREF "virtio_i2c: "
#define DPRINTF(fmt, args...) \
       do { if (virtio_i2c_debug) pr_info(VIRTIO_I2C_PREF fmt, ##args); } while (0)
#define WPRINTF(fmt, args...) pr_err(VIRTIO_I2C_PREF fmt, ##args)

#define MAX_NODE_NAME_LEN	20
#define MAX_I2C_VDEV		128
#define MAX_NATIVE_I2C_ADAPTER	16
#define I2C_MSG_OK	0
#define I2C_MSG_ERR	1
#define I2C_NO_DEV	2

#define I2C_NO_FLAGS	0

#define VIRTIO_I2C_FLAGS_FAIL_NEXT	1 << 0
#define VIRTIO_I2C_FLAGS_M_RD		1 << 1

#define VIRTIO_I2C_F_ZERO_LENGTH_REQUEST 0
#define VIRTIO_I2C_HOSTCAPS   (1UL << VIRTIO_F_VERSION_1) | \
                              (1UL << VIRTIO_I2C_F_ZERO_LENGTH_REQUEST)

static int acpi_i2c_adapter_num = 0;
static void acpi_add_i2c_adapter(struct pci_vdev *dev, int i2c_bus);
static void acpi_add_cam1(struct pci_vdev *dev, int i2c_bus);
static void acpi_add_cam2(struct pci_vdev *dev, int i2c_bus);
static void acpi_add_hdac(struct pci_vdev *dev, int i2c_bus);
static void acpi_add_default(struct pci_vdev *dev, int i2c_bus);

struct acpi_node {
	char node_name[MAX_NODE_NAME_LEN];
	void (*add_node_fn)(struct pci_vdev *, int);
};

static struct acpi_node acpi_node_table[] = {
/* cam1, cam2 and hdac is dump from MRB board */
	{"cam1", acpi_add_cam1},
	{"cam2", acpi_add_cam2},
	{"hdac", acpi_add_hdac},
	{"default", acpi_add_default},
};

struct virtio_i2c_out_hdr {
	uint16_t addr;      /* client address */
	uint16_t padding;
	uint32_t flags;
};

struct virtio_i2c_in_hdr {
	uint8_t status;
};

struct native_i2c_adapter {
	int 		fd;
	int 		bus;
	bool 		i2cdev_enable[MAX_I2C_VDEV];
};

/*
 * Per-device struct
 */
struct virtio_i2c {
	struct virtio_base base;
	pthread_mutex_t mtx;
	struct native_i2c_adapter *native_adapter[MAX_NATIVE_I2C_ADAPTER];
	int native_adapter_num;
	uint16_t adapter_map[MAX_I2C_VDEV];
	char acpi_nodes[MAX_I2C_VDEV][MAX_NODE_NAME_LEN];
	struct virtio_vq_info vq;
	char ident[256];
	pthread_t req_tid;
	pthread_mutex_t req_mtx;
	pthread_cond_t req_cond;
	int in_process;
	int closing;
};

static void virtio_i2c_reset(void *);
static void virtio_i2c_notify(void *, struct virtio_vq_info *);

static struct virtio_ops virtio_i2c_ops = {
	"virtio_i2c",		/* our name */
	1,			/* we support 1 virtqueue */
	0, /* config reg size */
	virtio_i2c_reset,	/* reset */
	virtio_i2c_notify,	/* device-wide qnotify */
	NULL,	/* read PCI config */
	NULL,	/* write PCI config */
	NULL,			/* apply negotiated features */
	NULL,			/* called on guest set status */
};

static void
acpi_add_i2c_adapter(struct pci_vdev *dev, int i2c_bus)
{
	dsdt_line("Device (I2C%d)", i2c_bus);
	dsdt_line("{");
	dsdt_line("    Name (_ADR, 0x%04X%04X)", dev->slot, dev->func);
	dsdt_line("    Name (_DDN, \"Intel(R) I2C Controller #%d\")", i2c_bus);
	dsdt_line("    Name (_UID, One)");
	dsdt_line("    Name (LINK, \"\\\\_SB.PCI%d.I2C%d\")", dev->bus, i2c_bus);
	dsdt_line("    Name (RBUF, ResourceTemplate ()");
	dsdt_line("    {");
	dsdt_line("    })");
	dsdt_line("    Name (IC0S, 0x00061A80)");
	dsdt_line("    Name (_DSD, Package (0x02)");
	dsdt_line("    {");
	dsdt_line("        ToUUID (\"daffd814-6eba-4d8c-8a91-bc9bbf4aa301\")"
				" ,");
	dsdt_line("        Package (0x01)");
	dsdt_line("        {");
	dsdt_line("            Package (0x02)");
	dsdt_line("            {");
	dsdt_line("                \"clock-frequency\", ");
	dsdt_line("                IC0S");
	dsdt_line("            }");
	dsdt_line("        }");
	dsdt_line("    })");
	dsdt_line("");
	dsdt_line("}");
}

static void
acpi_add_cam1(struct pci_vdev *dev, int i2c_bus)
{
	dsdt_line("Scope(I2C%d)", i2c_bus);
	dsdt_line("{");
	dsdt_line("    Device (CAM1)");
	dsdt_line("    {");
	dsdt_line("        Name (_ADR, Zero)  // _ADR: Address");
	dsdt_line("        Name (_HID, \"ADV7481A\")  // _HID: Hardware ID");
	dsdt_line("        Name (_CID, \"ADV7481A\")  // _CID: Compatible ID");
	dsdt_line("        Name (_UID, One)  // _UID: Unique ID");
	dsdt_line("        Method (_CRS, 0, Serialized)");
	dsdt_line("        {");
	dsdt_line("            Name (SBUF, ResourceTemplate ()");
	dsdt_line("            {");
	dsdt_line("                GpioIo (Exclusive, PullDefault, 0x0000, "
					"0x0000, IoRestrictionInputOnly,");
	dsdt_line("                    \"\\\\_SB.GPO0\", 0x00, "
					"ResourceConsumer, ,");
	dsdt_line("                    )");
	dsdt_line("                    {   // Pin list");
	dsdt_line("                        0x001E");
	dsdt_line("                    }");
	dsdt_line("                I2cSerialBusV2 (0x0070, "
					"ControllerInitiated, 0x00061A80,");
	dsdt_line("                    AddressingMode7Bit, "
						"\"\\\\_SB.PCI%d.I2C%d\",",
							dev->bus, i2c_bus);
	dsdt_line("                    0x00, ResourceConsumer, , Exclusive,");
	dsdt_line("                    )");
	dsdt_line("            })");
	dsdt_line("            Return (SBUF)");
	dsdt_line("        }");
	dsdt_line("        Method (_DSM, 4, NotSerialized)");
	dsdt_line("        {");
	dsdt_line("            If ((Arg0 == ToUUID ("
				"\"377ba76a-f390-4aff-ab38-9b1bf33a3015\")))");
	dsdt_line("            {");
	dsdt_line("                Return (\"ADV7481A\")");
	dsdt_line("            }");
	dsdt_line("");
	dsdt_line("            If ((Arg0 == ToUUID ("
				"\"ea3b7bd8-e09b-4239-ad6e-ed525f3f26ab\")))");
	dsdt_line("            {");
	dsdt_line("                Return (0x40)");
	dsdt_line("            }");
	dsdt_line("");
	dsdt_line("            If ((Arg0 == ToUUID ("
				"\"8dbe2651-70c1-4c6f-ac87-a37cb46e4af6\")))");
	dsdt_line("            {");
	dsdt_line("                Return (0xFF)");
	dsdt_line("            }");
	dsdt_line("");
	dsdt_line("            If ((Arg0 == ToUUID ("
				"\"26257549-9271-4ca4-bb43-c4899d5a4881\")))");
	dsdt_line("            {");
	dsdt_line("                If (Arg2 == One)");
	dsdt_line("                {");
	dsdt_line("                    Return (0x02)");
	dsdt_line("                }");
	dsdt_line("                If (Arg2 == 0x02)");
	dsdt_line("                {");
	dsdt_line("                    Return (0x02001000)");
	dsdt_line("                }");
	dsdt_line("                If (Arg2 == 0x03)");
	dsdt_line("                {");
	dsdt_line("                    Return (0x02000E01)");
	dsdt_line("                }");
	dsdt_line("            }");
	dsdt_line("            Return (Zero)");
	dsdt_line("        }");
	dsdt_line("    }");
	dsdt_line("}");
}

static void
acpi_add_cam2(struct pci_vdev *dev, int i2c_bus)
{
		dsdt_line("Scope(I2C%d)", i2c_bus);
		dsdt_line("{");
		dsdt_line("    Device (CAM2)");
		dsdt_line("    {");
		dsdt_line("        Name (_ADR, Zero)  // _ADR: Address");
		dsdt_line("        Name (_HID, \"ADV7481B\")  // _HID: Hardware ID");
		dsdt_line("        Name (_CID, \"ADV7481B\")  // _CID: Compatible ID");
		dsdt_line("        Name (_UID, One)  // _UID: Unique ID");
		dsdt_line("        Method (_CRS, 0, Serialized)");
		dsdt_line("        {");
		dsdt_line("            Name (SBUF, ResourceTemplate ()");
		dsdt_line("            {");
		dsdt_line("                GpioIo (Exclusive, PullDefault, 0x000, "
						"0x0000, IoRestrictionInputOnly,");
		dsdt_line("                    \"\\\\_SB.GPO0\", 0x00, "
						"ResourceConsumer, ,");
		dsdt_line("                    )");
		dsdt_line("                    {   // Pin list");
		dsdt_line("                        0x001E");
		dsdt_line("                    }");
		dsdt_line("                I2cSerialBusV2 (0x0071, "
						"ControllerInitiated, 0x00061A80,");
		dsdt_line("                    AddressingMode7Bit, "
							"\"\\\\_SB.PCI%d.I2C%d\",",
								dev->bus, i2c_bus);
		dsdt_line("                    0x00, ResourceConsumer, , Exclusive,");
		dsdt_line("                    )");
		dsdt_line("            })");
		dsdt_line("            Return (SBUF)");
		dsdt_line("        }");
		dsdt_line("        Method (_DSM, 4, NotSerialized) ");
		dsdt_line("        {");
		dsdt_line("            If ((Arg0 == ToUUID ("
					"\"377ba76a-f390-4aff-ab38-9b1bf33a3015\")))");
		dsdt_line("            {");
		dsdt_line("                Return (\"ADV7481B\")");
		dsdt_line("            }");
		dsdt_line("");
		dsdt_line("            If ((Arg0 == ToUUID ("
					"\"ea3b7bd8-e09b-4239-ad6e-ed525f3f26ab\")))");
		dsdt_line("            {");
		dsdt_line("                Return (0x14)");
		dsdt_line("            }");
		dsdt_line("");
		dsdt_line("            If ((Arg0 == ToUUID ("
					"\"8dbe2651-70c1-4c6f-ac87-a37cb46e4af6\")))");
		dsdt_line("            {");
		dsdt_line("                Return (0xFF)");
		dsdt_line("            }");
		dsdt_line("");
		dsdt_line("            If ((Arg0 == ToUUID ("
					"\"26257549-9271-4ca4-bb43-c4899d5a4881\")))");
		dsdt_line("            {");
		dsdt_line("                If (Arg2 == One)");
		dsdt_line("                {");
		dsdt_line("                    Return (0x02)");
		dsdt_line("                }");
		dsdt_line("                If (Arg2 == 0x02)");
		dsdt_line("                {");
		dsdt_line("                    Return (0x02001000)");
		dsdt_line("                }");
		dsdt_line("                If (Arg2 == 0x03)");
		dsdt_line("                {");
		dsdt_line("                    Return (0x02000E01)");
		dsdt_line("                }");
		dsdt_line("            }");
		dsdt_line("            Return (Zero)");
		dsdt_line("        }");
		dsdt_line("    }");
		dsdt_line("");
		dsdt_line("}");
}

static void
acpi_add_hdac(struct pci_vdev *dev, int i2c_bus)
{
	dsdt_line("Scope(I2C%d)", i2c_bus);
	dsdt_line("{");
	dsdt_line("    Device (HDAC)");
	dsdt_line("    {");
	dsdt_line("        Name (_HID, \"INT34C3\")  // _HID: Hardware ID");
	dsdt_line("        Name (_CID, \"INT34C3\")  // _CID: Compatible ID");
	dsdt_line("        Name (_DDN, \"Intel(R) Smart Sound Technology "
			"Audio Codec\")  // _DDN: DOS Device Name");
	dsdt_line("        Name (_UID, One)  // _UID: Unique ID");
	dsdt_line("        Method (_INI, 0, NotSerialized)");
	dsdt_line("        {");
	dsdt_line("        }");
	dsdt_line("");
	dsdt_line("        Method (_CRS, 0, NotSerialized)");
	dsdt_line("        {");
	dsdt_line("            Name (SBFB, ResourceTemplate ()");
	dsdt_line("            {");
	dsdt_line("                I2cSerialBusV2 (0x006C, "
					"ControllerInitiated, 0x00061A80,");
	dsdt_line("                    AddressingMode7Bit, "
						"\"\\\\_SB.PCI%d.I2C%d\",",
							dev->bus, i2c_bus);
	dsdt_line("                    0x00, ResourceConsumer, , Exclusive,");
	dsdt_line("                    )");
	dsdt_line("            })");
	dsdt_line("            Name (SBFI, ResourceTemplate ()");
	dsdt_line("            {");
	dsdt_line("            })");
	dsdt_line("            Return (ConcatenateResTemplate (SBFB, SBFI))");
	dsdt_line("        }");
	dsdt_line("");
	dsdt_line("        Method (_STA, 0, NotSerialized)  // _STA: Status");
	dsdt_line("        {");
	dsdt_line("            Return (0x0F)");
	dsdt_line("        }");
	dsdt_line("    }");
	dsdt_line("}");
}

static void
acpi_add_default(struct pci_vdev *dev, int i2c_bus)
{
	/* Add nothing */
}

static bool
native_client_access_ok(struct native_i2c_adapter *adapter, uint16_t addr)
{
	if (ioctl(adapter->fd, I2C_SLAVE, addr) < 0) {
		if (errno == EBUSY) {
			WPRINTF("i2c_core: client device %x is busy!\n", addr);
		} else {
			WPRINTF("i2c_core: client device %d is not exsit!\n", addr);
		}
		return false;
	}
	return true;
}

static struct native_i2c_adapter *
native_adapter_find(struct virtio_i2c *vi2c, uint16_t addr)
{
	int idx;

	if (addr < MAX_I2C_VDEV && ((idx = vi2c->adapter_map[addr]) != 0)) {
		return vi2c->native_adapter[idx - 1];
	}
	return NULL;
}

static uint8_t
native_adapter_proc(struct virtio_i2c *vi2c, struct i2c_msg *msg)
{
	int ret;
	uint16_t addr;
	struct i2c_rdwr_ioctl_data work_queue;
	struct native_i2c_adapter *adapter;
	uint8_t status;

	addr = msg->addr;
	adapter = native_adapter_find(vi2c, addr);
	if (!adapter) {
		DPRINTF("%s: could not find device for addr %x\n", __func__, msg->addr);
		return I2C_MSG_ERR;
	}

	work_queue.nmsgs = 1;
	work_queue.msgs = msg;

	ret = ioctl(adapter->fd, I2C_RDWR, &work_queue);
	if (ret < 0)
		status = I2C_MSG_ERR;
	else
		status = I2C_MSG_OK;
	if (msg->len)
		DPRINTF("i2c_core: i2c msg: flags=0x%x, addr=0x%x, len=0x%x buf=%x\n",
				msg->flags,
				msg->addr,
				msg->len,
				msg->buf[0]);
	else
		DPRINTF("i2c_core: i2c msg: flags=0x%x, addr=0x%x, len=0x%x\n",
				msg->flags,
				msg->addr,
				msg->len);
	return status;
}

static struct native_i2c_adapter *
native_adapter_create(int bus, uint16_t client_addr[], int n_client)
{
	int fd;
	struct native_i2c_adapter *native_adapter;
	char native_path[20];
	int i;
	unsigned long funcs;

	if (bus < 0)
		return NULL;

	native_adapter = calloc(1, sizeof(struct native_i2c_adapter));
	if (native_adapter == NULL) {
		WPRINTF("i2c_core: failed to calloc struct virtio_i2c_vdev");
		return NULL;
	}

	snprintf(native_path, sizeof(native_path), "/dev/i2c-%d", bus);
	native_path[sizeof(native_path) - 1] = '\0';

	fd = open(native_path, O_RDWR);
	if (fd < 0) {
		WPRINTF("virtio_i2c: failed to open %s\n", native_path);
		goto fail;
	}
	if (ioctl(fd, I2C_FUNCS, &funcs) < 0) {
		WPRINTF("virtio_i2c: failed to get the funcs \n");
		goto fail;
	}

	if (!(funcs & I2C_FUNC_I2C)) {
		WPRINTF("virtio_i2c: this adapter %s doesn't support I2C_FUNC_I2C mode "
				"(plain i2c-level commands)!\n", native_path);
		goto fail;
	}
	native_adapter->fd = fd;
	native_adapter->bus = bus;
	for (i = 0; i < n_client; i++) {
		if (client_addr[i]) {
			if (native_client_access_ok(native_adapter, client_addr[i])) {
				if (native_adapter->i2cdev_enable[client_addr[i]]) {
					WPRINTF("client addr 0x%x repeat, not allowed.\n", client_addr[i]);
					goto fail;
				}
				native_adapter->i2cdev_enable[client_addr[i]] = true;
				DPRINTF("virtio_i2c: add client 0x%x\n", client_addr[i]);
			} else {
				goto fail;
			}
		}
	}
	return native_adapter;

fail:
	free(native_adapter);
	return NULL;
}

static void
native_adapter_remove(struct virtio_i2c *vi2c)
{
	int i;
	struct native_i2c_adapter *native_adapter;

	for (i = 0; i < MAX_NATIVE_I2C_ADAPTER; i++) {
		native_adapter = vi2c->native_adapter[i];
		if (native_adapter) {
			if (native_adapter->fd > 0)
				close(native_adapter->fd);
			free(native_adapter);
			vi2c->native_adapter[i] = NULL;
		}
	}
}

static void
virtio_i2c_req_stop(struct virtio_i2c *vi2c)
{
	void *jval;

	pthread_mutex_lock(&vi2c->req_mtx);
	vi2c->closing = 1;
	pthread_cond_broadcast(&vi2c->req_cond);
	pthread_mutex_unlock(&vi2c->req_mtx);
	pthread_join(vi2c->req_tid, &jval);
}

static void *
virtio_i2c_proc_thread(void *arg)
{
	struct virtio_i2c *vi2c = arg;
	struct virtio_vq_info *vq = &vi2c->vq;
	struct iovec iov[3];
	uint16_t idx, flags[3];
	struct i2c_msg msg;
	int n;
	bool fail_next = false;
	struct virtio_i2c_out_hdr *out_hdr;
	struct virtio_i2c_in_hdr *in_hdr;

	for (;;) {
		pthread_mutex_lock(&vi2c->req_mtx);

		vi2c->in_process = 0;
		while (!vq_has_descs(vq) && !vi2c->closing)
			pthread_cond_wait(&vi2c->req_cond, &vi2c->req_mtx);

		if (vi2c->closing) {
			pthread_mutex_unlock(&vi2c->req_mtx);
			return NULL;
		}
		vi2c->in_process = 1;
		pthread_mutex_unlock(&vi2c->req_mtx);
		do {
			n = vq_getchain(vq, &idx, iov, 3, flags);
			if (n < 2 || n > 3) {
				WPRINTF("virtio_i2c_proc: failed to get iov from virtqueue\n");
				continue;
			}
			out_hdr = iov[0].iov_base;
			/* From v1.2-cs01 virtio spec, 7-bit address is defined as:
			 * -----------------------------------------------------------
			 *  Bits 	|15|14|13|12|11|10|9|8|7 |6 |5 |4 |3 |2 |1 |0|
			 * -------------+--+--+--+--+--+--+-+-+--+--+--+--+--+--+--+-+
			 * 7-bit address|0 |0 |0 |0 |0 |0 |0|0|A6|A5|A4|A3|A2|A1|A0|0|
			 * -------------+--+--+--+--+--+--+-+-+--+--+--+--+--+--+--+-+
			 */
			msg.addr = out_hdr->addr >> 1;
			if (out_hdr->flags & VIRTIO_I2C_FLAGS_M_RD)
				msg.flags = I2C_M_RD;
			else
				msg.flags = I2C_NO_FLAGS;
			if (n == 3) {
				msg.buf = iov[1].iov_base;
				msg.len = iov[1].iov_len;
				in_hdr = iov[2].iov_base;
			} else {
				// this is a zero-length request
				msg.buf = NULL;
				msg.len = 0;
				in_hdr = iov[1].iov_base;
			}

			/*
			 * From v1.2-cs01 virtio spec:
			 * VIRTIO_I2C_FLAGS_FAIL_NEXT(0) is used to group the requests. For a group requests,
			 * a driver clears this bit on the final request and sets it on the other requests.
			 * If this bit is set and a device fails to process the current request, it needs to
			 * fail the next request instead of attempting to execute it.
			 */
			if (!fail_next) {
				in_hdr->status = native_adapter_proc(vi2c, &msg);
				if ((out_hdr->flags & VIRTIO_I2C_FLAGS_FAIL_NEXT) && (in_hdr->status == I2C_MSG_ERR))
					fail_next = true;
			} else {
				in_hdr->status = I2C_MSG_ERR;
			}
			vq_relchain(vq, idx, 1);

			if (!(out_hdr->flags & VIRTIO_I2C_FLAGS_FAIL_NEXT))
				fail_next = false;
		} while (vq_has_descs(vq));
		vq_endchains(vq, 0);
	}
}

static int
virtio_i2c_map(struct virtio_i2c *vi2c)
{
	int i, client_addr;
	struct native_i2c_adapter *native_adapter;

	/*
	 * Flatten the map for client address and native adapter to the array:
	 *
	 * adapter_map[MAX_I2C_VDEV]:
	 *
	 * Native Adapter | adapter2 | none  | adapter1 | adapter3 | none | none| (val)
	 *                |----------|-------|----------|----------|------|-----|
	 * Slave Address  | addr 1   | none  | addr 2   | addr 3   | none | none| (idx)
	 *                |<-----------------------MAX_I2C_VDEV---------------->|
	 */
	for (i = 0; i < vi2c->native_adapter_num; i++) {
		native_adapter = vi2c->native_adapter[i];
		for (client_addr = 0; client_addr < MAX_I2C_VDEV; client_addr++) {
			if (native_adapter->i2cdev_enable[client_addr]) {
				if (vi2c->adapter_map[client_addr]) {
					WPRINTF("client addr %x repeat, not support!\n", client_addr);
					return -1;
				}
				/* As 0 is the initiate value, + 1 for index */
				vi2c->adapter_map[client_addr] = i + 1;
				DPRINTF("client:%d -> native adapter: %d\n",
							client_addr,
							native_adapter->bus);
			}
		}
	}
	return 0;
}

static int
virtio_i2c_parse(struct virtio_i2c *vi2c, char *optstr)
{
	char *cp, *t, *dsdt_str, *p;
	uint16_t client_addr[MAX_I2C_VDEV];
	int addr, bus, n_adapter, n_client;

	/*
	 * virtio-i2c,<bus>:<client_addr[@<node>]>[:<client_addr[@<node>]>],
	 * [<bus>:<client_addr[@<node>]>[:<client_addr>[@<node>]]]
	 *
	 * bus (dec): native adatper bus number.
	 * 	e.g. 2 for /dev/i2c-2
	 * client_addr (hex): address for native client device
	 * 	e.g. 0x1C or 1C
	 * @<node>: node is the acpi node name defined in acpi_node_table[]
	 * 	e.g. @cam1 means adding 'cam1' node to dsdt table.
	 *
	 * Note: client address can not repeat.
	 */
	n_adapter = 0;
	while (optstr != NULL) {
		cp = strsep(&optstr, ",");
		/*
		 * <bus>:<client_addr>[:<client_addr>]...
		 */
		n_client = 0;
		bus = -1;
		while (cp != NULL && *cp !='\0') {
			if (*cp == ':')
				cp++;

			if (bus == -1) {
				if (dm_strtoi(cp, &t, 10, &bus) || bus < 0)
					return -1;
			} else {
				if (dm_strtoi(cp, &t, 16, &addr) || addr < 0)
					return -1;
				if (n_client > MAX_I2C_VDEV) {
					WPRINTF("too many devices, only support %d \n", MAX_I2C_VDEV);
					return -1;
				}
				client_addr[n_client] = (uint16_t)(addr & (MAX_I2C_VDEV - 1));
				p = &vi2c->acpi_nodes[client_addr[n_client]][0];
				if (t != NULL && *t == '@') {
					t++;
					dsdt_str = strsep(&t, ":,");
					snprintf(p, MAX_NODE_NAME_LEN, "%s", dsdt_str);
				} else {
					snprintf(p, MAX_NODE_NAME_LEN, "default");
				}
				DPRINTF("native i2c adapter %d:0x%x (%s)\n",
						bus,
						client_addr[n_client],
						p);
				n_client++;
			}
			cp = t;
		}
		if (n_adapter >= MAX_NATIVE_I2C_ADAPTER) {
			WPRINTF("too many adapter, only support %d \n", MAX_NATIVE_I2C_ADAPTER);
			return -1;
		}
		vi2c->native_adapter[n_adapter] = native_adapter_create(bus, client_addr, n_client);
		if (!vi2c->native_adapter[n_adapter])
			return -1;
		n_adapter++;
	}
	vi2c->native_adapter_num = n_adapter;

	return 0;
}

static void
virtio_i2c_dsdt(struct pci_vdev *dev)
{
	int i, j, node_num;
	struct acpi_node *anode;
	int i2c_bus;
	int found;
	struct virtio_i2c *vi2c = (struct virtio_i2c *) dev->arg;

	/* i2c bus number in acpi start from 0 */
	i2c_bus = acpi_i2c_adapter_num;
	/* add i2c adapter */
	acpi_add_i2c_adapter(dev, i2c_bus);
	DPRINTF("add dsdt for i2c adapter %d\n", i2c_bus);

	/* add client devices */
	node_num = sizeof(acpi_node_table) / sizeof(struct acpi_node);
	for (i = 0; i < MAX_I2C_VDEV; i++) {
		if (!native_adapter_find(vi2c, i))
			continue;

		found = 0;
		for (j = 0; j < node_num; j++) {
			anode = &acpi_node_table[j];
			if (!strncmp(anode->node_name, vi2c->acpi_nodes[i], sizeof(anode->node_name))) {
				found = 1;
				if (anode->add_node_fn) {
					anode->add_node_fn(dev, i2c_bus);
					DPRINTF("add dsdt for %s \n", anode->node_name);
				}
			}
		}
		if (!found)
			WPRINTF("cannot find acpi node info for %s \n", vi2c->acpi_nodes[i]);
	}
	acpi_i2c_adapter_num++;
}

static void
virtio_i2c_reset(void *vdev)
{
	struct virtio_i2c *vi2c = vdev;

	DPRINTF("device reset requested !\n");
	virtio_reset_dev(&vi2c->base);
}

static void
virtio_i2c_notify(void *vdev, struct virtio_vq_info *vq)
{
	struct virtio_i2c *vi2c = vdev;

	if (!vq_has_descs(vq))
		return;

	pthread_mutex_lock(&vi2c->req_mtx);
	if (!vi2c->in_process)
		pthread_cond_signal(&vi2c->req_cond);
	pthread_mutex_unlock(&vi2c->req_mtx);
}

static int
virtio_i2c_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	u_char digest[16];
	struct virtio_i2c *vi2c;
	pthread_mutexattr_t attr;
	int rc = -1;

	vi2c = calloc(1, sizeof(struct virtio_i2c));
	if (!vi2c) {
		WPRINTF("calloc returns NULL\n");
		return -ENOMEM;
	}

	if (virtio_i2c_parse(vi2c, opts)) {
		WPRINTF("failed to parse parameters %s \n", opts);
		goto mtx_fail;
	}

	if (virtio_i2c_map(vi2c))
		goto mtx_fail;

	/* init mutex attribute properly to avoid deadlock */
	rc = pthread_mutexattr_init(&attr);
	if (rc) {
		WPRINTF("mutexattr init failed with erro %d!\n", rc);
		goto mtx_fail;
	}
	rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
	if (rc) {
		WPRINTF("mutexattr_settype failed with "
					"error %d!\n", rc);
		goto mtx_fail;
	}

	rc = pthread_mutex_init(&vi2c->mtx, &attr);
	if (rc) {
		WPRINTF("pthread_mutex_init failed with "
					"error %d!\n", rc);
		goto mtx_fail;
	}

	/* init virtio struct and virtqueues */
	virtio_linkup(&vi2c->base, &virtio_i2c_ops, vi2c, dev, &vi2c->vq, BACKEND_VBSU);
	vi2c->base.mtx = &vi2c->mtx;
	vi2c->base.device_caps = VIRTIO_I2C_HOSTCAPS;
	vi2c->vq.qsize = 64;
	vi2c->native_adapter_num = 0;

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
	EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
	EVP_DigestUpdate(mdctx, "vi2c", strlen("vi2c"));
	EVP_DigestFinal_ex(mdctx, digest, NULL);
	EVP_MD_CTX_free(mdctx);
#else
	MD5_CTX mdctx;
	MD5_Init(&mdctx);
	MD5_Update(&mdctx, "vi2c", strlen("vi2c"));
	MD5_Final(digest, &mdctx);
#endif
	rc = snprintf(vi2c->ident, sizeof(vi2c->ident),
		"ACRN--%02X%02X-%02X%02X-%02X%02X", digest[0],
		digest[1], digest[2], digest[3], digest[4],
		digest[5]);
	if (rc < 0) {
		WPRINTF("create ident failed");
		goto fail;
	}
	if (rc >= sizeof(vi2c->ident)) {
		WPRINTF("ident too long\n");
	}

	pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_I2C);
	pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
	pci_set_cfgdata8(dev, PCIR_CLASS, PCIS_SERIALBUS_SMBUS);
	pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_I2C);
	pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);

	if (virtio_interrupt_init(&vi2c->base, virtio_uses_msix())) {
		WPRINTF("failed to init interrupt");
		rc = -1;
		goto fail;
	}
	rc = virtio_set_modern_bar(&vi2c->base, false);
	vi2c->in_process = 0;
	vi2c->closing = 0;
	pthread_mutex_init(&vi2c->req_mtx, NULL);
	pthread_cond_init(&vi2c->req_cond, NULL);
	pthread_create(&vi2c->req_tid, NULL, virtio_i2c_proc_thread, vi2c);
	pthread_setname_np(vi2c->req_tid, "virtio-i2c");
	return 0;

fail:
	pthread_mutex_destroy(&vi2c->mtx);
mtx_fail:
	native_adapter_remove(vi2c);
	free(vi2c);
	return rc;
}

static void
virtio_i2c_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	struct virtio_i2c *vi2c;

	if (dev->arg) {
		DPRINTF("deinit\n");
		vi2c = (struct virtio_i2c *) dev->arg;
		virtio_i2c_req_stop(vi2c);
		native_adapter_remove(vi2c);
		pthread_mutex_destroy(&vi2c->req_mtx);
		pthread_mutex_destroy(&vi2c->mtx);
		virtio_i2c_reset(vi2c);
		acpi_i2c_adapter_num--;
		assert(acpi_i2c_adapter_num >= 0);
		free(vi2c);
		dev->arg = NULL;
	}
}

struct pci_vdev_ops pci_ops_virtio_i2c = {
	.class_name		= "virtio-i2c",
	.vdev_init		= virtio_i2c_init,
	.vdev_deinit		= virtio_i2c_deinit,
	.vdev_barwrite		= virtio_pci_write,
	.vdev_barread		= virtio_pci_read,
	.vdev_write_dsdt	= virtio_i2c_dsdt,
};
DEFINE_PCI_DEVTYPE(pci_ops_virtio_i2c);
