/*-
 * Copyright (c) 2016 iXsystems Inc.
 * All rights reserved.
 *
 * This software was developed by Jakub Klama <jceel@FreeBSD.org>
 * under sponsorship from iXsystems Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/uio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <termios.h>
#include <limits.h>

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

#define	VIRTIO_CONSOLE_RINGSZ	64
#define	VIRTIO_CONSOLE_MAXPORTS	16
#define	VIRTIO_CONSOLE_MAXQ	(VIRTIO_CONSOLE_MAXPORTS * 2 + 2)

#define	VIRTIO_CONSOLE_DEVICE_READY	0
#define	VIRTIO_CONSOLE_DEVICE_ADD	1
#define	VIRTIO_CONSOLE_DEVICE_REMOVE	2
#define	VIRTIO_CONSOLE_PORT_READY	3
#define	VIRTIO_CONSOLE_CONSOLE_PORT	4
#define	VIRTIO_CONSOLE_CONSOLE_RESIZE	5
#define	VIRTIO_CONSOLE_PORT_OPEN	6
#define	VIRTIO_CONSOLE_PORT_NAME	7

#define	VIRTIO_CONSOLE_F_SIZE		0
#define	VIRTIO_CONSOLE_F_MULTIPORT	1
#define	VIRTIO_CONSOLE_F_EMERG_WRITE	2
#define	VIRTIO_CONSOLE_S_HOSTCAPS	\
	(VIRTIO_CONSOLE_F_SIZE |	\
	VIRTIO_CONSOLE_F_MULTIPORT |	\
	VIRTIO_CONSOLE_F_EMERG_WRITE)

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

struct virtio_console;
struct virtio_console_port;
struct virtio_console_config;
typedef void (virtio_console_cb_t)(struct virtio_console_port *, void *,
				   struct iovec *, int);

enum virtio_console_be_type {
	VIRTIO_CONSOLE_BE_STDIO = 0,
	VIRTIO_CONSOLE_BE_TTY,
	VIRTIO_CONSOLE_BE_PTY,
	VIRTIO_CONSOLE_BE_FILE,
	VIRTIO_CONSOLE_BE_SOCKET,
	VIRTIO_CONSOLE_BE_MAX,
	VIRTIO_CONSOLE_BE_INVALID = VIRTIO_CONSOLE_BE_MAX
};

struct virtio_console_port {
	struct virtio_console	*console;
	int			id;
	const char		*name;
	bool			enabled;
	bool			is_console;
	bool			rx_ready;
	bool			open;
	int			rxq;
	int			txq;
	void			*arg;
	virtio_console_cb_t	*cb;
};

struct virtio_console_backend {
	struct virtio_console_port	*port;
	struct mevent			*evp;
	struct mevent			*conn_evp;
	int				fd;
	int				server_fd;
	bool				open;
	enum virtio_console_be_type	be_type;
	int				pts_fd;	/* only valid for PTY */
	const char 			*portpath;
	const char 			*socket_type;
};

struct virtio_console {
	struct virtio_base		base;
	struct virtio_vq_info		queues[VIRTIO_CONSOLE_MAXQ];
	pthread_mutex_t			mtx;
	uint64_t			cfg;
	uint64_t			features;
	int				nports;
	bool				ready;
	struct virtio_console_port	control_port;
	struct virtio_console_port	ports[VIRTIO_CONSOLE_MAXPORTS];
	struct virtio_console_config	*config;
	int				ref_count;
};

struct virtio_console_config {
	uint16_t cols;
	uint16_t rows;
	uint32_t max_nr_ports;
	uint32_t emerg_wr;
} __attribute__((packed));

struct virtio_console_control {
	uint32_t id;
	uint16_t event;
	uint16_t value;
} __attribute__((packed));

struct virtio_console_console_resize {
	uint16_t cols;
	uint16_t rows;
} __attribute__((packed));

static void virtio_console_reset(void *);
static void virtio_console_notify_rx(void *, struct virtio_vq_info *);
static void virtio_console_notify_tx(void *, struct virtio_vq_info *);
static int virtio_console_cfgread(void *, int, int, uint32_t *);
static void virtio_console_neg_features(void *, uint64_t);
static void virtio_console_control_send(struct virtio_console *,
	struct virtio_console_control *, const void *, size_t);
