diff options
Diffstat (limited to 'usr/src/uts/sun4v/io/vldc.c')
| -rw-r--r-- | usr/src/uts/sun4v/io/vldc.c | 1581 |
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, ©_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(©_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, ©_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(©_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); +} |
