/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*	Copyright (c) 1990, 1991 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved					*/

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 *
 * Copyright (c) 2004 Christian Limpach.
 * All rights reserved.
 *
 * 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.
 * 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.
 * 3. This section intentionally left blank.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
 */
/*
 * Section 3 of the above license was updated in response to bug 6379571.
 */

/*
 * Hypervisor virtual console driver
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/stream.h>
#include <sys/termio.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/stropts.h>
#include <sys/strsubr.h>
#include <sys/strtty.h>
#include <sys/debug.h>
#include <sys/kbio.h>
#include <sys/cred.h>
#include <sys/stat.h>
#include <sys/consdev.h>
#include <sys/mkdev.h>
#include <sys/kmem.h>
#include <sys/cred.h>
#include <sys/strsun.h>
#ifdef DEBUG
#include <sys/promif.h>
#endif
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/policy.h>
#include <sys/atomic.h>
#include <sys/psm.h>
#include <xen/public/io/console.h>

#include "xencons.h"

#include <sys/hypervisor.h>
#include <sys/evtchn_impl.h>
#include <xen/sys/xenbus_impl.h>
#include <xen/sys/xendev.h>

#ifdef DEBUG
#define	XENCONS_DEBUG_INIT	0x0001	/* msgs during driver initialization. */
#define	XENCONS_DEBUG_INPUT	0x0002	/* characters received during int. */
#define	XENCONS_DEBUG_EOT	0x0004	/* msgs when wait for xmit to finish. */
#define	XENCONS_DEBUG_CLOSE	0x0008	/* msgs when driver open/close called */
#define	XENCONS_DEBUG_PROCS	0x0020	/* each proc name as it is entered. */
#define	XENCONS_DEBUG_OUT	0x0100	/* msgs about output events. */
#define	XENCONS_DEBUG_BUSY	0x0200	/* msgs when xmit is enabled/disabled */
#define	XENCONS_DEBUG_MODEM	0x0400	/* msgs about modem status & control. */
#define	XENCONS_DEBUG_MODM2	0x0800	/* msgs about modem status & control. */
#define	XENCONS_DEBUG_IOCTL	0x1000	/* Output msgs about ioctl messages. */
#define	XENCONS_DEBUG_CHIP	0x2000	/* msgs about chip identification. */
#define	XENCONS_DEBUG_SFLOW	0x4000	/* msgs when S/W flowcontrol active */
#define	XENCONS_DEBUG(x) (debug & (x))
static int debug  = 0;
#else
#define	XENCONS_DEBUG(x) B_FALSE
#endif

#define	XENCONS_WBUFSIZE	4096

static boolean_t abort_charseq_recognize(uchar_t);

/* The async interrupt entry points */
static void	xcasync_ioctl(struct asyncline *, queue_t *, mblk_t *);
static void	xcasync_reioctl(void *);
static void	xcasync_start(struct asyncline *);
static void	xenconsputchar(cons_polledio_arg_t, uchar_t);
static int	xenconsgetchar(cons_polledio_arg_t);
static boolean_t	xenconsischar(cons_polledio_arg_t);

static uint_t	xenconsintr(caddr_t);
static uint_t	xenconsintr_priv(caddr_t);
/*PRINTFLIKE2*/
static void	xenconserror(int, const char *, ...) __KPRINTFLIKE(2);
static void	xencons_soft_state_free(struct xencons *);
static boolean_t
xcasync_flowcontrol_sw_input(struct xencons *, async_flowc_action, int);
static void
xcasync_flowcontrol_sw_output(struct xencons *, async_flowc_action);

void		*xencons_soft_state;
char		*xencons_wbuf;
struct xencons	*xencons_console;

static void
xenconssetup_avintr(struct xencons *xcp, int attach)
{
	/*
	 * On xen, CPU 0 always exists and can't be taken offline,
	 * so binding this thread to it should always succeed.
	 */
	mutex_enter(&cpu_lock);
	thread_affinity_set(curthread, 0);
	mutex_exit(&cpu_lock);

	if (attach) {
		/* Setup our interrupt binding. */
		(void) add_avintr(NULL, IPL_CONS, (avfunc)xenconsintr_priv,
		    "xencons", xcp->console_irq, (caddr_t)xcp, NULL, NULL,
		    xcp->dip);
	} else {
		/*
		 * Cleanup interrupt configuration.  Note that the framework
		 * _should_ ensure that when rem_avintr() returns the interrupt
		 * service routine is not currently executing and that it won't
		 * be invoked again.
		 */
		(void) rem_avintr(NULL, IPL_CONS, (avfunc)xenconsintr_priv,
		    xcp->console_irq);
	}

	/* Notify our caller that we're done. */
	mutex_enter(&xcp->excl);
	cv_signal(&xcp->excl_cv);
	mutex_exit(&xcp->excl);

	/* Clear our binding to CPU 0 */
	thread_affinity_clear(curthread);

}

static void
xenconssetup_add_avintr(struct xencons *xcp)
{
	xenconssetup_avintr(xcp, B_TRUE);
}

static void
xenconssetup_rem_avintr(struct xencons *xcp)
{
	xenconssetup_avintr(xcp, B_FALSE);
}

static int
xenconsdetach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance;
	struct xencons *xcp;

	if (cmd != DDI_DETACH && cmd != DDI_SUSPEND)
		return (DDI_FAILURE);

	if (cmd == DDI_SUSPEND) {
		ddi_remove_intr(devi, 0, NULL);
		return (DDI_SUCCESS);
	}

	/*
	 * We should never try to detach the console driver on a domU
	 * because it should always be held open
	 */
	ASSERT(DOMAIN_IS_INITDOMAIN(xen_info));
	if (!DOMAIN_IS_INITDOMAIN(xen_info))
		return (DDI_FAILURE);

	instance = ddi_get_instance(devi);	/* find out which unit */

	xcp = ddi_get_soft_state(xencons_soft_state, instance);
	if (xcp == NULL)
		return (DDI_FAILURE);

	/*
	 * Cleanup our interrupt bindings.  For more info on why we
	 * do this in a seperate thread, see the comments for when we
	 * setup the interrupt bindings.
	 */
	xencons_console = NULL;
	mutex_enter(&xcp->excl);
	(void) taskq_dispatch(system_taskq,
	    (void (*)(void *))xenconssetup_rem_avintr, xcp, TQ_SLEEP);
	cv_wait(&xcp->excl_cv, &xcp->excl);
	mutex_exit(&xcp->excl);

	/* remove all minor device node(s) for this device */
	ddi_remove_minor_node(devi, NULL);

	/* free up state */
	xencons_soft_state_free(xcp);
	kmem_free(xencons_wbuf, XENCONS_WBUFSIZE);

	DEBUGNOTE1(XENCONS_DEBUG_INIT, "xencons%d: shutdown complete",
	    instance);
	return (DDI_SUCCESS);
}