static void virtio_console_announce_port(struct virtio_console_port *);
static void virtio_console_open_port(struct virtio_console_port *, bool);
static void virtio_console_teardown_backend(void *);

static struct virtio_ops virtio_console_ops = {
	"vtcon",			/* our name */
	VIRTIO_CONSOLE_MAXQ,		/* we support VTCON_MAXQ virtqueues */
	sizeof(struct virtio_console_config), /* config reg size */
	virtio_console_reset,		/* reset */
	NULL,				/* device-wide qnotify */
	virtio_console_cfgread,		/* read virtio config */
	NULL,				/* write virtio config */
	virtio_console_neg_features,	/* apply negotiated features */
	NULL,				/* called on guest set status */
};

static const char *virtio_console_be_table[VIRTIO_CONSOLE_BE_MAX] = {
	[VIRTIO_CONSOLE_BE_STDIO]	= "stdio",
	[VIRTIO_CONSOLE_BE_TTY]		= "tty",
	[VIRTIO_CONSOLE_BE_PTY]		= "pty",
	[VIRTIO_CONSOLE_BE_FILE]	= "file",
	[VIRTIO_CONSOLE_BE_SOCKET]	= "socket"
};

static struct termios virtio_console_saved_tio;
static int virtio_console_saved_flags;

