OpenSolaris_b135/uts/sun4v/io/vcc.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/uio.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/stat.h> /* needed for S_IFBLK and S_IFCHR */
#include <sys/debug.h>
#include <sys/promif.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cyclic.h>
#include <sys/termio.h>
#include <sys/intr.h>
#include <sys/ivintr.h>
#include <sys/note.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/sysmacros.h>

#include <sys/ldc.h>
#include <sys/mdeg.h>
#include <sys/vcc_impl.h>

#define	VCC_LDC_RETRIES		5
#define	VCC_LDC_DELAY		1000 /* usec */

/*
 * Function prototypes.
 */

/* DDI entrypoints */
static int	vcc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int	vcc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int	vcc_open(dev_t *devp, int flag, int otyp, cred_t *cred);
static int	vcc_close(dev_t dev, int flag, int otyp, cred_t *cred);
static int	vcc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
			cred_t *credp, int *rvalp);
static int	vcc_read(dev_t dev, struct uio *uiop, cred_t *credp);
static int	vcc_write(dev_t dev, struct uio *uiop, cred_t *credp);
static int	vcc_chpoll(dev_t dev, short events, int anyyet,
			short *reventsp, struct pollhead **phpp);
static int	vcc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
			void *arg, void **resultp);

/* callback functions */
static uint_t	vcc_ldc_cb(uint64_t event, caddr_t arg);
static int	vcc_mdeg_cb(void *cb_argp, mdeg_result_t *resp);

/* Internal functions */
static int	i_vcc_ldc_init(vcc_t *vccp, vcc_port_t *vport);
static int	i_vcc_add_port(vcc_t *vccp, char *group_name, uint64_t tcp_port,
			uint_t portno, char *domain_name);
static int	i_vcc_config_port(vcc_t *vccp, uint_t portno, uint64_t ldc_id);
static int	i_vcc_reset_events(vcc_t *vccp);
static int	i_vcc_cons_tbl(vcc_t *vccp, uint_t num_ports,
			caddr_t buf, int mode);
static int	i_vcc_del_cons_ok(vcc_t *vccp, caddr_t buf, int mode);
static int	i_vcc_close_port(vcc_port_t *vport);
static int	i_vcc_write_ldc(vcc_port_t *vport, vcc_msg_t *buf);
static int	i_vcc_read_ldc(vcc_port_t *vport, char *data_buf, size_t *sz);

static void *vcc_ssp;

static struct cb_ops vcc_cb_ops = {
	vcc_open,	    /* open */
	vcc_close,	    /* close */
	nodev,		    /* strategy */
	nodev,		    /* print */
	nodev,		    /* dump */
	vcc_read,	    /* read */
	vcc_write,	    /* write */
	vcc_ioctl,	    /* ioctl */
	nodev,		    /* devmap */
	nodev,		    /* mmap */
	ddi_segmap,	    /* segmap */
	vcc_chpoll,	    /* chpoll */
	ddi_prop_op,	    /* prop_op */
	NULL,		    /* stream */
	D_NEW | D_MP	    /* flags */
};


static struct dev_ops vcc_ops = {
	DEVO_REV,		/* rev */
	0,			/* ref count */
	vcc_getinfo,		/* getinfo */
	nulldev,		/* identify */
	nulldev,		/* probe */
	vcc_attach,		/* attach */
	vcc_detach,		/* detach */
	nodev,			/* reset */
	&vcc_cb_ops,		/* cb_ops */
	(struct bus_ops *)NULL,	/* bus_ops */
	NULL,			/* power */
	ddi_quiesce_not_needed,		/* quiesce */
};

extern struct mod_ops mod_driverops;

#define	    VCC_CHANNEL_ENDPOINT	"channel-endpoint"
#define	    VCC_ID_PROP		"id"

/*
 * This is the string displayed by modinfo(1m).
 */
static char vcc_ident[] = "sun4v Virtual Console Concentrator Driver";

static struct modldrv md = {
	&mod_driverops, 	/* Type - it is a driver */
	vcc_ident,		/* Name of the module */
	&vcc_ops,		/* driver specfic opts */
};

static struct modlinkage ml = {
	MODREV_1,
	&md,
	NULL
};

/*
 * Matching criteria passed to the MDEG to register interest
 * in changes to 'virtual-device-port' nodes identified by their
 * 'id' property.
 */
static md_prop_match_t vcc_port_prop_match[] = {
	{ MDET_PROP_VAL,	    "id"   },
	{ MDET_LIST_END,	    NULL    }
};

static mdeg_node_match_t vcc_port_match = {"virtual-device-port",
					vcc_port_prop_match};

/*
 * Specification of an MD node passed to the MDEG to filter any
 * 'virtual-device-port' nodes that do not belong to the specified node.
 * This template is copied for each vldc instance and filled in with
 * the appropriate 'cfg-handle' value before being passed to the MDEG.
 */
static mdeg_prop_spec_t vcc_prop_template[] = {
	{ MDET_PROP_STR,    "name",	"virtual-console-concentrator"	},
	{ MDET_PROP_VAL,    "cfg-handle",	NULL	},
	{ MDET_LIST_END,    NULL,		NULL	}
};

#define	VCC_SET_MDEG_PROP_INST(specp, val) (specp)[1].ps_val = (val);


#ifdef DEBUG

/*
 * Print debug messages
 *
 * set vldcdbg to 0xf to enable all messages
 *
 * 0x8 - Errors
 * 0x4 - Warnings
 * 0x2 - All debug messages (most verbose)
 * 0x1 - Minimal debug messages
 */

int vccdbg = 0x8;

static void
vccdebug(const char *fmt, ...)
{
	char buf[512];
	va_list ap;

	va_start(ap, fmt);
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);

	cmn_err(CE_CONT, "%s\n", buf);
}

#define	D1		\
if (vccdbg & 0x01)	\
	vccdebug

#define	D2		\
if (vccdbg & 0x02)	\
	vccdebug

#define	DWARN		\
if (vccdbg & 0x04)	\
	vccdebug

#else

#define	D1
#define	D2
#define	DWARN

#endif

/* _init(9E): initialize the loadable module */
int
_init(void)
{
	int error;

	/* init the soft state structure */
	error = ddi_soft_state_init(&vcc_ssp, sizeof (vcc_t), 1);
	if (error != 0) {
		return (error);
	}

	/* Link the driver into the system */
	error = mod_install(&ml);

	return (error);

}

/* _info(9E): return information about the loadable module */
int
_info(struct modinfo *modinfop)
{
	/* Report status of the dynamically loadable driver module */
	return (mod_info(&ml, modinfop));
}

/* _fini(9E): prepare the module for unloading. */
int
_fini(void)
{
	int error;

	/* Unlink the driver module from the system */
	if ((error = mod_remove(&ml)) == 0) {
		/*
		 * We have successfully "removed" the driver.
		 * destroy soft state
		 */
		ddi_soft_state_fini(&vcc_ssp);
	}

	return (error);
}

/* getinfo(9E) */
static int
vcc_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,  void *arg, void **resultp)
{
	_NOTE(ARGUNUSED(dip))

	int	instance = VCCINST(getminor((dev_t)arg));
	vcc_t	*vccp = NULL;

	switch (cmd) {

	case DDI_INFO_DEVT2DEVINFO:
		if ((vccp = ddi_get_soft_state(vcc_ssp, instance)) == NULL) {
			*resultp = NULL;
			return (DDI_FAILURE);
		}
		*resultp = vccp->dip;
		return (DDI_SUCCESS);

	case DDI_INFO_DEVT2INSTANCE:
		*resultp = (void *)(uintptr_t)instance;
		return (DDI_SUCCESS);

	default:
		*resultp = NULL;
		return (DDI_FAILURE);
	}
}

/*
 * There are two cases that need special blocking. One of them is to block
 * a minor node without a port and another is to block application other
 * than vntsd.
 *
 * A minor node can exist in the file system without associated with a port
 * because when a port is deleted, ddi_remove_minor does not unlink it.
 * Clients might try to open a minor node even after the corresponding port
 * node has been removed.  To identify and block these calls,
 * we need to validate the association between a port and its minor node.
 *
 * An application other than vntsd can access a console port as long
 * as vntsd is not using the port. A port opened by an application other
 * than vntsd will be closed when vntsd wants to use the port.
 * However, other application could use same file descriptor
 * access vcc cb_ops. So we need to identify and block caller other
 * than vntsd, when vntsd is using the port.
 */
