#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>
#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
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);
static uint_t vcc_ldc_cb(uint64_t event, caddr_t arg);
static int vcc_mdeg_cb(void *cb_argp, mdeg_result_t *resp);
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,
vcc_close,
nodev,
nodev,
nodev,
vcc_read,
vcc_write,
vcc_ioctl,
nodev,
nodev,
ddi_segmap,
vcc_chpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP
};
static struct dev_ops vcc_ops = {
DEVO_REV,
0,
vcc_getinfo,
nulldev,
nulldev,
vcc_attach,
vcc_detach,
nodev,
&vcc_cb_ops,
(struct bus_ops *)NULL,
NULL,
ddi_quiesce_not_needed,
};
extern struct mod_ops mod_driverops;
#define VCC_CHANNEL_ENDPOINT "channel-endpoint"
#define VCC_ID_PROP "id"
static char vcc_ident[] = "sun4v Virtual Console Concentrator Driver";
static struct modldrv md = {
&mod_driverops,
vcc_ident,
&vcc_ops,
};
static struct modlinkage ml = {
MODREV_1,
&md,
NULL
};
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};
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
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
int
_init(void)
{
int error;
error = ddi_soft_state_init(&vcc_ssp, sizeof (vcc_t), 1);
if (error != 0) {
return (error);
}
error = mod_install(&ml);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ml, modinfop));
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&ml)) == 0) {
ddi_soft_state_fini(&vcc_ssp);
}
return (error);
}
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);
}
}
static int
i_vcc_can_use_port(vcc_minor_t *minorp, vcc_port_t *vport)
{
if (vport->minorp != minorp) {
return (ENXIO);
}
if (vport->valid_pid == VCC_NO_PID_BLOCKING) {
return (0);
}
if (vport->valid_pid != ddi_get_pid()) {
return (EIO);
}
return (0);
}
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) {
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);
return (EINTR);
}
}
}
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);
}
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);
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);
}
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);
}
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);
}
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);
}
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));
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;
buf.type = LDC_CONSOLE_CTRL;
buf.ctrl_msg = LDC_CONSOLE_HUP;
buf.size = 0;
(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);
}
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;
}
(void) ldc_set_cb_mode(vport->ldc_handle, LDC_CB_DISABLE);
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 {
while (ldc_unreg_callback(vport->ldc_handle) == EAGAIN)
drv_usecwait(VCC_LDC_DELAY);
}
}
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;
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);
}
if (size == 0) {
if (*sz > 0) {
return (0);
}
return (EAGAIN);
}
if (size < VCC_HDR_SZ) {
return (EIO);
}
if (buf.type != LDC_CONSOLE_DATA) {
return (EIO);
}
if (buf.size == 0) {
if (*sz > 0) {
return (0);
}
return (EAGAIN);
}
for (i = 0; i < buf.size; i++, (*sz)++) {
data_buf[*sz] = buf.data[i];
}
space_left -= buf.size;
}
return (0);
}
static uint_t
vcc_ldc_cb(uint64_t event, caddr_t arg)
{
vcc_port_t *vport = (vcc_port_t *)arg;
boolean_t hasdata;
D2("vcc_ldc_cb: callback invoked port=%d events=%llx\n",
vport->number, event);
if (event & LDC_EVT_WRITE) {
i_vcc_set_port_status(vport, &vport->write_cv,
VCC_PORT_LDC_WRITE_READY);
return (LDC_SUCCESS);
}
if (event & LDC_EVT_READ) {
(void) ldc_chkq(vport->ldc_handle, &hasdata);
if (!hasdata) {
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) {
i_vcc_set_port_status(vport, &vport->write_cv,
VCC_PORT_LDC_LINK_DOWN);
cv_broadcast(&vport->read_cv);
}
return (LDC_SUCCESS);
}
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);
vport->ldc_id = ldc_id;
if (vport->status & VCC_PORT_OPEN) {
if ((rv = i_vcc_ldc_init(vccp, vport)) != 0) {
mutex_exit(&vport->lock);
return (rv);
}
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);
}
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) {
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);
}
for (minor_idx = 0; minor_idx < vccp->minors_assigned; minor_idx++) {
if (strcmp(vccp->minor_tbl[minor_idx].domain_name,
domain_name) == 0) {
break;
}
}
if (minor_idx == vccp->minors_assigned) {
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);
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);
}
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) {
vport->valid_pid = VCC_NO_PID_BLOCKING;
rv = i_vcc_close_port(vport);
}
(void) snprintf(name, MAXPATHLEN-1, "%s%s", VCC_MINOR_NAME_PREFIX,
vport->minorp->domain_name);
ddi_remove_minor_node(vccp->dip, name);
cv_broadcast(&vport->read_cv);
cv_broadcast(&vport->write_cv);
vport->status = 0;
return (rv);
}
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;
sz = sizeof (vcc_prop_template);
pspecp = kmem_alloc(sz, KM_SLEEP);
bcopy(vcc_prop_template, pspecp, sz);
VCC_SET_MDEG_PROP_INST(pspecp, instance);
ispecp = kmem_zalloc(sizeof (mdeg_node_spec_t), KM_SLEEP);
ispecp->namep = "virtual-device";
ispecp->specp = pspecp;
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);
}
vccp->md_ispecp = (void *)ispecp;
vccp->mdeg_hdl = mdeg_hdl;
return (0);
}
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);
}
}
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);
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;
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);
ddi_soft_state_free(vccp, instance);
return (DDI_FAILURE);
}
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);
ddi_remove_minor_node(vccp->dip, NULL);
mutex_exit(&vccp->lock);
ddi_soft_state_free(vccp, instance);
return (DDI_FAILURE);
}
mutex_exit(&vccp->lock);
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);
ddi_remove_minor_node(vccp->dip, NULL);
ddi_soft_state_free(vccp, instance);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
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);
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));
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);
}
mutex_destroy(&vccp->lock);
ddi_soft_state_free(vcc_ssp, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
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) {
mutex_exit(&vport->lock);
return (ENXIO);
}
if (vport->status & VCC_PORT_OPEN) {
cmn_err(CE_CONT, "vcc_open: virtual-console-concentrator@%d:%d "
"is already open\n", instance, portno);
mutex_exit(&vport->lock);
return (EAGAIN);
}
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);
}
if (vport->ldc_id == VCC_INVALID_CHANNEL) {
mutex_exit(&vport->lock);
return (ENXIO);
}
if ((vport->status & VCC_PORT_LDC_CHANNEL_READY) == 0) {
rv = i_vcc_ldc_init(vccp, vport);
if (rv) {
mutex_exit(&vport->lock);
return (EIO);
}
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);
}
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) {
i_vcc_ldc_fini(vport);
vport->status &= ~VCC_PORT_LDC_CHANNEL_READY;
}
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;
cv_broadcast(&vport->read_cv);
cv_broadcast(&vport->write_cv);
return (0);
}
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]);
mutex_enter(&vport->lock);
if ((vport->status & VCC_PORT_OPEN) == 0) {
mutex_exit(&vport->lock);
return (0);
}
if (portno == VCC_CONTROL_PORT) {
mutex_exit(&vport->lock);
rv = i_vcc_reset_events(vccp);
return (0);
}
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);
}
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;
}
mutex_enter(&vport->lock);
if (num_ports == 0) {
vport->status |= VCC_PORT_ADDED;
notify_vntsd = B_TRUE;
mutex_exit(&vport->lock);
continue;
}
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);
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);
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) {
if (notify_vntsd) {
vport = &vccp->port[VCC_CONTROL_PORT];
mutex_enter(&vport->lock);
vport->pollevent |= VCC_POLL_ADD_PORT;
mutex_exit(&vport->lock);
}
return (0);
}
bzero(&cons, sizeof (vcc_console_t));
cons.cons_no = -1;
while (num_ports > 0) {
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);
}
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) {
return;
}
}
vport = &vccp->port[VCC_CONTROL_PORT];
mutex_enter(&vport->lock);
vport->pollevent &= ~event;
mutex_exit(&vport->lock);
}
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];
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;
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),
(void) snprintf(cons.dev_name, MAXPATHLEN-1, "%s:%s%s",
pathname, VCC_MINOR_NAME_PREFIX, cons.domain_name);
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);
}
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);
}
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) {
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);
}
}
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);
}
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) {
mutex_enter(&vport->lock);
vport->status &= ~VCC_PORT_OPEN;
vport->pollevent = 0;
vport->pollflag = 0;
mutex_exit(&vport->lock);
continue;
}
if (vport->status & VCC_PORT_ADDED) {
mutex_enter(&vport->lock);
vport->status &= ~VCC_PORT_ADDED;
mutex_exit(&vport->lock);
}
}
vport = &vccp->port[VCC_CONTROL_PORT];
return (0);
}
static int
i_vcc_force_close(vcc_t *vccp, caddr_t buf, int mode)
{
uint_t portno;
vcc_port_t *vport;
int rv;
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);
vport->valid_pid = ddi_get_pid();
mutex_exit(&vport->lock);
return (rv);
}
static int
i_vcc_cons_status(vcc_t *vccp, caddr_t buf, int mode)
{
vcc_console_t console;
vcc_port_t *vport;
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);
}
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);
return (ddi_copyout((void *)&num_ports, arg,
sizeof (int), mode));
case VCC_CONS_TBL:
return (i_vcc_cons_tbl(vccp, num_ports, (caddr_t)arg, mode));
case VCC_INQUIRY:
return (i_vcc_inquiry(vccp, (caddr_t)arg, mode));
case VCC_CONS_INFO:
return (i_vcc_cons_info(vccp, (caddr_t)arg, mode));
case VCC_FORCE_CLOSE:
return (i_vcc_force_close(vccp, (caddr_t)arg, mode));
case VCC_CONS_STATUS:
return (i_vcc_cons_status(vccp, (caddr_t)arg, mode));
default:
return (ENODEV);
}
}
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);
}
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;
}
}
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) {
case TCGETA:
case TCGETS:
mutex_enter(&vport->lock);
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);
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:
mutex_enter(&vport->lock);
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_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:
if (ddi_copyin(arg, &cmd, sizeof (int), mode) != 0) {
return (EFAULT);
}
mutex_enter(&vport->lock);
if ((rv = i_vcc_can_use_port(VCCMINORP(vccp, minor),
vport)) != 0) {
mutex_exit(&vport->lock);
return (rv);
}
switch (cmd) {
case 0:
vport->status &= ~VCC_PORT_TERM_RD;
break;
case 1:
vport->status |= VCC_PORT_TERM_RD;
cv_broadcast(&vport->read_cv);
break;
case 2:
vport->status &= ~VCC_PORT_TERM_WR;
break;
case 3:
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);
}
}
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) {
return (i_vcc_ctrl_ioctl(vccp, cmd, (void *)arg, mode));
}
return (i_vcc_port_ioctl(vccp, minor, portno, cmd, (void *)arg, mode));
}
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;
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);
if (portno == VCC_CONTROL_PORT) {
return (EIO);
}
uio_size = uiop->uio_resid;
if (uio_size < VCC_MTU_SZ) {
return (EINVAL);
}
vport = &(vccp->port[portno]);
mutex_enter(&vport->lock);
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) {
if (vport->status & VCC_PORT_NONBLOCK) {
break;
}
} else if (rv) {
break;
}
if (size > 0) {
break;
}
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)) {
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);
}
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);
if (portno == VCC_CONTROL_PORT) {
return (EIO);
}
vport = &(vccp->port[portno]);
mutex_enter(&vport->lock);
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);
rv = uiomove(&(buf.data), bytes, UIO_WRITE, uiop);
if (rv) {
break;
}
buf.size = bytes;
mutex_enter(&vport->lock);
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);
}
static int
i_vcc_md_remove_port(md_t *mdp, mde_cookie_t mdep, vcc_t *vccp)
{
uint64_t portno;
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]);
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);
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);
}
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);
}
static int
i_vcc_md_add_port(md_t *mdp, mde_cookie_t mdep, vcc_t *vccp)
{
uint64_t portno;
char *domain_name;
char *group_name;
uint64_t ldc_id;
uint64_t tcp_port;
vcc_port_t *vport;
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);
}
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);
}
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);
}
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);
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);
}
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) {
mutex_enter(&vport->lock);
vport->pollevent |= VCC_POLL_ADD_PORT;
mutex_exit(&vport->lock);
pollwakeup(&vport->poll, POLLIN);
}
return (MDEG_SUCCESS);
}
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);
}
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);
}
}
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);
}
}
return (MDEG_SUCCESS);
}
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);
}
if (vport->pollevent) {
*reventsp |= (events & POLLIN);
}
if ((((*reventsp) == 0) && (!anyyet)) || (events & POLLET)) {
*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);
}