static void
virtio_console_reset(void *vdev)
{
	struct virtio_console *console;

	console = vdev;

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

static void
virtio_console_neg_features(void *vdev, uint64_t negotiated_features)
{
	struct virtio_console *console = vdev;

	console->features = negotiated_features;
}

static int
virtio_console_cfgread(void *vdev, int offset, int size, uint32_t *retval)
{
	struct virtio_console *console = vdev;
	void *ptr;

	ptr = (uint8_t *)console->config + offset;
	memcpy(retval, ptr, size);
	return 0;
}

static inline struct virtio_console_port *
virtio_console_vq_to_port(struct virtio_console *console,
			  struct virtio_vq_info *vq)
{
	uint16_t num = vq->num;

	if (num == 0 || num == 1)
		return &console->ports[0];

	if (num == 2 || num == 3)
		return &console->control_port;

	return &console->ports[(num / 2) - 1];
}

static inline struct virtio_vq_info *
virtio_console_port_to_vq(struct virtio_console_port *port, bool tx_queue)
{
	int qnum;

	qnum = tx_queue ? port->txq : port->rxq;
	return &port->console->queues[qnum];
}

static struct virtio_console_port *
virtio_console_add_port(struct virtio_console *console, const char *name,
			virtio_console_cb_t *cb, void *arg, bool is_console)
{
	struct virtio_console_port *port;

	if (console->nports == VIRTIO_CONSOLE_MAXPORTS) {
		errno = EBUSY;
		return NULL;
	}

	port = &console->ports[console->nports++];
	port->id = console->nports - 1;
	port->console = console;
	port->name = name;
	port->cb = cb;
	port->arg = arg;
	port->is_console = is_console;

	if (port->id == 0) {
		/* port0 */
		port->txq = 0;
		port->rxq = 1;
	} else {
		port->txq = console->nports * 2;
		port->rxq = port->txq + 1;
	}

	port->enabled = true;
	return port;
}

static void
virtio_console_control_tx(struct virtio_console_port *port, void *arg,
			  struct iovec *iov, int niov __attribute__((unused)))
{
	struct virtio_console *console;
	struct virtio_console_port *tmp;
	struct virtio_console_control resp, *ctrl;
	int i;

	console = port->console;
	ctrl = (struct virtio_console_control *)iov->iov_base;

	if ((console == NULL) || (ctrl == NULL))
		return;

	switch (ctrl->event) {
	case VIRTIO_CONSOLE_DEVICE_READY:
		console->ready = true;
		/* set port ready events for registered ports */
		for (i = 0; i < VIRTIO_CONSOLE_MAXPORTS; i++) {
			tmp = &console->ports[i];
			if (tmp->enabled)
				virtio_console_announce_port(tmp);

			if (tmp->open)
				virtio_console_open_port(tmp, true);
		}
		break;

	case VIRTIO_CONSOLE_PORT_READY:
		if (ctrl->id >= console->nports) {
			WPRINTF(("VTCONSOLE_PORT_READY for unknown port %d\n",
			    ctrl->id));
			return;
		}

		tmp = &console->ports[ctrl->id];
		if (tmp->is_console) {
			resp.event = VIRTIO_CONSOLE_CONSOLE_PORT;
			resp.id = ctrl->id;
			resp.value = 1;
			virtio_console_control_send(console, &resp, NULL, 0);
		}
		break;
	}
}

static void
virtio_console_announce_port(struct virtio_console_port *port)
{
	struct virtio_console_control event;

	event.id = port->id;
	event.event = VIRTIO_CONSOLE_DEVICE_ADD;
	event.value = 1;
	virtio_console_control_send(port->console, &event, NULL, 0);

	event.event = VIRTIO_CONSOLE_PORT_NAME;
	virtio_console_control_send(port->console, &event, port->name,
	    strnlen(port->name, NAME_MAX));
}

static void
virtio_console_open_port(struct virtio_console_port *port, bool open)
{
	struct virtio_console_control event;

	if (!port->console->ready) {
		port->open = true;
		return;
	}

	event.id = port->id;
	event.event = VIRTIO_CONSOLE_PORT_OPEN;
	event.value = (int)open;
	virtio_console_control_send(port->console, &event, NULL, 0);
}

static void
virtio_console_control_send(struct virtio_console *console,
			    struct virtio_console_control *ctrl,
			    const void *payload, size_t len)
{
	struct virtio_vq_info *vq;
	struct iovec iov;
	uint16_t idx;
	int n;

	vq = virtio_console_port_to_vq(&console->control_port, true);

	if (!vq_has_descs(vq))
		return;

	n = vq_getchain(vq, &idx, &iov, 1, NULL);
	if (n < 1) {
		WPRINTF(("vtcon: control_send vq_getchain error %d\n", n));
		return;
	}

	memcpy(iov.iov_base, ctrl, sizeof(struct virtio_console_control));
	if (payload != NULL && len > 0)
		memcpy(iov.iov_base + sizeof(struct virtio_console_control),
		     payload, len);

	vq_relchain(vq, idx, sizeof(struct virtio_console_control) + len);
	vq_endchains(vq, 1);
}

static void
virtio_console_notify_tx(void *vdev, struct virtio_vq_info *vq)
{
	struct virtio_console *console;
	struct virtio_console_port *port;
	struct iovec iov[1];
	uint16_t idx;
	uint16_t flags[8];

	console = vdev;
	port = virtio_console_vq_to_port(console, vq);

	while (vq_has_descs(vq)) {
		if (vq_getchain(vq, &idx, iov, 1, flags) < 1) {
			pr_err("%s: fail to getchain!\n", __func__);
			break;
		}
		if ((port != NULL) && (port->cb != NULL))
			port->cb(port, port->arg, iov, 1);

		/*
		 * Release this chain and handle more
		 */
		vq_relchain(vq, idx, 0);
	}
	vq_endchains(vq, 1);	/* Generate interrupt if appropriate. */
}

static void
virtio_console_notify_rx(void *vdev, struct virtio_vq_info *vq)
{
	struct virtio_console *console;
	struct virtio_console_port *port;

	console = vdev;
	port = virtio_console_vq_to_port(console, vq);

	if (!port->rx_ready) {
		port->rx_ready = 1;
		if (vq_has_descs(vq)) {
			vq->used->flags |= VRING_USED_F_NO_NOTIFY;
		}
	}
}

static void
virtio_console_reset_backend(struct virtio_console_backend *be)
{
	if (!be)
		return;

	if (be->evp)
		mevent_disable(be->evp);
	if (be->fd != STDIN_FILENO)
		close(be->fd);
	be->fd = -1;
	be->open = false;
}

static void
virtio_console_socket_clear(struct virtio_console_backend *be)
{
	if (be->conn_evp) {
		mevent_delete(be->conn_evp);
		be->conn_evp = NULL;
	}
	if (be->fd != -1) {
		close(be->fd);
		be->fd = -1;
	}
}

static void
virtio_console_backend_read(int fd __attribute__((unused)),
			    enum ev_type t __attribute__((unused)),
			    void *arg)
{
	struct virtio_console_port *port;
	struct virtio_console_backend *be = arg;
	struct virtio_vq_info *vq;
	struct iovec iov;
	static char dummybuf[2048];
	int len, n;
	uint16_t idx;

	port = be->port;
	vq = virtio_console_port_to_vq(port, true);

	if (!be->open || !port->rx_ready || !vq_ring_ready(vq)) {
		len = read(be->fd, dummybuf, sizeof(dummybuf));
		if (len == 0)
			goto close;
		return;
	}

	if (!vq_has_descs(vq)) {
		len = read(be->fd, dummybuf, sizeof(dummybuf));
		vq_endchains(vq, 1);
		if (len == 0)
			goto close;
		return;
	}

	do {
		n = vq_getchain(vq, &idx, &iov, 1, NULL);
		if (n < 1){
			pr_err("%s: fail to getchain!\n", __func__);
			break;
		}
		len = readv(be->fd, &iov, n);
		if (len <= 0) {
			vq_retchain(vq);
			vq_endchains(vq, 0);

			/* no data available */
			if (len == -1 && errno == EAGAIN)
				return;

			/* when client User VM reboot or shutdown,
			 * be->fd will be closed, then the return
			 * value of readv function will be 0 */
			if (len == 0 || errno == ECONNRESET)
				goto clear;
			/* any other errors */
			goto close;
		}

		vq_relchain(vq, idx, len);
	} while (vq_has_descs(vq));

	vq_endchains(vq, 1);
	return;

close:
	virtio_console_reset_backend(be);
	WPRINTF(("vtcon: be read failed and close! len = %d, errno = %d\n",
		len, errno));
clear:
	if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
		|| !strcmp(be->socket_type,"server"))) {
		virtio_console_socket_clear(be);
	} else if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET
		&& !strcmp(be->socket_type,"client")) {
		virtio_console_reset_backend(be);
		WPRINTF(("vtcon: be read failed and close! len = %d, errno = %d\n",
			len, errno));
	}
}