static int
i_vcc_can_use_port(vcc_minor_t *minorp, vcc_port_t *vport)
{
	if (vport->minorp != minorp) {
		/* port config changed */
		return (ENXIO);
	}

	if (vport->valid_pid == VCC_NO_PID_BLOCKING) {
		/* no blocking needed */
		return (0);
	}

	if (vport->valid_pid != ddi_get_pid()) {
		return (EIO);
	}

	return (0);
}


/* Syncronization between thread using cv_wait */
static int
i_vcc_wait_port_status(vcc_port_t *vport, kcondvar_t *cv, uint32_t status)
{

	int	    rv;

	ASSERT(mutex_owned(&vport->lock));

	for (; ; ) {

		if ((vport->status & VCC_PORT_AVAIL) == 0) {
			/* port has been deleted */
			D1("i_vcc_wait_port_status: port%d deleted\n",
			    vport->number);
			return (EIO);
		}

		if ((vport->status & VCC_PORT_OPEN) == 0) {
			D1("i_vcc_wait_port_status: port%d is closed \n",
			    vport->number);
			return (EIO);
		}

		if (vport->status & VCC_PORT_LDC_LINK_DOWN) {
			return (EIO);
		}

		if ((vport->valid_pid != VCC_NO_PID_BLOCKING) &&
		    (vport->valid_pid != ddi_get_pid())) {
			return (EIO);
		}

		if ((vport->status & status) == status) {
			return (0);
		}

		if (!ddi_can_receive_sig()) {
			return (EIO);
		}

		rv = cv_wait_sig(cv, &vport->lock);
		if (rv == 0) {
			D1("i_vcc_wait_port_status: port%d get intr \n",
			    vport->number);
			/* got signal */
			return (EINTR);
		}
	}

}

/* Syncronization between threads, signal state change */
static void
i_vcc_set_port_status(vcc_port_t *vport, kcondvar_t *cv, uint32_t status)
{

	mutex_enter(&vport->lock);
	vport->status |= status;
	cv_broadcast(cv);
	mutex_exit(&vport->lock);
}

/* initialize a ldc channel */
static int
i_vcc_ldc_init(vcc_t *vccp, vcc_port_t *vport)
{
	ldc_attr_t 	attr;
	int		rv = EIO;

	ASSERT(mutex_owned(&vport->lock));
	ASSERT(vport->ldc_id != VCC_INVALID_CHANNEL);

	/* initialize the channel */
	attr.devclass = LDC_DEV_SERIAL;
	attr.instance = ddi_get_instance(vccp->dip);
	attr.mtu = VCC_MTU_SZ;
	attr.mode = LDC_MODE_RAW;

	if ((rv = ldc_init(vport->ldc_id, &attr, &(vport->ldc_handle))) != 0) {
		cmn_err(CE_CONT, "i_vcc_ldc_init: port %d ldc channel %ld"
		    " failed ldc_init %d \n", vport->number, vport->ldc_id, rv);
		vport->ldc_id = VCC_INVALID_CHANNEL;
		return (rv);
	}

	/* register it */
	if ((rv = ldc_reg_callback(vport->ldc_handle, vcc_ldc_cb,
	    (caddr_t)vport)) != 0) {
		cmn_err(CE_CONT, "i_vcc_ldc_init: port@%d ldc_register_cb"
		    "failed\n", vport->number);
		(void) ldc_fini(vport->ldc_handle);
		vport->ldc_id = VCC_INVALID_CHANNEL;
		return (rv);
	}

	/* open and bring channel up */
	if ((rv = ldc_open(vport->ldc_handle)) != 0) {
		cmn_err(CE_CONT, "i_vcc_ldc_init: port@%d inv channel 0x%lx\n",
		    vport->number, vport->ldc_id);
		(void) ldc_unreg_callback(vport->ldc_handle);
		(void) ldc_fini(vport->ldc_handle);
		vport->ldc_id = VCC_INVALID_CHANNEL;
		return (rv);
	}

	/* init the channel status */
	if ((rv = ldc_status(vport->ldc_handle, &vport->ldc_status)) != 0) {
		cmn_err(CE_CONT, "i_vcc_ldc_init: port@%d ldc_status failed\n",
		    vport->number);
		(void) ldc_close(vport->ldc_handle);
		(void) ldc_unreg_callback(vport->ldc_handle);
		(void) ldc_fini(vport->ldc_handle);
		vport->ldc_id = VCC_INVALID_CHANNEL;
		return (rv);
	}

	return (0);
}

/*  release a ldc channel */
static void
i_vcc_ldc_fini(vcc_port_t *vport)
{
	int 		rv = EIO;
	vcc_msg_t	buf;
	size_t		sz;
	int		retry = 0;

	D1("i_vcc_ldc_fini: port@%lld, ldc_id%%llx\n", vport->number,
	    vport->ldc_id);

	ASSERT(mutex_owned(&vport->lock));

	/* wait for write available */
	rv = i_vcc_wait_port_status(vport, &vport->write_cv,
	    VCC_PORT_USE_WRITE_LDC);

	if (rv == 0) {
		vport->status &= ~VCC_PORT_USE_WRITE_LDC;

		/* send a HUP message */
		buf.type = LDC_CONSOLE_CTRL;
		buf.ctrl_msg = LDC_CONSOLE_HUP;
		buf.size = 0;

		/*
		 * ignore write error since we still want to clean up
		 * ldc channel.
		 */
		(void) i_vcc_write_ldc(vport, &buf);

		mutex_exit(&vport->lock);
		i_vcc_set_port_status(vport, &vport->write_cv,
		    VCC_PORT_USE_WRITE_LDC);
		mutex_enter(&vport->lock);
	}

	/* flush ldc channel */
	rv = i_vcc_wait_port_status(vport, &vport->read_cv,
	    VCC_PORT_USE_READ_LDC);

	if (rv == 0) {
		vport->status &= ~VCC_PORT_USE_READ_LDC;
		do {
			sz = sizeof (buf);
			rv = i_vcc_read_ldc(vport, (char *)&buf, &sz);
		} while (rv == 0 && sz > 0);

		vport->status |= VCC_PORT_USE_READ_LDC;

	}

	/*
	 * ignore read error since we still want to clean up
	 * ldc channel.
	 */

	(void) ldc_set_cb_mode(vport->ldc_handle, LDC_CB_DISABLE);

	/* close LDC channel - retry on EAGAIN */
	while ((rv = ldc_close(vport->ldc_handle)) == EAGAIN) {

		if (++retry > VCC_LDC_RETRIES) {
			cmn_err(CE_CONT, "i_vcc_ldc_fini: cannot close channel"
			    " %ld\n", vport->ldc_id);
			break;
		}

		drv_usecwait(VCC_LDC_DELAY);
	}

	if (rv == 0) {
		(void) ldc_unreg_callback(vport->ldc_handle);
		(void) ldc_fini(vport->ldc_handle);
	} else {
		/*
		 * Closing the LDC channel has failed. Ideally we should
		 * fail here but there is no Zeus level infrastructure
		 * to handle this. The MD has already been changed and
		 * we have to do the close. So we try to do as much
		 * clean up as we can.
		 */
		while (ldc_unreg_callback(vport->ldc_handle) == EAGAIN)
			drv_usecwait(VCC_LDC_DELAY);
	}

}

/* read data from ldc channel */

static int
i_vcc_read_ldc(vcc_port_t *vport, char *data_buf, size_t *sz)
{

	int		rv;
	size_t		size;
	size_t		space_left = *sz;
	vcc_msg_t  	buf;
	int 		i;




	/* make sure holding read lock */
	ASSERT((vport->status & VCC_PORT_USE_READ_LDC) == 0);
	ASSERT(space_left >= VCC_MTU_SZ);

	*sz = 0;
	while (space_left >= VCC_MTU_SZ)  {
		size = sizeof (buf);

		rv = ldc_read(vport->ldc_handle, (caddr_t)&buf, &size);

		if (rv) {
			return (rv);
		}


		/*
		 * FIXME: ldc_read should not reaturn 0 with
		 * either size == 0, buf.size == 0 or size < VCC_HDR_SZ
		 */
		if (size == 0) {
			if (*sz > 0) {
				return (0);
			}
			return (EAGAIN);
		}

		if (size < VCC_HDR_SZ) {
			return (EIO);
		}

		/*
		 * only data is expected from console - otherwise
		 * return error
		 */
		if (buf.type != LDC_CONSOLE_DATA) {
			return (EIO);
		}

		if (buf.size == 0) {
			if (*sz > 0) {
				return (0);
			}
			return (EAGAIN);
		}

		/* copy  data */
		for (i = 0; i < buf.size; i++, (*sz)++) {
			data_buf[*sz] = buf.data[i];
		}

		space_left -= buf.size;
	}

	return (0);
}