static void
xenconssetup(struct xencons *xcp)
{
	xcp->ifp = (volatile struct xencons_interface *)HYPERVISOR_console_page;

	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		xencons_wbuf = kmem_alloc(XENCONS_WBUFSIZE, KM_SLEEP);

		/*
		 * Activate the xen console virq.  Note that xen requires
		 * that VIRQs be bound to CPU 0 when first created.
		 */
		xcp->console_irq = ec_bind_virq_to_irq(VIRQ_CONSOLE, 0);

		/*
		 * Ok.  This is kinda ugly.  We want to register an
		 * interrupt handler for the xen console virq, but
		 * virq's are xen sepcific and currently the DDI doesn't
		 * support binding to them.  So instead we need to use
		 * add_avintr().  So to make things more complicated,
		 * we already had to bind the xen console VIRQ to CPU 0,
		 * and add_avintr() needs to be invoked on the same CPU
		 * where the VIRQ is bound, in this case on CPU 0.  We
		 * could just temporarily bind ourselves to CPU 0, but
		 * we don't want to do that since this attach thread
		 * could have been invoked in a user thread context,
		 * in which case this thread could already have some
		 * pre-existing cpu binding.  So to avoid changing our
		 * cpu binding we're going to use a taskq thread that
		 * will bind to CPU 0 and register our interrupts
		 * handler for us.
		 */
		mutex_enter(&xcp->excl);
		(void) taskq_dispatch(system_taskq,
		    (void (*)(void *))xenconssetup_add_avintr, xcp, TQ_SLEEP);
		cv_wait(&xcp->excl_cv, &xcp->excl);
		mutex_exit(&xcp->excl);
	} else {
		(void) xvdi_alloc_evtchn(xcp->dip);
		xcp->evtchn = xvdi_get_evtchn(xcp->dip);
		(void) ddi_add_intr(xcp->dip, 0, NULL, NULL, xenconsintr,
		    (caddr_t)xcp);
	}
}

static int
xenconsattach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	int instance = ddi_get_instance(devi);
	struct xencons *xcp;
	int ret;

	/* There can be only one. */
	if (instance != 0)
		return (DDI_FAILURE);

	switch (cmd) {
	case DDI_RESUME:
		xcp = xencons_console;
		xenconssetup(xcp);
		return (DDI_SUCCESS);
	case DDI_ATTACH:
		break;
	default:
		return (DDI_FAILURE);
	}

	ret = ddi_soft_state_zalloc(xencons_soft_state, instance);
	if (ret != DDI_SUCCESS)
		return (DDI_FAILURE);
	xcp = ddi_get_soft_state(xencons_soft_state, instance);
	ASSERT(xcp != NULL);	/* can't fail - we only just allocated it */

	/*
	 * Set up the other components of the xencons structure for this port.
	 */
	xcp->unit = instance;
	xcp->dip = devi;

	/* Fill in the polled I/O structure. */
	xcp->polledio.cons_polledio_version = CONSPOLLEDIO_V0;
	xcp->polledio.cons_polledio_argument = (cons_polledio_arg_t)xcp;
	xcp->polledio.cons_polledio_putchar = xenconsputchar;
	xcp->polledio.cons_polledio_getchar = xenconsgetchar;
	xcp->polledio.cons_polledio_ischar = xenconsischar;
	xcp->polledio.cons_polledio_enter = NULL;
	xcp->polledio.cons_polledio_exit = NULL;

	/*
	 * Initializes the asyncline structure which has TTY protocol-private
	 * data before enabling interrupts.
	 */
	xcp->priv = kmem_zalloc(sizeof (struct asyncline), KM_SLEEP);
	xcp->priv->async_common = xcp;
	cv_init(&xcp->priv->async_flags_cv, NULL, CV_DRIVER, NULL);

	/* Initialize mutexes before accessing the interface. */
	mutex_init(&xcp->excl, NULL, MUTEX_DRIVER, NULL);
	cv_init(&xcp->excl_cv, NULL, CV_DEFAULT, NULL);

	/* create minor device node for this device */
	ret = ddi_create_minor_node(devi, "xencons", S_IFCHR, instance,
	    DDI_NT_SERIAL, NULL);
	if (ret != DDI_SUCCESS) {
		ddi_remove_minor_node(devi, NULL);
		xencons_soft_state_free(xcp);
		return (DDI_FAILURE);
	}

	ddi_report_dev(devi);
	xencons_console = xcp;
	xenconssetup(xcp);
	DEBUGCONT1(XENCONS_DEBUG_INIT, "xencons%dattach: done\n", instance);
	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
xenconsinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
	void **result)
{
	dev_t dev = (dev_t)arg;
	int instance, error;
	struct xencons *xcp;

	instance = getminor(dev);
	xcp = ddi_get_soft_state(xencons_soft_state, instance);
	if (xcp == NULL)
		return (DDI_FAILURE);

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		if (xcp->dip == NULL)
			error = DDI_FAILURE;
		else {
			*result = (void *) xcp->dip;
			error = DDI_SUCCESS;
		}
		break;
	case DDI_INFO_DEVT2INSTANCE:
		*result = (void *)(intptr_t)instance;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}

/* xencons_soft_state_free - local wrapper for ddi_soft_state_free(9F) */

static void
xencons_soft_state_free(struct xencons *xcp)
{
	mutex_destroy(&xcp->excl);
	cv_destroy(&xcp->excl_cv);
	kmem_free(xcp->priv, sizeof (struct asyncline));
	ddi_soft_state_free(xencons_soft_state, xcp->unit);
}