static void
virtio_console_backend_write(struct virtio_console_port *port, void *arg,
			     struct iovec *iov, int niov)
{
	struct virtio_console_backend *be;
	int ret;

	be = arg;

	if (be->fd == -1)
		return;

	ret = writev(be->fd, iov, niov);
	if (ret <= 0) {
		/* Case 1:backend cannot receive more data. For example when pts is
		 * not connected to any client, its tty buffer will become full.
		 * In this case we just drop data from guest hvc console.
		 *
		 * Case 2: Backend connection not yet setup. For example, when
		 * virtio-console is used as console port with socket backend, guest
		 * kernel tries to hook it up with hvc console and sets it up. It
		 * doesn't check if a client is connected and can result in ENOTCONN
		 * with virtio-console backend being reset. This will prevent
		 * client connection at a later point. To avoid this, ignore
		 * ENOTCONN error.
		 *
		 * PS: For Kata, the runtime first launches VM and then proxy which
		 * acts as a client connects to this socket.
		 */
		if (ret == -1 && (errno == EAGAIN || errno == ENOTCONN))
			return;

		if (ret == -1 && errno == EBADF) {
			if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
				|| !strcmp(be->socket_type,"server"))) {
				virtio_console_socket_clear(be);
				return;
			}
		}
		virtio_console_reset_backend(be);
		WPRINTF(("vtcon: be write failed! errno = %d\n", errno));
	}
}

static void
virtio_console_restore_stdio(void)
{
	tcsetattr(STDIN_FILENO, TCSANOW, &virtio_console_saved_tio);
	fcntl(STDIN_FILENO, F_SETFL, virtio_console_saved_flags);
	stdio_in_use = false;
}

static bool
virtio_console_backend_can_read(enum virtio_console_be_type be_type)
{
	return (be_type == VIRTIO_CONSOLE_BE_FILE) ? false : true;
}

static int
virtio_console_open_backend(const char *path,
			    enum virtio_console_be_type be_type)
{
	int fd = -1;