/* callback from ldc */
static uint_t
vcc_ldc_cb(uint64_t event, caddr_t arg)
{

	vcc_port_t  *vport = (vcc_port_t *)arg;
	boolean_t   hasdata;

	/*
	 * do not need to hold lock because if ldc calls back, the
	 * ldc_handle must be valid.
	 */
	D2("vcc_ldc_cb: callback invoked port=%d events=%llx\n",
	    vport->number, event);

	/* check event from ldc */
	if (event & LDC_EVT_WRITE) {
		/* channel has space for write */

		i_vcc_set_port_status(vport, &vport->write_cv,
		    VCC_PORT_LDC_WRITE_READY);
		return (LDC_SUCCESS);
	}

	if (event & LDC_EVT_READ) {

		/* channel has data for read */
		(void) ldc_chkq(vport->ldc_handle, &hasdata);
		if (!hasdata) {
			/* data already read */
			return (LDC_SUCCESS);
		}

		i_vcc_set_port_status(vport, &vport->read_cv,
		    VCC_PORT_LDC_DATA_READY);
		return (LDC_SUCCESS);
	}

	if (event & LDC_EVT_DOWN) {
		/* channel is down */
		i_vcc_set_port_status(vport, &vport->write_cv,
		    VCC_PORT_LDC_LINK_DOWN);
		cv_broadcast(&vport->read_cv);

	}

	return (LDC_SUCCESS);

}


/* configure a vcc port with ldc channel */
static int
i_vcc_config_port(vcc_t *vccp, uint_t portno, uint64_t ldc_id)
{
	int 		rv = EIO;
	vcc_port_t 	*vport;

	if ((portno >= VCC_MAX_PORTS) || (portno == VCC_CONTROL_PORT)) {
		cmn_err(CE_CONT, "i_vcc_config_port: invalid port number %d\n",
		    portno);
		return (EINVAL);
	}

	vport = &(vccp->port[portno]);
	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		cmn_err(CE_CONT, "i_vcc_config_port: port@%d does not exist\n",
		    portno);
		return (EINVAL);
	}


	if (vport->ldc_id != VCC_INVALID_CHANNEL) {
		cmn_err(CE_CONT, "i_vcc_config_port: port@%d channel already"
		    "configured\n", portno);
		return (EINVAL);
	}

	mutex_enter(&vport->lock);

	/* store the ldc ID */
	vport->ldc_id = ldc_id;
	/* check if someone has already opened this port */
	if (vport->status & VCC_PORT_OPEN) {

		if ((rv = i_vcc_ldc_init(vccp, vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		/* mark port as ready */
		vport->status |= VCC_PORT_LDC_CHANNEL_READY;
		cv_broadcast(&vport->read_cv);
		cv_broadcast(&vport->write_cv);
	}

	mutex_exit(&vport->lock);

	D1("i_vcc_config_port: port@%d ldc=%d, domain=%s",
	    vport->number, vport->ldc_id, vport->minorp->domain_name);

	return (0);
}

/* add a vcc console port */
static int
i_vcc_add_port(vcc_t *vccp, char *group_name, uint64_t tcp_port,
    uint_t portno, char *domain_name)
{
	int 		instance;
	int		rv = MDEG_FAILURE;
	minor_t 	minor;
	vcc_port_t 	*vport;
	uint_t		minor_idx;
	char		name[MAXPATHLEN];

	if ((portno >= VCC_MAX_PORTS) || (portno == VCC_CONTROL_PORT)) {
		DWARN("i_vcc_add_port: invalid port number %d\n", portno);
		return (MDEG_FAILURE);
	}

	vport = &(vccp->port[portno]);
	if (vport->status & VCC_PORT_AVAIL) {
		/* this port already exists */
		cmn_err(CE_CONT, "i_vcc_add_port: invalid port - port@%d "
		    "exists\n", portno);
		return (MDEG_FAILURE);
	}

	vport->number = portno;
	vport->ldc_id = VCC_INVALID_CHANNEL;

	if (domain_name == NULL) {
		cmn_err(CE_CONT, "i_vcc_add_port: invalid domain name\n");
		return (MDEG_FAILURE);
	}

	if (group_name == NULL) {
		cmn_err(CE_CONT, "i_vcc_add_port: invalid group name\n");
		return (MDEG_FAILURE);
	}

	/* look up minor number */
	for (minor_idx = 0; minor_idx < vccp->minors_assigned; minor_idx++) {
		if (strcmp(vccp->minor_tbl[minor_idx].domain_name,
		    domain_name) == 0) {
			/* found previous assigned minor number */
			break;
		}
	}

	if (minor_idx == vccp->minors_assigned) {
		/* end of lookup - assign new minor number */
		if (minor_idx == VCC_MAX_PORTS) {
			cmn_err(CE_CONT, "i_vcc_add_port:"
			    "too many minornodes (%d)\n",
			    minor_idx);
			return (MDEG_FAILURE);
		}

		(void) strlcpy(vccp->minor_tbl[minor_idx].domain_name,
		    domain_name, MAXPATHLEN);

		vccp->minors_assigned++;
	}

	vport->minorp = &vccp->minor_tbl[minor_idx];
	vccp->minor_tbl[minor_idx].portno = portno;

	(void) strlcpy(vport->group_name, group_name, MAXPATHLEN);

	vport->tcp_port = tcp_port;
	D1("i_vcc_add_port:@%d domain=%s, group=%s, tcp=%lld",
	    vport->number, vport->minorp->domain_name,
	    vport->group_name, vport->tcp_port);


	/*
	 * Create a minor node. The minor number is
	 * (instance << VCC_INST_SHIFT) | minor_idx
	 */
	instance = ddi_get_instance(vccp->dip);

	minor = (instance << VCC_INST_SHIFT) | (minor_idx);

	(void) snprintf(name, MAXPATHLEN - 1, "%s%s", VCC_MINOR_NAME_PREFIX,
	    domain_name);

	rv = ddi_create_minor_node(vccp->dip, name, S_IFCHR, minor,
	    DDI_NT_SERIAL, 0);

	if (rv != DDI_SUCCESS) {
		vccp->minors_assigned--;
		return (MDEG_FAILURE);
	}

	mutex_enter(&vport->lock);
	vport->status = VCC_PORT_AVAIL | VCC_PORT_ADDED;
	mutex_exit(&vport->lock);


	return (MDEG_SUCCESS);
}

/* delete a port */
static int
i_vcc_delete_port(vcc_t *vccp, vcc_port_t *vport)
{

	char	name[MAXPATHLEN];
	int	rv;


	ASSERT(mutex_owned(&vport->lock));

	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		D1("vcc_del_port port already deleted \n");
		return (0);
	}

	if (vport->status & VCC_PORT_OPEN) {
		/* do not block mdeg callback */
		vport->valid_pid = VCC_NO_PID_BLOCKING;
		rv = i_vcc_close_port(vport);
	}

	/* remove minor node */
	(void) snprintf(name, MAXPATHLEN-1, "%s%s", VCC_MINOR_NAME_PREFIX,
	    vport->minorp->domain_name);

	ddi_remove_minor_node(vccp->dip, name);

	/* let read and write thread know */
	cv_broadcast(&vport->read_cv);
	cv_broadcast(&vport->write_cv);
	vport->status = 0;
	return (rv);


}

/* register callback to MDEG */
static int
i_vcc_mdeg_register(vcc_t *vccp, int instance)
{
	mdeg_prop_spec_t	*pspecp;
	mdeg_node_spec_t	*ispecp;
	mdeg_handle_t		mdeg_hdl;
	int			sz;
	int			rv;

	/*
	 * Allocate and initialize a per-instance copy
	 * of the global property spec array that will
	 * uniquely identify this vcc instance.
	 */
	sz = sizeof (vcc_prop_template);
	pspecp = kmem_alloc(sz, KM_SLEEP);

	bcopy(vcc_prop_template, pspecp, sz);

	VCC_SET_MDEG_PROP_INST(pspecp, instance);

	/* initialize the complete prop spec structure */
	ispecp = kmem_zalloc(sizeof (mdeg_node_spec_t), KM_SLEEP);
	ispecp->namep = "virtual-device";
	ispecp->specp = pspecp;

	/* perform the registration */
	rv = mdeg_register(ispecp, &vcc_port_match, vcc_mdeg_cb,
	    vccp, &mdeg_hdl);

	if (rv != MDEG_SUCCESS) {
		cmn_err(CE_CONT, "i_vcc_mdeg_register:"
		    "mdeg_register failed (%d)\n", rv);
		kmem_free(ispecp, sizeof (mdeg_node_spec_t));
		kmem_free(pspecp, sz);
		return (DDI_FAILURE);
	}

	/* save off data that will be needed later */
	vccp->md_ispecp = (void *)ispecp;
	vccp->mdeg_hdl = mdeg_hdl;

	return (0);
}

/* destroy all mutex from port table */
static void
i_vcc_cleanup_port_table(vcc_t *vccp)
{
	int i;
	vcc_port_t *vport;

	for (i = 0; i < VCC_MAX_PORTS; i++) {
		vport = &(vccp->port[i]);
		mutex_destroy(&vport->lock);
		cv_destroy(&vport->read_cv);
		cv_destroy(&vport->write_cv);
	}
}

/*
 * attach(9E): attach a device to the system.
 * called once for each instance of the device on the system.
 */
static int
vcc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int 		i, instance, inst;
	int 		rv = DDI_FAILURE;
	vcc_t		*vccp;
	minor_t 	minor;
	vcc_port_t	*vport;

	switch (cmd) {

	case DDI_ATTACH:

		instance = ddi_get_instance(dip);
		if (ddi_soft_state_zalloc(vcc_ssp, instance) != DDI_SUCCESS)
			return (DDI_FAILURE);

		vccp = ddi_get_soft_state(vcc_ssp, instance);
		if (vccp == NULL) {
			ddi_soft_state_free(vccp, instance);
			return (ENXIO);
		}

		D1("vcc_attach: DDI_ATTACH instance=%d\n", instance);

		/* initialize the mutex */
		mutex_init(&vccp->lock, NULL, MUTEX_DRIVER, NULL);

		mutex_enter(&vccp->lock);

		vccp->dip = dip;

		for (i = 0; i < VCC_MAX_PORTS; i++) {
			vport = &(vccp->port[i]);
			mutex_init(&vport->lock, NULL, MUTEX_DRIVER, NULL);
			cv_init(&vport->read_cv, NULL, CV_DRIVER, NULL);
			cv_init(&vport->write_cv, NULL, CV_DRIVER, NULL);
			vport->valid_pid = VCC_NO_PID_BLOCKING;
		}

		vport = &vccp->port[VCC_CONTROL_PORT];
		mutex_enter(&vport->lock);

		vport->minorp = &vccp->minor_tbl[VCC_CONTROL_MINOR_IDX];
		vport->status |= VCC_PORT_AVAIL;

		/* create a minor node for vcc control */
		minor = (instance << VCC_INST_SHIFT) | VCC_CONTROL_MINOR_IDX;

		vccp->minor_tbl[VCC_CONTROL_PORT].portno =
		    VCC_CONTROL_MINOR_IDX;


		rv = ddi_create_minor_node(vccp->dip, "ctl", S_IFCHR, minor,
		    DDI_NT_SERIAL, 0);

		mutex_exit(&vport->lock);

		if (rv != DDI_SUCCESS) {
			cmn_err(CE_CONT, "vcc_attach: error"
			    "creating control minor node\n");

			i_vcc_cleanup_port_table(vccp);

			mutex_exit(&vccp->lock);
			/* clean up soft state */
			ddi_soft_state_free(vccp, instance);

			return (DDI_FAILURE);
		}

		/* get the instance number by reading 'reg' property */
		inst = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
		    "reg", -1);
		if (inst == -1) {
			cmn_err(CE_CONT, "vcc_attach: vcc%d has no "
			    "'reg' property\n",
			    ddi_get_instance(dip));

			i_vcc_cleanup_port_table(vccp);

			/* remove minor */
			ddi_remove_minor_node(vccp->dip, NULL);

			/* clean up soft state */
			mutex_exit(&vccp->lock);
			ddi_soft_state_free(vccp, instance);

			return (DDI_FAILURE);
		}

		/*
		 * Mdeg might invoke callback in the same call sequence
		 * if there is a domain port at the time of registration.
		 * Since the callback also grabs vcc->lock mutex, to avoid
		 * mutex reentry error, release the lock before registration
		 */
		mutex_exit(&vccp->lock);

		/* register for notifications from Zeus */
		rv = i_vcc_mdeg_register(vccp, inst);
		if (rv != MDEG_SUCCESS) {
			cmn_err(CE_CONT, "vcc_attach: error register to MD\n");

			i_vcc_cleanup_port_table(vccp);

			/* remove minor */
			ddi_remove_minor_node(vccp->dip, NULL);

			/* clean up soft state */
			ddi_soft_state_free(vccp, instance);

			return (DDI_FAILURE);
		}

		return (DDI_SUCCESS);

	case DDI_RESUME:

		return (DDI_SUCCESS);

	default:

		return (DDI_FAILURE);
	}
}

