#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>
#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>
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);
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);
static void *vldc_ssp;
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 };
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,
vldc_close,
nodev,
nodev,
nodev,
vldc_read,
vldc_write,
vldc_ioctl,
nodev,
nodev,
ddi_segmap,
vldc_chpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP
};
static struct dev_ops vldc_ops = {
DEVO_REV,
0,
ddi_getinfo_1to1,
nulldev,
nulldev,
vldc_attach,
vldc_detach,
nodev,
&vldc_cb_ops,
(struct bus_ops *)NULL,
NULL,
ddi_quiesce_not_needed,
};
extern struct mod_ops mod_driverops;
static struct modldrv md = {
&mod_driverops,
"sun4v Virtual LDC Driver",
&vldc_ops,
};
static struct modlinkage ml = {
MODREV_1,
&md,
NULL
};
uint32_t vldc_max_mtu = VLDC_MAX_MTU;
uint64_t vldc_max_cookie = VLDC_MAX_COOKIE;
static clock_t vldc_close_delay = VLDC_CLOSE_DELAY;
#ifdef DEBUG
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
#define D1 if (0) printf
#define D2 if (0) printf
#define DWARN if (0) printf
#endif
int
_init(void)
{
int error;
error = ddi_soft_state_init(&vldc_ssp, sizeof (vldc_t), 1);
if (error != 0) {
return (error);
}
error = mod_install(&ml);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ml, modinfop));
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&ml)) == 0) {
ddi_soft_state_fini(&vldc_ssp);
}
return (error);
}
static uint_t
i_vldc_cb(uint64_t event, caddr_t arg)
{
int rv;
vldc_port_t *vport = (vldc_port_t *)arg;
ldc_status_t old_status;
short pollevents = 0;
ASSERT(vport != NULL);
ASSERT(vport->minorp != NULL);
D1("i_vldc_cb: vldc@%d:%d callback invoked, channel=0x%lx, "
"event=0x%lx\n", vport->inst, vport->number, vport->ldc_id, event);
mutex_enter(&vport->minorp->lock);
if (vport->status == VLDC_PORT_CLOSED) {
return (LDC_SUCCESS);
}
old_status = vport->ldc_status;
rv = ldc_status(vport->ldc_handle, &vport->ldc_status);
if (rv != 0) {
DWARN("i_vldc_cb: vldc@%d:%d could not get ldc status, "
"rv=%d\n", vport->inst, vport->number, rv);
mutex_exit(&vport->minorp->lock);
return (LDC_SUCCESS);
}
if (event & LDC_EVT_UP) {
pollevents |= POLLOUT;
vport->hanged_up = B_FALSE;
} else if (event & LDC_EVT_RESET) {
if (old_status == LDC_UP) {
vport->status = VLDC_PORT_RESET;
vport->hanged_up = B_TRUE;
pollevents = POLLHUP;
} else {
rv = ldc_up(vport->ldc_handle);
if (rv) {
DWARN("i_vldc_cb: vldc@%d:%d cannot bring "
"channel UP rv=%d\n", vport->inst,
vport->number, rv);
mutex_exit(&vport->minorp->lock);
return (LDC_SUCCESS);
}
rv = ldc_status(vport->ldc_handle, &vport->ldc_status);
if (rv != 0) {
DWARN("i_vldc_cb: vldc@%d:%d could not get "
"ldc status, rv=%d\n", vport->inst,
vport->number, rv);
mutex_exit(&vport->minorp->lock);
return (LDC_SUCCESS);
}
if (vport->ldc_status == LDC_UP) {
pollevents |= POLLOUT;
vport->hanged_up = B_FALSE;
}
}
} else if (event & LDC_EVT_DOWN) {
vport->status = VLDC_PORT_RESET;
vport->hanged_up = B_TRUE;
pollevents = POLLHUP;
}
if (event & LDC_EVT_READ)
pollevents |= POLLIN;
mutex_exit(&vport->minorp->lock);
if (pollevents != 0) {
D1("i_vldc_cb: port@%d pollwakeup=0x%x\n",
vport->number, pollevents);
pollwakeup(&vport->poll, pollevents);
}
return (LDC_SUCCESS);
}
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);
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);
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);
}
}
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);
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;
}
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);
}
}
mutex_exit(&vldcp->lock);
return (MDEG_SUCCESS);
}
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;
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);
}
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);
templatesz = sizeof (vldc_prop_template);
pspecp = kmem_alloc(templatesz, KM_SLEEP);
bcopy(vldc_prop_template, pspecp, templatesz);
namesz = strlen(nameprop) + 1;
name = kmem_alloc(namesz, KM_SLEEP);
bcopy(nameprop, name, namesz);
VLDC_SET_MDEG_PROP_NAME(pspecp, name);
ddi_prop_free(nameprop);
VLDC_SET_MDEG_PROP_INST(pspecp, inst);
inst_specp = kmem_alloc(sizeof (mdeg_node_spec_t), KM_SLEEP);
inst_specp->namep = "virtual-device";
inst_specp->specp = pspecp;
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);
}
vldcp->inst_spec = inst_specp;
vldcp->mdeg_hdl = mdeg_hdl;
return (DDI_SUCCESS);
}
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);
}
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;
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);
}
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);
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);
}
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;
ASSERT(MUTEX_HELD(&vldcp->lock));
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;
if (i_vldc_get_port_channel(mdp, node, &vport->ldc_id) == -1) {
return (MDEG_FAILURE);
}
vport->mtu = VLDC_DEFAULT_MTU;
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);
}
for (minor_idx = 0; minor_idx < vldcp->minors_assigned;
minor_idx++) {
if (strcmp(vldcp->minor_tbl[minor_idx].sname, sname) == 0) {
break;
}
}
new_minor = B_FALSE;
if (minor_idx == vldcp->minors_assigned) {
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;
}
if (vldcp->minor_tbl[minor_idx].portno != VLDC_INVALID_PORTNO) {
cmn_err(CE_NOTE, "?i_vldc_add_port: trying to add a port (%lu)"
" which has a minor number in use by port (%u)",
portno, vldcp->minor_tbl[minor_idx].portno);
return (MDEG_FAILURE);
}
vldc_inst = ddi_get_instance(vldcp->dip);
vport->inst = vldc_inst;
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: vldc@%d:%d mtu=%d, ldc=%ld, service=%s\n",
vport->inst, vport->number, vport->mtu, vport->ldc_id, sname);
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);
}
vport->status = VLDC_PORT_CLOSED;
D1("i_vldc_add_port: port %lu initialized\n", portno);
return (MDEG_SUCCESS);
}
static int
i_vldc_remove_port(vldc_t *vldcp, uint_t portno)
{
vldc_port_t *vport;
vldc_minor_t *vminor;
ASSERT(vldcp != NULL);
ASSERT(MUTEX_HELD(&vldcp->lock));
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);
}
mutex_enter(&vminor->lock);
vminor->portno = VLDC_INVALID_PORTNO;
mutex_exit(&vminor->lock);
pollwakeup(&vport->poll, POLLHUP);
mutex_enter(&vminor->lock);
while (vminor->in_use > 0) {
cv_wait(&vminor->cv, &vminor->lock);
}
if (vport->status != VLDC_PORT_CLOSED) {
(void) i_vldc_close_port(vldcp, portno);
}
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);
}
static int
i_vldc_ldc_close(vldc_port_t *vport)
{
int err = 0;
ASSERT(MUTEX_HELD(&vport->minorp->lock));
err = ldc_close(vport->ldc_handle);
if ((err != 0) && (err != EFAULT))
return (err);
err = ldc_unreg_callback(vport->ldc_handle);
if (err != 0)
return (err);
err = ldc_fini(vport->ldc_handle);
if (err != 0)
return (err);
vport->status = VLDC_PORT_OPEN;
return (0);
}
static int
i_vldc_close_port(vldc_t *vldcp, uint_t portno)
{
vldc_port_t *vport;
vldc_minor_t *vminor;
int rv = DDI_SUCCESS;
vport = &(vldcp->port[portno]);
ASSERT(MUTEX_HELD(&vport->minorp->lock));
D1("i_vldc_close_port: vldc@%d:%d: closing port\n",
vport->inst, vport->minorp->portno);
vminor = vport->minorp;
switch (vport->status) {
case VLDC_PORT_CLOSED:
DWARN("i_vldc_close_port: port %d in an unexpected "
"state (%d)\n", portno, vport->status);
return (DDI_SUCCESS);
case VLDC_PORT_READY:
case VLDC_PORT_RESET:
do {
rv = i_vldc_ldc_close(vport);
if (rv != EAGAIN)
break;
(void) cv_reltimedwait(&vminor->cv, &vminor->lock,
drv_usectohz(vldc_close_delay), TR_CLOCK_TICK);
rv = 0;
} while (vport->status != VLDC_PORT_CLOSED);
if ((rv != 0) || (vport->status == VLDC_PORT_CLOSED))
return (rv);
break;
case VLDC_PORT_OPEN:
break;
default:
DWARN("i_vldc_close_port: port %d in an unexpected "
"state (%d)\n", portno, vport->status);
ASSERT(0);
return (EINVAL);
}
ASSERT(vport->status == VLDC_PORT_OPEN);
kmem_free(vport->send_buf, vport->mtu);
kmem_free(vport->recv_buf, vport->mtu);
if (strcmp(vminor->sname, VLDC_HVCTL_SVCNAME) == 0)
kmem_free(vport->cookie_buf, vldc_max_cookie);
vport->status = VLDC_PORT_CLOSED;
return (rv);
}
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++) {
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);
vldcp->minor_tbl[i].portno = VLDC_INVALID_PORTNO;
}
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);
}
}
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);
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);
}
}
vldcp->detaching = B_TRUE;
mutex_exit(&vldcp->lock);
if (i_vldc_mdeg_unregister(vldcp) != MDEG_SUCCESS) {
vldcp->detaching = B_FALSE;
return (DDI_FAILURE);
}
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);
}
}
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);
if (strcmp(vport->minorp->sname, VLDC_HVCTL_SVCNAME) == 0)
vport->cookie_buf = kmem_alloc(vldc_max_cookie, KM_SLEEP);
vport->is_stream = B_FALSE;
vport->hanged_up = B_FALSE;
vport->status = VLDC_PORT_OPEN;
mutex_exit(&vminor->lock);
return (DDI_SUCCESS);
}
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));
switch (channel_mode) {
case LDC_MODE_RELIABLE:
vport->is_stream = B_TRUE;
break;
case LDC_MODE_RAW:
case LDC_MODE_UNRELIABLE:
vport->is_stream = B_FALSE;
break;
default:
return (EINVAL);
}
if (vport->status == VLDC_PORT_READY) {
rv = i_vldc_ldc_close(vport);
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;
attr.devclass = LDC_DEV_SERIAL;
attr.instance = ddi_get_instance(vldcp->dip);
attr.mtu = vport->mtu;
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;
}
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;
}
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;
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;
}
rv = ldc_status(vport->ldc_handle, &vport->ldc_status);
if (rv != 0) {
DWARN("vldc_ioctl_opt_op: vldc@%d:%d could not get ldc "
"status, rv=%d\n", vport->inst, vport->number, 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);
}
static int
i_vldc_ioctl_read_cookie(vldc_port_t *vport, int vldc_instance, void *arg,
int mode)
{
vldc_data_t copy_info;
uint64_t len, balance, copy_size;
caddr_t src_addr, dst_addr;
int rv;
if (ddi_copyin(arg, ©_info, sizeof (copy_info), mode) == -1) {
return (EFAULT);
}
len = balance = copy_info.length;
src_addr = (caddr_t)copy_info.src_addr;
dst_addr = (caddr_t)copy_info.dst_addr;
while (balance > 0) {
copy_size = MIN(balance, vldc_max_cookie);
mutex_enter(&vport->minorp->lock);
D2("i_vldc_ioctl_read_cookie: vldc@%d:%d reading from 0x%p "
"size 0x%lx to 0x%p\n", vldc_instance, vport->number,
dst_addr, copy_size, src_addr);
rv = ldc_mem_rdwr_cookie(vport->ldc_handle, vport->cookie_buf,
©_size, dst_addr, LDC_COPY_IN);
if (rv != 0) {
DWARN("i_vldc_ioctl_read_cookie: vldc@%d:%d cannot "
"read address 0x%p, rv=%d\n",
vldc_instance, vport->number, dst_addr, rv);
mutex_exit(&vport->minorp->lock);
return (EFAULT);
}
D2("i_vldc_ioctl_read_cookie: vldc@%d:%d read succeeded\n",
vldc_instance, vport->number);
mutex_exit(&vport->minorp->lock);
rv = ddi_copyout(vport->cookie_buf, src_addr, copy_size, mode);
if (rv != 0) {
return (EFAULT);
}
balance -= copy_size;
src_addr += copy_size;
dst_addr += copy_size;
}
copy_info.length = len;
if (ddi_copyout(©_info, arg, sizeof (copy_info), mode) != 0) {
return (EFAULT);
}
return (0);
}
static int
i_vldc_ioctl_write_cookie(vldc_port_t *vport, int vldc_instance, void *arg,
int mode)
{
vldc_data_t copy_info;
uint64_t len, balance, copy_size;
caddr_t src_addr, dst_addr;
int rv;
if (ddi_copyin(arg, ©_info, sizeof (copy_info), mode) != 0) {
return (EFAULT);
}
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);
len = balance = copy_info.length;
src_addr = (caddr_t)copy_info.src_addr;
dst_addr = (caddr_t)copy_info.dst_addr;
while (balance > 0) {
copy_size = MIN(balance, vldc_max_cookie);
if (ddi_copyin((caddr_t)src_addr, vport->cookie_buf,
copy_size, mode) != 0) {
return (EFAULT);
}
mutex_enter(&vport->minorp->lock);
rv = ldc_mem_rdwr_cookie(vport->ldc_handle, vport->cookie_buf,
©_size, dst_addr, LDC_COPY_OUT);
if (rv != 0) {
DWARN("i_vldc_ioctl_write_cookie: vldc@%d:%d "
"failed to write at address 0x%p\n, rv=%d",
vldc_instance, vport->number, dst_addr, rv);
mutex_exit(&vport->minorp->lock);
return (EFAULT);
}
D2("i_vldc_ioctl_write_cookie: vldc@%d:%d write succeeded\n",
vldc_instance, vport->number);
mutex_exit(&vport->minorp->lock);
balance -= copy_size;
src_addr += copy_size;
dst_addr += copy_size;
}
copy_info.length = len;
if (ddi_copyout(©_info, (caddr_t)arg,
sizeof (copy_info), mode) != 0) {
return (EFAULT);
}
return (0);
}
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)) {
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);
}
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:
if (strcmp(vport->minorp->sname, VLDC_HVCTL_SVCNAME)) {
rv = EINVAL;
break;
}
rv = i_vldc_ioctl_read_cookie(vport, instance,
(void *)arg, mode);
break;
case VLDC_IOCTL_WRITE_COOKIE:
if (strcmp(vport->minorp->sname, VLDC_HVCTL_SVCNAME)) {
rv = EINVAL;
break;
}
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);
}
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]);
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);
}
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);
}
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]);
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) {
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);
uiop->uio_resid = orig_size - size;
return (rv);
}
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;
boolean_t haspkts;
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]);
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);
*reventsp = 0;
if (vport->ldc_status == LDC_UP) {
if (events & POLLIN) {
if ((ldc_chkq(vport->ldc_handle, &haspkts) == 0) &&
haspkts) {
*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);
}