	switch (be_type) {
	case VIRTIO_CONSOLE_BE_PTY:
		fd = posix_openpt(O_RDWR | O_NOCTTY);
		if (fd == -1)
			WPRINTF(("vtcon: posix_openpt failed, errno = %d\n",
				errno));
		else if (grantpt(fd) == -1 || unlockpt(fd) == -1) {
			WPRINTF(("vtcon: grant/unlock failed, errno = %d\n",
				errno));
			close(fd);
			fd = -1;
		}
		break;
	case VIRTIO_CONSOLE_BE_STDIO:
		if (stdio_in_use) {
			WPRINTF(("vtcon: stdio is used by other device\n"));
			break;
		}
		fd = STDIN_FILENO;
		stdio_in_use = true;
		break;
	case VIRTIO_CONSOLE_BE_TTY:
		fd = open(path, O_RDWR | O_NONBLOCK);
		if (fd < 0)
			WPRINTF(("vtcon: open failed: %s\n", path));
		else if (!isatty(fd)) {
			WPRINTF(("vtcon: not a tty: %s\n", path));
			close(fd);
			fd = -1;
		}
		break;
	case VIRTIO_CONSOLE_BE_FILE:
		fd = open(path, O_WRONLY|O_CREAT|O_APPEND|O_NONBLOCK, 0666);
		if (fd < 0)
			WPRINTF(("vtcon: open failed: %s\n", path));
		break;
	case VIRTIO_CONSOLE_BE_SOCKET:
		fd = socket(AF_UNIX, SOCK_STREAM, 0);
		if (fd < 0)
			WPRINTF(("vtcon: socket open failed \n"));
		break;
	default:
		WPRINTF(("not supported backend %d!\n", be_type));
	}

	return fd;
}

static int
make_socket_non_blocking(int fd)
{
	int flags, s;

	flags = fcntl(fd, F_GETFL, 0);
	if (flags == -1) {
		WPRINTF(("fcntl get failed =%d\n", errno));
		return -1;
	}

	flags |= O_NONBLOCK;
	s = fcntl(fd, F_SETFL, flags);
	if (s == -1) {
		WPRINTF(("fcntl set failed =%d\n", errno));
		return -1;
	}

	return 0;
}

static void
virtio_console_accept_new_connection(int fd __attribute__((unused)),
					enum ev_type t __attribute__((unused)), void *arg)
{

	int accepted_fd;
	uint32_t len;
	struct sockaddr_un addr;
	struct virtio_console_backend *be = arg;

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strncpy(addr.sun_path, be->portpath, sizeof(addr.sun_path));
	addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';

	len = sizeof(addr);
	/* be->server_fd is kept for client User VM reconnect again */
	accepted_fd = accept(be->server_fd, (struct sockaddr *)&addr, &len);
	if (accepted_fd == -1) {
		WPRINTF(("accept error= %d, addr.sun_path=%s\n", errno, addr.sun_path));
		return;
	} else {
		be->fd = accepted_fd;
	}

	be->conn_evp = mevent_add(be->fd, EVF_READ, virtio_console_backend_read, be,
				NULL, NULL);
	if (be->conn_evp == NULL) {
		WPRINTF(("accepted fd mevent_add failed\n"));
		return;
	}

	if (make_socket_non_blocking(be->fd) == -1) {
		WPRINTF(("accepted fd non-blocking failed\n"));
		return;
	}
}