/*
 * detach(9E): detach a device from the system.
 */
static int
vcc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	int		    i, instance;
	vcc_t		    *vccp;
	mdeg_node_spec_t    *ispecp;
	vcc_port_t	    *vport;

	switch (cmd) {

	case DDI_DETACH:

		instance = ddi_get_instance(dip);
		vccp = ddi_get_soft_state(vcc_ssp, instance);
		if (vccp == NULL)
			return (ENXIO);

		D1("vcc_detach: DDI_DETACH instance=%d\n", instance);

		mutex_enter(&vccp->lock);

		/* unregister from MD event generator */

		ASSERT(vccp->mdeg_hdl);
		(void) mdeg_unregister(vccp->mdeg_hdl);

		ispecp = (mdeg_node_spec_t *)vccp->md_ispecp;
		ASSERT(ispecp);

		kmem_free(ispecp->specp, sizeof (vcc_prop_template));
		kmem_free(ispecp, sizeof (mdeg_node_spec_t));

		/* remove minor nodes */
		ddi_remove_minor_node(vccp->dip, NULL);
		mutex_exit(&vccp->lock);

		for (i = 0; i < VCC_MAX_PORTS; i++) {

			vport = &vccp->port[i];
			mutex_enter(&vport->lock);
			if (i == VCC_CONTROL_PORT) {
				if (vport->status & VCC_PORT_OPEN) {
					(void) i_vcc_close_port(vport);
				}
			}

			if ((vccp->port[i].status & VCC_PORT_AVAIL) &&
			    (i != VCC_CONTROL_PORT)) {
				D1("vcc_detach: removing port port@%d\n", i);
				(void) i_vcc_delete_port(vccp, vport);
			}
			mutex_exit(&vport->lock);
			cv_destroy(&vport->read_cv);
			cv_destroy(&vport->write_cv);
			mutex_destroy(&vport->lock);
		}



		/* destroy mutex and free the soft state */
		mutex_destroy(&vccp->lock);
		ddi_soft_state_free(vcc_ssp, instance);

		return (DDI_SUCCESS);

	case DDI_SUSPEND:

		return (DDI_SUCCESS);

	default:

		return (DDI_FAILURE);
	}
}

/* cb_open */
static int
vcc_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
	_NOTE(ARGUNUSED(otyp, cred))

	int	    instance;
	int	    rv = EIO;
	minor_t	    minor;
	uint_t	    portno;
	vcc_t	    *vccp;
	vcc_port_t  *vport;

	minor = getminor(*devp);
	instance = VCCINST(minor);

	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	vport = &(vccp->port[portno]);

	mutex_enter(&vport->lock);

	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		/* port may be removed */
		mutex_exit(&vport->lock);
		return (ENXIO);
	}

	if (vport->status & VCC_PORT_OPEN) {
		/* only one open per port */
		cmn_err(CE_CONT, "vcc_open: virtual-console-concentrator@%d:%d "
		    "is already open\n", instance, portno);
		mutex_exit(&vport->lock);
		return (EAGAIN);
	}

	/* check minor no and pid */
	if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
	    vport)) != 0) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	if (portno == VCC_CONTROL_PORT) {
		vport->status |= VCC_PORT_OPEN;
		mutex_exit(&vport->lock);
		return (0);
	}

	/*
	 * the port may just be added by mdeg callback and may
	 * not be configured yet.
	 */
	if (vport->ldc_id == VCC_INVALID_CHANNEL) {
		mutex_exit(&vport->lock);
		return (ENXIO);
	}


	/* check if channel has been initialized */
	if ((vport->status & VCC_PORT_LDC_CHANNEL_READY) == 0) {
		rv = i_vcc_ldc_init(vccp, vport);
		if (rv) {
			mutex_exit(&vport->lock);
			return (EIO);
		}

		/* mark port as ready */
		vport->status |= VCC_PORT_LDC_CHANNEL_READY;
	}

	vport->status |= VCC_PORT_USE_READ_LDC | VCC_PORT_USE_WRITE_LDC|
	    VCC_PORT_TERM_RD|VCC_PORT_TERM_WR|VCC_PORT_OPEN;

	if ((flag & O_NONBLOCK) || (flag & O_NDELAY)) {
		vport->status |= VCC_PORT_NONBLOCK;
	}

	mutex_exit(&vport->lock);

	return (0);
}