/*ARGSUSED*/
static int
xenconsopen(queue_t *rq, dev_t *dev, int flag, int sflag, cred_t *cr)
{
	struct xencons	*xcp;
	struct asyncline *async;
	int		unit;

	unit = getminor(*dev);
	DEBUGCONT1(XENCONS_DEBUG_CLOSE, "xencons%dopen\n", unit);
	xcp = ddi_get_soft_state(xencons_soft_state, unit);
	if (xcp == NULL)
		return (ENXIO);		/* unit not configured */
	async = xcp->priv;
	mutex_enter(&xcp->excl);

again:

	if ((async->async_flags & ASYNC_ISOPEN) == 0) {
		async->async_ttycommon.t_iflag = 0;
		async->async_ttycommon.t_iocpending = NULL;
		async->async_ttycommon.t_size.ws_row = 0;
		async->async_ttycommon.t_size.ws_col = 0;
		async->async_ttycommon.t_size.ws_xpixel = 0;
		async->async_ttycommon.t_size.ws_ypixel = 0;
		async->async_dev = *dev;
		async->async_wbufcid = 0;

		async->async_startc = CSTART;
		async->async_stopc = CSTOP;
	} else if ((async->async_ttycommon.t_flags & TS_XCLUDE) &&
	    secpolicy_excl_open(cr) != 0) {
		mutex_exit(&xcp->excl);
		return (EBUSY);
	}

	async->async_ttycommon.t_flags |= TS_SOFTCAR;

	async->async_ttycommon.t_readq = rq;
	async->async_ttycommon.t_writeq = WR(rq);
	rq->q_ptr = WR(rq)->q_ptr = (caddr_t)async;
	mutex_exit(&xcp->excl);
	/*
	 * Caution here -- qprocson sets the pointers that are used by canput
	 * called by xencons_rxint.  ASYNC_ISOPEN must *not* be set until those
	 * pointers are valid.
	 */
	qprocson(rq);
	async->async_flags |= ASYNC_ISOPEN;
	DEBUGCONT1(XENCONS_DEBUG_INIT, "asy%dopen: done\n", unit);
	return (0);
}


/*
 * Close routine.
 */
/*ARGSUSED*/
static int
xenconsclose(queue_t *q, int flag, cred_t *credp)
{
	struct asyncline *async;
	struct xencons	 *xcp;
#ifdef DEBUG
	int instance;
#endif

	async = (struct asyncline *)q->q_ptr;
	ASSERT(async != NULL);
	xcp = async->async_common;
#ifdef DEBUG
	instance = xcp->unit;
	DEBUGCONT1(XENCONS_DEBUG_CLOSE, "xencons%dclose\n", instance);
#endif

	mutex_enter(&xcp->excl);
	async->async_flags |= ASYNC_CLOSING;

	async->async_ocnt = 0;
	if (async->async_xmitblk != NULL)
		freeb(async->async_xmitblk);
	async->async_xmitblk = NULL;

out:
	ttycommon_close(&async->async_ttycommon);

	/*
	 * Cancel outstanding "bufcall" request.
	 */
	if (async->async_wbufcid != 0) {
		unbufcall(async->async_wbufcid);
		async->async_wbufcid = 0;
	}

	/* Note that qprocsoff can't be done until after interrupts are off */
	qprocsoff(q);
	q->q_ptr = WR(q)->q_ptr = NULL;
	async->async_ttycommon.t_readq = NULL;
	async->async_ttycommon.t_writeq = NULL;

	/*
	 * Clear out device state, except persistant device property flags.
	 */
	async->async_flags = 0;
	cv_broadcast(&async->async_flags_cv);
	mutex_exit(&xcp->excl);

	DEBUGCONT1(XENCONS_DEBUG_CLOSE, "xencons%dclose: done\n", instance);
	return (0);
}

#define	INBUF_IX(ix, ifp)	(DOMAIN_IS_INITDOMAIN(xen_info) ? \
	(ix) : MASK_XENCONS_IDX((ix), (ifp)->in))

/*
 * Handle a xen console rx interrupt.
 */
