summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4v/io/vldc.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4v/io/vldc.c')
-rw-r--r--usr/src/uts/sun4v/io/vldc.c1581
1 files changed, 1581 insertions, 0 deletions
diff --git a/usr/src/uts/sun4v/io/vldc.c b/usr/src/uts/sun4v/io/vldc.c
new file mode 100644
index 0000000000..6c366c5c59
--- /dev/null
+++ b/usr/src/uts/sun4v/io/vldc.c
@@ -0,0 +1,1581 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/uio.h>
+#include <sys/open.h>
+#include <sys/cred.h>
+#include <sys/kmem.h>
+#include <sys/conf.h>
+#include <sys/cmn_err.h>
+#include <sys/ksynch.h>
+#include <sys/modctl.h>
+#include <sys/stat.h> /* needed for S_IFBLK and S_IFCHR */
+#include <sys/debug.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/cred.h>
+#include <sys/promif.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/cyclic.h>
+#include <sys/note.h>
+#include <sys/mach_descrip.h>
+#include <sys/mdeg.h>
+#include <sys/ldc.h>
+#include <sys/vldc_impl.h>
+
+/*
+ * Function prototypes.
+ */
+
+/* DDI entrypoints */
+static int vldc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
+static int vldc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
+static int vldc_open(dev_t *devp, int flag, int otyp, cred_t *cred);
+static int vldc_close(dev_t dev, int flag, int otyp, cred_t *cred);
+static int vldc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
+ cred_t *credp, int *rvalp);
+static int vldc_read(dev_t dev, struct uio *uiop, cred_t *credp);
+static int vldc_write(dev_t dev, struct uio *uiop, cred_t *credp);
+static int vldc_chpoll(dev_t dev, short events, int anyyet,
+ short *reventsp, struct pollhead **phpp);
+
+/* Internal functions */
+static uint_t i_vldc_cb(uint64_t event, caddr_t arg);
+static int i_vldc_mdeg_cb(void *cb_argp, mdeg_result_t *resp);
+static int i_vldc_mdeg_register(vldc_t *vldcp);
+static int i_vldc_mdeg_unregister(vldc_t *vldcp);
+static int i_vldc_add_port(vldc_t *vldcp, md_t *mdp, mde_cookie_t node);
+static int i_vldc_remove_port(vldc_t *vldcp, uint_t portno);
+static int i_vldc_close_port(vldc_t *vldcp, uint_t portno);
+
+/* soft state structure */
+static void *vldc_ssp;
+
+/*
+ * 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 vport_prop_match[] = {
+ { MDET_PROP_VAL, "id" },
+ { MDET_LIST_END, NULL }
+};
+
+static mdeg_node_match_t vport_match = { "virtual-device-port",
+ vport_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 'name' and 'cfg-handle' values before
+ * being passed to the MDEG.
+ */
+static mdeg_prop_spec_t vldc_prop_template[] = {
+ { MDET_PROP_STR, "name", NULL },
+ { MDET_PROP_VAL, "cfg-handle", NULL },
+ { MDET_LIST_END, NULL, NULL }
+};
+
+#define VLDC_MDEG_PROP_NAME(specp) ((specp)[0].ps_str)
+#define VLDC_SET_MDEG_PROP_NAME(specp, name) ((specp)[0].ps_str = (name))
+#define VLDC_SET_MDEG_PROP_INST(specp, inst) ((specp)[1].ps_val = (inst))
+
+
+static struct cb_ops vldc_cb_ops = {
+ vldc_open, /* open */
+ vldc_close, /* close */
+ nodev, /* strategy */
+ nodev, /* print */
+ nodev, /* dump */
+ vldc_read, /* read */
+ vldc_write, /* write */
+ vldc_ioctl, /* ioctl */
+ nodev, /* devmap */
+ nodev, /* mmap */
+ ddi_segmap, /* segmap */
+ vldc_chpoll, /* chpoll */
+ ddi_prop_op, /* prop_op */
+ NULL, /* stream */
+ D_NEW | D_MP /* flag */
+};
+
+static struct dev_ops vldc_ops = {
+ DEVO_REV, /* rev */
+ 0, /* ref count */
+ ddi_getinfo_1to1, /* getinfo */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ vldc_attach, /* attach */
+ vldc_detach, /* detach */
+ nodev, /* reset */
+ &vldc_cb_ops, /* cb_ops */
+ (struct bus_ops *)NULL /* bus_ops */
+};
+
+extern struct mod_ops mod_driverops;
+
+static struct modldrv md = {
+ &mod_driverops, /* Type - it is a driver */
+ "sun4v Virtual LDC Driver %I%", /* Name of the module */
+ &vldc_ops, /* driver specific ops */
+};
+
+static struct modlinkage ml = {
+ MODREV_1,
+ &md,
+ NULL
+};
+
+/* maximum MTU and cookie size tunables */
+uint32_t vldc_max_mtu = VLDC_MAX_MTU;
+uint64_t vldc_max_cookie = VLDC_MAX_COOKIE;
+
+
+#ifdef DEBUG
+
+/*
+ * Print debug messages
+ *
+ * set vldcdbg to 0x7 to enable all messages
+ *
+ * 0x4 - Warnings
+ * 0x2 - All debug messages (most verbose)
+ * 0x1 - Minimal debug messages
+ */
+
+int vldcdbg = 0x0;
+
+static void
+vldcdebug(const char *fmt, ...)
+{
+ char buf[512];
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) vsnprintf(buf, sizeof (buf), fmt, ap);
+ va_end(ap);
+
+ cmn_err(CE_CONT, "?%s", buf);
+}
+
+#define D1 if (vldcdbg & 0x01) vldcdebug
+#define D2 if (vldcdbg & 0x02) vldcdebug
+#define DWARN if (vldcdbg & 0x04) vldcdebug
+
+#else /* not DEBUG */
+
+#define D1 if (0) printf
+#define D2 if (0) printf
+#define DWARN if (0) printf
+
+#endif /* not DEBUG */
+
+
+/* _init(9E): initialize the loadable module */
+int
+_init(void)
+{
+ int error;
+
+ /* init the soft state structure */
+ error = ddi_soft_state_init(&vldc_ssp, sizeof (vldc_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(&vldc_ssp);
+ }
+
+ return (error);
+}
+
+/* ldc callback */
+static uint_t
+i_vldc_cb(uint64_t event, caddr_t arg)
+{
+ vldc_port_t *vport = (vldc_port_t *)arg;
+ short pollevents = 0;
+ int rv;
+
+ D1("i_vldc_cb: callback invoked port=%d, event=0x%lx\n",
+ vport->number, event);
+
+ if (event & LDC_EVT_UP) {
+ pollevents |= POLLOUT;
+ vport->hanged_up = B_FALSE;
+
+ } else if (event & LDC_EVT_DOWN) {
+ pollevents |= POLLHUP;
+ vport->hanged_up = B_TRUE;
+
+ } else if (event & LDC_EVT_RESET) {
+ /* do an ldc_up because we can't be sure the other side will */
+ if ((rv = ldc_up(vport->ldc_handle)) != 0)
+ if (rv != ECONNREFUSED)
+ DWARN("i_vldc_cb: port@%d failed to"
+ " bring up LDC channel=%ld, err=%d\n",
+ vport->number, vport->ldc_id, rv);
+ }
+
+ if (event & LDC_EVT_READ)
+ pollevents |= POLLIN;
+
+ if (pollevents != 0) {
+ D1("i_vldc_cb: port@%d pollwakeup=0x%x\n",
+ vport->number, pollevents);
+ pollwakeup(&vport->poll, pollevents);
+ }
+
+ return (LDC_SUCCESS);
+}
+
+/* mdeg callback */
+static int
+i_vldc_mdeg_cb(void *cb_argp, mdeg_result_t *resp)
+{
+ vldc_t *vldcp;
+ int idx;
+ uint64_t portno;
+ int rv;
+ md_t *mdp;
+ mde_cookie_t node;
+
+ if (resp == NULL) {
+ D1("i_vldc_mdeg_cb: no result returned\n");
+ return (MDEG_FAILURE);
+ }
+
+ vldcp = (vldc_t *)cb_argp;
+
+ mutex_enter(&vldcp->lock);
+ if (vldcp->detaching == B_TRUE) {
+ D1("i_vldc_mdeg_cb: detach in progress\n");
+ mutex_exit(&vldcp->lock);
+ return (MDEG_FAILURE);
+ }
+
+ D1("i_vldc_mdeg_cb: added=%d, removed=%d, matched=%d\n",
+ resp->added.nelem, resp->removed.nelem, resp->match_prev.nelem);
+
+ /* process added ports */
+ for (idx = 0; idx < resp->added.nelem; idx++) {
+ mdp = resp->added.mdp;
+ node = resp->added.mdep[idx];
+
+ D1("i_vldc_mdeg_cb: processing added node 0x%lx\n", node);
+
+ /* attempt to add a port */
+ if ((rv = i_vldc_add_port(vldcp, mdp, node)) != MDEG_SUCCESS) {
+ cmn_err(CE_NOTE, "?i_vldc_mdeg_cb: unable to add port, "
+ "err = %d", rv);
+ }
+ }
+
+ /* process removed ports */
+ for (idx = 0; idx < resp->removed.nelem; idx++) {
+ mdp = resp->removed.mdp;
+ node = resp->removed.mdep[idx];
+
+ D1("i_vldc_mdeg_cb: processing removed node 0x%lx\n", node);
+
+ /* read in the port's id property */
+ if (md_get_prop_val(mdp, node, "id", &portno)) {
+ cmn_err(CE_NOTE, "?i_vldc_mdeg_cb: node 0x%lx of "
+ "removed list has no 'id' property", node);
+ continue;
+ }
+
+ /* attempt to remove a port */
+ if ((rv = i_vldc_remove_port(vldcp, portno)) != 0) {
+ cmn_err(CE_NOTE, "?i_vldc_mdeg_cb: unable to remove "
+ "port %lu, err %d", portno, rv);
+ }
+ }
+
+ /*
+ * Currently no support for updating already active ports. So, ignore
+ * the match_curr and match_prev arrays for now.
+ */
+
+ mutex_exit(&vldcp->lock);
+
+ return (MDEG_SUCCESS);
+}
+
+/* register callback to mdeg */
+static int
+i_vldc_mdeg_register(vldc_t *vldcp)
+{
+ mdeg_prop_spec_t *pspecp;
+ mdeg_node_spec_t *inst_specp;
+ mdeg_handle_t mdeg_hdl;
+ size_t templatesz;
+ int inst;
+ char *name;
+ size_t namesz;
+ char *nameprop;
+ int rv;
+
+ /* get the unique vldc instance assigned by the LDom manager */
+ inst = ddi_prop_get_int(DDI_DEV_T_ANY, vldcp->dip,
+ DDI_PROP_DONTPASS, "reg", -1);
+ if (inst == -1) {
+ cmn_err(CE_NOTE, "?vldc%d has no 'reg' property",
+ ddi_get_instance(vldcp->dip));
+ return (DDI_FAILURE);
+ }
+
+ /* get the name of the vldc instance */
+ rv = ddi_prop_lookup_string(DDI_DEV_T_ANY, vldcp->dip,
+ DDI_PROP_DONTPASS, "name", &nameprop);
+ if (rv != DDI_PROP_SUCCESS) {
+ cmn_err(CE_NOTE, "?vldc%d has no 'name' property",
+ ddi_get_instance(vldcp->dip));
+ return (DDI_FAILURE);
+ }
+
+ D1("i_vldc_mdeg_register: name=%s, instance=%d\n", nameprop, inst);
+
+ /*
+ * Allocate and initialize a per-instance copy
+ * of the global property spec array that will
+ * uniquely identify this vldc instance.
+ */
+ templatesz = sizeof (vldc_prop_template);
+ pspecp = kmem_alloc(templatesz, KM_SLEEP);
+
+ bcopy(vldc_prop_template, pspecp, templatesz);
+
+ /* copy in the name property */
+ namesz = strlen(nameprop) + 1;
+ name = kmem_alloc(namesz, KM_SLEEP);
+
+ bcopy(nameprop, name, namesz);
+ VLDC_SET_MDEG_PROP_NAME(pspecp, name);
+
+ /* copy in the instance property */
+ VLDC_SET_MDEG_PROP_INST(pspecp, inst);
+
+ /* initialize the complete prop spec structure */
+ inst_specp = kmem_alloc(sizeof (mdeg_node_spec_t), KM_SLEEP);
+ inst_specp->namep = "virtual-device";
+ inst_specp->specp = pspecp;
+
+ /* perform the registration */
+ rv = mdeg_register(inst_specp, &vport_match, i_vldc_mdeg_cb,
+ vldcp, &mdeg_hdl);
+
+ if (rv != MDEG_SUCCESS) {
+ cmn_err(CE_NOTE, "?i_vldc_mdeg_register: mdeg_register "
+ "failed, err = %d", rv);
+ kmem_free(name, namesz);
+ kmem_free(pspecp, templatesz);
+ kmem_free(inst_specp, sizeof (mdeg_node_spec_t));
+ return (DDI_FAILURE);
+ }
+
+ /* save off data that will be needed later */
+ vldcp->inst_spec = inst_specp;
+ vldcp->mdeg_hdl = mdeg_hdl;
+
+ return (DDI_SUCCESS);
+}
+
+/* unregister callback from mdeg */
+static int
+i_vldc_mdeg_unregister(vldc_t *vldcp)
+{
+ char *name;
+ int rv;
+
+ D1("i_vldc_mdeg_unregister: hdl=0x%lx\n", vldcp->mdeg_hdl);
+
+ rv = mdeg_unregister(vldcp->mdeg_hdl);
+ if (rv != MDEG_SUCCESS) {
+ return (rv);
+ }
+
+ /*
+ * Clean up cached MDEG data
+ */
+ name = VLDC_MDEG_PROP_NAME(vldcp->inst_spec->specp);
+ if (name != NULL) {
+ kmem_free(name, strlen(name) + 1);
+ }
+ kmem_free(vldcp->inst_spec->specp, sizeof (vldc_prop_template));
+ vldcp->inst_spec->specp = NULL;
+
+ kmem_free(vldcp->inst_spec, sizeof (mdeg_node_spec_t));
+ vldcp->inst_spec = NULL;
+
+ return (MDEG_SUCCESS);
+}
+
+static int
+i_vldc_get_port_channel(md_t *mdp, mde_cookie_t node, uint64_t *ldc_id)
+{
+ int num_nodes, nchan;
+ size_t listsz;
+ mde_cookie_t *listp;
+
+ /*
+ * Find the channel-endpoint node(s) (which should be under this
+ * port node) which contain the channel id(s).
+ */
+ if ((num_nodes = md_node_count(mdp)) <= 0) {
+ cmn_err(CE_NOTE, "?i_vldc_get_port_channel: invalid number of "
+ "channel-endpoint nodes found (%d)", num_nodes);
+ return (-1);
+ }
+
+ /* allocate space for node list */
+ listsz = num_nodes * sizeof (mde_cookie_t);
+ listp = kmem_alloc(listsz, KM_SLEEP);
+
+ nchan = md_scan_dag(mdp, node, md_find_name(mdp, "channel-endpoint"),
+ md_find_name(mdp, "fwd"), listp);
+
+ if (nchan <= 0) {
+ cmn_err(CE_NOTE, "?i_vldc_get_port_channel: no channel-endpoint"
+ " nodes found");
+ kmem_free(listp, listsz);
+ return (-1);
+ }
+
+ D2("i_vldc_get_port_channel: %d channel-endpoint nodes found", nchan);
+
+ /* use property from first node found */
+ if (md_get_prop_val(mdp, listp[0], "id", ldc_id)) {
+ cmn_err(CE_NOTE, "?i_vldc_get_port_channel: channel-endpoint "
+ "has no 'id' property");
+ kmem_free(listp, listsz);
+ return (-1);
+ }
+
+ kmem_free(listp, listsz);
+
+ return (0);
+}
+
+/* add a vldc port */
+static int
+i_vldc_add_port(vldc_t *vldcp, md_t *mdp, mde_cookie_t node)
+{
+ vldc_port_t *vport;
+ char *sname;
+ uint64_t portno;
+ int vldc_inst;
+ minor_t minor;
+ int minor_idx;
+ boolean_t new_minor;
+ int rv;
+
+ /* read in the port's id property */
+ if (md_get_prop_val(mdp, node, "id", &portno)) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: node 0x%lx of added "
+ "list has no 'id' property", node);
+ return (MDEG_FAILURE);
+ }
+
+ if (portno >= VLDC_MAX_PORTS) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: found port number (%lu) "
+ "larger than maximum supported number of ports", portno);
+ return (MDEG_FAILURE);
+ }
+
+ vport = &(vldcp->port[portno]);
+
+ if (vport->minorp != NULL) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: trying to add a port (%lu)"
+ " which is already bound", portno);
+ return (MDEG_FAILURE);
+ }
+
+ vport->number = portno;
+
+ /* get all channels for this device (currently only one) */
+ if (i_vldc_get_port_channel(mdp, node, &vport->ldc_id) == -1) {
+ return (MDEG_FAILURE);
+ }
+
+ /* set the default MTU */
+ vport->mtu = VLDC_DEFAULT_MTU;
+
+ /* get the service being exported by this port */
+ if (md_get_prop_str(mdp, node, "vldc-svc-name", &sname)) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: vdevice has no "
+ "'vldc-svc-name' property");
+ return (MDEG_FAILURE);
+ }
+
+ /* minor number look up */
+ for (minor_idx = 0; minor_idx < vldcp->minors_assigned;
+ minor_idx++) {
+ if (strcmp(vldcp->minor_tbl[minor_idx].sname, sname) == 0) {
+ /* found previously assigned minor number */
+ break;
+ }
+ }
+
+ new_minor = B_FALSE;
+ if (minor_idx == vldcp->minors_assigned) {
+ /* end of lookup - assign new minor number */
+ if (vldcp->minors_assigned == VLDC_MAX_MINORS) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: too many minor "
+ "nodes (%d)", minor_idx);
+ return (MDEG_FAILURE);
+ }
+
+ (void) strlcpy(vldcp->minor_tbl[minor_idx].sname,
+ sname, MAXPATHLEN);
+
+ vldcp->minors_assigned++;
+ new_minor = B_TRUE;
+ }
+
+ ASSERT(vldcp->minor_tbl[minor_idx].portno == VLDC_INVALID_PORTNO);
+
+ vport->minorp = &vldcp->minor_tbl[minor_idx];
+ vldcp->minor_tbl[minor_idx].portno = portno;
+ vldcp->minor_tbl[minor_idx].in_use = 0;
+
+ D1("i_vldc_add_port: port@%d mtu=%d, ldc=%ld, service=%s\n",
+ vport->number, vport->mtu, vport->ldc_id, sname);
+
+ /*
+ * Create a minor node. The minor number is
+ * (vldc_inst << VLDC_INST_SHIFT) | minor_idx
+ */
+ vldc_inst = ddi_get_instance(vldcp->dip);
+
+ minor = (vldc_inst << VLDC_INST_SHIFT) | (minor_idx);
+
+ rv = ddi_create_minor_node(vldcp->dip, sname, S_IFCHR,
+ minor, DDI_NT_SERIAL, 0);
+
+ if (rv != DDI_SUCCESS) {
+ cmn_err(CE_NOTE, "?i_vldc_add_port: failed to create minor"
+ "node (%u), err = %d", minor, rv);
+ vldcp->minor_tbl[minor_idx].portno = VLDC_INVALID_PORTNO;
+ if (new_minor) {
+ vldcp->minors_assigned--;
+ }
+ return (MDEG_FAILURE);
+ }
+
+ /*
+ * The port is now bound to a minor node and is initially in the
+ * closed state.
+ */
+ vport->status = VLDC_PORT_CLOSED;
+
+ D1("i_vldc_add_port: port %lu initialized\n", portno);
+
+ return (MDEG_SUCCESS);
+}
+
+/* remove a vldc port */
+static int
+i_vldc_remove_port(vldc_t *vldcp, uint_t portno)
+{
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+
+ vport = &(vldcp->port[portno]);
+ vminor = vport->minorp;
+ if (vminor == NULL) {
+ cmn_err(CE_NOTE, "?i_vldc_remove_port: trying to remove a "
+ "port (%u) which is not bound", portno);
+ return (MDEG_FAILURE);
+ }
+
+ /*
+ * Make sure that all new attempts to open or use the minor node
+ * associated with the port will fail.
+ */
+ mutex_enter(&vminor->lock);
+ vminor->portno = VLDC_INVALID_PORTNO;
+ mutex_exit(&vminor->lock);
+
+ /* send hangup to anyone polling */
+ pollwakeup(&vport->poll, POLLHUP);
+
+ /* Now wait for all current users of the minor node to finish. */
+ mutex_enter(&vminor->lock);
+ while (vminor->in_use > 0) {
+ cv_wait(&vminor->cv, &vminor->lock);
+ }
+
+ if ((vport->status == VLDC_PORT_READY) ||
+ (vport->status == VLDC_PORT_OPEN)) {
+ /* close the port before it is torn down */
+ (void) i_vldc_close_port(vldcp, portno);
+ }
+
+ /* remove minor node */
+ ddi_remove_minor_node(vldcp->dip, vport->minorp->sname);
+ vport->minorp = NULL;
+
+ mutex_exit(&vminor->lock);
+
+ D1("i_vldc_remove_port: removed vldc port %u\n", portno);
+
+ return (MDEG_SUCCESS);
+}
+
+/* close a ldc channel */
+static int
+i_vldc_ldc_close(vldc_port_t *vport)
+{
+ int rv = 0;
+ int err;
+
+ err = ldc_close(vport->ldc_handle);
+ if (err != 0)
+ rv = err;
+ err = ldc_unreg_callback(vport->ldc_handle);
+ if ((err != 0) && (rv != 0))
+ rv = err;
+ err = ldc_fini(vport->ldc_handle);
+ if ((err != 0) && (rv != 0))
+ rv = err;
+
+ return (rv);
+}
+
+/* close a vldc port */
+static int
+i_vldc_close_port(vldc_t *vldcp, uint_t portno)
+{
+ vldc_port_t *vport;
+ int rv;
+
+ vport = &(vldcp->port[portno]);
+
+ ASSERT(MUTEX_HELD(&vport->minorp->lock));
+
+ if (vport->status == VLDC_PORT_CLOSED) {
+ /* nothing to do */
+ DWARN("i_vldc_close_port: port %d in an unexpected "
+ "state (%d)\n", portno, vport->status);
+ return (DDI_SUCCESS);
+ }
+
+ rv = DDI_SUCCESS;
+ if (vport->status == VLDC_PORT_READY) {
+ rv = i_vldc_ldc_close(vport);
+ } else {
+ ASSERT(vport->status == VLDC_PORT_OPEN);
+ }
+
+ /* free memory */
+ kmem_free(vport->send_buf, vport->mtu);
+ kmem_free(vport->recv_buf, vport->mtu);
+
+ vport->status = VLDC_PORT_CLOSED;
+
+ return (rv);
+}
+
+/*
+ * attach(9E): attach a device to the system.
+ * called once for each instance of the device on the system.
+ */
+static int
+vldc_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ int i, instance;
+ vldc_t *vldcp;
+
+ switch (cmd) {
+
+ case DDI_ATTACH:
+
+ instance = ddi_get_instance(dip);
+
+ if (ddi_soft_state_zalloc(vldc_ssp, instance) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ ddi_soft_state_free(vldc_ssp, instance);
+ return (ENXIO);
+ }
+
+ D1("vldc_attach: DDI_ATTACH instance=%d\n", instance);
+
+ mutex_init(&vldcp->lock, NULL, MUTEX_DRIVER, NULL);
+ vldcp->dip = dip;
+ vldcp->detaching = B_FALSE;
+
+ for (i = 0; i < VLDC_MAX_PORTS; i++) {
+ /* No minor node association to start with */
+ vldcp->port[i].minorp = NULL;
+ }
+
+ for (i = 0; i < VLDC_MAX_MINORS; i++) {
+ mutex_init(&(vldcp->minor_tbl[i].lock), NULL,
+ MUTEX_DRIVER, NULL);
+ cv_init(&(vldcp->minor_tbl[i].cv), NULL,
+ CV_DRIVER, NULL);
+ /* No port association to start with */
+ vldcp->minor_tbl[i].portno = VLDC_INVALID_PORTNO;
+ }
+
+ /* Register for MD update notification */
+ if (i_vldc_mdeg_register(vldcp) != DDI_SUCCESS) {
+ ddi_soft_state_free(vldc_ssp, 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
+vldc_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ int i, instance;
+ vldc_t *vldcp;
+
+ switch (cmd) {
+
+ case DDI_DETACH:
+
+ instance = ddi_get_instance(dip);
+
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (DDI_FAILURE);
+ }
+
+ D1("vldc_detach: DDI_DETACH instance=%d\n", instance);
+
+ mutex_enter(&vldcp->lock);
+
+ /* Fail the detach if all ports have not been removed. */
+ for (i = 0; i < VLDC_MAX_MINORS; i++) {
+ if (vldcp->minor_tbl[i].portno != VLDC_INVALID_PORTNO) {
+ D1("vldc_detach: vldc@%d:%d is bound, "
+ "detach failed\n",
+ instance, vldcp->minor_tbl[i].portno);
+ mutex_exit(&vldcp->lock);
+ return (DDI_FAILURE);
+ }
+ }
+
+ /*
+ * Prevent MDEG from adding new ports before the callback can
+ * be unregistered. The lock can't be held accross the
+ * unregistration call because a callback may be in progress
+ * and blocked on the lock.
+ */
+ vldcp->detaching = B_TRUE;
+
+ mutex_exit(&vldcp->lock);
+
+ if (i_vldc_mdeg_unregister(vldcp) != MDEG_SUCCESS) {
+ vldcp->detaching = B_FALSE;
+ return (DDI_FAILURE);
+ }
+
+ /* Tear down all bound ports and free resources. */
+ for (i = 0; i < VLDC_MAX_MINORS; i++) {
+ if (vldcp->minor_tbl[i].portno != VLDC_INVALID_PORTNO) {
+ (void) i_vldc_remove_port(vldcp, i);
+ }
+ mutex_destroy(&(vldcp->minor_tbl[i].lock));
+ cv_destroy(&(vldcp->minor_tbl[i].cv));
+ }
+
+ mutex_destroy(&vldcp->lock);
+ ddi_soft_state_free(vldc_ssp, instance);
+
+ return (DDI_SUCCESS);
+
+ case DDI_SUSPEND:
+
+ return (DDI_SUCCESS);
+
+ default:
+
+ return (DDI_FAILURE);
+ }
+}
+
+/* cb_open */
+static int
+vldc_open(dev_t *devp, int flag, int otyp, cred_t *cred)
+{
+ _NOTE(ARGUNUSED(flag, otyp, cred))
+
+ int instance;
+ minor_t minor;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+
+ minor = getminor(*devp);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL)
+ return (ENXIO);
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENXIO);
+ }
+
+ vport = &(vldcp->port[portno]);
+
+ D1("vldc_open: opening vldc@%d:%lu\n", instance, portno);
+
+ if (vport->status != VLDC_PORT_CLOSED) {
+ mutex_exit(&vminor->lock);
+ return (EBUSY);
+ }
+
+ vport->recv_buf = kmem_alloc(vport->mtu, KM_SLEEP);
+ vport->send_buf = kmem_alloc(vport->mtu, KM_SLEEP);
+
+ vport->is_stream = B_FALSE; /* assume not a stream */
+ vport->hanged_up = B_FALSE;
+
+ vport->status = VLDC_PORT_OPEN;
+
+ mutex_exit(&vminor->lock);
+
+ return (DDI_SUCCESS);
+}
+
+/* cb_close */
+static int
+vldc_close(dev_t dev, int flag, int otyp, cred_t *cred)
+{
+ _NOTE(ARGUNUSED(flag, otyp, cred))
+
+ int instance;
+ minor_t minor;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_minor_t *vminor;
+ int rv;
+
+ minor = getminor(dev);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (ENXIO);
+ }
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENOLINK);
+ }
+
+ D1("vldc_close: closing vldc@%d:%lu\n", instance, portno);
+
+ rv = i_vldc_close_port(vldcp, portno);
+
+ mutex_exit(&vminor->lock);
+
+ return (rv);
+}
+
+static int
+vldc_set_ldc_mode(vldc_port_t *vport, vldc_t *vldcp, int channel_mode)
+{
+ ldc_attr_t attr;
+ int rv;
+
+ ASSERT(MUTEX_HELD(&vport->minorp->lock));
+
+ /* validate mode */
+ switch (channel_mode) {
+ case LDC_MODE_STREAM:
+ vport->is_stream = B_TRUE;
+ break;
+ case LDC_MODE_RAW:
+ case LDC_MODE_UNRELIABLE:
+ case LDC_MODE_RELIABLE:
+ vport->is_stream = B_FALSE;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if (vport->status == VLDC_PORT_READY) {
+ rv = i_vldc_ldc_close(vport);
+ vport->status = VLDC_PORT_OPEN;
+ if (rv != 0) {
+ DWARN("vldc_set_ldc_mode: i_vldc_ldc_close "
+ "failed, rv=%d\n", rv);
+ return (rv);
+ }
+ }
+
+ D1("vldc_set_ldc_mode: vport status %d, mode %d\n",
+ vport->status, channel_mode);
+
+ vport->ldc_mode = channel_mode;
+
+ /* initialize the channel */
+ attr.devclass = LDC_DEV_SERIAL;
+ attr.instance = ddi_get_instance(vldcp->dip);
+ attr.qlen = VLDC_QUEUE_LEN;
+ attr.mode = vport->ldc_mode;
+
+ if ((rv = ldc_init(vport->ldc_id, &attr,
+ &vport->ldc_handle)) != 0) {
+ DWARN("vldc_ioctl_opt_op: ldc_init failed, rv=%d\n", rv);
+ goto error_init;
+ }
+
+ /* register it */
+ if ((rv = ldc_reg_callback(vport->ldc_handle,
+ i_vldc_cb, (caddr_t)vport)) != 0) {
+ DWARN("vldc_ioctl_opt_op: ldc_reg_callback failed, rv=%d\n",
+ rv);
+ goto error_reg;
+ }
+
+ /* open the channel */
+ if ((rv = ldc_open(vport->ldc_handle)) != 0) {
+ DWARN("vldc_ioctl_opt_op: ldc_open failed, rv=%d\n", rv);
+ goto error_open;
+ }
+
+ vport->status = VLDC_PORT_READY;
+
+ /*
+ * Attempt to bring the channel up, but do not
+ * fail if the other end is not up yet.
+ */
+ rv = ldc_up(vport->ldc_handle);
+
+ if (rv == ECONNREFUSED) {
+ D1("vldc_ioctl_opt_op: remote endpoint not up yet\n");
+ } else if (rv != 0) {
+ DWARN("vldc_ioctl_opt_op: ldc_up failed, rv=%d\n", rv);
+ goto error_up;
+ }
+
+ D1("vldc_ioctl_opt_op: ldc %ld initialized successfully\n",
+ vport->ldc_id);
+
+ return (0);
+
+error_up:
+ vport->status = VLDC_PORT_OPEN;
+ (void) ldc_close(vport->ldc_handle);
+error_open:
+ (void) ldc_unreg_callback(vport->ldc_handle);
+error_reg:
+ (void) ldc_fini(vport->ldc_handle);
+error_init:
+ return (rv);
+}
+
+/* ioctl to read cookie */
+static int
+i_vldc_ioctl_read_cookie(vldc_port_t *vport, int vldc_instance, void *arg,
+ int mode)
+{
+ vldc_data_t copy_info;
+ caddr_t buf;
+ uint64_t len;
+ int rv;
+
+ if (ddi_copyin(arg, &copy_info, sizeof (copy_info), mode) == -1) {
+ return (EFAULT);
+ }
+
+ len = copy_info.length;
+ if (len > vldc_max_cookie) {
+ return (EINVAL);
+ }
+
+ /* allocate a temporary buffer */
+ buf = kmem_alloc(len, KM_SLEEP);
+
+ mutex_enter(&vport->minorp->lock);
+
+ D2("i_vldc_ioctl_read_cookie: vldc@%d:%d reading from 0x%lx "
+ "size 0x%lx to 0x%lx\n", vldc_instance, vport->number,
+ copy_info.dst_addr, copy_info.length, copy_info.src_addr);
+
+ /* read from the HV into the temporary buffer */
+ rv = ldc_mem_rdwr_pa(vport->ldc_handle, buf, &len,
+ (caddr_t)copy_info.dst_addr, LDC_COPY_IN);
+ if (rv != 0) {
+ DWARN("i_vldc_ioctl_read_cookie: vldc@%d:%d cannot read "
+ "address 0x%lx, rv=%d\n", vldc_instance, vport->number,
+ copy_info.dst_addr, rv);
+ mutex_exit(&vport->minorp->lock);
+ kmem_free(buf, copy_info.length);
+ return (EFAULT);
+ }
+
+ D2("i_vldc_ioctl_read_cookie: vldc@%d:%d read succeeded\n",
+ vldc_instance, vport->number);
+
+ mutex_exit(&vport->minorp->lock);
+
+ /* copy data from temporary buffer out to the caller and free buffer */
+ rv = ddi_copyout(buf, (caddr_t)copy_info.src_addr, len, mode);
+ kmem_free(buf, copy_info.length);
+ if (rv != 0) {
+ return (EFAULT);
+ }
+
+ /* set the structure to reflect outcome */
+ copy_info.length = len;
+ if (ddi_copyout(&copy_info, arg, sizeof (copy_info), mode) != 0) {
+ return (EFAULT);
+ }
+
+ return (0);
+}
+
+/* ioctl to write cookie */
+static int
+i_vldc_ioctl_write_cookie(vldc_port_t *vport, int vldc_instance, void *arg,
+ int mode)
+{
+ vldc_data_t copy_info;
+ caddr_t buf;
+ uint64_t len;
+ int rv;
+
+ if (ddi_copyin((caddr_t)arg, &copy_info,
+ sizeof (copy_info), mode) != 0) {
+ return (EFAULT);
+ }
+
+ len = copy_info.length;
+ if (len > vldc_max_cookie) {
+ return (EINVAL);
+ }
+
+ D2("i_vldc_ioctl_write_cookie: vldc@%d:%d writing 0x%lx size 0x%lx "
+ "to 0x%lx\n", vldc_instance, vport->number, copy_info.src_addr,
+ copy_info.length, copy_info.dst_addr);
+
+ /* allocate a temporary buffer */
+ buf = kmem_alloc(len, KM_SLEEP);
+
+ /* copy into the temporary buffer the data to be written to the HV */
+ if (ddi_copyin((caddr_t)copy_info.src_addr, buf,
+ copy_info.length, mode) != 0) {
+ kmem_free(buf, copy_info.length);
+ return (EFAULT);
+ }
+
+ mutex_enter(&vport->minorp->lock);
+
+ /* write the data from the temporary buffer to the HV */
+ rv = ldc_mem_rdwr_pa(vport->ldc_handle, buf, &len,
+ (caddr_t)copy_info.dst_addr, LDC_COPY_OUT);
+ if (rv != 0) {
+ DWARN("i_vldc_ioctl_write_cookie: vldc@%d:%d failed to write at"
+ " address 0x%lx\n, rv=%d", vldc_instance, vport->number,
+ copy_info.dst_addr, rv);
+ mutex_exit(&vport->minorp->lock);
+ kmem_free(buf, copy_info.length);
+ return (EFAULT);
+ }
+
+ D2("i_vldc_ioctl_write_cookie: vldc@%d:%d write succeeded\n",
+ vldc_instance, vport->number);
+
+ mutex_exit(&vport->minorp->lock);
+
+ kmem_free(buf, copy_info.length);
+
+ /* set the structure to reflect outcome */
+ copy_info.length = len;
+ if (ddi_copyout(&copy_info, (caddr_t)arg,
+ sizeof (copy_info), mode) != 0) {
+ return (EFAULT);
+ }
+
+ return (0);
+}
+
+/* vldc specific ioctl option commands */
+static int
+i_vldc_ioctl_opt_op(vldc_port_t *vport, vldc_t *vldcp, void *arg, int mode)
+{
+ vldc_opt_op_t vldc_cmd;
+ uint32_t new_mtu;
+ int rv = 0;
+
+ if (ddi_copyin(arg, &vldc_cmd, sizeof (vldc_cmd), mode) != 0) {
+ return (EFAULT);
+ }
+
+ D1("vldc_ioctl_opt_op: op %d\n", vldc_cmd.opt_sel);
+
+ switch (vldc_cmd.opt_sel) {
+
+ case VLDC_OPT_MTU_SZ:
+
+ if (vldc_cmd.op_sel == VLDC_OP_GET) {
+ vldc_cmd.opt_val = vport->mtu;
+ if (ddi_copyout(&vldc_cmd, arg,
+ sizeof (vldc_cmd), mode) == -1) {
+ return (EFAULT);
+ }
+ } else {
+ new_mtu = vldc_cmd.opt_val;
+
+ if ((new_mtu < LDC_PACKET_SIZE) ||
+ (new_mtu > vldc_max_mtu)) {
+ return (EINVAL);
+ }
+
+ mutex_enter(&vport->minorp->lock);
+
+ if ((vport->status != VLDC_PORT_CLOSED) &&
+ (new_mtu != vport->mtu)) {
+ /*
+ * The port has buffers allocated since it is
+ * not closed plus the MTU size has changed.
+ * Reallocate the buffers to the new MTU size.
+ */
+ kmem_free(vport->recv_buf, vport->mtu);
+ vport->recv_buf = kmem_alloc(new_mtu, KM_SLEEP);
+
+ kmem_free(vport->send_buf, vport->mtu);
+ vport->send_buf = kmem_alloc(new_mtu, KM_SLEEP);
+
+ vport->mtu = new_mtu;
+ }
+
+ mutex_exit(&vport->minorp->lock);
+ }
+
+ break;
+
+ case VLDC_OPT_STATUS:
+
+ if (vldc_cmd.op_sel == VLDC_OP_GET) {
+ vldc_cmd.opt_val = vport->status;
+ if (ddi_copyout(&vldc_cmd, arg,
+ sizeof (vldc_cmd), mode) == -1) {
+ return (EFAULT);
+ }
+ } else {
+ return (ENOTSUP);
+ }
+
+ break;
+
+ case VLDC_OPT_MODE:
+
+ if (vldc_cmd.op_sel == VLDC_OP_GET) {
+ vldc_cmd.opt_val = vport->ldc_mode;
+ if (ddi_copyout(&vldc_cmd, arg,
+ sizeof (vldc_cmd), mode) == -1) {
+ return (EFAULT);
+ }
+ } else {
+ mutex_enter(&vport->minorp->lock);
+ rv = vldc_set_ldc_mode(vport, vldcp, vldc_cmd.opt_val);
+ mutex_exit(&vport->minorp->lock);
+ }
+
+ break;
+
+ default:
+
+ D1("vldc_ioctl_opt_op: unsupported op %d\n", vldc_cmd.opt_sel);
+ return (ENOTSUP);
+ }
+
+ return (rv);
+}
+
+/* cb_ioctl */
+static int
+vldc_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+ int *rvalp)
+{
+ _NOTE(ARGUNUSED(credp, rvalp))
+
+ int rv = EINVAL;
+ int instance;
+ minor_t minor;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+
+ minor = getminor(dev);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (ENXIO);
+ }
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENOLINK);
+ }
+ vminor->in_use += 1;
+ mutex_exit(&vminor->lock);
+
+ vport = &(vldcp->port[portno]);
+
+ D1("vldc_ioctl: vldc@%d:%lu cmd=0x%x\n", instance, portno, cmd);
+
+ switch (cmd) {
+
+ case VLDC_IOCTL_OPT_OP:
+
+ rv = i_vldc_ioctl_opt_op(vport, vldcp, (void *)arg, mode);
+ break;
+
+ case VLDC_IOCTL_READ_COOKIE:
+
+ rv = i_vldc_ioctl_read_cookie(vport, instance,
+ (void *)arg, mode);
+ break;
+
+ case VLDC_IOCTL_WRITE_COOKIE:
+
+ rv = i_vldc_ioctl_write_cookie(vport, instance,
+ (void *)arg, mode);
+ break;
+
+ default:
+
+ DWARN("vldc_ioctl: vldc@%d:%lu unknown cmd=0x%x\n",
+ instance, portno, cmd);
+ rv = EINVAL;
+ break;
+ }
+
+ mutex_enter(&vminor->lock);
+ vminor->in_use -= 1;
+ if (vminor->in_use == 0) {
+ cv_signal(&vminor->cv);
+ }
+ mutex_exit(&vminor->lock);
+
+ D1("vldc_ioctl: rv=%d\n", rv);
+
+ return (rv);
+}
+
+/* cb_read */
+static int
+vldc_read(dev_t dev, struct uio *uiop, cred_t *credp)
+{
+ _NOTE(ARGUNUSED(credp))
+
+ int instance;
+ minor_t minor;
+ size_t size = 0;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+ int rv = 0;
+
+ minor = getminor(dev);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (ENXIO);
+ }
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENOLINK);
+ }
+
+ D2("vldc_read: vldc@%d:%lu reading data\n", instance, portno);
+
+ vport = &(vldcp->port[portno]);
+
+ /* check the port status */
+ if (vport->status != VLDC_PORT_READY) {
+ DWARN("vldc_read: vldc@%d:%lu not in the ready state\n",
+ instance, portno);
+ mutex_exit(&vminor->lock);
+ return (ENOTACTIVE);
+ }
+
+ /* read data */
+ size = MIN(vport->mtu, uiop->uio_resid);
+ rv = ldc_read(vport->ldc_handle, vport->recv_buf, &size);
+
+ D2("vldc_read: vldc@%d:%lu ldc_read size=%ld, rv=%d\n",
+ instance, portno, size, rv);
+
+ if (rv == 0) {
+ if (size != 0) {
+ rv = uiomove(vport->recv_buf, size, UIO_READ, uiop);
+ } else {
+ rv = EWOULDBLOCK;
+ }
+ } else {
+ switch (rv) {
+ case ENOBUFS:
+ break;
+ case ETIMEDOUT:
+ case EWOULDBLOCK:
+ rv = EWOULDBLOCK;
+ break;
+ default:
+ rv = ECONNRESET;
+ break;
+ }
+ }
+
+ mutex_exit(&vminor->lock);
+
+ return (rv);
+}
+
+/* cb_write */
+static int
+vldc_write(dev_t dev, struct uio *uiop, cred_t *credp)
+{
+ _NOTE(ARGUNUSED(credp))
+
+ int instance;
+ minor_t minor;
+ size_t size;
+ size_t orig_size;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+ int rv = EINVAL;
+
+ minor = getminor(dev);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (ENXIO);
+ }
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENOLINK);
+ }
+
+ vport = &(vldcp->port[portno]);
+
+ /* check the port status */
+ if (vport->status != VLDC_PORT_READY) {
+ DWARN("vldc_write: vldc@%d:%lu not in the ready state\n",
+ instance, portno);
+ mutex_exit(&vminor->lock);
+ return (ENOTACTIVE);
+ }
+
+ orig_size = uiop->uio_resid;
+ size = orig_size;
+
+ if (size > vport->mtu) {
+ if (vport->is_stream) {
+ /* can only send MTU size at a time */
+ size = vport->mtu;
+ } else {
+ mutex_exit(&vminor->lock);
+ return (EMSGSIZE);
+ }
+ }
+
+ D2("vldc_write: vldc@%d:%lu writing %lu bytes\n", instance, portno,
+ size);
+
+ rv = uiomove(vport->send_buf, size, UIO_WRITE, uiop);
+ if (rv == 0) {
+ rv = ldc_write(vport->ldc_handle, (caddr_t)vport->send_buf,
+ &size);
+ if (rv != 0) {
+ DWARN("vldc_write: vldc@%d:%lu failed writing %lu "
+ "bytes rv=%d\n", instance, portno, size, rv);
+ }
+ } else {
+ size = 0;
+ }
+
+ mutex_exit(&vminor->lock);
+
+ /* resid is total number of bytes *not* sent */
+ uiop->uio_resid = orig_size - size;
+
+ return (rv);
+}
+
+/* cb_chpoll */
+static int
+vldc_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
+ struct pollhead **phpp)
+{
+ int instance;
+ minor_t minor;
+ uint64_t portno;
+ vldc_t *vldcp;
+ vldc_port_t *vport;
+ vldc_minor_t *vminor;
+ ldc_status_t ldc_state;
+ boolean_t isempty;
+ int rv;
+
+ minor = getminor(dev);
+ instance = VLDCINST(minor);
+ vldcp = ddi_get_soft_state(vldc_ssp, instance);
+ if (vldcp == NULL) {
+ return (ENXIO);
+ }
+
+ vminor = VLDCMINOR(vldcp, minor);
+ mutex_enter(&vminor->lock);
+ portno = vminor->portno;
+ if (portno == VLDC_INVALID_PORTNO) {
+ mutex_exit(&vminor->lock);
+ return (ENOLINK);
+ }
+
+ vport = &(vldcp->port[portno]);
+
+ /* check the port status */
+ if (vport->status != VLDC_PORT_READY) {
+ mutex_exit(&vminor->lock);
+ return (ENOTACTIVE);
+ }
+
+ D2("vldc_chpoll: vldc@%d:%lu polling events 0x%x\n",
+ instance, portno, events);
+
+ rv = ldc_status(vport->ldc_handle, &ldc_state);
+ if (rv != 0) {
+ DWARN("vldc_chpoll: vldc@%d:%lu could not get ldc status, "
+ "rv=%d\n", instance, portno, rv);
+ mutex_exit(&vminor->lock);
+ return (EBADFD);
+ }
+
+ *reventsp = 0;
+
+ if (ldc_state == LDC_UP) {
+ /*
+ * Check if the receive queue is empty and if not, signal that
+ * there is data ready to read.
+ */
+ if (events & POLLIN) {
+ if ((ldc_chkq(vport->ldc_handle, &isempty) == 0) &&
+ (isempty == B_FALSE)) {
+ *reventsp |= POLLIN;
+ }
+ }
+
+ if (events & POLLOUT)
+ *reventsp |= POLLOUT;
+
+ } else if (vport->hanged_up) {
+ *reventsp |= POLLHUP;
+ vport->hanged_up = B_FALSE;
+ }
+
+ mutex_exit(&vminor->lock);
+
+ if (((*reventsp) == 0) && (!anyyet)) {
+ *phpp = &vport->poll;
+ }
+
+ D2("vldc_chpoll: vldc@%d:%lu ev=0x%x, rev=0x%x\n",
+ instance, portno, events, *reventsp);
+
+ return (0);
+}