static int
virtio_console_config_backend(struct virtio_console_backend *be)
{
	int fd, flags;
	char *pts_name = NULL;
	int client_fd = -1;
	struct termios tio, saved_tio;
	struct sockaddr_un addr;

	if (!be || be->fd == -1)
		return -1;

	fd = be->fd;
	switch (be->be_type) {
	case VIRTIO_CONSOLE_BE_PTY:
		pts_name = ptsname(fd);
		if (pts_name == NULL) {
			WPRINTF(("vtcon: ptsname return NULL, errno = %d\n",
				errno));
			return -1;
		}

		client_fd = open(pts_name, O_RDWR);
		if (client_fd == -1) {
			WPRINTF(("vtcon: client_fd open failed, errno = %d\n",
				errno));
			return -1;
		}

		tcgetattr(client_fd, &tio);
		cfmakeraw(&tio);
		tcsetattr(client_fd, TCSAFLUSH, &tio);
		be->pts_fd = client_fd;

		WPRINTF(("***********************************************\n"));
		WPRINTF(("virt-console backend redirected to %s\n", pts_name));
		WPRINTF(("***********************************************\n"));

		flags = fcntl(fd, F_GETFL);
		fcntl(fd, F_SETFL, flags | O_NONBLOCK);
		break;
	case VIRTIO_CONSOLE_BE_TTY:
	case VIRTIO_CONSOLE_BE_STDIO:
		tcgetattr(fd, &tio);
		saved_tio = tio;
		cfmakeraw(&tio);
		tio.c_cflag |= CLOCAL;
		tio.c_oflag |= OPOST;
		tcsetattr(fd, TCSANOW, &tio);

		if (be->be_type == VIRTIO_CONSOLE_BE_STDIO) {
			flags = fcntl(fd, F_GETFL);
			fcntl(fd, F_SETFL, flags | O_NONBLOCK);

			virtio_console_saved_flags = flags;
			virtio_console_saved_tio = saved_tio;
			atexit(virtio_console_restore_stdio);
		}
		break;
	case VIRTIO_CONSOLE_BE_SOCKET:
		if (be->portpath == NULL) {
			WPRINTF(("vtcon: portpath is NULL\n"));
			return -1;
		}

		memset(&addr, 0, sizeof(addr));
		addr.sun_family = AF_UNIX;
		strncpy(addr.sun_path, be->portpath, sizeof(addr.sun_path));
		addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';

		if (be->socket_type == NULL || !strcmp(be->socket_type,"server")) {
			unlink(be->portpath);
			if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
				WPRINTF(("Bind Error = %d\n", errno));
				return -1;
			}
			if (listen(fd, 64) == -1) {
				WPRINTF(("Listen Error= %d\n", errno));
				return -1;
			}
			if (make_socket_non_blocking(fd) == -1) {
				WPRINTF(("Backend config: fcntl Error\n"));
				return -1;
			}
		} else if (!strcmp(be->socket_type,"client")) {
			if (access(be->portpath,0)) {
				WPRINTF(("%s not exist\n", be->portpath));
				return -1;
			}
			if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
				WPRINTF(("vtcon: connect error[%d] \n", errno));
			} else {
				if (make_socket_non_blocking(fd) == -1) {
					WPRINTF(("Backend config: fcntl Error\n"));
				}
			}
		} else {
			WPRINTF(("Socket type not exist\n"));
			return -1;
		}

	default:
		break; /* nothing to do */
	}

	return 0;
}

static enum virtio_console_be_type
virtio_console_get_be_type(const char *backend)
{
	int i;

	for (i = 0; i < VIRTIO_CONSOLE_BE_MAX; i++)
		if (strcasecmp(backend, virtio_console_be_table[i]) == 0)
			return i;

	return VIRTIO_CONSOLE_BE_INVALID;
}