/*ARGSUSED*/
static void
xencons_rxint(struct xencons *xcp)
{
	struct asyncline *async;
	short	cc;
	mblk_t	*bp;
	queue_t	*q;
	uchar_t	c, buf[16];
	uchar_t	*cp;
	tty_common_t	*tp;
	int instance;
	volatile struct xencons_interface *ifp;
	XENCONS_RING_IDX cons, prod;

	DEBUGCONT0(XENCONS_DEBUG_PROCS, "xencons_rxint\n");

loop:
	mutex_enter(&xcp->excl);

	/* sanity check if we should bail */
	if (xencons_console == NULL) {
		mutex_exit(&xcp->excl);
		goto out;
	}

	async = xcp->priv;
	instance = xcp->unit;
	ifp = xcp->ifp;
	tp = &async->async_ttycommon;
	q = tp->t_readq;

	if (async->async_flags & ASYNC_OUT_FLW_RESUME) {
		xcasync_start(async);
		async->async_flags &= ~ASYNC_OUT_FLW_RESUME;
	}

	/*
	 * If data is available, send it up the stream if there's
	 * somebody listening.
	 */
	if (!(async->async_flags & ASYNC_ISOPEN)) {
		mutex_exit(&xcp->excl);
		goto out;
	}
	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		cc = HYPERVISOR_console_io(CONSOLEIO_read, 16, (char *)buf);
		cp = buf;
		cons = 0;
	} else {
		cons = ifp->in_cons;
		prod = ifp->in_prod;

		cc = prod - cons;
		cp = (uchar_t *)ifp->in;
	}
	if (cc <= 0) {
		mutex_exit(&xcp->excl);
		goto out;
	}

	/*
	 * Check for character break sequence.
	 *
	 * Note that normally asy drivers only check for a character sequence
	 * if abort_enable == KIOCABORTALTERNATE and otherwise use a break
	 * sensed on the line to do an abort_sequence_enter.  Since the
	 * hypervisor does not use a real chip for the console we default to
	 * using the alternate sequence.
	 */
	if ((abort_enable == KIOCABORTENABLE) && (xcp->flags & ASY_CONSOLE)) {
		XENCONS_RING_IDX i;

		for (i = 0; i < cc; i++) {
			c = cp[INBUF_IX(cons + i, ifp)];
			if (abort_charseq_recognize(c)) {
				/*
				 * Eat abort seg, it's not a valid debugger
				 * command.
				 */
				if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
					membar_producer();
					ifp->in_cons = cons + i;
				} else {
					cons += i;
				}
				abort_sequence_enter((char *)NULL);
				/*
				 * Back from debugger, resume normal processing
				 */
				mutex_exit(&xcp->excl);
				goto loop;
			}
		}
	}

	if (!canput(q)) {
		if (!(async->async_inflow_source & IN_FLOW_STREAMS)) {
			(void) xcasync_flowcontrol_sw_input(xcp, FLOW_STOP,
			    IN_FLOW_STREAMS);
		}
		mutex_exit(&xcp->excl);
		goto out;
	}
	if (async->async_inflow_source & IN_FLOW_STREAMS) {
		(void) xcasync_flowcontrol_sw_input(xcp, FLOW_START,
		    IN_FLOW_STREAMS);
	}
	DEBUGCONT2(XENCONS_DEBUG_INPUT,
	    "xencons%d_rxint: %d char(s) in queue.\n", instance, cc);
	if (!(bp = allocb(cc, BPRI_MED))) {
		mutex_exit(&xcp->excl);
		ttycommon_qfull(&async->async_ttycommon, q);
		goto out;
	}
	do {
		c = cp[INBUF_IX(cons++, ifp)];
		/*
		 * We handle XON/XOFF char if IXON is set,
		 * but if received char is _POSIX_VDISABLE,
		 * we left it to the up level module.
		 */
		if (tp->t_iflag & IXON) {
			if ((c == async->async_stopc) &&
			    (c != _POSIX_VDISABLE)) {
				xcasync_flowcontrol_sw_output(xcp, FLOW_STOP);
				continue;
			} else if ((c == async->async_startc) &&
			    (c != _POSIX_VDISABLE)) {
				xcasync_flowcontrol_sw_output(xcp, FLOW_START);
				continue;
			}
			if ((tp->t_iflag & IXANY) &&
			    (async->async_flags & ASYNC_SW_OUT_FLW)) {
				xcasync_flowcontrol_sw_output(xcp, FLOW_START);
			}
		}
		*bp->b_wptr++ = c;
	} while (--cc);
	membar_producer();
	if (!DOMAIN_IS_INITDOMAIN(xen_info))
		ifp->in_cons = cons;
	mutex_exit(&xcp->excl);
	if (bp->b_wptr > bp->b_rptr) {
		if (!canput(q)) {
			xenconserror(CE_NOTE, "xencons%d: local queue full",
			    instance);
			freemsg(bp);
		} else
			(void) putq(q, bp);
	} else
		freemsg(bp);
	if (DOMAIN_IS_INITDOMAIN(xen_info))
		goto loop;
out:
	DEBUGCONT1(XENCONS_DEBUG_PROCS, "xencons%d_rxint: done\n", instance);
	if (!DOMAIN_IS_INITDOMAIN(xen_info))
		ec_notify_via_evtchn(xcp->evtchn);
}


/*
 * Handle a xen console tx interrupt.
 */
/*ARGSUSED*/
static void
xencons_txint(struct xencons *xcp)
{
	struct asyncline *async;

	DEBUGCONT0(XENCONS_DEBUG_PROCS, "xencons_txint\n");

	/*
	 * prevent recursive entry
	 */
	if (mutex_owner(&xcp->excl) == curthread) {
		goto out;
	}

	mutex_enter(&xcp->excl);
	if (xencons_console == NULL) {
		mutex_exit(&xcp->excl);
		goto out;
	}

	/* make sure the device is open */
	async = xcp->priv;
	if ((async->async_flags & ASYNC_ISOPEN) != 0)
		xcasync_start(async);

	mutex_exit(&xcp->excl);
out:
	DEBUGCONT0(XENCONS_DEBUG_PROCS, "xencons_txint: done\n");
}


/*
 * Get an event when input ring becomes not empty or output ring becomes not
 * full.
 */
static uint_t
xenconsintr(caddr_t arg)
{
	struct xencons *xcp = (struct xencons *)arg;
	volatile struct xencons_interface *ifp = xcp->ifp;

	if (ifp->in_prod != ifp->in_cons)
		xencons_rxint(xcp);
	if (ifp->out_prod - ifp->out_cons < sizeof (ifp->out))
		xencons_txint(xcp);
	return (DDI_INTR_CLAIMED);
}

/*
 * Console interrupt routine for priviliged domains
 */
static uint_t
xenconsintr_priv(caddr_t arg)
{
	struct xencons *xcp = (struct xencons *)arg;

	xencons_rxint(xcp);
	xencons_txint(xcp);
	return (DDI_INTR_CLAIMED);
}

/*
 * Start output on a line, unless it's busy, frozen, or otherwise.
 */