/* close port */
static int
i_vcc_close_port(vcc_port_t *vport)
{

	if ((vport->status & VCC_PORT_OPEN) == 0) {
		return (0);
	}

	ASSERT(mutex_owned(&vport->lock));

	if (vport->status & VCC_PORT_LDC_CHANNEL_READY) {
		/* clean up ldc channel */
		i_vcc_ldc_fini(vport);
		vport->status &= ~VCC_PORT_LDC_CHANNEL_READY;
	}

	/* reset  rd/wr suspends  */
	vport->status |= VCC_PORT_TERM_RD | VCC_PORT_TERM_WR;
	vport->status &= ~VCC_PORT_NONBLOCK;
	vport->status &= ~VCC_PORT_OPEN;
	vport->valid_pid = VCC_NO_PID_BLOCKING;

	/* signal any blocked read and write thread */
	cv_broadcast(&vport->read_cv);
	cv_broadcast(&vport->write_cv);

	return (0);
}

/* cb_close */
static int
vcc_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
	_NOTE(ARGUNUSED(flag, otyp, cred))

	int	    instance;
	minor_t	    minor;
	int	    rv = EIO;
	uint_t	    portno;
	vcc_t	    *vccp;
	vcc_port_t  *vport;

	minor = getminor(dev);

	instance = VCCINST(minor);
	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	D1("vcc_close: closing virtual-console-concentrator@%d:%d\n",
	    instance, portno);
	vport = &(vccp->port[portno]);


	/*
	 * needs lock to provent i_vcc_delete_port, which is called by
	 * the mdeg callback, from closing port.
	 */
	mutex_enter(&vport->lock);

	if ((vport->status & VCC_PORT_OPEN) == 0) {
		mutex_exit(&vport->lock);
		return (0);
	}

	if (portno == VCC_CONTROL_PORT) {
		/*
		 * vntsd closes control port before it exits. There
		 * could be events still pending for vntsd.
		 */
		mutex_exit(&vport->lock);
		rv = i_vcc_reset_events(vccp);
		return (0);
	}


	/* check minor no and pid */
	if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
	    vport)) != 0) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	rv = i_vcc_close_port(vport);
	mutex_exit(&vport->lock);

	return (rv);
}

/*
 * ioctl VCC_CONS_TBL - vntsd allocates buffer according to return of
 * VCC_NUM_PORTS. However, when vntsd requests for the console table, console
 * ports could be deleted or added. parameter num_ports is number of structures
 * that vntsd allocated for the table. If there are more ports than
 * num_ports, set up to wakeup vntsd to add ports.
 * If there less ports than num_ports, fill (-1) for cons_no to tell vntsd.
 */
static int
i_vcc_cons_tbl(vcc_t *vccp, uint_t num_ports, caddr_t buf, int mode)
{
	vcc_console_t	cons;
	int		i;
	vcc_port_t	*vport;
	boolean_t	notify_vntsd = B_FALSE;
	char pathname[MAXPATHLEN];


	(void) ddi_pathname(vccp->dip, pathname);
	for (i = 0; i < VCC_MAX_PORTS; i++) {

		vport = &vccp->port[i];

		if (i == VCC_CONTROL_PORT) {
			continue;
		}

		if ((vport->status & VCC_PORT_AVAIL) == 0) {
			continue;
		}

		/* a port exists before vntsd becomes online */
		mutex_enter(&vport->lock);

		if (num_ports == 0) {
			/* more ports than vntsd's buffer can hold */
			vport->status |= VCC_PORT_ADDED;
			notify_vntsd = B_TRUE;
			mutex_exit(&vport->lock);
			continue;
		}

		bzero(&cons, sizeof (vcc_console_t));

		/* construct console buffer */
		cons.cons_no = vport->number;
		cons.tcp_port = vport->tcp_port;
		(void) memcpy(cons.domain_name,
		    vport->minorp->domain_name, MAXPATHLEN);

		(void) memcpy(cons.group_name, vport->group_name,
		    MAXPATHLEN);
		vport->status &= ~VCC_PORT_ADDED;
		mutex_exit(&vport->lock);

		(void) snprintf(cons.dev_name, MAXPATHLEN-1, "%s:%s%s",
		    pathname, VCC_MINOR_NAME_PREFIX, cons.domain_name);

		/* copy out data */
		if (ddi_copyout(&cons, (void *)buf,
		    sizeof (vcc_console_t), mode)) {
			mutex_exit(&vport->lock);
			return (EFAULT);
		}
		buf += sizeof (vcc_console_t);

		num_ports--;

	}

	if (num_ports == 0) {
		/* vntsd's buffer is full */

		if (notify_vntsd) {
			/* more ports need to notify vntsd */
			vport = &vccp->port[VCC_CONTROL_PORT];
			mutex_enter(&vport->lock);
			vport->pollevent |= VCC_POLL_ADD_PORT;
			mutex_exit(&vport->lock);
		}

		return (0);
	}

	/* less ports than vntsd expected */
	bzero(&cons, sizeof (vcc_console_t));
	cons.cons_no = -1;

	while (num_ports > 0) {
		/* fill vntsd buffer with no console */
		if (ddi_copyout(&cons, (void *)buf,
		    sizeof (vcc_console_t), mode) != 0) {
			mutex_exit(&vport->lock);
			return (EFAULT);
		}
		D1("i_vcc_cons_tbl: a port is  deleted\n");
		buf += sizeof (vcc_console_t) +MAXPATHLEN;
		num_ports--;
	}

	return (0);
}


/* turn off event flag if there is no more change */
static void
i_vcc_turn_off_event(vcc_t *vccp, uint32_t port_status, uint32_t event)
{

	vcc_port_t *vport;
	int i;

	for (i = 0; i < VCC_MAX_PORTS; i++) {

		vport = &(vccp->port[i]);

		if ((vport->status & VCC_PORT_AVAIL) == 0) {
			continue;
		}


		if (vport->status & port_status) {
			/* more port changes status */
			return;
		}

	}

	/* no more changed port  */
	vport = &vccp->port[VCC_CONTROL_PORT];

	/* turn off event */
	mutex_enter(&vport->lock);
	vport->pollevent &= ~event;
	mutex_exit(&vport->lock);
}

/* ioctl VCC_CONS_INFO */
static int
i_vcc_cons_info(vcc_t *vccp, caddr_t buf, int mode)
{
	vcc_console_t	cons;
	uint_t		portno;
	vcc_port_t	*vport;
	char pathname[MAXPATHLEN];

	/* read in portno */
	if (ddi_copyin((void*)buf, &portno, sizeof (uint_t), mode)) {
		return (EFAULT);
	}

	D1("i_vcc_cons_info@%d:\n", portno);

	if ((portno >= VCC_MAX_PORTS) || (portno == VCC_CONTROL_PORT)) {
		return (EINVAL);
	}

	vport = &vccp->port[portno];

	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		return (EINVAL);
	}

	mutex_enter(&vport->lock);
	vport->status &= ~VCC_PORT_ADDED;

	/* construct configruation data  */
	bzero(&cons, sizeof (vcc_console_t));

	cons.cons_no = vport->number;
	cons.tcp_port = vport->tcp_port;

	(void) memcpy(cons.domain_name, vport->minorp->domain_name, MAXPATHLEN);

	(void) memcpy(cons.group_name, vport->group_name, MAXPATHLEN);

	mutex_exit(&vport->lock);

	(void) ddi_pathname(vccp->dip, pathname),

	    /* copy device name */
	    (void) snprintf(cons.dev_name, MAXPATHLEN-1, "%s:%s%s",
	    pathname, VCC_MINOR_NAME_PREFIX, cons.domain_name);
	/* copy data */
	if (ddi_copyout(&cons, (void *)buf,
	    sizeof (vcc_console_t), mode) != 0) {
		mutex_exit(&vport->lock);
		return (EFAULT);
	}

	D1("i_vcc_cons_info@%d:domain:%s serv:%s tcp@%lld %s\n",
	    cons.cons_no, cons.domain_name,
	    cons.group_name, cons.tcp_port, cons.dev_name);

	i_vcc_turn_off_event(vccp, VCC_PORT_ADDED, VCC_POLL_ADD_PORT);

	return (0);
}


