vcc.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* 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
* 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/sysmacros.h>
#include <sys/vcc_impl.h>
#define VCC_LDC_RETRIES 5
/*
* Function prototypes.
*/
/* DDI entrypoints */
/* callback functions */
/* Internal functions */
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 */
};
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 */
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";
&mod_driverops, /* Type - it is a driver */
vcc_ident, /* Name of the module */
&vcc_ops, /* driver specfic opts */
};
static struct modlinkage ml = {
&md,
};
/*
* 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 }
};
/*
* 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[] = {
};
#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
{
char buf[512];
}
#define D1 \
if (vccdbg & 0x01) \
#define D2 \
if (vccdbg & 0x02) \
#define DWARN \
if (vccdbg & 0x04) \
#else
#define D1
#define D2
#define DWARN
#endif
/* _init(9E): initialize the loadable module */
int
_init(void)
{
int error;
/* init the soft state structure */
if (error != 0) {
return (error);
}
/* Link the driver into the system */
return (error);
}
/* _info(9E): return information about the loadable module */
int
{
/* Report status of the dynamically loadable driver module */
}
/* _fini(9E): prepare the module for unloading. */
int
_fini(void)
{
int error;
/* Unlink the driver module from the system */
/*
* We have successfully "removed" the driver.
* destroy soft state
*/
}
return (error);
}
/* getinfo(9E) */
static int
{
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
return (DDI_SUCCESS);
default:
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
{
/* port config changed */
return (ENXIO);
}
/* no blocking needed */
return (0);
}
return (EIO);
}
return (0);
}
/* Syncronization between thread using cv_wait */
static int
{
int rv;
for (; ; ) {
/* port has been deleted */
D1("i_vcc_wait_port_status: port%d deleted\n",
return (EIO);
}
D1("i_vcc_wait_port_status: port%d is closed \n",
return (EIO);
}
return (EIO);
}
return (EIO);
}
return (0);
}
if (!ddi_can_receive_sig()) {
return (EIO);
}
if (rv == 0) {
D1("i_vcc_wait_port_status: port%d get intr \n",
/* got signal */
return (EINTR);
}
}
}
/* Syncronization between threads, signal state change */
static void
{
}
/* initialize a ldc channel */
static int
{
/* initialize the channel */
return (rv);
}
/* register it */
return (rv);
}
/* open and bring channel up */
return (rv);
}
/* init the channel status */
return (rv);
}
return (0);
}
/* release a ldc channel */
static void
{
int retry = 0;
/* wait for write available */
if (rv == 0) {
/* send a HUP message */
/*
* ignore write error since we still want to clean up
* ldc channel.
*/
}
/* flush ldc channel */
if (rv == 0) {
do {
}
/*
* ignore read error since we still want to clean up
* ldc channel.
*/
/* close LDC channel - retry on EAGAIN */
if (++retry > VCC_LDC_RETRIES) {
break;
}
}
if (rv == 0) {
} 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.
*/
}
}
/* read data from ldc channel */
static int
{
int rv;
int i;
/* make sure holding read lock */
*sz = 0;
while (space_left >= VCC_MTU_SZ) {
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
*/
return (EIO);
}
if (*sz > 0) {
return (0);
}
return (EAGAIN);
}
/* copy data */
}
}
return (0);
}
/* callback from ldc */
static uint_t
{
/*
* 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",
/* check event from ldc */
if (event & LDC_EVT_WRITE) {
/* channel has space for write */
return (LDC_SUCCESS);
}
if (event & LDC_EVT_READ) {
/* channel has data for read */
if (!hasdata) {
/* data already read */
return (LDC_SUCCESS);
}
return (LDC_SUCCESS);
}
if (event & LDC_EVT_DOWN) {
/* channel is down */
}
return (LDC_SUCCESS);
}
/* configure a vcc port with ldc channel */
static int
{
portno);
return (EINVAL);
}
portno);
return (EINVAL);
}
"configured\n", portno);
return (EINVAL);
}
/* store the ldc ID */
/* check if someone has already opened this port */
return (rv);
}
/* mark port as ready */
}
D1("i_vcc_config_port: port@%d ldc=%d, domain=%s",
return (0);
}
/* add a vcc console port */
static int
{
int instance;
int rv = MDEG_FAILURE;
char name[MAXPATHLEN];
return (MDEG_FAILURE);
}
/* this port already exists */
"exists\n", portno);
return (MDEG_FAILURE);
}
if (domain_name == NULL) {
return (MDEG_FAILURE);
}
if (group_name == NULL) {
return (MDEG_FAILURE);
}
/* look up minor number */
domain_name) == 0) {
/* found previous assigned minor number */
break;
}
}
/* end of lookup - assign new minor number */
if (minor_idx == VCC_MAX_PORTS) {
"too many minornodes (%d)\n",
return (MDEG_FAILURE);
}
vccp->minors_assigned++;
}
D1("i_vcc_add_port:@%d domain=%s, group=%s, tcp=%lld",
/*
* Create a minor node. The minor number is
* (instance << VCC_INST_SHIFT) | minor_idx
*/
DDI_NT_SERIAL, 0);
if (rv != DDI_SUCCESS) {
vccp->minors_assigned--;
return (MDEG_FAILURE);
}
return (MDEG_SUCCESS);
}
/* delete a port */
static int
{
char name[MAXPATHLEN];
int rv;
D1("vcc_del_port port already deleted \n");
return (0);
}
/* do not block mdeg callback */
}
/* remove minor node */
/* let read and write thread know */
return (rv);
}
/* register callback to MDEG */
static int
{
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);
/* initialize the complete prop spec structure */
/* perform the registration */
if (rv != MDEG_SUCCESS) {
"mdeg_register failed (%d)\n", rv);
return (DDI_FAILURE);
}
/* save off data that will be needed later */
return (0);
}
/* destroy all mutex from port table */
static void
{
int i;
for (i = 0; i < VCC_MAX_PORTS; i++) {
}
}
/*
* attach(9E): attach a device to the system.
* called once for each instance of the device on the system.
*/
static int
{
int rv = DDI_FAILURE;
switch (cmd) {
case DDI_ATTACH:
return (DDI_FAILURE);
return (ENXIO);
}
/* initialize the mutex */
for (i = 0; i < VCC_MAX_PORTS; i++) {
}
/* create a minor node for vcc control */
DDI_NT_SERIAL, 0);
if (rv != DDI_SUCCESS) {
"creating control minor node\n");
/* clean up soft state */
return (DDI_FAILURE);
}
/* get the instance number by reading 'reg' property */
"reg", -1);
if (inst == -1) {
"'reg' property\n",
/* remove minor */
/* clean up soft state */
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
*/
/* register for notifications from Zeus */
if (rv != MDEG_SUCCESS) {
/* remove minor */
/* clean up soft state */
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
{
int i, instance;
switch (cmd) {
case DDI_DETACH:
return (ENXIO);
/* unregister from MD event generator */
/* remove minor nodes */
for (i = 0; i < VCC_MAX_PORTS; i++) {
if (i == VCC_CONTROL_PORT) {
(void) i_vcc_close_port(vport);
}
}
(i != VCC_CONTROL_PORT)) {
D1("vcc_detach: removing port port@%d\n", i);
}
}
/* destroy mutex and free the soft state */
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/* cb_open */
static int
{
int instance;
return (ENXIO);
}
/* port may be removed */
return (ENXIO);
}
/* only one open per port */
return (EAGAIN);
}
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
if (portno == VCC_CONTROL_PORT) {
return (0);
}
/*
* the port may just be added by mdeg callback and may
* not be configured yet.
*/
return (ENXIO);
}
/* check if channel has been initialized */
if (rv) {
return (EIO);
}
/* mark port as ready */
}
}
return (0);
}
/* close port */
static int
{
return (0);
}
/* clean up ldc channel */
}
/* signal any blocked read and write thread */
return (0);
}
/* cb_close */
static int
{
int instance;
return (ENXIO);
}
D1("vcc_close: closing virtual-console-concentrator@%d:%d\n",
/*
* needs lock to provent i_vcc_delete_port, which is called by
* the mdeg callback, from closing port.
*/
return (0);
}
if (portno == VCC_CONTROL_PORT) {
/*
* vntsd closes control port before it exits. There
* could be events still pending for vntsd.
*/
return (0);
}
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
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
{
int i;
char pathname[MAXPATHLEN];
for (i = 0; i < VCC_MAX_PORTS; i++) {
if (i == VCC_CONTROL_PORT) {
continue;
}
continue;
}
/* a port exists before vntsd becomes online */
if (num_ports == 0) {
/* more ports than vntsd's buffer can hold */
continue;
}
/* construct console buffer */
/* copy out data */
sizeof (vcc_console_t), mode)) {
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 */
}
return (0);
}
/* less ports than vntsd expected */
while (num_ports > 0) {
/* fill vntsd buffer with no console */
sizeof (vcc_console_t), mode) != 0) {
return (EFAULT);
}
D1("i_vcc_cons_tbl: a port is deleted\n");
num_ports--;
}
return (0);
}
/* turn off event flag if there is no more change */
static void
{
int i;
for (i = 0; i < VCC_MAX_PORTS; i++) {
continue;
}
/* more port changes status */
return;
}
}
/* no more changed port */
/* turn off event */
}
/* ioctl VCC_CONS_INFO */
static int
{
char pathname[MAXPATHLEN];
/* read in portno */
return (EFAULT);
}
return (EINVAL);
}
return (EINVAL);
}
/* construct configruation data */
/* copy device name */
/* copy data */
sizeof (vcc_console_t), mode) != 0) {
return (EFAULT);
}
D1("i_vcc_cons_info@%d:domain:%s serv:%s tcp@%lld %s\n",
return (0);
}
/* response to vntsd inquiry ioctl call */
static int
{
uint_t i;
return (EINVAL);
}
/* an added port */
D1("i_vcc_inquiry\n");
for (i = 0; i < VCC_MAX_PORTS; i++) {
continue;
}
/* port added */
"ddi_copyout"
" failed\n");
return (EFAULT);
}
return (0);
}
}
/* the added port was deleted before vntsd wakes up */
" failed\n");
return (EFAULT);
}
return (0);
}
/* clean up events after vntsd exits */
static int
{
uint_t i;
for (i = 0; i < VCC_MAX_PORTS; i++) {
continue;
}
if (i == VCC_CONTROL_PORT) {
/* close control port */
/* clean up poll events */
continue;
}
/* pending added port event to vntsd */
}
}
return (0);
}
/* ioctl VCC_FORCE_CLOSE */
static int
{
int rv;
/* read in portno */
return (EFAULT);
}
return (EINVAL);
}
return (EINVAL);
}
/* block callers other than vntsd */
return (rv);
}
/* ioctl VCC_CONS_STATUS */
static int
{
/* read in portno */
return (EFAULT);
}
return (EINVAL);
}
MAXPATHLEN)) {
MAXPATHLEN)) {
}
return (EFAULT);
}
return (0);
}
/* cb_ioctl handler for vcc control port */
static int
{
switch (cmd) {
case VCC_NUM_CONSOLE:
/* number of consoles */
sizeof (int), mode));
case VCC_CONS_TBL:
/* console config table */
case VCC_INQUIRY:
/* reason for wakeup */
case VCC_CONS_INFO:
/* a console config */
case VCC_FORCE_CLOSE:
/* force to close a console */
case VCC_CONS_STATUS:
/* console status */
default:
/* unknown command */
return (ENODEV);
}
}
/* write data to ldc. may block if channel has no space for write */
static int
{
for (; ; ) {
D1("i_vcc_write_ldc: port@%d: err=%d %d bytes\n",
if (rv == 0) {
return (rv);
}
if (rv != EWOULDBLOCK) {
return (EIO);
}
return (EAGAIN);
}
/* block util ldc has more space */
if (rv) {
return (rv);
}
}
}
/* cb_ioctl handler for port ioctl */
static int
int mode)
{
int rv;
return (EIO);
}
switch (cmd) {
/* terminal support */
case TCGETA:
case TCGETS:
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
case TCSETS:
case TCSETA:
case TCSETAW:
case TCSETAF:
return (EFAULT);
}
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
return (0);
case TCSBRK:
/* send break to console */
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
/* wait for write available */
if (rv) {
return (rv);
}
return (0);
case TCXONC:
/* suspend read or write */
return (EFAULT);
}
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
switch (cmd) {
case 0:
/* suspend read */
break;
case 1:
/* resume read */
break;
case 2:
/* suspend write */
break;
case 3:
/* resume write */
break;
default:
return (EINVAL);
}
return (0);
case TCFLSH:
return (0);
default:
return (EINVAL);
}
}
/* cb_ioctl */
static int
{
int instance;
int portno;
return (ENXIO);
}
if (portno >= VCC_MAX_PORTS) {
" invalid portno\n", portno);
return (EINVAL);
}
D1("vcc_ioctl: virtual-console-concentrator@%d:%d ioctl cmd=%d\n",
if (portno == VCC_CONTROL_PORT) {
/* control ioctl */
}
/* data port ioctl */
}
/* cb_read */
static int
{
int instance;
char *buf;
return (ENXIO);
}
/* no read for control port */
if (portno == VCC_CONTROL_PORT) {
return (EIO);
}
/* temp buf to hold ldc data */
if (uio_size < VCC_MTU_SZ) {
return (EINVAL);
}
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
if (rv) {
return (rv);
}
for (; ; ) {
/* should block? */
break;
}
} else if (rv) {
/* error */
break;
}
if (size > 0) {
/* got data */
break;
}
/* wait for data from ldc */
if (rv) {
break;
}
}
/* data is in buf */
}
return (rv);
}
/* cb_write */
static int
{
int instance;
return (ENXIO);
}
/* no write for control port */
if (portno == VCC_CONTROL_PORT) {
return (EIO);
}
/*
* check if the channel has been configured,
* if write has been suspend and grab write lock.
*/
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
if (rv) {
return (rv);
}
D2("vcc_write: virtual-console-concentrator@%d:%d writing %d bytes\n",
while (size) {
/* move data */
if (rv) {
break;
}
/* write to ldc */
/* check minor no and pid */
vport)) != 0) {
return (rv);
}
if (rv) {
break;
}
}
return (rv);
}
/* mdeg callback for a removed port */
static int
{
int rv = MDEG_FAILURE;
return (MDEG_FAILURE);
}
portno);
return (MDEG_FAILURE);
}
if (portno == VCC_CONTROL_PORT) {
"control port\n",
portno);
return (MDEG_FAILURE);
}
/* delete the port */
}
static int
{
int num_nodes;
int num_channels;
" Invalid node count in Machine Description subtree");
return (-1);
}
/* Look for channel endpoint child(ren) of the vdisk MD node */
" found for vcc");
return (-1);
}
/* Get the "id" value for the first channel endpoint node */
"for channel-endpoint of vcc");
return (-1);
}
if (num_channels > 1) {
" of multiple channels for this vcc");
}
return (0);
}
/* mdeg callback for an added port */
static int
{
char *domain_name;
char *group_name;
/* read in the port's reg property */
"property\n");
return (MDEG_FAILURE);
}
/* read in the port's "vcc-doman-name" property */
"no 'vcc-domain-name' property\n", portno);
return (MDEG_FAILURE);
}
/* read in the port's "vcc-group-name" property */
"'vcc-group-name'property\n", portno);
return (MDEG_FAILURE);
}
/* read in the port's "vcc-tcp-port" property */
"'vcc-tcp-port' property\n", portno);
return (MDEG_FAILURE);
}
D1("i_vcc_md_add_port: port@%d domain-name=%s group-name=%s"
/* add the port */
return (MDEG_FAILURE);
}
return (MDEG_FAILURE);
}
/* configure the port */
return (MDEG_FAILURE);
}
/* wakeup vntsd */
}
return (MDEG_SUCCESS);
}
/* mdeg callback */
static int
{
int idx;
int rv;
return (MDEG_FAILURE);
}
/* added port */
if (rv != MDEG_SUCCESS) {
return (rv);
}
}
/* removed port */
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
{
int instance;
return (ENXIO);
}
D1("vcc_chpoll: virtual-console-concentrator@%d events 0x%x\n",
*reventsp = 0;
if (portno != VCC_CONTROL_PORT) {
return (ENXIO);
}
/* poll for config change */
}
} else {
return (ENXIO);
}
}
D1("vcc_chpoll: virtual-console-concentrator@%d:%d ev=0x%x, "
"rev=0x%x pev=0x%x, flag=0x%x\n",
return (0);
}