/*ARGSUSED*/
static void
xcasync_start(struct asyncline *async)
{
	struct xencons *xcp = async->async_common;
	int cc;
	queue_t *q;
	mblk_t *bp;
	int	len, space, blen;
	mblk_t *nbp;

#ifdef DEBUG
	int instance = xcp->unit;

	DEBUGCONT1(XENCONS_DEBUG_PROCS, "async%d_nstart\n", instance);
#endif
	ASSERT(mutex_owned(&xcp->excl));

	/*
	 * Check only pended sw input flow control.
	 */
domore:
	(void) xcasync_flowcontrol_sw_input(xcp, FLOW_CHECK, IN_FLOW_NULL);

	if ((q = async->async_ttycommon.t_writeq) == NULL) {
		return;	/* not attached to a stream */
	}

	for (;;) {
		if ((bp = getq(q)) == NULL)
			return;	/* no data to transmit */

		/*
		 * We have a message block to work on.
		 * Check whether it's a break, a delay, or an ioctl (the latter
		 * occurs if the ioctl in question was waiting for the output
		 * to drain).  If it's one of those, process it immediately.
		 */
		switch (bp->b_datap->db_type) {

		case M_IOCTL:
			/*
			 * This ioctl was waiting for the output ahead of
			 * it to drain; obviously, it has.  Do it, and
			 * then grab the next message after it.
			 */
			mutex_exit(&xcp->excl);
			xcasync_ioctl(async, q, bp);
			mutex_enter(&xcp->excl);
			continue;
		}

		while (bp != NULL && (cc = bp->b_wptr - bp->b_rptr) == 0) {
			nbp = bp->b_cont;
			freeb(bp);
			bp = nbp;
		}
		if (bp != NULL)
			break;
	}

	/*
	 * We have data to transmit.  If output is stopped, put
	 * it back and try again later.
	 */
	if (async->async_flags & (ASYNC_SW_OUT_FLW | ASYNC_STOPPED)) {
		(void) putbq(q, bp);
		return;
	}


	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		len = 0;
		space = XENCONS_WBUFSIZE;
		while (bp != NULL && space) {
			blen = bp->b_wptr - bp->b_rptr;
			cc = min(blen, space);
			bcopy(bp->b_rptr, &xencons_wbuf[len], cc);
			bp->b_rptr += cc;
			if (cc == blen) {
				nbp = bp->b_cont;
				freeb(bp);
				bp = nbp;
			}
			space -= cc;
			len += cc;
		}
		mutex_exit(&xcp->excl);
		(void) HYPERVISOR_console_io(CONSOLEIO_write, len,
		    xencons_wbuf);
		mutex_enter(&xcp->excl);
		if (bp != NULL)
			(void) putbq(q, bp); /* not done with this msg yet */
		/*
		 * There are no completion interrupts when using the
		 * HYPERVISOR_console_io call to write console data
		 * so we loop here till we have sent all the data to the
		 * hypervisor.
		 */
		goto domore;
	} else {
		volatile struct xencons_interface *ifp = xcp->ifp;
		XENCONS_RING_IDX cons, prod;

		cons = ifp->out_cons;
		prod = ifp->out_prod;
		membar_enter();
		while (bp != NULL && ((prod - cons) < sizeof (ifp->out))) {
			ifp->out[MASK_XENCONS_IDX(prod++, ifp->out)] =
			    *bp->b_rptr++;
			if (bp->b_rptr == bp->b_wptr) {
				nbp = bp->b_cont;
				freeb(bp);
				bp = nbp;
			}
		}
		membar_producer();
		ifp->out_prod = prod;
		ec_notify_via_evtchn(xcp->evtchn);
		if (bp != NULL)
			(void) putbq(q, bp); /* not done with this msg yet */
	}
}


/*
 * Process an "ioctl" message sent down to us.
 * Note that we don't need to get any locks until we are ready to access
 * the hardware.  Nothing we access until then is going to be altered
 * outside of the STREAMS framework, so we should be safe.
 */
static void
xcasync_ioctl(struct asyncline *async, queue_t *wq, mblk_t *mp)
{
	struct xencons *xcp = async->async_common;
	tty_common_t  *tp = &async->async_ttycommon;
	struct iocblk *iocp;
	unsigned datasize;
	int error = 0;

#ifdef DEBUG
	int instance = xcp->unit;

	DEBUGCONT1(XENCONS_DEBUG_PROCS, "async%d_ioctl\n", instance);
#endif

	if (tp->t_iocpending != NULL) {
		/*
		 * We were holding an "ioctl" response pending the
		 * availability of an "mblk" to hold data to be passed up;
		 * another "ioctl" came through, which means that "ioctl"
		 * must have timed out or been aborted.
		 */
		freemsg(async->async_ttycommon.t_iocpending);
		async->async_ttycommon.t_iocpending = NULL;
	}

	iocp = (struct iocblk *)mp->b_rptr;

	/*
	 * For TIOCMGET and the PPS ioctls, do NOT call ttycommon_ioctl()
	 * because this function frees up the message block (mp->b_cont) that
	 * contains the user location where we pass back the results.
	 *
	 * Similarly, CONSOPENPOLLEDIO needs ioc_count, which ttycommon_ioctl
	 * zaps.  We know that ttycommon_ioctl doesn't know any CONS*
	 * ioctls, so keep the others safe too.
	 */
	DEBUGCONT2(XENCONS_DEBUG_IOCTL, "async%d_ioctl: %s\n",
	    instance,
	    iocp->ioc_cmd == TIOCMGET ? "TIOCMGET" :
	    iocp->ioc_cmd == TIOCMSET ? "TIOCMSET" :
	    iocp->ioc_cmd == TIOCMBIS ? "TIOCMBIS" :
	    iocp->ioc_cmd == TIOCMBIC ? "TIOCMBIC" : "other");

	switch (iocp->ioc_cmd) {
	case TIOCMGET:
	case TIOCGPPS:
	case TIOCSPPS:
	case TIOCGPPSEV:
	case CONSOPENPOLLEDIO:
	case CONSCLOSEPOLLEDIO:
	case CONSSETABORTENABLE:
	case CONSGETABORTENABLE:
		error = -1; /* Do Nothing */
		break;
	default:

		/*
		 * The only way in which "ttycommon_ioctl" can fail is if the
		 * "ioctl" requires a response containing data to be returned
		 * to the user, and no mblk could be allocated for the data.
		 * No such "ioctl" alters our state.  Thus, we always go ahead
		 * and do any state-changes the "ioctl" calls for.  If we
		 * couldn't allocate the data, "ttycommon_ioctl" has stashed
		 * the "ioctl" away safely, so we just call "bufcall" to
		 * request that we be called back when we stand a better
		 * chance of allocating the data.
		 */
		if ((datasize = ttycommon_ioctl(tp, wq, mp, &error)) != 0) {
			if (async->async_wbufcid)
				unbufcall(async->async_wbufcid);
			async->async_wbufcid = bufcall(datasize, BPRI_HI,
			    (void (*)(void *)) xcasync_reioctl,
			    (void *)(intptr_t)async->async_common->unit);
			return;
		}
	}

	mutex_enter(&xcp->excl);

	if (error == 0) {
		/*
		 * "ttycommon_ioctl" did most of the work; we just use the
		 * data it set up.
		 */
		switch (iocp->ioc_cmd) {

		case TCSETS:
		case TCSETSF:
		case TCSETSW:
		case TCSETA:
		case TCSETAW:
		case TCSETAF:
			break;
		}
	} else if (error < 0) {
		/*
		 * "ttycommon_ioctl" didn't do anything; we process it here.
		 */
		error = 0;
		switch (iocp->ioc_cmd) {

		case TCSBRK:
			error = miocpullup(mp, sizeof (int));
			break;

		case TIOCSBRK:
			mioc2ack(mp, NULL, 0, 0);
			break;

		case TIOCCBRK:
			mioc2ack(mp, NULL, 0, 0);
			break;

		case CONSOPENPOLLEDIO:
			error = miocpullup(mp, sizeof (cons_polledio_arg_t));
			if (error != 0)
				break;

			*(cons_polledio_arg_t *)mp->b_cont->b_rptr =
			    (cons_polledio_arg_t)&xcp->polledio;

			mp->b_datap->db_type = M_IOCACK;
			break;

		case CONSCLOSEPOLLEDIO:
			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_error = 0;
			iocp->ioc_rval = 0;
			break;

		case CONSSETABORTENABLE:
			error = secpolicy_console(iocp->ioc_cr);
			if (error != 0)
				break;

			if (iocp->ioc_count != TRANSPARENT) {
				error = EINVAL;
				break;
			}

			if (*(intptr_t *)mp->b_cont->b_rptr)
				xcp->flags |= ASY_CONSOLE;
			else
				xcp->flags &= ~ASY_CONSOLE;

			mp->b_datap->db_type = M_IOCACK;
			iocp->ioc_error = 0;
			iocp->ioc_rval = 0;
			break;

		case CONSGETABORTENABLE:
			/*CONSTANTCONDITION*/
			ASSERT(sizeof (boolean_t) <= sizeof (boolean_t *));
			/*
			 * Store the return value right in the payload
			 * we were passed.  Crude.
			 */
			mcopyout(mp, NULL, sizeof (boolean_t), NULL, NULL);
			*(boolean_t *)mp->b_cont->b_rptr =
			    (xcp->flags & ASY_CONSOLE) != 0;
			break;

		default:
			/*
			 * If we don't understand it, it's an error.  NAK it.
			 */
			error = EINVAL;
			break;
		}
	}
	if (error != 0) {
		iocp->ioc_error = error;
		mp->b_datap->db_type = M_IOCNAK;
	}
	mutex_exit(&xcp->excl);
	qreply(wq, mp);
	DEBUGCONT1(XENCONS_DEBUG_PROCS, "async%d_ioctl: done\n", instance);
}