/* response to vntsd inquiry ioctl call */
static int
i_vcc_inquiry(vcc_t *vccp, caddr_t buf, int mode)
{
	vcc_port_t	*vport;
	uint_t		i;
	vcc_response_t	msg;

	vport = &(vccp->port[VCC_CONTROL_PORT]);

	if ((vport->pollevent & VCC_POLL_ADD_PORT) == 0) {
		return (EINVAL);
	}

	/* an added port */

	D1("i_vcc_inquiry\n");

	for (i = 0; i < VCC_MAX_PORTS; i++) {
		if ((vccp->port[i].status & VCC_PORT_AVAIL) == 0) {
			continue;
		}

		if (vccp->port[i].status & VCC_PORT_ADDED) {
			/* port added */
			msg.reason = VCC_CONS_ADDED;
			msg.cons_no = i;

			if (ddi_copyout((void *)&msg, (void *)buf,
			    sizeof (msg), mode) == -1) {
				cmn_err(CE_CONT, "i_vcc_find_changed_port:"
				    "ddi_copyout"
				    " failed\n");
				return (EFAULT);
			}
			return (0);
		}
	}

	/* the added port was deleted before vntsd wakes up */
	msg.reason = VCC_CONS_MISS_ADDED;

	if (ddi_copyout((void *)&msg, (void *)buf,
	    sizeof (msg), mode) == -1) {
		cmn_err(CE_CONT, "i_vcc_find_changed_port: ddi_copyout"
		    " failed\n");
		return (EFAULT);
	}

	return (0);
}

/* clean up events after vntsd exits */
static int
i_vcc_reset_events(vcc_t *vccp)
{
	uint_t	    i;
	vcc_port_t  *vport;

	for (i = 0; i < VCC_MAX_PORTS; i++) {
		vport = &(vccp->port[i]);

		if ((vport->status & VCC_PORT_AVAIL) == 0) {
			continue;
		}

		ASSERT(!mutex_owned(&vport->lock));

		if (i == VCC_CONTROL_PORT) {
			/* close control port */
			mutex_enter(&vport->lock);
			vport->status &= ~VCC_PORT_OPEN;

			/* clean up poll events */
			vport->pollevent = 0;
			vport->pollflag = 0;
			mutex_exit(&vport->lock);
			continue;
		}
		if (vport->status & VCC_PORT_ADDED) {
			/* pending added port event to vntsd */
			mutex_enter(&vport->lock);
			vport->status &= ~VCC_PORT_ADDED;
			mutex_exit(&vport->lock);
		}

	}

	vport = &vccp->port[VCC_CONTROL_PORT];

	return (0);
}

/* ioctl VCC_FORCE_CLOSE */
static int
i_vcc_force_close(vcc_t *vccp, caddr_t buf, int mode)
{
	uint_t		portno;
	vcc_port_t	*vport;
	int		rv;

	/* read in portno */
	if (ddi_copyin((void*)buf, &portno, sizeof (uint_t), mode)) {
		return (EFAULT);
	}

	D1("i_vcc_force_close@%d:\n", portno);

	if ((portno >= VCC_MAX_PORTS) || (portno == VCC_CONTROL_PORT)) {
		return (EINVAL);
	}

	vport = &vccp->port[portno];

	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		return (EINVAL);
	}

	mutex_enter(&vport->lock);

	rv = i_vcc_close_port(vport);

	/* block callers other than vntsd */
	vport->valid_pid = ddi_get_pid();

	mutex_exit(&vport->lock);
	return (rv);

}

/* ioctl VCC_CONS_STATUS */
static int
i_vcc_cons_status(vcc_t *vccp, caddr_t buf, int mode)
{
	vcc_console_t	console;
	vcc_port_t	*vport;

	/* read in portno */
	if (ddi_copyin((void*)buf, &console, sizeof (console), mode)) {
		return (EFAULT);
	}

	D1("i_vcc_cons_status@%d:\n", console.cons_no);

	if ((console.cons_no >= VCC_MAX_PORTS) ||
	    (console.cons_no == VCC_CONTROL_PORT)) {
		return (EINVAL);
	}


	vport = &vccp->port[console.cons_no];
	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		console.cons_no = -1;
	} else  if (strncmp(console.domain_name, vport->minorp->domain_name,
	    MAXPATHLEN)) {
		console.cons_no = -1;
	} else if (strncmp(console.group_name, vport->group_name,
	    MAXPATHLEN)) {
		console.cons_no = -1;
	} else if (console.tcp_port != vport->tcp_port) {
		console.cons_no = -1;
	} else if (vport->ldc_id == VCC_INVALID_CHANNEL) {
		console.cons_no = -1;
	}

	D1("i_vcc_cons_status@%d: %s %s %llx\n", console.cons_no,
	    console.group_name, console.domain_name, console.tcp_port);
	if (ddi_copyout(&console, (void *)buf, sizeof (console), mode) == -1) {
		cmn_err(CE_CONT, "i_vcc_cons_status ddi_copyout failed\n");
		return (EFAULT);
	}

	return (0);
}

/* cb_ioctl handler for vcc control port */
static int
i_vcc_ctrl_ioctl(vcc_t *vccp, int cmd, void* arg, int mode)
{

	static uint_t	num_ports;


	switch (cmd) {

	case VCC_NUM_CONSOLE:

		mutex_enter(&vccp->lock);
		num_ports = vccp->num_ports;
		mutex_exit(&vccp->lock);
		/* number of consoles */

		return (ddi_copyout((void *)&num_ports, arg,
		    sizeof (int), mode));
	case VCC_CONS_TBL:

		/* console config table */
		return (i_vcc_cons_tbl(vccp, num_ports, (caddr_t)arg, mode));

	case VCC_INQUIRY:

		/* reason for wakeup */
		return (i_vcc_inquiry(vccp, (caddr_t)arg, mode));

	case VCC_CONS_INFO:
		/* a console config */
		return (i_vcc_cons_info(vccp, (caddr_t)arg, mode));

	case VCC_FORCE_CLOSE:
		/* force to close a console */
		return (i_vcc_force_close(vccp, (caddr_t)arg, mode));

	case VCC_CONS_STATUS:
		/* console status */
		return (i_vcc_cons_status(vccp, (caddr_t)arg, mode));

	default:

		/* unknown command */
		return (ENODEV);
	}


}

/* write data to ldc. may block if channel has no space for write */
static int
i_vcc_write_ldc(vcc_port_t *vport, vcc_msg_t *buf)
{
	int	rv = EIO;
	size_t	size;

	ASSERT(mutex_owned(&vport->lock));
	ASSERT((vport->status & VCC_PORT_USE_WRITE_LDC) == 0);

	for (; ; ) {

		size = VCC_HDR_SZ + buf->size;
		rv = ldc_write(vport->ldc_handle, (caddr_t)buf, &size);

		D1("i_vcc_write_ldc: port@%d: err=%d %d bytes\n",
		    vport->number, rv, size);

		if (rv == 0) {
			return (rv);
		}

		if (rv != EWOULDBLOCK) {
			return (EIO);
		}

		if (vport->status & VCC_PORT_NONBLOCK) {
			return (EAGAIN);
		}

		/*  block util ldc has more space */

		rv = i_vcc_wait_port_status(vport, &vport->write_cv,
		    VCC_PORT_LDC_WRITE_READY);

		if (rv) {
			return (rv);
		}

		vport->status &= ~VCC_PORT_LDC_WRITE_READY;

	}

}



