/*
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/sysmacros.h>
#include <sys/mach_descrip.h>
#include <sys/vldc_impl.h>
/*
* Function prototypes.
*/
/* DDI entrypoints */
/* Internal functions */
/* 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.
*/
{ 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 'name' and 'cfg-handle' values before
* being passed to the MDEG.
*/
};
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 */
};
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 */
NULL, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type - it is a driver */
"sun4v Virtual LDC Driver", /* Name of the module */
&vldc_ops, /* driver specific ops */
};
&md,
};
/* maximum MTU and cookie size tunables */
/*
* when ldc_close() returns EAGAIN, it is retried with a wait
* of 'vldc_close_delay' between each retry.
*/
#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
*/
static void
{
}
#else /* not DEBUG */
#endif /* not DEBUG */
/* _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);
}
/* ldc callback */
static uint_t
{
int rv;
short pollevents = 0;
D1("i_vldc_cb: vldc@%d:%d callback invoked, channel=0x%lx, "
/* ensure the port can't be destroyed while we are handling the cb */
return (LDC_SUCCESS);
}
if (rv != 0) {
DWARN("i_vldc_cb: vldc@%d:%d could not get ldc status, "
return (LDC_SUCCESS);
}
if (event & LDC_EVT_UP) {
pollevents |= POLLOUT;
} else if (event & LDC_EVT_RESET) {
/*
* Mark the port in reset, if it is not CLOSED and
* the channel was previously in LDC_UP state. This
* implies that the port cannot be used until it has
* been closed and reopened.
*/
if (old_status == LDC_UP) {
} else {
if (rv) {
DWARN("i_vldc_cb: vldc@%d:%d cannot bring "
return (LDC_SUCCESS);
}
if (rv != 0) {
DWARN("i_vldc_cb: vldc@%d:%d could not get "
return (LDC_SUCCESS);
}
pollevents |= POLLOUT;
}
}
} else if (event & LDC_EVT_DOWN) {
/*
* The other side went away - mark port in RESET state
*/
}
if (event & LDC_EVT_READ)
pollevents |= POLLIN;
if (pollevents != 0) {
D1("i_vldc_cb: port@%d pollwakeup=0x%x\n",
}
return (LDC_SUCCESS);
}
/* mdeg callback */
static int
{
int idx;
int rv;
D1("i_vldc_mdeg_cb: no result returned\n");
return (MDEG_FAILURE);
}
D1("i_vldc_mdeg_cb: detach in progress\n");
return (MDEG_FAILURE);
}
D1("i_vldc_mdeg_cb: added=%d, removed=%d, matched=%d\n",
/* process added ports */
/* attempt to add a port */
"err = %d", rv);
}
}
/* process removed ports */
/* read in the port's id property */
"removed list has no 'id' property", node);
continue;
}
/* attempt to remove a port */
}
}
/*
* Currently no support for updating already active ports. So, ignore
* the match_curr and match_prev arrays for now.
*/
return (MDEG_SUCCESS);
}
/* register callback to mdeg */
static int
{
int inst;
char *name;
char *nameprop;
int rv;
/* get the unique vldc instance assigned by the LDom manager */
if (inst == -1) {
return (DDI_FAILURE);
}
/* get the name of the vldc instance */
if (rv != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
/*
* 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);
/* copy in the name property */
/* copy in the instance property */
/* initialize the complete prop spec structure */
/* perform the registration */
if (rv != MDEG_SUCCESS) {
"failed, err = %d", rv);
return (DDI_FAILURE);
}
/* save off data that will be needed later */
return (DDI_SUCCESS);
}
/* unregister callback from mdeg */
static int
{
char *name;
int rv;
if (rv != MDEG_SUCCESS) {
return (rv);
}
/*
* Clean up cached MDEG data
*/
}
return (MDEG_SUCCESS);
}
static int
{
/*
* Find the channel-endpoint node(s) (which should be under this
* port node) which contain the channel id(s).
*/
"channel-endpoint nodes found (%d)", num_nodes);
return (-1);
}
/* allocate space for node list */
if (nchan <= 0) {
" nodes found");
return (-1);
}
/* use property from first node found */
"has no 'id' property");
return (-1);
}
return (0);
}
/* add a vldc port */
static int
{
char *sname;
int vldc_inst;
int minor_idx;
int rv;
/* read in the port's id property */
"list has no 'id' property", node);
return (MDEG_FAILURE);
}
if (portno >= VLDC_MAX_PORTS) {
"larger than maximum supported number of ports", portno);
return (MDEG_FAILURE);
}
" which is already bound", portno);
return (MDEG_FAILURE);
}
/* get all channels for this device (currently only one) */
return (MDEG_FAILURE);
}
/* set the default MTU */
/* get the service being exported by this port */
"'vldc-svc-name' property");
return (MDEG_FAILURE);
}
/* minor number look up */
minor_idx++) {
/* found previously assigned minor number */
break;
}
}
/* end of lookup - assign new minor number */
"nodes (%d)", minor_idx);
return (MDEG_FAILURE);
}
sname, MAXPATHLEN);
vldcp->minors_assigned++;
}
" which has a minor number in use by port (%u)",
return (MDEG_FAILURE);
}
D1("i_vldc_add_port: vldc@%d:%d mtu=%d, ldc=%ld, service=%s\n",
/*
* Create a minor node. The minor number is
* (vldc_inst << VLDC_INST_SHIFT) | minor_idx
*/
minor, DDI_NT_SERIAL, 0);
if (rv != DDI_SUCCESS) {
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.
*/
return (MDEG_SUCCESS);
}
/* remove a vldc port */
static int
{
"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.
*/
/* send hangup to anyone polling */
/* Now wait for all current users of the minor node to finish. */
}
/* close the port before it is torn down */
}
/* remove minor node */
return (MDEG_SUCCESS);
}
/*
* Close and destroy the ldc channel associated with the port 'vport'
*
* NOTE It may not be possible close and destroy the channel if resources
* are still in use so the fucntion may exit before all the teardown
* operations are completed and would have to be called again by the
* vldc framework.
*
* This function needs to be able to handle the case where it is called
* more than once and has to pick up from where it left off.
*/
static int
{
int err = 0;
/*
* If ldc_close() succeeded or if the channel was already closed[*]
* (possibly by a previously unsuccessful call to this function)
* we keep going and try to teardown the rest of the LDC state,
* otherwise we bail out.
*
* [*] indicated by ldc_close() returning a value of EFAULT
*/
return (err);
if (err != 0)
return (err);
if (err != 0)
return (err);
return (0);
}
/* close a vldc port */
static int
{
D1("i_vldc_close_port: vldc@%d:%d: closing port\n",
case VLDC_PORT_CLOSED:
/* nothing to do */
DWARN("i_vldc_close_port: port %d in an unexpected "
return (DDI_SUCCESS);
case VLDC_PORT_READY:
case VLDC_PORT_RESET:
do {
break;
/*
* EAGAIN indicates that ldc_close() failed because
* ldc callback thread is active for the channel.
* cv_timedwait() is used to release vminor->lock and
* allow ldc callback thread to complete.
* after waking up, check if the port has been closed
* by another thread in the meantime.
*/
rv = 0;
return (rv);
break;
case VLDC_PORT_OPEN:
break;
default:
DWARN("i_vldc_close_port: port %d in an unexpected "
ASSERT(0); /* fail quickly to help diagnosis */
return (EINVAL);
}
/* free memory */
return (rv);
}
/*
* attach(9E): attach a device to the system.
* called once for each instance of the device on the system.
*/
static int
{
int i, instance;
switch (cmd) {
case DDI_ATTACH:
return (DDI_FAILURE);
}
return (ENXIO);
}
for (i = 0; i < VLDC_MAX_PORTS; i++) {
/* No minor node association to start with */
}
for (i = 0; i < VLDC_MAX_MINORS; i++) {
MUTEX_DRIVER, NULL);
/* No port association to start with */
}
/* Register for MD update notification */
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 (DDI_FAILURE);
}
/* Fail the detach if all ports have not been removed. */
for (i = 0; i < VLDC_MAX_MINORS; i++) {
D1("vldc_detach: vldc@%d:%d is bound, "
"detach failed\n",
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.
*/
return (DDI_FAILURE);
}
/* Tear down all bound ports and free resources. */
for (i = 0; i < VLDC_MAX_MINORS; i++) {
(void) i_vldc_remove_port(vldcp, i);
}
}
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/* cb_open */
static int
{
int instance;
return (ENXIO);
if (portno == VLDC_INVALID_PORTNO) {
return (ENXIO);
}
return (EBUSY);
}
return (DDI_SUCCESS);
}
/* cb_close */
static int
{
int instance;
int rv;
return (ENXIO);
}
if (portno == VLDC_INVALID_PORTNO) {
return (ENOLINK);
}
return (rv);
}
static int
{
int rv;
/* validate mode */
switch (channel_mode) {
case LDC_MODE_RELIABLE:
break;
case LDC_MODE_RAW:
case LDC_MODE_UNRELIABLE:
break;
default:
return (EINVAL);
}
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",
/* initialize the channel */
&vport->ldc_handle)) != 0) {
goto error_init;
}
/* register it */
DWARN("vldc_ioctl_opt_op: ldc_reg_callback failed, rv=%d\n",
rv);
goto error_reg;
}
/* open the channel */
goto error_open;
}
/*
* Attempt to bring the channel up, but do not
* fail if the other end is not up yet.
*/
if (rv == ECONNREFUSED) {
D1("vldc_ioctl_opt_op: remote endpoint not up yet\n");
} else if (rv != 0) {
goto error_up;
}
if (rv != 0) {
DWARN("vldc_ioctl_opt_op: vldc@%d:%d could not get ldc "
goto error_up;
}
D1("vldc_ioctl_opt_op: ldc %ld initialized successfully\n",
return (0);
return (rv);
}
/* ioctl to read cookie */
static int
int mode)
{
int rv;
return (EFAULT);
}
while (balance > 0) {
/* get the max amount to the copied */
D2("i_vldc_ioctl_read_cookie: vldc@%d:%d reading from 0x%p "
/* read from the HV into the temporary buffer */
if (rv != 0) {
DWARN("i_vldc_ioctl_read_cookie: vldc@%d:%d cannot "
"read address 0x%p, rv=%d\n",
return (EFAULT);
}
D2("i_vldc_ioctl_read_cookie: vldc@%d:%d read succeeded\n",
/*
* copy data from temporary buffer out to the
* caller and free buffer
*/
if (rv != 0) {
return (EFAULT);
}
/* adjust len, source and dest */
}
/* set the structure to reflect outcome */
return (EFAULT);
}
return (0);
}
/* ioctl to write cookie */
static int
int mode)
{
int rv;
return (EFAULT);
}
D2("i_vldc_ioctl_write_cookie: vldc@%d:%d writing 0x%lx size 0x%lx "
while (balance > 0) {
/* get the max amount to the copied */
/*
* copy into the temporary buffer the data
* to be written to the HV
*/
return (EFAULT);
}
/* write the data from the temporary buffer to the HV */
if (rv != 0) {
DWARN("i_vldc_ioctl_write_cookie: vldc@%d:%d "
"failed to write at address 0x%p\n, rv=%d",
return (EFAULT);
}
D2("i_vldc_ioctl_write_cookie: vldc@%d:%d write succeeded\n",
/* adjust len, source and dest */
}
/* set the structure to reflect outcome */
return (EFAULT);
}
return (0);
}
/* vldc specific ioctl option commands */
static int
{
int rv = 0;
return (EFAULT);
}
case VLDC_OPT_MTU_SZ:
return (EFAULT);
}
} else {
if ((new_mtu < LDC_PACKET_SIZE) ||
(new_mtu > vldc_max_mtu)) {
return (EINVAL);
}
/*
* The port has buffers allocated since it is
* not closed plus the MTU size has changed.
* Reallocate the buffers to the new MTU size.
*/
}
}
break;
case VLDC_OPT_STATUS:
return (EFAULT);
}
} else {
return (ENOTSUP);
}
break;
case VLDC_OPT_MODE:
return (EFAULT);
}
} else {
}
break;
default:
return (ENOTSUP);
}
return (rv);
}
/* cb_ioctl */
static int
int *rvalp)
{
int instance;
return (ENXIO);
}
if (portno == VLDC_INVALID_PORTNO) {
return (ENOLINK);
}
switch (cmd) {
case VLDC_IOCTL_OPT_OP:
break;
case VLDC_IOCTL_READ_COOKIE:
break;
}
break;
case VLDC_IOCTL_WRITE_COOKIE:
break;
}
break;
default:
DWARN("vldc_ioctl: vldc@%d:%lu unknown cmd=0x%x\n",
break;
}
}
return (rv);
}
/* cb_read */
static int
{
int instance;
int rv = 0;
return (ENXIO);
}
if (portno == VLDC_INVALID_PORTNO) {
return (ENOLINK);
}
/* check the port status */
DWARN("vldc_read: vldc@%d:%lu not in the ready state\n",
return (ENOTACTIVE);
}
/* read data */
D2("vldc_read: vldc@%d:%lu ldc_read size=%ld, rv=%d\n",
if (rv == 0) {
if (size != 0) {
} else {
rv = EWOULDBLOCK;
}
} else {
switch (rv) {
case ENOBUFS:
break;
case ETIMEDOUT:
case EWOULDBLOCK:
rv = EWOULDBLOCK;
break;
default:
rv = ECONNRESET;
break;
}
}
return (rv);
}
/* cb_write */
static int
{
int instance;
return (ENXIO);
}
if (portno == VLDC_INVALID_PORTNO) {
return (ENOLINK);
}
/* check the port status */
DWARN("vldc_write: vldc@%d:%lu not in the ready state\n",
return (ENOTACTIVE);
}
/* can only send MTU size at a time */
} else {
return (EMSGSIZE);
}
}
size);
if (rv == 0) {
&size);
if (rv != 0) {
DWARN("vldc_write: vldc@%d:%lu failed writing %lu "
}
} else {
size = 0;
}
/* resid is total number of bytes *not* sent */
return (rv);
}
/* cb_chpoll */
static int
{
int instance;
return (ENXIO);
}
if (portno == VLDC_INVALID_PORTNO) {
return (ENOLINK);
}
/* check the port status */
return (ENOTACTIVE);
}
D2("vldc_chpoll: vldc@%d:%lu polling events 0x%x\n",
*reventsp = 0;
/*
* Check if the receive queue is empty and if not, signal that
* there is data ready to read.
*/
haspkts) {
}
}
}
}
D2("vldc_chpoll: vldc@%d:%lu ev=0x%x, rev=0x%x\n",
return (0);
}