/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Media independent RPC-like comms
*/
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/ksynch.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/varargs.h>
#ifdef DS_DDICT
#include <sys/nsctl/contract.h>
#endif
#include "ncall.h"
#include "ncall_module.h"
#include <sys/nsctl/nsvers.h>
/*
* cb_ops functions.
*/
static int ncallioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static int ncallprint(dev_t, char *);
static struct cb_ops ncall_cb_ops = {
nulldev, /* open */
nulldev, /* close */
nulldev, /* strategy */
ncallprint,
nodev, /* dump */
nodev, /* read */
nodev, /* write */
ncallioctl,
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op,
NULL, /* NOT a stream */
D_NEW | D_MP | D_64BIT,
CB_REV,
nodev, /* aread */
nodev, /* awrite */
};
/*
* dev_ops functions.
*/
static int ncall_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ncall_attach(dev_info_t *, ddi_attach_cmd_t);
static int ncall_detach(dev_info_t *, ddi_detach_cmd_t);
static struct dev_ops ncall_ops = {
DEVO_REV,
0,
ncall_getinfo,
nulldev, /* identify */
nulldev, /* probe */
ncall_attach,
ncall_detach,
nodev, /* reset */
&ncall_cb_ops,
(struct bus_ops *)0,
NULL /* power */
};
/*
* Module linkage.
*/
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"nws:Kernel Call:" ISS_VERSION_STR,
&ncall_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
&modldrv,
0
};
typedef struct ncall_modinfo_s {
struct ncall_modinfo_s *next;
ncall_module_t *module;
} ncall_modinfo_t;
static dev_info_t *ncall_dip; /* Single DIP for driver */
static kmutex_t ncall_mutex;
static ncall_modinfo_t *ncall_modules;
static int ncall_active;
static ncall_node_t ncall_nodeinfo;
static int ncallgetnodes(intptr_t, int, int *);
extern void ncall_init_stub(void);
int
_init(void)
{
int error;
mutex_init(&ncall_mutex, NULL, MUTEX_DRIVER, NULL);
if ((error = mod_install(&modlinkage)) != 0) {
mutex_destroy(&ncall_mutex);
return (error);
}
return (0);
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) != 0)
return (error);
mutex_destroy(&ncall_mutex);
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
ncall_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
ncall_dip = dip;
if (ddi_create_minor_node(dip, "c,ncall", S_IFCHR,
0, DDI_PSEUDO, 0) != DDI_SUCCESS)
goto failed;
ddi_report_dev(dip);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
failed:
(void) ncall_detach(dip, DDI_DETACH);
return (DDI_FAILURE);
}
static int
ncall_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
/*
* If still active, then refuse to detach.
*/
if (ncall_modules != NULL || ncall_active)
return (DDI_FAILURE);
/*
* Remove all minor nodes.
*/
ddi_remove_minor_node(dip, NULL);
ncall_dip = NULL;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/* ARGSUSED */
static int
ncall_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int rc = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = ncall_dip;
rc = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
/*
* We only have a single instance.
*/
*result = 0;
rc = DDI_SUCCESS;
break;
default:
break;
}
return (rc);
}
/* ARGSUSED */
static int
ncallprint(dev_t dev, char *str)
{
cmn_err(CE_WARN, "%s%d: %s", ddi_get_name(ncall_dip),
ddi_get_instance(ncall_dip), str);
return (0);
}
int
ncall_register_module(ncall_module_t *mp, ncall_node_t *nodep)
{
ncall_modinfo_t *new;
int rc = 0;
if (mp == NULL || mp->ncall_version != NCALL_MODULE_VER)
return (EINVAL);
new = kmem_alloc(sizeof (*new), KM_SLEEP);
if (new != NULL) {
new->module = mp;
mutex_enter(&ncall_mutex);
new->next = ncall_modules;
ncall_modules = new;
mutex_exit(&ncall_mutex);
} else {
rc = ENOMEM;
}
*nodep = ncall_nodeinfo; /* structure copy */
return (rc);
}
int
ncall_unregister_module(ncall_module_t *mod)
{
ncall_modinfo_t **mpp;
int rc = ESRCH;
mutex_enter(&ncall_mutex);
for (mpp = &ncall_modules; *mpp != NULL; mpp = &((*mpp)->next)) {
if ((*mpp)->module == mod) {
*mpp = (*mpp)->next;
rc = 0;
break;
}
}
mutex_exit(&ncall_mutex);
return (rc);
}
static int
ncall_stop(void)
{
ncall_modinfo_t *mod;
int rc = 0;
mutex_enter(&ncall_mutex);
while ((rc == 0) && ((mod = ncall_modules) != NULL)) {
mutex_exit(&ncall_mutex);
rc = (*mod->module->ncall_stop)();
mutex_enter(&ncall_mutex);
}
mutex_exit(&ncall_mutex);
return (rc);
}
/* ARGSUSED */
static int ncallioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *crp, int *rvalp)
{
ncall_node_t node = { 0, };
int mirror;
int rc = 0;
*rvalp = 0;
if ((rc = drv_priv(crp)) != 0)
return (rc);
switch (cmd) {
case NC_IOC_START:
if (ncall_active) {
rc = EALREADY;
break;
}
if (ddi_copyin((void *)arg, &node, sizeof (node), mode) < 0)
return (EFAULT);
bcopy(&node, &ncall_nodeinfo, sizeof (ncall_nodeinfo));
ncall_init_stub();
ncall_active = 1;
break;
case NC_IOC_STOP:
ncall_active = 0;
rc = ncall_stop();
break;
case NC_IOC_GETNODE:
if (!ncall_active) {
rc = ENONET;
break;
}
if (ddi_copyout(&ncall_nodeinfo, (void *)arg,
sizeof (ncall_nodeinfo), mode) < 0) {
rc = EFAULT;
break;
}
mirror = ncall_mirror(ncall_nodeinfo.nc_nodeid);
/*
* can't return -1, as this will mask the ioctl
* failure, so return 0.
*/
if (mirror == -1)
mirror = 0;
*rvalp = mirror;
break;
case NC_IOC_GETNETNODES:
rc = ncallgetnodes(arg, mode, rvalp);
break;
case NC_IOC_PING:
if (!ncall_active) {
rc = ENONET;
break;
}
if (ddi_copyin((void *)arg, &node, sizeof (node), mode) < 0) {
rc = EFAULT;
break;
}
node.nc_nodename[sizeof (node.nc_nodename)-1] = '\0';
rc = ncall_ping(node.nc_nodename, rvalp);
break;
default:
rc = EINVAL;
break;
}
return (rc);
}
void
ncall_register_svc(int svc_id, void (*func)(ncall_t *, int *))
{
if (ncall_modules)
(*ncall_modules->module->ncall_register_svc)(svc_id, func);
}
void
ncall_unregister_svc(int svc_id)
{
if (ncall_modules)
(*ncall_modules->module->ncall_unregister_svc)(svc_id);
}
int
ncall_nodeid(char *nodename)
{
if (ncall_modules)
return ((ncall_modules->module->ncall_nodeid)(nodename));
else
return (0);
}
char *
ncall_nodename(int nodeid)
{
if (ncall_modules)
return ((*ncall_modules->module->ncall_nodename)(nodeid));
else
return ("unknown");
}
int
ncall_mirror(int nodeid)
{
if (ncall_modules)
return ((*ncall_modules->module->ncall_mirror)(nodeid));
else
return (-1);
}
int
ncall_self(void)
{
if (ncall_modules)
return ((*ncall_modules->module->ncall_self)());
else
return (-1);
}
int
ncall_alloc(int host_id, int flags, int net, ncall_t **ncall_p)
{
int rc = ENOLINK;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_alloc)(host_id,
flags, net, ncall_p);
return (rc);
}
int
ncall_timedsend(ncall_t *ncall, int flags, int svc_id,
struct timeval *t, ...)
{
va_list ap;
int rc = ENOLINK;
va_start(ap, t);
if (ncall_modules)
rc = (*ncall_modules->module->ncall_timedsend)(ncall, flags,
svc_id, t, ap);
va_end(ap);
return (rc);
}
int
ncall_timedsendnotify(ncall_t *ncall, int flags, int svc_id,
struct timeval *t, void (*ncall_callback)(ncall_t *, void *),
void *vptr, ...)
{
va_list ap;
int rc = ENOLINK;
va_start(ap, vptr);
if (ncall_modules)
rc = (*ncall_modules->module->ncall_timedsendnotify)(ncall,
flags, svc_id, t, ncall_callback, vptr, ap);
va_end(ap);
return (rc);
}
int
ncall_broadcast(ncall_t *ncall, int flags, int svc_id,
struct timeval *t, ...)
{
va_list ap;
int rc = ENOLINK;
va_start(ap, t);
if (ncall_modules)
rc = (*ncall_modules->module->ncall_broadcast)(ncall, flags,
svc_id, t, ap);
va_end(ap);
return (rc);
}
int
ncall_send(ncall_t *ncall, int flags, int svc_id, ...)
{
va_list ap;
int rc = ENOLINK;
va_start(ap, svc_id);
if (ncall_modules)
rc = (*ncall_modules->module->ncall_timedsend)(ncall, flags,
svc_id, NULL, ap);
va_end(ap);
return (rc);
}
int
ncall_read_reply(ncall_t *ncall, int n, ...)
{
va_list ap;
int rc = ENOLINK;
va_start(ap, n);
if (ncall_modules)
rc = (*ncall_modules->module->ncall_read_reply)(ncall, n, ap);
va_end(ap);
return (rc);
}
void
ncall_reset(ncall_t *ncall)
{
if (ncall_modules)
(*ncall_modules->module->ncall_reset)(ncall);
}
void
ncall_free(ncall_t *ncall)
{
if (ncall_modules)
(*ncall_modules->module->ncall_free)(ncall);
}
int
ncall_put_data(ncall_t *ncall, void *data, int len)
{
int rc = ENOLINK;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_put_data)(ncall, data, len);
return (rc);
}
int
ncall_get_data(ncall_t *ncall, void *data, int len)
{
int rc = ENOLINK;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_get_data)(ncall, data, len);
return (rc);
}
int
ncall_sender(ncall_t *ncall)
{
int rc = -1;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_sender)(ncall);
return (rc);
}
void
ncall_reply(ncall_t *ncall, ...)
{
va_list ap;
if (ncall_modules) {
va_start(ap, ncall);
(*ncall_modules->module->ncall_reply)(ncall, ap);
va_end(ap);
}
}
void
ncall_pend(ncall_t *ncall)
{
if (ncall_modules)
(*ncall_modules->module->ncall_pend)(ncall);
}
void
ncall_done(ncall_t *ncall)
{
if (ncall_modules)
(*ncall_modules->module->ncall_done)(ncall);
}
int
ncall_ping(char *nodename, int *up)
{
int rc = ENOLINK;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_ping)(nodename, up);
return (rc);
}
int
ncall_maxnodes()
{
int rc = 0;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_maxnodes)();
return (rc);
}
int
ncall_nextnode(void **vptr)
{
int rc = 0;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_nextnode)(vptr);
return (rc);
}
int
ncall_errcode(ncall_t *ncall, int *result)
{
int rc = ENOLINK;
if (ncall_modules)
rc = (*ncall_modules->module->ncall_errcode)(ncall, result);
return (rc);
}
static int
ncallgetnodes(intptr_t uaddr, int mode, int *rvalp)
{
ncall_node_t *nodelist;
int slot;
int rc;
int nodecnt;
int nodeid;
void *sequence;
char *nodename;
rc = 0;
nodecnt = ncall_maxnodes();
if (nodecnt <= 0) {
return (ENONET);
}
/*
* If the user passes up a null address argument, then
* he/she doesn't want the actual nodes, but the configured
* maximum, so space can be correctly allocated.
*/
if (uaddr == NULL) {
*rvalp = nodecnt;
return (0);
}
nodelist = kmem_zalloc(sizeof (*nodelist) * nodecnt, KM_SLEEP);
slot = 0;
sequence = NULL;
while ((nodeid = ncall_nextnode(&sequence)) > 0) {
nodename = ncall_nodename(nodeid);
/*
* There is a small window where nextnode can
* return a valid nodeid, and it being disabled
* which will get nodename to return "".
* Discard the nodeid if this happens.
*/
if (strlen(nodename) > 0) {
int size = sizeof (nodelist[slot].nc_nodename) - 1;
ASSERT(slot < nodecnt);
/*
* make sure its null terminated when it
* gets to userland.
*/
nodelist[slot].nc_nodename[size] = 0;
(void) strncpy(nodelist[slot].nc_nodename, nodename,
size);
nodelist[slot].nc_nodeid = nodeid;
slot++;
}
}
if (ddi_copyout(nodelist, (void *)uaddr, sizeof (*nodelist) * slot,
mode) < 0) {
rc = EFAULT;
} else {
/*
* tell them how many have come back.
*/
*rvalp = slot;
}
kmem_free(nodelist, sizeof (*nodelist) * nodecnt);
return (rc);
}