static int
virtio_console_add_backend(struct virtio_console *console, char *opt)
{
	struct virtio_console_backend *be;
	int error = 0, fd = -1;
	bool is_console = false;
	char *backend = NULL;
	char *portname = NULL;
	char *portpath = NULL;
	char *socket_type = NULL;
	enum virtio_console_be_type be_type = VIRTIO_CONSOLE_BE_INVALID;

	backend = strsep(&opt, ":");
	if (backend == NULL) {
		WPRINTF(("vtcon: no backend is specified!\n"));
		error = -1;
		goto parse_fail;
	}

	if (backend[0] == '@') {
		is_console = true;
		backend++;
	} else
		is_console = false;

	be_type = virtio_console_get_be_type(backend);
	if (be_type == VIRTIO_CONSOLE_BE_INVALID) {
		WPRINTF(("vtcon: invalid backend %s!\n",
			backend));
		error = -1;
		goto parse_fail;
	}

	if (opt != NULL) {
		if (be_type == VIRTIO_CONSOLE_BE_SOCKET) {
			portname = strsep(&opt, "=");
			portpath = strsep(&opt, ":");
			socket_type = opt;
		} else {
			portname = strsep(&opt, "=");
			portpath = opt;
		}
		if (portname == NULL) {
			WPRINTF(("vtcon: portname missing \n"));
			error = -1;
			goto parse_fail;
		}
		if (portpath == NULL
			&& be_type != VIRTIO_CONSOLE_BE_STDIO
			&& be_type != VIRTIO_CONSOLE_BE_PTY
			&& be_type != VIRTIO_CONSOLE_BE_SOCKET) {
			WPRINTF(("vtcon: portpath missing for %s\n",
				portname));
			error = -1;
			goto parse_fail;
		}
	} else {
		WPRINTF(("vtcon: please at least config portname\n"));
		error = -1;
		goto parse_fail;
	}

	be = calloc(1, sizeof(struct virtio_console_backend));
	if (be == NULL) {
		error = -1;
		goto out;
	}

	fd = virtio_console_open_backend(portpath, be_type);
	if (fd < 0) {
		error = -1;
		goto out;
	}

	be->fd = fd;
	be->server_fd = fd;
	be->be_type = be_type;
	be->portpath = portpath;
	be->socket_type = socket_type;

	if (virtio_console_config_backend(be) < 0) {
		WPRINTF(("vtcon: virtio_console_config_backend failed\n"));
		error = -1;
		goto out;
	}

	be->port = virtio_console_add_port(console, portname,
		virtio_console_backend_write, be, is_console);
	if (be->port == NULL) {
		WPRINTF(("vtcon: virtio_console_add_port failed\n"));
		error = -1;
		goto out;
	}

	if (virtio_console_backend_can_read(be_type)) {
		if (be->be_type == VIRTIO_CONSOLE_BE_SOCKET && (be->socket_type == NULL
			|| !strcmp(be->socket_type,"server"))) {
			be->evp = mevent_add(fd, EVF_READ,
					virtio_console_accept_new_connection, be,
					virtio_console_teardown_backend, be);
			if (be->evp == NULL) {
				WPRINTF(("vtcon: mevent_add failed\n"));
				error = -1;
				goto out;
			}
			console->ref_count++;
		}
		else if (isatty(fd) || (be->be_type == VIRTIO_CONSOLE_BE_SOCKET
			&& !strcmp(be->socket_type,"client"))) {
			be->evp = mevent_add(fd, EVF_READ,
					virtio_console_backend_read, be,
					virtio_console_teardown_backend, be);
			if (be->evp == NULL) {
				WPRINTF(("vtcon: mevent_add failed\n"));
				error = -1;
				goto out;
			}
			console->ref_count++;
		}
	}

	virtio_console_open_port(be->port, true);
	be->open = true;

out:
	if (error != 0) {
		if (be) {
			if (be->port) {
				be->port->enabled = false;
				be->port->arg = NULL;
			}
			if (be->be_type == VIRTIO_CONSOLE_BE_PTY &&
				be->pts_fd > 0)
				close(be->pts_fd);
			free(be);
		}
		if (fd != -1 && fd != STDIN_FILENO)
			close(fd);
	}

parse_fail:
	return error;
}

static int
virtio_console_add_backends(struct virtio_console *console, char *opts)
{
	char *opt;

	/* virtio-console,[@]stdio|tty|pty|file:portname[=portpath]
	 * [,[@]stdio|tty|pty|file:portname[=portpath][:socket_type]]
	 */
	while ((opt = strsep(&opts, ",")) != NULL) {
		if (virtio_console_add_backend(console, opt))
			return -1;
	}
	return 0;
}

static void
virtio_console_close_backend(struct virtio_console_backend *be)
{
	if (!be)
		return;

	switch (be->be_type) {
	case VIRTIO_CONSOLE_BE_PTY:
		if (be->pts_fd > 0) {
			close(be->pts_fd);
			be->pts_fd = -1;
		}
		break;
	case VIRTIO_CONSOLE_BE_STDIO:
		virtio_console_restore_stdio();
		break;
	case VIRTIO_CONSOLE_BE_SOCKET:
		if (be->socket_type == NULL || !strcmp(be->socket_type,"server")) {
			virtio_console_socket_clear(be);
			if (be->server_fd > 0) {
				close(be->server_fd);
				be->server_fd = -1;
			}
		}
		break;
	default:
		break;
	}

	if (be->be_type != VIRTIO_CONSOLE_BE_STDIO && be->fd > 0) {
		close(be->fd);
		be->fd = -1;
	}

	memset(be->port, 0, sizeof(*be->port));
	free(be);
}

static void
virtio_console_destroy(struct virtio_console *console)
{
	if (console) {
		virtio_console_reset(console);
		if (console->config)
			free(console->config);
		free(console);
		console = NULL;
	}
}

static void
virtio_console_teardown_backend(void *param)
{
	struct virtio_console *console = NULL;
	struct virtio_console_backend *be;

	be = (struct virtio_console_backend *)param;
	if (!be)
		return;

	if (be->port)
		console = be->port->console;

	virtio_console_close_backend(be);

	if (console) {
		console->ref_count--;
		/* free virtio_console if this is the last backend */
		if (console->ref_count == 0)
			virtio_console_destroy(console);
	}
}