static int
xenconsrsrv(queue_t *q)
{
	mblk_t *bp;

	while (canputnext(q) && (bp = getq(q)))
		putnext(q, bp);
	return (0);
}

/*
 * Put procedure for write queue.
 * Respond to M_STOP, M_START, M_IOCTL, and M_FLUSH messages here;
 * set the flow control character for M_STOPI and M_STARTI messages;
 * queue up M_BREAK, M_DELAY, and M_DATA messages for processing
 * by the start routine, and then call the start routine; discard
 * everything else.  Note that this driver does not incorporate any
 * mechanism to negotiate to handle the canonicalization process.
 * It expects that these functions are handled in upper module(s),
 * as we do in ldterm.
 */
static int
xenconswput(queue_t *q, mblk_t *mp)
{
	struct asyncline *async;
	struct xencons *xcp;

	async = (struct asyncline *)q->q_ptr;
	xcp = async->async_common;

	switch (mp->b_datap->db_type) {

	case M_STOP:
		mutex_enter(&xcp->excl);
		async->async_flags |= ASYNC_STOPPED;
		mutex_exit(&xcp->excl);
		freemsg(mp);
		break;

	case M_START:
		mutex_enter(&xcp->excl);
		if (async->async_flags & ASYNC_STOPPED) {
			async->async_flags &= ~ASYNC_STOPPED;
			xcasync_start(async);
		}
		mutex_exit(&xcp->excl);
		freemsg(mp);
		break;

	case M_IOCTL:
		switch (((struct iocblk *)mp->b_rptr)->ioc_cmd) {

		case TCSETSW:
		case TCSETSF:
		case TCSETAW:
		case TCSETAF:
			/*
			 * The changes do not take effect until all
			 * output queued before them is drained.
			 * Put this message on the queue, so that
			 * "xcasync_start" will see it when it's done
			 * with the output before it.  Poke the
			 * start routine, just in case.
			 */
			(void) putq(q, mp);
			mutex_enter(&xcp->excl);
			xcasync_start(async);
			mutex_exit(&xcp->excl);
			break;

		default:
			/*
			 * Do it now.
			 */
			xcasync_ioctl(async, q, mp);
			break;
		}
		break;

	case M_FLUSH:
		if (*mp->b_rptr & FLUSHW) {
			mutex_enter(&xcp->excl);
			/*
			 * Flush our write queue.
			 */
			flushq(q, FLUSHDATA);	/* XXX doesn't flush M_DELAY */
			if (async->async_xmitblk != NULL) {
				freeb(async->async_xmitblk);
				async->async_xmitblk = NULL;
			}
			mutex_exit(&xcp->excl);
			*mp->b_rptr &= ~FLUSHW;	/* it has been flushed */
		}
		if (*mp->b_rptr & FLUSHR) {
			flushq(RD(q), FLUSHDATA);
			qreply(q, mp);	/* give the read queues a crack at it */
		} else {
			freemsg(mp);
		}

		/*
		 * We must make sure we process messages that survive the
		 * write-side flush.
		 */
		mutex_enter(&xcp->excl);
		xcasync_start(async);
		mutex_exit(&xcp->excl);
		break;

	case M_BREAK:
	case M_DELAY:
	case M_DATA:
		/*
		 * Queue the message up to be transmitted,
		 * and poke the start routine.
		 */
		(void) putq(q, mp);
		mutex_enter(&xcp->excl);
		xcasync_start(async);
		mutex_exit(&xcp->excl);
		break;

	case M_STOPI:
		mutex_enter(&xcp->excl);
		mutex_enter(&xcp->excl);
		if (!(async->async_inflow_source & IN_FLOW_USER)) {
			(void) xcasync_flowcontrol_sw_input(xcp, FLOW_STOP,
			    IN_FLOW_USER);
		}
		mutex_exit(&xcp->excl);
		mutex_exit(&xcp->excl);
		freemsg(mp);
		break;

	case M_STARTI:
		mutex_enter(&xcp->excl);
		mutex_enter(&xcp->excl);
		if (async->async_inflow_source & IN_FLOW_USER) {
			(void) xcasync_flowcontrol_sw_input(xcp, FLOW_START,
			    IN_FLOW_USER);
		}
		mutex_exit(&xcp->excl);
		mutex_exit(&xcp->excl);
		freemsg(mp);
		break;

	case M_CTL:
		if (MBLKL(mp) >= sizeof (struct iocblk) &&
		    ((struct iocblk *)mp->b_rptr)->ioc_cmd == MC_POSIXQUERY) {
			((struct iocblk *)mp->b_rptr)->ioc_cmd = MC_HAS_POSIX;
			qreply(q, mp);
		} else {
			freemsg(mp);
		}
		break;

	default:
		freemsg(mp);
		break;
	}
	return (0);
}