/* cb_ioctl handler for port ioctl */
static int
i_vcc_port_ioctl(vcc_t *vccp, minor_t minor, int portno, int cmd, void *arg,
    int mode)
{

	vcc_port_t	*vport;
	struct termios	term;
	vcc_msg_t	buf;
	int		rv;

	D1("i_vcc_port_ioctl@%d cmd %d\n", portno, cmd);

	vport = &(vccp->port[portno]);

	if ((vport->status & VCC_PORT_AVAIL) == 0) {
		return (EIO);
	}


	switch (cmd) {

	/* terminal support */
	case TCGETA:
	case TCGETS:

		mutex_enter(&vport->lock);

		/* check minor no and pid */
		if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
		    vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		(void) memcpy(&term, &vport->term, sizeof (term));
		mutex_exit(&vport->lock);

		return (ddi_copyout(&term, arg, sizeof (term), mode));

	case TCSETS:
	case TCSETA:
	case TCSETAW:
	case TCSETAF:

		if (ddi_copyin(arg, &term, sizeof (term), mode) != 0) {
			return (EFAULT);
		}

		mutex_enter(&vport->lock);

		/* check minor no and pid */
		if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
		    vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		(void) memcpy(&vport->term, &term, sizeof (term));
		mutex_exit(&vport->lock);
		return (0);


	case TCSBRK:

		/* send break to console */
		mutex_enter(&vport->lock);

		/* check minor no and pid */
		if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
		    vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		/* wait for write available */
		rv = i_vcc_wait_port_status(vport, &vport->write_cv,
		    VCC_PORT_LDC_CHANNEL_READY| VCC_PORT_USE_WRITE_LDC);
		if (rv) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		vport->status &= ~VCC_PORT_USE_WRITE_LDC;

		buf.type = LDC_CONSOLE_CTRL;
		buf.ctrl_msg = LDC_CONSOLE_BREAK;
		buf.size = 0;

		rv = i_vcc_write_ldc(vport, &buf);

		mutex_exit(&vport->lock);

		i_vcc_set_port_status(vport, &vport->write_cv,
		    VCC_PORT_USE_WRITE_LDC);
		return (0);

	case TCXONC:
		/* suspend read or write */
		if (ddi_copyin(arg, &cmd, sizeof (int), mode) != 0) {
			return (EFAULT);
		}

		mutex_enter(&vport->lock);

		/* check minor no and pid */
		if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
		    vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}


		switch (cmd) {

		case 0:
			/* suspend read */
			vport->status &= ~VCC_PORT_TERM_RD;
			break;

		case 1:
			/* resume read */
			vport->status |= VCC_PORT_TERM_RD;
			cv_broadcast(&vport->read_cv);
			break;

		case 2:
			/* suspend write */
			vport->status &= ~VCC_PORT_TERM_WR;
			break;

		case 3:
			/* resume write */
			vport->status |= VCC_PORT_TERM_WR;
			cv_broadcast(&vport->write_cv);
			break;

		default:
			mutex_exit(&vport->lock);
			return (EINVAL);
		}

		mutex_exit(&vport->lock);
		return (0);

	case TCFLSH:
		return (0);

	default:
		return (EINVAL);
	}

}

/* cb_ioctl */
static int
vcc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp)
{
	_NOTE(ARGUNUSED(credp, rvalp))

	int instance;
	minor_t minor;
	int portno;
	vcc_t *vccp;

	minor = getminor(dev);

	instance = VCCINST(minor);

	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	D1("vcc_ioctl: virtual-console-concentrator@%d:%d\n", instance, portno);

	if (portno >= VCC_MAX_PORTS) {
		cmn_err(CE_CONT, "vcc_ioctl:virtual-console-concentrator@%d"
		    " invalid portno\n", portno);
		return (EINVAL);
	}

	D1("vcc_ioctl: virtual-console-concentrator@%d:%d ioctl cmd=%d\n",
	    instance, portno, cmd);

	if (portno == VCC_CONTROL_PORT) {
		/* control ioctl */
		return (i_vcc_ctrl_ioctl(vccp, cmd, (void *)arg, mode));
	}

	/* data port ioctl */
	return (i_vcc_port_ioctl(vccp, minor, portno, cmd, (void *)arg, mode));
}

/* cb_read */
static int
vcc_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
	_NOTE(ARGUNUSED(credp))

	int	    instance;
	minor_t	    minor;
	uint_t	    portno;
	vcc_t	    *vccp;
	vcc_port_t  *vport;
	int	    rv = EIO;	/* by default fail ! */
	char 		*buf;
	size_t		uio_size;
	size_t		size;

	minor = getminor(dev);

	instance = VCCINST(minor);

	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	/* no read for control port */
	if (portno == VCC_CONTROL_PORT) {
		return (EIO);
	}

	/* temp buf to hold ldc data */
	uio_size = uiop->uio_resid;

	if (uio_size < VCC_MTU_SZ) {
		return (EINVAL);
	}

	vport = &(vccp->port[portno]);

	mutex_enter(&vport->lock);

	/* check minor no and pid */
	if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
	    vport)) != 0) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	rv = i_vcc_wait_port_status(vport, &vport->read_cv,
	    VCC_PORT_TERM_RD|VCC_PORT_LDC_CHANNEL_READY|
	    VCC_PORT_USE_READ_LDC);
	if (rv) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	buf = kmem_alloc(uio_size, KM_SLEEP);

	vport->status &= ~VCC_PORT_USE_READ_LDC;

	for (; ; ) {

		size = uio_size;
		rv = i_vcc_read_ldc(vport, buf, &size);


		if (rv == EAGAIN) {
			/* should block? */
			if (vport->status & VCC_PORT_NONBLOCK) {
				break;
			}

		} else if (rv) {
			/* error */
			break;
		}

		if (size > 0) {
			/* got data */
			break;
		}

		/* wait for data from ldc */
		vport->status &= ~VCC_PORT_LDC_DATA_READY;

		mutex_exit(&vport->lock);
		i_vcc_set_port_status(vport, &vport->read_cv,
		    VCC_PORT_USE_READ_LDC);
		mutex_enter(&vport->lock);

		rv = i_vcc_wait_port_status(vport, &vport->read_cv,
		    VCC_PORT_TERM_RD|VCC_PORT_LDC_CHANNEL_READY|
		    VCC_PORT_USE_READ_LDC| VCC_PORT_LDC_DATA_READY);
		if (rv) {
			break;
		}

		vport->status &= ~VCC_PORT_USE_READ_LDC;
	}

	mutex_exit(&vport->lock);

	if ((rv == 0) && (size > 0)) {
		/* data is in buf */
		rv = uiomove(buf, size, UIO_READ, uiop);
	}

	kmem_free(buf, uio_size);
	i_vcc_set_port_status(vport, &vport->read_cv, VCC_PORT_USE_READ_LDC);

	return (rv);
}


/* cb_write */
static int
vcc_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
	_NOTE(ARGUNUSED(credp))

	int	    instance;
	minor_t	    minor;
	size_t	    size;
	size_t	    bytes;
	uint_t	    portno;
	vcc_t	    *vccp;

	vcc_port_t  *vport;
	int	    rv = EIO;

	vcc_msg_t	buf;

	minor = getminor(dev);

	instance = VCCINST(minor);

	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	/* no write for control port */
	if (portno == VCC_CONTROL_PORT) {
		return (EIO);
	}
	vport = &(vccp->port[portno]);

	/*
	 * check if the channel has been configured,
	 * if write has been suspend and grab write lock.
	 */
	mutex_enter(&vport->lock);

	/* check minor no and pid */
	if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
	    vport)) != 0) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	rv = i_vcc_wait_port_status(vport, &vport->write_cv,
	    VCC_PORT_TERM_WR|VCC_PORT_LDC_CHANNEL_READY|
	    VCC_PORT_USE_WRITE_LDC);
	if (rv) {
		mutex_exit(&vport->lock);
		return (rv);
	}

	vport->status &= ~VCC_PORT_USE_WRITE_LDC;
	mutex_exit(&vport->lock);
	size = uiop->uio_resid;

	D2("vcc_write: virtual-console-concentrator@%d:%d writing %d bytes\n",
	    instance, portno, size);



	buf.type = LDC_CONSOLE_DATA;

	while (size) {

		bytes = MIN(size, VCC_MTU_SZ);
		/* move data */
		rv = uiomove(&(buf.data), bytes, UIO_WRITE, uiop);

		if (rv) {
			break;
		}

		/* write to ldc */
		buf.size = bytes;

		mutex_enter(&vport->lock);

		/* check minor no and pid */
		if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
		    vport)) != 0) {
			mutex_exit(&vport->lock);
			return (rv);
		}

		rv = i_vcc_write_ldc(vport, &buf);

		mutex_exit(&vport->lock);

		if (rv) {
			break;
		}

		size -= bytes;

	}

	i_vcc_set_port_status(vport, &vport->write_cv, VCC_PORT_USE_WRITE_LDC);
	return (rv);
}