static int
virtio_console_close_all(struct virtio_console *console)
{
	int i, rc = 0, nports_num;
	struct virtio_console_port *port;
	struct virtio_console_backend *be;

	/*
	 * we should close ports without mevent first.
	 * port->enabled is reset to false when a backend is closed.
	 */
	for (i = 0; i < console->nports; i++) {
		port = &console->ports[i];

		if (!port->enabled)
			continue;

		be = (struct virtio_console_backend *)port->arg;
		if (be && !be->evp)
			virtio_console_close_backend(be);
	}
	/*
	 * The mevent_delete function will call teardown to delete
	 * the console if the console->nports = 1, after the console
	 * be destroyed there will be a NULL pointer issue.
	 * Why don't check the console ? because the console delete
	 * is not at the same thread.
	 * */

	nports_num = console->nports;
	for (i = 0; i < nports_num; i++) {
		port = &console->ports[i];

		if (!port->enabled)
			continue;

		be = (struct virtio_console_backend *)port->arg;
		if (be && be->evp) {
			/* resources will be freed in the teardown callback */
			mevent_delete(be->evp);
			rc = 1;
		}
	}

	return rc;
}

static int
virtio_console_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	struct virtio_console *console;
	int i;
	pthread_mutexattr_t attr;
	int rc;

	if (!opts) {
		WPRINTF(("vtcon: invalid opts\n"));
		return -1;
	}

	console = calloc(1, sizeof(struct virtio_console));
	if (!console) {
		WPRINTF(("vtcon: calloc returns NULL\n"));
		return -1;
	}

	console->config = calloc(1, sizeof(struct virtio_console_config));
	if (!console->config) {
		WPRINTF(("vtcon->config: calloc returns NULL\n"));
		free(console);
		return -1;
	}

	console->config->max_nr_ports = VIRTIO_CONSOLE_MAXPORTS;
	console->config->cols = 80;
	console->config->rows = 25;

	/* 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(("virtio_console: mutexattr_settype failed with "
					"error %d!\n", rc));

	rc = pthread_mutex_init(&console->mtx, &attr);
	if (rc)
		DPRINTF(("virtio_console: pthread_mutex_init failed with "
					"error %d!\n", rc));

	virtio_linkup(&console->base, &virtio_console_ops, console, dev,
		console->queues, BACKEND_VBSU);
	console->base.mtx = &console->mtx;
	console->base.device_caps = VIRTIO_CONSOLE_S_HOSTCAPS;

	for (i = 0; i < VIRTIO_CONSOLE_MAXQ; i++) {
		console->queues[i].qsize = VIRTIO_CONSOLE_RINGSZ;
		console->queues[i].notify = i % 2 == 0
		    ? virtio_console_notify_rx
		    : virtio_console_notify_tx;
	}

	/* initialize config space */
	pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_CONSOLE);
	pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
	pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_SIMPLECOMM);
	pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_SIMPLECOMM_OTHER);
	pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_CONSOLE);
	pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);

	if (virtio_interrupt_init(&console->base, virtio_uses_msix())) {
		if (console) {
			if (console->config)
				free(console->config);
			free(console);
		}
		return -1;
	}
	virtio_set_io_bar(&console->base, 0);

	/* create control port */
	console->control_port.console = console;
	console->control_port.txq = 2;
	console->control_port.rxq = 3;
	console->control_port.cb = virtio_console_control_tx;
	console->control_port.enabled = true;
	if (virtio_console_add_backends(console, opts) < 0) {
		return -1;
	}

	return 0;
}

static void
virtio_console_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
	struct virtio_console *console;
	int rc;

	console = (struct virtio_console *)dev->arg;
	if (console) {
		rc = virtio_console_close_all(console);
		/*
		 * if all the ports are without mevent attached,
		 * no teardown will be called, we should destroy
		 * console here explicitly.
		 */
		if (!rc)
			virtio_console_destroy(console);
	}
}

struct pci_vdev_ops pci_ops_virtio_console = {
	.class_name	= "virtio-console",
	.vdev_init	= virtio_console_init,
	.vdev_deinit	= virtio_console_deinit,
	.vdev_barwrite	= virtio_pci_write,
	.vdev_barread	= virtio_pci_read
};
DEFINE_PCI_DEVTYPE(pci_ops_virtio_console);