/*
 * Retry an "ioctl", now that "bufcall" claims we may be able to allocate
 * the buffer we need.
 */
static void
xcasync_reioctl(void *unit)
{
	int instance = (uintptr_t)unit;
	struct asyncline *async;
	struct xencons *xcp;
	queue_t	*q;
	mblk_t	*mp;

	xcp = ddi_get_soft_state(xencons_soft_state, instance);
	ASSERT(xcp != NULL);
	async = xcp->priv;

	/*
	 * The bufcall is no longer pending.
	 */
	mutex_enter(&xcp->excl);
	async->async_wbufcid = 0;
	if ((q = async->async_ttycommon.t_writeq) == NULL) {
		mutex_exit(&xcp->excl);
		return;
	}
	if ((mp = async->async_ttycommon.t_iocpending) != NULL) {
		/* not pending any more */
		async->async_ttycommon.t_iocpending = NULL;
		mutex_exit(&xcp->excl);
		xcasync_ioctl(async, q, mp);
	} else
		mutex_exit(&xcp->excl);
}


/*
 * debugger/console support routines.
 */

/*
 * put a character out
 * Do not use interrupts.  If char is LF, put out CR, LF.
 */
/*ARGSUSED*/
static void
xenconsputchar(cons_polledio_arg_t arg, uchar_t c)
{
	struct xencons *xcp = xencons_console;
	volatile struct xencons_interface *ifp = xcp->ifp;
	XENCONS_RING_IDX prod;

	if (c == '\n')
		xenconsputchar(arg, '\r');

	/*
	 * domain 0 can use the console I/O...
	 */
	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		char	buffer[1];

		buffer[0] = c;
		(void) HYPERVISOR_console_io(CONSOLEIO_write, 1, buffer);
		return;
	}

	/*
	 * domU has to go through dom0 virtual console.
	 */
	while (ifp->out_prod - ifp->out_cons == sizeof (ifp->out))
		(void) HYPERVISOR_yield();

	prod = ifp->out_prod;
	ifp->out[MASK_XENCONS_IDX(prod++, ifp->out)] = c;
	membar_producer();
	ifp->out_prod = prod;
	ec_notify_via_evtchn(xcp->evtchn);
}

/*
 * See if there's a character available. If no character is
 * available, return 0. Run in polled mode, no interrupts.
 */
static boolean_t
xenconsischar(cons_polledio_arg_t arg)
{
	struct xencons *xcp = (struct xencons *)arg;
	volatile struct xencons_interface *ifp = xcp->ifp;

	if (xcp->polldix < xcp->polllen)
		return (B_TRUE);
	/*
	 * domain 0 can use the console I/O...
	 */
	xcp->polldix = 0;
	xcp->polllen = 0;
	if (DOMAIN_IS_INITDOMAIN(xen_info)) {
		xcp->polllen = HYPERVISOR_console_io(CONSOLEIO_read, 1,
		    (char *)xcp->pollbuf);
		return (xcp->polllen != 0);
	}

	/*
	 * domU has to go through virtual console device.
	 */
	if (ifp->in_prod != ifp->in_cons) {
		XENCONS_RING_IDX cons;

		cons = ifp->in_cons;
		membar_enter();
		xcp->pollbuf[0] = ifp->in[MASK_XENCONS_IDX(cons++, ifp->in)];
		membar_producer();
		ifp->in_cons = cons;
		xcp->polllen = 1;
	}
	return (xcp->polllen != 0);
}

/*
 * Get a character. Run in polled mode, no interrupts.
 */
static int
xenconsgetchar(cons_polledio_arg_t arg)
{
	struct xencons *xcp = (struct xencons *)arg;

	ec_wait_on_evtchn(xcp->evtchn, (int (*)(void *))xenconsischar, arg);

	return (xcp->pollbuf[xcp->polldix++]);
}

static void
xenconserror(int level, const char *fmt, ...)
{
	va_list adx;
	static time_t	last;
	static const char *lastfmt;
	time_t now;

	/*
	 * Don't print the same error message too often.
	 * Print the message only if we have not printed the
	 * message within the last second.
	 * Note: that fmt cannot be a pointer to a string
	 * stored on the stack. The fmt pointer
	 * must be in the data segment otherwise lastfmt would point
	 * to non-sense.
	 */
	now = gethrestime_sec();
	if (last == now && lastfmt == fmt)
		return;

	last = now;
	lastfmt = fmt;

	va_start(adx, fmt);
	vcmn_err(level, fmt, adx);
	va_end(adx);
}


/*
 * Check for abort character sequence
 */
static boolean_t
abort_charseq_recognize(uchar_t ch)
{
	static int state = 0;
#define	CNTRL(c) ((c)&037)
	static char sequence[] = { '\r', '~', CNTRL('b') };

	if (ch == sequence[state]) {
		if (++state >= sizeof (sequence)) {
			state = 0;
			return (B_TRUE);
		}
	} else {
		state = (ch == sequence[0]) ? 1 : 0;
	}
	return (B_FALSE);
}

/*
 * Flow control functions
 */

/*
 * Software output flow control
 * This function can be executed sucessfully at any situation.
 * It does not handle HW, and just change the SW output flow control flag.
 * INPUT VALUE of onoff:
 *                 FLOW_START means to clear SW output flow control flag,
 *			also set ASYNC_OUT_FLW_RESUME.
 *                 FLOW_STOP means to set SW output flow control flag,
 *			also clear ASYNC_OUT_FLW_RESUME.
 */