/* mdeg callback for a removed port */
static int
i_vcc_md_remove_port(md_t *mdp, mde_cookie_t mdep, vcc_t *vccp)
{
	uint64_t  portno;	/* md requires 64bit for port number */
	int rv = MDEG_FAILURE;
	vcc_port_t *vport;

	if (md_get_prop_val(mdp, mdep, "id", &portno)) {
		cmn_err(CE_CONT, "vcc_mdeg_cb: port has no 'id' property\n");
		return (MDEG_FAILURE);
	}

	if ((portno >= VCC_MAX_PORTS) || (portno < 0)) {
		cmn_err(CE_CONT, "i_vcc_md_remove_port@%ld invalid port no\n",
		    portno);
		return (MDEG_FAILURE);
	}

	if (portno == VCC_CONTROL_PORT) {
		cmn_err(CE_CONT, "i_vcc_md_remove_port@%ld can not remove"
		    "control port\n",
		    portno);
		return (MDEG_FAILURE);
	}

	vport = &(vccp->port[portno]);

	/* delete the port */
	mutex_enter(&vport->lock);
	rv = i_vcc_delete_port(vccp, vport);
	mutex_exit(&vport->lock);

	mutex_enter(&vccp->lock);
	vccp->num_ports--;
	mutex_exit(&vccp->lock);

	return (rv ? MDEG_FAILURE : MDEG_SUCCESS);
}

static int
i_vcc_get_ldc_id(md_t *md, mde_cookie_t mdep, uint64_t *ldc_id)
{
	int		num_nodes;
	size_t		size;
	mde_cookie_t	*channel;
	int		num_channels;


	if ((num_nodes = md_node_count(md)) <= 0) {
		cmn_err(CE_CONT, "i_vcc_get_ldc_channel_id:"
		    "  Invalid node count in Machine Description subtree");
		return (-1);
	}
	size = num_nodes*(sizeof (*channel));
	channel = kmem_zalloc(size, KM_SLEEP);
	ASSERT(channel != NULL);	/* because KM_SLEEP */


	/* Look for channel endpoint child(ren) of the vdisk MD node */
	if ((num_channels = md_scan_dag(md, mdep,
	    md_find_name(md, "channel-endpoint"),
	    md_find_name(md, "fwd"), channel)) <= 0) {
		cmn_err(CE_CONT, "i_vcc_get_ldc_id:  No 'channel-endpoint'"
		    " found for vcc");
		kmem_free(channel, size);
		return (-1);
	}

	/* Get the "id" value for the first channel endpoint node */
	if (md_get_prop_val(md, channel[0], "id", ldc_id) != 0) {
		cmn_err(CE_CONT, "i_vcc_get_ldc:  No id property found "
		    "for channel-endpoint of vcc");
		kmem_free(channel, size);
		return (-1);
	}

	if (num_channels > 1) {
		cmn_err(CE_CONT, "i_vcc_get_ldc:  Warning:  Using ID of first"
		    " of multiple channels for this vcc");
	}

	kmem_free(channel, size);
	return (0);
}
/* mdeg callback for an added port  */
static int
i_vcc_md_add_port(md_t *mdp, mde_cookie_t mdep, vcc_t *vccp)
{
	uint64_t	portno;		/* md requires 64 bit */
	char		*domain_name;
	char		*group_name;
	uint64_t	ldc_id;
	uint64_t	tcp_port;
	vcc_port_t	*vport;

	/* read in the port's reg property */
	if (md_get_prop_val(mdp, mdep, "id", &portno)) {
		cmn_err(CE_CONT, "i_vcc_md_add_port_: port has no 'id' "
		    "property\n");
		return (MDEG_FAILURE);
	}

	/* read in the port's "vcc-doman-name" property */
	if (md_get_prop_str(mdp, mdep, "vcc-domain-name", &domain_name)) {
		cmn_err(CE_CONT, "i_vcc_md_add_port: port%ld has "
		    "no 'vcc-domain-name' property\n", portno);
		return (MDEG_FAILURE);
	}


	/* read in the port's "vcc-group-name" property */
	if (md_get_prop_str(mdp, mdep, "vcc-group-name", &group_name)) {
		cmn_err(CE_CONT, "i_vcc_md_add_port: port%ld has no "
		    "'vcc-group-name'property\n", portno);
		return (MDEG_FAILURE);
	}


	/* read in the port's "vcc-tcp-port" property */
	if (md_get_prop_val(mdp, mdep, "vcc-tcp-port", &tcp_port)) {
		cmn_err(CE_CONT, "i_vcc_md_add_port: port%ld has no"
		    "'vcc-tcp-port' property\n", portno);
		return (MDEG_FAILURE);
	}

	D1("i_vcc_md_add_port: port@%d domain-name=%s group-name=%s"
	    " tcp-port=%lld\n", portno, domain_name, group_name, tcp_port);

	/* add the port */
	if (i_vcc_add_port(vccp, group_name, tcp_port, portno, domain_name)) {
		return (MDEG_FAILURE);
	}

	vport = &vccp->port[portno];
	if (i_vcc_get_ldc_id(mdp, mdep, &ldc_id)) {
		mutex_enter(&vport->lock);
		(void) i_vcc_delete_port(vccp, vport);
		mutex_exit(&vport->lock);
		return (MDEG_FAILURE);
	}

	/* configure the port */
	if (i_vcc_config_port(vccp, portno, ldc_id)) {
		mutex_enter(&vport->lock);
		(void) i_vcc_delete_port(vccp, vport);
		mutex_exit(&vport->lock);
		return (MDEG_FAILURE);
	}

	mutex_enter(&vccp->lock);
	vccp->num_ports++;
	mutex_exit(&vccp->lock);

	vport = &vccp->port[VCC_CONTROL_PORT];

	if (vport->pollflag & VCC_POLL_CONFIG) {
		/* wakeup vntsd */
		mutex_enter(&vport->lock);
		vport->pollevent |= VCC_POLL_ADD_PORT;
		mutex_exit(&vport->lock);
		pollwakeup(&vport->poll, POLLIN);
	}

	return (MDEG_SUCCESS);
}

/* mdeg callback */
static int
vcc_mdeg_cb(void *cb_argp, mdeg_result_t *resp)
{
	int	idx;
	vcc_t 	*vccp;
	int	rv;

	vccp = (vcc_t *)cb_argp;
	ASSERT(vccp);

	if (resp == NULL) {
		return (MDEG_FAILURE);
	}

	/* added port */
	D1("vcc_mdeg_cb: added %d port(s)\n", resp->added.nelem);

	for (idx = 0; idx < resp->added.nelem; idx++) {
		rv = i_vcc_md_add_port(resp->added.mdp,
		    resp->added.mdep[idx], vccp);

		if (rv !=  MDEG_SUCCESS) {
			return (rv);
		}
	}

	/* removed port */
	D1("vcc_mdeg_cb: removed %d port(s)\n", resp->removed.nelem);

	for (idx = 0; idx < resp->removed.nelem; idx++) {
		rv = i_vcc_md_remove_port(resp->removed.mdp,
		    resp->removed.mdep[idx], vccp);

		if (rv !=  MDEG_SUCCESS) {
			return (rv);
		}

	}

	/*
	 * XXX - Currently no support for updating already active
	 * ports. So, ignore the match_curr and match_prev arrays
	 * for now.
	 */

	return (MDEG_SUCCESS);
}


/* cb_chpoll */
static int
vcc_chpoll(dev_t dev, short events, int anyyet,  short *reventsp,
    struct pollhead **phpp)
{
	int	    instance;
	minor_t	    minor;
	uint_t	    portno;
	vcc_t	    *vccp;
	vcc_port_t  *vport;

	minor = getminor(dev);

	instance = VCCINST(minor);

	vccp = ddi_get_soft_state(vcc_ssp, instance);
	if (vccp == NULL) {
		return (ENXIO);
	}

	portno = VCCPORT(vccp, minor);

	vport = &(vccp->port[portno]);

	D1("vcc_chpoll: virtual-console-concentrator@%d events 0x%x\n",
	    portno, events);

	*reventsp = 0;

	if (portno != VCC_CONTROL_PORT) {
		return (ENXIO);
	}

	/* poll for config change */
	if (vport->pollevent) {
		*reventsp |= (events & POLLIN);
	}

	if (((*reventsp) == 0) && (!anyyet)) {
		*phpp = &vport->poll;
		if (events & POLLIN) {
			mutex_enter(&vport->lock);
			vport->pollflag |= VCC_POLL_CONFIG;
			mutex_exit(&vport->lock);
		} else {
			return (ENXIO);
		}
	}

	D1("vcc_chpoll: virtual-console-concentrator@%d:%d ev=0x%x, "
	    "rev=0x%x pev=0x%x, flag=0x%x\n",
	    instance, portno, events, (*reventsp),
	    vport->pollevent, vport->pollflag);


	return (0);
}