/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include /* needed for S_IFBLK and S_IFCHR */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 */ }; 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 v%I%"; 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 int i_vcc_ldc_fini(vcc_port_t *vport) { int rv = EIO; vcc_msg_t buf; size_t sz; 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) { return (rv); } 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; /* in case of error, 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) { return (rv); } 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); if ((rv = ldc_close(vport->ldc_handle)) != 0) { cmn_err(CE_CONT, "i_vcc_ldc_fini: cannot close channel %ld\n", vport->ldc_id); return (rv); } if ((rv = ldc_unreg_callback(vport->ldc_handle)) != 0) { cmn_err(CE_CONT, "i_vcc_ldc_fini: port@%d ldc_unreg_callback" "failed\n", vport->number); return (rv); } if ((rv = ldc_fini(vport->ldc_handle)) != 0) { cmn_err(CE_CONT, "i_vcc_ldc_fini: cannot finilize channel" "%ld\n", vport->ldc_id); return (rv); } return (0); } /* 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); } /* 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) { int rv = EIO; 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 */ if ((rv = i_vcc_ldc_fini(vport)) != 0) { return (rv); } 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]); if ((vport->status & VCC_PORT_OPEN) == 0) { return (0); } if (portno == VCC_CONTROL_PORT) { /* * vntsd closes control port before it exits. There * could be events still pending for vntsd. */ rv = i_vcc_reset_events(vccp); return (0); } 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_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); }