static void
xcasync_flowcontrol_sw_output(struct xencons *xcp, async_flowc_action onoff)
{
	struct asyncline *async = xcp->priv;
	int instance = xcp->unit;

	ASSERT(mutex_owned(&xcp->excl));

	if (!(async->async_ttycommon.t_iflag & IXON))
		return;

	switch (onoff) {
	case FLOW_STOP:
		async->async_flags |= ASYNC_SW_OUT_FLW;
		async->async_flags &= ~ASYNC_OUT_FLW_RESUME;
		DEBUGCONT1(XENCONS_DEBUG_SFLOW,
		    "xencons%d: output sflow stop\n", instance);
		break;
	case FLOW_START:
		async->async_flags &= ~ASYNC_SW_OUT_FLW;
		async->async_flags |= ASYNC_OUT_FLW_RESUME;
		DEBUGCONT1(XENCONS_DEBUG_SFLOW,
		    "xencons%d: output sflow start\n", instance);
		break;
	default:
		break;
	}
}

/*
 * Software input flow control
 * This function can execute software input flow control
 * INPUT VALUE of onoff:
 *               FLOW_START means to send out a XON char
 *                          and clear SW input flow control flag.
 *               FLOW_STOP means to send out a XOFF char
 *                          and set SW input flow control flag.
 *               FLOW_CHECK means to check whether there is pending XON/XOFF
 *                          if it is true, send it out.
 * INPUT VALUE of type:
 *		 IN_FLOW_STREAMS means flow control is due to STREAMS
 *		 IN_FLOW_USER means flow control is due to user's commands
 * RETURN VALUE: B_FALSE means no flow control char is sent
 *               B_TRUE means one flow control char is sent
 */
static boolean_t
xcasync_flowcontrol_sw_input(struct xencons *xcp, async_flowc_action onoff,
    int type)
{
	struct asyncline *async = xcp->priv;
	int instance = xcp->unit;
	int rval = B_FALSE;

	ASSERT(mutex_owned(&xcp->excl));

	if (!(async->async_ttycommon.t_iflag & IXOFF))
		return (rval);

	/*
	 * If we get this far, then we know IXOFF is set.
	 */
	switch (onoff) {
	case FLOW_STOP:
		async->async_inflow_source |= type;

		/*
		 * We'll send an XOFF character for each of up to
		 * three different input flow control attempts to stop input.
		 * If we already send out one XOFF, but FLOW_STOP comes again,
		 * it seems that input flow control becomes more serious,
		 * then send XOFF again.
		 */
		if (async->async_inflow_source & (IN_FLOW_STREAMS |
		    IN_FLOW_USER))
			async->async_flags |= ASYNC_SW_IN_FLOW |
			    ASYNC_SW_IN_NEEDED;
		DEBUGCONT2(XENCONS_DEBUG_SFLOW, "xencons%d: input sflow stop, "
		    "type = %x\n", instance, async->async_inflow_source);
		break;
	case FLOW_START:
		async->async_inflow_source &= ~type;
		if (async->async_inflow_source == 0) {
			async->async_flags = (async->async_flags &
			    ~ASYNC_SW_IN_FLOW) | ASYNC_SW_IN_NEEDED;
			DEBUGCONT1(XENCONS_DEBUG_SFLOW, "xencons%d: "
			    "input sflow start\n", instance);
		}
		break;
	default:
		break;
	}

	if (async->async_flags & ASYNC_SW_IN_NEEDED) {
		/*
		 * If we get this far, then we know we need to send out
		 * XON or XOFF char.
		 */
		char c;

		rval = B_TRUE;
		c = (async->async_flags & ASYNC_SW_IN_FLOW) ?
		    async->async_stopc : async->async_startc;
		if (DOMAIN_IS_INITDOMAIN(xen_info)) {
			(void) HYPERVISOR_console_io(CONSOLEIO_write, 1, &c);
			async->async_flags &= ~ASYNC_SW_IN_NEEDED;
			return (rval);
		} else {
			xenconsputchar(NULL, c);
		}
	}
	return (rval);
}

struct module_info xencons_info = {
	0,
	"xencons",
	0,
	INFPSZ,
	4096,
	128
};

static struct qinit xencons_rint = {
	putq,
	xenconsrsrv,
	xenconsopen,
	xenconsclose,
	NULL,
	&xencons_info,
	NULL
};

static struct qinit xencons_wint = {
	xenconswput,
	NULL,
	NULL,
	NULL,
	NULL,
	&xencons_info,
	NULL
};

struct streamtab xencons_str_info = {
	&xencons_rint,
	&xencons_wint,
	NULL,
	NULL
};

static struct cb_ops cb_xencons_ops = {
	nodev,			/* cb_open */
	nodev,			/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	nodev,			/* cb_read */
	nodev,			/* cb_write */
	nodev,			/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	&xencons_str_info,		/* cb_stream */
	D_MP			/* cb_flag */
};

struct dev_ops xencons_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	xenconsinfo,		/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	xenconsattach,		/* devo_attach */
	xenconsdetach,		/* devo_detach */
	nodev,			/* devo_reset */
	&cb_xencons_ops,	/* devo_cb_ops */
	NULL,			/* devo_bus_ops */
	NULL,			/* devo_power */
	ddi_quiesce_not_needed,		/* devo_quiesce */
};

static struct modldrv modldrv = {
	&mod_driverops, /* Type of module.  This one is a driver */
	"virtual console driver",
	&xencons_ops,	/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1,
	(void *)&modldrv,
	NULL
};

int
_init(void)
{
	int rv;

	if ((rv = ddi_soft_state_init(&xencons_soft_state,
	    sizeof (struct xencons), 1)) != 0)
		return (rv);
	if ((rv = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&xencons_soft_state);
		return (rv);
	}
	DEBUGCONT2(XENCONS_DEBUG_INIT, "%s, debug = %x\n",
	    modldrv.drv_linkinfo, debug);
	return (0);
}

int
_fini(void)
{
	int rv;

	if ((rv = mod_remove(&modlinkage)) != 0)
		return (rv);

	ddi_soft_state_fini(&xencons_soft_state);
	return (0);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}